From e416a5092417e5cfae0f96dae98c7b976b693c3c Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 3 Sep 2019 12:03:07 +0200 Subject: [PATCH 001/257] MOBILE-3139 core: Unlock plugin and libraries versions --- config.xml | 42 ++++++------- package.json | 170 +++++++++++++++++++++++++-------------------------- 2 files changed, 106 insertions(+), 106 deletions(-) diff --git a/config.xml b/config.xml index 1ab1ad371..d1681d129 100644 --- a/config.xml +++ b/config.xml @@ -113,33 +113,33 @@ - - + + - - - - + + + + - - + + - - - - + + + + - - - - - - - - - + + + + + + + + + diff --git a/package.json b/package.json index 8d5fda2e1..caab7e861 100644 --- a/package.json +++ b/package.json @@ -40,102 +40,102 @@ "windows.store": "npx electron-windows-store --input-directory .\\desktop\\dist\\win-unpacked --output-directory .\\desktop\\store -a .\\resources\\desktop -m .\\desktop\\assets\\windows\\AppXManifest.xml --package-version 0.0.0.0 --package-name MoodleDesktop" }, "dependencies": { - "@angular/animations": "5.2.10", - "@angular/common": "5.2.10", - "@angular/compiler": "5.2.10", - "@angular/compiler-cli": "5.2.10", - "@angular/core": "5.2.10", - "@angular/forms": "5.2.10", - "@angular/http": "5.2.10", - "@angular/platform-browser": "5.2.10", - "@angular/platform-browser-dynamic": "5.2.10", - "@ionic-native/badge": "4.17.0", - "@ionic-native/camera": "4.17.0", - "@ionic-native/clipboard": "4.17.0", - "@ionic-native/core": "4.11.0", - "@ionic-native/device": "4.17.0", - "@ionic-native/file": "4.17.0", - "@ionic-native/file-opener": "4.17.0", - "@ionic-native/file-transfer": "4.17.0", - "@ionic-native/globalization": "4.17.0", - "@ionic-native/in-app-browser": "4.17.0", - "@ionic-native/keyboard": "4.17.0", - "@ionic-native/local-notifications": "4.17.0", - "@ionic-native/media-capture": "4.17.0", - "@ionic-native/network": "4.17.0", - "@ionic-native/push": "4.17.0", - "@ionic-native/screen-orientation": "4.17.0", - "@ionic-native/splash-screen": "4.17.0", - "@ionic-native/sqlite": "4.17.0", - "@ionic-native/status-bar": "4.17.0", - "@ionic-native/web-intent": "4.17.0", - "@ionic-native/zip": "4.17.0", - "@ngx-translate/core": "8.0.0", - "@ngx-translate/http-loader": "2.0.1", - "@types/cordova": "0.0.34", - "@types/cordova-plugin-file-transfer": "0.0.3", - "@types/cordova-plugin-globalization": "0.0.3", - "@types/cordova-plugin-network-information": "0.0.3", - "@types/node": "8.10.19", - "@types/promise.prototype.finally": "2.0.2", - "chart.js": "2.7.2", - "com-darryncampbell-cordova-plugin-intent": "1.1.7", + "@angular/animations": "^5.2.10", + "@angular/common": "^5.2.10", + "@angular/compiler": "^5.2.10", + "@angular/compiler-cli": "^5.2.10", + "@angular/core": "^5.2.10", + "@angular/forms": "^5.2.10", + "@angular/http": "^5.2.10", + "@angular/platform-browser": "^5.2.10", + "@angular/platform-browser-dynamic": "^5.2.10", + "@ionic-native/badge": "^4.17.0", + "@ionic-native/camera": "^4.17.0", + "@ionic-native/clipboard": "^4.17.0", + "@ionic-native/core": "^4.11.0", + "@ionic-native/device": "^4.17.0", + "@ionic-native/file": "^4.17.0", + "@ionic-native/file-opener": "^4.17.0", + "@ionic-native/file-transfer": "^4.17.0", + "@ionic-native/globalization": "^4.17.0", + "@ionic-native/in-app-browser": "^4.17.0", + "@ionic-native/keyboard": "^4.17.0", + "@ionic-native/local-notifications": "^4.17.0", + "@ionic-native/media-capture": "^4.17.0", + "@ionic-native/network": "^4.17.0", + "@ionic-native/push": "^4.17.0", + "@ionic-native/screen-orientation": "^4.17.0", + "@ionic-native/splash-screen": "^4.17.0", + "@ionic-native/sqlite": "^4.17.0", + "@ionic-native/status-bar": "^4.17.0", + "@ionic-native/web-intent": "^4.17.0", + "@ionic-native/zip": "^4.17.0", + "@ngx-translate/core": "^8.0.0", + "@ngx-translate/http-loader": "^2.0.1", + "@types/cordova": "^0.0.34", + "@types/cordova-plugin-file-transfer": "^0.0.3", + "@types/cordova-plugin-globalization": "^0.0.3", + "@types/cordova-plugin-network-information": "^0.0.3", + "@types/node": "^8.10.19", + "@types/promise.prototype.finally": "^2.0.2", + "chart.js": "^2.7.2", + "com-darryncampbell-cordova-plugin-intent": "^1.1.7", "cordova": "8.1.2", "cordova-android": "7.1.2", - "cordova-android-support-gradle-release": "3.0.1", - "cordova-clipboard": "1.3.0", + "cordova-android-support-gradle-release": "^3.0.1", + "cordova-clipboard": "^1.3.0", "cordova-ios": "4.5.5", - "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-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-transfer": "1.7.1", - "cordova-plugin-globalization": "1.11.0", - "cordova-plugin-inappbrowser": "3.1.0", - "cordova-plugin-ionic-keyboard": "2.1.3", + "cordova-plugin-file-transfer": "^1.7.1", + "cordova-plugin-globalization": "^1.11.0", + "cordova-plugin-inappbrowser": "^3.1.0", + "cordova-plugin-ionic-keyboard": "^2.1.3", "cordova-plugin-local-notification": "git+https://github.com/moodlemobile/cordova-plugin-local-notification.git#moodle", - "cordova-plugin-media-capture": "3.0.3", - "cordova-plugin-network-information": "2.0.2", - "cordova-plugin-screen-orientation": "3.0.2", - "cordova-plugin-splashscreen": "5.0.3", - "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-support-google-services": "1.2.1", - "es6-promise-plugin": "4.2.2", - "font-awesome": "4.7.0", + "cordova-plugin-media-capture": "^3.0.3", + "cordova-plugin-network-information": "^2.0.2", + "cordova-plugin-screen-orientation": "^3.0.2", + "cordova-plugin-splashscreen": "^5.0.3", + "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-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", - "moment": "2.22.2", - "nl.kingsquare.cordova.background-audio": "1.0.1", - "phonegap-plugin-multidex": "1.0.0", + "ionicons": "^3.0.0", + "jszip": "^3.1.5", + "moment": "^2.22.2", + "nl.kingsquare.cordova.background-audio": "^1.0.1", + "phonegap-plugin-multidex": "^1.0.0", "phonegap-plugin-push": "git+https://github.com/moodlemobile/phonegap-plugin-push.git#moodle-v3", - "promise.prototype.finally": "3.1.0", - "rxjs": "5.5.11", - "sw-toolbox": "3.6.0", - "ts-md5": "1.2.4", - "web-animations-js": "2.3.1", - "zone.js": "0.8.26" + "promise.prototype.finally": "^3.1.0", + "rxjs": "^5.5.11", + "sw-toolbox": "^3.6.0", + "ts-md5": "^1.2.4", + "web-animations-js": "^2.3.1", + "zone.js": "^0.8.26" }, "devDependencies": { "@ionic/app-scripts": "3.2.2", - "electron-builder-lib": "20.23.1", - "electron-rebuild": "1.8.1", + "electron-builder-lib": "^20.23.1", + "electron-rebuild": "^1.8.1", "gulp": "4.0.2", - "gulp-clip-empty-files": "0.1.2", - "gulp-concat": "2.6.1", - "gulp-flatten": "0.4.0", - "gulp-rename": "1.3.0", - "gulp-slash": "1.1.3", - "gulp-util": "3.0.8", - "node-loader": "0.6.0", - "through": "2.3.8", - "typescript": "2.6.2", - "webpack-merge": "4.1.2" + "gulp-clip-empty-files": "^0.1.2", + "gulp-concat": "^2.6.1", + "gulp-flatten": "^0.4.0", + "gulp-rename": "^1.3.0", + "gulp-slash": "^1.1.3", + "gulp-util": "^3.0.8", + "node-loader": "^0.6.0", + "through": "^2.3.8", + "typescript": "^2.6.2", + "webpack-merge": "^4.1.2" }, "browser": { "electron": false From e6d86042b623d7728cb964a0f8151d1548a7a62c Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 3 Sep 2019 12:05:26 +0200 Subject: [PATCH 002/257] MOBILE-3139 core: Update platform versions --- config.xml | 4 +- package-lock.json | 896 +++++++++++++++++++--------------------------- package.json | 4 +- 3 files changed, 379 insertions(+), 525 deletions(-) diff --git a/config.xml b/config.xml index d1681d129..43605fd7c 100644 --- a/config.xml +++ b/config.xml @@ -156,6 +156,6 @@ YES - - + + diff --git a/package-lock.json b/package-lock.json index c82b49deb..ee0b1bf29 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1198,8 +1198,7 @@ "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", @@ -1269,6 +1268,19 @@ "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-colors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-1.1.0.tgz", @@ -1805,8 +1817,7 @@ "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", @@ -1824,6 +1835,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", @@ -1919,6 +1935,22 @@ "type-is": "~1.6.16" } }, + "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", @@ -7261,212 +7293,27 @@ } }, "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 } } }, @@ -7491,297 +7338,84 @@ "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, - "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 - }, - "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" - } + "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-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": "3.0.6", - "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" - } - }, - "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": { - "version": "3.0.1", - "bundled": true - }, - "wrappy": { - "version": "1.0.2", - "bundled": true - }, - "xcode": { - "version": "0.9.3", - "bundled": true, - "requires": { - "pegjs": "^0.10.0", - "simple-plist": "^0.2.1", - "uuid": "3.0.1" - } - }, - "xml-escape": { - "version": "1.1.0", - "bundled": true - }, - "xmlbuilder": { - "version": "8.2.2", - "bundled": true - }, - "xmldom": { - "version": "0.1.27", - "bundled": true } } }, @@ -8037,6 +7671,16 @@ "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", "dev": true }, + "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=" + }, "default-compare": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", @@ -8122,6 +7766,11 @@ } } }, + "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", @@ -8534,6 +8183,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", @@ -8570,6 +8234,16 @@ "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", @@ -8952,6 +8626,11 @@ "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", "dev": true }, + "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", @@ -9346,6 +9025,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", @@ -9449,8 +9136,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", @@ -10007,7 +9693,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", @@ -11548,7 +11233,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" @@ -11592,6 +11276,32 @@ "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" + } + } + } + }, "ipaddr.js": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", @@ -11883,8 +11593,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", @@ -11972,7 +11681,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" } @@ -13000,6 +12708,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", @@ -13396,6 +13109,11 @@ } } }, + "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", @@ -13527,6 +13245,14 @@ } } }, + "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", @@ -13540,7 +13266,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -13584,8 +13309,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 +13333,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" @@ -13749,8 +13471,7 @@ "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,11 +13533,6 @@ "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", @@ -13861,7 +13577,6 @@ "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", @@ -13975,6 +13690,14 @@ "function-bind": "^1.1.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", @@ -14432,6 +14155,14 @@ "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", @@ -14874,8 +14605,7 @@ "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-greatest-satisfied-range": { "version": "1.1.0", @@ -14984,7 +14714,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,8 +14721,12 @@ "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", @@ -15001,6 +14734,32 @@ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true }, + "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", "resolved": "https://registry.npmjs.org/slash/-/slash-0.1.3.tgz", @@ -15307,6 +15066,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 +15113,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", @@ -15432,12 +15211,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", @@ -15478,8 +15315,7 @@ "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", @@ -15857,6 +15693,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", @@ -15905,8 +15746,12 @@ "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", @@ -16053,8 +15898,7 @@ "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", @@ -17353,7 +17197,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" } @@ -17407,8 +17250,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "ws": { "version": "3.3.2", @@ -17421,6 +17263,20 @@ "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" + } + }, + "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 +17290,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", diff --git a/package.json b/package.json index caab7e861..fd7498cd4 100644 --- a/package.json +++ b/package.json @@ -81,10 +81,10 @@ "chart.js": "^2.7.2", "com-darryncampbell-cordova-plugin-intent": "^1.1.7", "cordova": "8.1.2", - "cordova-android": "7.1.2", + "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", From 6053e1cc62e45b2a5c901f7dfa96017a7f300dbd Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 3 Sep 2019 12:14:23 +0200 Subject: [PATCH 003/257] MOBILE-3139 core: Fix missing peer dependency warning --- package-lock.json | 60 +++++++++++++++++++++++++++++++---------------- package.json | 1 + 2 files changed, 41 insertions(+), 20 deletions(-) diff --git a/package-lock.json b/package-lock.json index ee0b1bf29..4088d7a77 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1234,15 +1234,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": { @@ -8621,10 +8620,9 @@ } }, "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-json-parse": { "version": "1.0.3", @@ -8634,8 +8632,7 @@ "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", @@ -11651,10 +11648,9 @@ "dev": true }, "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", @@ -12777,6 +12773,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", @@ -12796,6 +12798,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": { @@ -12809,6 +12825,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", @@ -15815,7 +15837,6 @@ "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" }, @@ -15823,8 +15844,7 @@ "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==" } } }, diff --git a/package.json b/package.json index fd7498cd4..8f366c35e 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,7 @@ "@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", From ae83ae03548762e1ff68cce652372b6f1085dadd Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Wed, 4 Sep 2019 12:20:45 +0200 Subject: [PATCH 004/257] MOBILE-3133 android: Fix back button on fullscreen video --- MainActivity.java | 56 +++++++++++++++++++++++++++++++++++++++++++++++ config.xml | 1 + 2 files changed, 57 insertions(+) create mode 100755 MainActivity.java diff --git a/MainActivity.java b/MainActivity.java new file mode 100755 index 000000000..74acfbb07 --- /dev/null +++ b/MainActivity.java @@ -0,0 +1,56 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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/config.xml b/config.xml index 1ab1ad371..0c1b4bc5d 100644 --- a/config.xml +++ b/config.xml @@ -42,6 +42,7 @@ + From c3acfeacf7630a9a6437c419a84850585f95af37 Mon Sep 17 00:00:00 2001 From: sam marshall Date: Wed, 4 Sep 2019 15:18:12 +0100 Subject: [PATCH 005/257] MOBILE-3140 Blocks: Make blocks work if displaydata not specified If displaydata is not specified in mobile.php for a block, it did not work due to a JavaScript error. --- src/core/siteplugins/providers/helper.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/core/siteplugins/providers/helper.ts b/src/core/siteplugins/providers/helper.ts index b6c33d0fc..304c6626e 100644 --- a/src/core/siteplugins/providers/helper.ts +++ b/src/core/siteplugins/providers/helper.ts @@ -664,7 +664,8 @@ export class CoreSitePluginsHelperProvider { const uniqueName = this.sitePluginsProvider.getHandlerUniqueName(plugin, handlerName), blockName = (handlerSchema.moodlecomponent || plugin.component).replace('block_', ''), - prefixedTitle = this.getPrefixedString(plugin.addon, handlerSchema.displaydata.title || 'pluginname'); + titleString = (handlerSchema.displaydata && handlerSchema.displaydata.title) ? handlerSchema.displaydata.title : 'pluginname', + prefixedTitle = this.getPrefixedString(plugin.addon, titleString); this.blockDelegate.registerHandler( new CoreSitePluginsBlockHandler(uniqueName, prefixedTitle, blockName, handlerSchema, initResult)); From 06c0a266b79eeefd68c6bc6ea4c74238d0ec58e8 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 5 Sep 2019 09:23:44 +0200 Subject: [PATCH 006/257] MOBILE-3132 mod: Fill context menu even if there's an error --- src/addon/mod/assign/components/index/index.ts | 3 +-- src/addon/mod/book/components/index/index.ts | 5 ++--- src/addon/mod/chat/components/index/index.ts | 5 ++--- src/addon/mod/choice/components/index/index.ts | 3 +-- src/addon/mod/data/components/index/index.ts | 4 +--- src/addon/mod/folder/components/index/index.ts | 3 +-- src/addon/mod/forum/components/index/index.ts | 3 +-- src/addon/mod/glossary/components/index/index.ts | 3 +-- src/addon/mod/imscp/components/index/index.ts | 2 +- src/addon/mod/lesson/components/index/index.ts | 5 ++--- src/addon/mod/lti/components/index/index.ts | 3 +-- src/addon/mod/page/components/index/index.ts | 4 ++-- src/addon/mod/quiz/components/index/index.ts | 5 ++--- src/addon/mod/resource/components/index/index.ts | 3 +-- src/addon/mod/scorm/components/index/index.ts | 3 +-- src/addon/mod/survey/components/index/index.ts | 3 +-- src/addon/mod/wiki/components/index/index.ts | 3 +-- src/addon/mod/workshop/components/index/index.ts | 3 +-- 18 files changed, 23 insertions(+), 40 deletions(-) diff --git a/src/addon/mod/assign/components/index/index.ts b/src/addon/mod/assign/components/index/index.ts index 26d57888e..a576d75a5 100644 --- a/src/addon/mod/assign/components/index/index.ts +++ b/src/addon/mod/assign/components/index/index.ts @@ -219,8 +219,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo } }); }); - }).then(() => { - // All data obtained, now fill the context menu. + }).finally(() => { this.fillContextMenu(refresh); }); } diff --git a/src/addon/mod/book/components/index/index.ts b/src/addon/mod/book/components/index/index.ts index 569060aa3..56ed8c12e 100644 --- a/src/addon/mod/book/components/index/index.ts +++ b/src/addon/mod/book/components/index/index.ts @@ -163,12 +163,11 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp // We could load the main file but the download failed. Show error message. this.domUtils.showErrorModal('core.errordownloadingsomefiles', true); } - - // All data obtained, now fill the context menu. - this.fillContextMenu(refresh); }).catch(() => { // Ignore errors, they're handled inside the loadChapter function. }); + }).finally(() => { + this.fillContextMenu(refresh); }); } diff --git a/src/addon/mod/chat/components/index/index.ts b/src/addon/mod/chat/components/index/index.ts index 585c6f4e8..9b8f1f4ac 100644 --- a/src/addon/mod/chat/components/index/index.ts +++ b/src/addon/mod/chat/components/index/index.ts @@ -82,12 +82,11 @@ export class AddonModChatIndexComponent extends CoreCourseModuleMainActivityComp this.dataRetrieved.emit(chat); - // All data obtained, now fill the context menu. - this.fillContextMenu(refresh); - return this.chatProvider.areSessionsAvailable().then((available) => { this.sessionsAvailable = available; }); + }).finally(() => { + this.fillContextMenu(refresh); }); } diff --git a/src/addon/mod/choice/components/index/index.ts b/src/addon/mod/choice/components/index/index.ts index b543e4013..514dbae8a 100644 --- a/src/addon/mod/choice/components/index/index.ts +++ b/src/addon/mod/choice/components/index/index.ts @@ -154,8 +154,7 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo return this.fetchOptions(hasOffline).then(() => { return this.fetchResults(); }); - }).then(() => { - // All data obtained, now fill the context menu. + }).finally(() => { this.fillContextMenu(refresh); }); } diff --git a/src/addon/mod/data/components/index/index.ts b/src/addon/mod/data/components/index/index.ts index bc3463842..2bc9aab6f 100644 --- a/src/addon/mod/data/components/index/index.ts +++ b/src/addon/mod/data/components/index/index.ts @@ -247,12 +247,10 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp return this.fetchEntriesData(); }); - }).then(() => { - // All data obtained, now fill the context menu. - this.fillContextMenu(refresh); }).finally(() => { this.canAdd = canAdd; this.canSearch = canSearch; + this.fillContextMenu(refresh); }); } diff --git a/src/addon/mod/folder/components/index/index.ts b/src/addon/mod/folder/components/index/index.ts index d8c296b00..11e08acde 100644 --- a/src/addon/mod/folder/components/index/index.ts +++ b/src/addon/mod/folder/components/index/index.ts @@ -129,8 +129,7 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo } this.showModuleData(folder); - - // All data obtained, now fill the context menu. + }).finally(() => { this.fillContextMenu(refresh); }); } diff --git a/src/addon/mod/forum/components/index/index.ts b/src/addon/mod/forum/components/index/index.ts index b3a4209b6..78e4d4d84 100644 --- a/src/addon/mod/forum/components/index/index.ts +++ b/src/addon/mod/forum/components/index/index.ts @@ -244,8 +244,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); }); } diff --git a/src/addon/mod/glossary/components/index/index.ts b/src/addon/mod/glossary/components/index/index.ts index ed3d807e9..74296d771 100644 --- a/src/addon/mod/glossary/components/index/index.ts +++ b/src/addon/mod/glossary/components/index/index.ts @@ -158,8 +158,7 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity })); return Promise.all(promises); - }).then(() => { - // All data obtained, now fill the context menu. + }).finally(() => { this.fillContextMenu(refresh); }); } diff --git a/src/addon/mod/imscp/components/index/index.ts b/src/addon/mod/imscp/components/index/index.ts index 3b793da19..1c7dce1b2 100644 --- a/src/addon/mod/imscp/components/index/index.ts +++ b/src/addon/mod/imscp/components/index/index.ts @@ -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); }); } diff --git a/src/addon/mod/lesson/components/index/index.ts b/src/addon/mod/lesson/components/index/index.ts index b179e7993..65411e0f2 100644 --- a/src/addon/mod/lesson/components/index/index.ts +++ b/src/addon/mod/lesson/components/index/index.ts @@ -211,6 +211,8 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo this.lessonReady(refresh); } }); + }).finally(() => { + this.fillContextMenu(refresh); }); } @@ -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); } /** diff --git a/src/addon/mod/lti/components/index/index.ts b/src/addon/mod/lti/components/index/index.ts index f61c1e50f..600ca24b5 100644 --- a/src/addon/mod/lti/components/index/index.ts +++ b/src/addon/mod/lti/components/index/index.ts @@ -67,8 +67,7 @@ export class AddonModLtiIndexComponent extends CoreCourseModuleMainActivityCompo this.lti = ltiData; this.description = this.lti.intro || this.description; this.dataRetrieved.emit(this.lti); - }).then(() => { - // All data obtained, now fill the context menu. + }).finally(() => { this.fillContextMenu(refresh); }); } diff --git a/src/addon/mod/page/components/index/index.ts b/src/addon/mod/page/components/index/index.ts index 254bc0f38..bec1d8d23 100644 --- a/src/addon/mod/page/components/index/index.ts +++ b/src/addon/mod/page/components/index/index.ts @@ -127,8 +127,6 @@ export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComp // Get the page HTML. promises.push(this.pageHelper.getPageHtml(this.module.contents, this.module.id).then((content) => { - // All data obtained, now fill the context menu. - this.fillContextMenu(refresh); this.contents = content; @@ -139,6 +137,8 @@ export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComp })); return Promise.all(promises); + }).finally(() => { + this.fillContextMenu(refresh); }); } } diff --git a/src/addon/mod/quiz/components/index/index.ts b/src/addon/mod/quiz/components/index/index.ts index cabdb8406..c5b8ae1f7 100644 --- a/src/addon/mod/quiz/components/index/index.ts +++ b/src/addon/mod/quiz/components/index/index.ts @@ -224,11 +224,10 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp }); }).then(() => { - // All data obtained, now fill the context menu. - this.fillContextMenu(refresh); - // Quiz is ready to be shown, move it to the variable that is displayed. this.quiz = this.quizData; + }).finally(() => { + this.fillContextMenu(refresh); }); } diff --git a/src/addon/mod/resource/components/index/index.ts b/src/addon/mod/resource/components/index/index.ts index ab8ccffac..52ec1bcfc 100644 --- a/src/addon/mod/resource/components/index/index.ts +++ b/src/addon/mod/resource/components/index/index.ts @@ -135,8 +135,7 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource } else { this.mode = 'external'; } - }).then(() => { - // All data obtained, now fill the context menu. + }).finally(() => { this.fillContextMenu(refresh); }); } diff --git a/src/addon/mod/scorm/components/index/index.ts b/src/addon/mod/scorm/components/index/index.ts index 6672548b7..ea51cd2a4 100644 --- a/src/addon/mod/scorm/components/index/index.ts +++ b/src/addon/mod/scorm/components/index/index.ts @@ -254,8 +254,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom } }); }); - }).then(() => { - // All data obtained, now fill the context menu. + }).finally(() => { this.fillContextMenu(refresh); }); } diff --git a/src/addon/mod/survey/components/index/index.ts b/src/addon/mod/survey/components/index/index.ts index 8b6cfbb65..23b279d47 100644 --- a/src/addon/mod/survey/components/index/index.ts +++ b/src/addon/mod/survey/components/index/index.ts @@ -126,8 +126,7 @@ export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityCo if (!this.survey.surveydone && !this.hasOffline) { return this.fetchQuestions(); } - }).then(() => { - // All data obtained, now fill the context menu. + }).finally(() => { this.fillContextMenu(refresh); }); } diff --git a/src/addon/mod/wiki/components/index/index.ts b/src/addon/mod/wiki/components/index/index.ts index cb91ef072..79b2b9089 100644 --- a/src/addon/mod/wiki/components/index/index.ts +++ b/src/addon/mod/wiki/components/index/index.ts @@ -299,8 +299,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) { diff --git a/src/addon/mod/workshop/components/index/index.ts b/src/addon/mod/workshop/components/index/index.ts index 6e96123a2..7369f8a5e 100644 --- a/src/addon/mod/workshop/components/index/index.ts +++ b/src/addon/mod/workshop/components/index/index.ts @@ -231,8 +231,7 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity }); }).then(() => { return this.setPhaseInfo(); - }).then(() => { - // All data obtained, now fill the context menu. + }).finally(() => { this.fillContextMenu(refresh); }); } From 4e590b26dc8f0fe83d9fdafaa51c00a0fd7f8543 Mon Sep 17 00:00:00 2001 From: Mark Johnson Date: Wed, 11 Sep 2019 09:20:56 +0100 Subject: [PATCH 007/257] MOBILE-3143 Tabs: Prevent tabs disappearing when scrolling down. --- src/components/tabs/tabs.ts | 48 +++++++++++++++++-------------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/src/components/tabs/tabs.ts b/src/components/tabs/tabs.ts index ba7b9fdb1..5715c32bb 100644 --- a/src/components/tabs/tabs.ts +++ b/src/components/tabs/tabs.ts @@ -223,10 +223,8 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges, OnDe return; } - setTimeout(() => { - this.calculateMaxSlides(); - this.updateSlides(); - }); + this.calculateMaxSlides(); + this.updateSlides(); } /** @@ -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 { From b55cab45d71650a87372dce00f2e036dd7927003 Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Tue, 10 Sep 2019 16:29:25 +0200 Subject: [PATCH 008/257] MOBILE-3147 scripts: Script to remove jsdoc types --- scripts/remove-jsdocs-types.js | 71 ++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 scripts/remove-jsdocs-types.js 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) +}) From a7c2d22cb89fc428e433d7143b3270bc5cb37239 Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Tue, 10 Sep 2019 16:48:56 +0200 Subject: [PATCH 009/257] MOBILE-3147 doc: Remove jsdoc types from all typescript files --- .../badges/pages/issued-badge/issued-badge.ts | 4 +- .../badges/pages/user-badges/user-badges.ts | 6 +- .../badges/providers/badge-link-handler.ts | 20 +- src/addon/badges/providers/badges.ts | 26 +- .../badges/providers/mybadges-link-handler.ts | 20 +- .../badges/providers/push-click-handler.ts | 8 +- src/addon/badges/providers/user-handler.ts | 14 +- .../activitymodules/activitymodules.ts | 4 +- .../providers/block-handler.ts | 10 +- .../block/badges/providers/block-handler.ts | 10 +- .../block/blogmenu/providers/block-handler.ts | 10 +- .../blogrecent/providers/block-handler.ts | 10 +- .../block/blogtags/providers/block-handler.ts | 10 +- .../calendarmonth/providers/block-handler.ts | 10 +- .../providers/block-handler.ts | 10 +- .../block/comments/providers/block-handler.ts | 10 +- .../providers/block-handler.ts | 10 +- .../glossaryrandom/providers/block-handler.ts | 10 +- .../block/html/providers/block-handler.ts | 10 +- .../learningplans/providers/block-handler.ts | 10 +- .../components/myoverview/myoverview.ts | 14 +- .../myoverview/providers/block-handler.ts | 12 +- .../newsitems/providers/block-handler.ts | 10 +- .../onlineusers/providers/block-handler.ts | 10 +- .../privatefiles/providers/block-handler.ts | 10 +- .../recentactivity/providers/block-handler.ts | 10 +- .../recentlyaccessedcourses.ts | 6 +- .../providers/block-handler.ts | 10 +- .../recentlyaccesseditems.ts | 8 +- .../providers/block-handler.ts | 10 +- .../providers/recentlyaccesseditems.ts | 10 +- .../rssclient/providers/block-handler.ts | 10 +- .../selfcompletion/providers/block-handler.ts | 10 +- .../components/sitemainmenu/sitemainmenu.ts | 4 +- .../sitemainmenu/providers/block-handler.ts | 10 +- .../starredcourses/starredcourses.ts | 6 +- .../starredcourses/providers/block-handler.ts | 10 +- .../block/tags/providers/block-handler.ts | 10 +- .../timeline/components/events/events.ts | 10 +- .../timeline/components/timeline/timeline.ts | 16 +- .../block/timeline/providers/block-handler.ts | 12 +- .../block/timeline/providers/timeline.ts | 52 +- src/addon/blog/components/entries/entries.ts | 12 +- src/addon/blog/providers/blog.ts | 28 +- .../blog/providers/course-option-handler.ts | 30 +- .../blog/providers/index-link-handler.ts | 20 +- src/addon/blog/providers/mainmenu-handler.ts | 4 +- src/addon/blog/providers/tag-area-handler.ts | 10 +- src/addon/blog/providers/user-handler.ts | 14 +- .../calendar/components/calendar/calendar.ts | 26 +- .../upcoming-events/upcoming-events.ts | 20 +- src/addon/calendar/pages/day/day.ts | 50 +- .../calendar/pages/edit-event/edit-event.ts | 22 +- src/addon/calendar/pages/event/event.ts | 30 +- src/addon/calendar/pages/index/index.ts | 30 +- src/addon/calendar/pages/list/list.ts | 64 +- src/addon/calendar/pages/settings/settings.ts | 2 +- .../calendar/providers/calendar-offline.ts | 78 +- src/addon/calendar/providers/calendar-sync.ts | 28 +- src/addon/calendar/providers/calendar.ts | 426 ++++----- src/addon/calendar/providers/helper.ts | 64 +- .../calendar/providers/mainmenu-handler.ts | 4 +- .../calendar/providers/sync-cron-handler.ts | 8 +- .../calendar/providers/view-link-handler.ts | 20 +- .../competency/components/course/course.ts | 8 +- .../pages/competencies/competencies.ts | 6 +- .../competency/pages/competency/competency.ts | 6 +- .../competencysummary/competencysummary.ts | 6 +- src/addon/competency/pages/plan/plan.ts | 6 +- .../competency/pages/planlist/planlist.ts | 6 +- .../providers/competency-link-handler.ts | 20 +- src/addon/competency/providers/competency.ts | 174 ++-- .../providers/course-option-handler.ts | 30 +- src/addon/competency/providers/helper.ts | 10 +- .../competency/providers/mainmenu-handler.ts | 4 +- .../competency/providers/plan-link-handler.ts | 20 +- .../providers/plans-link-handler.ts | 20 +- .../providers/push-click-handler.ts | 8 +- .../providers/user-competency-link-handler.ts | 20 +- .../competency/providers/user-handler.ts | 14 +- .../components/report/report.ts | 4 +- .../providers/course-option-handler.ts | 28 +- .../providers/coursecompletion.ts | 50 +- .../providers/user-handler.ts | 14 +- src/addon/files/pages/list/list.ts | 6 +- src/addon/files/providers/files.ts | 100 +- src/addon/files/providers/helper.ts | 4 +- src/addon/files/providers/mainmenu-handler.ts | 4 +- .../airnotifier/pages/devices/devices.ts | 8 +- .../airnotifier/providers/airnotifier.ts | 20 +- .../airnotifier/providers/handler.ts | 6 +- src/addon/messageoutput/providers/delegate.ts | 14 +- .../confirmed-contacts/confirmed-contacts.ts | 16 +- .../contact-requests/contact-requests.ts | 16 +- .../messages/components/contacts/contacts.ts | 22 +- .../components/discussions/discussions.ts | 18 +- src/addon/messages/pages/contacts/contacts.ts | 6 +- .../conversation-info/conversation-info.ts | 16 +- .../messages/pages/discussion/discussion.ts | 86 +- .../group-conversations.ts | 68 +- src/addon/messages/pages/index/index.ts | 4 +- src/addon/messages/pages/search/search.ts | 16 +- src/addon/messages/pages/settings/settings.ts | 12 +- .../providers/contact-request-link-handler.ts | 20 +- .../providers/discussion-link-handler.ts | 20 +- .../messages/providers/index-link-handler.ts | 20 +- .../messages/providers/mainmenu-handler.ts | 32 +- .../messages/providers/messages-offline.ts | 86 +- src/addon/messages/providers/messages.ts | 715 +++++++------- .../messages/providers/push-click-handler.ts | 8 +- .../messages/providers/settings-handler.ts | 4 +- .../messages/providers/sync-cron-handler.ts | 8 +- src/addon/messages/providers/sync.ts | 44 +- .../providers/user-add-contact-handler.ts | 27 +- .../providers/user-block-contact-handler.ts | 23 +- .../providers/user-send-message-handler.ts | 14 +- .../assign/classes/base-feedback-handler.ts | 90 +- .../assign/classes/base-submission-handler.ts | 130 +-- .../classes/feedback-plugin-component.ts | 4 +- .../classes/submission-plugin-component.ts | 2 +- .../feedback-plugin/feedback-plugin.ts | 2 +- .../mod/assign/components/index/index.ts | 28 +- .../submission-plugin/submission-plugin.ts | 2 +- .../components/submission/submission.ts | 28 +- .../feedback/comments/component/comments.ts | 6 +- .../feedback/comments/providers/handler.ts | 94 +- .../feedback/editpdf/providers/handler.ts | 18 +- .../assign/feedback/file/providers/handler.ts | 18 +- .../edit-feedback-modal.ts | 10 +- src/addon/mod/assign/pages/edit/edit.ts | 14 +- src/addon/mod/assign/pages/index/index.ts | 2 +- .../pages/submission-list/submission-list.ts | 12 +- .../submission-review/submission-review.ts | 8 +- .../mod/assign/providers/assign-offline.ts | 136 +-- src/addon/mod/assign/providers/assign-sync.ts | 60 +- src/addon/mod/assign/providers/assign.ts | 376 ++++---- .../mod/assign/providers/feedback-delegate.ts | 191 ++-- src/addon/mod/assign/providers/helper.ts | 178 ++-- .../mod/assign/providers/list-link-handler.ts | 2 +- .../mod/assign/providers/module-handler.ts | 16 +- .../mod/assign/providers/prefetch-handler.ts | 94 +- .../assign/providers/push-click-handler.ts | 8 +- .../assign/providers/submission-delegate.ts | 271 +++--- .../mod/assign/providers/sync-cron-handler.ts | 8 +- .../submission/comments/component/comments.ts | 2 +- .../submission/comments/providers/handler.ts | 30 +- .../submission/file/providers/handler.ts | 120 +-- .../onlinetext/component/onlinetext.ts | 2 +- .../onlinetext/providers/handler.ts | 106 +-- src/addon/mod/book/components/index/index.ts | 16 +- src/addon/mod/book/pages/index/index.ts | 2 +- src/addon/mod/book/pages/toc/toc.ts | 2 +- src/addon/mod/book/providers/book.ts | 93 +- src/addon/mod/book/providers/link-handler.ts | 10 +- .../mod/book/providers/list-link-handler.ts | 10 +- .../mod/book/providers/module-handler.ts | 18 +- .../mod/book/providers/prefetch-handler.ts | 28 +- .../mod/book/providers/tag-area-handler.ts | 10 +- src/addon/mod/chat/components/index/index.ts | 8 +- src/addon/mod/chat/pages/chat/chat.ts | 16 +- src/addon/mod/chat/pages/index/index.ts | 2 +- .../session-messages/session-messages.ts | 10 +- src/addon/mod/chat/pages/sessions/sessions.ts | 12 +- src/addon/mod/chat/pages/users/users.ts | 4 +- src/addon/mod/chat/providers/chat.ts | 130 +-- .../mod/chat/providers/module-handler.ts | 16 +- .../mod/chat/providers/prefetch-handler.ts | 46 +- .../mod/choice/components/index/index.ts | 34 +- src/addon/mod/choice/pages/index/index.ts | 2 +- src/addon/mod/choice/providers/choice.ts | 146 +-- .../mod/choice/providers/module-handler.ts | 16 +- src/addon/mod/choice/providers/offline.ts | 44 +- .../mod/choice/providers/prefetch-handler.ts | 46 +- .../mod/choice/providers/sync-cron-handler.ts | 8 +- src/addon/mod/choice/providers/sync.ts | 34 +- .../data/classes/field-plugin-component.ts | 6 +- .../mod/data/components/action/action.ts | 2 +- src/addon/mod/data/components/index/index.ts | 32 +- .../fields/checkbox/component/checkbox.ts | 2 +- .../data/fields/checkbox/providers/handler.ts | 42 +- .../mod/data/fields/date/providers/handler.ts | 42 +- .../mod/data/fields/file/component/file.ts | 6 +- .../mod/data/fields/file/providers/handler.ts | 46 +- .../data/fields/latlong/component/latlong.ts | 14 +- .../data/fields/latlong/providers/handler.ts | 42 +- .../mod/data/fields/menu/providers/handler.ts | 42 +- .../fields/multimenu/component/multimenu.ts | 2 +- .../fields/multimenu/providers/handler.ts | 42 +- .../data/fields/number/providers/handler.ts | 20 +- .../data/fields/picture/component/picture.ts | 12 +- .../data/fields/picture/providers/handler.ts | 46 +- .../fields/radiobutton/providers/handler.ts | 42 +- .../mod/data/fields/text/providers/handler.ts | 42 +- .../fields/textarea/component/textarea.ts | 4 +- .../data/fields/textarea/providers/handler.ts | 34 +- .../mod/data/fields/url/providers/handler.ts | 18 +- src/addon/mod/data/pages/edit/edit.ts | 16 +- src/addon/mod/data/pages/entry/entry.ts | 24 +- src/addon/mod/data/pages/index/index.ts | 2 +- src/addon/mod/data/pages/search/search.ts | 12 +- .../data/providers/approve-link-handler.ts | 20 +- src/addon/mod/data/providers/data.ts | 350 +++---- .../data/providers/default-field-handler.ts | 40 +- .../mod/data/providers/delete-link-handler.ts | 20 +- .../mod/data/providers/edit-link-handler.ts | 20 +- .../mod/data/providers/fields-delegate.ts | 105 +-- src/addon/mod/data/providers/helper.ts | 190 ++-- .../mod/data/providers/list-link-handler.ts | 2 +- .../mod/data/providers/module-handler.ts | 16 +- src/addon/mod/data/providers/offline.ts | 100 +- .../mod/data/providers/prefetch-handler.ts | 92 +- .../mod/data/providers/show-link-handler.ts | 20 +- .../mod/data/providers/sync-cron-handler.ts | 8 +- src/addon/mod/data/providers/sync.ts | 48 +- .../mod/data/providers/tag-area-handler.ts | 10 +- .../mod/feedback/components/index/index.ts | 46 +- .../mod/feedback/pages/attempt/attempt.ts | 2 +- src/addon/mod/feedback/pages/form/form.ts | 10 +- src/addon/mod/feedback/pages/index/index.ts | 2 +- .../pages/nonrespondents/nonrespondents.ts | 12 +- .../feedback/pages/respondents/respondents.ts | 14 +- .../providers/analysis-link-handler.ts | 20 +- .../providers/complete-link-handler.ts | 20 +- src/addon/mod/feedback/providers/feedback.ts | 382 ++++---- src/addon/mod/feedback/providers/helper.ts | 96 +- .../feedback/providers/list-link-handler.ts | 2 +- .../mod/feedback/providers/module-handler.ts | 16 +- src/addon/mod/feedback/providers/offline.ts | 44 +- .../feedback/providers/prefetch-handler.ts | 62 +- .../feedback/providers/print-link-handler.ts | 20 +- .../feedback/providers/push-click-handler.ts | 8 +- .../providers/show-entries-link-handler.ts | 20 +- .../show-non-respondents-link-handler.ts | 20 +- .../feedback/providers/sync-cron-handler.ts | 8 +- src/addon/mod/feedback/providers/sync.ts | 46 +- .../mod/folder/components/index/index.ts | 8 +- src/addon/mod/folder/pages/index/index.ts | 2 +- src/addon/mod/folder/providers/folder.ts | 45 +- src/addon/mod/folder/providers/helper.ts | 4 +- .../mod/folder/providers/module-handler.ts | 16 +- .../folder/providers/pluginfile-handler.ts | 8 +- .../mod/folder/providers/prefetch-handler.ts | 26 +- src/addon/mod/forum/components/index/index.ts | 42 +- src/addon/mod/forum/components/post/post.ts | 16 +- .../mod/forum/pages/discussion/discussion.ts | 40 +- src/addon/mod/forum/pages/index/index.ts | 2 +- .../pages/new-discussion/new-discussion.ts | 30 +- .../sort-order-selector.ts | 2 +- .../providers/discussion-link-handler.ts | 22 +- src/addon/mod/forum/providers/forum.ts | 280 +++--- src/addon/mod/forum/providers/helper.ts | 136 ++- .../mod/forum/providers/index-link-handler.ts | 20 +- .../mod/forum/providers/module-handler.ts | 26 +- src/addon/mod/forum/providers/offline.ts | 156 +-- .../mod/forum/providers/post-link-handler.ts | 20 +- .../mod/forum/providers/prefetch-handler.ts | 64 +- .../mod/forum/providers/push-click-handler.ts | 8 +- .../mod/forum/providers/sync-cron-handler.ts | 8 +- src/addon/mod/forum/providers/sync.ts | 106 +-- .../mod/forum/providers/tag-area-handler.ts | 10 +- .../mod/glossary/components/index/index.ts | 40 +- .../components/mode-picker/mode-picker.ts | 6 +- src/addon/mod/glossary/pages/edit/edit.ts | 4 +- src/addon/mod/glossary/pages/entry/entry.ts | 8 +- src/addon/mod/glossary/pages/index/index.ts | 2 +- .../glossary/providers/edit-link-handler.ts | 20 +- .../glossary/providers/entry-link-handler.ts | 10 +- src/addon/mod/glossary/providers/glossary.ts | 420 ++++----- src/addon/mod/glossary/providers/helper.ts | 54 +- .../mod/glossary/providers/module-handler.ts | 18 +- src/addon/mod/glossary/providers/offline.ts | 84 +- .../glossary/providers/prefetch-handler.ts | 50 +- .../glossary/providers/sync-cron-handler.ts | 8 +- src/addon/mod/glossary/providers/sync.ts | 60 +- .../glossary/providers/tag-area-handler.ts | 10 +- src/addon/mod/imscp/components/index/index.ts | 12 +- src/addon/mod/imscp/pages/index/index.ts | 2 +- src/addon/mod/imscp/pages/toc/toc.ts | 6 +- src/addon/mod/imscp/providers/imscp.ts | 94 +- .../mod/imscp/providers/list-link-handler.ts | 2 +- .../mod/imscp/providers/module-handler.ts | 16 +- .../mod/imscp/providers/pluginfile-handler.ts | 8 +- .../mod/imscp/providers/prefetch-handler.ts | 38 +- src/addon/mod/label/providers/label.ts | 64 +- .../mod/label/providers/module-handler.ts | 18 +- .../mod/label/providers/prefetch-handler.ts | 20 +- .../mod/lesson/components/index/index.ts | 48 +- src/addon/mod/lesson/pages/index/index.ts | 2 +- .../mod/lesson/pages/menu-modal/menu-modal.ts | 3 +- .../pages/password-modal/password-modal.ts | 4 +- src/addon/mod/lesson/pages/player/player.ts | 46 +- .../lesson/pages/user-retake/user-retake.ts | 12 +- .../lesson/providers/grade-link-handler.ts | 22 +- src/addon/mod/lesson/providers/helper.ts | 44 +- .../lesson/providers/index-link-handler.ts | 32 +- .../mod/lesson/providers/lesson-offline.ts | 190 ++-- src/addon/mod/lesson/providers/lesson-sync.ts | 74 +- src/addon/mod/lesson/providers/lesson.ts | 891 +++++++++--------- .../mod/lesson/providers/list-link-handler.ts | 2 +- .../mod/lesson/providers/module-handler.ts | 16 +- .../mod/lesson/providers/prefetch-handler.ts | 88 +- .../lesson/providers/push-click-handler.ts | 8 +- .../lesson/providers/report-link-handler.ts | 48 +- .../mod/lesson/providers/sync-cron-handler.ts | 8 +- src/addon/mod/lti/components/index/index.ts | 10 +- src/addon/mod/lti/pages/index/index.ts | 2 +- src/addon/mod/lti/providers/lti.ts | 48 +- src/addon/mod/lti/providers/module-handler.ts | 16 +- src/addon/mod/page/components/index/index.ts | 6 +- src/addon/mod/page/pages/index/index.ts | 2 +- src/addon/mod/page/providers/helper.ts | 10 +- .../mod/page/providers/list-link-handler.ts | 2 +- .../mod/page/providers/module-handler.ts | 16 +- src/addon/mod/page/providers/page.ts | 49 +- .../mod/page/providers/pluginfile-handler.ts | 8 +- .../mod/page/providers/prefetch-handler.ts | 26 +- .../delaybetweenattempts/providers/handler.ts | 12 +- .../ipaddress/providers/handler.ts | 12 +- .../numattempts/providers/handler.ts | 12 +- .../offlineattempts/providers/handler.ts | 28 +- .../openclosedate/providers/handler.ts | 20 +- .../accessrules/password/providers/handler.ts | 72 +- .../safebrowser/providers/handler.ts | 12 +- .../securewindow/providers/handler.ts | 12 +- .../timelimit/providers/handler.ts | 24 +- src/addon/mod/quiz/classes/auto-save.ts | 40 +- src/addon/mod/quiz/components/index/index.ts | 34 +- src/addon/mod/quiz/pages/attempt/attempt.ts | 8 +- src/addon/mod/quiz/pages/index/index.ts | 2 +- .../navigation-modal/navigation-modal.ts | 5 +- src/addon/mod/quiz/pages/player/player.ts | 46 +- .../pages/preflight-modal/preflight-modal.ts | 2 +- src/addon/mod/quiz/pages/review/review.ts | 20 +- .../quiz/providers/access-rules-delegate.ts | 147 ++- .../mod/quiz/providers/grade-link-handler.ts | 10 +- src/addon/mod/quiz/providers/helper.ts | 90 +- .../mod/quiz/providers/index-link-handler.ts | 10 +- .../mod/quiz/providers/module-handler.ts | 16 +- .../mod/quiz/providers/prefetch-handler.ts | 124 +-- .../mod/quiz/providers/push-click-handler.ts | 8 +- src/addon/mod/quiz/providers/quiz-offline.ts | 90 +- src/addon/mod/quiz/providers/quiz-sync.ts | 78 +- src/addon/mod/quiz/providers/quiz.ts | 674 ++++++------- .../mod/quiz/providers/review-link-handler.ts | 22 +- .../mod/quiz/providers/sync-cron-handler.ts | 8 +- .../mod/resource/components/index/index.ts | 6 +- src/addon/mod/resource/pages/index/index.ts | 2 +- src/addon/mod/resource/providers/helper.ts | 30 +- .../resource/providers/list-link-handler.ts | 2 +- .../mod/resource/providers/module-handler.ts | 28 +- .../resource/providers/pluginfile-handler.ts | 8 +- .../resource/providers/prefetch-handler.ts | 42 +- src/addon/mod/resource/providers/resource.ts | 50 +- src/addon/mod/scorm/classes/data-model-12.ts | 82 +- src/addon/mod/scorm/components/index/index.ts | 48 +- src/addon/mod/scorm/pages/index/index.ts | 2 +- src/addon/mod/scorm/pages/player/player.ts | 20 +- src/addon/mod/scorm/pages/toc/toc.ts | 2 +- src/addon/mod/scorm/providers/helper.ts | 80 +- .../mod/scorm/providers/module-handler.ts | 16 +- .../mod/scorm/providers/pluginfile-handler.ts | 8 +- .../mod/scorm/providers/prefetch-handler.ts | 125 ++- .../mod/scorm/providers/scorm-offline.ts | 236 ++--- src/addon/mod/scorm/providers/scorm-sync.ts | 139 ++- src/addon/mod/scorm/providers/scorm.ts | 442 +++++---- .../mod/scorm/providers/sync-cron-handler.ts | 8 +- .../mod/survey/components/index/index.ts | 24 +- src/addon/mod/survey/pages/index/index.ts | 2 +- src/addon/mod/survey/providers/helper.ts | 12 +- .../mod/survey/providers/module-handler.ts | 16 +- src/addon/mod/survey/providers/offline.ts | 50 +- .../mod/survey/providers/prefetch-handler.ts | 48 +- src/addon/mod/survey/providers/survey.ts | 100 +- .../mod/survey/providers/sync-cron-handler.ts | 8 +- src/addon/mod/survey/providers/sync.ts | 34 +- src/addon/mod/url/components/index/index.ts | 12 +- src/addon/mod/url/pages/index/index.ts | 2 +- src/addon/mod/url/providers/helper.ts | 2 +- src/addon/mod/url/providers/module-handler.ts | 26 +- src/addon/mod/url/providers/url.ts | 54 +- src/addon/mod/wiki/components/index/index.ts | 74 +- .../subwiki-picker/subwiki-picker.ts | 6 +- src/addon/mod/wiki/pages/edit/edit.ts | 22 +- src/addon/mod/wiki/pages/index/index.ts | 2 +- .../mod/wiki/providers/create-link-handler.ts | 18 +- .../mod/wiki/providers/edit-link-handler.ts | 10 +- .../mod/wiki/providers/module-handler.ts | 16 +- .../providers/page-or-map-link-handler.ts | 20 +- .../mod/wiki/providers/prefetch-handler.ts | 68 +- .../mod/wiki/providers/sync-cron-handler.ts | 8 +- .../mod/wiki/providers/tag-area-handler.ts | 10 +- src/addon/mod/wiki/providers/wiki-offline.ts | 76 +- src/addon/mod/wiki/providers/wiki-sync.ts | 64 +- src/addon/mod/wiki/providers/wiki.ts | 309 +++--- .../accumulative/providers/handler.ts | 24 +- .../assessment/comments/providers/handler.ts | 24 +- .../assessment/numerrors/providers/handler.ts | 24 +- .../assessment/rubric/providers/handler.ts | 24 +- .../assessment-strategy.ts | 8 +- .../mod/workshop/components/index/index.ts | 34 +- .../workshop/pages/assessment/assessment.ts | 12 +- .../pages/edit-submission/edit-submission.ts | 12 +- src/addon/mod/workshop/pages/index/index.ts | 2 +- src/addon/mod/workshop/pages/phase/phase.ts | 2 +- .../workshop/pages/submission/submission.ts | 16 +- .../providers/assessment-strategy-delegate.ts | 57 +- src/addon/mod/workshop/providers/helper.ts | 212 ++--- .../workshop/providers/list-link-handler.ts | 2 +- .../mod/workshop/providers/module-handler.ts | 16 +- src/addon/mod/workshop/providers/offline.ts | 230 ++--- .../workshop/providers/prefetch-handler.ts | 72 +- .../workshop/providers/sync-cron-handler.ts | 8 +- src/addon/mod/workshop/providers/sync.ts | 70 +- src/addon/mod/workshop/providers/workshop.ts | 540 +++++------ src/addon/notes/components/list/list.ts | 26 +- src/addon/notes/pages/add/add.ts | 2 +- .../notes/providers/course-option-handler.ts | 20 +- src/addon/notes/providers/notes-offline.ts | 104 +- src/addon/notes/providers/notes-sync.ts | 24 +- src/addon/notes/providers/notes.ts | 120 +-- .../notes/providers/sync-cron-handler.ts | 8 +- src/addon/notes/providers/user-handler.ts | 16 +- .../components/actions/actions.ts | 4 +- src/addon/notifications/pages/list/list.ts | 12 +- .../notifications/pages/settings/settings.ts | 16 +- .../notifications/providers/cron-handler.ts | 22 +- src/addon/notifications/providers/helper.ts | 14 +- .../providers/mainmenu-handler.ts | 6 +- .../notifications/providers/notifications.ts | 104 +- .../providers/push-click-handler.ts | 8 +- .../providers/settings-handler.ts | 4 +- .../qbehaviour/adaptive/providers/handler.ts | 10 +- .../adaptivenopenalty/providers/handler.ts | 10 +- .../deferredcbm/providers/handler.ts | 38 +- .../deferredfeedback/providers/handler.ts | 44 +- .../immediatecbm/providers/handler.ts | 10 +- .../immediatefeedback/providers/handler.ts | 10 +- .../informationitem/providers/handler.ts | 20 +- .../interactive/providers/handler.ts | 10 +- .../interactivecountback/providers/handler.ts | 10 +- .../manualgraded/providers/handler.ts | 44 +- .../qtype/calculated/providers/handler.ts | 40 +- .../calculatedmulti/providers/handler.ts | 28 +- .../calculatedsimple/providers/handler.ts | 28 +- .../ddimageortext/classes/ddimageortext.ts | 52 +- .../qtype/ddimageortext/providers/handler.ts | 34 +- src/addon/qtype/ddmarker/classes/ddmarker.ts | 116 +-- .../qtype/ddmarker/classes/graphics_api.ts | 10 +- src/addon/qtype/ddmarker/providers/handler.ts | 40 +- src/addon/qtype/ddwtos/classes/ddwtos.ts | 66 +- src/addon/qtype/ddwtos/providers/handler.ts | 34 +- .../qtype/description/providers/handler.ts | 20 +- src/addon/qtype/essay/providers/handler.ts | 48 +- .../qtype/gapselect/providers/handler.ts | 34 +- src/addon/qtype/match/providers/handler.ts | 34 +- .../qtype/multianswer/providers/handler.ts | 40 +- .../qtype/multichoice/providers/handler.ts | 52 +- .../qtype/randomsamatch/providers/handler.ts | 28 +- .../qtype/shortanswer/providers/handler.ts | 28 +- .../qtype/truefalse/providers/handler.ts | 38 +- .../remotethemes/providers/remotethemes.ts | 42 +- .../pages/course-storage/course-storage.ts | 6 +- .../providers/coursemenu-handler.ts | 18 +- .../checkbox/providers/handler.ts | 16 +- .../datetime/providers/handler.ts | 16 +- .../menu/providers/handler.ts | 16 +- .../text/providers/handler.ts | 16 +- .../textarea/providers/handler.ts | 16 +- src/app/app.component.ts | 4 +- src/classes/base-sync.ts | 87 +- src/classes/cache.ts | 22 +- src/classes/delegate.ts | 91 +- src/classes/interceptor.ts | 6 +- src/classes/site.ts | 329 +++---- src/classes/sqlitedb.ts | 330 +++---- src/components/attachments/attachments.ts | 8 +- src/components/chart/chart.ts | 4 +- .../context-menu/context-menu-item.ts | 6 +- .../context-menu/context-menu-popover.ts | 6 +- src/components/context-menu/context-menu.ts | 8 +- .../course-picker-menu-popover.ts | 6 +- .../download-refresh/download-refresh.ts | 4 +- .../dynamic-component/dynamic-component.ts | 8 +- src/components/file/file.ts | 10 +- src/components/icon/icon.ts | 4 +- src/components/iframe/iframe.ts | 2 - .../infinite-loading/infinite-loading.ts | 10 +- src/components/ion-tabs/ion-tabs.ts | 31 +- src/components/local-file/local-file.ts | 10 +- .../navbar-buttons/navbar-buttons.ts | 14 +- src/components/recaptcha/recaptchamodal.ts | 2 +- .../rich-text-editor/rich-text-editor.ts | 38 +- src/components/search-box/search-box.ts | 2 +- .../send-message-form/send-message-form.ts | 6 +- src/components/show-password/show-password.ts | 2 +- src/components/split-view/split-view.ts | 18 +- src/components/style/style.ts | 6 +- src/components/tabs/tab.ts | 4 +- src/components/tabs/tabs.ts | 14 +- .../block/classes/base-block-component.ts | 24 +- src/core/block/classes/base-block-handler.ts | 12 +- src/core/block/components/block/block.ts | 10 +- .../components/course-blocks/course-blocks.ts | 4 +- src/core/block/providers/delegate.ts | 53 +- src/core/block/providers/helper.ts | 6 +- .../comments/components/comments/comments.ts | 10 +- src/core/comments/pages/add/add.ts | 2 +- src/core/comments/pages/viewer/viewer.ts | 32 +- src/core/comments/providers/comments.ts | 148 +-- src/core/comments/providers/offline.ts | 116 +-- .../comments/providers/sync-cron-handler.ts | 8 +- src/core/comments/providers/sync.ts | 52 +- .../components/compile-html/compile-html.ts | 12 +- src/core/compile/providers/compile.ts | 30 +- src/core/contentlinks/classes/base-handler.ts | 33 +- .../classes/module-grade-handler.ts | 34 +- .../classes/module-index-handler.ts | 17 +- .../classes/module-list-handler.ts | 17 +- .../pages/choose-site/choose-site.ts | 2 +- src/core/contentlinks/providers/delegate.ts | 77 +- src/core/contentlinks/providers/helper.ts | 56 +- .../classes/activity-prefetch-handler.ts | 64 +- src/core/course/classes/activity-sync.ts | 10 +- .../course/classes/main-activity-component.ts | 56 +- .../course/classes/main-resource-component.ts | 28 +- .../course/classes/module-prefetch-handler.ts | 128 ++- .../classes/resource-prefetch-handler.ts | 54 +- src/core/course/components/format/format.ts | 38 +- .../module-completion/module-completion.ts | 2 +- src/core/course/components/module/module.ts | 10 +- .../course/components/tag-area/tag-area.ts | 2 +- .../components/singleactivity.ts | 8 +- .../singleactivity/providers/handler.ts | 36 +- .../formats/topics/providers/handler.ts | 2 +- .../course/formats/weeks/providers/handler.ts | 14 +- .../pages/list-mod-type/list-mod-type.ts | 4 +- .../section-selector/section-selector.ts | 2 +- src/core/course/pages/section/section.ts | 18 +- src/core/course/providers/course-offline.ts | 34 +- .../providers/course-tag-area-handler.ts | 10 +- src/core/course/providers/course.ts | 278 +++--- src/core/course/providers/default-format.ts | 48 +- src/core/course/providers/default-module.ts | 20 +- src/core/course/providers/format-delegate.ts | 159 ++-- src/core/course/providers/helper.ts | 316 +++---- src/core/course/providers/log-cron-handler.ts | 10 +- src/core/course/providers/log-helper.ts | 124 +-- src/core/course/providers/module-delegate.ts | 116 +-- .../providers/module-prefetch-delegate.ts | 343 ++++--- .../providers/modules-tag-area-handler.ts | 10 +- src/core/course/providers/options-delegate.ts | 176 ++-- .../course/providers/sync-cron-handler.ts | 8 +- src/core/course/providers/sync.ts | 24 +- .../course-list-item/course-list-item.ts | 2 +- .../course-options-menu.ts | 2 +- .../course-progress/course-progress.ts | 12 +- .../components/my-courses/my-courses.ts | 8 +- .../available-courses/available-courses.ts | 4 +- .../courses/pages/categories/categories.ts | 6 +- .../pages/course-preview/course-preview.ts | 22 +- src/core/courses/pages/dashboard/dashboard.ts | 8 +- src/core/courses/pages/search/search.ts | 6 +- .../self-enrol-password.ts | 4 +- .../courses/providers/course-link-handler.ts | 48 +- .../providers/courses-index-link-handler.ts | 10 +- src/core/courses/providers/courses.ts | 300 +++--- .../providers/dashboard-link-handler.ts | 20 +- src/core/courses/providers/dashboard.ts | 28 +- .../providers/enrol-push-click-handler.ts | 8 +- src/core/courses/providers/helper.ts | 28 +- .../courses/providers/mainmenu-handler.ts | 8 +- .../providers/request-push-click-handler.ts | 8 +- src/core/emulator/classes/filesystem.ts | 104 +- .../emulator/classes/inappbrowserobject.ts | 18 +- src/core/emulator/classes/sqlitedb.ts | 18 +- .../pages/capture-media/capture-media.ts | 10 +- src/core/emulator/providers/badge.ts | 19 +- src/core/emulator/providers/camera.ts | 6 +- src/core/emulator/providers/capture-helper.ts | 16 +- src/core/emulator/providers/clipboard.ts | 6 +- src/core/emulator/providers/file-opener.ts | 14 +- src/core/emulator/providers/file-transfer.ts | 44 +- src/core/emulator/providers/file.ts | 228 +++-- src/core/emulator/providers/globalization.ts | 36 +- src/core/emulator/providers/helper.ts | 36 +- src/core/emulator/providers/inappbrowser.ts | 8 +- .../emulator/providers/local-notifications.ts | 167 ++-- src/core/emulator/providers/media-capture.ts | 12 +- src/core/emulator/providers/network.ts | 6 +- src/core/emulator/providers/push.ts | 22 +- src/core/emulator/providers/zip.ts | 14 +- .../fileuploader/providers/album-handler.ts | 8 +- .../fileuploader/providers/audio-handler.ts | 8 +- .../fileuploader/providers/camera-handler.ts | 8 +- src/core/fileuploader/providers/delegate.ts | 39 +- .../fileuploader/providers/file-handler.ts | 8 +- .../fileuploader/providers/fileuploader.ts | 113 ++- src/core/fileuploader/providers/helper.ts | 168 ++-- .../fileuploader/providers/video-handler.ts | 8 +- src/core/grades/components/course/course.ts | 8 +- src/core/grades/pages/courses/courses.ts | 6 +- src/core/grades/pages/grade/grade.ts | 4 +- .../grades/providers/course-option-handler.ts | 30 +- src/core/grades/providers/grades.ts | 112 +-- src/core/grades/providers/helper.ts | 116 +-- src/core/grades/providers/mainmenu-handler.ts | 4 +- .../grades/providers/overview-link-handler.ts | 20 +- src/core/grades/providers/user-handler.ts | 24 +- .../grades/providers/user-link-handler.ts | 22 +- .../login/pages/credentials/credentials.ts | 8 +- .../login/pages/email-signup/email-signup.ts | 14 +- .../forgotten-password/forgotten-password.ts | 2 +- src/core/login/pages/init/init.ts | 2 +- src/core/login/pages/reconnect/reconnect.ts | 6 +- .../login/pages/site-policy/site-policy.ts | 2 +- src/core/login/pages/site/site.ts | 10 +- src/core/login/pages/sites/sites.ts | 6 +- src/core/login/providers/helper.ts | 250 +++-- src/core/mainmenu/pages/menu/menu.ts | 2 +- src/core/mainmenu/pages/more/more.ts | 4 +- src/core/mainmenu/providers/delegate.ts | 19 +- src/core/mainmenu/providers/mainmenu.ts | 24 +- .../pushnotifications/providers/delegate.ts | 39 +- .../providers/pushnotifications.ts | 115 ++- .../providers/register-cron-handler.ts | 10 +- .../providers/unregister-cron-handler.ts | 6 +- .../classes/base-behaviour-handler.ts | 20 +- .../classes/base-question-component.ts | 16 +- .../question/classes/base-question-handler.ts | 54 +- .../question/components/question/question.ts | 2 +- .../question/providers/behaviour-delegate.ts | 41 +- src/core/question/providers/delegate.ts | 129 ++- src/core/question/providers/helper.ts | 112 +-- src/core/question/providers/question.ts | 167 ++-- src/core/rating/pages/ratings/ratings.ts | 4 +- src/core/rating/providers/offline.ts | 94 +- src/core/rating/providers/rating.ts | 128 +-- src/core/rating/providers/sync.ts | 56 +- src/core/settings/pages/list/list.ts | 4 +- .../settings/pages/space-usage/space-usage.ts | 16 +- .../pages/synchronization/synchronization.ts | 6 +- src/core/settings/providers/delegate.ts | 10 +- src/core/settings/providers/helper.ts | 24 +- .../pages/choose-site/choose-site.ts | 2 +- src/core/sharedfiles/pages/list/list.ts | 16 +- src/core/sharedfiles/providers/helper.ts | 34 +- src/core/sharedfiles/providers/sharedfiles.ts | 42 +- .../sharedfiles/providers/upload-handler.ts | 8 +- src/core/sitehome/components/index/index.ts | 4 +- .../sitehome/providers/index-link-handler.ts | 20 +- src/core/sitehome/providers/sitehome.ts | 20 +- .../classes/call-ws-click-directive.ts | 2 +- .../siteplugins/classes/call-ws-directive.ts | 8 +- .../classes/compile-init-component.ts | 4 +- .../handlers/assign-feedback-handler.ts | 12 +- .../handlers/assign-submission-handler.ts | 14 +- .../classes/handlers/base-handler.ts | 2 +- .../classes/handlers/block-handler.ts | 10 +- .../classes/handlers/course-format-handler.ts | 18 +- .../classes/handlers/course-option-handler.ts | 28 +- .../classes/handlers/main-menu-handler.ts | 2 +- .../handlers/message-output-handler.ts | 2 +- .../classes/handlers/module-handler.ts | 18 +- .../handlers/module-prefetch-handler.ts | 68 +- .../handlers/question-behaviour-handler.ts | 8 +- .../classes/handlers/question-handler.ts | 6 +- .../handlers/quiz-access-rule-handler.ts | 58 +- .../classes/handlers/settings-handler.ts | 2 +- .../classes/handlers/user-handler.ts | 20 +- .../handlers/user-profile-field-handler.ts | 14 +- .../workshop-assessment-strategy-handler.ts | 28 +- .../assign-feedback/assign-feedback.ts | 2 +- .../assign-submission/assign-submission.ts | 2 +- .../components/course-format/course-format.ts | 8 +- .../components/course-option/course-option.ts | 2 +- .../components/module-index/module-index.ts | 12 +- .../plugin-content/plugin-content.ts | 34 +- .../directives/call-ws-new-content.ts | 2 +- src/core/siteplugins/directives/call-ws.ts | 2 +- .../pages/module-index/module-index.ts | 4 +- .../pages/plugin-page/plugin-page.ts | 4 +- src/core/siteplugins/providers/helper.ts | 222 ++--- src/core/siteplugins/providers/siteplugins.ts | 162 ++-- src/core/tag/pages/index-area/index-area.ts | 10 +- src/core/tag/pages/index/index.ts | 6 +- src/core/tag/pages/search/search.ts | 10 +- src/core/tag/providers/area-delegate.ts | 31 +- src/core/tag/providers/helper.ts | 4 +- src/core/tag/providers/index-link-handler.ts | 22 +- src/core/tag/providers/mainmenu-handler.ts | 4 +- src/core/tag/providers/search-link-handler.ts | 22 +- src/core/tag/providers/tag.ts | 120 +-- .../components/participants/participants.ts | 12 +- src/core/user/pages/about/about.ts | 2 +- src/core/user/pages/profile/profile.ts | 6 +- .../user/providers/course-option-handler.ts | 36 +- src/core/user/providers/helper.ts | 12 +- src/core/user/providers/offline.ts | 14 +- .../providers/participants-link-handler.ts | 20 +- src/core/user/providers/sync-cron-handler.ts | 8 +- src/core/user/providers/sync.ts | 8 +- src/core/user/providers/tag-area-handler.ts | 10 +- src/core/user/providers/user-delegate.ts | 49 +- src/core/user/providers/user-handler.ts | 14 +- src/core/user/providers/user-link-handler.ts | 20 +- .../providers/user-profile-field-delegate.ts | 46 +- src/core/user/providers/user.ts | 172 ++-- src/directives/auto-rows.ts | 2 +- src/directives/external-content.ts | 14 +- src/directives/format-text.ts | 37 +- src/directives/link.ts | 2 +- src/directives/supress-events.ts | 2 +- src/pipes/bytes-to-size.ts | 4 +- src/pipes/create-links.ts | 4 +- src/pipes/date-day-or-time.ts | 4 +- src/pipes/duration.ts | 4 +- src/pipes/format-date.ts | 10 +- src/pipes/no-tags.ts | 4 +- src/pipes/seconds-to-hms.ts | 4 +- src/pipes/time-ago.ts | 4 +- src/pipes/to-locale-string.ts | 4 +- src/providers/app.ts | 72 +- src/providers/config.ts | 16 +- src/providers/cron.ts | 91 +- src/providers/db.ts | 10 +- src/providers/events.ts | 20 +- src/providers/file-helper.ts | 58 +- src/providers/file-session.ts | 52 +- src/providers/file.ts | 263 +++--- src/providers/filepool.ts | 764 ++++++++------- src/providers/groups.ts | 138 ++- src/providers/init.ts | 15 +- src/providers/lang.ts | 46 +- src/providers/local-notifications.ts | 108 +-- src/providers/logger.ts | 10 +- src/providers/plugin-file-delegate.ts | 28 +- src/providers/sites-factory.ts | 18 +- src/providers/sites.ts | 270 +++--- src/providers/sync.ts | 56 +- src/providers/update-manager.ts | 51 +- src/providers/urlschemes.ts | 35 +- src/providers/utils/dom.ts | 366 ++++--- src/providers/utils/iframe.ts | 28 +- src/providers/utils/mimetype.ts | 88 +- src/providers/utils/text.ts | 190 ++-- src/providers/utils/time.ts | 50 +- src/providers/utils/url.ts | 80 +- src/providers/utils/utils.ts | 281 +++--- src/providers/ws.ts | 140 ++- 749 files changed, 15872 insertions(+), 16313 deletions(-) diff --git a/src/addon/badges/pages/issued-badge/issued-badge.ts b/src/addon/badges/pages/issued-badge/issued-badge.ts index ff79f7841..d33990ddb 100644 --- a/src/addon/badges/pages/issued-badge/issued-badge.ts +++ b/src/addon/badges/pages/issued-badge/issued-badge.ts @@ -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.ts b/src/addon/badges/pages/user-badges/user-badges.ts index cde1db77a..fe5ea5c1c 100644 --- a/src/addon/badges/pages/user-badges/user-badges.ts +++ b/src/addon/badges/pages/user-badges/user-badges.ts @@ -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..0f98f0f0d 100644 --- a/src/addon/badges/providers/badge-link-handler.ts +++ b/src/addon/badges/providers/badge-link-handler.ts @@ -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..f2b16814b 100644 --- a/src/addon/badges/providers/badges.ts +++ b/src/addon/badges/providers/badges.ts @@ -35,8 +35,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 +54,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,10 +65,10 @@ 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 { @@ -98,10 +98,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 { diff --git a/src/addon/badges/providers/mybadges-link-handler.ts b/src/addon/badges/providers/mybadges-link-handler.ts index 190e575ac..f9ea5e8a3 100644 --- a/src/addon/badges/providers/mybadges-link-handler.ts +++ b/src/addon/badges/providers/mybadges-link-handler.ts @@ -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..c36e1e3e2 100644 --- a/src/addon/badges/providers/push-click-handler.ts +++ b/src/addon/badges/providers/push-click-handler.ts @@ -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..5c8e705e3 100644 --- a/src/addon/badges/providers/user-handler.ts +++ b/src/addon/badges/providers/user-handler.ts @@ -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/components/activitymodules/activitymodules.ts b/src/addon/block/activitymodules/components/activitymodules/activitymodules.ts index 69a0122bb..6a96b2169 100644 --- a/src/addon/block/activitymodules/components/activitymodules/activitymodules.ts +++ b/src/addon/block/activitymodules/components/activitymodules/activitymodules.ts @@ -52,7 +52,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,7 +61,7 @@ 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) => { diff --git a/src/addon/block/activitymodules/providers/block-handler.ts b/src/addon/block/activitymodules/providers/block-handler.ts index 752d23a47..aeee44e52 100644 --- a/src/addon/block/activitymodules/providers/block-handler.ts +++ b/src/addon/block/activitymodules/providers/block-handler.ts @@ -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/providers/block-handler.ts b/src/addon/block/badges/providers/block-handler.ts index d6cfb677a..4b9b6971e 100644 --- a/src/addon/block/badges/providers/block-handler.ts +++ b/src/addon/block/badges/providers/block-handler.ts @@ -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/providers/block-handler.ts b/src/addon/block/blogmenu/providers/block-handler.ts index 231137b8e..86446a1fe 100644 --- a/src/addon/block/blogmenu/providers/block-handler.ts +++ b/src/addon/block/blogmenu/providers/block-handler.ts @@ -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/providers/block-handler.ts b/src/addon/block/blogrecent/providers/block-handler.ts index 55f03cb8e..8fca85a70 100644 --- a/src/addon/block/blogrecent/providers/block-handler.ts +++ b/src/addon/block/blogrecent/providers/block-handler.ts @@ -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/providers/block-handler.ts b/src/addon/block/blogtags/providers/block-handler.ts index aa2a17495..a73d45f17 100644 --- a/src/addon/block/blogtags/providers/block-handler.ts +++ b/src/addon/block/blogtags/providers/block-handler.ts @@ -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/providers/block-handler.ts b/src/addon/block/calendarmonth/providers/block-handler.ts index 80e85edf8..4f1724ea6 100644 --- a/src/addon/block/calendarmonth/providers/block-handler.ts +++ b/src/addon/block/calendarmonth/providers/block-handler.ts @@ -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/providers/block-handler.ts b/src/addon/block/calendarupcoming/providers/block-handler.ts index c58a0d080..edadfb05c 100644 --- a/src/addon/block/calendarupcoming/providers/block-handler.ts +++ b/src/addon/block/calendarupcoming/providers/block-handler.ts @@ -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/providers/block-handler.ts b/src/addon/block/comments/providers/block-handler.ts index ada6e1654..594b262f7 100644 --- a/src/addon/block/comments/providers/block-handler.ts +++ b/src/addon/block/comments/providers/block-handler.ts @@ -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/providers/block-handler.ts b/src/addon/block/completionstatus/providers/block-handler.ts index 88fca4ec8..7edac6643 100644 --- a/src/addon/block/completionstatus/providers/block-handler.ts +++ b/src/addon/block/completionstatus/providers/block-handler.ts @@ -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/providers/block-handler.ts b/src/addon/block/glossaryrandom/providers/block-handler.ts index d639ccb16..8e54cf602 100644 --- a/src/addon/block/glossaryrandom/providers/block-handler.ts +++ b/src/addon/block/glossaryrandom/providers/block-handler.ts @@ -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/providers/block-handler.ts b/src/addon/block/html/providers/block-handler.ts index a5603fb53..4d91839c1 100644 --- a/src/addon/block/html/providers/block-handler.ts +++ b/src/addon/block/html/providers/block-handler.ts @@ -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/providers/block-handler.ts b/src/addon/block/learningplans/providers/block-handler.ts index fa98be638..e831019be 100644 --- a/src/addon/block/learningplans/providers/block-handler.ts +++ b/src/addon/block/learningplans/providers/block-handler.ts @@ -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/myoverview/myoverview.ts b/src/addon/block/myoverview/components/myoverview/myoverview.ts index ed68b9205..06c57fd6e 100644 --- a/src/addon/block/myoverview/components/myoverview/myoverview.ts +++ b/src/addon/block/myoverview/components/myoverview/myoverview.ts @@ -133,7 +133,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,7 +161,7 @@ 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) => { @@ -197,7 +197,7 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem /** * 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(); @@ -239,7 +239,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, @@ -264,7 +264,7 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem /** * Init courses filters. * - * @param {any[]} courses Courses to filter. + * @param courses Courses to filter. */ initCourseFilters(courses: any[]): void { if (this.showSortFilter) { @@ -319,7 +319,7 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem /** * The selected courses sort filter have changed. * - * @param {string} sort New sorting. + * @param sort New sorting. */ switchSort(sort: string): void { this.sort = sort; @@ -344,7 +344,7 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem /** * 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; diff --git a/src/addon/block/myoverview/providers/block-handler.ts b/src/addon/block/myoverview/providers/block-handler.ts index e0a593b9a..f7d434747 100644 --- a/src/addon/block/myoverview/providers/block-handler.ts +++ b/src/addon/block/myoverview/providers/block-handler.ts @@ -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/providers/block-handler.ts b/src/addon/block/newsitems/providers/block-handler.ts index c077474d8..86b12b57f 100644 --- a/src/addon/block/newsitems/providers/block-handler.ts +++ b/src/addon/block/newsitems/providers/block-handler.ts @@ -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/providers/block-handler.ts b/src/addon/block/onlineusers/providers/block-handler.ts index 353835c83..78505b0de 100644 --- a/src/addon/block/onlineusers/providers/block-handler.ts +++ b/src/addon/block/onlineusers/providers/block-handler.ts @@ -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/providers/block-handler.ts b/src/addon/block/privatefiles/providers/block-handler.ts index f0284df97..811aea153 100644 --- a/src/addon/block/privatefiles/providers/block-handler.ts +++ b/src/addon/block/privatefiles/providers/block-handler.ts @@ -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..b3d6042a4 100644 --- a/src/addon/block/recentactivity/providers/block-handler.ts +++ b/src/addon/block/recentactivity/providers/block-handler.ts @@ -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/recentlyaccessedcourses/components/recentlyaccessedcourses/recentlyaccessedcourses.ts b/src/addon/block/recentlyaccessedcourses/components/recentlyaccessedcourses/recentlyaccessedcourses.ts index 92697e4f0..7db55fcfd 100644 --- a/src/addon/block/recentlyaccessedcourses/components/recentlyaccessedcourses/recentlyaccessedcourses.ts +++ b/src/addon/block/recentlyaccessedcourses/components/recentlyaccessedcourses/recentlyaccessedcourses.ts @@ -79,7 +79,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,7 +104,7 @@ 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) => { @@ -133,7 +133,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; diff --git a/src/addon/block/recentlyaccessedcourses/providers/block-handler.ts b/src/addon/block/recentlyaccessedcourses/providers/block-handler.ts index 3af9cf0dd..0f70d2768 100644 --- a/src/addon/block/recentlyaccessedcourses/providers/block-handler.ts +++ b/src/addon/block/recentlyaccessedcourses/providers/block-handler.ts @@ -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/recentlyaccesseditems/components/recentlyaccesseditems/recentlyaccesseditems.ts b/src/addon/block/recentlyaccesseditems/components/recentlyaccesseditems/recentlyaccesseditems.ts index e1c9bf372..6aa7b3650 100644 --- a/src/addon/block/recentlyaccesseditems/components/recentlyaccesseditems/recentlyaccesseditems.ts +++ b/src/addon/block/recentlyaccesseditems/components/recentlyaccesseditems/recentlyaccesseditems.ts @@ -50,7 +50,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 +59,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 +70,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..928f33e1b 100644 --- a/src/addon/block/recentlyaccesseditems/providers/block-handler.ts +++ b/src/addon/block/recentlyaccesseditems/providers/block-handler.ts @@ -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..a24bc035c 100644 --- a/src/addon/block/recentlyaccesseditems/providers/recentlyaccesseditems.ts +++ b/src/addon/block/recentlyaccesseditems/providers/recentlyaccesseditems.ts @@ -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,8 +39,8 @@ 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 { @@ -63,8 +63,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) => { diff --git a/src/addon/block/rssclient/providers/block-handler.ts b/src/addon/block/rssclient/providers/block-handler.ts index 8976f2182..770f74398 100644 --- a/src/addon/block/rssclient/providers/block-handler.ts +++ b/src/addon/block/rssclient/providers/block-handler.ts @@ -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/selfcompletion/providers/block-handler.ts b/src/addon/block/selfcompletion/providers/block-handler.ts index 57d716e5a..f9ea51d35 100644 --- a/src/addon/block/selfcompletion/providers/block-handler.ts +++ b/src/addon/block/selfcompletion/providers/block-handler.ts @@ -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/sitemainmenu/components/sitemainmenu/sitemainmenu.ts b/src/addon/block/sitemainmenu/components/sitemainmenu/sitemainmenu.ts index 23bfb74ca..19030b3cc 100644 --- a/src/addon/block/sitemainmenu/components/sitemainmenu/sitemainmenu.ts +++ b/src/addon/block/sitemainmenu/components/sitemainmenu/sitemainmenu.ts @@ -54,7 +54,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 +73,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) => { diff --git a/src/addon/block/sitemainmenu/providers/block-handler.ts b/src/addon/block/sitemainmenu/providers/block-handler.ts index dcaf4fe01..b7118a5f4 100644 --- a/src/addon/block/sitemainmenu/providers/block-handler.ts +++ b/src/addon/block/sitemainmenu/providers/block-handler.ts @@ -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/starredcourses/components/starredcourses/starredcourses.ts b/src/addon/block/starredcourses/components/starredcourses/starredcourses.ts index ca25f3354..ca7e3156e 100644 --- a/src/addon/block/starredcourses/components/starredcourses/starredcourses.ts +++ b/src/addon/block/starredcourses/components/starredcourses/starredcourses.ts @@ -79,7 +79,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,7 +104,7 @@ 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) => { @@ -133,7 +133,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; diff --git a/src/addon/block/starredcourses/providers/block-handler.ts b/src/addon/block/starredcourses/providers/block-handler.ts index c7abc3485..c65f01aff 100644 --- a/src/addon/block/starredcourses/providers/block-handler.ts +++ b/src/addon/block/starredcourses/providers/block-handler.ts @@ -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/tags/providers/block-handler.ts b/src/addon/block/tags/providers/block-handler.ts index f1c7d4cd8..963ae63f3 100644 --- a/src/addon/block/tags/providers/block-handler.ts +++ b/src/addon/block/tags/providers/block-handler.ts @@ -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/timeline/components/events/events.ts b/src/addon/block/timeline/components/events/events.ts index a4f50e96a..07dbf0ac0 100644 --- a/src/addon/block/timeline/components/events/events.ts +++ b/src/addon/block/timeline/components/events/events.ts @@ -89,9 +89,9 @@ export class AddonBlockTimelineEventsComponent implements OnChanges { /** * Filter the events by time. * - * @param {number} start Number of days to start getting events from today. E.g. -1 will get events from yesterday. - * @param {number} [end] Number of days after the start. - * @return {any[]} Filtered events. + * @param start Number of days to start getting events from today. E.g. -1 will get events from yesterday. + * @param end Number of days after the start. + * @return Filtered events. */ protected filterEventsByTime(start: number, end?: number): any[] { start = moment().add(start, 'days').startOf('day').unix(); @@ -121,8 +121,8 @@ export class AddonBlockTimelineEventsComponent implements OnChanges { /** * Action clicked. * - * @param {Event} e Click event. - * @param {string} url Url of the action. + * @param e Click event. + * @param url Url of the action. */ action(e: Event, url: string): void { e.preventDefault(); diff --git a/src/addon/block/timeline/components/timeline/timeline.ts b/src/addon/block/timeline/components/timeline/timeline.ts index 20976cc5b..8325cbcf3 100644 --- a/src/addon/block/timeline/components/timeline/timeline.ts +++ b/src/addon/block/timeline/components/timeline/timeline.ts @@ -75,7 +75,7 @@ export class AddonBlockTimelineComponent extends CoreBlockBaseComponent implemen /** * Perform the invalidate content function. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected invalidateContent(): Promise { const promises = []; @@ -94,7 +94,7 @@ export class AddonBlockTimelineComponent extends CoreBlockBaseComponent implemen /** * Fetch the courses for my overview. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchContent(): Promise { if (this.sort == 'sortbydates') { @@ -120,8 +120,8 @@ export class AddonBlockTimelineComponent extends CoreBlockBaseComponent implemen /** * Load more events. * - * @param {any} course Course. - * @return {Promise} Promise resolved when done. + * @param course Course. + * @return Promise resolved when done. */ loadMoreCourse(course: any): Promise { return this.timelineProvider.getActionEventsByCourse(course.id, course.canLoadMore).then((courseEvents) => { @@ -135,8 +135,8 @@ export class AddonBlockTimelineComponent extends CoreBlockBaseComponent implemen /** * Fetch the timeline. * - * @param {number} [afterEventId] The last event id. - * @return {Promise} Promise resolved when done. + * @param afterEventId The last event id. + * @return Promise resolved when done. */ protected fetchMyOverviewTimeline(afterEventId?: number): Promise { return this.timelineProvider.getActionEventsByTimesort(afterEventId).then((events) => { @@ -148,7 +148,7 @@ export class AddonBlockTimelineComponent extends CoreBlockBaseComponent implemen /** * Fetch the timeline by courses. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchMyOverviewTimelineByCourses(): Promise { return this.coursesHelper.getUserCoursesWithOptions().then((courses) => { @@ -210,7 +210,7 @@ export class AddonBlockTimelineComponent extends CoreBlockBaseComponent implemen /** * Change timeline sort being viewed. * - * @param {string} sort New sorting. + * @param sort New sorting. */ switchSort(sort: string): void { this.sort = sort; diff --git a/src/addon/block/timeline/providers/block-handler.ts b/src/addon/block/timeline/providers/block-handler.ts index 8973fbc5d..e116002b7 100644 --- a/src/addon/block/timeline/providers/block-handler.ts +++ b/src/addon/block/timeline/providers/block-handler.ts @@ -37,7 +37,7 @@ export class AddonBlockTimelineHandler 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.timelineProvider.isAvailable().then((enabled) => { @@ -49,11 +49,11 @@ export class AddonBlockTimelineHandler 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/timeline/providers/timeline.ts b/src/addon/block/timeline/providers/timeline.ts index 148aed267..6f90184e8 100644 --- a/src/addon/block/timeline/providers/timeline.ts +++ b/src/addon/block/timeline/providers/timeline.ts @@ -32,10 +32,10 @@ export class AddonBlockTimelineProvider { /** * Get calendar action events for the given course. * - * @param {number} courseId Only events in this course. - * @param {number} [afterEventId] The last seen event id. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise<{events: any[], canLoadMore: number}>} Promise resolved when the info is retrieved. + * @param courseId Only events in this course. + * @param afterEventId The last seen event id. + * @param siteId Site ID. If not defined, use current site. + * @return Promise resolved when the info is retrieved. */ getActionEventsByCourse(courseId: number, afterEventId?: number, siteId?: string): Promise<{ events: any[], canLoadMore: number }> { @@ -68,8 +68,8 @@ export class AddonBlockTimelineProvider { /** * Get cache key for get calendar action events for the given course value WS call. * - * @param {number} courseId Only events in this course. - * @return {string} Cache key. + * @param courseId Only events in this course. + * @return Cache key. */ protected getActionEventsByCourseCacheKey(courseId: number): string { return this.getActionEventsByCoursesCacheKey() + ':' + courseId; @@ -78,9 +78,9 @@ export class AddonBlockTimelineProvider { /** * Get calendar action events for a given list of courses. * - * @param {number[]} courseIds Course IDs. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise<{[s: string]: {events: any[], canLoadMore: number}}>} Promise resolved when the info is retrieved. + * @param courseIds Course IDs. + * @param siteId Site ID. If not defined, use current site. + * @return Promise resolved when the info is retrieved. */ getActionEventsByCourses(courseIds: number[], siteId?: string): Promise<{ [s: string]: { events: any[], canLoadMore: number } }> { @@ -114,7 +114,7 @@ export class AddonBlockTimelineProvider { /** * Get cache key for get calendar action events for a given list of courses value WS call. * - * @return {string} Cache key. + * @return Cache key. */ protected getActionEventsByCoursesCacheKey(): string { return this.ROOT_CACHE_KEY + 'bycourse'; @@ -123,9 +123,9 @@ export class AddonBlockTimelineProvider { /** * Get calendar action events based on the timesort value. * - * @param {number} [afterEventId] The last seen event id. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise<{events: any[], canLoadMore: number}>} Promise resolved when the info is retrieved. + * @param afterEventId The last seen event id. + * @param siteId Site ID. If not defined, use current site. + * @return Promise resolved when the info is retrieved. */ getActionEventsByTimesort(afterEventId: number, siteId?: string): Promise<{ events: any[], canLoadMore: number }> { return this.sitesProvider.getSite(siteId).then((site) => { @@ -167,7 +167,7 @@ export class AddonBlockTimelineProvider { /** * Get prefix cache key for calendar action events based on the timesort value WS calls. * - * @return {string} Cache key. + * @return Cache key. */ protected getActionEventsByTimesortPrefixCacheKey(): string { return this.ROOT_CACHE_KEY + 'bytimesort:'; @@ -176,9 +176,9 @@ export class AddonBlockTimelineProvider { /** * Get cache key for get calendar action events based on the timesort value WS call. * - * @param {number} [afterEventId] The last seen event id. - * @param {number} [limit] Limit num of the call. - * @return {string} Cache key. + * @param afterEventId The last seen event id. + * @param limit Limit num of the call. + * @return Cache key. */ protected getActionEventsByTimesortCacheKey(afterEventId?: number, limit?: number): string { afterEventId = afterEventId || 0; @@ -190,8 +190,8 @@ export class AddonBlockTimelineProvider { /** * Invalidates get calendar action events for a given list of courses 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. */ invalidateActionEventsByCourses(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -202,8 +202,8 @@ export class AddonBlockTimelineProvider { /** * Invalidates get calendar action events based on the timesort value 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. */ invalidateActionEventsByTimesort(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -214,8 +214,8 @@ export class AddonBlockTimelineProvider { /** * Returns whether or not My Overview 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. */ isAvailable(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -232,9 +232,9 @@ export class AddonBlockTimelineProvider { /** * Handles course events, filtering and treating if more can be loaded. * - * @param {any} course Object containing response course events info. - * @param {number} timeFrom Current time to filter events from. - * @return {{events: any[], canLoadMore: number}} Object with course events and last loaded event id if more can be loaded. + * @param course Object containing response course events info. + * @param timeFrom Current time to filter events from. + * @return Object with course events and last loaded event id if more can be loaded. */ protected treatCourseEvents(course: any, timeFrom: number): { events: any[], canLoadMore: number } { const canLoadMore: number = diff --git a/src/addon/blog/components/entries/entries.ts b/src/addon/blog/components/entries/entries.ts index aa66beaef..3cd10ebd6 100644 --- a/src/addon/blog/components/entries/entries.ts +++ b/src/addon/blog/components/entries/entries.ts @@ -104,8 +104,8 @@ export class AddonBlogEntriesComponent implements OnInit { /** * Fetch blog entries. * - * @param {boolean} [refresh] Empty events array first. - * @return {Promise} Promise with the entries. + * @param refresh Empty events array first. + * @return Promise with the entries. */ private fetchEntries(refresh: boolean = false): Promise { this.loadMoreError = false; @@ -174,7 +174,7 @@ export class AddonBlogEntriesComponent implements OnInit { /** * Toggle between showing only my entries or not. * - * @param {boolean} enabled If true, filter my entries. False otherwise. + * @param enabled If true, filter my entries. False otherwise. */ onlyMyEntriesToggleChanged(enabled: boolean): void { if (enabled) { @@ -198,8 +198,8 @@ export class AddonBlogEntriesComponent implements OnInit { /** * Function to load more entries. * - * @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.fetchEntries().finally(() => { @@ -210,7 +210,7 @@ export class AddonBlogEntriesComponent implements OnInit { /** * Refresh blog entries on PTR. * - * @param {any} refresher Refresher instance. + * @param refresher Refresher instance. */ refresh(refresher?: any): void { const promises = this.entries.map((entry) => { diff --git a/src/addon/blog/providers/blog.ts b/src/addon/blog/providers/blog.ts index 90b875283..a816fd9d3 100644 --- a/src/addon/blog/providers/blog.ts +++ b/src/addon/blog/providers/blog.ts @@ -40,8 +40,8 @@ export class AddonBlogProvider { * 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) => { @@ -53,8 +53,8 @@ export class AddonBlogProvider { /** * Get the cache key for the blog entries. * - * @param {any} [filter] Filter to apply on search. - * @return {string} Cache key. + * @param filter Filter to apply on search. + * @return Cache key. */ getEntriesCacheKey(filter: any = {}): string { return this.ROOT_CACHE_KEY + this.utils.sortAndStringify(filter); @@ -63,10 +63,10 @@ export class AddonBlogProvider { /** * Get blog entries. * - * @param {any} [filter] Filter to apply on search. - * @param {any} [page=0] Page of the blog entries to fetch. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise to be resolved when the entries are retrieved. + * @param filter Filter to apply on search. + * @param page Page of the blog entries to fetch. + * @param siteId Site ID. If not defined, current site. + * @return Promise to be resolved when the entries are retrieved. */ getEntries(filter: any = {}, page: number = 0, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -88,9 +88,9 @@ export class AddonBlogProvider { /** * Invalidate blog entries WS call. * - * @param {any} [filter] Filter to apply on search - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when data is invalidated. + * @param filter Filter to apply on search + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when data is invalidated. */ invalidateEntries(filter: any = {}, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -101,9 +101,9 @@ export class AddonBlogProvider { /** * Trigger the blog_entries_viewed event. * - * @param {any} [filter] Filter to apply on search. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise to be resolved when done. + * @param filter Filter to apply on search. + * @param siteId Site ID. If not defined, current site. + * @return Promise to be resolved when done. */ logView(filter: any = {}, siteId?: string): Promise { this.pushNotificationsProvider.logViewListEvent('blog', 'core_blog_view_entries', filter, siteId); diff --git a/src/addon/blog/providers/course-option-handler.ts b/src/addon/blog/providers/course-option-handler.ts index 5617d6c92..34140ca2a 100644 --- a/src/addon/blog/providers/course-option-handler.ts +++ b/src/addon/blog/providers/course-option-handler.ts @@ -37,10 +37,10 @@ export class AddonBlogCourseOptionHandler implements CoreCourseOptionsHandler { /** * 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.courseProvider.invalidateCourseBlocks(courseId); @@ -49,7 +49,7 @@ export class AddonBlogCourseOptionHandler implements CoreCourseOptionsHandler { /** * 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.blogProvider.isPluginEnabled(); @@ -58,11 +58,11 @@ export class AddonBlogCourseOptionHandler 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 { return this.courseHelper.hasABlockNamed(courseId, 'blog_menu').then((enabled) => { @@ -77,9 +77,9 @@ export class AddonBlogCourseOptionHandler implements CoreCourseOptionsHandler { /** * 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 { @@ -92,8 +92,8 @@ export class AddonBlogCourseOptionHandler 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 { const siteId = this.sitesProvider.getCurrentSiteId(); diff --git a/src/addon/blog/providers/index-link-handler.ts b/src/addon/blog/providers/index-link-handler.ts index 0670b322b..ab728fd06 100644 --- a/src/addon/blog/providers/index-link-handler.ts +++ b/src/addon/blog/providers/index-link-handler.ts @@ -34,11 +34,11 @@ export class AddonBlogIndexLinkHandler 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 { @@ -62,11 +62,11 @@ export class AddonBlogIndexLinkHandler 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/blog/providers/mainmenu-handler.ts b/src/addon/blog/providers/mainmenu-handler.ts index e45bfd06f..4f157e5bb 100644 --- a/src/addon/blog/providers/mainmenu-handler.ts +++ b/src/addon/blog/providers/mainmenu-handler.ts @@ -29,7 +29,7 @@ export class AddonBlogMainMenuHandler 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.blogProvider.isPluginEnabled(); @@ -38,7 +38,7 @@ export class AddonBlogMainMenuHandler 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/blog/providers/tag-area-handler.ts b/src/addon/blog/providers/tag-area-handler.ts index 41413d824..7d38f48c2 100644 --- a/src/addon/blog/providers/tag-area-handler.ts +++ b/src/addon/blog/providers/tag-area-handler.ts @@ -30,7 +30,7 @@ export class AddonBlogTagAreaHandler 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 this.blogProvider.isPluginEnabled(); @@ -39,8 +39,8 @@ export class AddonBlogTagAreaHandler 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); @@ -49,8 +49,8 @@ export class AddonBlogTagAreaHandler 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/blog/providers/user-handler.ts b/src/addon/blog/providers/user-handler.ts index fe19563b8..6911ba70d 100644 --- a/src/addon/blog/providers/user-handler.ts +++ b/src/addon/blog/providers/user-handler.ts @@ -31,7 +31,7 @@ export class AddonBlogUserHandler 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.blogProvider.isPluginEnabled(); @@ -40,11 +40,11 @@ export class AddonBlogUserHandler 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 { return true; @@ -53,7 +53,7 @@ export class AddonBlogUserHandler 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/calendar/components/calendar/calendar.ts b/src/addon/calendar/components/calendar/calendar.ts index 269310608..391116337 100644 --- a/src/addon/calendar/components/calendar/calendar.ts +++ b/src/addon/calendar/components/calendar/calendar.ts @@ -134,8 +134,8 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest /** * Fetch contacts. * - * @param {boolean} [refresh=false] True if we are refreshing events. - * @return {Promise} Promise resolved when done. + * @param refresh True if we are refreshing events. + * @return Promise resolved when done. */ fetchData(refresh: boolean = false): Promise { const promises = []; @@ -184,7 +184,7 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest /** * Fetch the events for current month. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ fetchEvents(): Promise { // Don't pass courseId and categoryId, we'll filter them locally. @@ -238,7 +238,7 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest /** * Load categories to be able to filter events. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected loadCategories(): Promise { if (this.categoriesRetrieved) { @@ -285,8 +285,8 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest /** * Refresh events. * - * @param {boolean} [afterChange] Whether the refresh is done after an event has changed or has been synced. - * @return {Promise} Promise resolved when done. + * @param afterChange Whether the refresh is done after an event has changed or has been synced. + * @return Promise resolved when done. */ refreshData(afterChange?: boolean): Promise { const promises = []; @@ -340,8 +340,8 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest /** * An event was clicked. * - * @param {any} calendarEvent Calendar event.. - * @param {MouseEvent} event Mouse event. + * @param calendarEvent Calendar event.. + * @param event Mouse event. */ eventClicked(calendarEvent: any, event: MouseEvent): void { this.onEventClicked.emit(calendarEvent.id); @@ -351,7 +351,7 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest /** * A day was clicked. * - * @param {number} day Day. + * @param day Day. */ dayClicked(day: number): void { this.onDayClicked.emit({day: day, month: this.month, year: this.year}); @@ -461,7 +461,7 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest /** * 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) => { @@ -476,7 +476,7 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest /** * Undelete a certain event. * - * @param {number} eventId Event ID. + * @param eventId Event ID. */ protected undeleteEvent(eventId: number): void { if (!this.weeks) { @@ -498,8 +498,8 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest /** * 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; diff --git a/src/addon/calendar/components/upcoming-events/upcoming-events.ts b/src/addon/calendar/components/upcoming-events/upcoming-events.ts index d56a3470b..43489bd80 100644 --- a/src/addon/calendar/components/upcoming-events/upcoming-events.ts +++ b/src/addon/calendar/components/upcoming-events/upcoming-events.ts @@ -106,8 +106,8 @@ export class AddonCalendarUpcomingEventsComponent implements OnInit, OnChanges, /** * Fetch data. * - * @param {boolean} [refresh=false] True if we are refreshing events. - * @return {Promise} Promise resolved when done. + * @param refresh True if we are refreshing events. + * @return Promise resolved when done. */ fetchData(refresh: boolean = false): Promise { const promises = []; @@ -151,7 +151,7 @@ export class AddonCalendarUpcomingEventsComponent implements OnInit, OnChanges, /** * Fetch upcoming events. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ fetchEvents(): Promise { // Don't pass courseId and categoryId, we'll filter them locally. @@ -185,7 +185,7 @@ export class AddonCalendarUpcomingEventsComponent implements OnInit, OnChanges, /** * Load categories to be able to filter events. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected loadCategories(): Promise { if (this.categoriesRetrieved) { @@ -225,8 +225,8 @@ export class AddonCalendarUpcomingEventsComponent implements OnInit, OnChanges, /** * Refresh events. * - * @param {boolean} [afterChange] Whether the refresh is done after an event has changed or has been synced. - * @return {Promise} Promise resolved when done. + * @param afterChange Whether the refresh is done after an event has changed or has been synced. + * @return Promise resolved when done. */ refreshData(afterChange?: boolean): Promise { const promises = []; @@ -249,7 +249,7 @@ export class AddonCalendarUpcomingEventsComponent implements OnInit, OnChanges, /** * An event was clicked. * - * @param {any} event Event. + * @param event Event. */ eventClicked(event: any): void { this.onEventClicked.emit(event.id); @@ -258,7 +258,7 @@ export class AddonCalendarUpcomingEventsComponent implements OnInit, OnChanges, /** * Merge online events with the offline events of that period. * - * @return {any[]} Merged events. + * @return Merged events. */ protected mergeEvents(): any[] { if (!this.offlineEvents.length && !this.deletedEvents.length) { @@ -302,7 +302,7 @@ export class AddonCalendarUpcomingEventsComponent implements OnInit, OnChanges, /** * 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) => { @@ -317,7 +317,7 @@ export class AddonCalendarUpcomingEventsComponent implements OnInit, OnChanges, /** * Undelete a certain event. * - * @param {number} eventId Event ID. + * @param eventId Event ID. */ protected undeleteEvent(eventId: number): void { const event = this.onlineEvents.find((event) => { diff --git a/src/addon/calendar/pages/day/day.ts b/src/addon/calendar/pages/day/day.ts index c3b566f88..d87725e8f 100644 --- a/src/addon/calendar/pages/day/day.ts +++ b/src/addon/calendar/pages/day/day.ts @@ -208,9 +208,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 { @@ -280,7 +280,7 @@ 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. @@ -328,7 +328,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; @@ -389,7 +389,7 @@ export class AddonCalendarDayPage implements OnInit, 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) => { @@ -404,10 +404,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 +423,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 +449,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 +467,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 +495,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,7 +511,7 @@ 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) => { @@ -532,7 +532,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { /** * 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 = {}; @@ -658,9 +658,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 +678,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; diff --git a/src/addon/calendar/pages/edit-event/edit-event.ts b/src/addon/calendar/pages/edit-event/edit-event.ts index b8d8ef9f4..df1f0f8c7 100644 --- a/src/addon/calendar/pages/edit-event/edit-event.ts +++ b/src/addon/calendar/pages/edit-event/edit-event.ts @@ -148,8 +148,8 @@ 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; @@ -289,9 +289,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 +344,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 +375,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 +396,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; @@ -515,7 +515,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 +568,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.ts b/src/addon/calendar/pages/event/event.ts index 8edc9b96e..21b503b5a 100644 --- a/src/addon/calendar/pages/event/event.ts +++ b/src/addon/calendar/pages/event/event.ts @@ -144,9 +144,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(), @@ -312,7 +312,7 @@ export class AddonCalendarEventPage implements OnDestroy { /** * Add a reminder for this event. * - * @param {Event} e Click event. + * @param e Click event. */ addNotificationTime(e: Event): void { e.preventDefault(); @@ -342,8 +342,8 @@ 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(); @@ -359,10 +359,10 @@ export class AddonCalendarEventPage 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.eventLoaded) { @@ -378,9 +378,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 +511,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.ts b/src/addon/calendar/pages/index/index.ts index 8f3d5a129..e9dd336de 100644 --- a/src/addon/calendar/pages/index/index.ts +++ b/src/addon/calendar/pages/index/index.ts @@ -164,9 +164,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 { @@ -230,10 +230,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 +249,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 +276,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 +292,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 = { @@ -311,7 +311,7 @@ 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) => { @@ -330,7 +330,7 @@ export class AddonCalendarIndexPage implements OnInit, OnDestroy { /** * 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 = {}; diff --git a/src/addon/calendar/pages/list/list.ts b/src/addon/calendar/pages/list/list.ts index 0b864a78a..77b8922d1 100644 --- a/src/addon/calendar/pages/list/list.ts +++ b/src/addon/calendar/pages/list/list.ts @@ -233,10 +233,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(); @@ -314,8 +314,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; @@ -388,8 +388,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(() => { @@ -400,7 +400,7 @@ export class AddonCalendarListPage implements OnDestroy { /** * Get filtered events. * - * @return {any[]} Filtered events. + * @return Filtered events. */ protected getFilteredEvents(): any[] { if (!this.courseId) { @@ -415,8 +415,8 @@ export class AddonCalendarListPage implements OnDestroy { /** * 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 +433,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 +451,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 +501,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 +516,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 +535,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 +561,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,8 +578,8 @@ 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 { if (!event.timeduration) { @@ -594,7 +594,7 @@ 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) => { @@ -617,7 +617,7 @@ export class AddonCalendarListPage implements OnDestroy { /** * 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; @@ -644,7 +644,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 +662,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) => { diff --git a/src/addon/calendar/pages/settings/settings.ts b/src/addon/calendar/pages/settings/settings.ts index bebc4f1e9..9027272db 100644 --- a/src/addon/calendar/pages/settings/settings.ts +++ b/src/addon/calendar/pages/settings/settings.ts @@ -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..868d8558b 100644 --- a/src/addon/calendar/providers/calendar-offline.ts +++ b/src/addon/calendar/providers/calendar-offline.ts @@ -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..d237e0897 100644 --- a/src/addon/calendar/providers/calendar-sync.ts +++ b/src/addon/calendar/providers/calendar-sync.ts @@ -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..c518a989d 100644 --- a/src/addon/calendar/providers/calendar.ts +++ b/src/addon/calendar/providers/calendar.ts @@ -335,8 +335,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 +350,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 +363,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 +378,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 +392,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 +407,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 +420,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 +442,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,10 +484,10 @@ 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 { return this.sitesProvider.getSite(siteId).then((site) => { @@ -511,9 +511,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) => { @@ -538,8 +538,8 @@ export class AddonCalendarProvider { /** * Check if event ends the same day or not. * - * @param {any} event Event info. - * @return {boolean} If the . + * @param event Event info. + * @return If the . */ endsSameDay(event: any): boolean { if (!event.timeduration) { @@ -554,13 +554,13 @@ export class AddonCalendarProvider { /** * 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 { @@ -630,9 +630,9 @@ 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 { @@ -653,8 +653,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 +663,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,9 +675,9 @@ 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 { @@ -709,8 +709,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 +719,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 +739,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 +766,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 +797,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,10 +811,10 @@ 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 refresh True when we should update the event data. + * @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 { return this.sitesProvider.getSite(siteId).then((site) => { @@ -848,10 +848,10 @@ 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 refresh True when we should update the event data. + * @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 { @@ -877,8 +877,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 +887,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,10 +913,10 @@ 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 { return this.sitesProvider.getSite(siteId).then((site) => { @@ -935,8 +935,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 +949,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,14 +966,14 @@ 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 { @@ -1014,7 +1014,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 +1023,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 +1035,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 +1050,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,11 +1067,11 @@ 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 participants are retrieved. */ getEventsList(initialTime?: number, daysToStart: number = 0, daysInterval: number = AddonCalendarProvider.DAYS_INTERVAL, siteId?: string): Promise { @@ -1137,7 +1137,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 +1146,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 +1157,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,13 +1169,13 @@ 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 { @@ -1230,7 +1230,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 +1239,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 +1250,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,11 +1264,11 @@ 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 { @@ -1304,7 +1304,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 +1313,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 +1324,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 +1349,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 +1361,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 +1374,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 +1387,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 +1399,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 +1413,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 +1433,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 +1446,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 +1458,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 +1471,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 +1483,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 +1497,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 +1507,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 +1517,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 +1529,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 +1541,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 +1556,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 +1571,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,10 +1603,10 @@ 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 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 { if (this.localNotificationsProvider.isAvailable()) { @@ -1668,9 +1668,9 @@ 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 { @@ -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,13 +1798,13 @@ 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}> { @@ -1842,10 +1842,10 @@ 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 { return this.sitesProvider.getSite(siteId).then((site) => { diff --git a/src/addon/calendar/providers/helper.ts b/src/addon/calendar/providers/helper.ts index 7fd222659..6c2cd8a6b 100644 --- a/src/addon/calendar/providers/helper.ts +++ b/src/addon/calendar/providers/helper.ts @@ -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,7 +128,7 @@ 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; @@ -157,8 +157,8 @@ 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}[] { const options = []; @@ -185,9 +185,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 +198,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 +256,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) { @@ -297,11 +297,11 @@ export class AddonCalendarHelperProvider { /** * 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') { @@ -345,9 +345,9 @@ export class AddonCalendarHelperProvider { * 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,10 +458,10 @@ 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); diff --git a/src/addon/calendar/providers/mainmenu-handler.ts b/src/addon/calendar/providers/mainmenu-handler.ts index 0568eeb01..2b38a6507 100644 --- a/src/addon/calendar/providers/mainmenu-handler.ts +++ b/src/addon/calendar/providers/mainmenu-handler.ts @@ -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..a73e8d1a8 100644 --- a/src/addon/calendar/providers/sync-cron-handler.ts +++ b/src/addon/calendar/providers/sync-cron-handler.ts @@ -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..630c4c4fb 100644 --- a/src/addon/calendar/providers/view-link-handler.ts +++ b/src/addon/calendar/providers/view-link-handler.ts @@ -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/components/course/course.ts b/src/addon/competency/components/course/course.ts index 65cdb0643..c9c2a0c15 100644 --- a/src/addon/competency/components/course/course.ts +++ b/src/addon/competency/components/course/course.ts @@ -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,7 +83,7 @@ export class AddonCompetencyCourseComponent { /** * Opens the summary of a competency. * - * @param {number} competencyId + * @param competencyId */ openCompetencySummary(competencyId: number): void { this.navCtrl.push('AddonCompetencyCompetencySummaryPage', {competencyId}); @@ -92,7 +92,7 @@ export class AddonCompetencyCourseComponent { /** * 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.ts b/src/addon/competency/pages/competencies/competencies.ts index 1278e318f..40eed9b3d 100644 --- a/src/addon/competency/pages/competencies/competencies.ts +++ b/src/addon/competency/pages/competencies/competencies.ts @@ -69,7 +69,7 @@ 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; @@ -102,7 +102,7 @@ export class AddonCompetencyCompetenciesPage { /** * Opens a competency. * - * @param {number} competencyId + * @param competencyId */ openCompetency(competencyId: number): void { this.competencyId = competencyId; @@ -118,7 +118,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.ts b/src/addon/competency/pages/competency/competency.ts index c3930b945..a9999c740 100644 --- a/src/addon/competency/pages/competency/competency.ts +++ b/src/addon/competency/pages/competency/competency.ts @@ -76,7 +76,7 @@ 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; @@ -124,7 +124,7 @@ export class AddonCompetencyCompetencyPage { /** * Refreshes the competency. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshCompetency(refresher: any): void { let promise; @@ -144,7 +144,7 @@ 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. diff --git a/src/addon/competency/pages/competencysummary/competencysummary.ts b/src/addon/competency/pages/competencysummary/competencysummary.ts index db4c7a32d..fc81a80fc 100644 --- a/src/addon/competency/pages/competencysummary/competencysummary.ts +++ b/src/addon/competency/pages/competencysummary/competencysummary.ts @@ -55,7 +55,7 @@ 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) => { @@ -68,7 +68,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,7 +81,7 @@ 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. diff --git a/src/addon/competency/pages/plan/plan.ts b/src/addon/competency/pages/plan/plan.ts index 33a01c8b8..acbb1e419 100644 --- a/src/addon/competency/pages/plan/plan.ts +++ b/src/addon/competency/pages/plan/plan.ts @@ -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) => { @@ -74,7 +74,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 +88,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.ts b/src/addon/competency/pages/planlist/planlist.ts index 6c05ab924..04994ac20 100644 --- a/src/addon/competency/pages/planlist/planlist.ts +++ b/src/addon/competency/pages/planlist/planlist.ts @@ -62,7 +62,7 @@ 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) => { @@ -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,7 +102,7 @@ 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; diff --git a/src/addon/competency/providers/competency-link-handler.ts b/src/addon/competency/providers/competency-link-handler.ts index a85c8d1bb..9facae6b8 100644 --- a/src/addon/competency/providers/competency-link-handler.ts +++ b/src/addon/competency/providers/competency-link-handler.ts @@ -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..ea0f6df89 100644 --- a/src/addon/competency/providers/competency.ts +++ b/src/addon/competency/providers/competency.ts @@ -48,8 +48,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 +62,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 +72,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 +82,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 +93,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 +105,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 +116,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 +126,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,9 +143,9 @@ 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 { return this.sitesProvider.getSite(siteId).then((site) => { @@ -174,9 +174,9 @@ 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 plans are retrieved. */ getLearningPlan(planId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -204,10 +204,10 @@ 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 plans are retrieved. */ getCompetencyInPlan(planId: number, competencyId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -236,12 +236,12 @@ 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 plans are retrieved. */ getCompetencyInCourse(courseId: number, competencyId: number, userId?: number, siteId?: string, ignoreCache?: boolean) : Promise { @@ -279,11 +279,11 @@ 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 plans are retrieved. */ getCompetencySummary(competencyId: number, userId?: number, siteId?: string, ignoreCache?: boolean): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -318,11 +318,11 @@ 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 { return this.sitesProvider.getSite(siteId).then((site) => { @@ -373,9 +373,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 +388,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 +401,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 +415,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 +432,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 +448,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,13 +477,13 @@ 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 { @@ -519,12 +519,12 @@ 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 { @@ -558,10 +558,10 @@ 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 { if (competencyId) { diff --git a/src/addon/competency/providers/course-option-handler.ts b/src/addon/competency/providers/course-option-handler.ts index 2f8176995..8462d1ef2 100644 --- a/src/addon/competency/providers/course-option-handler.ts +++ b/src/addon/competency/providers/course-option-handler.ts @@ -30,7 +30,7 @@ export class AddonCompetencyCourseOptionHandler implements CoreCourseOptionsHand /** * 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 +39,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 +62,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 +77,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 +94,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. diff --git a/src/addon/competency/providers/helper.ts b/src/addon/competency/providers/helper.ts index bf95bd3ac..65f990a65 100644 --- a/src/addon/competency/providers/helper.ts +++ b/src/addon/competency/providers/helper.ts @@ -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..eea927c3d 100644 --- a/src/addon/competency/providers/mainmenu-handler.ts +++ b/src/addon/competency/providers/mainmenu-handler.ts @@ -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..07a7ae848 100644 --- a/src/addon/competency/providers/plan-link-handler.ts +++ b/src/addon/competency/providers/plan-link-handler.ts @@ -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..37a6f97c0 100644 --- a/src/addon/competency/providers/plans-link-handler.ts +++ b/src/addon/competency/providers/plans-link-handler.ts @@ -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..2122fe313 100644 --- a/src/addon/competency/providers/push-click-handler.ts +++ b/src/addon/competency/providers/push-click-handler.ts @@ -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..0af9deeef 100644 --- a/src/addon/competency/providers/user-competency-link-handler.ts +++ b/src/addon/competency/providers/user-competency-link-handler.ts @@ -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..b377f4e4d 100644 --- a/src/addon/competency/providers/user-handler.ts +++ b/src/addon/competency/providers/user-handler.ts @@ -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/report/report.ts b/src/addon/coursecompletion/components/report/report.ts index 50d12cf90..3b6371244 100644 --- a/src/addon/coursecompletion/components/report/report.ts +++ b/src/addon/coursecompletion/components/report/report.ts @@ -54,7 +54,7 @@ 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) => { @@ -77,7 +77,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/providers/course-option-handler.ts b/src/addon/coursecompletion/providers/course-option-handler.ts index d0483b68a..fa71437db 100644 --- a/src/addon/coursecompletion/providers/course-option-handler.ts +++ b/src/addon/coursecompletion/providers/course-option-handler.ts @@ -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..64b4c6f01 100644 --- a/src/addon/coursecompletion/providers/coursecompletion.ts +++ b/src/addon/coursecompletion/providers/coursecompletion.ts @@ -39,9 +39,9 @@ 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 { let selfCompletionActive = false, @@ -65,8 +65,8 @@ 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 { if (completion.completed) { @@ -90,11 +90,11 @@ 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 { return this.sitesProvider.getSite(siteId).then((site) => { @@ -125,9 +125,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 +136,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 +149,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 +158,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 +187,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,8 +242,8 @@ export class AddonCourseCompletionProvider { /** * Mark a course as self completed. * - * @param {number} courseId Course ID. - * @return {Promise} Resolved on success. + * @param courseId Course ID. + * @return Resolved on success. */ markCourseAsSelfCompleted(courseId: number): Promise { const params = { diff --git a/src/addon/coursecompletion/providers/user-handler.ts b/src/addon/coursecompletion/providers/user-handler.ts index 8faf49ccd..5e3924689 100644 --- a/src/addon/coursecompletion/providers/user-handler.ts +++ b/src/addon/coursecompletion/providers/user-handler.ts @@ -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/pages/list/list.ts b/src/addon/files/pages/list/list.ts index 2867acb5c..24aeb0390 100644 --- a/src/addon/files/pages/list/list.ts +++ b/src/addon/files/pages/list/list.ts @@ -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,7 +144,7 @@ export class AddonFilesListPage implements OnDestroy { /** * Fetch the files. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchFiles(): Promise { let promise; @@ -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..f9051494f 100644 --- a/src/addon/files/providers/files.ts +++ b/src/addon/files/providers/files.ts @@ -31,7 +31,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 +40,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 +49,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 +58,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,9 +69,9 @@ 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 { @@ -121,8 +121,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,7 +133,7 @@ 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 { return this.getFiles(this.getPrivateFilesRootParams()); @@ -142,7 +142,7 @@ export class AddonFilesProvider { /** * Get params to get root private files directory. * - * @return {any} Params. + * @return Params. */ protected getPrivateFilesRootParams(): any { return { @@ -160,9 +160,9 @@ 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 { return this.sitesProvider.getSite(siteId).then((site) => { @@ -183,8 +183,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 +193,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,7 +202,7 @@ export class AddonFilesProvider { /** * Get the site files. * - * @return {Promise} Promise resolved with the files. + * @return Promise resolved with the files. */ getSiteFiles(): Promise { return this.getFiles(this.getSiteFilesRootParams()); @@ -211,7 +211,7 @@ export class AddonFilesProvider { /** * Get params to get root site files directory. * - * @return {any} Params. + * @return Params. */ protected getSiteFilesRootParams(): any { return { @@ -227,10 +227,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 +252,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 +264,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 +279,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 +291,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 +303,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 +312,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 +324,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 +336,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 +348,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 +360,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 +372,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,9 +384,9 @@ 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 { const params = { @@ -404,8 +404,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) => { diff --git a/src/addon/files/providers/helper.ts b/src/addon/files/providers/helper.ts index 4962b88e0..7cb3fa32d 100644 --- a/src/addon/files/providers/helper.ts +++ b/src/addon/files/providers/helper.ts @@ -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..d362a44a8 100644 --- a/src/addon/files/providers/mainmenu-handler.ts +++ b/src/addon/files/providers/mainmenu-handler.ts @@ -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/messageoutput/airnotifier/pages/devices/devices.ts b/src/addon/messageoutput/airnotifier/pages/devices/devices.ts index ddb966afa..a9b581f37 100644 --- a/src/addon/messageoutput/airnotifier/pages/devices/devices.ts +++ b/src/addon/messageoutput/airnotifier/pages/devices/devices.ts @@ -47,7 +47,7 @@ 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) => { @@ -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,8 +107,8 @@ 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 { device.updating = true; diff --git a/src/addon/messageoutput/airnotifier/providers/airnotifier.ts b/src/addon/messageoutput/airnotifier/providers/airnotifier.ts index 129c1ebf0..e67e134bb 100644 --- a/src/addon/messageoutput/airnotifier/providers/airnotifier.ts +++ b/src/addon/messageoutput/airnotifier/providers/airnotifier.ts @@ -34,10 +34,10 @@ 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 { return this.sitesProvider.getSite(siteId).then((site) => { @@ -62,7 +62,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,8 +71,8 @@ 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 { this.logger.debug('Get user devices'); @@ -95,8 +95,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 +107,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 { diff --git a/src/addon/messageoutput/airnotifier/providers/handler.ts b/src/addon/messageoutput/airnotifier/providers/handler.ts index 8d5cc15ae..555c6e238 100644 --- a/src/addon/messageoutput/airnotifier/providers/handler.ts +++ b/src/addon/messageoutput/airnotifier/providers/handler.ts @@ -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/providers/delegate.ts b/src/addon/messageoutput/providers/delegate.ts index 36ef9f416..dc11821cb 100644 --- a/src/addon/messageoutput/providers/delegate.ts +++ b/src/addon/messageoutput/providers/delegate.ts @@ -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/confirmed-contacts/confirmed-contacts.ts b/src/addon/messages/components/confirmed-contacts/confirmed-contacts.ts index 528b48a9a..53983afe9 100644 --- a/src/addon/messages/components/confirmed-contacts/confirmed-contacts.ts +++ b/src/addon/messages/components/confirmed-contacts/confirmed-contacts.ts @@ -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/contact-requests.ts b/src/addon/messages/components/contact-requests/contact-requests.ts index 4a77bfdd1..1eb5aba36 100644 --- a/src/addon/messages/components/contact-requests/contact-requests.ts +++ b/src/addon/messages/components/contact-requests/contact-requests.ts @@ -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/contacts.ts b/src/addon/messages/components/contacts/contacts.ts index 8df67aabb..311196532 100644 --- a/src/addon/messages/components/contacts/contacts.ts +++ b/src/addon/messages/components/contacts/contacts.ts @@ -101,8 +101,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 +125,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 +147,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 +179,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 +196,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) => { @@ -214,8 +214,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; diff --git a/src/addon/messages/components/discussions/discussions.ts b/src/addon/messages/components/discussions/discussions.ts index 983e27a13..ddbd5c3f2 100644 --- a/src/addon/messages/components/discussions/discussions.ts +++ b/src/addon/messages/components/discussions/discussions.ts @@ -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/pages/contacts/contacts.ts b/src/addon/messages/pages/contacts/contacts.ts index 047961507..cde871624 100644 --- a/src/addon/messages/pages/contacts/contacts.ts +++ b/src/addon/messages/pages/contacts/contacts.ts @@ -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.ts b/src/addon/messages/pages/conversation-info/conversation-info.ts index b79ece62b..f28253463 100644 --- a/src/addon/messages/pages/conversation-info/conversation-info.ts +++ b/src/addon/messages/pages/conversation-info/conversation-info.ts @@ -52,7 +52,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 +69,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 +91,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 +106,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 +125,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.ts b/src/addon/messages/pages/discussion/discussion.ts index 26c1eb0fb..ef908e1c9 100644 --- a/src/addon/messages/pages/discussion/discussion.ts +++ b/src/addon/messages/pages/discussion/discussion.ts @@ -136,8 +136,8 @@ export class AddonMessagesDiscussionPage implements OnDestroy { /** * Adds a new message to the message list. * - * @param {any} message Message to be added. - * @param {boolean} [keep=true] If set the keep flag or not. + * @param message Message to be added. + * @param keep If set the keep flag or not. */ protected addMessage(message: any, keep: boolean = true): void { /* Create a hash to identify the message. The text of online messages isn't reliable because it can have random data @@ -156,7 +156,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { /** * Remove a message if it shouldn't be in the list anymore. * - * @param {string} hash Hash of the message to be removed. + * @param hash Hash of the message to be removed. */ protected removeMessage(hash: any): void { if (this.keepMessageMap[hash]) { @@ -197,7 +197,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { /** * Convenience function to fetch the conversation data. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected fetchData(): Promise { let loader; @@ -300,7 +300,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { /** * Convenience function to fetch messages. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected fetchMessages(): Promise { this.loadMoreError = false; @@ -351,7 +351,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { /** * Format and load a list of messages into the view. * - * @param {any[]} messages Messages to load. + * @param messages Messages to load. */ protected loadMessages(messages: any[]): void { if (this.viewDestroyed) { @@ -406,9 +406,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy { /** * Get the conversation. * - * @param {number} conversationId Conversation ID. - * @param {number} userId User ID. - * @return {Promise} Promise resolved with a boolean: whether the conversation exists or not. + * @param conversationId Conversation ID. + * @param userId User ID. + * @return Promise resolved with a boolean: whether the conversation exists or not. */ protected getConversation(conversationId: number, userId: number): Promise { let promise, @@ -491,9 +491,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy { /** * Get the messages of the conversation. Used if group messaging is supported. * - * @param {number} pagesToLoad Number of "pages" to load. - * @param {number} [offset=0] Offset for message list. - * @return {Promise} Promise resolved with the list of messages. + * @param pagesToLoad Number of "pages" to load. + * @param offset Offset for message list. + * @return Promise resolved with the list of messages. */ protected getConversationMessages(pagesToLoad: number, offset: number = 0): Promise { const excludePending = offset > 0; @@ -527,12 +527,12 @@ export class AddonMessagesDiscussionPage implements OnDestroy { /** * Get a discussion. Can load several "pages". * - * @param {number} pagesToLoad Number of pages to load. - * @param {number} [lfReceivedUnread=0] Number of unread received messages already fetched, so fetch will be done from this. - * @param {number} [lfReceivedRead=0] Number of read received messages already fetched, so fetch will be done from this. - * @param {number} [lfSentUnread=0] Number of unread sent messages already fetched, so fetch will be done from this. - * @param {number} [lfSentRead=0] Number of read sent messages already fetched, so fetch will be done from this. - * @return {Promise} Resolved when done. + * @param pagesToLoad Number of pages to load. + * @param lfReceivedUnread Number of unread received messages already fetched, so fetch will be done from this. + * @param lfReceivedRead Number of read received messages already fetched, so fetch will be done from this. + * @param lfSentUnread Number of unread sent messages already fetched, so fetch will be done from this. + * @param lfSentRead Number of read sent messages already fetched, so fetch will be done from this. + * @return Resolved when done. */ protected getDiscussionMessages(pagesToLoad: number, lfReceivedUnread: number = 0, lfReceivedRead: number = 0, lfSentUnread: number = 0, lfSentRead: number = 0): Promise { @@ -755,7 +755,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { /** * Wait until fetching is false. - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected waitForFetch(): Promise { if (!this.fetching) { @@ -806,7 +806,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { /** * Copy message to clipboard. * - * @param {any} message Message to be copied. + * @param message Message to be copied. */ copyMessage(message: any): void { const text = this.textUtils.decodeHTMLEntities(message.smallmessage || message.text || ''); @@ -816,8 +816,8 @@ export class AddonMessagesDiscussionPage implements OnDestroy { /** * Function to delete a message. * - * @param {any} message Message object to delete. - * @param {number} index Index where the message is to delete it from the view. + * @param message Message object to delete. + * @param index Index where the message is to delete it from the view. */ deleteMessage(message: any, index: number): void { const canDeleteAll = this.conversation && this.conversation.candeletemessagesforallusers, @@ -857,8 +857,8 @@ export class AddonMessagesDiscussionPage implements OnDestroy { /** * Function to load previous messages. * - * @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. */ loadPrevious(infiniteComplete?: any): Promise { let infiniteHeight = this.infinite ? this.infinite.getHeight() : 0; @@ -959,7 +959,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { /** * Sends a message to the server. * - * @param {string} text Message text. + * @param text Message text. */ sendMessage(text: string): void { let message; @@ -1046,9 +1046,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy { * Check date should be shown on message list for the current message. * If date has changed from previous to current message it should be shown. * - * @param {any} message Current message where to show the date. - * @param {any} [prevMessage] Previous message where to compare the date with. - * @return {boolean} If date has changed and should be shown. + * @param message Current message where to show the date. + * @param prevMessage Previous message where to compare the date with. + * @return If date has changed and should be shown. */ showDate(message: any, prevMessage?: any): boolean { if (!prevMessage) { @@ -1064,9 +1064,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy { * Check if the user info should be displayed for the current message. * User data is only displayed for group conversations if the previous message was from another user. * - * @param {any} message Current message where to show the user info. - * @param {any} [prevMessage] Previous message. - * @return {boolean} Whether user data should be shown. + * @param message Current message where to show the user info. + * @param prevMessage Previous message. + * @return Whether user data should be shown. */ showUserData(message: any, prevMessage?: any): boolean { return this.isGroup && message.useridfrom != this.currentUserId && this.members[message.useridfrom] && @@ -1076,9 +1076,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy { /** * Check if a css tail should be shown. * - * @param {any} message Current message where to show the user info. - * @param {any} [nextMessage] Next message. - * @return {boolean} Whether user data should be shown. + * @param message Current message where to show the user info. + * @param nextMessage Next message. + * @return Whether user data should be shown. */ showTail(message: any, nextMessage?: any): boolean { return !nextMessage || nextMessage.useridfrom != message.useridfrom || nextMessage.showDate; @@ -1125,7 +1125,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { /** * Change the favourite state of the current conversation. * - * @param {Function} [done] Function to call when done. + * @param done Function to call when done. */ changeFavourite(done?: () => void): void { this.favouriteIcon = 'spinner'; @@ -1153,7 +1153,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { /** * Change the mute state of the current conversation. * - * @param {Function} [done] Function to call when done. + * @param done Function to call when done. */ changeMute(done?: () => void): void { this.muteIcon = 'spinner'; @@ -1218,7 +1218,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { /** * Displays a confirmation modal to block the user of the individual conversation. * - * @return {Promise} Promise resolved when user is blocked or dialog is cancelled. + * @return Promise resolved when user is blocked or dialog is cancelled. */ blockUser(): Promise { if (!this.otherMember) { @@ -1249,7 +1249,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { /** * Delete the conversation. * - * @param {Function} [done] Function to call when done. + * @param done Function to call when done. */ deleteConversation(done?: () => void): void { const confirmMessage = 'addon.messages.' + (this.isSelf ? 'deleteallselfconfirm' : 'deleteallconfirm'); @@ -1276,7 +1276,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { /** * Displays a confirmation modal to unblock the user of the individual conversation. * - * @return {Promise} Promise resolved when user is unblocked or dialog is cancelled. + * @return Promise resolved when user is unblocked or dialog is cancelled. */ unblockUser(): Promise { if (!this.otherMember) { @@ -1307,7 +1307,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { /** * Displays a confirmation modal to send a contact request to the other user of the individual conversation. * - * @return {Promise} Promise resolved when the request is sent or the dialog is cancelled. + * @return Promise resolved when the request is sent or the dialog is cancelled. */ createContactRequest(): Promise { if (!this.otherMember) { @@ -1338,7 +1338,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { /** * Confirms the contact request of the other user of the individual conversation. * - * @return {Promise} Promise resolved when the request is confirmed. + * @return Promise resolved when the request is confirmed. */ confirmContactRequest(): Promise { if (!this.otherMember) { @@ -1360,7 +1360,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { /** * Declines the contact request of the other user of the individual conversation. * - * @return {Promise} Promise resolved when the request is confirmed. + * @return Promise resolved when the request is confirmed. */ declineContactRequest(): Promise { if (!this.otherMember) { @@ -1382,7 +1382,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { /** * Displays a confirmation modal to remove the other user of the conversation from contacts. * - * @return {Promise} Promise resolved when the request is sent or the dialog is cancelled. + * @return Promise resolved when the request is sent or the dialog is cancelled. */ removeContact(): Promise { if (!this.otherMember) { diff --git a/src/addon/messages/pages/group-conversations/group-conversations.ts b/src/addon/messages/pages/group-conversations/group-conversations.ts index 92b81f75e..de862bc39 100644 --- a/src/addon/messages/pages/group-conversations/group-conversations.ts +++ b/src/addon/messages/pages/group-conversations/group-conversations.ts @@ -256,8 +256,8 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { /** * Fetch conversations. * - * @param {booleam} [refreshUnreadCounts=true] Whether to refresh unread counts. - * @return {Promise} Promise resolved when done. + * @param refreshUnreadCounts Whether to refresh unread counts. + * @return Promise resolved when done. */ protected fetchData(refreshUnreadCounts: boolean = true): Promise { this.loadingMessage = this.loadingString; @@ -329,7 +329,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { /** * Fetch data for the expanded option. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchDataForExpandedOption(): Promise { const expandedOption = this.getExpandedOption(); @@ -344,10 +344,10 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { /** * Fetch data for a certain option. * - * @param {any} option The option to fetch data for. - * @param {boolean} [loadingMore} Whether we are loading more data or just the first ones. - * @param {booleam} [getCounts] Whether to get counts data. - * @return {Promise} Promise resolved when done. + * @param option The option to fetch data for. + * @param [loadingMore} Whether we are loading more data or just the first ones. + * @param getCounts Whether to get counts data. + * @return Promise resolved when done. */ fetchDataForOption(option: any, loadingMore?: boolean, getCounts?: boolean): Promise { option.loadMoreError = false; @@ -399,7 +399,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { /** * Fetch conversation counts. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchConversationCounts(): Promise { // Always try to get the latest data. @@ -417,10 +417,10 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { /** * Find a conversation in the list of loaded conversations. * - * @param {number} conversationId The conversation ID to search. - * @param {number} userId User ID to search (if no conversationId). - * @param {any} [option] The option to search in. If not defined, search in all options. - * @return {any} Conversation. + * @param conversationId The conversation ID to search. + * @param userId User ID to search (if no conversationId). + * @param option The option to search in. If not defined, search in all options. + * @return Conversation. */ protected findConversation(conversationId: number, userId?: number, option?: any): any { if (conversationId) { @@ -443,7 +443,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { /** * Get the option that is currently expanded, undefined if they are all collapsed. * - * @return {any} Option currently expanded. + * @return Option currently expanded. */ protected getExpandedOption(): any { if (this.favourites.expanded) { @@ -465,9 +465,9 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { /** * Navigate to a particular conversation. * - * @param {number} conversationId Conversation Id to load. - * @param {number} userId User of the conversation. Only if there is no conversationId. - * @param {number} [messageId] Message to scroll after loading the discussion. Used when searching. + * @param conversationId Conversation Id to load. + * @param userId User of the conversation. Only if there is no conversationId. + * @param messageId Message to scroll after loading the discussion. Used when searching. */ gotoConversation(conversationId: number, userId?: number, messageId?: number): void { this.selectedConversationId = conversationId; @@ -493,9 +493,9 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { /** * Function to load more conversations. * - * @param {any} option The option to fetch data for. - * @param {any} [infiniteComplete] Infinite scroll complete function. Only used from core-infinite-loading. - * @return {Promise} Resolved when done. + * @param option The option to fetch data for. + * @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading. + * @return Resolved when done. */ loadMoreConversations(option: any, infiniteComplete?: any): Promise { return this.fetchDataForOption(option, true).catch((error) => { @@ -509,9 +509,9 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { /** * Load offline messages into the conversations. * - * @param {any} option The option where the messages should be loaded. - * @param {any[]} messages Offline messages. - * @return {Promise} Promise resolved when done. + * @param option The option where the messages should be loaded. + * @param messages Offline messages. + * @return Promise resolved when done. */ protected loadOfflineMessages(option: any, messages: any[]): Promise { const promises = []; @@ -575,7 +575,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { /** * Add an offline conversation into the right list of conversations. * - * @param {any} conversation Offline conversation to add. + * @param conversation Offline conversation to add. */ protected addOfflineConversation(conversation: any): void { const option = this.getConversationOption(conversation); @@ -585,8 +585,8 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { /** * Add a last offline message into a conversation. * - * @param {any} conversation Conversation where to put the last message. - * @param {any} message Offline message to add. + * @param conversation Conversation where to put the last message. + * @param message Offline message to add. */ protected addLastOfflineMessage(conversation: any, message: any): void { conversation.lastmessage = message.text; @@ -598,8 +598,8 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { /** * Given a conversation, return its option (favourites, group, individual). * - * @param {any} conversation Conversation to check. - * @return {any} Option object. + * @param conversation Conversation to check. + * @return Option object. */ protected getConversationOption(conversation: any): any { if (conversation.isfavourite) { @@ -614,9 +614,9 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { /** * Refresh the data. * - * @param {any} [refresher] Refresher. - * @param {booleam} [refreshUnreadCounts=true] Whether to refresh unread counts. - * @return {Promise} Promise resolved when done. + * @param refresher Refresher. + * @param refreshUnreadCounts Whether to refresh unread counts. + * @return Promise resolved when done. */ refreshData(refresher?: any, refreshUnreadCounts: boolean = true): Promise { // Don't invalidate conversations and so, they always try to get latest data. @@ -636,7 +636,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { /** * Toogle the visibility of an option (expand/collapse). * - * @param {any} option The option to expand/collapse. + * @param option The option to expand/collapse. */ toggle(option: any): void { if (option.expanded) { @@ -654,9 +654,9 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { /** * Expand a certain option. * - * @param {any} option The option to expand. - * @param {booleam} [getCounts] Whether to get counts data. - * @return {Promise} Promise resolved when done. + * @param option The option to expand. + * @param getCounts Whether to get counts data. + * @return Promise resolved when done. */ protected expandOption(option: any, getCounts?: boolean): Promise { // Collapse all and expand the right one. diff --git a/src/addon/messages/pages/index/index.ts b/src/addon/messages/pages/index/index.ts index 66010ab05..44730992e 100644 --- a/src/addon/messages/pages/index/index.ts +++ b/src/addon/messages/pages/index/index.ts @@ -50,8 +50,8 @@ export class AddonMessagesIndexPage 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 discussionUserId Discussion Id to load. + * @param messageId Message to scroll after loading the discussion. Used when searching. */ gotoDiscussion(discussionUserId: number, messageId?: number): void { const params = { diff --git a/src/addon/messages/pages/search/search.ts b/src/addon/messages/pages/search/search.ts index 14d5b6ab1..54e5a74c7 100644 --- a/src/addon/messages/pages/search/search.ts +++ b/src/addon/messages/pages/search/search.ts @@ -103,10 +103,10 @@ export class AddonMessagesSearchPage implements OnDestroy { /** * Start a new search or load more results. * - * @param {string} query Text to search for. - * @param {strings} loadMore Load more contacts, noncontacts or messages. If undefined, start a new search. - * @param {any} [infiniteComplete] Infinite scroll complete function. Only used from core-infinite-loading. - * @return {Promise} Resolved when done. + * @param query Text to search for. + * @param loadMore Load more contacts, noncontacts or messages. If undefined, start a new search. + * @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading. + * @return Resolved when done. */ search(query: string, loadMore?: 'contacts' | 'noncontacts' | 'messages', infiniteComplete?: any): Promise { this.appProvider.closeKeyboard(); @@ -225,8 +225,8 @@ export class AddonMessagesSearchPage implements OnDestroy { /** * Open a conversation in the split view. * - * @param {any} result User or message. - * @param {boolean} [onInit=false] Whether the tser was selected on initial load. + * @param result User or message. + * @param onInit Whether the tser was selected on initial load. */ openConversation(result: any, onInit: boolean = false): void { if (!onInit || this.splitviewCtrl.isOn()) { @@ -244,8 +244,8 @@ export class AddonMessagesSearchPage implements OnDestroy { /** * Set the highlight values for each entry. * - * @param {any[]} results Results to highlight. - * @param {boolean} isUser Whether the results are from a user search or from a message search. + * @param results Results to highlight. + * @param isUser Whether the results are from a user search or from a message search. */ setHighlight(results: any[], isUser: boolean): void { results.forEach((result) => { diff --git a/src/addon/messages/pages/settings/settings.ts b/src/addon/messages/pages/settings/settings.ts index d7e30cd01..b2eed0099 100644 --- a/src/addon/messages/pages/settings/settings.ts +++ b/src/addon/messages/pages/settings/settings.ts @@ -78,7 +78,7 @@ export class AddonMessagesSettingsPage implements OnDestroy { /** * Fetches preference data. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected fetchPreferences(): Promise { return this.messagesProvider.getMessagePreferences().then((preferences) => { @@ -133,7 +133,7 @@ export class AddonMessagesSettingsPage implements OnDestroy { /** * Save the contactable privacy setting.. * - * @param {number|boolean} value The value to set. + * @param value The value to set. */ saveContactablePrivacy(value: number | boolean): void { if (this.contactablePrivacy == this.previousContactableValue) { @@ -164,9 +164,9 @@ export class AddonMessagesSettingsPage implements OnDestroy { /** * Change the value of a certain preference. * - * @param {any} notification Notification object. - * @param {string} state State name, ['loggedin', 'loggedoff']. - * @param {any} processor Notification processor. + * @param notification Notification object. + * @param state State name, ['loggedin', 'loggedoff']. + * @param processor Notification processor. */ changePreference(notification: any, state: string, processor: any): void { if (this.groupMessagingEnabled) { @@ -238,7 +238,7 @@ export class AddonMessagesSettingsPage implements OnDestroy { /** * Refresh the list of preferences. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshPreferences(refresher: any): void { this.messagesProvider.invalidateMessagePreferences().finally(() => { diff --git a/src/addon/messages/providers/contact-request-link-handler.ts b/src/addon/messages/providers/contact-request-link-handler.ts index aebf2e8cd..0b3696b41 100644 --- a/src/addon/messages/providers/contact-request-link-handler.ts +++ b/src/addon/messages/providers/contact-request-link-handler.ts @@ -33,11 +33,11 @@ export class AddonMessagesContactRequestLinkHandler extends CoreContentLinksHand /** * 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 { @@ -52,11 +52,11 @@ export class AddonMessagesContactRequestLinkHandler extends CoreContentLinksHand * 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.messagesProvider.isPluginEnabled(siteId).then((enabled) => { diff --git a/src/addon/messages/providers/discussion-link-handler.ts b/src/addon/messages/providers/discussion-link-handler.ts index b916798c6..623738241 100644 --- a/src/addon/messages/providers/discussion-link-handler.ts +++ b/src/addon/messages/providers/discussion-link-handler.ts @@ -36,11 +36,11 @@ export class AddonMessagesDiscussionLinkHandler 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 { @@ -58,11 +58,11 @@ export class AddonMessagesDiscussionLinkHandler 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 this.messagesProvider.isPluginEnabled(siteId).then((enabled) => { diff --git a/src/addon/messages/providers/index-link-handler.ts b/src/addon/messages/providers/index-link-handler.ts index 400bd4eb1..e711070c3 100644 --- a/src/addon/messages/providers/index-link-handler.ts +++ b/src/addon/messages/providers/index-link-handler.ts @@ -34,11 +34,11 @@ export class AddonMessagesIndexLinkHandler 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 { @@ -58,11 +58,11 @@ export class AddonMessagesIndexLinkHandler 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.messagesProvider.isPluginEnabled(siteId); diff --git a/src/addon/messages/providers/mainmenu-handler.ts b/src/addon/messages/providers/mainmenu-handler.ts index 0875f19b6..b39725450 100644 --- a/src/addon/messages/providers/mainmenu-handler.ts +++ b/src/addon/messages/providers/mainmenu-handler.ts @@ -89,7 +89,7 @@ export class AddonMessagesMainMenuHandler implements CoreMainMenuHandler, CoreCr /** * 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.messagesProvider.isPluginEnabled(); @@ -98,7 +98,7 @@ export class AddonMessagesMainMenuHandler implements CoreMainMenuHandler, CoreCr /** * Returns the data needed to render the handler. * - * @return {CoreMainMenuHandlerToDisplay} Data needed to render the handler. + * @return Data needed to render the handler. */ getDisplayData(): CoreMainMenuHandlerToDisplay { this.handler.page = this.messagesProvider.isGroupMessagingEnabled() ? @@ -114,9 +114,9 @@ export class AddonMessagesMainMenuHandler implements CoreMainMenuHandler, CoreCr /** * Refreshes badge number. * - * @param {string} [siteId] Site ID or current Site if undefined. - * @param {boolean} [unreadOnly] If true only the unread conversations count is refreshed. - * @return {Promise} Resolve when done. + * @param siteId Site ID or current Site if undefined. + * @param unreadOnly If true only the unread conversations count is refreshed. + * @return Resolve when done. */ refreshBadge(siteId?: string, unreadOnly?: boolean): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -147,7 +147,7 @@ export class AddonMessagesMainMenuHandler implements CoreMainMenuHandler, CoreCr /** * Update badge number and push notifications counter from loaded data. * - * @param {string} siteId Site ID. + * @param siteId Site ID. */ updateBadge(siteId: string): void { const totalCount = this.unreadCount + (this.contactRequestsCount || 0); @@ -165,9 +165,9 @@ export class AddonMessagesMainMenuHandler implements CoreMainMenuHandler, CoreCr * 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 { if (this.sitesProvider.isCurrentSite(siteId)) { @@ -186,7 +186,7 @@ export class AddonMessagesMainMenuHandler implements CoreMainMenuHandler, CoreCr /** * Get the time between consecutive executions. * - * @return {number} Time between consecutive executions (in ms). + * @return Time between consecutive executions (in ms). */ getInterval(): number { if (this.appProvider.isDesktop()) { @@ -201,7 +201,7 @@ export class AddonMessagesMainMenuHandler implements CoreMainMenuHandler, CoreCr /** * Whether it's a synchronization process or not. * - * @return {boolean} True if is a sync process, false otherwise. + * @return True if is a sync process, false otherwise. */ isSync(): boolean { // This is done to use only wifi if using the fallback function. @@ -217,7 +217,7 @@ export class AddonMessagesMainMenuHandler implements CoreMainMenuHandler, CoreCr /** * Whether the process should be executed during a manual sync. * - * @return {boolean} True if is a manual sync process, false otherwise. + * @return True if is a manual sync process, false otherwise. */ canManualSync(): boolean { return true; @@ -226,8 +226,8 @@ export class AddonMessagesMainMenuHandler implements CoreMainMenuHandler, CoreCr /** * Get the latest unread received messages from a site. * - * @param {string} [siteId] Site ID. Default current. - * @return {Promise} Promise resolved with the notifications. + * @param siteId Site ID. Default current. + * @return Promise resolved with the notifications. */ protected fetchMessages(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -289,8 +289,8 @@ export class AddonMessagesMainMenuHandler implements CoreMainMenuHandler, CoreCr /** * Given a message, return the title and the text for the message. * - * @param {any} message Message. - * @return {Promise} Promise resolved with an object with title and text. + * @param message Message. + * @return Promise resolved with an object with title and text. */ protected getTitleAndText(message: any): Promise { const data = { diff --git a/src/addon/messages/providers/messages-offline.ts b/src/addon/messages/providers/messages-offline.ts index 38737c50e..d9818cd83 100644 --- a/src/addon/messages/providers/messages-offline.ts +++ b/src/addon/messages/providers/messages-offline.ts @@ -97,11 +97,11 @@ export class AddonMessagesOfflineProvider { /** * Delete a message. * - * @param {number} conversationId Conversation ID. - * @param {string} message The message. - * @param {number} timeCreated The time the message was created. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if stored, rejected if failure. + * @param conversationId Conversation ID. + * @param message The message. + * @param timeCreated The time the message was created. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if stored, rejected if failure. */ deleteConversationMessage(conversationId: number, message: string, timeCreated: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -116,9 +116,9 @@ export class AddonMessagesOfflineProvider { /** * Delete all the messages in a conversation. * - * @param {number} conversationId Conversation ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if stored, rejected if failure. + * @param conversationId Conversation ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if stored, rejected if failure. */ deleteConversationMessages(conversationId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -131,11 +131,11 @@ export class AddonMessagesOfflineProvider { /** * Delete a message. * - * @param {number} toUserId User ID to send the message to. - * @param {string} message The message. - * @param {number} timeCreated The time the message was created. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if stored, rejected if failure. + * @param toUserId User ID to send the message to. + * @param message The message. + * @param timeCreated The time the message was created. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if stored, rejected if failure. */ deleteMessage(toUserId: number, message: string, timeCreated: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -150,8 +150,8 @@ export class AddonMessagesOfflineProvider { /** * Get all messages where deviceoffline is set to 1. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with messages. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with messages. */ getAllDeviceOfflineMessages(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -171,8 +171,8 @@ export class AddonMessagesOfflineProvider { /** * Get all offline messages. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with messages. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with messages. */ getAllMessages(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -192,9 +192,9 @@ export class AddonMessagesOfflineProvider { /** * Get offline messages to send to a certain user. * - * @param {number} conversationId Conversation ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with messages. + * @param conversationId Conversation ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with messages. */ getConversationMessages(conversationId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -209,9 +209,9 @@ export class AddonMessagesOfflineProvider { /** * Get offline messages to send to a certain user. * - * @param {number} toUserId User ID to get messages to. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with messages. + * @param toUserId User ID to get messages to. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with messages. */ getMessages(toUserId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -222,9 +222,9 @@ export class AddonMessagesOfflineProvider { /** * Check if there are offline messages to send to a conversation. * - * @param {number} conversationId Conversation ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean: true if has offline messages, false otherwise. + * @param conversationId Conversation ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: true if has offline messages, false otherwise. */ hasConversationMessages(conversationId: number, siteId?: string): Promise { return this.getConversationMessages(conversationId, siteId).then((messages) => { @@ -235,9 +235,9 @@ export class AddonMessagesOfflineProvider { /** * Check if there are offline messages to send to a certain user. * - * @param {number} toUserId User ID to check. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean: true if has offline messages, false otherwise. + * @param toUserId User ID to check. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: true if has offline messages, false otherwise. */ hasMessages(toUserId: number, siteId?: string): Promise { return this.getMessages(toUserId, siteId).then((messages) => { @@ -248,8 +248,8 @@ export class AddonMessagesOfflineProvider { /** * Parse some fields of each offline conversation messages. * - * @param {any[]} messages List of messages to parse. - * @return {any[]} Parsed messages. + * @param messages List of messages to parse. + * @return Parsed messages. */ protected parseConversationMessages(messages: any[]): any[] { if (!messages) { @@ -268,10 +268,10 @@ export class AddonMessagesOfflineProvider { /** * Save a conversation message to be sent later. * - * @param {any} conversation Conversation. - * @param {string} message The message to send. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if stored, rejected if failure. + * @param conversation Conversation. + * @param message The message to send. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if stored, rejected if failure. */ saveConversationMessage(conversation: any, message: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -298,10 +298,10 @@ export class AddonMessagesOfflineProvider { /** * Save a message to be sent later. * - * @param {number} toUserId User ID recipient of the message. - * @param {string} message The message to send. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if stored, rejected if failure. + * @param toUserId User ID recipient of the message. + * @param message The message to send. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if stored, rejected if failure. */ saveMessage(toUserId: number, message: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -322,10 +322,10 @@ export class AddonMessagesOfflineProvider { /** * Set deviceoffline for a group of messages. * - * @param {any} messages Messages to update. Should be the same entry as retrieved from the DB. - * @param {boolean} value Value to set. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if stored, rejected if failure. + * @param messages Messages to update. Should be the same entry as retrieved from the DB. + * @param value Value to set. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if stored, rejected if failure. */ setMessagesDeviceOffline(messages: any, value: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { diff --git a/src/addon/messages/providers/messages.ts b/src/addon/messages/providers/messages.ts index b4181773a..a16d9810f 100644 --- a/src/addon/messages/providers/messages.ts +++ b/src/addon/messages/providers/messages.ts @@ -67,9 +67,9 @@ export class AddonMessagesProvider { /** * Add a contact. * - * @param {number} userId User ID of the person to add. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Resolved when done. + * @param userId User ID of the person to add. + * @param siteId Site ID. If not defined, use current site. + * @return Resolved when done. * @deprecated since Moodle 3.6 */ addContact(userId: number, siteId?: string): Promise { @@ -87,9 +87,9 @@ export class AddonMessagesProvider { /** * Block a user. * - * @param {number} userId User ID of the person to block. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Resolved when done. + * @param userId User ID of the person to block. + * @param siteId Site ID. If not defined, use current site. + * @return Resolved when done. */ blockContact(userId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -120,9 +120,9 @@ export class AddonMessagesProvider { /** * Confirm a contact request from another user. * - * @param {number} userId ID of the user who made the contact request. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Resolved when done. + * @param userId ID of the user who made the contact request. + * @param siteId Site ID. If not defined, use current site. + * @return Resolved when done. * @since 3.6 */ confirmContactRequest(userId: number, siteId?: string): Promise { @@ -149,9 +149,9 @@ export class AddonMessagesProvider { /** * Send a contact request to another user. * - * @param {number} userId ID of the receiver of the contact request. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Resolved when done. + * @param userId ID of the receiver of the contact request. + * @param siteId Site ID. If not defined, use current site. + * @return Resolved when done. * @since 3.6 */ createContactRequest(userId: number, siteId?: string): Promise { @@ -173,9 +173,9 @@ export class AddonMessagesProvider { /** * Decline a contact request from another user. * - * @param {number} userId ID of the user who made the contact request. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Resolved when done. + * @param userId ID of the user who made the contact request. + * @param siteId Site ID. If not defined, use current site. + * @return Resolved when done. * @since 3.6 */ declineContactRequest(userId: number, siteId?: string): Promise { @@ -200,10 +200,10 @@ export class AddonMessagesProvider { /** * Delete a conversation. * - * @param {number} conversationId Conversation to delete. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @param {number} [userId] User ID. If not defined, current user in the site. - * @return {Promise} Promise resolved when the conversation has been deleted. + * @param conversationId Conversation to delete. + * @param siteId Site ID. If not defined, use current site. + * @param userId User ID. If not defined, current user in the site. + * @return Promise resolved when the conversation has been deleted. */ deleteConversation(conversationId: number, siteId?: string, userId?: number): Promise { return this.deleteConversations([conversationId], siteId, userId); @@ -212,10 +212,10 @@ export class AddonMessagesProvider { /** * Delete several conversations. * - * @param {number[]} conversationIds Conversations to delete. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @param {number} [userId] User ID. If not defined, current user in the site. - * @return {Promise} Promise resolved when the conversations have been deleted. + * @param conversationIds Conversations to delete. + * @param siteId Site ID. If not defined, use current site. + * @param userId User ID. If not defined, current user in the site. + * @return Promise resolved when the conversations have been deleted. */ deleteConversations(conversationIds: number[], siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -243,9 +243,9 @@ export class AddonMessagesProvider { /** * Delete a message (online or offline). * - * @param {any} message Message to delete. - * @param {boolean} [deleteForAll] Whether the message should be deleted for all users. - * @return {Promise} Promise resolved when the message has been deleted. + * @param message Message to delete. + * @param deleteForAll Whether the message should be deleted for all users. + * @return Promise resolved when the message has been deleted. */ deleteMessage(message: any, deleteForAll?: boolean): Promise { if (message.id) { @@ -268,10 +268,10 @@ export class AddonMessagesProvider { /** * Delete a message from the server. * - * @param {number} id Message ID. - * @param {number} read 1 if message is read, 0 otherwise. - * @param {number} [userId] User we want to delete the message for. If not defined, use current user. - * @return {Promise} Promise resolved when the message has been deleted. + * @param id Message ID. + * @param read 1 if message is read, 0 otherwise. + * @param userId User we want to delete the message for. If not defined, use current user. + * @return Promise resolved when the message has been deleted. */ deleteMessageOnline(id: number, read: number, userId?: number): Promise { const params: any = { @@ -291,9 +291,9 @@ export class AddonMessagesProvider { /** * Delete a message for all users. * - * @param {number} id Message ID. - * @param {number} [userId] User we want to delete the message for. If not defined, use current user. - * @return {Promise} Promise resolved when the message has been deleted. + * @param id Message ID. + * @param userId User we want to delete the message for. If not defined, use current user. + * @return Promise resolved when the message has been deleted. */ deleteMessageForAllOnline(id: number, userId?: number): Promise { const params: any = { @@ -309,9 +309,9 @@ export class AddonMessagesProvider { /** * Format a conversation. * - * @param {any} conversation Conversation to format. - * @param {number} userId User ID viewing the conversation. - * @return {any} Formatted conversation. + * @param conversation Conversation to format. + * @param userId User ID viewing the conversation. + * @return Formatted conversation. */ protected formatConversation(conversation: any, userId: number): any { const numMessages = conversation.messages.length, @@ -346,8 +346,8 @@ export class AddonMessagesProvider { /** * Get the cache key for blocked contacts. * - * @param {number} userId The user who's contacts we're looking for. - * @return {string} Cache key. + * @param userId The user who's contacts we're looking for. + * @return Cache key. */ protected getCacheKeyForBlockedContacts(userId: number): string { return this.ROOT_CACHE_KEY + 'blockedContacts:' + userId; @@ -356,7 +356,7 @@ export class AddonMessagesProvider { /** * Get the cache key for contacts. * - * @return {string} Cache key. + * @return Cache key. */ protected getCacheKeyForContacts(): string { return this.ROOT_CACHE_KEY + 'contacts'; @@ -365,7 +365,7 @@ export class AddonMessagesProvider { /** * Get the cache key for comfirmed contacts. * - * @return {string} Cache key. + * @return Cache key. */ protected getCacheKeyForUserContacts(): string { return this.ROOT_CACHE_KEY + 'userContacts'; @@ -374,7 +374,7 @@ export class AddonMessagesProvider { /** * Get the cache key for contact requests. * - * @return {string} Cache key. + * @return Cache key. */ protected getCacheKeyForContactRequests(): string { return this.ROOT_CACHE_KEY + 'contactRequests'; @@ -383,7 +383,7 @@ export class AddonMessagesProvider { /** * Get the cache key for contact requests count. * - * @return {string} Cache key. + * @return Cache key. */ protected getCacheKeyForContactRequestsCount(): string { return this.ROOT_CACHE_KEY + 'contactRequestsCount'; @@ -392,8 +392,8 @@ export class AddonMessagesProvider { /** * Get the cache key for a discussion. * - * @param {number} userId The other person with whom the current user is having the discussion. - * @return {string} Cache key. + * @param userId The other person with whom the current user is having the discussion. + * @return Cache key. */ protected getCacheKeyForDiscussion(userId: number): string { return this.ROOT_CACHE_KEY + 'discussion:' + userId; @@ -402,8 +402,8 @@ export class AddonMessagesProvider { /** * Get the cache key for the message count. * - * @param {number} userId User ID. - * @return {string} Cache key. + * @param userId User ID. + * @return Cache key. */ protected getCacheKeyForMessageCount(userId: number): string { return this.ROOT_CACHE_KEY + 'count:' + userId; @@ -412,7 +412,7 @@ export class AddonMessagesProvider { /** * Get the cache key for unread conversation counts. * - * @return {string} Cache key. + * @return Cache key. */ protected getCacheKeyForUnreadConversationCounts(): string { return this.ROOT_CACHE_KEY + 'unreadConversationCounts'; @@ -421,7 +421,7 @@ export class AddonMessagesProvider { /** * Get the cache key for the list of discussions. * - * @return {string} Cache key. + * @return Cache key. */ protected getCacheKeyForDiscussions(): string { return this.ROOT_CACHE_KEY + 'discussions'; @@ -430,9 +430,9 @@ export class AddonMessagesProvider { /** * Get cache key for get conversations. * - * @param {number} userId User ID. - * @param {number} conversationId Conversation ID. - * @return {string} Cache key. + * @param userId User ID. + * @param conversationId Conversation ID. + * @return Cache key. */ protected getCacheKeyForConversation(userId: number, conversationId: number): string { return this.ROOT_CACHE_KEY + 'conversation:' + userId + ':' + conversationId; @@ -441,9 +441,9 @@ export class AddonMessagesProvider { /** * Get cache key for get conversations between users. * - * @param {number} userId User ID. - * @param {number} otherUserId Other user ID. - * @return {string} Cache key. + * @param userId User ID. + * @param otherUserId Other user ID. + * @return Cache key. */ protected getCacheKeyForConversationBetweenUsers(userId: number, otherUserId: number): string { return this.ROOT_CACHE_KEY + 'conversationBetweenUsers:' + userId + ':' + otherUserId; @@ -452,9 +452,9 @@ export class AddonMessagesProvider { /** * Get cache key for get conversation members. * - * @param {number} userId User ID. - * @param {number} conversationId Conversation ID. - * @return {string} Cache key. + * @param userId User ID. + * @param conversationId Conversation ID. + * @return Cache key. */ protected getCacheKeyForConversationMembers(userId: number, conversationId: number): string { return this.ROOT_CACHE_KEY + 'conversationMembers:' + userId + ':' + conversationId; @@ -463,9 +463,9 @@ export class AddonMessagesProvider { /** * Get cache key for get conversation messages. * - * @param {number} userId User ID. - * @param {number} conversationId Conversation ID. - * @return {string} Cache key. + * @param userId User ID. + * @param conversationId Conversation ID. + * @return Cache key. */ protected getCacheKeyForConversationMessages(userId: number, conversationId: number): string { return this.ROOT_CACHE_KEY + 'conversationMessages:' + userId + ':' + conversationId; @@ -474,10 +474,10 @@ export class AddonMessagesProvider { /** * Get cache key for get conversations. * - * @param {number} userId User ID. - * @param {number} [type] Filter by type. - * @param {boolean} [favourites] Filter favourites. - * @return {string} Cache key. + * @param userId User ID. + * @param type Filter by type. + * @param favourites Filter favourites. + * @return Cache key. */ protected getCacheKeyForConversations(userId: number, type?: number, favourites?: boolean): string { return this.getCommonCacheKeyForUserConversations(userId) + ':' + type + ':' + favourites; @@ -486,7 +486,7 @@ export class AddonMessagesProvider { /** * Get cache key for conversation counts. * - * @return {string} Cache key. + * @return Cache key. */ protected getCacheKeyForConversationCounts(): string { return this.ROOT_CACHE_KEY + 'conversationCounts'; @@ -495,9 +495,9 @@ export class AddonMessagesProvider { /** * Get cache key for member info. * - * @param {number} userId User ID. - * @param {number} otherUserId The other user ID. - * @return {string} Cache key. + * @param userId User ID. + * @param otherUserId The other user ID. + * @return Cache key. */ protected getCacheKeyForMemberInfo(userId: number, otherUserId: number): string { return this.ROOT_CACHE_KEY + 'memberInfo:' + userId + ':' + otherUserId; @@ -506,8 +506,8 @@ export class AddonMessagesProvider { /** * Get cache key for get self conversation. * - * @param {number} userId User ID. - * @return {string} Cache key. + * @param userId User ID. + * @return Cache key. */ protected getCacheKeyForSelfConversation(userId: number): string { return this.ROOT_CACHE_KEY + 'selfconversation:' + userId; @@ -516,8 +516,8 @@ export class AddonMessagesProvider { /** * Get common cache key for get user conversations. * - * @param {number} userId User ID. - * @return {string} Cache key. + * @param userId User ID. + * @return Cache key. */ protected getCommonCacheKeyForUserConversations(userId: number): string { return this.getRootCacheKeyForConversations() + userId; @@ -526,7 +526,7 @@ export class AddonMessagesProvider { /** * Get root cache key for get conversations. * - * @return {string} Cache key. + * @return Cache key. */ protected getRootCacheKeyForConversations(): string { return this.ROOT_CACHE_KEY + 'conversations:'; @@ -535,8 +535,8 @@ export class AddonMessagesProvider { /** * Get all the contacts of the current user. * - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Resolved with the WS data. + * @param siteId Site ID. If not defined, use current site. + * @return Resolved with the WS data. * @deprecated since Moodle 3.6 */ getAllContacts(siteId?: string): Promise { @@ -561,8 +561,8 @@ export class AddonMessagesProvider { /** * Get all the users blocked by the current user. * - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Resolved with the WS data. + * @param siteId Site ID. If not defined, use current site. + * @return Resolved with the WS data. */ getBlockedContacts(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -584,8 +584,8 @@ export class AddonMessagesProvider { * * This excludes the blocked users. * - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Resolved with the WS data. + * @param siteId Site ID. If not defined, use current site. + * @return Resolved with the WS data. * @deprecated since Moodle 3.6 */ getContacts(siteId?: string): Promise { @@ -618,10 +618,10 @@ export class AddonMessagesProvider { /** * Get the list of user contacts. * - * @param {number} [limitFrom=0] Position of the first contact to fetch. - * @param {number} [limitNum] Number of contacts to fetch. Default is AddonMessagesProvider.LIMIT_CONTACTS. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise<{contacts: any[], canLoadMore: boolean}>} Resolved with the list of user contacts. + * @param limitFrom Position of the first contact to fetch. + * @param limitNum Number of contacts to fetch. Default is AddonMessagesProvider.LIMIT_CONTACTS. + * @param siteId Site ID. If not defined, use current site. + * @return Resolved with the list of user contacts. * @since 3.6 */ getUserContacts(limitFrom: number = 0, limitNum: number = AddonMessagesProvider.LIMIT_CONTACTS , siteId?: string): @@ -660,10 +660,10 @@ export class AddonMessagesProvider { /** * Get the contact request sent to the current user. * - * @param {number} [limitFrom=0] Position of the first contact request to fetch. - * @param {number} [limitNum] Number of contact requests to fetch. Default is AddonMessagesProvider.LIMIT_CONTACTS. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise<{requests: any[], canLoadMore: boolean}>} Resolved with the list of contact requests. + * @param limitFrom Position of the first contact request to fetch. + * @param limitNum Number of contact requests to fetch. Default is AddonMessagesProvider.LIMIT_CONTACTS. + * @param siteId Site ID. If not defined, use current site. + * @return Resolved with the list of contact requests. * @since 3.6 */ getContactRequests(limitFrom: number = 0, limitNum: number = AddonMessagesProvider.LIMIT_CONTACTS, siteId?: string): @@ -702,8 +702,8 @@ export class AddonMessagesProvider { /** * Get the number of contact requests sent to the current user. * - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Resolved with the number of contact requests. + * @param siteId Site ID. If not defined, use current site. + * @return Resolved with the number of contact requests. * @since 3.6 */ getContactRequestsCount(siteId?: string): Promise { @@ -728,19 +728,19 @@ export class AddonMessagesProvider { /** * Get a conversation by the conversation ID. * - * @param {number} conversationId Conversation ID to fetch. - * @param {boolean} [includeContactRequests] Include contact requests. - * @param {boolean} [includePrivacyInfo] Include privacy info. - * @param {number} [messageOffset=0] Offset for messages list. - * @param {number} [messageLimit=1] Limit of messages. Defaults to 1 (last message). - * We recommend getConversationMessages to get them. - * @param {number} [memberOffset=0] Offset for members list. - * @param {number} [memberLimit=2] Limit of members. Defaults to 2 (to be able to know the other user in individual ones). - * We recommend getConversationMembers to get them. - * @param {boolean} [newestFirst=true] Whether to order messages by newest first. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @param {number} [userId] User ID. If not defined, current user in the site. - * @return {Promise} Promise resolved with the response. + * @param conversationId Conversation ID to fetch. + * @param includeContactRequests Include contact requests. + * @param includePrivacyInfo Include privacy info. + * @param messageOffset Offset for messages list. + * @param messageLimit Limit of messages. Defaults to 1 (last message). + * We recommend getConversationMessages to get them. + * @param memberOffset Offset for members list. + * @param memberLimit Limit of members. Defaults to 2 (to be able to know the other user in individual ones). + * We recommend getConversationMembers to get them. + * @param newestFirst Whether to order messages by newest first. + * @param siteId Site ID. If not defined, use current site. + * @param userId User ID. If not defined, current user in the site. + * @return Promise resolved with the response. * @since 3.6 */ getConversation(conversationId: number, includeContactRequests?: boolean, includePrivacyInfo?: boolean, @@ -774,20 +774,20 @@ export class AddonMessagesProvider { /** * Get a conversation between two users. * - * @param {number} otherUserId The other user ID. - * @param {boolean} [includeContactRequests] Include contact requests. - * @param {boolean} [includePrivacyInfo] Include privacy info. - * @param {number} [messageOffset=0] Offset for messages list. - * @param {number} [messageLimit=1] Limit of messages. Defaults to 1 (last message). - * We recommend getConversationMessages to get them. - * @param {number} [memberOffset=0] Offset for members list. - * @param {number} [memberLimit=2] Limit of members. Defaults to 2 (to be able to know the other user in individual ones). - * We recommend getConversationMembers to get them. - * @param {boolean} [newestFirst=true] Whether to order messages by newest first. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @param {number} [userId] User ID. If not defined, current user in the site. - * @param {boolean} [preferCache] True if shouldn't call WS if data is cached, false otherwise. - * @return {Promise} Promise resolved with the response. + * @param otherUserId The other user ID. + * @param includeContactRequests Include contact requests. + * @param includePrivacyInfo Include privacy info. + * @param messageOffset Offset for messages list. + * @param messageLimit Limit of messages. Defaults to 1 (last message). + * We recommend getConversationMessages to get them. + * @param memberOffset Offset for members list. + * @param memberLimit Limit of members. Defaults to 2 (to be able to know the other user in individual ones). + * We recommend getConversationMembers to get them. + * @param newestFirst Whether to order messages by newest first. + * @param siteId Site ID. If not defined, use current site. + * @param userId User ID. If not defined, current user in the site. + * @param preferCache True if shouldn't call WS if data is cached, false otherwise. + * @return Promise resolved with the response. * @since 3.6 */ getConversationBetweenUsers(otherUserId: number, includeContactRequests?: boolean, includePrivacyInfo?: boolean, @@ -822,12 +822,12 @@ export class AddonMessagesProvider { /** * Get a conversation members. * - * @param {number} conversationId Conversation ID to fetch. - * @param {number} [limitFrom=0] Offset for members list. - * @param {number} [limitTo] Limit of members. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @param {number} [userId] User ID. If not defined, current user in the site. - * @return {Promise} Promise resolved with the response. + * @param conversationId Conversation ID to fetch. + * @param limitFrom Offset for members list. + * @param limitTo Limit of members. + * @param siteId Site ID. If not defined, use current site. + * @param userId User ID. If not defined, current user in the site. + * @return Promise resolved with the response. * @since 3.6 */ getConversationMembers(conversationId: number, limitFrom: number = 0, limitTo?: number, includeContactRequests?: boolean, @@ -872,15 +872,15 @@ export class AddonMessagesProvider { /** * Get a conversation by the conversation ID. * - * @param {number} conversationId Conversation ID to fetch. - * @param {boolean} excludePending True to exclude messages pending to be sent. - * @param {number} [limitFrom=0] Offset for messages list. - * @param {number} [limitTo] Limit of messages. - * @param {boolean} [newestFirst=true] Whether to order messages by newest first. - * @param {number} [timeFrom] The timestamp from which the messages were created. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @param {number} [userId] User ID. If not defined, current user in the site. - * @return {Promise} Promise resolved with the response. + * @param conversationId Conversation ID to fetch. + * @param excludePending True to exclude messages pending to be sent. + * @param limitFrom Offset for messages list. + * @param limitTo Limit of messages. + * @param newestFirst Whether to order messages by newest first. + * @param timeFrom The timestamp from which the messages were created. + * @param siteId Site ID. If not defined, use current site. + * @param userId User ID. If not defined, current user in the site. + * @return Promise resolved with the response. * @since 3.6 */ getConversationMessages(conversationId: number, excludePending: boolean, limitFrom: number = 0, limitTo?: number, @@ -963,15 +963,15 @@ export class AddonMessagesProvider { * Get the discussions of a certain user. This function is used in Moodle sites higher than 3.6. * If the site is older than 3.6, please use getDiscussions. * - * @param {number} [type] Filter by type. - * @param {boolean} [favourites] Whether to restrict the results to contain NO favourite conversations (false), ONLY favourite - * conversation (true), or ignore any restriction altogether (undefined or null). - * @param {number} [limitFrom=0] The offset to start at. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @param {number} [userId] User ID. If not defined, current user in the site. - * @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). - * @return {Promise} Promise resolved with the conversations. + * @param type Filter by type. + * @param favourites Whether to restrict the results to contain NO favourite conversations (false), ONLY favourite + * conversation (true), or ignore any restriction altogether (undefined or null). + * @param limitFrom The offset to start at. + * @param siteId Site ID. If not defined, use current site. + * @param userId User ID. If not defined, current user in the site. + * @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). + * @return Promise resolved with the conversations. * @since 3.6 */ getConversations(type?: number, favourites?: boolean, limitFrom: number = 0, siteId?: string, userId?: number, @@ -1041,9 +1041,9 @@ export class AddonMessagesProvider { /** * Get conversation counts by type. * - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Promise resolved with favourite, - * individual, group and self conversation counts. + * @param siteId Site ID. If not defined, use current site. + * @return Promise resolved with favourite, + * individual, group and self conversation counts. * @since 3.6 */ getConversationCounts(siteId?: string): Promise<{favourites: number, individual: number, group: number, self: number}> { @@ -1069,15 +1069,15 @@ export class AddonMessagesProvider { /** * Return the current user's discussion with another user. * - * @param {number} userId The ID of the other user. - * @param {boolean} excludePending True to exclude messages pending to be sent. - * @param {number} [lfReceivedUnread=0] Number of unread received messages already fetched, so fetch will be done from this. - * @param {number} [lfReceivedRead=0] Number of read received messages already fetched, so fetch will be done from this. - * @param {number} [lfSentUnread=0] Number of unread sent messages already fetched, so fetch will be done from this. - * @param {number} [lfSentRead=0] Number of read sent messages already fetched, so fetch will be done from this. - * @param {boolean} [toDisplay=true] True if messages will be displayed to the user, either in view or in a notification. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Promise resolved with messages and a boolean telling if can load more messages. + * @param userId The ID of the other user. + * @param excludePending True to exclude messages pending to be sent. + * @param lfReceivedUnread Number of unread received messages already fetched, so fetch will be done from this. + * @param lfReceivedRead Number of read received messages already fetched, so fetch will be done from this. + * @param lfSentUnread Number of unread sent messages already fetched, so fetch will be done from this. + * @param lfSentRead Number of read sent messages already fetched, so fetch will be done from this. + * @param toDisplay True if messages will be displayed to the user, either in view or in a notification. + * @param siteId Site ID. If not defined, use current site. + * @return Promise resolved with messages and a boolean telling if can load more messages. */ getDiscussion(userId: number, excludePending: boolean, lfReceivedUnread: number = 0, lfReceivedRead: number = 0, lfSentUnread: number = 0, lfSentRead: number = 0, toDisplay: boolean = true, siteId?: string): Promise { @@ -1152,8 +1152,8 @@ export class AddonMessagesProvider { * Get the discussions of the current user. This function is used in Moodle sites older than 3.6. * If the site is 3.6 or higher, please use getConversations. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Resolved with an object where the keys are the user ID of the other user. + * @param siteId Site ID. If not defined, current site. + * @return Resolved with an object where the keys are the user ID of the other user. */ getDiscussions(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1240,9 +1240,9 @@ export class AddonMessagesProvider { /** * Get user images for all the discussions that don't have one already. * - * @param {any} discussions List of discussions. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise always resolved. Resolve param is the formatted discussions. + * @param discussions List of discussions. + * @param siteId Site ID. If not defined, current site. + * @return Promise always resolved. Resolve param is the formatted discussions. */ protected getDiscussionsUserImg(discussions: any, siteId?: string): Promise { const promises = []; @@ -1266,10 +1266,10 @@ export class AddonMessagesProvider { /** * Get conversation member info by user id, works even if no conversation betwen the users exists. * - * @param {number} otherUserId The other user ID. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @param {number} [userId] User ID. If not defined, current user in the site. - * @return {Promise} Promise resolved with the member info. + * @param otherUserId The other user ID. + * @param siteId Site ID. If not defined, use current site. + * @param userId User ID. If not defined, current user in the site. + * @return Promise resolved with the member info. * @since 3.6 */ getMemberInfo(otherUserId: number, siteId?: string, userId?: number): Promise { @@ -1301,7 +1301,7 @@ export class AddonMessagesProvider { /** * Get the cache key for the get message preferences call. * - * @return {string} Cache key. + * @return Cache key. */ protected getMessagePreferencesCacheKey(): string { return this.ROOT_CACHE_KEY + 'messagePreferences'; @@ -1310,8 +1310,8 @@ export class AddonMessagesProvider { /** * Get message preferences. * - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Promise resolved with the message preferences. + * @param siteId Site ID. If not defined, use current site. + * @return Promise resolved with the message preferences. */ getMessagePreferences(siteId?: string): Promise { this.logger.debug('Get message preferences'); @@ -1337,11 +1337,10 @@ export class AddonMessagesProvider { /** * Get messages according to the params. * - * @param {any} params Parameters to pass to the WS. - * @param {any} preSets Set of presets for the WS. - * @param {boolean} [toDisplay=true] True if messages will be displayed to the user, either in view or in a notification. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} + * @param params Parameters to pass to the WS. + * @param preSets Set of presets for the WS. + * @param toDisplay True if messages will be displayed to the user, either in view or in a notification. + * @param siteId Site ID. If not defined, use current site. */ protected getMessages(params: any, preSets: any, toDisplay: boolean = true, siteId?: string): Promise { params['type'] = 'conversations'; @@ -1372,13 +1371,12 @@ export class AddonMessagesProvider { /** * Get the most recent messages. * - * @param {any} params Parameters to pass to the WS. - * @param {any} preSets Set of presets for the WS. - * @param {number} [limitFromUnread=0] Number of read messages already fetched, so fetch will be done from this number. - * @param {number} [limitFromRead=0] Number of unread messages already fetched, so fetch will be done from this number. - * @param {boolean} [toDisplay=true] True if messages will be displayed to the user, either in view or in a notification. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} + * @param params Parameters to pass to the WS. + * @param preSets Set of presets for the WS. + * @param limitFromUnread Number of read messages already fetched, so fetch will be done from this number. + * @param limitFromRead Number of unread messages already fetched, so fetch will be done from this number. + * @param toDisplay True if messages will be displayed to the user, either in view or in a notification. + * @param siteId Site ID. If not defined, use current site. */ protected getRecentMessages(params: any, preSets: any, limitFromUnread: number = 0, limitFromRead: number = 0, toDisplay: boolean = true, siteId?: string): Promise { @@ -1419,13 +1417,13 @@ export class AddonMessagesProvider { /** * Get a self conversation. * - * @param {number} [messageOffset=0] Offset for messages list. - * @param {number} [messageLimit=1] Limit of messages. Defaults to 1 (last message). - * We recommend getConversationMessages to get them. - * @param {boolean} [newestFirst=true] Whether to order messages by newest first. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @param {string} [userId] User ID to get the self conversation for. If not defined, current user in the site. - * @return {Promise} Promise resolved with the response. + * @param messageOffset Offset for messages list. + * @param messageLimit Limit of messages. Defaults to 1 (last message). + * We recommend getConversationMessages to get them. + * @param newestFirst Whether to order messages by newest first. + * @param siteId Site ID. If not defined, use current site. + * @param userId User ID to get the self conversation for. If not defined, current user in the site. + * @return Promise resolved with the response. * @since 3.7 */ getSelfConversation(messageOffset: number = 0, messageLimit: number = 1, newestFirst: boolean = true, siteId?: string, @@ -1453,8 +1451,8 @@ export class AddonMessagesProvider { /** * Get unread conversation counts by type. * - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Resolved with the unread favourite, individual and group conversation counts. + * @param siteId Site ID. If not defined, use current site. + * @return Resolved with the unread favourite, individual and group conversation counts. */ getUnreadConversationCounts(siteId?: string): Promise<{favourites: number, individual: number, group: number, self: number, orMore?: boolean}> { @@ -1531,11 +1529,11 @@ export class AddonMessagesProvider { /** * Get the latest unread received messages. * - * @param {boolean} [toDisplay=true] True if messages 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 the message unread count. + * @param toDisplay True if messages 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 the message unread count. */ getUnreadReceivedMessages(toDisplay: boolean = true, forceCache: boolean = false, ignoreCache: boolean = false, siteId?: string): Promise { @@ -1563,9 +1561,9 @@ export class AddonMessagesProvider { /** * Invalidate all contacts cache. * - * @param {number} userId The user ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Resolved when done. + * @param userId The user ID. + * @param siteId Site ID. If not defined, current site. + * @return Resolved when done. */ invalidateAllContactsCache(userId: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -1578,9 +1576,8 @@ export class AddonMessagesProvider { /** * Invalidate blocked contacts cache. * - * @param {number} userId The user ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} + * @param userId The user ID. + * @param siteId Site ID. If not defined, current site. */ invalidateBlockedContactsCache(userId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1591,8 +1588,8 @@ export class AddonMessagesProvider { /** * Invalidate contacts cache. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Resolved when done. + * @param siteId Site ID. If not defined, current site. + * @return Resolved when done. */ invalidateContactsCache(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1603,8 +1600,8 @@ export class AddonMessagesProvider { /** * Invalidate user contacts cache. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Resolved when done. + * @param siteId Site ID. If not defined, current site. + * @return Resolved when done. */ invalidateUserContacts(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1615,8 +1612,8 @@ export class AddonMessagesProvider { /** * Invalidate contact requests cache. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Resolved when done. + * @param siteId Site ID. If not defined, current site. + * @return Resolved when done. */ invalidateContactRequestsCache(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1627,8 +1624,8 @@ export class AddonMessagesProvider { /** * Invalidate contact requests count cache. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Resolved when done. + * @param siteId Site ID. If not defined, current site. + * @return Resolved when done. */ invalidateContactRequestsCountCache(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1639,10 +1636,10 @@ export class AddonMessagesProvider { /** * Invalidate conversation. * - * @param {number} conversationId Conversation ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User ID. If not defined, current user in the site. - * @return {Promise} Resolved when done. + * @param conversationId Conversation ID. + * @param siteId Site ID. If not defined, current site. + * @param userId User ID. If not defined, current user in the site. + * @return Resolved when done. */ invalidateConversation(conversationId: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1655,10 +1652,10 @@ export class AddonMessagesProvider { /** * Invalidate conversation between users. * - * @param {number} otherUserId Other user ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User ID. If not defined, current user in the site. - * @return {Promise} Resolved when done. + * @param otherUserId Other user ID. + * @param siteId Site ID. If not defined, current site. + * @param userId User ID. If not defined, current user in the site. + * @return Resolved when done. */ invalidateConversationBetweenUsers(otherUserId: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1671,10 +1668,10 @@ export class AddonMessagesProvider { /** * Invalidate conversation members cache. * - * @param {number} conversationId Conversation ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User ID. If not defined, current user in the site. - * @return {Promise} Resolved when done. + * @param conversationId Conversation ID. + * @param siteId Site ID. If not defined, current site. + * @param userId User ID. If not defined, current user in the site. + * @return Resolved when done. */ invalidateConversationMembers(conversationId: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1687,10 +1684,10 @@ export class AddonMessagesProvider { /** * Invalidate conversation messages cache. * - * @param {number} conversationId Conversation ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User ID. If not defined, current user in the site. - * @return {Promise} Resolved when done. + * @param conversationId Conversation ID. + * @param siteId Site ID. If not defined, current site. + * @param userId User ID. If not defined, current user in the site. + * @return Resolved when done. */ invalidateConversationMessages(conversationId: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1703,9 +1700,9 @@ export class AddonMessagesProvider { /** * Invalidate conversations cache. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User ID. If not defined, current user in the site. - * @return {Promise} Resolved when done. + * @param siteId Site ID. If not defined, current site. + * @param userId User ID. If not defined, current user in the site. + * @return Resolved when done. */ invalidateConversations(siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1718,8 +1715,8 @@ export class AddonMessagesProvider { /** * Invalidate conversation counts cache. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Resolved when done. + * @param siteId Site ID. If not defined, current site. + * @return Resolved when done. */ invalidateConversationCounts(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1730,9 +1727,9 @@ export class AddonMessagesProvider { /** * Invalidate discussion cache. * - * @param {number} userId The user ID with whom the current user is having the discussion. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Resolved when done. + * @param userId The user ID with whom the current user is having the discussion. + * @param siteId Site ID. If not defined, current site. + * @return Resolved when done. */ invalidateDiscussionCache(userId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1745,8 +1742,8 @@ export class AddonMessagesProvider { * * Note that {@link this.getDiscussions} uses the contacts, so we need to invalidate contacts too. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Resolved when done. + * @param siteId Site ID. If not defined, current site. + * @return Resolved when done. */ invalidateDiscussionsCache(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1761,10 +1758,10 @@ export class AddonMessagesProvider { /** * Invalidate member info cache. * - * @param {number} otherUserId The other user ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User ID. If not defined, current user in the site. - * @return {Promise} Resolved when done. + * @param otherUserId The other user ID. + * @param siteId Site ID. If not defined, current site. + * @param userId User ID. If not defined, current user in the site. + * @return Resolved when done. */ invalidateMemberInfo(otherUserId: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1777,8 +1774,8 @@ export class AddonMessagesProvider { /** * Invalidate get message 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. */ invalidateMessagePreferences(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1789,9 +1786,9 @@ export class AddonMessagesProvider { /** * Invalidate all cache entries with member info. * - * @param {number} userId Id of the user to invalidate. - * @param {CoreSite} site Site object. - * @return {Promie} Promise resolved when done. + * @param userId Id of the user to invalidate. + * @param site Site object. + * @return Promise resolved when done. */ protected invalidateAllMemberInfo(userId: number, site: CoreSite): Promise { return this.utils.allPromises([ @@ -1814,9 +1811,9 @@ export class AddonMessagesProvider { /** * Invalidate a self conversation. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User ID. If not defined, current user in the site. - * @return {Promise} Resolved when done. + * @param siteId Site ID. If not defined, current site. + * @param userId User ID. If not defined, current user in the site. + * @return Resolved when done. */ invalidateSelfConversation(siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1829,8 +1826,8 @@ export class AddonMessagesProvider { /** * Invalidate unread conversation counts cache. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Resolved when done. + * @param siteId Site ID. If not defined, current site. + * @return Resolved when done. */ invalidateUnreadConversationCounts(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1848,9 +1845,9 @@ export class AddonMessagesProvider { /** * Checks if the a user is blocked by the current user. * - * @param {number} userId The user ID to check against. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Resolved with boolean, rejected when we do not know. + * @param userId The user ID to check against. + * @param siteId Site ID. If not defined, use current site. + * @return Resolved with boolean, rejected when we do not know. */ isBlocked(userId: number, siteId?: string): Promise { if (this.isGroupMessagingEnabled()) { @@ -1873,9 +1870,9 @@ export class AddonMessagesProvider { /** * Checks if the a user is a contact of the current user. * - * @param {number} userId The user ID to check against. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Resolved with boolean, rejected when we do not know. + * @param userId The user ID to check against. + * @param siteId Site ID. If not defined, use current site. + * @return Resolved with boolean, rejected when we do not know. */ isContact(userId: number, siteId?: string): Promise { if (this.isGroupMessagingEnabled()) { @@ -1900,7 +1897,7 @@ export class AddonMessagesProvider { /** * Returns whether or not group messaging is supported. * - * @return {boolean} If related WS is available on current site. + * @return If related WS is available on current site. * @since 3.6 */ isGroupMessagingEnabled(): boolean { @@ -1910,8 +1907,8 @@ export class AddonMessagesProvider { /** * Returns whether or not group messaging is supported in a certain site. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean: whether related WS is available on a certain site. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: whether related WS is available on a certain site. * @since 3.6 */ isGroupMessagingEnabledInSite(siteId?: string): Promise { @@ -1925,7 +1922,7 @@ export class AddonMessagesProvider { /** * Returns whether or not we can mark all messages as read. * - * @return {boolean} If related WS is available on current site. + * @return If related WS is available on current site. * @since 3.2 */ isMarkAllMessagesReadEnabled(): boolean { @@ -1935,7 +1932,7 @@ export class AddonMessagesProvider { /** * Returns whether or not we can count unread messages. * - * @return {boolean} True if enabled, false otherwise. + * @return True if enabled, false otherwise. * @since 3.2 */ isMessageCountEnabled(): boolean { @@ -1945,7 +1942,7 @@ export class AddonMessagesProvider { /** * Returns whether or not the message preferences are enabled for the current site. * - * @return {boolean} True if enabled, false otherwise. + * @return True if enabled, false otherwise. * @since 3.2 */ isMessagePreferencesEnabled(): boolean { @@ -1957,8 +1954,8 @@ export class AddonMessagesProvider { * * This could call a WS so do not abuse this method. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Resolved when enabled, otherwise rejected. + * @param siteId Site ID. If not defined, current site. + * @return Resolved when enabled, otherwise rejected. */ isMessagingEnabledForSite(siteId?: string): Promise { return this.isPluginEnabled(siteId).then((enabled) => { @@ -1971,8 +1968,8 @@ export class AddonMessagesProvider { /** * Returns whether or not a site supports muting or unmuting a conversation. * - * @param {CoreSite} [site] The site to check, undefined for current site. - * @return {boolean} If related WS is available on current site. + * @param site The site to check, undefined for current site. + * @return If related WS is available on current site. * @since 3.7 */ isMuteConversationEnabled(site?: CoreSite): boolean { @@ -1984,8 +1981,8 @@ export class AddonMessagesProvider { /** * Returns whether or not a site supports muting or unmuting a conversation. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean: whether related WS is available on a certain site. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: whether related WS is available on a certain site. * @since 3.7 */ isMuteConversationEnabledInSite(siteId?: string): Promise { @@ -1999,8 +1996,8 @@ export class AddonMessagesProvider { /** * Returns 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 enabled, rejected or resolved with false otherwise. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with true if enabled, rejected or resolved with false otherwise. */ isPluginEnabled(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -2011,7 +2008,6 @@ export class AddonMessagesProvider { /** * Returns whether or not we can search messages. * - * @return {boolean} * @since 3.2 */ isSearchMessagesEnabled(): boolean { @@ -2021,8 +2017,8 @@ export class AddonMessagesProvider { /** * Returns whether or not self conversation is supported in a certain site. * - * @param {CoreSite} [site] Site. If not defined, current site. - * @return {boolean} If related WS is available on the site. + * @param site Site. If not defined, current site. + * @return If related WS is available on the site. * @since 3.7 */ isSelfConversationEnabled(site?: CoreSite): boolean { @@ -2034,8 +2030,8 @@ export class AddonMessagesProvider { /** * Returns whether or not self conversation is supported in a certain site. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean: whether related WS is available on a certain site. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: whether related WS is available on a certain site. * @since 3.7 */ isSelfConversationEnabledInSite(siteId?: string): Promise { @@ -2049,9 +2045,9 @@ export class AddonMessagesProvider { /** * Mark message as read. * - * @param {number} messageId ID of message to mark as read - * @param {string} [siteId] Site ID. If not defined, current site. - * @returns {Promise} Promise resolved with boolean marking success or not. + * @param messageId ID of message to mark as read + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean marking success or not. */ markMessageRead(messageId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -2067,8 +2063,8 @@ export class AddonMessagesProvider { /** * Mark all messages of a conversation as read. * - * @param {number} conversationId Conversation ID. - * @returns {Promise} Promise resolved if success. + * @param conversationId Conversation ID. + * @return Promise resolved if success. * @since 3.6 */ markAllConversationMessagesRead(conversationId?: number): Promise { @@ -2086,8 +2082,8 @@ export class AddonMessagesProvider { /** * Mark all messages of a discussion as read. * - * @param {number} userIdFrom User Id for the sender. - * @returns {Promise} Promise resolved with boolean marking success or not. + * @param userIdFrom User Id for the sender. + * @return Promise resolved with boolean marking success or not. */ markAllMessagesRead(userIdFrom?: number): Promise { const params = { @@ -2104,11 +2100,11 @@ export class AddonMessagesProvider { /** * Mute or unmute a conversation. * - * @param {number} conversationId Conversation ID. - * @param {boolean} set Whether to mute or unmute. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @param {number} [userId] User ID. If not defined, current user in the site. - * @return {Promise} Resolved when done. + * @param conversationId Conversation ID. + * @param set Whether to mute or unmute. + * @param siteId Site ID. If not defined, use current site. + * @param userId User ID. If not defined, current user in the site. + * @return Resolved when done. */ muteConversation(conversationId: number, set: boolean, siteId?: string, userId?: number): Promise { return this.muteConversations([conversationId], set, siteId, userId); @@ -2117,11 +2113,11 @@ export class AddonMessagesProvider { /** * Mute or unmute some conversations. * - * @param {number[]} conversations Conversation IDs. - * @param {boolean} set Whether to mute or unmute. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @param {number} [userId] User ID. If not defined, current user in the site. - * @return {Promise} Resolved when done. + * @param conversations Conversation IDs. + * @param set Whether to mute or unmute. + * @param siteId Site ID. If not defined, use current site. + * @param userId User ID. If not defined, current user in the site. + * @return Resolved when done. */ muteConversations(conversations: number[], set: boolean, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -2151,8 +2147,8 @@ export class AddonMessagesProvider { /** * Refresh the number of contact requests sent to the current user. * - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Resolved with the number of contact requests. + * @param siteId Site ID. If not defined, use current site. + * @return Resolved with the number of contact requests. * @since 3.6 */ refreshContactRequestsCount(siteId?: string): Promise { @@ -2166,8 +2162,8 @@ export class AddonMessagesProvider { /** * Refresh unread conversation counts and trigger event. * - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Resolved with the unread favourite, individual and group conversation counts. + * @param siteId Site ID. If not defined, use current site. + * @return Resolved with the unread favourite, individual and group conversation counts. */ refreshUnreadConversationCounts(siteId?: string): Promise<{favourites: number, individual: number, group: number, orMore?: boolean}> { @@ -2182,9 +2178,9 @@ export class AddonMessagesProvider { /** * Remove a contact. * - * @param {number} userId User ID of the person to remove. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Resolved when done. + * @param userId User ID of the person to remove. + * @param siteId Site ID. If not defined, use current site. + * @return Resolved when done. */ removeContact(userId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -2218,10 +2214,9 @@ export class AddonMessagesProvider { * of results which would take a while to process. The limit here is just a convenience to * prevent viewed to crash because too many DOM elements are created. * - * @param {string} query The query string. - * @param {number} [limit=100] The number of results to return, 0 for none. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} + * @param query The query string. + * @param limit The number of results to return, 0 for none. + * @param siteId Site ID. If not defined, current site. */ searchContacts(query: string, limit: number = 100, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -2247,12 +2242,12 @@ export class AddonMessagesProvider { /** * Search for all the messges with a specific text. * - * @param {string} query The query string. - * @param {number} [userId] The user ID. If not defined, current user. - * @param {number} [limitFrom=0] Position of the first result to get. Defaults to 0. - * @param {number} [limitNum] Number of results to get. Defaults to AddonMessagesProvider.LIMIT_SEARCH. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the results. + * @param query The query string. + * @param userId The user ID. If not defined, current user. + * @param limitFrom Position of the first result to get. Defaults to 0. + * @param limitNum Number of results to get. Defaults to AddonMessagesProvider.LIMIT_SEARCH. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the results. */ searchMessages(query: string, userId?: number, limitFrom: number = 0, limitNum: number = AddonMessagesProvider.LIMIT_SEARCH, siteId?: string): Promise<{messages: any[], canLoadMore: boolean}> { @@ -2294,11 +2289,11 @@ export class AddonMessagesProvider { /** * Search for users. * - * @param {string} query Text to search for. - * @param {number} [limitFrom=0] Position of the first found user to fetch. - * @param {number} [limitNum] Number of found users to fetch. Defaults to AddonMessagesProvider.LIMIT_SEARCH. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Resolved with two lists of found users: contacts and non-contacts. + * @param query Text to search for. + * @param limitFrom Position of the first found user to fetch. + * @param limitNum Number of found users to fetch. Defaults to AddonMessagesProvider.LIMIT_SEARCH. + * @param siteId Site ID. If not defined, use current site. + * @return Resolved with two lists of found users: contacts and non-contacts. * @since 3.6 */ searchUsers(query: string, limitFrom: number = 0, limitNum: number = AddonMessagesProvider.LIMIT_SEARCH, siteId?: string): @@ -2339,12 +2334,12 @@ export class AddonMessagesProvider { /** * Send a message to someone. * - * @param {number} userIdTo User ID to send the message to. - * @param {string} message The message to send - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with: - * - sent (Boolean) True if message was sent to server, false if stored in device. - * - message (Object) If sent=false, contains the stored message. + * @param userIdTo User ID to send the message to. + * @param message The message to send + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with: + * - sent (Boolean) True if message was sent to server, false if stored in device. + * - message (Object) If sent=false, contains the stored message. */ sendMessage(toUserId: number, message: string, siteId?: string): Promise { // Convenience function to store a message to be synchronized later. @@ -2395,10 +2390,10 @@ export class AddonMessagesProvider { /** * Send a message to someone. It will fail if offline or cannot connect. * - * @param {number} toUserId User ID to send the message to. - * @param {string} message The message to send - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if success, rejected if failure. + * @param toUserId User ID to send the message to. + * @param message The message to send + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if success, rejected if failure. */ sendMessageOnline(toUserId: number, message: string, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -2430,10 +2425,10 @@ export class AddonMessagesProvider { * IMPORTANT: Sending several messages at once for the same discussions can cause problems with display order, * since messages with same timecreated aren't ordered by ID. * - * @param {any} messages Messages to send. Each message must contain touserid, text and textformat. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if success, rejected if failure. Promise resolved doesn't mean that messages - * have been sent, the resolve param can contain errors for messages not sent. + * @param messages Messages to send. Each message must contain touserid, text and textformat. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if success, rejected if failure. Promise resolved doesn't mean that messages + * have been sent, the resolve param can contain errors for messages not sent. */ sendMessagesOnline(messages: any, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -2448,12 +2443,12 @@ export class AddonMessagesProvider { /** * Send a message to a conversation. * - * @param {any} conversation Conversation. - * @param {string} message The message to send. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with: - * - sent (boolean) True if message was sent to server, false if stored in device. - * - message (any) If sent=false, contains the stored message. + * @param conversation Conversation. + * @param message The message to send. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with: + * - sent (boolean) True if message was sent to server, false if stored in device. + * - message (any) If sent=false, contains the stored message. * @since 3.6 */ sendMessageToConversation(conversation: any, message: string, siteId?: string): Promise { @@ -2505,10 +2500,10 @@ export class AddonMessagesProvider { /** * Send a message to a conversation. It will fail if offline or cannot connect. * - * @param {number} conversationId Conversation ID. - * @param {string} message The message to send - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if success, rejected if failure. + * @param conversationId Conversation ID. + * @param message The message to send + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if success, rejected if failure. * @since 3.6 */ sendMessageToConversationOnline(conversationId: number, message: string, siteId?: string): Promise { @@ -2533,10 +2528,10 @@ export class AddonMessagesProvider { /** * Send some messages to a conversation. It will fail if offline or cannot connect. * - * @param {number} conversationId Conversation ID. - * @param {any} messages Messages to send. Each message must contain text and, optionally, textformat. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if success, rejected if failure. + * @param conversationId Conversation ID. + * @param messages Messages to send. Each message must contain text and, optionally, textformat. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if success, rejected if failure. * @since 3.6 */ sendMessagesToConversationOnline(conversationId: number, messages: any, siteId?: string): Promise { @@ -2558,11 +2553,11 @@ export class AddonMessagesProvider { /** * Set or unset a conversation as favourite. * - * @param {number} conversationId Conversation ID. - * @param {boolean} set Whether to set or unset it as favourite. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @param {number} [userId] User ID. If not defined, current user in the site. - * @return {Promise} Resolved when done. + * @param conversationId Conversation ID. + * @param set Whether to set or unset it as favourite. + * @param siteId Site ID. If not defined, use current site. + * @param userId User ID. If not defined, current user in the site. + * @return Resolved when done. */ setFavouriteConversation(conversationId: number, set: boolean, siteId?: string, userId?: number): Promise { return this.setFavouriteConversations([conversationId], set, siteId, userId); @@ -2571,11 +2566,11 @@ export class AddonMessagesProvider { /** * Set or unset some conversations as favourites. * - * @param {number[]} conversations Conversation IDs. - * @param {boolean} set Whether to set or unset them as favourites. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @param {number} [userId] User ID. If not defined, current user in the site. - * @return {Promise} Resolved when done. + * @param conversations Conversation IDs. + * @param set Whether to set or unset them as favourites. + * @param siteId Site ID. If not defined, use current site. + * @param userId User ID. If not defined, current user in the site. + * @return Resolved when done. */ setFavouriteConversations(conversations: number[], set: boolean, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -2605,8 +2600,8 @@ export class AddonMessagesProvider { /** * Helper method to sort conversations by last message time. * - * @param {any[]} conversations Array of conversations. - * @return {any[]} Conversations sorted with most recent last. + * @param conversations Array of conversations. + * @return Conversations sorted with most recent last. */ sortConversations(conversations: any[]): any[] { return conversations.sort((a, b) => { @@ -2625,8 +2620,8 @@ export class AddonMessagesProvider { /** * Helper method to sort messages by time. * - * @param {any[]} messages Array of messages containing the key 'timecreated'. - * @return {any[]} Messages sorted with most recent last. + * @param messages Array of messages containing the key 'timecreated'. + * @return Messages sorted with most recent last. */ sortMessages(messages: any[]): any[] { return messages.sort((a, b) => { @@ -2651,10 +2646,10 @@ export class AddonMessagesProvider { /** * Store the last received message if it's newer than the last stored. * - * @param {number} convIdOrUserIdFrom Conversation ID (3.6+) or ID of the useridfrom retrieved (3.5-), 0 for all users. - * @param {any} message Last message received. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param convIdOrUserIdFrom Conversation ID (3.6+) or ID of the useridfrom retrieved (3.5-), 0 for all users. + * @param message Last message received. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ protected storeLastReceivedMessageIfNeeded(convIdOrUserIdFrom: number, message: any, siteId?: string): Promise { const component = AddonMessagesProvider.PUSH_SIMULATION_COMPONENT; @@ -2678,7 +2673,7 @@ export class AddonMessagesProvider { /** * Store user data from contacts in local DB. * - * @param {any} contactTypes List of contacts grouped in types. + * @param contactTypes List of contacts grouped in types. */ protected storeUsersFromAllContacts(contactTypes: any): void { for (const x in contactTypes) { @@ -2689,8 +2684,8 @@ export class AddonMessagesProvider { /** * Store user data from discussions in local DB. * - * @param {any} discussions List of discussions. - * @param {string} [siteId] Site ID. If not defined, current site. + * @param discussions List of discussions. + * @param siteId Site ID. If not defined, current site. */ protected storeUsersFromDiscussions(discussions: any, siteId?: string): void { const users = []; @@ -2707,9 +2702,9 @@ export class AddonMessagesProvider { /** * Unblock a user. * - * @param {number} userId User ID of the person to unblock. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Resolved when done. + * @param userId User ID of the person to unblock. + * @param siteId Site ID. If not defined, use current site. + * @return Resolved when done. */ unblockContact(userId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { diff --git a/src/addon/messages/providers/push-click-handler.ts b/src/addon/messages/providers/push-click-handler.ts index f6fcf3c0e..7e1fcad43 100644 --- a/src/addon/messages/providers/push-click-handler.ts +++ b/src/addon/messages/providers/push-click-handler.ts @@ -33,8 +33,8 @@ export class AddonMessagesPushClickHandler 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 { if (this.utils.isTrueOrOne(notification.notif) && notification.name != 'messagecontactrequests') { @@ -48,8 +48,8 @@ export class AddonMessagesPushClickHandler 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 { return this.messagesProvider.invalidateDiscussionsCache(notification.site).catch(() => { diff --git a/src/addon/messages/providers/settings-handler.ts b/src/addon/messages/providers/settings-handler.ts index 06a8cdbc3..870df829b 100644 --- a/src/addon/messages/providers/settings-handler.ts +++ b/src/addon/messages/providers/settings-handler.ts @@ -30,7 +30,7 @@ export class AddonMessagesSettingsHandler 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 { return this.messagesProvider.isMessagePreferencesEnabled(); @@ -39,7 +39,7 @@ export class AddonMessagesSettingsHandler 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/messages/providers/sync-cron-handler.ts b/src/addon/messages/providers/sync-cron-handler.ts index 466a593c3..585414b39 100644 --- a/src/addon/messages/providers/sync-cron-handler.ts +++ b/src/addon/messages/providers/sync-cron-handler.ts @@ -29,9 +29,9 @@ export class AddonMessagesSyncCronHandler 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.messagesSync.syncAllDiscussions(siteId); @@ -40,7 +40,7 @@ export class AddonMessagesSyncCronHandler 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/messages/providers/sync.ts b/src/addon/messages/providers/sync.ts index d8e46f6e8..6c7d33886 100644 --- a/src/addon/messages/providers/sync.ts +++ b/src/addon/messages/providers/sync.ts @@ -46,9 +46,9 @@ export class AddonMessagesSyncProvider extends CoreSyncBaseProvider { /** * Get the ID of a discussion sync. * - * @param {number} conversationId Conversation ID. - * @param {number} userId User ID talking to (if no conversation ID). - * @return {string} Sync ID. + * @param conversationId Conversation ID. + * @param userId User ID talking to (if no conversation ID). + * @return Sync ID. */ protected getSyncId(conversationId: number, userId: number): string { if (conversationId) { @@ -61,10 +61,10 @@ export class AddonMessagesSyncProvider extends CoreSyncBaseProvider { /** * Try to synchronize all the discussions in a certain site or in all sites. * - * @param {string} [siteId] Site ID to sync. If not defined, sync all sites. - * @param {boolean} [onlyDeviceOffline=false] True to only sync discussions that failed because device was offline, - * false to sync all. - * @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 onlyDeviceOffline True to only sync discussions that failed because device was offline, + * false to sync all. + * @return Promise resolved if sync is successful, rejected if sync fails. */ syncAllDiscussions(siteId?: string, onlyDeviceOffline: boolean = false): Promise { const syncFunctionLog = 'all discussions' + (onlyDeviceOffline ? ' (Only offline)' : ''); @@ -75,9 +75,9 @@ export class AddonMessagesSyncProvider extends CoreSyncBaseProvider { /** * Get all messages pending to be sent in the site. * - * @param {string} [siteId] Site ID to sync. If not defined, sync all sites. - * @param {boolean} [onlyDeviceOffline=false] True to only sync discussions that failed because device was offline. - * @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 onlyDeviceOffline True to only sync discussions that failed because device was offline. + * @param Promise resolved if sync is successful, rejected if sync fails. */ protected syncAllDiscussionsFunc(siteId?: string, onlyDeviceOffline: boolean = false): Promise { const promise = onlyDeviceOffline ? @@ -132,9 +132,9 @@ export class AddonMessagesSyncProvider extends CoreSyncBaseProvider { /** * Synchronize a discussion. * - * @param {number} conversationId Conversation ID. - * @param {number} userId User ID talking to (if no conversation ID). - * @return {Promise} Promise resolved if sync is successful, rejected otherwise. + * @param conversationId Conversation ID. + * @param userId User ID talking to (if no conversation ID). + * @return Promise resolved if sync is successful, rejected otherwise. */ syncDiscussion(conversationId: number, userId: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -245,11 +245,11 @@ export class AddonMessagesSyncProvider extends CoreSyncBaseProvider { /** * Handle sync errors. * - * @param {number} conversationId Conversation ID. - * @param {number} userId User ID talking to (if no conversation ID). - * @param {any[]} errors List of errors. - * @param {any[]} warnings Array where to place the warnings. - * @return {Promise} Promise resolved when done. + * @param conversationId Conversation ID. + * @param userId User ID talking to (if no conversation ID). + * @param errors List of errors. + * @param warnings Array where to place the warnings. + * @return Promise resolved when done. */ protected handleSyncErrors(conversationId: number, userId: number, errors: any[], warnings: any[]): Promise { if (errors && errors.length) { @@ -289,10 +289,10 @@ export class AddonMessagesSyncProvider extends CoreSyncBaseProvider { * If there's an ongoing sync for a certain conversation, wait for it to end. * If there's no sync ongoing the promise will be resolved right away. * - * @param {number} conversationId Conversation ID. - * @param {number} userId User ID talking to (if no conversation ID). - * @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 conversationId Conversation ID. + * @param userId User ID talking to (if no conversation ID). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when there's no sync going on for the identifier. */ waitForSyncConversation(conversationId: number, userId: number, siteId?: string): Promise { const syncId = this.getSyncId(conversationId, userId); diff --git a/src/addon/messages/providers/user-add-contact-handler.ts b/src/addon/messages/providers/user-add-contact-handler.ts index 903d2b2e2..913695dd9 100644 --- a/src/addon/messages/providers/user-add-contact-handler.ts +++ b/src/addon/messages/providers/user-add-contact-handler.ts @@ -28,7 +28,6 @@ import { TranslateService } from '@ngx-translate/core'; export class AddonMessagesAddContactUserHandler implements CoreUserProfileHandler, OnDestroy { /** * Update handler information event. - * @type {string} */ static UPDATED_EVENT = 'AddonMessagesAddContactUserHandler_updated_event'; @@ -51,7 +50,7 @@ export class AddonMessagesAddContactUserHandler implements CoreUserProfileHandle /** * Check if handler is enabled. * - * @return {Promise} Promise resolved with true if enabled, rejected or resolved with false otherwise. + * @return Promise resolved with true if enabled, rejected or resolved with false otherwise. */ isEnabled(): Promise { return this.messagesProvider.isPluginEnabled(); @@ -60,11 +59,11 @@ export class AddonMessagesAddContactUserHandler implements CoreUserProfileHandle /** * 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 { return user.id != this.sitesProvider.getCurrentSiteUserId(); @@ -73,7 +72,7 @@ export class AddonMessagesAddContactUserHandler implements CoreUserProfileHandle /** * 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 { this.checkButton(user.id); @@ -119,8 +118,8 @@ export class AddonMessagesAddContactUserHandler implements CoreUserProfileHandle /** * Update Button with avalaible data. - * @param {number} userId User Id to update. - * @return {Promise} Promise resolved when done. + * @param userId User Id to update. + * @return Promise resolved when done. */ protected checkButton(userId: number): Promise { this.updateButton(userId, {spinner: true}); @@ -154,8 +153,8 @@ export class AddonMessagesAddContactUserHandler implements CoreUserProfileHandle /** * Triggers the event to update the handler information. * - * @param {number} userId The user ID the handler belongs to. - * @param {any} data Data that should be updated. + * @param userId The user ID the handler belongs to. + * @param data Data that should be updated. */ protected updateButton(userId: number, data: any): void { // This fails for some reason, let's just hide the button. @@ -165,8 +164,8 @@ export class AddonMessagesAddContactUserHandler implements CoreUserProfileHandle /** * Add a contact or send a contact request if group messaging is enabled. * - * @param {any} user User to add as contact. - * @return {Promise} Promise resolved when done. + * @param user User to add as contact. + * @return Promise resolved when done. */ protected addContact(user: any): Promise { if (!this.messagesProvider.isGroupMessagingEnabled()) { diff --git a/src/addon/messages/providers/user-block-contact-handler.ts b/src/addon/messages/providers/user-block-contact-handler.ts index 0b325420c..d3c36e730 100644 --- a/src/addon/messages/providers/user-block-contact-handler.ts +++ b/src/addon/messages/providers/user-block-contact-handler.ts @@ -28,7 +28,6 @@ import { TranslateService } from '@ngx-translate/core'; export class AddonMessagesBlockContactUserHandler implements CoreUserProfileHandler, OnDestroy { /** * Update handler information event. - * @type {string} */ static UPDATED_EVENT = 'AddonMessagesBlockContactUserHandler_updated_event'; @@ -51,7 +50,7 @@ export class AddonMessagesBlockContactUserHandler implements CoreUserProfileHand /** * Check if handler is enabled. * - * @return {Promise} Promise resolved with true if enabled, rejected or resolved with false otherwise. + * @return Promise resolved with true if enabled, rejected or resolved with false otherwise. */ isEnabled(): Promise { return this.messagesProvider.isPluginEnabled(); @@ -60,11 +59,11 @@ export class AddonMessagesBlockContactUserHandler implements CoreUserProfileHand /** * 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 { return user.id != this.sitesProvider.getCurrentSiteUserId(); @@ -73,7 +72,7 @@ export class AddonMessagesBlockContactUserHandler implements CoreUserProfileHand /** * 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 { @@ -125,8 +124,8 @@ export class AddonMessagesBlockContactUserHandler implements CoreUserProfileHand /** * Update Button with avalaible data. - * @param {number} userId User Id to update. - * @return {Promise} Promise resolved when done. + * @param userId User Id to update. + * @return Promise resolved when done. */ protected checkButton(userId: number): Promise { this.updateButton(userId, {spinner: true}); @@ -158,8 +157,8 @@ export class AddonMessagesBlockContactUserHandler implements CoreUserProfileHand /** * Triggers the event to update the handler information. * - * @param {number} userId The user ID the handler belongs to. - * @param {any} data Data that should be updated. + * @param userId The user ID the handler belongs to. + * @param data Data that should be updated. */ protected updateButton(userId: number, data: any): void { // This fails for some reason, let's just hide the button. diff --git a/src/addon/messages/providers/user-send-message-handler.ts b/src/addon/messages/providers/user-send-message-handler.ts index 8fd8982d9..86759fb60 100644 --- a/src/addon/messages/providers/user-send-message-handler.ts +++ b/src/addon/messages/providers/user-send-message-handler.ts @@ -33,7 +33,7 @@ export class AddonMessagesSendMessageUserHandler implements CoreUserProfileHandl /** * Check if handler is enabled. * - * @return {Promise} Promise resolved with true if enabled, rejected or resolved with false otherwise. + * @return Promise resolved with true if enabled, rejected or resolved with false otherwise. */ isEnabled(): Promise { return this.messagesProvider.isPluginEnabled(); @@ -42,11 +42,11 @@ export class AddonMessagesSendMessageUserHandler implements CoreUserProfileHandl /** * 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 { const currentSite = this.sitesProvider.getCurrentSite(); @@ -62,7 +62,7 @@ export class AddonMessagesSendMessageUserHandler implements CoreUserProfileHandl /** * 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/mod/assign/classes/base-feedback-handler.ts b/src/addon/mod/assign/classes/base-feedback-handler.ts index 61b9addbc..33e08c2d4 100644 --- a/src/addon/mod/assign/classes/base-feedback-handler.ts +++ b/src/addon/mod/assign/classes/base-feedback-handler.ts @@ -31,10 +31,10 @@ export class AddonModAssignBaseFeedbackHandler implements AddonModAssignFeedback /** * Discard the draft data of the feedback plugin. * - * @param {number} assignId The assignment ID. - * @param {number} userId User ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {void|Promise} If the function is async, it should return a Promise resolved when done. + * @param assignId The assignment ID. + * @param userId User ID. + * @param siteId Site ID. If not defined, current site. + * @return If the function is async, it should return a Promise resolved when done. */ discardDraft(assignId: number, userId: number, siteId?: string): void | Promise { // Nothing to do. @@ -44,9 +44,9 @@ export class AddonModAssignBaseFeedbackHandler implements AddonModAssignFeedback * 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} plugin The plugin object. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param plugin The plugin object. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector, plugin: any): any | Promise { // Nothing to do. @@ -55,10 +55,10 @@ export class AddonModAssignBaseFeedbackHandler implements AddonModAssignFeedback /** * Return the draft saved data of the feedback plugin. * - * @param {number} assignId The assignment ID. - * @param {number} userId User ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {any|Promise} Data (or promise resolved with the data). + * @param assignId The assignment ID. + * @param userId User ID. + * @param siteId Site ID. If not defined, current site. + * @return Data (or promise resolved with the data). */ getDraft(assignId: number, userId: number, siteId?: string): any | Promise { // Nothing to do. @@ -68,11 +68,11 @@ export class AddonModAssignBaseFeedbackHandler implements AddonModAssignFeedback * Get files used by this plugin. * The files returned by this function will be prefetched when the user prefetches the assign. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {any[]|Promise} The files (or promise resolved with the files). + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param siteId Site ID. If not defined, current site. + * @return The files (or promise resolved with the files). */ getPluginFiles(assign: any, submission: any, plugin: any, siteId?: string): any[] | Promise { return []; @@ -81,8 +81,8 @@ export class AddonModAssignBaseFeedbackHandler implements AddonModAssignFeedback /** * Get a readable name to use for the plugin. * - * @param {any} plugin The plugin object. - * @return {string} The plugin name. + * @param plugin The plugin object. + * @return The plugin name. */ getPluginName(plugin: any): string { // Check if there's a translated string for the plugin. @@ -103,11 +103,11 @@ export class AddonModAssignBaseFeedbackHandler implements AddonModAssignFeedback /** * Check if the feedback data has changed for this plugin. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {any} inputData Data entered by the user for the feedback. - * @return {boolean|Promise} Boolean (or promise resolved with boolean): whether the data has changed. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param inputData Data entered by the user for the feedback. + * @return Boolean (or promise resolved with boolean): whether the data has changed. */ hasDataChanged(assign: any, submission: any, plugin: any, inputData: any): boolean | Promise { return false; @@ -116,10 +116,10 @@ export class AddonModAssignBaseFeedbackHandler implements AddonModAssignFeedback /** * Check whether the plugin has draft data stored. * - * @param {number} assignId The assignment ID. - * @param {number} userId User ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {boolean|Promise} Boolean or promise resolved with boolean: whether the plugin has draft data. + * @param assignId The assignment ID. + * @param userId User ID. + * @param siteId Site ID. If not defined, current site. + * @return Boolean or promise resolved with boolean: whether the plugin has draft data. */ hasDraftData(assignId: number, userId: number, siteId?: string): boolean | Promise { return false; @@ -128,7 +128,7 @@ export class AddonModAssignBaseFeedbackHandler implements AddonModAssignFeedback /** * 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; @@ -138,11 +138,11 @@ export class AddonModAssignBaseFeedbackHandler implements AddonModAssignFeedback * Prefetch any required data for the plugin. * This should NOT prefetch files. Files to be prefetched should be returned by the getPluginFiles function. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ prefetch(assign: any, submission: any, plugin: any, siteId?: string): Promise { return Promise.resolve(); @@ -151,12 +151,12 @@ export class AddonModAssignBaseFeedbackHandler implements AddonModAssignFeedback /** * Prepare and add to pluginData the data to send to the server based on the draft data saved. * - * @param {number} assignId The assignment ID. - * @param {number} userId User ID. - * @param {any} plugin The plugin object. - * @param {any} pluginData Object where to store the data to send. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {void|Promise} If the function is async, it should return a Promise resolved when done. + * @param assignId The assignment ID. + * @param userId User ID. + * @param plugin The plugin object. + * @param pluginData Object where to store the data to send. + * @param siteId Site ID. If not defined, current site. + * @return If the function is async, it should return a Promise resolved when done. */ prepareFeedbackData(assignId: number, userId: number, plugin: any, pluginData: any, siteId?: string): void | Promise { // Nothing to do. @@ -165,12 +165,12 @@ export class AddonModAssignBaseFeedbackHandler implements AddonModAssignFeedback /** * Save draft data of the feedback plugin. * - * @param {number} assignId The assignment ID. - * @param {number} userId User ID. - * @param {any} plugin The plugin object. - * @param {any} data The data to save. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {void|Promise} If the function is async, it should return a Promise resolved when done. + * @param assignId The assignment ID. + * @param userId User ID. + * @param plugin The plugin object. + * @param data The data to save. + * @param siteId Site ID. If not defined, current site. + * @return If the function is async, it should return a Promise resolved when done. */ saveDraft(assignId: number, userId: number, plugin: any, data: any, siteId?: string): void | Promise { // Nothing to do. diff --git a/src/addon/mod/assign/classes/base-submission-handler.ts b/src/addon/mod/assign/classes/base-submission-handler.ts index 7f3d52bdf..e0e6bb34e 100644 --- a/src/addon/mod/assign/classes/base-submission-handler.ts +++ b/src/addon/mod/assign/classes/base-submission-handler.ts @@ -33,10 +33,10 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis * plugin uses Moodle filters. The reason is that the app only prefetches filtered data, and the user should edit * unfiltered data. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @return {boolean|Promise} Boolean or promise resolved with boolean: whether it can be edited in offline. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @return Boolean or promise resolved with boolean: whether it can be edited in offline. */ canEditOffline(assign: any, submission: any, plugin: any): boolean | Promise { return false; @@ -45,10 +45,10 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis /** * Should clear temporary data for a cancelled submission. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {any} inputData Data entered by the user for the submission. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param inputData Data entered by the user for the submission. */ clearTmpData(assign: any, submission: any, plugin: any, inputData: any): void { // Nothing to do. @@ -58,12 +58,12 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis * This function will be called when the user wants to create a new submission based on the previous one. * It should add to pluginData the data to send to server based in the data in plugin (previous attempt). * - * @param {any} assign The assignment. - * @param {any} plugin The plugin object. - * @param {any} pluginData Object where to store the data to send. - * @param {number} [userId] User ID. If not defined, site's current user. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {void|Promise} If the function is async, it should return a Promise resolved when done. + * @param assign The assignment. + * @param plugin The plugin object. + * @param pluginData Object where to store the data to send. + * @param userId User ID. If not defined, site's current user. + * @param siteId Site ID. If not defined, current site. + * @return If the function is async, it should return a Promise resolved when done. */ copySubmissionData(assign: any, plugin: any, pluginData: any, userId?: number, siteId?: string): void | Promise { // Nothing to do. @@ -72,12 +72,12 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis /** * Delete any stored data for the plugin and submission. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {any} offlineData Offline data stored. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {void|Promise} If the function is async, it should return a Promise resolved when done. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param offlineData Offline data stored. + * @param siteId Site ID. If not defined, current site. + * @return If the function is async, it should return a Promise resolved when done. */ deleteOfflineData(assign: any, submission: any, plugin: any, offlineData: any, siteId?: string): void | Promise { // Nothing to do. @@ -87,10 +87,10 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis * Return the Component to use to display the plugin data, either in read or in edit mode. * 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} plugin The plugin object. - * @param {boolean} [edit] Whether the user is editing. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param plugin The plugin object. + * @param edit Whether the user is editing. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector, plugin: any, edit?: boolean): any | Promise { // Nothing to do. @@ -100,11 +100,11 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis * Get files used by this plugin. * The files returned by this function will be prefetched when the user prefetches the assign. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {any[]|Promise} The files (or promise resolved with the files). + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param siteId Site ID. If not defined, current site. + * @return The files (or promise resolved with the files). */ getPluginFiles(assign: any, submission: any, plugin: any, siteId?: string): any[] | Promise { return []; @@ -113,8 +113,8 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis /** * Get a readable name to use for the plugin. * - * @param {any} plugin The plugin object. - * @return {string} The plugin name. + * @param plugin The plugin object. + * @return The plugin name. */ getPluginName(plugin: any): string { // Check if there's a translated string for the plugin. @@ -135,9 +135,9 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis /** * Get the size of data (in bytes) this plugin will send to copy a previous submission. * - * @param {any} assign The assignment. - * @param {any} plugin The plugin object. - * @return {number|Promise} The size (or promise resolved with size). + * @param assign The assignment. + * @param plugin The plugin object. + * @return The size (or promise resolved with size). */ getSizeForCopy(assign: any, plugin: any): number | Promise { return 0; @@ -146,9 +146,9 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis /** * Get the size of data (in bytes) this plugin will send to add or edit a submission. * - * @param {any} assign The assignment. - * @param {any} plugin The plugin object. - * @return {number|Promise} The size (or promise resolved with size). + * @param assign The assignment. + * @param plugin The plugin object. + * @return The size (or promise resolved with size). */ getSizeForEdit(assign: any, plugin: any): number | Promise { return 0; @@ -157,11 +157,11 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis /** * Check if the submission data has changed for this plugin. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {any} inputData Data entered by the user for the submission. - * @return {boolean|Promise} Boolean (or promise resolved with boolean): whether the data has changed. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param inputData Data entered by the user for the submission. + * @return Boolean (or promise resolved with boolean): whether the data has changed. */ hasDataChanged(assign: any, submission: any, plugin: any, inputData: any): boolean | Promise { return false; @@ -170,7 +170,7 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis /** * 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; @@ -178,7 +178,7 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis /** * Whether or not the handler is enabled for edit on a site level. - * @return {boolean|Promise} Whether or not the handler is enabled for edit on a site level. + * @return Whether or not the handler is enabled for edit on a site level. */ isEnabledForEdit(): boolean | Promise { return false; @@ -188,11 +188,11 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis * Prefetch any required data for the plugin. * This should NOT prefetch files. Files to be prefetched should be returned by the getPluginFiles function. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ prefetch(assign: any, submission: any, plugin: any, siteId?: string): Promise { return Promise.resolve(); @@ -201,15 +201,15 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis /** * Prepare and add to pluginData the data to send to the server based on the input data. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {any} inputData Data entered by the user for the submission. - * @param {any} pluginData Object where to store the data to send. - * @param {boolean} [offline] Whether the user is editing in offline. - * @param {number} [userId] User ID. If not defined, site's current user. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {void|Promise} If the function is async, it should return a Promise resolved when done. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param inputData Data entered by the user for the submission. + * @param pluginData Object where to store the data to send. + * @param offline Whether the user is editing in offline. + * @param userId User ID. If not defined, site's current user. + * @param siteId Site ID. If not defined, current site. + * @return If the function is async, it should return a Promise resolved when done. */ prepareSubmissionData?(assign: any, submission: any, plugin: any, inputData: any, pluginData: any, offline?: boolean, userId?: number, siteId?: string): void | Promise { @@ -220,13 +220,13 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis * Prepare and add to pluginData the data to send to the server based on the offline data stored. * This will be used when performing a synchronization. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {any} offlineData Offline data stored. - * @param {any} pluginData Object where to store the data to send. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {void|Promise} If the function is async, it should return a Promise resolved when done. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param offlineData Offline data stored. + * @param pluginData Object where to store the data to send. + * @param siteId Site ID. If not defined, current site. + * @return If the function is async, it should return a Promise resolved when done. */ prepareSyncData?(assign: any, submission: any, plugin: any, offlineData: any, pluginData: any, siteId?: string) : void | Promise { diff --git a/src/addon/mod/assign/classes/feedback-plugin-component.ts b/src/addon/mod/assign/classes/feedback-plugin-component.ts index dfb9250cd..d315d1b7f 100644 --- a/src/addon/mod/assign/classes/feedback-plugin-component.ts +++ b/src/addon/mod/assign/classes/feedback-plugin-component.ts @@ -32,7 +32,7 @@ export class AddonModAssignFeedbackPluginComponentBase { /** * Open a modal to edit the feedback plugin. * - * @return {Promise} Promise resolved with the input data, rejected if cancelled. + * @return Promise resolved with the input data, rejected if cancelled. */ editFeedback(): Promise { if (this.canEdit) { @@ -62,7 +62,7 @@ export class AddonModAssignFeedbackPluginComponentBase { /** * Invalidate the data. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ invalidate(): Promise { return Promise.resolve(); diff --git a/src/addon/mod/assign/classes/submission-plugin-component.ts b/src/addon/mod/assign/classes/submission-plugin-component.ts index 35e750585..39e8cdd2b 100644 --- a/src/addon/mod/assign/classes/submission-plugin-component.ts +++ b/src/addon/mod/assign/classes/submission-plugin-component.ts @@ -32,7 +32,7 @@ export class AddonModAssignSubmissionPluginComponent { /** * Invalidate the data. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ invalidate(): Promise { return Promise.resolve(); diff --git a/src/addon/mod/assign/components/feedback-plugin/feedback-plugin.ts b/src/addon/mod/assign/components/feedback-plugin/feedback-plugin.ts index 82b9511bb..94b847fda 100644 --- a/src/addon/mod/assign/components/feedback-plugin/feedback-plugin.ts +++ b/src/addon/mod/assign/components/feedback-plugin/feedback-plugin.ts @@ -96,7 +96,7 @@ export class AddonModAssignFeedbackPluginComponent implements OnInit { /** * Invalidate the plugin data. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ invalidate(): Promise { return Promise.resolve(this.dynamicComponent && this.dynamicComponent.callComponentFunction('invalidate', [])); diff --git a/src/addon/mod/assign/components/index/index.ts b/src/addon/mod/assign/components/index/index.ts index 26d57888e..2697f6c36 100644 --- a/src/addon/mod/assign/components/index/index.ts +++ b/src/addon/mod/assign/components/index/index.ts @@ -141,10 +141,10 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo /** * Get assignment 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 { @@ -228,8 +228,8 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo /** * Set group to see the summary. * - * @param {number} groupId Group ID. - * @return {Promise} Resolved when done. + * @param groupId Group ID. + * @return Resolved when done. */ setGroup(groupId: number): Promise { this.group = groupId; @@ -245,8 +245,8 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo /** * Go to view a list of submissions. * - * @param {string} status Status to see. - * @param {number} count Number of submissions with the status. + * @param status Status to see. + * @param count Number of submissions with the status. */ goToSubmissionList(status: string, count: number): void { if (typeof status == 'undefined') { @@ -270,8 +270,8 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo /** * Checks if sync has succeed from result sync data. * - * @param {any} result Data returned by the sync function. - * @return {boolean} If succeed or not. + * @param result Data returned by the sync function. + * @return If succeed or not. */ protected hasSyncSucceed(result: any): boolean { if (result.updated) { @@ -284,7 +284,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo /** * Perform the invalidate content function. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected invalidateContent(): Promise { const promises = []; @@ -325,8 +325,8 @@ export class AddonModAssignIndexComponent 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 { if (this.assign && syncEventData.assignId == this.assign.id) { @@ -344,7 +344,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo /** * Performs the sync of the activity. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected sync(): Promise { return this.syncProvider.syncAssign(this.assign.id); diff --git a/src/addon/mod/assign/components/submission-plugin/submission-plugin.ts b/src/addon/mod/assign/components/submission-plugin/submission-plugin.ts index 3d4d0dba5..f8ba6d005 100644 --- a/src/addon/mod/assign/components/submission-plugin/submission-plugin.ts +++ b/src/addon/mod/assign/components/submission-plugin/submission-plugin.ts @@ -90,7 +90,7 @@ export class AddonModAssignSubmissionPluginComponent implements OnInit { /** * Invalidate the plugin data. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ invalidate(): Promise { return Promise.resolve(this.dynamicComponent && this.dynamicComponent.callComponentFunction('invalidate', [])); diff --git a/src/addon/mod/assign/components/submission/submission.ts b/src/addon/mod/assign/components/submission/submission.ts index 49489a9a5..762270fb6 100644 --- a/src/addon/mod/assign/components/submission/submission.ts +++ b/src/addon/mod/assign/components/submission/submission.ts @@ -131,7 +131,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy { /** * Calculate the time remaining message and class. * - * @param {any} response Response of get submission status. + * @param response Response of get submission status. */ protected calculateTimeRemaining(response: any): void { if (this.assign.duedate > 0) { @@ -178,7 +178,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy { /** * Check if the user can leave the view. If there are changes to be saved, it will ask for confirm. * - * @return {Promise} Promise resolved if can leave the view, rejected otherwise. + * @return Promise resolved if can leave the view, rejected otherwise. */ canLeave(): Promise { // Check if there is data to save. @@ -252,7 +252,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy { /** * Discard feedback drafts. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected discardDrafts(): Promise { if (this.feedback && this.feedback.plugins) { @@ -277,7 +277,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy { /** * Check if there's data to save (grade). * - * @return {Promise} Promise resolved with boolean: whether there's data to save. + * @return Promise resolved with boolean: whether there's data to save. */ protected hasDataToSave(): Promise { if (!this.canSaveGrades || !this.loaded) { @@ -329,7 +329,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy { /** * Invalidate and refresh data. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ invalidateAndRefresh(): Promise { this.loaded = false; @@ -363,7 +363,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy { /** * Load the data to render the submission. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected loadData(): Promise { let isBlind = !!this.blindId; @@ -458,8 +458,8 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy { /** * Load the data to render the feedback and grade. * - * @param {any} feedback The feedback data from the submission status. - * @return {Promise} Promise resolved when done. + * @param feedback The feedback data from the submission status. + * @return Promise resolved when done. */ protected loadFeedback(feedback: any): Promise { this.grade = { @@ -623,7 +623,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy { /** * Set the submission status name and class. * - * @param {any} status Submission status. + * @param status Submission status. */ protected setStatusNameAndClass(status: any): void { if (this.hasOffline || this.submittedOffline) { @@ -679,7 +679,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy { /** * Submit for grading. * - * @param {boolean} acceptStatement Whether the statement has been accepted. + * @param acceptStatement Whether the statement has been accepted. */ submitForGrading(acceptStatement: boolean): void { if (this.assign.requiresubmissionstatement && !acceptStatement) { @@ -712,7 +712,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy { /** * Submit a grade and feedback. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ submitGrade(): Promise { // Check if there's something to be saved. @@ -774,7 +774,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy { /** * Treat the grade info. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected treatGradeInfo(): Promise { // Check if grading method is simple or not. @@ -857,8 +857,8 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy { /** * Treat the last attempt. * - * @param {any} response Response of get submission status. - * @param {any[]} promises List where to add the promises. + * @param response Response of get submission status. + * @param promises List where to add the promises. */ protected treatLastAttempt(response: any, promises: any[]): void { if (!response.lastattempt) { diff --git a/src/addon/mod/assign/feedback/comments/component/comments.ts b/src/addon/mod/assign/feedback/comments/component/comments.ts index 910eec587..4a0edfac3 100644 --- a/src/addon/mod/assign/feedback/comments/component/comments.ts +++ b/src/addon/mod/assign/feedback/comments/component/comments.ts @@ -98,7 +98,7 @@ export class AddonModAssignFeedbackCommentsComponent extends AddonModAssignFeedb /** * Get the text for the plugin. * - * @return {Promise} Promise resolved with the text. + * @return Promise resolved with the text. */ protected getText(): Promise { // Check if the user already modified the comment. @@ -133,8 +133,8 @@ export class AddonModAssignFeedbackCommentsComponent extends AddonModAssignFeedb /** * Replace @@PLUGINFILE@@ wildcards with the real URL of embedded files. * - * @param {string} Text to treat. - * @return {string} Treated text. + * @param Text to treat. + * @return Treated text. */ replacePluginfileUrls(text: string): string { const files = this.plugin.fileareas && this.plugin.fileareas[0] && this.plugin.fileareas[0].files; diff --git a/src/addon/mod/assign/feedback/comments/providers/handler.ts b/src/addon/mod/assign/feedback/comments/providers/handler.ts index 55c003d9a..ef4e1655b 100644 --- a/src/addon/mod/assign/feedback/comments/providers/handler.ts +++ b/src/addon/mod/assign/feedback/comments/providers/handler.ts @@ -37,10 +37,10 @@ export class AddonModAssignFeedbackCommentsHandler implements AddonModAssignFeed /** * Discard the draft data of the feedback plugin. * - * @param {number} assignId The assignment ID. - * @param {number} userId User ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {void|Promise} If the function is async, it should return a Promise resolved when done. + * @param assignId The assignment ID. + * @param userId User ID. + * @param siteId Site ID. If not defined, current site. + * @return If the function is async, it should return a Promise resolved when done. */ discardDraft(assignId: number, userId: number, siteId?: string): void | Promise { const id = this.getDraftId(assignId, userId, siteId); @@ -53,9 +53,9 @@ export class AddonModAssignFeedbackCommentsHandler implements AddonModAssignFeed * Return the Component to use to display the plugin data, either in read or in edit mode. * 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} plugin The plugin object. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param plugin The plugin object. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector, plugin: any): any | Promise { return AddonModAssignFeedbackCommentsComponent; @@ -64,10 +64,10 @@ export class AddonModAssignFeedbackCommentsHandler implements AddonModAssignFeed /** * Return the draft saved data of the feedback plugin. * - * @param {number} assignId The assignment ID. - * @param {number} userId User ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {any|Promise} Data (or promise resolved with the data). + * @param assignId The assignment ID. + * @param userId User ID. + * @param siteId Site ID. If not defined, current site. + * @return Data (or promise resolved with the data). */ getDraft(assignId: number, userId: number, siteId?: string): any | Promise { const id = this.getDraftId(assignId, userId, siteId); @@ -80,10 +80,10 @@ export class AddonModAssignFeedbackCommentsHandler implements AddonModAssignFeed /** * Get a draft ID. * - * @param {number} assignId The assignment ID. - * @param {number} userId User ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {string} Draft ID. + * @param assignId The assignment ID. + * @param userId User ID. + * @param siteId Site ID. If not defined, current site. + * @return Draft ID. */ protected getDraftId(assignId: number, userId: number, siteId?: string): string { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -95,11 +95,11 @@ export class AddonModAssignFeedbackCommentsHandler implements AddonModAssignFeed * Get files used by this plugin. * The files returned by this function will be prefetched when the user prefetches the assign. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {any[]|Promise} The files (or promise resolved with the files). + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param siteId Site ID. If not defined, current site. + * @return The files (or promise resolved with the files). */ getPluginFiles(assign: any, submission: any, plugin: any, siteId?: string): any[] | Promise { return this.assignProvider.getSubmissionPluginAttachments(plugin); @@ -108,10 +108,10 @@ export class AddonModAssignFeedbackCommentsHandler implements AddonModAssignFeed /** * Get the text to submit. * - * @param {CoreTextUtilsProvider} textUtils Text utils instance. - * @param {any} plugin Plugin. - * @param {any} inputData Data entered in the feedback edit form. - * @return {string} Text to submit. + * @param textUtils Text utils instance. + * @param plugin Plugin. + * @param inputData Data entered in the feedback edit form. + * @return Text to submit. */ static getTextFromInputData(textUtils: CoreTextUtilsProvider, plugin: any, inputData: any): string { const files = plugin.fileareas && plugin.fileareas[0] ? plugin.fileareas[0].files : []; @@ -128,12 +128,12 @@ export class AddonModAssignFeedbackCommentsHandler implements AddonModAssignFeed /** * Check if the feedback data has changed for this plugin. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {any} inputData Data entered by the user for the feedback. - * @param {number} userId User ID of the submission. - * @return {boolean|Promise} Boolean (or promise resolved with boolean): whether the data has changed. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param inputData Data entered by the user for the feedback. + * @param userId User ID of the submission. + * @return Boolean (or promise resolved with boolean): whether the data has changed. */ hasDataChanged(assign: any, submission: any, plugin: any, inputData: any, userId: number): boolean | Promise { // Get it from plugin or offline. @@ -161,10 +161,10 @@ export class AddonModAssignFeedbackCommentsHandler implements AddonModAssignFeed /** * Check whether the plugin has draft data stored. * - * @param {number} assignId The assignment ID. - * @param {number} userId User ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {boolean|Promise} Boolean or promise resolved with boolean: whether the plugin has draft data. + * @param assignId The assignment ID. + * @param userId User ID. + * @param siteId Site ID. If not defined, current site. + * @return Boolean or promise resolved with boolean: whether the plugin has draft data. */ hasDraftData(assignId: number, userId: number, siteId?: string): boolean | Promise { const draft = this.getDraft(assignId, userId, siteId); @@ -175,7 +175,7 @@ export class AddonModAssignFeedbackCommentsHandler implements AddonModAssignFeed /** * 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; @@ -184,12 +184,12 @@ export class AddonModAssignFeedbackCommentsHandler implements AddonModAssignFeed /** * Prepare and add to pluginData the data to send to the server based on the draft data saved. * - * @param {number} assignId The assignment ID. - * @param {number} userId User ID. - * @param {any} plugin The plugin object. - * @param {any} pluginData Object where to store the data to send. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {void|Promise} If the function is async, it should return a Promise resolved when done. + * @param assignId The assignment ID. + * @param userId User ID. + * @param plugin The plugin object. + * @param pluginData Object where to store the data to send. + * @param siteId Site ID. If not defined, current site. + * @return If the function is async, it should return a Promise resolved when done. */ prepareFeedbackData(assignId: number, userId: number, plugin: any, pluginData: any, siteId?: string): void | Promise { const draft = this.getDraft(assignId, userId, siteId); @@ -205,12 +205,12 @@ export class AddonModAssignFeedbackCommentsHandler implements AddonModAssignFeed /** * Save draft data of the feedback plugin. * - * @param {number} assignId The assignment ID. - * @param {number} userId User ID. - * @param {any} plugin The plugin object. - * @param {any} data The data to save. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {void|Promise} If the function is async, it should return a Promise resolved when done. + * @param assignId The assignment ID. + * @param userId User ID. + * @param plugin The plugin object. + * @param data The data to save. + * @param siteId Site ID. If not defined, current site. + * @return If the function is async, it should return a Promise resolved when done. */ saveDraft(assignId: number, userId: number, plugin: any, data: any, siteId?: string): void | Promise { if (data) { diff --git a/src/addon/mod/assign/feedback/editpdf/providers/handler.ts b/src/addon/mod/assign/feedback/editpdf/providers/handler.ts index 1758fb8fe..45276e932 100644 --- a/src/addon/mod/assign/feedback/editpdf/providers/handler.ts +++ b/src/addon/mod/assign/feedback/editpdf/providers/handler.ts @@ -32,9 +32,9 @@ export class AddonModAssignFeedbackEditPdfHandler implements AddonModAssignFeedb * Return the Component to use to display the plugin data, either in read or in edit mode. * 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} plugin The plugin object. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param plugin The plugin object. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector, plugin: any): any | Promise { return AddonModAssignFeedbackEditPdfComponent; @@ -44,11 +44,11 @@ export class AddonModAssignFeedbackEditPdfHandler implements AddonModAssignFeedb * Get files used by this plugin. * The files returned by this function will be prefetched when the user prefetches the assign. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {any[]|Promise} The files (or promise resolved with the files). + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param siteId Site ID. If not defined, current site. + * @return The files (or promise resolved with the files). */ getPluginFiles(assign: any, submission: any, plugin: any, siteId?: string): any[] | Promise { return this.assignProvider.getSubmissionPluginAttachments(plugin); @@ -57,7 +57,7 @@ export class AddonModAssignFeedbackEditPdfHandler implements AddonModAssignFeedb /** * 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/assign/feedback/file/providers/handler.ts b/src/addon/mod/assign/feedback/file/providers/handler.ts index fb3936a26..2d55cc35d 100644 --- a/src/addon/mod/assign/feedback/file/providers/handler.ts +++ b/src/addon/mod/assign/feedback/file/providers/handler.ts @@ -32,9 +32,9 @@ export class AddonModAssignFeedbackFileHandler implements AddonModAssignFeedback * Return the Component to use to display the plugin data, either in read or in edit mode. * 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} plugin The plugin object. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param plugin The plugin object. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector, plugin: any): any | Promise { return AddonModAssignFeedbackFileComponent; @@ -44,11 +44,11 @@ export class AddonModAssignFeedbackFileHandler implements AddonModAssignFeedback * Get files used by this plugin. * The files returned by this function will be prefetched when the user prefetches the assign. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {any[]|Promise} The files (or promise resolved with the files). + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param siteId Site ID. If not defined, current site. + * @return The files (or promise resolved with the files). */ getPluginFiles(assign: any, submission: any, plugin: any, siteId?: string): any[] | Promise { return this.assignProvider.getSubmissionPluginAttachments(plugin); @@ -57,7 +57,7 @@ export class AddonModAssignFeedbackFileHandler implements AddonModAssignFeedback /** * 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/assign/pages/edit-feedback-modal/edit-feedback-modal.ts b/src/addon/mod/assign/pages/edit-feedback-modal/edit-feedback-modal.ts index 08f2369bb..3c60de76e 100644 --- a/src/addon/mod/assign/pages/edit-feedback-modal/edit-feedback-modal.ts +++ b/src/addon/mod/assign/pages/edit-feedback-modal/edit-feedback-modal.ts @@ -47,7 +47,7 @@ export class AddonModAssignEditFeedbackModalPage { /** * 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) { @@ -64,7 +64,7 @@ export class AddonModAssignEditFeedbackModalPage { /** * Close modal. * - * @param {any} data Data to return to the page. + * @param data Data to return to the page. */ closeModal(data: any): void { this.viewCtrl.dismiss(data); @@ -73,7 +73,7 @@ export class AddonModAssignEditFeedbackModalPage { /** * Done editing. * - * @param {Event} e Click event. + * @param e Click event. */ done(e: Event): void { e.preventDefault(); @@ -87,7 +87,7 @@ export class AddonModAssignEditFeedbackModalPage { /** * Get the input data. * - * @return {any} Object with the data. + * @return Object with the data. */ protected getInputData(): any { return this.domUtils.getDataFromForm(document.forms['addon-mod_assign-edit-feedback-form']); @@ -96,7 +96,7 @@ export class AddonModAssignEditFeedbackModalPage { /** * Check if data has changed. * - * @return {Promise} Promise resolved with boolean: whether the data has changed. + * @return Promise resolved with boolean: whether the data has changed. */ protected hasDataChanged(): Promise { return this.feedbackDelegate.hasPluginDataChanged(this.assign, this.userId, this.plugin, this.getInputData(), this.userId) diff --git a/src/addon/mod/assign/pages/edit/edit.ts b/src/addon/mod/assign/pages/edit/edit.ts index f6072f48f..51f72b0a6 100644 --- a/src/addon/mod/assign/pages/edit/edit.ts +++ b/src/addon/mod/assign/pages/edit/edit.ts @@ -80,7 +80,7 @@ export class AddonModAssignEditPage 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) { @@ -101,7 +101,7 @@ export class AddonModAssignEditPage implements OnInit, OnDestroy { /** * Fetch assignment data. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchAssignment(): Promise { const currentUserId = this.sitesProvider.getCurrentSiteUserId(); @@ -175,7 +175,7 @@ export class AddonModAssignEditPage implements OnInit, OnDestroy { /** * Get the input data. * - * @return {any} Input data. + * @return Input data. */ protected getInputData(): any { return this.domUtils.getDataFromForm(document.forms['addon-mod_assign-edit-form']); @@ -184,7 +184,7 @@ export class AddonModAssignEditPage implements OnInit, OnDestroy { /** * Check if data has changed. * - * @return {Promise} Promise resolved with boolean: whether data has changed. + * @return Promise resolved with boolean: whether data has changed. */ protected hasDataChanged(): Promise { // Usually the hasSubmissionDataChanged call will be resolved inmediately, causing the modal to be shown just an instant. @@ -220,8 +220,8 @@ export class AddonModAssignEditPage implements OnInit, OnDestroy { /** * Get data to submit based on the input data. * - * @param {any} inputData The input data. - * @return {Promise} Promise resolved with the data to submit. + * @param inputData The input data. + * @return Promise resolved with the data to submit. */ protected prepareSubmissionData(inputData: any): Promise { // If there's offline data, always save it in offline. @@ -263,7 +263,7 @@ export class AddonModAssignEditPage implements OnInit, OnDestroy { /** * Save the submission. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected saveSubmission(): Promise { const inputData = this.getInputData(); diff --git a/src/addon/mod/assign/pages/index/index.ts b/src/addon/mod/assign/pages/index/index.ts index f9b960f67..3dedd405a 100644 --- a/src/addon/mod/assign/pages/index/index.ts +++ b/src/addon/mod/assign/pages/index/index.ts @@ -40,7 +40,7 @@ export class AddonModAssignIndexPage { /** * Update some data based on the assign instance. * - * @param {any} assign Assign instance. + * @param assign Assign instance. */ updateData(assign: any): void { this.title = assign.name || this.title; diff --git a/src/addon/mod/assign/pages/submission-list/submission-list.ts b/src/addon/mod/assign/pages/submission-list/submission-list.ts index a8bcf8c6d..9cc87c045 100644 --- a/src/addon/mod/assign/pages/submission-list/submission-list.ts +++ b/src/addon/mod/assign/pages/submission-list/submission-list.ts @@ -105,7 +105,7 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy { /** * Fetch assignment data. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchAssignment(): Promise { @@ -138,8 +138,8 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy { /** * Set group to see the summary. * - * @param {number} groupId Group ID. - * @return {Promise} Resolved when done. + * @param groupId Group ID. + * @return Resolved when done. */ setGroup(groupId: number): Promise { this.groupId = groupId; @@ -242,7 +242,7 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy { /** * Load a certain submission. * - * @param {any} submission The submission to load. + * @param submission The submission to load. */ loadSubmission(submission: any): void { if (this.selectedSubmissionId === submission.submitid && this.splitviewCtrl.isOn()) { @@ -263,7 +263,7 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy { /** * Refresh all the data. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected refreshAllData(): Promise { const promises = []; @@ -284,7 +284,7 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy { /** * Refresh the list. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshList(refresher: any): void { this.refreshAllData().finally(() => { diff --git a/src/addon/mod/assign/pages/submission-review/submission-review.ts b/src/addon/mod/assign/pages/submission-review/submission-review.ts index 44b0ca4a9..a3652667d 100644 --- a/src/addon/mod/assign/pages/submission-review/submission-review.ts +++ b/src/addon/mod/assign/pages/submission-review/submission-review.ts @@ -67,7 +67,7 @@ export class AddonModAssignSubmissionReviewPage 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 { if (!this.submissionComponent || this.forceLeave) { @@ -95,7 +95,7 @@ export class AddonModAssignSubmissionReviewPage implements OnInit { /** * Get the submission. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchSubmission(): Promise { return this.assignProvider.getAssignment(this.courseId, this.moduleId).then((assignment) => { @@ -123,7 +123,7 @@ export class AddonModAssignSubmissionReviewPage implements OnInit { /** * Refresh all the data. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected refreshAllData(): Promise { const promises = []; @@ -146,7 +146,7 @@ export class AddonModAssignSubmissionReviewPage implements OnInit { /** * Refresh the data. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshSubmission(refresher: any): void { this.refreshAllData().finally(() => { diff --git a/src/addon/mod/assign/providers/assign-offline.ts b/src/addon/mod/assign/providers/assign-offline.ts index 87d630837..f870bae6d 100644 --- a/src/addon/mod/assign/providers/assign-offline.ts +++ b/src/addon/mod/assign/providers/assign-offline.ts @@ -138,10 +138,10 @@ export class AddonModAssignOfflineProvider { /** * Delete a submission. * - * @param {number} assignId Assignment ID. - * @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 deleted, rejected if failure. + * @param assignId Assignment ID. + * @param userId User ID. If not defined, site's current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if deleted, rejected if failure. */ deleteSubmission(assignId: number, userId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -155,10 +155,10 @@ export class AddonModAssignOfflineProvider { /** * Delete a submission grade. * - * @param {number} assignId Assignment ID. - * @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 deleted, rejected if failure. + * @param assignId Assignment ID. + * @param userId User ID. If not defined, site's current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if deleted, rejected if failure. */ deleteSubmissionGrade(assignId: number, userId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -172,8 +172,8 @@ export class AddonModAssignOfflineProvider { /** * Get all the assignments ids that have something to be synced. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with assignments id that have something to be synced. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with assignments id that have something to be synced. */ getAllAssigns(siteId?: string): Promise { const promises = []; @@ -202,8 +202,8 @@ export class AddonModAssignOfflineProvider { /** * Get all the stored submissions from all the assignments. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { @@ -222,8 +222,8 @@ export class AddonModAssignOfflineProvider { /** * Get all the stored submissions grades from all the assignments. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with submissions grades. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with submissions grades. */ protected getAllSubmissionsGrade(siteId?: string): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { @@ -243,9 +243,9 @@ export class AddonModAssignOfflineProvider { /** * Get all the stored submissions for a certain assignment. * - * @param {number} assignId Assignment ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with submissions. + * @param assignId Assignment ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with submissions. */ getAssignSubmissions(assignId: number, siteId?: string): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { @@ -264,9 +264,9 @@ export class AddonModAssignOfflineProvider { /** * Get all the stored submissions grades for a certain assignment. * - * @param {number} assignId Assignment ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with submissions grades. + * @param assignId Assignment ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with submissions grades. */ getAssignSubmissionsGrade(assignId: number, siteId?: string): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { @@ -286,10 +286,10 @@ export class AddonModAssignOfflineProvider { /** * Get a stored submission. * - * @param {number} assignId Assignment ID. - * @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 with submission. + * @param assignId Assignment ID. + * @param userId User ID. If not defined, site's current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with submission. */ getSubmission(assignId: number, userId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -308,10 +308,10 @@ export class AddonModAssignOfflineProvider { /** * Get the path to the folder where to store files for an offline submission. * - * @param {number} assignId Assignment ID. - * @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 with the path. + * @param assignId Assignment ID. + * @param userId User ID. If not defined, site's current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the path. */ getSubmissionFolder(assignId: number, userId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -328,10 +328,10 @@ export class AddonModAssignOfflineProvider { * Get a stored submission grade. * Submission grades are not identified using attempt number so it can retrieve the feedback for a previous attempt. * - * @param {number} assignId Assignment ID. - * @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 with submission grade. + * @param assignId Assignment ID. + * @param userId User ID. If not defined, site's current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with submission grade. */ getSubmissionGrade(assignId: number, userId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -352,11 +352,11 @@ export class AddonModAssignOfflineProvider { /** * Get the path to the folder where to store files for a certain plugin in an offline submission. * - * @param {number} assignId Assignment ID. - * @param {string} pluginName Name of the plugin. Must be unique (both in submission and feedback plugins). - * @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 with the path. + * @param assignId Assignment ID. + * @param pluginName Name of the plugin. Must be unique (both in submission and feedback plugins). + * @param userId User ID. If not defined, site's current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the path. */ getSubmissionPluginFolder(assignId: number, pluginName: string, userId?: number, siteId?: string): Promise { return this.getSubmissionFolder(assignId, userId, siteId).then((folderPath) => { @@ -367,9 +367,9 @@ export class AddonModAssignOfflineProvider { /** * Check if the assignment has something to be synced. * - * @param {number} assignId Assignment ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean: whether the assignment has something to be synced. + * @param assignId Assignment ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: whether the assignment has something to be synced. */ hasAssignOfflineData(assignId: number, siteId?: string): Promise { const promises = []; @@ -396,14 +396,14 @@ export class AddonModAssignOfflineProvider { /** * Mark/Unmark a submission as being submitted. * - * @param {number} assignId Assignment ID. - * @param {number} courseId Course ID the assign belongs to. - * @param {boolean} submitted True to mark as submitted, false to mark as not submitted. - * @param {boolean} acceptStatement True to accept the submission statement, false otherwise. - * @param {number} timemodified The time the submission was last modified in online. - * @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 marked, rejected if failure. + * @param assignId Assignment ID. + * @param courseId Course ID the assign belongs to. + * @param submitted True to mark as submitted, false to mark as not submitted. + * @param acceptStatement True to accept the submission statement, false otherwise. + * @param timemodified The time the submission was last modified in online. + * @param userId User ID. If not defined, site's current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if marked, rejected if failure. */ markSubmitted(assignId: number, courseId: number, submitted: boolean, acceptStatement: boolean, timemodified: number, userId?: number, siteId?: string): Promise { @@ -438,14 +438,14 @@ export class AddonModAssignOfflineProvider { /** * Save a submission to be sent later. * - * @param {number} assignId Assignment ID. - * @param {number} courseId Course ID the assign belongs to. - * @param {any} pluginData Data to save. - * @param {number} timemodified The time the submission was last modified in online. - * @param {boolean} submitted True if submission has been submitted, false otherwise. - * @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 stored, rejected if failure. + * @param assignId Assignment ID. + * @param courseId Course ID the assign belongs to. + * @param pluginData Data to save. + * @param timemodified The time the submission was last modified in online. + * @param submitted True if submission has been submitted, false otherwise. + * @param userId User ID. If not defined, site's current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if stored, rejected if failure. */ saveSubmission(assignId: number, courseId: number, pluginData: any, timemodified: number, submitted: boolean, userId?: number, siteId?: string): Promise { @@ -472,18 +472,18 @@ export class AddonModAssignOfflineProvider { /** * Save a grading to be sent later. * - * @param {number} assignId Assign ID. - * @param {number} userId User ID. - * @param {number} courseId Course ID the assign belongs to. - * @param {number} grade Grade to submit. - * @param {number} attemptNumber Number of the attempt being graded. - * @param {boolean} addAttempt Admit the user to attempt again. - * @param {string} workflowState Next workflow State. - * @param {boolean} applyToAll If it's a team submission, whether the grade applies to all group members. - * @param {any} outcomes Object including all outcomes values. If empty, any of them will be sent. - * @param {any} pluginData Plugin data to save. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if stored, rejected if failure. + * @param assignId Assign ID. + * @param userId User ID. + * @param courseId Course ID the assign belongs to. + * @param grade Grade to submit. + * @param attemptNumber Number of the attempt being graded. + * @param addAttempt Admit the user to attempt again. + * @param workflowState Next workflow State. + * @param applyToAll If it's a team submission, whether the grade applies to all group members. + * @param outcomes Object including all outcomes values. If empty, any of them will be sent. + * @param pluginData Plugin data to save. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if stored, rejected if failure. */ submitGradingForm(assignId: number, userId: number, courseId: number, grade: number, attemptNumber: number, addAttempt: boolean, workflowState: string, applyToAll: boolean, outcomes: any, pluginData: any, siteId?: string): Promise { diff --git a/src/addon/mod/assign/providers/assign-sync.ts b/src/addon/mod/assign/providers/assign-sync.ts index 049b44ba3..60967dc07 100644 --- a/src/addon/mod/assign/providers/assign-sync.ts +++ b/src/addon/mod/assign/providers/assign-sync.ts @@ -36,13 +36,11 @@ import { AddonModAssignSubmissionDelegate } from './submission-delegate'; export interface AddonModAssignSyncResult { /** * List of warnings. - * @type {string[]} */ warnings: string[]; /** * Whether data was updated in the site. - * @type {boolean} */ updated: boolean; } @@ -74,9 +72,9 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider { /** * Convenience function to get scale selected option. * - * @param {string} options Possible options. - * @param {number} selected Selected option to search. - * @return {number} Index of the selected option. + * @param options Possible options. + * @param selected Selected option to search. + * @return Index of the selected option. */ protected getSelectedScaleId(options: string, selected: string): number { let optionsList = options.split(','); @@ -98,9 +96,9 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider { /** * Check if an assignment has data to synchronize. * - * @param {number} assignId Assign ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean: whether it has data to sync. + * @param assignId Assign ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: whether it has data to sync. */ hasDataToSync(assignId: number, siteId?: string): Promise { return this.assignOfflineProvider.hasAssignOfflineData(assignId, siteId); @@ -109,9 +107,9 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider { /** * Try to synchronize all the assignments 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. */ syncAllAssignments(siteId?: string, force?: boolean): Promise { return this.syncOnSites('all assignments', this.syncAllAssignmentsFunc.bind(this), [force], siteId); @@ -120,9 +118,9 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider { /** * Sync all assignments on a site. * - * @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. - * @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 force Wether to force sync not depending on last execution. + * @param Promise resolved if sync is successful, rejected if sync fails. */ protected syncAllAssignmentsFunc(siteId?: string, force?: boolean): Promise { // Get all assignments that have offline data. @@ -149,9 +147,9 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider { /** * Sync an assignment only if a certain time has passed since the last time. * - * @param {number} assignId Assign ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the assign is synced or it doesn't need to be synced. + * @param assignId Assign ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the assign is synced or it doesn't need to be synced. */ syncAssignIfNeeded(assignId: number, siteId?: string): Promise { return this.isSyncNeeded(assignId, siteId).then((needed) => { @@ -164,9 +162,9 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider { /** * Try to synchronize an assign. * - * @param {number} assignId Assign ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved in success. + * @param assignId Assign ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved in success. */ syncAssign(assignId: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -265,11 +263,11 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider { /** * Synchronize a submission. * - * @param {any} assign Assignment. - * @param {any} offlineData Submission offline data. - * @param {string[]} warnings List of warnings. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if success, rejected otherwise. + * @param assign Assignment. + * @param offlineData Submission offline data. + * @param warnings List of warnings. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if success, rejected otherwise. */ protected syncSubmission(assign: any, offlineData: any, warnings: string[], siteId?: string): Promise { const userId = offlineData.userid, @@ -353,12 +351,12 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider { /** * Synchronize a submission grade. * - * @param {any} assign Assignment. - * @param {any} offlineData Submission grade offline data. - * @param {string[]} warnings List of warnings. - * @param {number} courseId Course Id. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if success, rejected otherwise. + * @param assign Assignment. + * @param offlineData Submission grade offline data. + * @param warnings List of warnings. + * @param courseId Course Id. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if success, rejected otherwise. */ protected syncSubmissionGrade(assign: any, offlineData: any, warnings: string[], courseId: number, siteId?: string) : Promise { diff --git a/src/addon/mod/assign/providers/assign.ts b/src/addon/mod/assign/providers/assign.ts index 26c6ecf41..e819751a6 100644 --- a/src/addon/mod/assign/providers/assign.ts +++ b/src/addon/mod/assign/providers/assign.ts @@ -78,9 +78,9 @@ export class AddonModAssignProvider { * be used (offline usage). * This function doesn't check if the submission is empty, it should be checked before calling this function. * - * @param {any} assign Assignment instance. - * @param {any} submissionStatus Submission status returned by getSubmissionStatus. - * @return {boolean} Whether it can submit. + * @param assign Assignment instance. + * @param submissionStatus Submission status returned by getSubmissionStatus. + * @return Whether it can submit. */ canSubmitOffline(assign: any, submissionStatus: any): boolean { if (!this.isSubmissionOpen(assign, submissionStatus)) { @@ -117,11 +117,11 @@ export class AddonModAssignProvider { /** * Get an assignment by course module ID. * - * @param {number} courseId Course ID the assignment belongs to. - * @param {number} cmId Assignment module ID. - * @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 assignment. + * @param courseId Course ID the assignment belongs to. + * @param cmId Assignment module 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 with the assignment. */ getAssignment(courseId: number, cmId: number, ignoreCache?: boolean, siteId?: string): Promise { return this.getAssignmentByField(courseId, 'cmid', cmId, ignoreCache, siteId); @@ -130,12 +130,12 @@ export class AddonModAssignProvider { /** * Get an assigment 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} [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 assignment is retrieved. + * @param courseId Course ID. + * @param key Name of the property to check. + * @param value Value to search. + * @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 assignment is retrieved. */ protected getAssignmentByField(courseId: number, key: string, value: any, ignoreCache?: boolean, siteId?: string) : Promise { @@ -181,11 +181,11 @@ export class AddonModAssignProvider { /** * Get an assignment by instance ID. * - * @param {number} courseId Course ID the assignment belongs to. - * @param {number} cmId Assignment instance ID. - * @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 assignment. + * @param courseId Course ID the assignment belongs to. + * @param cmId Assignment instance 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 with the assignment. */ getAssignmentById(courseId: number, id: number, ignoreCache?: boolean, siteId?: string): Promise { return this.getAssignmentByField(courseId, 'id', id, ignoreCache, siteId); @@ -194,8 +194,8 @@ export class AddonModAssignProvider { /** * Get cache key for assignment data WS calls. * - * @param {number} courseId Course ID. - * @return {string} Cache key. + * @param courseId Course ID. + * @return Cache key. */ protected getAssignmentCacheKey(courseId: number): string { return this.ROOT_CACHE_KEY + 'assignment:' + courseId; @@ -204,11 +204,11 @@ export class AddonModAssignProvider { /** * Get an assignment user mapping for blind marking. * - * @param {number} assignId Assignment Id. - * @param {number} userId User Id to be blinded. - * @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 user blind id. + * @param assignId Assignment Id. + * @param userId User Id to be blinded. + * @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 user blind id. */ getAssignmentUserMappings(assignId: number, userId: number, ignoreCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -256,8 +256,8 @@ export class AddonModAssignProvider { /** * Get cache key for assignment user mappings data WS calls. * - * @param {number} assignId Assignment ID. - * @return {string} Cache key. + * @param assignId Assignment ID. + * @return Cache key. */ protected getAssignmentUserMappingsCacheKey(assignId: number): string { return this.ROOT_CACHE_KEY + 'usermappings:' + assignId; @@ -266,10 +266,10 @@ export class AddonModAssignProvider { /** * Returns grade information from assign_grades for the requested assignment id * - * @param {number} assignId Assignment Id. - * @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} Resolved with requested info when done. + * @param assignId Assignment 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 Resolved with requested info when done. */ getAssignmentGrades(assignId: number, ignoreCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -310,8 +310,8 @@ export class AddonModAssignProvider { /** * Get cache key for assignment grades data WS calls. * - * @param {number} assignId Assignment ID. - * @return {string} Cache key. + * @param assignId Assignment ID. + * @return Cache key. */ protected getAssignmentGradesCacheKey(assignId: number): string { return this.ROOT_CACHE_KEY + 'assigngrades:' + assignId; @@ -320,8 +320,8 @@ export class AddonModAssignProvider { /** * Returns the color name for a given grading status name. * - * @param {string} status Grading status name - * @return {string} The color name. + * @param status Grading status name + * @return The color name. */ getSubmissionGradingStatusColor(status: string): string { if (!status) { @@ -339,8 +339,8 @@ export class AddonModAssignProvider { /** * Returns the translation id for a given grading status name. * - * @param {string} status Grading Status name - * @return {string} The status translation identifier. + * @param status Grading Status name + * @return The status translation identifier. */ getSubmissionGradingStatusTranslationId(status: string): string { if (!status) { @@ -358,9 +358,9 @@ export class AddonModAssignProvider { /** * Get the submission object from an attempt. * - * @param {any} assign Assign. - * @param {any} attempt Attempt. - * @return {any} Submission object or null. + * @param assign Assign. + * @param attempt Attempt. + * @return Submission object or null. */ getSubmissionObjectFromAttempt(assign: any, attempt: any): any { if (!attempt) { @@ -373,8 +373,8 @@ export class AddonModAssignProvider { /** * Get attachments of a submission plugin. * - * @param {any} submissionPlugin Submission plugin. - * @return {any[]} Submission plugin attachments. + * @param submissionPlugin Submission plugin. + * @return Submission plugin attachments. */ getSubmissionPluginAttachments(submissionPlugin: any): any[] { const files = []; @@ -403,9 +403,9 @@ export class AddonModAssignProvider { /** * Get text of a submission plugin. * - * @param {any} submissionPlugin Submission plugin. - * @param {boolean} [keepUrls] True if it should keep original URLs, false if they should be replaced. - * @return {string} Submission text. + * @param submissionPlugin Submission plugin. + * @param keepUrls True if it should keep original URLs, false if they should be replaced. + * @return Submission text. */ getSubmissionPluginText(submissionPlugin: any, keepUrls?: boolean): string { let text = ''; @@ -426,10 +426,10 @@ export class AddonModAssignProvider { /** * Get an assignment submissions. * - * @param {number} assignId Assignment id. - * @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<{canviewsubmissions: boolean, submissions?: any[]}>} Promise resolved when done. + * @param assignId Assignment 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 done. */ getSubmissions(assignId: number, ignoreCache?: boolean, siteId?: string) : Promise<{canviewsubmissions: boolean, submissions?: any[]}> { @@ -469,8 +469,8 @@ export class AddonModAssignProvider { /** * Get cache key for assignment submissions data WS calls. * - * @param {number} assignId Assignment id. - * @return {string} Cache key. + * @param assignId Assignment id. + * @return Cache key. */ protected getSubmissionsCacheKey(assignId: number): string { return this.ROOT_CACHE_KEY + 'submissions:' + assignId; @@ -479,14 +479,14 @@ export class AddonModAssignProvider { /** * Get information about an assignment submission status for a given user. * - * @param {number} assignId Assignment instance id. - * @param {number} [userId] User Id (empty for current user). - * @param {number} [groupId] Group Id (empty for all participants). - * @param {boolean} [isBlind] If blind marking is enabled or not. - * @param {number} [filter=true] True to filter WS response and rewrite URLs, 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 (empty for current site). - * @return {Promise} Promise always resolved with the user submission status. + * @param assignId Assignment instance id. + * @param userId User Id (empty for current user). + * @param groupId Group Id (empty for all participants). + * @param isBlind If blind marking is enabled or not. + * @param filter True to filter WS response and rewrite URLs, false otherwise. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site id (empty for current site). + * @return Promise always resolved with the user submission status. */ getSubmissionStatus(assignId: number, userId?: number, groupId?: number, isBlind?: boolean, filter: boolean = true, ignoreCache?: boolean, siteId?: string): Promise { @@ -530,14 +530,14 @@ export class AddonModAssignProvider { * Get information about an assignment submission status for a given user. * If the data doesn't include the user submission, retry ignoring cache. * - * @param {any} assign Assignment. - * @param {number} [userId] User id (empty for current user). - * @param {number} [groupId] Group Id (empty for all participants). - * @param {boolean} [isBlind] If blind marking is enabled or not. - * @param {number} [filter=true] True to filter WS response and rewrite URLs, 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 (empty for current site). - * @return {Promise} Promise always resolved with the user submission status. + * @param assign Assignment. + * @param userId User id (empty for current user). + * @param groupId Group Id (empty for all participants). + * @param isBlind If blind marking is enabled or not. + * @param filter True to filter WS response and rewrite URLs, false otherwise. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site id (empty for current site). + * @return Promise always resolved with the user submission status. */ getSubmissionStatusWithRetry(assign: any, userId?: number, groupId?: number, isBlind?: boolean, filter: boolean = true, ignoreCache?: boolean, siteId?: string): Promise { @@ -560,11 +560,11 @@ export class AddonModAssignProvider { /** * Get cache key for get submission status data WS calls. * - * @param {number} assignId Assignment instance id. - * @param {number} [userId] User id (empty for current user). - * @param {number} [groupId] Group Id (empty for all participants). - * @param {number} [isBlind] If blind marking is enabled or not. - * @return {string} Cache key. + * @param assignId Assignment instance id. + * @param userId User id (empty for current user). + * @param groupId Group Id (empty for all participants). + * @param isBlind If blind marking is enabled or not. + * @return Cache key. */ protected getSubmissionStatusCacheKey(assignId: number, userId: number, groupId?: number, isBlind?: boolean): string { if (!userId) { @@ -578,8 +578,8 @@ export class AddonModAssignProvider { /** * Returns the color name for a given status name. * - * @param {string} status Status name - * @return {string} The color name. + * @param status Status name + * @return The color name. */ getSubmissionStatusColor(status: string): string { switch (status) { @@ -601,8 +601,8 @@ export class AddonModAssignProvider { /** * Given a list of plugins, returns the plugin names that aren't supported for editing. * - * @param {any[]} plugins Plugins to check. - * @return {Promise} Promise resolved with unsupported plugin names. + * @param plugins Plugins to check. + * @return Promise resolved with unsupported plugin names. */ getUnsupportedEditPlugins(plugins: any[]): Promise { const notSupported = [], @@ -624,11 +624,11 @@ export class AddonModAssignProvider { /** * List the participants for a single assignment, with some summary info about their submissions. * - * @param {number} assignId Assignment id. - * @param {number} [groupId] Group id. If not defined, 0. - * @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 list of participants and summary of submissions. + * @param assignId Assignment id. + * @param groupId Group id. If not defined, 0. + * @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 list of participants and summary of submissions. */ listParticipants(assignId: number, groupId?: number, ignoreCache?: boolean, siteId?: string): Promise { groupId = groupId || 0; @@ -661,9 +661,9 @@ export class AddonModAssignProvider { /** * Get cache key for assignment list participants data WS calls. * - * @param {number} assignId Assignment id. - * @param {number} groupId Group id. - * @return {string} Cache key. + * @param assignId Assignment id. + * @param groupId Group id. + * @return Cache key. */ protected listParticipantsCacheKey(assignId: number, groupId: number): string { return this.listParticipantsPrefixCacheKey(assignId) + ':' + groupId; @@ -672,8 +672,8 @@ export class AddonModAssignProvider { /** * Get prefix cache key for assignment list participants data WS calls. * - * @param {number} assignId Assignment id. - * @return {string} Cache key. + * @param assignId Assignment id. + * @return Cache key. */ protected listParticipantsPrefixCacheKey(assignId: number): string { return this.ROOT_CACHE_KEY + 'participants:' + assignId; @@ -682,9 +682,9 @@ export class AddonModAssignProvider { /** * Invalidates all submission status data. * - * @param {number} assignId Assignment instance id. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param assignId Assignment instance id. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateAllSubmissionData(assignId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -695,9 +695,9 @@ export class AddonModAssignProvider { /** * Invalidates assignment data WS calls. * - * @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. */ invalidateAssignmentData(courseId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -708,9 +708,9 @@ export class AddonModAssignProvider { /** * Invalidates assignment user mappings data WS calls. * - * @param {number} assignId Assignment ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param assignId Assignment ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateAssignmentUserMappingsData(assignId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -721,9 +721,9 @@ export class AddonModAssignProvider { /** * Invalidates assignment grades data WS calls. * - * @param {number} assignId Assignment ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param assignId Assignment ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateAssignmentGradesData(assignId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -735,10 +735,10 @@ export class AddonModAssignProvider { * Invalidate the prefetched content except files. * To invalidate files, use AddonModAssignProvider.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(); @@ -762,8 +762,8 @@ export class AddonModAssignProvider { /** * 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(), @@ -773,9 +773,9 @@ export class AddonModAssignProvider { /** * Invalidates assignment submissions data WS calls. * - * @param {number} assignId Assignment instance id. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param assignId Assignment instance id. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateSubmissionData(assignId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -786,12 +786,12 @@ export class AddonModAssignProvider { /** * Invalidates submission status data. * - * @param {number} assignId Assignment instance id. - * @param {number} [userId] User id (empty for current user). - * @param {number} [groupId] Group Id (empty for all participants). - * @param {boolean} [isBlind] Whether blind marking is enabled or not. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param assignId Assignment instance id. + * @param userId User id (empty for current user). + * @param groupId Group Id (empty for all participants). + * @param isBlind Whether blind marking is enabled or not. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateSubmissionStatusData(assignId: number, userId?: number, groupId?: number, isBlind?: boolean, siteId?: string): Promise { @@ -803,9 +803,9 @@ export class AddonModAssignProvider { /** * Invalidates assignment participants data. * - * @param {number} assignId Assignment instance id. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param assignId Assignment instance id. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateListParticipantsData(assignId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -816,8 +816,8 @@ export class AddonModAssignProvider { /** * Convenience function to check if grading offline is enabled. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean: whether grading offline is enabled. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: whether grading offline is enabled. */ protected isGradingOfflineEnabled(siteId?: string): Promise { if (typeof this.gradingOfflineEnabled[siteId] != 'undefined') { @@ -834,8 +834,8 @@ export class AddonModAssignProvider { /** * Outcomes only can be edited if mod_assign_submit_grading_form is avalaible. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with true if outcomes edit is enabled, rejected or resolved with false otherwise. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with true if outcomes edit is enabled, rejected or resolved with false otherwise. * @since 3.2 */ isOutcomesEditEnabled(siteId?: string): Promise { @@ -847,8 +847,8 @@ export class AddonModAssignProvider { /** * Check if assignments plugin is enabled in a certain site. * - * @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 { return true; @@ -857,9 +857,9 @@ export class AddonModAssignProvider { /** * Check if a submission is open. This function is based on Moodle's submissions_open. * - * @param {any} assign Assignment instance. - * @param {any} submissionStatus Submission status returned by getSubmissionStatus. - * @return {boolean} Whether submission is open. + * @param assign Assignment instance. + * @param submissionStatus Submission status returned by getSubmissionStatus. + * @return Whether submission is open. */ isSubmissionOpen(assign: any, submissionStatus: any): boolean { if (!assign || !submissionStatus) { @@ -914,10 +914,10 @@ export class AddonModAssignProvider { /** * Report an assignment submission as being viewed. * - * @param {number} assignId Assignment ID. - * @param {string} [name] Name of the assign. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the WS call is successful. + * @param assignId Assignment ID. + * @param name Name of the assign. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the WS call is successful. */ logSubmissionView(assignId: number, name?: string, siteId?: string): Promise { @@ -934,10 +934,10 @@ export class AddonModAssignProvider { /** * Report an assignment grading table is being viewed. * - * @param {number} assignId Assignment ID. - * @param {string} [name] Name of the assign. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the WS call is successful. + * @param assignId Assignment ID. + * @param name Name of the assign. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the WS call is successful. */ logGradingView(assignId: number, name?: string, siteId?: string): Promise { const params = { @@ -951,10 +951,10 @@ export class AddonModAssignProvider { /** * Report an assign as being viewed. * - * @param {number} assignId Assignment ID. - * @param {string} [name] Name of the assign. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the WS call is successful. + * @param assignId Assignment ID. + * @param name Name of the assign. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the WS call is successful. */ logView(assignId: number, name?: string, siteId?: string): Promise { const params = { @@ -968,9 +968,9 @@ export class AddonModAssignProvider { /** * Returns if a submissions needs to be graded. * - * @param {any} submission Submission. - * @param {number} assignId Assignment ID. - * @return {Promise} Promise resolved with boolean: whether it needs to be graded or not. + * @param submission Submission. + * @param assignId Assignment ID. + * @return Promise resolved with boolean: whether it needs to be graded or not. */ needsSubmissionToBeGraded(submission: any, assignId: number): Promise { if (!submission.gradingstatus) { @@ -999,15 +999,15 @@ export class AddonModAssignProvider { /** * Save current user submission for a certain assignment. * - * @param {number} assignId Assign ID. - * @param {number} courseId Course ID the assign belongs to. - * @param {any} pluginData Data to save. - * @param {boolean} allowOffline Whether to allow offline usage. - * @param {number} timemodified The time the submission was last modified in online. - * @param {boolean} [allowsDrafts] Whether the assignment allows submission drafts. - * @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 with true if sent to server, resolved with false if stored in offline. + * @param assignId Assign ID. + * @param courseId Course ID the assign belongs to. + * @param pluginData Data to save. + * @param allowOffline Whether to allow offline usage. + * @param timemodified The time the submission was last modified in online. + * @param allowsDrafts Whether the assignment allows submission drafts. + * @param userId User ID. If not defined, site's current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with true if sent to server, resolved with false if stored in offline. */ saveSubmission(assignId: number, courseId: number, pluginData: any, allowOffline: boolean, timemodified: number, allowsDrafts?: boolean, userId?: number, siteId?: string): Promise { @@ -1046,10 +1046,10 @@ export class AddonModAssignProvider { /** * Save current user submission for a certain assignment. It will fail if offline or cannot connect. * - * @param {number} assignId Assign ID. - * @param {any} pluginData Data to save. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when saved, rejected otherwise. + * @param assignId Assign ID. + * @param pluginData Data to save. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when saved, rejected otherwise. */ saveSubmissionOnline(assignId: number, pluginData: any, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1070,13 +1070,13 @@ export class AddonModAssignProvider { /** * Submit the current user assignment for grading. * - * @param {number} assignId Assign ID. - * @param {number} courseId Course ID the assign belongs to. - * @param {boolean} acceptStatement True if submission statement is accepted, false otherwise. - * @param {number} timemodified The time the submission was last modified in online. - * @param {boolean} [forceOffline] True to always mark it in offline. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with true if sent to server, resolved with false if stored in offline. + * @param assignId Assign ID. + * @param courseId Course ID the assign belongs to. + * @param acceptStatement True if submission statement is accepted, false otherwise. + * @param timemodified The time the submission was last modified in online. + * @param forceOffline True to always mark it in offline. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with true if sent to server, resolved with false if stored in offline. */ submitForGrading(assignId: number, courseId: number, acceptStatement: boolean, timemodified: number, forceOffline?: boolean, siteId?: string): Promise { @@ -1115,10 +1115,10 @@ export class AddonModAssignProvider { /** * Submit the current user assignment for grading. It will fail if offline or cannot connect. * - * @param {number} assignId Assign ID. - * @param {boolean} acceptStatement True if submission statement is accepted, false otherwise. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when submitted, rejected otherwise. + * @param assignId Assign ID. + * @param acceptStatement True if submission statement is accepted, false otherwise. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when submitted, rejected otherwise. */ submitForGradingOnline(assignId: number, acceptStatement: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1139,18 +1139,18 @@ export class AddonModAssignProvider { /** * Submit the grading for the current user and assignment. It will use old or new WS depending on availability. * - * @param {number} assignId Assign ID. - * @param {number} userId User ID. - * @param {number} courseId Course ID the assign belongs to. - * @param {number} grade Grade to submit. - * @param {number} attemptNumber Number of the attempt being graded. - * @param {boolean} addAttempt Admit the user to attempt again. - * @param {string} workflowState Next workflow State. - * @param {boolean} applyToAll If it's a team submission, whether the grade applies to all group members. - * @param {any} outcomes Object including all outcomes values. If empty, any of them will be sent. - * @param {any} pluginData Feedback plugin data to save. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with true if sent to server, resolved with false if stored offline. + * @param assignId Assign ID. + * @param userId User ID. + * @param courseId Course ID the assign belongs to. + * @param grade Grade to submit. + * @param attemptNumber Number of the attempt being graded. + * @param addAttempt Admit the user to attempt again. + * @param workflowState Next workflow State. + * @param applyToAll If it's a team submission, whether the grade applies to all group members. + * @param outcomes Object including all outcomes values. If empty, any of them will be sent. + * @param pluginData Feedback plugin data to save. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with true if sent to server, resolved with false if stored offline. */ submitGradingForm(assignId: number, userId: number, courseId: number, grade: number, attemptNumber: number, addAttempt: boolean, workflowState: string, applyToAll: boolean, outcomes: any, pluginData: any, siteId?: string): Promise { @@ -1199,17 +1199,17 @@ export class AddonModAssignProvider { * Submit the grading for the current user and assignment. It will use old or new WS depending on availability. * It will fail if offline or cannot connect. * - * @param {number} assignId Assign ID. - * @param {number} userId User ID. - * @param {number} grade Grade to submit. - * @param {number} attemptNumber Number of the attempt being graded. - * @param {number} addAttempt Allow the user to attempt again. - * @param {string} workflowState Next workflow State. - * @param {boolean} applyToAll If it's a team submission, if the grade applies to all group members. - * @param {any} outcomes Object including all outcomes values. If empty, any of them will be sent. - * @param {any} pluginData Feedback plugin data to save. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when submitted, rejected otherwise. + * @param assignId Assign ID. + * @param userId User ID. + * @param grade Grade to submit. + * @param attemptNumber Number of the attempt being graded. + * @param addAttempt Allow the user to attempt again. + * @param workflowState Next workflow State. + * @param applyToAll If it's a team submission, if the grade applies to all group members. + * @param outcomes Object including all outcomes values. If empty, any of them will be sent. + * @param pluginData Feedback plugin data to save. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when submitted, rejected otherwise. */ submitGradingFormOnline(assignId: number, userId: number, grade: number, attemptNumber: number, addAttempt: boolean, workflowState: string, applyToAll: boolean, outcomes: any, pluginData: any, siteId?: string): Promise { diff --git a/src/addon/mod/assign/providers/feedback-delegate.ts b/src/addon/mod/assign/providers/feedback-delegate.ts index 6598bf4e9..c72dda969 100644 --- a/src/addon/mod/assign/providers/feedback-delegate.ts +++ b/src/addon/mod/assign/providers/feedback-delegate.ts @@ -26,17 +26,16 @@ export interface AddonModAssignFeedbackHandler extends CoreDelegateHandler { /** * Name of the type of feedback the handler supports. E.g. 'file'. - * @type {string} */ type: string; /** * Discard the draft data of the feedback plugin. * - * @param {number} assignId The assignment ID. - * @param {number} userId User ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {void|Promise} If the function is async, it should return a Promise resolved when done. + * @param assignId The assignment ID. + * @param userId User ID. + * @param siteId Site ID. If not defined, current site. + * @return If the function is async, it should return a Promise resolved when done. */ discardDraft?(assignId: number, userId: number, siteId?: string): void | Promise; @@ -44,19 +43,19 @@ export interface AddonModAssignFeedbackHandler extends CoreDelegateHandler { * 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} plugin The plugin object. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param plugin The plugin object. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent?(injector: Injector, plugin: any): any | Promise; /** * Return the draft saved data of the feedback plugin. * - * @param {number} assignId The assignment ID. - * @param {number} userId User ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {any|Promise} Data (or promise resolved with the data). + * @param assignId The assignment ID. + * @param userId User ID. + * @param siteId Site ID. If not defined, current site. + * @return Data (or promise resolved with the data). */ getDraft?(assignId: number, userId: number, siteId?: string): any | Promise; @@ -64,41 +63,41 @@ export interface AddonModAssignFeedbackHandler extends CoreDelegateHandler { * Get files used by this plugin. * The files returned by this function will be prefetched when the user prefetches the assign. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {any[]|Promise} The files (or promise resolved with the files). + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param siteId Site ID. If not defined, current site. + * @return The files (or promise resolved with the files). */ getPluginFiles?(assign: any, submission: any, plugin: any, siteId?: string): any[] | Promise; /** * Get a readable name to use for the plugin. * - * @param {any} plugin The plugin object. - * @return {string} The plugin name. + * @param plugin The plugin object. + * @return The plugin name. */ getPluginName?(plugin: any): string; /** * Check if the feedback data has changed for this plugin. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {any} inputData Data entered by the user for the feedback. - * @param {number} userId User ID of the submission. - * @return {boolean|Promise} Boolean (or promise resolved with boolean): whether the data has changed. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param inputData Data entered by the user for the feedback. + * @param userId User ID of the submission. + * @return Boolean (or promise resolved with boolean): whether the data has changed. */ hasDataChanged?(assign: any, submission: any, plugin: any, inputData: any, userId: number): boolean | Promise; /** * Check whether the plugin has draft data stored. * - * @param {number} assignId The assignment ID. - * @param {number} userId User ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {boolean|Promise} Boolean or promise resolved with boolean: whether the plugin has draft data. + * @param assignId The assignment ID. + * @param userId User ID. + * @param siteId Site ID. If not defined, current site. + * @return Boolean or promise resolved with boolean: whether the plugin has draft data. */ hasDraftData?(assignId: number, userId: number, siteId?: string): boolean | Promise; @@ -106,35 +105,35 @@ export interface AddonModAssignFeedbackHandler extends CoreDelegateHandler { * Prefetch any required data for the plugin. * This should NOT prefetch files. Files to be prefetched should be returned by the getPluginFiles function. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ prefetch?(assign: any, submission: any, plugin: any, siteId?: string): Promise; /** * Prepare and add to pluginData the data to send to the server based on the draft data saved. * - * @param {number} assignId The assignment ID. - * @param {number} userId User ID. - * @param {any} plugin The plugin object. - * @param {any} pluginData Object where to store the data to send. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {void|Promise} If the function is async, it should return a Promise resolved when done. + * @param assignId The assignment ID. + * @param userId User ID. + * @param plugin The plugin object. + * @param pluginData Object where to store the data to send. + * @param siteId Site ID. If not defined, current site. + * @return If the function is async, it should return a Promise resolved when done. */ prepareFeedbackData?(assignId: number, userId: number, plugin: any, pluginData: any, siteId?: string): void | Promise; /** * Save draft data of the feedback plugin. * - * @param {number} assignId The assignment ID. - * @param {number} userId User ID. - * @param {any} plugin The plugin object. - * @param {any} data The data to save. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {void|Promise} If the function is async, it should return a Promise resolved when done. + * @param assignId The assignment ID. + * @param userId User ID. + * @param plugin The plugin object. + * @param data The data to save. + * @param siteId Site ID. If not defined, current site. + * @return If the function is async, it should return a Promise resolved when done. */ saveDraft?(assignId: number, userId: number, plugin: any, data: any, siteId?: string): void | Promise; } @@ -155,11 +154,11 @@ export class AddonModAssignFeedbackDelegate extends CoreDelegate { /** * Discard the draft data of the feedback plugin. * - * @param {number} assignId The assignment ID. - * @param {number} userId User ID. - * @param {any} plugin The plugin object. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param assignId The assignment ID. + * @param userId User ID. + * @param plugin The plugin object. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ discardPluginFeedbackData(assignId: number, userId: number, plugin: any, siteId?: string): Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'discardDraft', [assignId, userId, siteId])); @@ -168,9 +167,9 @@ export class AddonModAssignFeedbackDelegate extends CoreDelegate { /** * Get the component to use for a certain feedback plugin. * - * @param {Injector} injector Injector. - * @param {any} plugin The plugin object. - * @return {Promise} Promise resolved with the component to use, undefined if not found. + * @param injector Injector. + * @param plugin The plugin object. + * @return Promise resolved with the component to use, undefined if not found. */ getComponentForPlugin(injector: Injector, plugin: any): Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'getComponent', [injector, plugin])); @@ -179,11 +178,11 @@ export class AddonModAssignFeedbackDelegate extends CoreDelegate { /** * Return the draft saved data of the feedback plugin. * - * @param {number} assignId The assignment ID. - * @param {number} userId User ID. - * @param {any} plugin The plugin object. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the draft data. + * @param assignId The assignment ID. + * @param userId User ID. + * @param plugin The plugin object. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the draft data. */ getPluginDraftData(assignId: number, userId: number, plugin: any, siteId?: string): Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'getDraft', [assignId, userId, siteId])); @@ -193,11 +192,11 @@ export class AddonModAssignFeedbackDelegate extends CoreDelegate { * Get files used by this plugin. * The files returned by this function will be prefetched when the user prefetches the assign. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the files. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the files. */ getPluginFiles(assign: any, submission: any, plugin: any, siteId?: string): Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'getPluginFiles', [assign, submission, plugin, siteId])); @@ -206,8 +205,8 @@ export class AddonModAssignFeedbackDelegate extends CoreDelegate { /** * Get a readable name to use for a certain feedback plugin. * - * @param {any} plugin Plugin to get the name for. - * @return {string} Human readable name. + * @param plugin Plugin to get the name for. + * @return Human readable name. */ getPluginName(plugin: any): string { return this.executeFunctionOnEnabled(plugin.type, 'getPluginName', [plugin]); @@ -216,12 +215,12 @@ export class AddonModAssignFeedbackDelegate extends CoreDelegate { /** * Check if the feedback data has changed for a certain plugin. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {any} inputData Data entered by the user for the feedback. - * @param {number} userId User ID of the submission. - * @return {Promise} Promise resolved with true if data has changed, resolved with false otherwise. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param inputData Data entered by the user for the feedback. + * @param userId User ID of the submission. + * @return Promise resolved with true if data has changed, resolved with false otherwise. */ hasPluginDataChanged(assign: any, submission: any, plugin: any, inputData: any, userId: number): Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'hasDataChanged', @@ -231,11 +230,11 @@ export class AddonModAssignFeedbackDelegate extends CoreDelegate { /** * Check whether the plugin has draft data stored. * - * @param {number} assignId The assignment ID. - * @param {number} userId User ID. - * @param {any} plugin The plugin object. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with true if it has draft data. + * @param assignId The assignment ID. + * @param userId User ID. + * @param plugin The plugin object. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with true if it has draft data. */ hasPluginDraftData(assignId: number, userId: number, plugin: any, siteId?: string): Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'hasDraftData', [assignId, userId, siteId])); @@ -244,8 +243,8 @@ export class AddonModAssignFeedbackDelegate extends CoreDelegate { /** * Check if a feedback plugin is supported. * - * @param {string} pluginType Type of the plugin. - * @return {boolean} Whether it's supported. + * @param pluginType Type of the plugin. + * @return Whether it's supported. */ isPluginSupported(pluginType: string): boolean { return this.hasHandler(pluginType, true); @@ -254,11 +253,11 @@ export class AddonModAssignFeedbackDelegate extends CoreDelegate { /** * Prefetch any required data for a feedback plugin. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ prefetch(assign: any, submission: any, plugin: any, siteId?: string): Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'prefetch', [assign, submission, plugin, siteId])); @@ -267,12 +266,12 @@ export class AddonModAssignFeedbackDelegate extends CoreDelegate { /** * Prepare and add to pluginData the data to submit for a certain feedback plugin. * - * @param {number} assignId The assignment ID. - * @param {number} userId User ID. - * @param {any} plugin The plugin object. - * @param {any} pluginData Object where to store the data to send. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when data has been gathered. + * @param assignId The assignment ID. + * @param userId User ID. + * @param plugin The plugin object. + * @param pluginData Object where to store the data to send. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when data has been gathered. */ preparePluginFeedbackData(assignId: number, userId: number, plugin: any, pluginData: any, siteId?: string): Promise { @@ -283,12 +282,12 @@ export class AddonModAssignFeedbackDelegate extends CoreDelegate { /** * Save draft data of the feedback plugin. * - * @param {number} assignId The assignment ID. - * @param {number} userId User ID. - * @param {any} plugin The plugin object. - * @param {any} inputData Data to save. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when data has been saved. + * @param assignId The assignment ID. + * @param userId User ID. + * @param plugin The plugin object. + * @param inputData Data to save. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when data has been saved. */ saveFeedbackDraft(assignId: number, userId: number, plugin: any, inputData: any, siteId?: string): Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'saveDraft', diff --git a/src/addon/mod/assign/providers/helper.ts b/src/addon/mod/assign/providers/helper.ts index 5f8f5df21..a634e520c 100644 --- a/src/addon/mod/assign/providers/helper.ts +++ b/src/addon/mod/assign/providers/helper.ts @@ -42,9 +42,9 @@ export class AddonModAssignHelperProvider { /** * Check if a submission can be edited in offline. * - * @param {any} assign Assignment. - * @param {any} submission Submission. - * @return {boolean} Whether it can be edited offline. + * @param assign Assignment. + * @param submission Submission. + * @return Whether it can be edited offline. */ canEditSubmissionOffline(assign: any, submission: any): Promise { if (!submission) { @@ -77,9 +77,9 @@ export class AddonModAssignHelperProvider { /** * Clear plugins temporary data because a submission was cancelled. * - * @param {any} assign Assignment. - * @param {any} submission Submission to clear the data for. - * @param {any} inputData Data entered in the submission form. + * @param assign Assignment. + * @param submission Submission to clear the data for. + * @param inputData Data entered in the submission form. */ clearSubmissionPluginTmpData(assign: any, submission: any, inputData: any): void { submission.plugins.forEach((plugin) => { @@ -91,9 +91,9 @@ export class AddonModAssignHelperProvider { * Copy the data from last submitted attempt to the current submission. * Since we don't have any WS for that we'll have to re-submit everything manually. * - * @param {any} assign Assignment. - * @param {any} previousSubmission Submission to copy. - * @return {Promise} Promise resolved when done. + * @param assign Assignment. + * @param previousSubmission Submission to copy. + * @return Promise resolved when done. */ copyPreviousAttempt(assign: any, previousSubmission: any): Promise { const pluginData = {}, @@ -115,11 +115,11 @@ export class AddonModAssignHelperProvider { /** * Delete stored submission files for a plugin. See storeSubmissionFiles. * - * @param {number} assignId Assignment ID. - * @param {string} folderName Name of the plugin folder. Must be unique (both in submission and feedback plugins). - * @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 when done. + * @param assignId Assignment ID. + * @param folderName Name of the plugin folder. Must be unique (both in submission and feedback plugins). + * @param userId User ID. If not defined, site's current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ deleteStoredSubmissionFiles(assignId: number, folderName: string, userId?: number, siteId?: string): Promise { return this.assignOffline.getSubmissionPluginFolder(assignId, folderName, userId, siteId).then((folderPath) => { @@ -130,11 +130,11 @@ export class AddonModAssignHelperProvider { /** * Delete all drafts of the feedback plugin data. * - * @param {number} assignId Assignment Id. - * @param {number} userId User Id. - * @param {any} feedback Feedback data. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param assignId Assignment Id. + * @param userId User Id. + * @param feedback Feedback data. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ discardFeedbackPluginData(assignId: number, userId: number, feedback: any, siteId?: string): Promise { const promises = []; @@ -149,11 +149,11 @@ export class AddonModAssignHelperProvider { /** * List the participants for a single assignment, with some summary info about their submissions. * - * @param {any} assign Assignment object. - * @param {number} [groupId] Group Id. - * @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 list of participants and summary of submissions. + * @param assign Assignment object. + * @param groupId Group 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 with the list of participants and summary of submissions. */ getParticipants(assign: any, groupId?: number, ignoreCache?: boolean, siteId?: string): Promise { groupId = groupId || 0; @@ -189,10 +189,10 @@ export class AddonModAssignHelperProvider { /** * Get plugin config from assignment config. * - * @param {any} assign Assignment object including all config. - * @param {string} subtype Subtype name (assignsubmission or assignfeedback) - * @param {string} type Name of the subplugin. - * @return {any} Object containing all configurations of the subplugin selected. + * @param assign Assignment object including all config. + * @param subtype Subtype name (assignsubmission or assignfeedback) + * @param type Name of the subplugin. + * @return Object containing all configurations of the subplugin selected. */ getPluginConfig(assign: any, subtype: string, type: string): any { const configs = {}; @@ -209,9 +209,9 @@ export class AddonModAssignHelperProvider { /** * Get enabled subplugins. * - * @param {any} assign Assignment object including all config. - * @param {string} subtype Subtype name (assignsubmission or assignfeedback) - * @return {any} List of enabled plugins for the assign. + * @param assign Assignment object including all config. + * @param subtype Subtype name (assignsubmission or assignfeedback) + * @return List of enabled plugins for the assign. */ getPluginsEnabled(assign: any, subtype: string): any[] { const enabled = []; @@ -231,11 +231,11 @@ export class AddonModAssignHelperProvider { /** * Get a list of stored submission files. See storeSubmissionFiles. * - * @param {number} assignId Assignment ID. - * @param {string} folderName Name of the plugin folder. Must be unique (both in submission and feedback plugins). - * @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 with the files. + * @param assignId Assignment ID. + * @param folderName Name of the plugin folder. Must be unique (both in submission and feedback plugins). + * @param userId User ID. If not defined, site's current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the files. */ getStoredSubmissionFiles(assignId: number, folderName: string, userId?: number, siteId?: string): Promise { return this.assignOffline.getSubmissionPluginFolder(assignId, folderName, userId, siteId).then((folderPath) => { @@ -246,9 +246,9 @@ export class AddonModAssignHelperProvider { /** * Get the size that will be uploaded to perform an attempt copy. * - * @param {any} assign Assignment. - * @param {any} previousSubmission Submission to copy. - * @return {Promise} Promise resolved with the size. + * @param assign Assignment. + * @param previousSubmission Submission to copy. + * @return Promise resolved with the size. */ getSubmissionSizeForCopy(assign: any, previousSubmission: any): Promise { const promises = []; @@ -268,10 +268,10 @@ export class AddonModAssignHelperProvider { /** * Get the size that will be uploaded to save a submission. * - * @param {any} assign Assignment. - * @param {any} submission Submission to check data. - * @param {any} inputData Data entered in the submission form. - * @return {Promise} Promise resolved with the size. + * @param assign Assignment. + * @param submission Submission to check data. + * @param inputData Data entered in the submission form. + * @return Promise resolved with the size. */ getSubmissionSizeForEdit(assign: any, submission: any, inputData: any): Promise { const promises = []; @@ -291,12 +291,12 @@ export class AddonModAssignHelperProvider { /** * Get user data for submissions since they only have userid. * - * @param {any} assign Assignment object. - * @param {any[]} submissions Submissions to get the data for. - * @param {number} [groupId] Group Id. - * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site id (empty for current site). - * @return {Promise} Promise always resolved. Resolve param is the formatted submissions. + * @param assign Assignment object. + * @param submissions Submissions to get the data for. + * @param groupId Group Id. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site id (empty for current site). + * @return Promise always resolved. Resolve param is the formatted submissions. */ getSubmissionsUserData(assign: any, submissions: any[], groupId?: number, ignoreCache?: boolean, siteId?: string): Promise { @@ -389,10 +389,10 @@ export class AddonModAssignHelperProvider { /** * Check if the feedback data has changed for a certain submission and assign. * - * @param {any} assign Assignment. - * @param {number} userId User Id. - * @param {any} feedback Feedback data. - * @return {Promise} Promise resolved with true if data has changed, resolved with false otherwise. + * @param assign Assignment. + * @param userId User Id. + * @param feedback Feedback data. + * @return Promise resolved with true if data has changed, resolved with false otherwise. */ hasFeedbackDataChanged(assign: any, userId: number, feedback: any): Promise { const promises = []; @@ -418,10 +418,10 @@ export class AddonModAssignHelperProvider { /** * Check if the submission data has changed for a certain submission and assign. * - * @param {any} assign Assignment. - * @param {any} submission Submission to check data. - * @param {any} inputData Data entered in the submission form. - * @return {Promise} Promise resolved with true if data has changed, resolved with false otherwise. + * @param assign Assignment. + * @param submission Submission to check data. + * @param inputData Data entered in the submission form. + * @return Promise resolved with true if data has changed, resolved with false otherwise. */ hasSubmissionDataChanged(assign: any, submission: any, inputData: any): Promise { const promises = []; @@ -445,11 +445,11 @@ export class AddonModAssignHelperProvider { /** * Prepare and return the plugin data to send for a certain feedback and assign. * - * @param {number} assignId Assignment Id. - * @param {number} userId User Id. - * @param {any} feedback Feedback data. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with plugin data to send to server. + * @param assignId Assignment Id. + * @param userId User Id. + * @param feedback Feedback data. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with plugin data to send to server. */ prepareFeedbackPluginData(assignId: number, userId: number, feedback: any, siteId?: string): Promise { const pluginData = {}, @@ -467,11 +467,11 @@ export class AddonModAssignHelperProvider { /** * Prepare and return the plugin data to send for a certain submission and assign. * - * @param {any} assign Assignment. - * @param {any} submission Submission to check data. - * @param {any} inputData Data entered in the submission form. - * @param {boolean} [offline] True to prepare the data for an offline submission, false otherwise. - * @return {Promise} Promise resolved with plugin data to send to server. + * @param assign Assignment. + * @param submission Submission to check data. + * @param inputData Data entered in the submission form. + * @param offline True to prepare the data for an offline submission, false otherwise. + * @return Promise resolved with plugin data to send to server. */ prepareSubmissionPluginData(assign: any, submission: any, inputData: any, offline?: boolean): Promise { const pluginData = {}, @@ -491,12 +491,12 @@ export class AddonModAssignHelperProvider { * 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} assignId Assignment ID. - * @param {string} folderName Name of the plugin folder. Must be unique (both in submission and feedback plugins). - * @param {any[]} files List of files. - * @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, rejected otherwise. + * @param assignId Assignment ID. + * @param folderName Name of the plugin folder. Must be unique (both in submission and feedback plugins). + * @param files List of files. + * @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, rejected otherwise. */ storeSubmissionFiles(assignId: number, folderName: string, files: any[], userId?: number, siteId?: string): Promise { // Get the folder where to store the files. @@ -508,11 +508,11 @@ export class AddonModAssignHelperProvider { /** * Upload a file to a draft area. If the file is an online file it will be downloaded and then re-uploaded. * - * @param {number} assignId Assignment ID. - * @param {any} file Online file or local FileEntry. - * @param {number} [itemId] Draft ID to use. Undefined or 0 to create a new draft ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the itemId. + * @param assignId Assignment ID. + * @param file Online file or local FileEntry. + * @param itemId Draft ID to use. Undefined or 0 to create a new draft ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the itemId. */ uploadFile(assignId: number, file: any, itemId?: number, siteId?: string): Promise { return this.fileUploaderProvider.uploadOrReuploadFile(file, itemId, AddonModAssignProvider.COMPONENT, assignId, siteId); @@ -523,10 +523,10 @@ export class AddonModAssignHelperProvider { * Online files will be downloaded and then re-uploaded. * If there are no files to upload it will return a fake draft ID (1). * - * @param {number} assignId Assignment ID. - * @param {any[]} files List of files. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the itemId. + * @param assignId Assignment ID. + * @param files List of files. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the itemId. */ uploadFiles(assignId: number, files: any[], siteId?: string): Promise { return this.fileUploaderProvider.uploadOrReuploadFiles(files, AddonModAssignProvider.COMPONENT, assignId, siteId); @@ -535,13 +535,13 @@ export class AddonModAssignHelperProvider { /** * Upload or store some files, depending if the user is offline or not. * - * @param {number} assignId Assignment ID. - * @param {string} folderName Name of the plugin folder. Must be unique (both in submission and feedback plugins). - * @param {any[]} files List of files. - * @param {boolean} offline True if files sould be stored for offline, false to upload them. - * @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 when done. + * @param assignId Assignment ID. + * @param folderName Name of the plugin folder. Must be unique (both in submission and feedback plugins). + * @param files List of files. + * @param offline True if files sould be stored for offline, false to upload them. + * @param userId User ID. If not defined, site's current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ uploadOrStoreFiles(assignId: number, folderName: string, files: any[], offline?: boolean, userId?: number, siteId?: string) : Promise { diff --git a/src/addon/mod/assign/providers/list-link-handler.ts b/src/addon/mod/assign/providers/list-link-handler.ts index 9e5315d04..2e2b2de0d 100644 --- a/src/addon/mod/assign/providers/list-link-handler.ts +++ b/src/addon/mod/assign/providers/list-link-handler.ts @@ -33,7 +33,7 @@ export class AddonModAssignListLinkHandler extends CoreContentLinksModuleListHan /** * 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 this.assignProvider.isPluginEnabled(); diff --git a/src/addon/mod/assign/providers/module-handler.ts b/src/addon/mod/assign/providers/module-handler.ts index 1aa0cda0d..baa09705f 100644 --- a/src/addon/mod/assign/providers/module-handler.ts +++ b/src/addon/mod/assign/providers/module-handler.ts @@ -48,7 +48,7 @@ export class AddonModAssignModuleHandler 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 this.assignProvider.isPluginEnabled(); @@ -57,10 +57,10 @@ export class AddonModAssignModuleHandler 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 { @@ -82,9 +82,9 @@ export class AddonModAssignModuleHandler 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 AddonModAssignIndexComponent; diff --git a/src/addon/mod/assign/providers/prefetch-handler.ts b/src/addon/mod/assign/providers/prefetch-handler.ts index 9c76f2ec8..284d558ab 100644 --- a/src/addon/mod/assign/providers/prefetch-handler.ts +++ b/src/addon/mod/assign/providers/prefetch-handler.ts @@ -59,9 +59,9 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan * 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 { // Teachers cannot use the WS because it doesn't check student submissions. @@ -84,11 +84,11 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan /** * 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 { @@ -149,11 +149,11 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan /** * Get submission files. * - * @param {any} assign Assign. - * @param {number} submitId User ID of the submission to get. - * @param {boolean} blindMarking True if blind marking, false otherwise. - * @param {string} siteId Site ID. If not defined, current site. - * @return {Promise} Promise resolved with array of files. + * @param assign Assign. + * @param submitId User ID of the submission to get. + * @param blindMarking True if blind marking, false otherwise. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with array of files. */ protected getSubmissionFiles(assign: any, submitId: number, blindMarking: boolean, siteId?: string) : Promise { @@ -195,9 +195,9 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan /** * 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.assignProvider.invalidateContent(moduleId, courseId); @@ -206,9 +206,9 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan /** * 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 { return this.assignProvider.invalidateAssignmentData(courseId); @@ -217,7 +217,7 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan /** * 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.assignProvider.isPluginEnabled(); @@ -226,11 +226,11 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan /** * 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.prefetchAssign.bind(this)); @@ -239,11 +239,11 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan /** * Prefetch an assignment. * - * @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. - * @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. + * @return Promise resolved when done. */ protected prefetchAssign(module: any, courseId: number, single: boolean, siteId: string): Promise { const userId = this.sitesProvider.getCurrentSiteUserId(), @@ -281,12 +281,12 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan /** * Prefetch assign submissions. * - * @param {any} assign Assign. - * @param {number} courseId Course ID. - * @param {number} moduleId Module ID. - * @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 when prefetched, rejected otherwise. + * @param assign Assign. + * @param courseId Course ID. + * @param moduleId Module ID. + * @param userId User ID. If not defined, site's current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when prefetched, rejected otherwise. */ protected prefetchSubmissions(assign: any, courseId: number, moduleId: number, userId: number, siteId: string): Promise { // Get submissions. @@ -377,13 +377,13 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan /** * Prefetch a submission. * - * @param {any} assign Assign. - * @param {number} courseId Course ID. - * @param {number} moduleId Module ID. - * @param {any} submission Data returned by AddonModAssignProvider.getSubmissionStatus. - * @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 when prefetched, rejected otherwise. + * @param assign Assign. + * @param courseId Course ID. + * @param moduleId Module ID. + * @param submission Data returned by AddonModAssignProvider.getSubmissionStatus. + * @param userId User ID. If not defined, site's current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when prefetched, rejected otherwise. */ protected prefetchSubmission(assign: any, courseId: number, moduleId: number, submission: any, userId?: number, siteId?: string): Promise { @@ -461,10 +461,10 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan /** * 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.syncAssign(module.instance, siteId); diff --git a/src/addon/mod/assign/providers/push-click-handler.ts b/src/addon/mod/assign/providers/push-click-handler.ts index abe6fa608..dd698cbd1 100644 --- a/src/addon/mod/assign/providers/push-click-handler.ts +++ b/src/addon/mod/assign/providers/push-click-handler.ts @@ -34,8 +34,8 @@ export class AddonModAssignPushClickHandler implements CorePushNotificationsClic /** * 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_assign' && @@ -45,8 +45,8 @@ export class AddonModAssignPushClickHandler implements CorePushNotificationsClic /** * 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/assign/providers/submission-delegate.ts b/src/addon/mod/assign/providers/submission-delegate.ts index 26d5da2f4..70c8e9752 100644 --- a/src/addon/mod/assign/providers/submission-delegate.ts +++ b/src/addon/mod/assign/providers/submission-delegate.ts @@ -26,7 +26,6 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler { /** * Name of the type of submission the handler supports. E.g. 'file'. - * @type {string} */ type: string; @@ -35,20 +34,20 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler { * plugin uses Moodle filters. The reason is that the app only prefetches filtered data, and the user should edit * unfiltered data. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @return {boolean|Promise} Boolean or promise resolved with boolean: whether it can be edited in offline. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @return Boolean or promise resolved with boolean: whether it can be edited in offline. */ canEditOffline?(assign: any, submission: any, plugin: any): boolean | Promise; /** * Should clear temporary data for a cancelled submission. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {any} inputData Data entered by the user for the submission. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param inputData Data entered by the user for the submission. */ clearTmpData?(assign: any, submission: any, plugin: any, inputData: any): void; @@ -56,24 +55,24 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler { * This function will be called when the user wants to create a new submission based on the previous one. * It should add to pluginData the data to send to server based in the data in plugin (previous attempt). * - * @param {any} assign The assignment. - * @param {any} plugin The plugin object. - * @param {any} pluginData Object where to store the data to send. - * @param {number} [userId] User ID. If not defined, site's current user. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {void|Promise} If the function is async, it should return a Promise resolved when done. + * @param assign The assignment. + * @param plugin The plugin object. + * @param pluginData Object where to store the data to send. + * @param userId User ID. If not defined, site's current user. + * @param siteId Site ID. If not defined, current site. + * @return If the function is async, it should return a Promise resolved when done. */ copySubmissionData?(assign: any, plugin: any, pluginData: any, userId?: number, siteId?: string): void | Promise; /** * Delete any stored data for the plugin and submission. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {any} offlineData Offline data stored. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {void|Promise} If the function is async, it should return a Promise resolved when done. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param offlineData Offline data stored. + * @param siteId Site ID. If not defined, current site. + * @return If the function is async, it should return a Promise resolved when done. */ deleteOfflineData?(assign: any, submission: any, plugin: any, offlineData: any, siteId?: string): void | Promise; @@ -81,10 +80,10 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler { * Return the Component to use to display the plugin data, either in read or in edit mode. * 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} plugin The plugin object. - * @param {boolean} [edit] Whether the user is editing. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param plugin The plugin object. + * @param edit Whether the user is editing. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent?(injector: Injector, plugin: any, edit?: boolean): any | Promise; @@ -92,57 +91,57 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler { * Get files used by this plugin. * The files returned by this function will be prefetched when the user prefetches the assign. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {any[]|Promise} The files (or promise resolved with the files). + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param siteId Site ID. If not defined, current site. + * @return The files (or promise resolved with the files). */ getPluginFiles?(assign: any, submission: any, plugin: any, siteId?: string): any[] | Promise; /** * Get a readable name to use for the plugin. * - * @param {any} plugin The plugin object. - * @return {string} The plugin name. + * @param plugin The plugin object. + * @return The plugin name. */ getPluginName?(plugin: any): string; /** * Get the size of data (in bytes) this plugin will send to copy a previous submission. * - * @param {any} assign The assignment. - * @param {any} plugin The plugin object. - * @return {number|Promise} The size (or promise resolved with size). + * @param assign The assignment. + * @param plugin The plugin object. + * @return The size (or promise resolved with size). */ getSizeForCopy?(assign: any, plugin: any): number | Promise; /** * Get the size of data (in bytes) this plugin will send to add or edit a submission. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {any} inputData Data entered by the user for the submission. - * @return {number|Promise} The size (or promise resolved with size). + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param inputData Data entered by the user for the submission. + * @return The size (or promise resolved with size). */ getSizeForEdit?(assign: any, submission: any, plugin: any, inputData: any): number | Promise; /** * Check if the submission data has changed for this plugin. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {any} inputData Data entered by the user for the submission. - * @return {boolean|Promise} Boolean (or promise resolved with boolean): whether the data has changed. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param inputData Data entered by the user for the submission. + * @return Boolean (or promise resolved with boolean): whether the data has changed. */ hasDataChanged?(assign: any, submission: any, plugin: any, inputData: any): boolean | Promise; /** * Whether or not the handler is enabled for edit on a site level. * - * @return {boolean|Promise} Whether or not the handler is enabled for edit on a site level. + * @return Whether or not the handler is enabled for edit on a site level. */ isEnabledForEdit?(): boolean | Promise; @@ -150,26 +149,26 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler { * Prefetch any required data for the plugin. * This should NOT prefetch files. Files to be prefetched should be returned by the getPluginFiles function. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ prefetch?(assign: any, submission: any, plugin: any, siteId?: string): Promise; /** * Prepare and add to pluginData the data to send to the server based on the input data. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {any} inputData Data entered by the user for the submission. - * @param {any} pluginData Object where to store the data to send. - * @param {boolean} [offline] Whether the user is editing in offline. - * @param {number} [userId] User ID. If not defined, site's current user. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {void|Promise} If the function is async, it should return a Promise resolved when done. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param inputData Data entered by the user for the submission. + * @param pluginData Object where to store the data to send. + * @param offline Whether the user is editing in offline. + * @param userId User ID. If not defined, site's current user. + * @param siteId Site ID. If not defined, current site. + * @return If the function is async, it should return a Promise resolved when done. */ prepareSubmissionData?(assign: any, submission: any, plugin: any, inputData: any, pluginData: any, offline?: boolean, userId?: number, siteId?: string): void | Promise; @@ -178,13 +177,13 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler { * Prepare and add to pluginData the data to send to the server based on the offline data stored. * This will be used when performing a synchronization. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {any} offlineData Offline data stored. - * @param {any} pluginData Object where to store the data to send. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {void|Promise} If the function is async, it should return a Promise resolved when done. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param offlineData Offline data stored. + * @param pluginData Object where to store the data to send. + * @param siteId Site ID. If not defined, current site. + * @return If the function is async, it should return a Promise resolved when done. */ prepareSyncData?(assign: any, submission: any, plugin: any, offlineData: any, pluginData: any, siteId?: string) : void | Promise; @@ -206,10 +205,10 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate { /** * Whether the plugin can be edited in offline for existing submissions. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @return {boolean|Promise} Promise resolved with boolean: whether it can be edited in offline. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @return Promise resolved with boolean: whether it can be edited in offline. */ canPluginEditOffline(assign: any, submission: any, plugin: any): Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'canEditOffline', [assign, submission, plugin])); @@ -218,10 +217,10 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate { /** * Clear some temporary data for a certain plugin because a submission was cancelled. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {any} inputData Data entered by the user for the submission. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param inputData Data entered by the user for the submission. */ clearTmpData(assign: any, submission: any, plugin: any, inputData: any): void { return this.executeFunctionOnEnabled(plugin.type, 'clearTmpData', [assign, submission, plugin, inputData]); @@ -230,12 +229,12 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate { /** * Copy the data from last submitted attempt to the current submission for a certain plugin. * - * @param {any} assign The assignment. - * @param {any} plugin The plugin object. - * @param {any} pluginData Object where to store the data to send. - * @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 when the data has been copied. + * @param assign The assignment. + * @param plugin The plugin object. + * @param pluginData Object where to store the data to send. + * @param userId User ID. If not defined, site's current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data has been copied. */ copyPluginSubmissionData(assign: any, plugin: any, pluginData: any, userId?: number, siteId?: string): void | Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'copySubmissionData', @@ -245,12 +244,12 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate { /** * Delete offline data stored for a certain submission and plugin. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {any} offlineData Offline data stored. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param offlineData Offline data stored. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ deletePluginOfflineData(assign: any, submission: any, plugin: any, offlineData: any, siteId?: string): Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'deleteOfflineData', @@ -260,10 +259,10 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate { /** * Get the component to use for a certain submission plugin. * - * @param {Injector} injector Injector. - * @param {any} plugin The plugin object. - * @param {boolean} [edit] Whether the user is editing. - * @return {Promise} Promise resolved with the component to use, undefined if not found. + * @param injector Injector. + * @param plugin The plugin object. + * @param edit Whether the user is editing. + * @return Promise resolved with the component to use, undefined if not found. */ getComponentForPlugin(injector: Injector, plugin: any, edit?: boolean): Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'getComponent', [injector, plugin, edit])); @@ -273,11 +272,11 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate { * Get files used by this plugin. * The files returned by this function will be prefetched when the user prefetches the assign. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the files. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the files. */ getPluginFiles(assign: any, submission: any, plugin: any, siteId?: string): Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'getPluginFiles', [assign, submission, plugin, siteId])); @@ -286,8 +285,8 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate { /** * Get a readable name to use for a certain submission plugin. * - * @param {any} plugin Plugin to get the name for. - * @return {string} Human readable name. + * @param plugin Plugin to get the name for. + * @return Human readable name. */ getPluginName(plugin: any): string { return this.executeFunctionOnEnabled(plugin.type, 'getPluginName', [plugin]); @@ -296,9 +295,9 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate { /** * Get the size of data (in bytes) this plugin will send to copy a previous submission. * - * @param {any} assign The assignment. - * @param {any} plugin The plugin object. - * @return {Promise} Promise resolved with size. + * @param assign The assignment. + * @param plugin The plugin object. + * @return Promise resolved with size. */ getPluginSizeForCopy(assign: any, plugin: any): Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'getSizeForCopy', [assign, plugin])); @@ -307,11 +306,11 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate { /** * Get the size of data (in bytes) this plugin will send to add or edit a submission. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {any} inputData Data entered by the user for the submission. - * @return {Promise} Promise resolved with size. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param inputData Data entered by the user for the submission. + * @return Promise resolved with size. */ getPluginSizeForEdit(assign: any, submission: any, plugin: any, inputData: any): Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'getSizeForEdit', @@ -321,11 +320,11 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate { /** * Check if the submission data has changed for a certain plugin. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {any} inputData Data entered by the user for the submission. - * @return {Promise} Promise resolved with true if data has changed, resolved with false otherwise. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param inputData Data entered by the user for the submission. + * @return Promise resolved with true if data has changed, resolved with false otherwise. */ hasPluginDataChanged(assign: any, submission: any, plugin: any, inputData: any): Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'hasDataChanged', @@ -335,8 +334,8 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate { /** * Check if a submission plugin is supported. * - * @param {string} pluginType Type of the plugin. - * @return {boolean} Whether it's supported. + * @param pluginType Type of the plugin. + * @return Whether it's supported. */ isPluginSupported(pluginType: string): boolean { return this.hasHandler(pluginType, true); @@ -345,8 +344,8 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate { /** * Check if a submission plugin is supported for edit. * - * @param {string} pluginType Type of the plugin. - * @return {Promise} Whether it's supported for edit. + * @param pluginType Type of the plugin. + * @return Whether it's supported for edit. */ isPluginSupportedForEdit(pluginType: string): Promise { return Promise.resolve(this.executeFunctionOnEnabled(pluginType, 'isEnabledForEdit')); @@ -355,11 +354,11 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate { /** * Prefetch any required data for a submission plugin. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ prefetch(assign: any, submission: any, plugin: any, siteId?: string): Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'prefetch', [assign, submission, plugin, siteId])); @@ -368,15 +367,15 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate { /** * Prepare and add to pluginData the data to submit for a certain submission plugin. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {any} inputData Data entered by the user for the submission. - * @param {any} pluginData Object where to store the data to send. - * @param {boolean} [offline] Whether the user is editing in offline. - * @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 when data has been gathered. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param inputData Data entered by the user for the submission. + * @param pluginData Object where to store the data to send. + * @param offline Whether the user is editing in offline. + * @param userId User ID. If not defined, site's current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when data has been gathered. */ preparePluginSubmissionData(assign: any, submission: any, plugin: any, inputData: any, pluginData: any, offline?: boolean, userId?: number, siteId?: string): Promise { @@ -388,13 +387,13 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate { /** * Prepare and add to pluginData the data to send to server to synchronize an offline submission. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {any} offlineData Offline data stored. - * @param {any} pluginData Object where to store the data to send. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when data has been gathered. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param offlineData Offline data stored. + * @param pluginData Object where to store the data to send. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when data has been gathered. */ preparePluginSyncData(assign: any, submission: any, plugin: any, offlineData: any, pluginData: any, siteId?: string) : Promise { diff --git a/src/addon/mod/assign/providers/sync-cron-handler.ts b/src/addon/mod/assign/providers/sync-cron-handler.ts index fbfb6600c..e610567ac 100644 --- a/src/addon/mod/assign/providers/sync-cron-handler.ts +++ b/src/addon/mod/assign/providers/sync-cron-handler.ts @@ -29,9 +29,9 @@ export class AddonModAssignSyncCronHandler 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.assignSync.syncAllAssignments(siteId, force); @@ -40,7 +40,7 @@ export class AddonModAssignSyncCronHandler 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.assignSync.syncInterval; diff --git a/src/addon/mod/assign/submission/comments/component/comments.ts b/src/addon/mod/assign/submission/comments/component/comments.ts index bd6bb57c2..a9d6c4784 100644 --- a/src/addon/mod/assign/submission/comments/component/comments.ts +++ b/src/addon/mod/assign/submission/comments/component/comments.ts @@ -38,7 +38,7 @@ export class AddonModAssignSubmissionCommentsComponent extends AddonModAssignSub /** * Invalidate the data. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ invalidate(): Promise { return this.commentsProvider.invalidateCommentsData('module', this.assign.cmid, 'assignsubmission_comments', diff --git a/src/addon/mod/assign/submission/comments/providers/handler.ts b/src/addon/mod/assign/submission/comments/providers/handler.ts index fa4f7af2b..54f450df5 100644 --- a/src/addon/mod/assign/submission/comments/providers/handler.ts +++ b/src/addon/mod/assign/submission/comments/providers/handler.ts @@ -33,10 +33,10 @@ export class AddonModAssignSubmissionCommentsHandler implements AddonModAssignSu * plugin uses Moodle filters. The reason is that the app only prefetches filtered data, and the user should edit * unfiltered data. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @return {boolean|Promise} Boolean or promise resolved with boolean: whether it can be edited in offline. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @return Boolean or promise resolved with boolean: whether it can be edited in offline. */ canEditOffline(assign: any, submission: any, plugin: any): boolean | Promise { // This plugin is read only, but return true to prevent blocking the edition. @@ -47,10 +47,10 @@ export class AddonModAssignSubmissionCommentsHandler implements AddonModAssignSu * Return the Component to use to display the plugin data, either in read or in edit mode. * 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} plugin The plugin object. - * @param {boolean} [edit] Whether the user is editing. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param plugin The plugin object. + * @param edit Whether the user is editing. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector, plugin: any, edit?: boolean): any | Promise { return edit ? undefined : AddonModAssignSubmissionCommentsComponent; @@ -59,7 +59,7 @@ export class AddonModAssignSubmissionCommentsHandler implements AddonModAssignSu /** * 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; @@ -68,7 +68,7 @@ export class AddonModAssignSubmissionCommentsHandler implements AddonModAssignSu /** * Whether or not the handler is enabled for edit on a site level. * - * @return {boolean|Promise} Whether or not the handler is enabled for edit on a site level. + * @return Whether or not the handler is enabled for edit on a site level. */ isEnabledForEdit(): boolean | Promise { return true; @@ -78,11 +78,11 @@ export class AddonModAssignSubmissionCommentsHandler implements AddonModAssignSu * Prefetch any required data for the plugin. * This should NOT prefetch files. Files to be prefetched should be returned by the getPluginFiles function. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ prefetch(assign: any, submission: any, plugin: any, siteId?: string): Promise { return this.commentsProvider.getComments('module', assign.cmid, 'assignsubmission_comments', submission.id, diff --git a/src/addon/mod/assign/submission/file/providers/handler.ts b/src/addon/mod/assign/submission/file/providers/handler.ts index 1e013db7a..57a4a70bf 100644 --- a/src/addon/mod/assign/submission/file/providers/handler.ts +++ b/src/addon/mod/assign/submission/file/providers/handler.ts @@ -48,10 +48,10 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis * plugin uses Moodle filters. The reason is that the app only prefetches filtered data, and the user should edit * unfiltered data. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @return {boolean|Promise} Boolean or promise resolved with boolean: whether it can be edited in offline. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @return Boolean or promise resolved with boolean: whether it can be edited in offline. */ canEditOffline(assign: any, submission: any, plugin: any): boolean | Promise { // This plugin doesn't use Moodle filters, it can be edited in offline. @@ -61,10 +61,10 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis /** * Should clear temporary data for a cancelled submission. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {any} inputData Data entered by the user for the submission. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param inputData Data entered by the user for the submission. */ clearTmpData(assign: any, submission: any, plugin: any, inputData: any): void { const files = this.fileSessionProvider.getFiles(AddonModAssignProvider.COMPONENT, assign.id); @@ -80,12 +80,12 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis * This function will be called when the user wants to create a new submission based on the previous one. * It should add to pluginData the data to send to server based in the data in plugin (previous attempt). * - * @param {any} assign The assignment. - * @param {any} plugin The plugin object. - * @param {any} pluginData Object where to store the data to send. - * @param {number} [userId] User ID. If not defined, site's current user. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {void|Promise} If the function is async, it should return a Promise resolved when done. + * @param assign The assignment. + * @param plugin The plugin object. + * @param pluginData Object where to store the data to send. + * @param userId User ID. If not defined, site's current user. + * @param siteId Site ID. If not defined, current site. + * @return If the function is async, it should return a Promise resolved when done. */ copySubmissionData(assign: any, plugin: any, pluginData: any, userId?: number, siteId?: string): void | Promise { // We need to re-upload all the existing files. @@ -100,10 +100,10 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis * Return the Component to use to display the plugin data, either in read or in edit mode. * 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} plugin The plugin object. - * @param {boolean} [edit] Whether the user is editing. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param plugin The plugin object. + * @param edit Whether the user is editing. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector, plugin: any, edit?: boolean): any | Promise { return AddonModAssignSubmissionFileComponent; @@ -112,12 +112,12 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis /** * Delete any stored data for the plugin and submission. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {any} offlineData Offline data stored. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {void|Promise} If the function is async, it should return a Promise resolved when done. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param offlineData Offline data stored. + * @param siteId Site ID. If not defined, current site. + * @return If the function is async, it should return a Promise resolved when done. */ deleteOfflineData(assign: any, submission: any, plugin: any, offlineData: any, siteId?: string): void | Promise { return this.assignHelper.deleteStoredSubmissionFiles(assign.id, AddonModAssignSubmissionFileHandler.FOLDER_NAME, @@ -130,11 +130,11 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis * Get files used by this plugin. * The files returned by this function will be prefetched when the user prefetches the assign. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {any[]|Promise} The files (or promise resolved with the files). + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param siteId Site ID. If not defined, current site. + * @return The files (or promise resolved with the files). */ getPluginFiles(assign: any, submission: any, plugin: any, siteId?: string): any[] | Promise { return this.assignProvider.getSubmissionPluginAttachments(plugin); @@ -143,9 +143,9 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis /** * Get the size of data (in bytes) this plugin will send to copy a previous submission. * - * @param {any} assign The assignment. - * @param {any} plugin The plugin object. - * @return {number|Promise} The size (or promise resolved with size). + * @param assign The assignment. + * @param plugin The plugin object. + * @return The size (or promise resolved with size). */ getSizeForCopy(assign: any, plugin: any): number | Promise { const files = this.assignProvider.getSubmissionPluginAttachments(plugin), @@ -171,11 +171,11 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis /** * Get the size of data (in bytes) this plugin will send to add or edit a submission. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {any} inputData Data entered by the user for the submission. - * @return {number|Promise} The size (or promise resolved with size). + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param inputData Data entered by the user for the submission. + * @return The size (or promise resolved with size). */ getSizeForEdit(assign: any, submission: any, plugin: any, inputData: any): number | Promise { const siteId = this.sitesProvider.getCurrentSiteId(); @@ -226,11 +226,11 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis /** * Check if the submission data has changed for this plugin. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {any} inputData Data entered by the user for the submission. - * @return {boolean|Promise} Boolean (or promise resolved with boolean): whether the data has changed. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param inputData Data entered by the user for the submission. + * @return Boolean (or promise resolved with boolean): whether the data has changed. */ hasDataChanged(assign: any, submission: any, plugin: any, inputData: any): boolean | Promise { // Check if there's any offline data. @@ -271,7 +271,7 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis /** * 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; @@ -280,7 +280,7 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis /** * Whether or not the handler is enabled for edit on a site level. * - * @return {boolean|Promise} Whether or not the handler is enabled for edit on a site level. + * @return Whether or not the handler is enabled for edit on a site level. */ isEnabledForEdit(): boolean | Promise { return true; @@ -289,15 +289,15 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis /** * Prepare and add to pluginData the data to send to the server based on the input data. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {any} inputData Data entered by the user for the submission. - * @param {any} pluginData Object where to store the data to send. - * @param {boolean} [offline] Whether the user is editing in offline. - * @param {number} [userId] User ID. If not defined, site's current user. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {void|Promise} If the function is async, it should return a Promise resolved when done. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param inputData Data entered by the user for the submission. + * @param pluginData Object where to store the data to send. + * @param offline Whether the user is editing in offline. + * @param userId User ID. If not defined, site's current user. + * @param siteId Site ID. If not defined, current site. + * @return If the function is async, it should return a Promise resolved when done. */ prepareSubmissionData(assign: any, submission: any, plugin: any, inputData: any, pluginData: any, offline?: boolean, userId?: number, siteId?: string): void | Promise { @@ -322,13 +322,13 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis * Prepare and add to pluginData the data to send to the server based on the offline data stored. * This will be used when performing a synchronization. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {any} offlineData Offline data stored. - * @param {any} pluginData Object where to store the data to send. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {void|Promise} If the function is async, it should return a Promise resolved when done. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param offlineData Offline data stored. + * @param pluginData Object where to store the data to send. + * @param siteId Site ID. If not defined, current site. + * @return If the function is async, it should return a Promise resolved when done. */ prepareSyncData(assign: any, submission: any, plugin: any, offlineData: any, pluginData: any, siteId?: string) : void | Promise { diff --git a/src/addon/mod/assign/submission/onlinetext/component/onlinetext.ts b/src/addon/mod/assign/submission/onlinetext/component/onlinetext.ts index 11ab6f2d0..25b0cd5e6 100644 --- a/src/addon/mod/assign/submission/onlinetext/component/onlinetext.ts +++ b/src/addon/mod/assign/submission/onlinetext/component/onlinetext.ts @@ -96,7 +96,7 @@ export class AddonModAssignSubmissionOnlineTextComponent extends AddonModAssignS /** * Text changed. * - * @param {string} text The new text. + * @param text The new text. */ onChange(text: string): void { // Count words if needed. diff --git a/src/addon/mod/assign/submission/onlinetext/providers/handler.ts b/src/addon/mod/assign/submission/onlinetext/providers/handler.ts index 80745907b..dd4b847a4 100644 --- a/src/addon/mod/assign/submission/onlinetext/providers/handler.ts +++ b/src/addon/mod/assign/submission/onlinetext/providers/handler.ts @@ -41,10 +41,10 @@ export class AddonModAssignSubmissionOnlineTextHandler implements AddonModAssign * plugin uses Moodle filters. The reason is that the app only prefetches filtered data, and the user should edit * unfiltered data. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @return {boolean|Promise} Boolean or promise resolved with boolean: whether it can be edited in offline. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @return Boolean or promise resolved with boolean: whether it can be edited in offline. */ canEditOffline(assign: any, submission: any, plugin: any): boolean | Promise { // This plugin uses Moodle filters, it cannot be edited in offline. @@ -55,12 +55,12 @@ export class AddonModAssignSubmissionOnlineTextHandler implements AddonModAssign * This function will be called when the user wants to create a new submission based on the previous one. * It should add to pluginData the data to send to server based in the data in plugin (previous attempt). * - * @param {any} assign The assignment. - * @param {any} plugin The plugin object. - * @param {any} pluginData Object where to store the data to send. - * @param {number} [userId] User ID. If not defined, site's current user. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {void|Promise} If the function is async, it should return a Promise resolved when done. + * @param assign The assignment. + * @param plugin The plugin object. + * @param pluginData Object where to store the data to send. + * @param userId User ID. If not defined, site's current user. + * @param siteId Site ID. If not defined, current site. + * @return If the function is async, it should return a Promise resolved when done. */ copySubmissionData(assign: any, plugin: any, pluginData: any, userId?: number, siteId?: string): void | Promise { const text = this.assignProvider.getSubmissionPluginText(plugin, true), @@ -88,10 +88,10 @@ export class AddonModAssignSubmissionOnlineTextHandler implements AddonModAssign * Return the Component to use to display the plugin data, either in read or in edit mode. * 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} plugin The plugin object. - * @param {boolean} [edit] Whether the user is editing. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param plugin The plugin object. + * @param edit Whether the user is editing. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector, plugin: any, edit?: boolean): any | Promise { return AddonModAssignSubmissionOnlineTextComponent; @@ -101,11 +101,11 @@ export class AddonModAssignSubmissionOnlineTextHandler implements AddonModAssign * Get files used by this plugin. * The files returned by this function will be prefetched when the user prefetches the assign. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {any[]|Promise} The files (or promise resolved with the files). + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param siteId Site ID. If not defined, current site. + * @return The files (or promise resolved with the files). */ getPluginFiles(assign: any, submission: any, plugin: any, siteId?: string): any[] | Promise { return this.assignProvider.getSubmissionPluginAttachments(plugin); @@ -114,9 +114,9 @@ export class AddonModAssignSubmissionOnlineTextHandler implements AddonModAssign /** * Get the size of data (in bytes) this plugin will send to copy a previous submission. * - * @param {any} assign The assignment. - * @param {any} plugin The plugin object. - * @return {number|Promise} The size (or promise resolved with size). + * @param assign The assignment. + * @param plugin The plugin object. + * @return The size (or promise resolved with size). */ getSizeForCopy(assign: any, plugin: any): number | Promise { const text = this.assignProvider.getSubmissionPluginText(plugin, true), @@ -147,11 +147,11 @@ export class AddonModAssignSubmissionOnlineTextHandler implements AddonModAssign /** * Get the size of data (in bytes) this plugin will send to add or edit a submission. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {any} inputData Data entered by the user for the submission. - * @return {number|Promise} The size (or promise resolved with size). + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param inputData Data entered by the user for the submission. + * @return The size (or promise resolved with size). */ getSizeForEdit(assign: any, submission: any, plugin: any, inputData: any): number | Promise { const text = this.assignProvider.getSubmissionPluginText(plugin, true); @@ -162,9 +162,9 @@ export class AddonModAssignSubmissionOnlineTextHandler implements AddonModAssign /** * Get the text to submit. * - * @param {any} plugin The plugin object. - * @param {any} inputData Data entered by the user for the submission. - * @return {string} Text to submit. + * @param plugin The plugin object. + * @param inputData Data entered by the user for the submission. + * @return Text to submit. */ protected getTextToSubmit(plugin: any, inputData: any): string { const text = inputData.onlinetext_editor_text, @@ -176,11 +176,11 @@ export class AddonModAssignSubmissionOnlineTextHandler implements AddonModAssign /** * Check if the submission data has changed for this plugin. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {any} inputData Data entered by the user for the submission. - * @return {boolean|Promise} Boolean (or promise resolved with boolean): whether the data has changed. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param inputData Data entered by the user for the submission. + * @return Boolean (or promise resolved with boolean): whether the data has changed. */ hasDataChanged(assign: any, submission: any, plugin: any, inputData: any): boolean | Promise { // Get the original text from plugin or offline. @@ -202,7 +202,7 @@ export class AddonModAssignSubmissionOnlineTextHandler implements AddonModAssign /** * 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; @@ -211,7 +211,7 @@ export class AddonModAssignSubmissionOnlineTextHandler implements AddonModAssign /** * Whether or not the handler is enabled for edit on a site level. * - * @return {boolean|Promise} Whether or not the handler is enabled for edit on a site level. + * @return Whether or not the handler is enabled for edit on a site level. */ isEnabledForEdit(): boolean | Promise { // There's a bug in Moodle 3.1.0 that doesn't allow submitting HTML, so we'll disable this plugin in that case. @@ -224,15 +224,15 @@ export class AddonModAssignSubmissionOnlineTextHandler implements AddonModAssign /** * Prepare and add to pluginData the data to send to the server based on the input data. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {any} inputData Data entered by the user for the submission. - * @param {any} pluginData Object where to store the data to send. - * @param {boolean} [offline] Whether the user is editing in offline. - * @param {number} [userId] User ID. If not defined, site's current user. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {void|Promise} If the function is async, it should return a Promise resolved when done. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param inputData Data entered by the user for the submission. + * @param pluginData Object where to store the data to send. + * @param offline Whether the user is editing in offline. + * @param userId User ID. If not defined, site's current user. + * @param siteId Site ID. If not defined, current site. + * @return If the function is async, it should return a Promise resolved when done. */ prepareSubmissionData(assign: any, submission: any, plugin: any, inputData: any, pluginData: any, offline?: boolean, userId?: number, siteId?: string): void | Promise { @@ -266,13 +266,13 @@ export class AddonModAssignSubmissionOnlineTextHandler implements AddonModAssign * Prepare and add to pluginData the data to send to the server based on the offline data stored. * This will be used when performing a synchronization. * - * @param {any} assign The assignment. - * @param {any} submission The submission. - * @param {any} plugin The plugin object. - * @param {any} offlineData Offline data stored. - * @param {any} pluginData Object where to store the data to send. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {void|Promise} If the function is async, it should return a Promise resolved when done. + * @param assign The assignment. + * @param submission The submission. + * @param plugin The plugin object. + * @param offlineData Offline data stored. + * @param pluginData Object where to store the data to send. + * @param siteId Site ID. If not defined, current site. + * @return If the function is async, it should return a Promise resolved when done. */ prepareSyncData(assign: any, submission: any, plugin: any, offlineData: any, pluginData: any, siteId?: string) : void | Promise { diff --git a/src/addon/mod/book/components/index/index.ts b/src/addon/mod/book/components/index/index.ts index 569060aa3..794d4970c 100644 --- a/src/addon/mod/book/components/index/index.ts +++ b/src/addon/mod/book/components/index/index.ts @@ -61,7 +61,7 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp /** * Show the TOC. * - * @param {MouseEvent} event Event. + * @param event Event. */ showToc(event: MouseEvent): void { // Create the toc modal. @@ -88,8 +88,8 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp /** * Change the current chapter. * - * @param {string} chapterId Chapter to load. - * @return {Promise} Promise resolved when done. + * @param chapterId Chapter to load. + * @return Promise resolved when done. */ changeChapter(chapterId: string): void { if (chapterId && chapterId != this.currentChapter) { @@ -102,7 +102,7 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp /** * Perform the invalidate content function. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected invalidateContent(): Promise { return this.bookProvider.invalidateContent(this.module.id, this.courseId); @@ -111,8 +111,8 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp /** * Download book contents and load the current chapter. * - * @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 { const promises = []; @@ -175,8 +175,8 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp /** * Load a book chapter. * - * @param {string} chapterId Chapter to load. - * @return {Promise} Promise resolved when done. + * @param chapterId Chapter to load. + * @return Promise resolved when done. */ protected loadChapter(chapterId: string): Promise { this.currentChapter = chapterId; diff --git a/src/addon/mod/book/pages/index/index.ts b/src/addon/mod/book/pages/index/index.ts index 7bb9e1f0f..fc95e6b4a 100644 --- a/src/addon/mod/book/pages/index/index.ts +++ b/src/addon/mod/book/pages/index/index.ts @@ -42,7 +42,7 @@ export class AddonModBookIndexPage { /** * Update some data based on the book instance. * - * @param {any} book Book instance. + * @param book Book instance. */ updateData(book: any): void { this.title = book.name || this.title; diff --git a/src/addon/mod/book/pages/toc/toc.ts b/src/addon/mod/book/pages/toc/toc.ts index 8048eacb8..0478faf2c 100644 --- a/src/addon/mod/book/pages/toc/toc.ts +++ b/src/addon/mod/book/pages/toc/toc.ts @@ -36,7 +36,7 @@ export class AddonModBookTocPage { /** * Function called when a course is clicked. * - * @param {string} id ID of the clicked chapter. + * @param id ID of the clicked chapter. */ loadChapter(id: string): void { this.viewCtrl.dismiss(id); diff --git a/src/addon/mod/book/providers/book.ts b/src/addon/mod/book/providers/book.ts index 74fd32706..1e97a6749 100644 --- a/src/addon/mod/book/providers/book.ts +++ b/src/addon/mod/book/providers/book.ts @@ -32,19 +32,16 @@ import { CoreTagItem } from '@core/tag/providers/tag'; export interface AddonModBookTocChapter { /** * ID to identify the chapter. - * @type {string} */ id: string; /** * Chapter's title. - * @type {string} */ title: string; /** * The chapter's level. - * @type {number} */ level: number; } @@ -81,10 +78,10 @@ export class AddonModBookProvider { /** * Get a book 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 book 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 book is retrieved. */ getBook(courseId: number, cmId: number, siteId?: string): Promise { return this.getBookByField(courseId, 'coursemodule', cmId, siteId); @@ -93,11 +90,11 @@ export class AddonModBookProvider { /** * Get a book 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 book 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 book is retrieved. */ protected getBookByField(courseId: number, key: string, value: any, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -128,8 +125,8 @@ export class AddonModBookProvider { /** * Get cache key for get book data WS calls. * - * @param {number} courseId Course ID. - * @return {string} Cache key. + * @param courseId Course ID. + * @return Cache key. */ protected getBookDataCacheKey(courseId: number): string { return this.ROOT_CACHE_KEY + 'book:' + courseId; @@ -138,10 +135,10 @@ export class AddonModBookProvider { /** * Gets a chapter contents. * - * @param {AddonModBookContentsMap} contentsMap Contents map returned by getContentsMap. - * @param {string} chapterId Chapter to retrieve. - * @param {number} moduleId The module ID. - * @return {Promise} Promise resolved with the contents. + * @param contentsMap Contents map returned by getContentsMap. + * @param chapterId Chapter to retrieve. + * @param moduleId The module ID. + * @return Promise resolved with the contents. */ getChapterContent(contentsMap: AddonModBookContentsMap, chapterId: string, moduleId: number): Promise { const indexUrl = contentsMap[chapterId] ? contentsMap[chapterId].indexUrl : undefined, @@ -182,8 +179,8 @@ export class AddonModBookProvider { * Convert an array of book contents into an object where contents are organized in chapters. * Each chapter has an indexUrl and the list of contents in that chapter. * - * @param {any[]} contents The module contents. - * @return {AddonModBookContentsMap} Contents map. + * @param contents The module contents. + * @return Contents map. */ getContentsMap(contents: any[]): AddonModBookContentsMap { const map: AddonModBookContentsMap = {}; @@ -236,8 +233,8 @@ export class AddonModBookProvider { /** * Get the first chapter of a book. * - * @param {AddonModBookTocChapter[]} chapters The chapters list. - * @return {string} The chapter id. + * @param chapters The chapters list. + * @return The chapter id. */ getFirstChapter(chapters: AddonModBookTocChapter[]): string { if (!chapters || !chapters.length) { @@ -250,9 +247,9 @@ export class AddonModBookProvider { /** * Get the next chapter to the given one. * - * @param {AddonModBookTocChapter[]} chapters The chapters list. - * @param {string} chapterId The current chapter. - * @return {string} The next chapter id. + * @param chapters The chapters list. + * @param chapterId The current chapter. + * @return The next chapter id. */ getNextChapter(chapters: AddonModBookTocChapter[], chapterId: string): string { let next = '0'; @@ -272,9 +269,9 @@ export class AddonModBookProvider { /** * Get the previous chapter to the given one. * - * @param {AddonModBookTocChapter[]} chapters The chapters list. - * @param {string} chapterId The current chapter. - * @return {string} The next chapter id. + * @param chapters The chapters list. + * @param chapterId The current chapter. + * @return The next chapter id. */ getPreviousChapter(chapters: AddonModBookTocChapter[], chapterId: string): string { let previous = '0'; @@ -292,8 +289,8 @@ export class AddonModBookProvider { /** * Get the book toc as an array. * - * @param {any[]} contents The module contents. - * @return {any[]} The toc. + * @param contents The module contents. + * @return The toc. */ getToc(contents: any[]): any[] { if (!contents || !contents.length) { @@ -306,8 +303,8 @@ export class AddonModBookProvider { /** * Get the book toc as an array of chapters (not nested). * - * @param {any[]} contents The module contents. - * @return {AddonModBookTocChapter[]} The toc as a list. + * @param contents The module contents. + * @return The toc as a list. */ getTocList(contents: any[]): AddonModBookTocChapter[] { const chapters = [], @@ -333,9 +330,9 @@ export class AddonModBookProvider { /** * Invalidates book 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. */ invalidateBookData(courseId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -346,10 +343,10 @@ export class AddonModBookProvider { /** * 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(); @@ -366,8 +363,8 @@ export class AddonModBookProvider { /** * Check if a file is downloadable. The file param must have a 'type' attribute like in core_course_get_contents response. * - * @param {any} file File to check. - * @return {boolean} Whether it's downloadable. + * @param file File to check. + * @return Whether it's downloadable. */ isFileDownloadable(file: any): boolean { return file.type === 'file'; @@ -376,8 +373,8 @@ export class AddonModBookProvider { /** * 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) => { @@ -388,11 +385,11 @@ export class AddonModBookProvider { /** * Report a book as being viewed. * - * @param {number} id Module ID. - * @param {string} chapterId Chapter ID. - * @param {string} [name] Name of the book. - * @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 chapterId Chapter ID. + * @param name Name of the book. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the WS call is successful. */ logView(id: number, chapterId: string, name?: string, siteId?: string): Promise { const params = { diff --git a/src/addon/mod/book/providers/link-handler.ts b/src/addon/mod/book/providers/link-handler.ts index 631885cbe..bba613b19 100644 --- a/src/addon/mod/book/providers/link-handler.ts +++ b/src/addon/mod/book/providers/link-handler.ts @@ -31,11 +31,11 @@ export class AddonModBookLinkHandler extends CoreContentLinksModuleIndexHandler /** * 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/book/providers/list-link-handler.ts b/src/addon/mod/book/providers/list-link-handler.ts index 5351a9ae4..4b3f403d0 100644 --- a/src/addon/mod/book/providers/list-link-handler.ts +++ b/src/addon/mod/book/providers/list-link-handler.ts @@ -34,11 +34,11 @@ export class AddonModBookListLinkHandler extends CoreContentLinksModuleListHandl * 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.bookProvider.isPluginEnabled(); diff --git a/src/addon/mod/book/providers/module-handler.ts b/src/addon/mod/book/providers/module-handler.ts index 6f92d6f32..565b5a36c 100644 --- a/src/addon/mod/book/providers/module-handler.ts +++ b/src/addon/mod/book/providers/module-handler.ts @@ -45,7 +45,7 @@ export class AddonModBookModuleHandler 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.bookProvider.isPluginEnabled(); @@ -54,10 +54,10 @@ export class AddonModBookModuleHandler 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,10 +80,10 @@ export class AddonModBookModuleHandler 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 { return AddonModBookIndexComponent; diff --git a/src/addon/mod/book/providers/prefetch-handler.ts b/src/addon/mod/book/providers/prefetch-handler.ts index 0289370cd..d1055bbec 100644 --- a/src/addon/mod/book/providers/prefetch-handler.ts +++ b/src/addon/mod/book/providers/prefetch-handler.ts @@ -43,13 +43,13 @@ export class AddonModBookPrefetchHandler extends CoreCourseResourcePrefetchHandl /** * 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 promises = []; @@ -65,9 +65,9 @@ export class AddonModBookPrefetchHandler extends CoreCourseResourcePrefetchHandl /** * 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.bookProvider.getBook(courseId, module.id).catch(() => { @@ -80,9 +80,9 @@ export class AddonModBookPrefetchHandler extends CoreCourseResourcePrefetchHandl /** * 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.bookProvider.invalidateContent(moduleId, courseId); @@ -91,7 +91,7 @@ export class AddonModBookPrefetchHandler extends CoreCourseResourcePrefetchHandl /** * 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.bookProvider.isPluginEnabled(); diff --git a/src/addon/mod/book/providers/tag-area-handler.ts b/src/addon/mod/book/providers/tag-area-handler.ts index 47c456cb6..afa55b00b 100644 --- a/src/addon/mod/book/providers/tag-area-handler.ts +++ b/src/addon/mod/book/providers/tag-area-handler.ts @@ -33,7 +33,7 @@ export class AddonModBookTagAreaHandler 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 this.bookProvider.isPluginEnabled(); @@ -42,8 +42,8 @@ export class AddonModBookTagAreaHandler 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 = this.tagHelper.parseFeedContent(content); @@ -66,8 +66,8 @@ export class AddonModBookTagAreaHandler 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/chat/components/index/index.ts b/src/addon/mod/chat/components/index/index.ts index 585c6f4e8..71e646d53 100644 --- a/src/addon/mod/chat/components/index/index.ts +++ b/src/addon/mod/chat/components/index/index.ts @@ -58,10 +58,10 @@ export class AddonModChatIndexComponent extends CoreCourseModuleMainActivityComp /** * Download chat. * - * @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 { return this.chatProvider.getChat(this.courseId, this.module.id).then((chat) => { diff --git a/src/addon/mod/chat/pages/chat/chat.ts b/src/addon/mod/chat/pages/chat/chat.ts index 41bd0e1ca..e4951d6e3 100644 --- a/src/addon/mod/chat/pages/chat/chat.ts +++ b/src/addon/mod/chat/pages/chat/chat.ts @@ -131,7 +131,7 @@ export class AddonModChatChatPage { /** * Convenience function to login the user. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected loginUser(): Promise { return this.chatProvider.loginUser(this.chatId).then((sessionId) => { @@ -142,7 +142,7 @@ export class AddonModChatChatPage { /** * Convenience function to fetch chat messages. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchMessages(): Promise { return this.chatProvider.getLatestMessages(this.sessionId, this.lastTime).then((messagesInfo) => { @@ -188,7 +188,7 @@ export class AddonModChatChatPage { /** * Convenience function to be called every certain time to fetch chat messages. * - * @return {Promise} Promised resolved when done. + * @return Promised resolved when done. */ protected fetchMessagesInterval(): Promise { this.logger.debug('Polling for messages'); @@ -221,9 +221,9 @@ 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. + * @param message New message object. + * @param prevMessage Previous message object. + * @return True if messages are from diferent days, false othetwise. */ showDate(message: any, prevMessage: any): boolean { if (!prevMessage) { @@ -237,8 +237,8 @@ export class AddonModChatChatPage { /** * 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) { diff --git a/src/addon/mod/chat/pages/index/index.ts b/src/addon/mod/chat/pages/index/index.ts index 391b654fc..7eb57a4b9 100644 --- a/src/addon/mod/chat/pages/index/index.ts +++ b/src/addon/mod/chat/pages/index/index.ts @@ -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.ts b/src/addon/mod/chat/pages/session-messages/session-messages.ts index 91fe8874b..b96ed597a 100644 --- a/src/addon/mod/chat/pages/session-messages/session-messages.ts +++ b/src/addon/mod/chat/pages/session-messages/session-messages.ts @@ -49,7 +49,7 @@ 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) @@ -67,7 +67,7 @@ export class AddonModChatSessionMessagesPage { /** * Refresh session messages. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshMessages(refresher: any): void { this.chatProvider.invalidateSessionMessages(this.chatId, this.sessionStart, this.groupId).finally(() => { @@ -80,9 +80,9 @@ 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. + * @param message New message object. + * @param prevMessage Previous message object. + * @return True if messages are from diferent days, false othetwise. */ showDate(message: any, prevMessage: any): boolean { if (!prevMessage) { diff --git a/src/addon/mod/chat/pages/sessions/sessions.ts b/src/addon/mod/chat/pages/sessions/sessions.ts index 373a38dce..6043f3f3b 100644 --- a/src/addon/mod/chat/pages/sessions/sessions.ts +++ b/src/addon/mod/chat/pages/sessions/sessions.ts @@ -62,8 +62,8 @@ export class AddonModChatSessionsPage { /** * Fetch chat sessions. * - * @param {number} [showLoading] Display a loading modal. - * @return {Promise} Promise resolved when done. + * @param showLoading Display a loading modal. + * @return Promise resolved when done. */ fetchSessions(showLoading?: boolean): Promise { const modal = showLoading ? this.domUtils.showModalLoading() : null; @@ -112,7 +112,7 @@ export class AddonModChatSessionsPage { /** * Refresh chat sessions. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshSessions(refresher: any): void { const promises = [ @@ -130,7 +130,7 @@ export class AddonModChatSessionsPage { /** * Navigate to a session. * - * @param {any} session Chat session. + * @param session Chat session. */ openSession(session: any): void { this.selectedSessionStart = session.sessionstart; @@ -148,8 +148,8 @@ export class AddonModChatSessionsPage { /** * Show more session users. * - * @param {any} session Chat session. - * @param {Event} $event The event. + * @param session Chat session. + * @param $event The event. */ showMoreUsers(session: any, $event: Event): void { session.sessionusers = session.allsessionusers; diff --git a/src/addon/mod/chat/pages/users/users.ts b/src/addon/mod/chat/pages/users/users.ts index 90e59df6d..4cd59049d 100644 --- a/src/addon/mod/chat/pages/users/users.ts +++ b/src/addon/mod/chat/pages/users/users.ts @@ -75,7 +75,7 @@ export class AddonModChatUsersPage { /** * Add "To user:". * - * @param {any} user User object. + * @param user User object. */ talkTo(user: any): void { this.viewCtrl.dismiss({talkTo: user.fullname}); @@ -84,7 +84,7 @@ export class AddonModChatUsersPage { /** * Beep a user. * - * @param {any} user User object. + * @param user User object. */ beepTo(user: any): void { this.viewCtrl.dismiss({beepTo: user.id}); diff --git a/src/addon/mod/chat/providers/chat.ts b/src/addon/mod/chat/providers/chat.ts index 4e6b6df9b..9b9a02e39 100644 --- a/src/addon/mod/chat/providers/chat.ts +++ b/src/addon/mod/chat/providers/chat.ts @@ -36,10 +36,10 @@ export class AddonModChatProvider { /** * Get a chat. * - * @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 chat 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 chat is retrieved. */ getChat(courseId: number, cmId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -67,8 +67,8 @@ export class AddonModChatProvider { /** * Log the user into a chat room. * - * @param {number} chatId Chat instance ID. - * @return {Promise} Promise resolved when the WS is executed. + * @param chatId Chat instance ID. + * @return Promise resolved when the WS is executed. */ loginUser(chatId: number): Promise { const params = { @@ -87,10 +87,10 @@ export class AddonModChatProvider { /** * Report a chat as being viewed. * - * @param {number} id Chat instance ID. - * @param {string} [name] Name of the chat. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the WS call is successful. + * @param id Chat instance ID. + * @param name Name of the chat. + * @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 = { @@ -103,10 +103,10 @@ export class AddonModChatProvider { /** * Send a message to a chat. * - * @param {number} sessionId Chat sessiond ID. - * @param {string} message Message text. - * @param {number} beepUserId Beep user ID. - * @return {Promise} Promise resolved when the WS is executed. + * @param sessionId Chat sessiond ID. + * @param message Message text. + * @param beepUserId Beep user ID. + * @return Promise resolved when the WS is executed. */ sendMessage(sessionId: number, message: string, beepUserId: number): Promise { const params = { @@ -127,9 +127,9 @@ export class AddonModChatProvider { /** * Get the latest messages from a chat session. * - * @param {number} sessionId Chat sessiond ID. - * @param {number} lastTime Last time when messages were retrieved. - * @return {Promise} Promise resolved when the WS is executed. + * @param sessionId Chat sessiond ID. + * @param lastTime Last time when messages were retrieved. + * @return Promise resolved when the WS is executed. */ getLatestMessages(sessionId: number, lastTime: number): Promise { const params = { @@ -145,9 +145,9 @@ export class AddonModChatProvider { /** * Get user data for messages since they only have userid. * - * @param {any[]} messages Messages to get the user data for. - * @param {number} courseId ID of the course the messages belong to. - * @return {Promise} Promise always resolved with the formatted messages. + * @param messages Messages to get the user data for. + * @param courseId ID of the course the messages belong to. + * @return Promise always resolved with the formatted messages. */ getMessagesUserData(messages: any[], courseId: number): Promise { const promises = messages.map((message) => { @@ -168,8 +168,8 @@ export class AddonModChatProvider { /** * Get the actives users of a current chat. * - * @param {number} sessionId Chat sessiond ID. - * @return {Promise} Promise resolved when the WS is executed. + * @param sessionId Chat sessiond ID. + * @return Promise resolved when the WS is executed. */ getChatUsers(sessionId: number): Promise { const params = { @@ -185,8 +185,8 @@ export class AddonModChatProvider { /** * Return whether WS for passed sessions are available. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with a boolean. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with a boolean. */ areSessionsAvailable(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -197,12 +197,12 @@ export class AddonModChatProvider { /** * Get chat sessions. * - * @param {number} chatId Chat ID. - * @param {number} [groupId=0] Group ID, 0 means that the function will determine the user group. - * @param {boolean} [showAll=false] Whether to include incomplete sessions or not. - * @param {boolean} [ignoreCache=false] 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 list of sessions. + * @param chatId Chat ID. + * @param groupId Group ID, 0 means that the function will determine the user group. + * @param showAll Whether to include incomplete sessions or not. + * @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 list of sessions. * @since 3.5 */ getSessions(chatId: number, groupId: number = 0, showAll: boolean = false, ignoreCache: boolean = false, siteId?: string): @@ -235,13 +235,13 @@ export class AddonModChatProvider { /** * Get chat session messages. * - * @param {number} chatId Chat ID. - * @param {number} sessionStart Session start time. - * @param {number} sessionEnd Session end time. - * @param {number} [groupId=0] Group ID, 0 means that the function will determine the user group. - * @param {boolean} [ignoreCache=false] 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 list of messages. + * @param chatId Chat ID. + * @param sessionStart Session start time. + * @param sessionEnd Session end time. + * @param groupId Group ID, 0 means that the function will determine the user group. + * @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 list of messages. * @since 3.5 */ getSessionMessages(chatId: number, sessionStart: number, sessionEnd: number, groupId: number = 0, ignoreCache: boolean = false, @@ -275,8 +275,8 @@ export class AddonModChatProvider { /** * Invalidate chats. * - * @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. */ invalidateChats(courseId: number): Promise { const site = this.sitesProvider.getCurrentSite(); @@ -287,10 +287,10 @@ export class AddonModChatProvider { /** * Invalidate chat sessions. * - * @param {number} chatId Chat ID. - * @param {number} [groupId=0] Group ID, 0 means that the function will determine the user group. - * @param {boolean} [showAll=false] Whether to include incomplete sessions or not. - * @return {Promise} Promise resolved when the data is invalidated. + * @param chatId Chat ID. + * @param groupId Group ID, 0 means that the function will determine the user group. + * @param showAll Whether to include incomplete sessions or not. + * @return Promise resolved when the data is invalidated. */ invalidateSessions(chatId: number, groupId: number = 0, showAll: boolean = false): Promise { const site = this.sitesProvider.getCurrentSite(); @@ -301,8 +301,8 @@ export class AddonModChatProvider { /** * Invalidate all chat sessions. * - * @param {number} chatId Chat ID. - * @return {Promise} Promise resolved when the data is invalidated. + * @param chatId Chat ID. + * @return Promise resolved when the data is invalidated. */ invalidateAllSessions(chatId: number): Promise { const site = this.sitesProvider.getCurrentSite(); @@ -313,10 +313,10 @@ export class AddonModChatProvider { /** * Invalidate chat session messages. * - * @param {number} chatId Chat ID. - * @param {number} sessionStart Session start time. - * @param {number} [groupId=0] Group ID, 0 means that the function will determine the user group. - * @return {Promise} Promise resolved when the data is invalidated. + * @param chatId Chat ID. + * @param sessionStart Session start time. + * @param groupId Group ID, 0 means that the function will determine the user group. + * @return Promise resolved when the data is invalidated. */ invalidateSessionMessages(chatId: number, sessionStart: number, groupId: number = 0): Promise { const site = this.sitesProvider.getCurrentSite(); @@ -327,8 +327,8 @@ export class AddonModChatProvider { /** * Invalidate all chat session messages. * - * @param {number} chatId Chat ID. - * @return {Promise} Promise resolved when the data is invalidated. + * @param chatId Chat ID. + * @return Promise resolved when the data is invalidated. */ invalidateAllSessionMessages(chatId: number): Promise { const site = this.sitesProvider.getCurrentSite(); @@ -339,8 +339,8 @@ export class AddonModChatProvider { /** * Get cache key for chats WS call. * - * @param {number} courseId Course ID. - * @return {string} Cache key. + * @param courseId Course ID. + * @return Cache key. */ protected getChatsCacheKey(courseId: number): string { return this.ROOT_CACHE_KEY + 'chats:' + courseId; @@ -349,10 +349,10 @@ export class AddonModChatProvider { /** * Get cache key for sessions WS call. * - * @param {number} chatId Chat ID. - * @param {number} groupId Goup ID, 0 means that the function will determine the user group. - * @param {boolean} showAll Whether to include incomplete sessions or not. - * @return {string} Cache key. + * @param chatId Chat ID. + * @param groupId Goup ID, 0 means that the function will determine the user group. + * @param showAll Whether to include incomplete sessions or not. + * @return Cache key. */ protected getSessionsCacheKey(chatId: number, groupId: number, showAll: boolean): string { return this.getSessionsCacheKeyPrefix(chatId) + groupId + ':' + (showAll ? 1 : 0); @@ -361,8 +361,8 @@ export class AddonModChatProvider { /** * Get cache key prefix for sessions WS call. * - * @param {number} chatId Chat ID. - * @return {string} Cache key prefix. + * @param chatId Chat ID. + * @return Cache key prefix. */ protected getSessionsCacheKeyPrefix(chatId: number): string { return this.ROOT_CACHE_KEY + 'sessions:' + chatId + ':'; @@ -371,10 +371,10 @@ export class AddonModChatProvider { /** * Get cache key for session messages WS call. * - * @param {number} chatId Chat ID. - * @param {number} sessionStart Session start time. - * @param {number} groupId Group ID, 0 means that the function will determine the user group. - * @return {string} Cache key. + * @param chatId Chat ID. + * @param sessionStart Session start time. + * @param groupId Group ID, 0 means that the function will determine the user group. + * @return Cache key. */ protected getSessionMessagesCacheKey(chatId: number, sessionStart: number, groupId: number): string { return this.getSessionMessagesCacheKeyPrefix(chatId) + sessionStart + ':' + groupId; @@ -383,8 +383,8 @@ export class AddonModChatProvider { /** * Get cache key prefix for session messages WS call. * - * @param {number} chatId Chat ID. - * @return {string} Cache key prefix. + * @param chatId Chat ID. + * @return Cache key prefix. */ protected getSessionMessagesCacheKeyPrefix(chatId: number): string { return this.ROOT_CACHE_KEY + 'sessionsMessages:' + chatId + ':'; diff --git a/src/addon/mod/chat/providers/module-handler.ts b/src/addon/mod/chat/providers/module-handler.ts index f85c2be5e..1a459766d 100644 --- a/src/addon/mod/chat/providers/module-handler.ts +++ b/src/addon/mod/chat/providers/module-handler.ts @@ -44,7 +44,7 @@ export class AddonModChatModuleHandler 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; @@ -53,10 +53,10 @@ export class AddonModChatModuleHandler 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 = { @@ -83,9 +83,9 @@ export class AddonModChatModuleHandler 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 AddonModChatIndexComponent; diff --git a/src/addon/mod/chat/providers/prefetch-handler.ts b/src/addon/mod/chat/providers/prefetch-handler.ts index c9f54a62a..ca146b06d 100644 --- a/src/addon/mod/chat/providers/prefetch-handler.ts +++ b/src/addon/mod/chat/providers/prefetch-handler.ts @@ -51,7 +51,7 @@ export class AddonModChatPrefetchHandler 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.chatProvider.areSessionsAvailable(); @@ -60,9 +60,9 @@ export class AddonModChatPrefetchHandler 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.chatProvider.getChat(courseId, moduleId).then((chat) => { @@ -79,9 +79,9 @@ export class AddonModChatPrefetchHandler extends CoreCourseActivityPrefetchHandl * 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 { const promises = [ @@ -95,11 +95,11 @@ export class AddonModChatPrefetchHandler 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 { return this.prefetchPackage(module, courseId, single, this.prefetchChat.bind(this)); @@ -108,11 +108,11 @@ export class AddonModChatPrefetchHandler extends CoreCourseActivityPrefetchHandl /** * Prefetch a chat. * - * @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 prefetchChat(module: any, courseId: number, single: boolean, siteId: string): Promise { // Prefetch chat and group info. @@ -158,12 +158,12 @@ export class AddonModChatPrefetchHandler extends CoreCourseActivityPrefetchHandl /** * Prefetch chat session messages and user profiles. * - * @param {number} chatId Chat ID. - * @param {any} session Session object. - * @param {number} groupId Group ID. - * @param {number} courseId Course ID the module belongs to. - * @param {string} siteId Site ID. - * @return {Promise} Promise resolved when done. + * @param chatId Chat ID. + * @param session Session object. + * @param groupId Group ID. + * @param courseId Course ID the module belongs to. + * @param siteId Site ID. + * @return Promise resolved when done. */ protected prefetchSession(chatId: number, session: any, groupId: number, courseId: number, siteId: string): Promise { return this.chatProvider.getSessionMessages(chatId, session.sessionstart, session.sessionend, groupId, true, siteId) diff --git a/src/addon/mod/choice/components/index/index.ts b/src/addon/mod/choice/components/index/index.ts index b543e4013..c5bcdc852 100644 --- a/src/addon/mod/choice/components/index/index.ts +++ b/src/addon/mod/choice/components/index/index.ts @@ -78,7 +78,7 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo /** * Perform the invalidate content function. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected invalidateContent(): Promise { const promises = []; @@ -96,8 +96,8 @@ export class AddonModChoiceIndexComponent 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 { if (this.choice && syncEventData.choiceId == this.choice.id && syncEventData.userId == this.userId) { @@ -112,10 +112,10 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo /** * Download choice contents. * - * @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 { this.now = new Date().getTime(); @@ -163,8 +163,8 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo /** * Convenience function to get choice options. * - * @param {boolean} hasOffline True if there are responses stored offline. - * @return {Promise} Promise resolved when done. + * @param hasOffline True if there are responses stored offline. + * @return Promise resolved when done. */ protected fetchOptions(hasOffline: boolean): Promise { return this.choiceProvider.getOptions(this.choice.id).then((options) => { @@ -277,7 +277,7 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo /** * Convenience function to get choice results. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected fetchResults(): Promise { if (this.choiceNotOpenYet) { @@ -307,7 +307,7 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo /** * Check if a choice is open. * - * @return {boolean} True if choice is open, false otherwise. + * @return True if choice is open, false otherwise. */ protected isChoiceOpen(): boolean { return (this.choice.timeopen === 0 || this.choice.timeopen <= this.now) && @@ -317,7 +317,7 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo /** * Return true if the user has selected at least one option. * - * @return {boolean} True if the user has responded. + * @return True if the user has responded. */ canSave(): boolean { if (this.choice.allowmultiple) { @@ -391,8 +391,8 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo /** * Function to call when some data has changed. It will refresh/prefetch data. * - * @param {boolean} online Whether the data was sent to server or stored in offline. - * @return {Promise} Promise resolved when done. + * @param online Whether the data was sent to server or stored in offline. + * @return Promise resolved when done. */ protected dataUpdated(online: boolean): Promise { if (online && this.isPrefetched()) { @@ -413,7 +413,7 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo /** * Performs the sync of the activity. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected sync(): Promise { return this.choiceSync.syncChoice(this.choice.id, this.userId); @@ -422,8 +422,8 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo /** * 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; diff --git a/src/addon/mod/choice/pages/index/index.ts b/src/addon/mod/choice/pages/index/index.ts index 1fb470cd4..432dba56c 100644 --- a/src/addon/mod/choice/pages/index/index.ts +++ b/src/addon/mod/choice/pages/index/index.ts @@ -40,7 +40,7 @@ export class AddonModChoiceIndexPage { /** * Update some data based on the choice instance. * - * @param {any} choice Choice instance. + * @param choice Choice instance. */ updateData(choice: any): void { this.title = choice.name || this.title; diff --git a/src/addon/mod/choice/providers/choice.ts b/src/addon/mod/choice/providers/choice.ts index 770afa6c0..b16119516 100644 --- a/src/addon/mod/choice/providers/choice.ts +++ b/src/addon/mod/choice/providers/choice.ts @@ -48,9 +48,9 @@ export class AddonModChoiceProvider { * - they're published after the choice is closed and it's closed, OR * - they're published after answering and the user has answered. * - * @param {any} choice Choice to check. - * @param {boolean} hasAnswered True if user has answered the choice, false otherwise. - * @return {boolean} True if the students can see the results. + * @param choice Choice to check. + * @param hasAnswered True if user has answered the choice, false otherwise. + * @return True if the students can see the results. */ canStudentSeeResults(choice: any, hasAnswered: boolean): boolean { const now = new Date().getTime(); @@ -64,12 +64,12 @@ export class AddonModChoiceProvider { /** * Delete responses from a choice. * - * @param {number} choiceId Choice ID. - * @param {string} name Choice name. - * @param {number} courseId Course ID the choice belongs to. - * @param {number[]} [responses] IDs of the answers. If not defined, delete all the answers of the current user. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean: true if response was sent to server, false if stored in device. + * @param choiceId Choice ID. + * @param name Choice name. + * @param courseId Course ID the choice belongs to. + * @param responses IDs of the answers. If not defined, delete all the answers of the current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: true if response was sent to server, false if stored in device. */ deleteResponses(choiceId: number, name: string, courseId: number, responses?: number[], siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -106,10 +106,10 @@ export class AddonModChoiceProvider { /** * Delete responses from a choice. It will fail if offline or cannot connect. * - * @param {number} choiceId Choice ID. - * @param {number[]} [responses] IDs of the answers. If not defined, delete all the answers of the current user. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when responses are successfully deleted. + * @param choiceId Choice ID. + * @param responses IDs of the answers. If not defined, delete all the answers of the current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when responses are successfully deleted. */ deleteResponsesOnline(choiceId: number, responses?: number[], siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -140,8 +140,8 @@ export class AddonModChoiceProvider { /** * Get cache key for choice data WS calls. * - * @param {number} courseId Course ID. - * @return {string} Cache key. + * @param courseId Course ID. + * @return Cache key. */ protected getChoiceDataCacheKey(courseId: number): string { return this.ROOT_CACHE_KEY + 'choice:' + courseId; @@ -150,8 +150,8 @@ export class AddonModChoiceProvider { /** * Get cache key for choice options WS calls. * - * @param {number} choiceId Choice ID. - * @return {string} Cache key. + * @param choiceId Choice ID. + * @return Cache key. */ protected getChoiceOptionsCacheKey(choiceId: number): string { return this.ROOT_CACHE_KEY + 'options:' + choiceId; @@ -160,8 +160,8 @@ export class AddonModChoiceProvider { /** * Get cache key for choice results WS calls. * - * @param {number} choiceId Choice ID. - * @return {string} Cache key. + * @param choiceId Choice ID. + * @return Cache key. */ protected getChoiceResultsCacheKey(choiceId: number): string { return this.ROOT_CACHE_KEY + 'results:' + choiceId; @@ -170,13 +170,13 @@ export class AddonModChoiceProvider { /** * Get a choice with key=value. If more than one is found, only the first will be returned. * - * @param {string} siteId Site ID. - * @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. Default false. - * @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 choice is retrieved. + * @param siteId Site ID. + * @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. Default false. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @return Promise resolved when the choice is retrieved. */ protected getChoiceByDataKey(siteId: string, courseId: number, key: string, value: any, forceCache?: boolean, ignoreCache?: boolean): Promise { @@ -214,12 +214,12 @@ export class AddonModChoiceProvider { /** * Get a choice 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. - * @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false. - * @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 choice is retrieved. + * @param courseId Course ID. + * @param cmId Course module ID. + * @param siteId Site ID. If not defined, current site. + * @param forceCache True to always get the value from cache, false otherwise. Default false. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @return Promise resolved when the choice is retrieved. */ getChoice(courseId: number, cmId: number, siteId?: string, forceCache?: boolean, ignoreCache?: boolean): Promise { return this.getChoiceByDataKey(siteId, courseId, 'coursemodule', cmId, forceCache, ignoreCache); @@ -228,12 +228,12 @@ export class AddonModChoiceProvider { /** * Get a choice by ID. * - * @param {number} courseId Course ID. - * @param {number} choiceId Choice ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false. - * @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 choice is retrieved. + * @param courseId Course ID. + * @param choiceId Choice ID. + * @param siteId Site ID. If not defined, current site. + * @param forceCache True to always get the value from cache, false otherwise. Default false. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @return Promise resolved when the choice is retrieved. */ getChoiceById(courseId: number, choiceId: number, siteId?: string, forceCache?: boolean, ignoreCache?: boolean): Promise { return this.getChoiceByDataKey(siteId, courseId, 'id', choiceId, forceCache, ignoreCache); @@ -242,10 +242,10 @@ export class AddonModChoiceProvider { /** * Get choice options. * - * @param {number} choiceId Choice ID. - * @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 choice options. + * @param choiceId Choice 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 with choice options. */ getOptions(choiceId: number, ignoreCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -275,10 +275,10 @@ export class AddonModChoiceProvider { /** * Get choice results. * - * @param {number} choiceId Choice ID. - * @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 choice results. + * @param choiceId Choice 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 with choice results. */ getResults(choiceId: number, ignoreCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -307,9 +307,9 @@ export class AddonModChoiceProvider { /** * Invalidate choice 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. */ invalidateChoiceData(courseId: number, siteId?: string): Promise { return this.sitesProvider.getSite(null).then((site) => { @@ -320,10 +320,10 @@ export class AddonModChoiceProvider { /** * 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(); @@ -346,9 +346,9 @@ export class AddonModChoiceProvider { /** * Invalidate choice options. * - * @param {number} choiceId Choice ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param choiceId Choice ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateOptions(choiceId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -359,9 +359,9 @@ export class AddonModChoiceProvider { /** * Invalidate choice results. * - * @param {number} choiceId Choice ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param choiceId Choice ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateResults(choiceId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -372,10 +372,10 @@ export class AddonModChoiceProvider { /** * Report the choice as being viewed. * - * @param {string} id Choice ID. - * @param {string} [name] Name of the choice. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the WS call is successful. + * @param id Choice ID. + * @param name Name of the choice. + * @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 = { @@ -389,12 +389,12 @@ export class AddonModChoiceProvider { /** * Send a response to a choice to Moodle. * - * @param {number} choiceId Choice ID. - * @param {string} name Choice name. - * @param {number} courseId Course ID the choice belongs to. - * @param {number[]} responses IDs of selected options. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean: true if response was sent to server, false if stored in device. + * @param choiceId Choice ID. + * @param name Choice name. + * @param courseId Course ID the choice belongs to. + * @param responses IDs of selected options. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: true if response was sent to server, false if stored in device. */ submitResponse(choiceId: number, name: string, courseId: number, responses: number[], siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -430,10 +430,10 @@ export class AddonModChoiceProvider { /** * Send a response to a choice to Moodle. It will fail if offline or cannot connect. * - * @param {number} choiceId Choice ID. - * @param {number[]} responses IDs of selected options. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when responses are successfully submitted. + * @param choiceId Choice ID. + * @param responses IDs of selected options. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when responses are successfully submitted. */ submitResponseOnline(choiceId: number, responses: number[], siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { diff --git a/src/addon/mod/choice/providers/module-handler.ts b/src/addon/mod/choice/providers/module-handler.ts index cc2448da9..789e59508 100644 --- a/src/addon/mod/choice/providers/module-handler.ts +++ b/src/addon/mod/choice/providers/module-handler.ts @@ -44,7 +44,7 @@ export class AddonModChoiceModuleHandler 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; @@ -53,10 +53,10 @@ export class AddonModChoiceModuleHandler 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 { @@ -78,9 +78,9 @@ export class AddonModChoiceModuleHandler 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 AddonModChoiceIndexComponent; diff --git a/src/addon/mod/choice/providers/offline.ts b/src/addon/mod/choice/providers/offline.ts index b4193f380..0b8e4294e 100644 --- a/src/addon/mod/choice/providers/offline.ts +++ b/src/addon/mod/choice/providers/offline.ts @@ -72,10 +72,10 @@ export class AddonModChoiceOfflineProvider { /** * Delete a response. * - * @param {number} choiceId Choice ID to remove. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User the responses belong to. If not defined, current user in site. - * @return {Promise} Promise resolved if stored, rejected if failure. + * @param choiceId Choice ID to remove. + * @param siteId Site ID. If not defined, current site. + * @param userId User the responses belong to. If not defined, current user in site. + * @return Promise resolved if stored, rejected if failure. */ deleteResponse(choiceId: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -88,8 +88,8 @@ export class AddonModChoiceOfflineProvider { /** * Get all offline responses. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promi[se resolved with responses. + * @param siteId Site ID. If not defined, current site. + * @return Promi[se resolved with responses. */ getResponses(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -106,10 +106,10 @@ export class AddonModChoiceOfflineProvider { /** * Check if there are offline responses to send. * - * @param {number} choiceId Choice ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User the responses belong to. If not defined, current user in site. - * @return {Promise} Promise resolved with boolean: true if has offline answers, false otherwise. + * @param choiceId Choice ID. + * @param siteId Site ID. If not defined, current site. + * @param userId User the responses belong to. If not defined, current user in site. + * @return Promise resolved with boolean: true if has offline answers, false otherwise. */ hasResponse(choiceId: number, siteId?: string, userId?: number): Promise { return this.getResponse(choiceId, siteId, userId).then((response) => { @@ -123,10 +123,10 @@ export class AddonModChoiceOfflineProvider { /** * Get response to be synced. * - * @param {number} choiceId Choice ID to get. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User the responses belong to. If not defined, current user in site. - * @return {Promise} Promise resolved with the object to be synced. + * @param choiceId Choice ID to get. + * @param siteId Site ID. If not defined, current site. + * @param userId User the responses belong to. If not defined, current user in site. + * @return Promise resolved with the object to be synced. */ getResponse(choiceId: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -144,14 +144,14 @@ export class AddonModChoiceOfflineProvider { /** * Offline version for sending a response to a choice to Moodle. * - * @param {number} choiceId Choice ID. - * @param {string} name Choice name. - * @param {number} courseId Course ID the choice belongs to. - * @param {number[]} responses IDs of selected options. - * @param {boolean} deleting If true, the user is deleting responses, if false, submitting. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User the responses belong to. If not defined, current user in site. - * @return {Promise} Promise resolved when results are successfully submitted. + * @param choiceId Choice ID. + * @param name Choice name. + * @param courseId Course ID the choice belongs to. + * @param responses IDs of selected options. + * @param deleting If true, the user is deleting responses, if false, submitting. + * @param siteId Site ID. If not defined, current site. + * @param userId User the responses belong to. If not defined, current user in site. + * @return Promise resolved when results are successfully submitted. */ saveResponse(choiceId: number, name: string, courseId: number, responses: number[], deleting: boolean, siteId?: string, userId?: number): Promise { diff --git a/src/addon/mod/choice/providers/prefetch-handler.ts b/src/addon/mod/choice/providers/prefetch-handler.ts index eb8a72d4b..053304bfe 100644 --- a/src/addon/mod/choice/providers/prefetch-handler.ts +++ b/src/addon/mod/choice/providers/prefetch-handler.ts @@ -48,11 +48,11 @@ export class AddonModChoicePrefetchHandler extends CoreCourseActivityPrefetchHan /** * 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.prefetchChoice.bind(this)); @@ -61,11 +61,11 @@ export class AddonModChoicePrefetchHandler extends CoreCourseActivityPrefetchHan /** * Prefetch a choice. * - * @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. - * @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. + * @return Promise resolved when done. */ protected prefetchChoice(module: any, courseId: number, single: boolean, siteId: string): Promise { return this.choiceProvider.getChoice(courseId, module.id, siteId, false, true).then((choice) => { @@ -103,9 +103,9 @@ export class AddonModChoicePrefetchHandler extends CoreCourseActivityPrefetchHan /** * Returns choice 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.choiceProvider.getChoice(courseId, module.id).catch(() => { @@ -118,9 +118,9 @@ export class AddonModChoicePrefetchHandler extends CoreCourseActivityPrefetchHan /** * 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.choiceProvider.invalidateContent(moduleId, courseId); @@ -129,9 +129,9 @@ export class AddonModChoicePrefetchHandler extends CoreCourseActivityPrefetchHan /** * 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 { return this.choiceProvider.invalidateChoiceData(courseId); @@ -140,10 +140,10 @@ export class AddonModChoicePrefetchHandler extends CoreCourseActivityPrefetchHan /** * 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/choice/providers/sync-cron-handler.ts b/src/addon/mod/choice/providers/sync-cron-handler.ts index 92d88b52b..92c2317a7 100644 --- a/src/addon/mod/choice/providers/sync-cron-handler.ts +++ b/src/addon/mod/choice/providers/sync-cron-handler.ts @@ -29,9 +29,9 @@ export class AddonModChoiceSyncCronHandler 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.choiceSync.syncAllChoices(siteId, force); @@ -40,7 +40,7 @@ export class AddonModChoiceSyncCronHandler 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.choiceSync.syncInterval; diff --git a/src/addon/mod/choice/providers/sync.ts b/src/addon/mod/choice/providers/sync.ts index 965a1b06d..7d381308e 100644 --- a/src/addon/mod/choice/providers/sync.ts +++ b/src/addon/mod/choice/providers/sync.ts @@ -56,9 +56,9 @@ export class AddonModChoiceSyncProvider extends CoreCourseActivitySyncBaseProvid /** * Get the ID of a choice sync. * - * @param {number} choiceId Choice ID. - * @param {number} userId User the responses belong to. - * @return {string} Sync ID. + * @param choiceId Choice ID. + * @param userId User the responses belong to. + * @return Sync ID. */ protected getSyncId(choiceId: number, userId: number): string { return choiceId + '#' + userId; @@ -67,9 +67,9 @@ export class AddonModChoiceSyncProvider extends CoreCourseActivitySyncBaseProvid /** * Try to synchronize all the choices 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. */ syncAllChoices(siteId?: string, force?: boolean): Promise { return this.syncOnSites('choices', this.syncAllChoicesFunc.bind(this), [force], siteId); @@ -78,9 +78,9 @@ export class AddonModChoiceSyncProvider extends CoreCourseActivitySyncBaseProvid /** * Sync all pending choices on a site. * - * @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. */ protected syncAllChoicesFunc(siteId?: string, force?: boolean): Promise { return this.choiceOffline.getResponses(siteId).then((responses) => { @@ -108,10 +108,10 @@ export class AddonModChoiceSyncProvider extends CoreCourseActivitySyncBaseProvid /** * Sync an choice only if a certain time has passed since the last time. * - * @param {number} choiceId Choice ID to be synced. - * @param {number} userId User the answers belong to. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the choice is synced or it doesn't need to be synced. + * @param choiceId Choice ID to be synced. + * @param userId User the answers belong to. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the choice is synced or it doesn't need to be synced. */ syncChoiceIfNeeded(choiceId: number, userId: number, siteId?: string): Promise { const syncId = this.getSyncId(choiceId, userId); @@ -126,10 +126,10 @@ export class AddonModChoiceSyncProvider extends CoreCourseActivitySyncBaseProvid /** * Synchronize a choice. * - * @param {number} choiceId Choice ID to be synced. - * @param {number} [userId] User the answers belong to. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if sync is successful, rejected otherwise. + * @param choiceId Choice ID to be synced. + * @param userId User the answers belong to. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if sync is successful, rejected otherwise. */ syncChoice(choiceId: number, userId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { diff --git a/src/addon/mod/data/classes/field-plugin-component.ts b/src/addon/mod/data/classes/field-plugin-component.ts index 9265a9ff6..62c12f13e 100644 --- a/src/addon/mod/data/classes/field-plugin-component.ts +++ b/src/addon/mod/data/classes/field-plugin-component.ts @@ -34,8 +34,8 @@ export class AddonModDataFieldPluginComponent implements OnInit, OnChanges { /** * Add the form control for the search mode. * - * @param {string} fieldName Control field name. - * @param {any} value Initial set value. + * @param fieldName Control field name. + * @param value Initial set value. */ protected addControl(fieldName: string, value?: any): void { if (!this.form) { @@ -68,7 +68,7 @@ export class AddonModDataFieldPluginComponent implements OnInit, OnChanges { /** * Return if is shown or list mode. * - * @return {boolean} True if mode is show or list. + * @return True if mode is show or list. */ isShowOrListMode(): boolean { return this.mode == 'list' || this.mode == 'show'; diff --git a/src/addon/mod/data/components/action/action.ts b/src/addon/mod/data/components/action/action.ts index b96dc078d..b7bca0b40 100644 --- a/src/addon/mod/data/components/action/action.ts +++ b/src/addon/mod/data/components/action/action.ts @@ -117,7 +117,7 @@ export class AddonModDataActionComponent implements OnInit { /** * Undo delete action. * - * @return {Promise} Solved when done. + * @return Solved when done. */ undoDelete(): Promise { const dataId = this.database.id, diff --git a/src/addon/mod/data/components/index/index.ts b/src/addon/mod/data/components/index/index.ts index bc3463842..ab8998c2d 100644 --- a/src/addon/mod/data/components/index/index.ts +++ b/src/addon/mod/data/components/index/index.ts @@ -141,7 +141,7 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp /** * Perform the invalidate content function. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected invalidateContent(): Promise { const promises = []; @@ -166,8 +166,8 @@ export class AddonModDataIndexComponent 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.data && syncEventData.dataId == this.data.id && typeof syncEventData.entryId == 'undefined') { @@ -184,10 +184,10 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp /** * Download data contents. * - * @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 { let canAdd = false, @@ -259,7 +259,7 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp /** * Fetch current database entries. * - * @return {Promise} Resolved then done. + * @return Resolved then done. */ protected fetchEntriesData(): Promise { @@ -356,8 +356,8 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp /** * Performs the search and closes the modal. * - * @param {number} page Page number. - * @return {Promise} Resolved when done. + * @param page Page number. + * @return Resolved when done. */ searchEntries(page: number): Promise { this.loaded = false; @@ -386,8 +386,8 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp /** * Set group to see the database. * - * @param {number} groupId Group ID. - * @return {Promise} Resolved when new group is selected or rejected if not. + * @param groupId Group ID. + * @return Resolved when new group is selected or rejected if not. */ setGroup(groupId: number): Promise { this.selectedGroup = groupId; @@ -416,7 +416,7 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp /** * Goto the selected entry. * - * @param {number} entryId Entry ID. + * @param entryId Entry ID. */ gotoEntry(entryId: number): void { const params = { @@ -439,7 +439,7 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp /** * 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); @@ -448,8 +448,8 @@ export class AddonModDataIndexComponent 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 { return result.updated; diff --git a/src/addon/mod/data/fields/checkbox/component/checkbox.ts b/src/addon/mod/data/fields/checkbox/component/checkbox.ts index 2db02302a..88a7d38c2 100644 --- a/src/addon/mod/data/fields/checkbox/component/checkbox.ts +++ b/src/addon/mod/data/fields/checkbox/component/checkbox.ts @@ -64,7 +64,7 @@ export class AddonModDataFieldCheckboxComponent extends AddonModDataFieldPluginC /** * 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/checkbox/providers/handler.ts b/src/addon/mod/data/fields/checkbox/providers/handler.ts index 3c3009394..e0b5aceb5 100644 --- a/src/addon/mod/data/fields/checkbox/providers/handler.ts +++ b/src/addon/mod/data/fields/checkbox/providers/handler.ts @@ -30,9 +30,9 @@ export class AddonModDataFieldCheckboxHandler implements AddonModDataFieldHandle * 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 AddonModDataFieldCheckboxComponent; @@ -41,9 +41,9 @@ export class AddonModDataFieldCheckboxHandler implements AddonModDataFieldHandle /** * 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 AddonModDataFieldCheckboxHandler implements AddonModDataFieldHandle /** * 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 AddonModDataFieldCheckboxHandler implements AddonModDataFieldHandle /** * 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 AddonModDataFieldCheckboxHandler implements AddonModDataFieldHandle /** * 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 AddonModDataFieldCheckboxHandler implements AddonModDataFieldHandle /** * 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 AddonModDataFieldCheckboxHandler implements AddonModDataFieldHandle /** * 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/date/providers/handler.ts b/src/addon/mod/data/fields/date/providers/handler.ts index bf9d65079..8c4988971 100644 --- a/src/addon/mod/data/fields/date/providers/handler.ts +++ b/src/addon/mod/data/fields/date/providers/handler.ts @@ -31,9 +31,9 @@ export class AddonModDataFieldDateHandler 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 AddonModDataFieldDateComponent; @@ -42,9 +42,9 @@ export class AddonModDataFieldDateHandler 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, @@ -82,9 +82,9 @@ export class AddonModDataFieldDateHandler 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; @@ -120,10 +120,10 @@ export class AddonModDataFieldDateHandler 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, @@ -138,9 +138,9 @@ export class AddonModDataFieldDateHandler 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 && @@ -155,10 +155,10 @@ export class AddonModDataFieldDateHandler 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 { let date = Date.UTC(offlineContent['year'] || '', offlineContent['month'] ? offlineContent['month'] - 1 : null, @@ -173,7 +173,7 @@ export class AddonModDataFieldDateHandler 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/file/component/file.ts b/src/addon/mod/data/fields/file/component/file.ts index 3522076fc..b5341afca 100644 --- a/src/addon/mod/data/fields/file/component/file.ts +++ b/src/addon/mod/data/fields/file/component/file.ts @@ -38,8 +38,8 @@ export class AddonModDataFieldFileComponent extends AddonModDataFieldPluginCompo /** * 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) || []; @@ -74,7 +74,7 @@ export class AddonModDataFieldFileComponent extends AddonModDataFieldPluginCompo /** * 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/file/providers/handler.ts b/src/addon/mod/data/fields/file/providers/handler.ts index d6e10b4b1..db6706a14 100644 --- a/src/addon/mod/data/fields/file/providers/handler.ts +++ b/src/addon/mod/data/fields/file/providers/handler.ts @@ -34,9 +34,9 @@ export class AddonModDataFieldFileHandler 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 AddonModDataFieldFileComponent; @@ -45,9 +45,9 @@ export class AddonModDataFieldFileHandler 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 AddonModDataFieldFileHandler 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); @@ -82,8 +82,8 @@ export class AddonModDataFieldFileHandler 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); @@ -92,10 +92,10 @@ export class AddonModDataFieldFileHandler 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 files = this.fileSessionprovider.getFiles(AddonModDataProvider.COMPONENT, field.dataid + '_' + field.id) || []; @@ -111,9 +111,9 @@ export class AddonModDataFieldFileHandler 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)) { @@ -126,10 +126,10 @@ export class AddonModDataFieldFileHandler 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) { @@ -146,7 +146,7 @@ export class AddonModDataFieldFileHandler 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/latlong/component/latlong.ts b/src/addon/mod/data/fields/latlong/component/latlong.ts index bef58067f..03f912cc3 100644 --- a/src/addon/mod/data/fields/latlong/component/latlong.ts +++ b/src/addon/mod/data/fields/latlong/component/latlong.ts @@ -35,9 +35,9 @@ export class AddonModDataFieldLatlongComponent extends AddonModDataFieldPluginCo /** * 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,9 +51,9 @@ 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 { if (north !== null || east !== null) { @@ -87,7 +87,7 @@ 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; diff --git a/src/addon/mod/data/fields/latlong/providers/handler.ts b/src/addon/mod/data/fields/latlong/providers/handler.ts index 606db2878..941466e83 100644 --- a/src/addon/mod/data/fields/latlong/providers/handler.ts +++ b/src/addon/mod/data/fields/latlong/providers/handler.ts @@ -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/providers/handler.ts b/src/addon/mod/data/fields/menu/providers/handler.ts index e8427cc66..6a928f5a5 100644 --- a/src/addon/mod/data/fields/menu/providers/handler.ts +++ b/src/addon/mod/data/fields/menu/providers/handler.ts @@ -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/multimenu.ts b/src/addon/mod/data/fields/multimenu/component/multimenu.ts index b31800229..933687535 100644 --- a/src/addon/mod/data/fields/multimenu/component/multimenu.ts +++ b/src/addon/mod/data/fields/multimenu/component/multimenu.ts @@ -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/providers/handler.ts b/src/addon/mod/data/fields/multimenu/providers/handler.ts index 0522bc304..060583426 100644 --- a/src/addon/mod/data/fields/multimenu/providers/handler.ts +++ b/src/addon/mod/data/fields/multimenu/providers/handler.ts @@ -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/providers/handler.ts b/src/addon/mod/data/fields/number/providers/handler.ts index 62308e6b5..e40100258 100644 --- a/src/addon/mod/data/fields/number/providers/handler.ts +++ b/src/addon/mod/data/fields/number/providers/handler.ts @@ -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..ab639cf6d 100644 --- a/src/addon/mod/data/fields/picture/component/picture.ts +++ b/src/addon/mod/data/fields/picture/component/picture.ts @@ -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/providers/handler.ts b/src/addon/mod/data/fields/picture/providers/handler.ts index 89abf7009..bd343ecd0 100644 --- a/src/addon/mod/data/fields/picture/providers/handler.ts +++ b/src/addon/mod/data/fields/picture/providers/handler.ts @@ -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/providers/handler.ts b/src/addon/mod/data/fields/radiobutton/providers/handler.ts index a58b407bf..7025136e6 100644 --- a/src/addon/mod/data/fields/radiobutton/providers/handler.ts +++ b/src/addon/mod/data/fields/radiobutton/providers/handler.ts @@ -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/text/providers/handler.ts b/src/addon/mod/data/fields/text/providers/handler.ts index 1a4fa8c26..7ede2628e 100644 --- a/src/addon/mod/data/fields/text/providers/handler.ts +++ b/src/addon/mod/data/fields/text/providers/handler.ts @@ -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/textarea/component/textarea.ts b/src/addon/mod/data/fields/textarea/component/textarea.ts index a988de521..176601718 100644 --- a/src/addon/mod/data/fields/textarea/component/textarea.ts +++ b/src/addon/mod/data/fields/textarea/component/textarea.ts @@ -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..4e41f69a3 100644 --- a/src/addon/mod/data/fields/textarea/providers/handler.ts +++ b/src/addon/mod/data/fields/textarea/providers/handler.ts @@ -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/url/providers/handler.ts b/src/addon/mod/data/fields/url/providers/handler.ts index 1854df829..f3774f547 100644 --- a/src/addon/mod/data/fields/url/providers/handler.ts +++ b/src/addon/mod/data/fields/url/providers/handler.ts @@ -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/pages/edit/edit.ts b/src/addon/mod/data/pages/edit/edit.ts index babfa1279..2d82e9f43 100644 --- a/src/addon/mod/data/pages/edit/edit.ts +++ b/src/addon/mod/data/pages/edit/edit.ts @@ -93,7 +93,7 @@ export class AddonModDataEditPage { /** * Check if we can leave the page or not and ask to confirm the lost of data. * - * @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 || !this.entry) { @@ -122,7 +122,7 @@ export class AddonModDataEditPage { /** * Fetch the entry data. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected fetchEntryData(): Promise { return this.dataProvider.getDatabase(this.courseId, this.module.id).then((data) => { @@ -159,8 +159,8 @@ export class AddonModDataEditPage { /** * Saves data. * - * @param {Event} e Event. - * @return {Promise} Resolved when done. + * @param e Event. + * @return Resolved when done. */ save(e: Event): Promise { e.preventDefault(); @@ -256,8 +256,8 @@ export class AddonModDataEditPage { /** * Set group to see the database. * - * @param {number} groupId Group identifier to set. - * @return {Promise} Resolved when done. + * @param groupId Group identifier to set. + * @return Resolved when done. */ setGroup(groupId: number): Promise { this.selectedGroup = groupId; @@ -269,7 +269,7 @@ export class AddonModDataEditPage { /** * Displays Edit Search Fields. * - * @return {string} Generated HTML. + * @return Generated HTML. */ protected displayEditFields(): string { this.jsData = { @@ -315,7 +315,7 @@ export class AddonModDataEditPage { /** * Return to the entry list (previous page) discarding temp data. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected returnToEntryList(): Promise { const inputData = this.editForm.value; diff --git a/src/addon/mod/data/pages/entry/entry.ts b/src/addon/mod/data/pages/entry/entry.ts index 95f96d8fc..eec7f7367 100644 --- a/src/addon/mod/data/pages/entry/entry.ts +++ b/src/addon/mod/data/pages/entry/entry.ts @@ -129,9 +129,9 @@ export class AddonModDataEntryPage implements OnDestroy { /** * Fetch the entry data. * - * @param {boolean} [refresh] Whether to refresh the current data or not. - * @param {boolean} [isPtr] Whether is a pull to refresh action. - * @return {Promise} Resolved when done. + * @param refresh Whether to refresh the current data or not. + * @param isPtr Whether is a pull to refresh action. + * @return Resolved when done. */ protected fetchEntryData(refresh?: boolean, isPtr?: boolean): Promise { this.isPullingToRefresh = isPtr; @@ -190,8 +190,8 @@ export class AddonModDataEntryPage implements OnDestroy { /** * Go to selected entry without changing state. * - * @param {number} offset Entry offset. - * @return {Promise} Resolved when done. + * @param offset Entry offset. + * @return Resolved when done. */ gotoEntry(offset: number): Promise { this.offset = offset; @@ -205,8 +205,8 @@ export class AddonModDataEntryPage implements OnDestroy { /** * Refresh all the data. * - * @param {boolean} [isPtr] Whether is a pull to refresh action. - * @return {Promise} Promise resolved when done. + * @param isPtr Whether is a pull to refresh action. + * @return Promise resolved when done. */ protected refreshAllData(isPtr?: boolean): Promise { const promises = []; @@ -233,8 +233,8 @@ export class AddonModDataEntryPage implements OnDestroy { /** * Refresh the data. * - * @param {any} [refresher] Refresher. - * @return {Promise} Promise resolved when done. + * @param refresher Refresher. + * @return Promise resolved when done. */ refreshDatabase(refresher?: any): Promise { if (this.entryLoaded) { @@ -247,8 +247,8 @@ export class AddonModDataEntryPage implements OnDestroy { /** * Set group to see the database. * - * @param {number} groupId Group identifier to set. - * @return {Promise} Resolved when done. + * @param groupId Group identifier to set. + * @return Resolved when done. */ setGroup(groupId: number): Promise { this.selectedGroup = groupId; @@ -263,7 +263,7 @@ export class AddonModDataEntryPage implements OnDestroy { /** * Convenience function to fetch the entry and set next/previous entries. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected setEntryFromOffset(): Promise { const emptyOffset = typeof this.offset != 'number'; diff --git a/src/addon/mod/data/pages/index/index.ts b/src/addon/mod/data/pages/index/index.ts index f415352a4..4ddf93488 100644 --- a/src/addon/mod/data/pages/index/index.ts +++ b/src/addon/mod/data/pages/index/index.ts @@ -42,7 +42,7 @@ export class AddonModDataIndexPage { /** * Update some data based on the data instance. * - * @param {any} data Data instance. + * @param data Data instance. */ updateData(data: any): void { this.title = data.name || this.title; diff --git a/src/addon/mod/data/pages/search/search.ts b/src/addon/mod/data/pages/search/search.ts index e61153d3c..bad3b39c5 100644 --- a/src/addon/mod/data/pages/search/search.ts +++ b/src/addon/mod/data/pages/search/search.ts @@ -82,7 +82,7 @@ export class AddonModDataSearchPage { /** * Displays Advanced Search Fields. * - * @return {string} Generated HTML. + * @return Generated HTML. */ protected renderAdvancedSearchFields(): string { this.jsData = { @@ -130,8 +130,8 @@ export class AddonModDataSearchPage { /** * Retrieve the entered data in search in a form. * - * @param {any} searchedData Array with the entered form values. - * @return {any[]} Array with the answers. + * @param searchedData Array with the entered form values. + * @return Array with the answers. */ getSearchDataFromForm(searchedData: any): any[] { const advancedSearch = []; @@ -172,7 +172,7 @@ export class AddonModDataSearchPage { /** * Close modal. * - * @param {any} [data] Data to return to the page. + * @param data Data to return to the page. */ closeModal(data?: any): void { this.viewCtrl.dismiss(data); @@ -181,7 +181,7 @@ export class AddonModDataSearchPage { /** * Toggles between advanced to normal search. * - * @param {boolean} advanced True for advanced, false for basic. + * @param advanced True for advanced, false for basic. */ changeAdvanced(advanced: boolean): void { this.search.searchingAdvanced = advanced; @@ -190,7 +190,7 @@ export class AddonModDataSearchPage { /** * Done editing. * - * @param {Event} e Event. + * @param e Event. */ searchEntries(e: Event): void { e.preventDefault(); diff --git a/src/addon/mod/data/providers/approve-link-handler.ts b/src/addon/mod/data/providers/approve-link-handler.ts index 5084b8fb3..bf4545516 100644 --- a/src/addon/mod/data/providers/approve-link-handler.ts +++ b/src/addon/mod/data/providers/approve-link-handler.ts @@ -35,11 +35,11 @@ export class AddonModDataApproveLinkHandler 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 { @@ -58,11 +58,11 @@ export class AddonModDataApproveLinkHandler 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 (typeof params.d == 'undefined' || (typeof params.approve == 'undefined' && typeof params.disapprove == 'undefined')) { diff --git a/src/addon/mod/data/providers/data.ts b/src/addon/mod/data/providers/data.ts index 63e53e364..98de9d3ae 100644 --- a/src/addon/mod/data/providers/data.ts +++ b/src/addon/mod/data/providers/data.ts @@ -106,15 +106,15 @@ export class AddonModDataProvider { /** * Adds a new entry to a database. * - * @param {number} dataId Data instance ID. - * @param {number} entryId EntryId or provisional entry ID when offline. - * @param {number} courseId Course ID. - * @param {any} contents The fields data to be created. - * @param {number} [groupId] Group id, 0 means that the function will determine the user group. - * @param {any[]} fields The fields that define the contents. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {boolean} [forceOffline] Force editing entry in offline. - * @return {Promise} Promise resolved when the action is done. + * @param dataId Data instance ID. + * @param entryId EntryId or provisional entry ID when offline. + * @param courseId Course ID. + * @param contents The fields data to be created. + * @param groupId Group id, 0 means that the function will determine the user group. + * @param fields The fields that define the contents. + * @param siteId Site ID. If not defined, current site. + * @param forceOffline Force editing entry in offline. + * @return Promise resolved when the action is done. */ addEntry(dataId: number, entryId: number, courseId: number, contents: AddonModDataSubfieldData[], groupId: number = 0, fields: any, siteId?: string, forceOffline: boolean = false): Promise { @@ -156,11 +156,11 @@ export class AddonModDataProvider { /** * Adds a new entry to a database. It does not cache calls. It will fail if offline or cannot connect. * - * @param {number} dataId Database ID. - * @param {any[]} data The fields data to be created. - * @param {number} [groupId] Group id, 0 means that the function will determine the user group. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the action is done. + * @param dataId Database ID. + * @param data The fields data to be created. + * @param groupId Group id, 0 means that the function will determine the user group. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the action is done. */ addEntryOnline(dataId: number, data: AddonModDataSubfieldData[], groupId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -180,12 +180,12 @@ export class AddonModDataProvider { /** * Approves or unapproves an entry. * - * @param {number} dataId Database ID. - * @param {number} entryId Entry ID. - * @param {boolean} approve Whether to approve (true) or unapprove the entry. - * @param {number} courseId Course ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the action is done. + * @param dataId Database ID. + * @param entryId Entry ID. + * @param approve Whether to approve (true) or unapprove the entry. + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the action is done. */ approveEntry(dataId: number, entryId: number, approve: boolean, courseId: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -225,10 +225,10 @@ export class AddonModDataProvider { /** * Approves or unapproves an entry. It does not cache calls. It will fail if offline or cannot connect. * - * @param {number} entryId Entry ID. - * @param {boolean} approve Whether to approve (true) or unapprove the entry. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the action is done. + * @param entryId Entry ID. + * @param approve Whether to approve (true) or unapprove the entry. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the action is done. */ approveEntryOnline(entryId: number, approve: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -244,9 +244,9 @@ export class AddonModDataProvider { /** * Convenience function to check fields requeriments here named "notifications". * - * @param {any} fields The fields that define the contents. - * @param {any} contents The contents data of the fields. - * @return {any} Array of notifications if any or false. + * @param fields The fields that define the contents. + * @param contents The contents data of the fields. + * @return Array of notifications if any or false. */ protected checkFields(fields: any, contents: AddonModDataSubfieldData[]): any[] | false { const notifications = [], @@ -277,11 +277,11 @@ export class AddonModDataProvider { /** * Deletes an entry. * - * @param {number} dataId Database ID. - * @param {number} entryId Entry ID. - * @param {number} courseId Course ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the action is done. + * @param dataId Database ID. + * @param entryId Entry ID. + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the action is done. */ deleteEntry(dataId: number, entryId: number, courseId: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -333,9 +333,9 @@ export class AddonModDataProvider { /** * Deletes an entry. It does not cache calls. It will fail if offline or cannot connect. * - * @param {number} entryId Entry ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the action is done. + * @param entryId Entry ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the action is done. */ deleteEntryOnline(entryId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -350,14 +350,14 @@ export class AddonModDataProvider { /** * Updates an existing entry. * - * @param {number} dataId Database ID. - * @param {number} entryId Entry ID. - * @param {number} courseId Course ID. - * @param {any[]} contents The contents data to be updated. - * @param {any} fields The fields that define the contents. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {boolean} forceOffline Force editing entry in offline. - * @return {Promise} Promise resolved when the action is done. + * @param dataId Database ID. + * @param entryId Entry ID. + * @param courseId Course ID. + * @param contents The contents data to be updated. + * @param fields The fields that define the contents. + * @param siteId Site ID. If not defined, current site. + * @param forceOffline Force editing entry in offline. + * @return Promise resolved when the action is done. */ editEntry(dataId: number, entryId: number, courseId: number, contents: AddonModDataSubfieldData[], fields: any, siteId?: string, forceOffline: boolean = false): Promise { @@ -433,10 +433,10 @@ export class AddonModDataProvider { /** * Updates an existing entry. It does not cache calls. It will fail if offline or cannot connect. * - * @param {number} entryId Entry ID. - * @param {any[]} data The fields data to be updated. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the action is done. + * @param entryId Entry ID. + * @param data The fields data to be updated. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the action is done. */ editEntryOnline(entryId: number, data: AddonModDataSubfieldData[], siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -452,16 +452,16 @@ export class AddonModDataProvider { /** * Performs the whole fetch of the entries in the database. * - * @param {number} dataId Data ID. - * @param {number} [groupId] Group ID. - * @param {string} [sort] Sort the records by this field id. See AddonModDataProvider#getEntries for more info. - * @param {string} [order] The direction of the sorting. See AddonModDataProvider#getEntries for more info. - * @param {number} [perPage] Records per page to fetch. It has to match with the prefetch. - * Default on AddonModDataProvider.PER_PAGE. - * @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false. - * @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 done. + * @param dataId Data ID. + * @param groupId Group ID. + * @param sort Sort the records by this field id. See AddonModDataProvider#getEntries for more info. + * @param order The direction of the sorting. See AddonModDataProvider#getEntries for more info. + * @param perPage Records per page to fetch. It has to match with the prefetch. + * Default on AddonModDataProvider.PER_PAGE. + * @param forceCache True to always get the value from cache, false otherwise. Default false. + * @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 done. */ fetchAllEntries(dataId: number, groupId: number = 0, sort: string = '0', order: string = 'DESC', perPage: number = AddonModDataProvider.PER_PAGE, forceCache: boolean = false, ignoreCache: boolean = false, @@ -474,17 +474,17 @@ export class AddonModDataProvider { /** * Recursive call on fetch all entries. * - * @param {number} dataId Data ID. - * @param {number} groupId Group ID. - * @param {string} sort Sort the records by this field id. See AddonModDataProvider#getEntries for more info. - * @param {string} order The direction of the sorting. See AddonModDataProvider#getEntries for more info. - * @param {number} perPage Records per page to fetch. It has to match with the prefetch. - * @param {boolean} forceCache True to always get the value from cache, false otherwise. Default false. - * @param {boolean} ignoreCache True if it should ignore cached data (it will always fail in offline or server down). - * @param {any} entries Entries already fetch (just to concatenate them). - * @param {number} page Page of records to return. - * @param {string} siteId Site ID. - * @return {Promise} Promise resolved when done. + * @param dataId Data ID. + * @param groupId Group ID. + * @param sort Sort the records by this field id. See AddonModDataProvider#getEntries for more info. + * @param order The direction of the sorting. See AddonModDataProvider#getEntries for more info. + * @param perPage Records per page to fetch. It has to match with the prefetch. + * @param forceCache True to always get the value from cache, false otherwise. Default false. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @param entries Entries already fetch (just to concatenate them). + * @param page Page of records to return. + * @param siteId Site ID. + * @return Promise resolved when done. */ protected fetchEntriesRecursive(dataId: number, groupId: number, sort: string, order: string, perPage: number, forceCache: boolean, ignoreCache: boolean, entries: any, page: number, siteId: string): Promise { @@ -505,8 +505,8 @@ export class AddonModDataProvider { /** * Get cache key for data data WS calls. * - * @param {number} courseId Course ID. - * @return {string} Cache key. + * @param courseId Course ID. + * @return Cache key. */ protected getDatabaseDataCacheKey(courseId: number): string { return this.ROOT_CACHE_KEY + 'data:' + courseId; @@ -515,8 +515,8 @@ export class AddonModDataProvider { /** * Get prefix cache key for all database activity data WS calls. * - * @param {number} dataId Data ID. - * @return {string} Cache key. + * @param dataId Data ID. + * @return Cache key. */ protected getDatabaseDataPrefixCacheKey(dataId: number): string { return this.ROOT_CACHE_KEY + dataId; @@ -525,12 +525,12 @@ export class AddonModDataProvider { /** * Get a database data. 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. - * @param {boolean} [forceCache=false] True to always get the value from cache, false otherwise. Default false. - * @return {Promise} Promise resolved when the data 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. + * @param forceCache True to always get the value from cache, false otherwise. Default false. + * @return Promise resolved when the data is retrieved. */ protected getDatabaseByKey(courseId: number, key: string, value: any, siteId?: string, forceCache: boolean = false): Promise { @@ -562,11 +562,11 @@ export class AddonModDataProvider { /** * Get a data 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. - * @param {boolean} [forceCache=false] True to always get the value from cache, false otherwise. Default false. - * @return {Promise} Promise resolved when the data is retrieved. + * @param courseId Course ID. + * @param cmId Course module ID. + * @param siteId Site ID. If not defined, current site. + * @param forceCache True to always get the value from cache, false otherwise. Default false. + * @return Promise resolved when the data is retrieved. */ getDatabase(courseId: number, cmId: number, siteId?: string, forceCache: boolean = false): Promise { return this.getDatabaseByKey(courseId, 'coursemodule', cmId, siteId, forceCache); @@ -575,11 +575,11 @@ export class AddonModDataProvider { /** * Get a data by ID. * - * @param {number} courseId Course ID. - * @param {number} id Data ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {boolean} [forceCache=false] True to always get the value from cache, false otherwise. Default false. - * @return {Promise} Promise resolved when the data is retrieved. + * @param courseId Course ID. + * @param id Data ID. + * @param siteId Site ID. If not defined, current site. + * @param forceCache True to always get the value from cache, false otherwise. Default false. + * @return Promise resolved when the data is retrieved. */ getDatabaseById(courseId: number, id: number, siteId?: string, forceCache: boolean = false): Promise { return this.getDatabaseByKey(courseId, 'id', id, siteId, forceCache); @@ -588,8 +588,8 @@ export class AddonModDataProvider { /** * Get prefix cache key for all database access information data WS calls. * - * @param {number} dataId Data ID. - * @return {string} Cache key. + * @param dataId Data ID. + * @return Cache key. */ protected getDatabaseAccessInformationDataPrefixCacheKey(dataId: number): string { return this.getDatabaseDataPrefixCacheKey(dataId) + ':access:'; @@ -598,9 +598,9 @@ export class AddonModDataProvider { /** * Get cache key for database access information data WS calls. * - * @param {number} dataId Data ID. - * @param {number} [groupId=0] Group ID. - * @return {string} Cache key. + * @param dataId Data ID. + * @param groupId Group ID. + * @return Cache key. */ protected getDatabaseAccessInformationDataCacheKey(dataId: number, groupId: number = 0): string { return this.getDatabaseAccessInformationDataPrefixCacheKey(dataId) + groupId; @@ -609,12 +609,12 @@ export class AddonModDataProvider { /** * Get access information for a given database. * - * @param {number} dataId Data ID. - * @param {number} [groupId] Group ID. - * @param {boolean} [offline=false] True if it should return cached data. Has priority over ignoreCache. - * @param {boolean} [ignoreCache=false] True if it should ignore cached data (it'll always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the database is retrieved. + * @param dataId Data ID. + * @param groupId Group ID. + * @param offline True if it should return cached data. Has priority over ignoreCache. + * @param ignoreCache True if it should ignore cached data (it'll always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the database is retrieved. */ getDatabaseAccessInformation(dataId: number, groupId?: number, offline: boolean = false, ignoreCache: boolean = false, siteId?: string): Promise { @@ -644,23 +644,23 @@ export class AddonModDataProvider { /** * Get entries for a specific database and group. * - * @param {number} dataId Data ID. - * @param {number} [groupId=0] Group ID. - * @param {string} [sort=0] Sort the records by this field id, reserved ids are: - * 0: timeadded - * -1: firstname - * -2: lastname - * -3: approved - * -4: timemodified. - * Empty for using the default database setting. - * @param {string} [order=DESC] The direction of the sorting: 'ASC' or 'DESC'. - * Empty for using the default database setting. - * @param {number} [page=0] Page of records to return. - * @param {number} [perPage=PER_PAGE] Records per page to return. Default on PER_PAGE. - * @param {boolean} [forceCache=false] True to always get the value from cache, false otherwise. Default false. - * @param {boolean} [ignoreCache=false] True if it should ignore cached data (it'll always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the database is retrieved. + * @param dataId Data ID. + * @param groupId Group ID. + * @param sort Sort the records by this field id, reserved ids are: + * 0: timeadded + * -1: firstname + * -2: lastname + * -3: approved + * -4: timemodified. + * Empty for using the default database setting. + * @param order The direction of the sorting: 'ASC' or 'DESC'. + * Empty for using the default database setting. + * @param page Page of records to return. + * @param perPage Records per page to return. Default on PER_PAGE. + * @param forceCache True to always get the value from cache, false otherwise. Default false. + * @param ignoreCache True if it should ignore cached data (it'll always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the database is retrieved. */ getEntries(dataId: number, groupId: number = 0, sort: string = '0', order: string = 'DESC', page: number = 0, perPage: number = AddonModDataProvider.PER_PAGE, forceCache: boolean = false, ignoreCache: boolean = false, @@ -701,9 +701,9 @@ export class AddonModDataProvider { /** * Get cache key for database entries data WS calls. * - * @param {number} dataId Data ID. - * @param {number} [groupId=0] Group ID. - * @return {string} Cache key. + * @param dataId Data ID. + * @param groupId Group ID. + * @return Cache key. */ protected getEntriesCacheKey(dataId: number, groupId: number = 0): string { return this.getEntriesPrefixCacheKey(dataId) + groupId; @@ -712,8 +712,8 @@ export class AddonModDataProvider { /** * Get prefix cache key for database all entries data WS calls. * - * @param {number} dataId Data ID. - * @return {string} Cache key. + * @param dataId Data ID. + * @return Cache key. */ protected getEntriesPrefixCacheKey(dataId: number): string { return this.getDatabaseDataPrefixCacheKey(dataId) + ':entries:'; @@ -722,11 +722,11 @@ export class AddonModDataProvider { /** * Get an entry of the database activity. * - * @param {number} dataId Data ID for caching purposes. - * @param {number} entryId Entry ID. - * @param {boolean} [ignoreCache=false] True if it should ignore cached data (it'll always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise<{entry: AddonModDataEntry, ratinginfo: CoreRatingInfo}>} Promise resolved when the entry is retrieved. + * @param dataId Data ID for caching purposes. + * @param entryId Entry ID. + * @param ignoreCache True if it should ignore cached data (it'll always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the entry is retrieved. */ getEntry(dataId: number, entryId: number, ignoreCache: boolean = false, siteId?: string): Promise<{entry: AddonModDataEntry, ratinginfo: CoreRatingInfo}> { @@ -756,9 +756,9 @@ export class AddonModDataProvider { /** * Get cache key for database entry data WS calls. * - * @param {number} dataId Data ID for caching purposes. - * @param {number} entryId Entry ID. - * @return {string} Cache key. + * @param dataId Data ID for caching purposes. + * @param entryId Entry ID. + * @return Cache key. */ protected getEntryCacheKey(dataId: number, entryId: number): string { return this.getDatabaseDataPrefixCacheKey(dataId) + ':entry:' + entryId; @@ -767,11 +767,11 @@ export class AddonModDataProvider { /** * Get the list of configured fields for the given database. * - * @param {number} dataId Data ID. - * @param {boolean} [forceCache=false] True to always get the value from cache, false otherwise. Default false. - * @param {boolean} [ignoreCache=false] 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 fields are retrieved. + * @param dataId Data ID. + * @param forceCache True to always get the value from cache, false otherwise. Default false. + * @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 fields are retrieved. */ getFields(dataId: number, forceCache: boolean = false, ignoreCache: boolean = false, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -803,8 +803,8 @@ export class AddonModDataProvider { /** * Get cache key for database fields data WS calls. * - * @param {number} dataId Data ID. - * @return {string} Cache key. + * @param dataId Data ID. + * @return Cache key. */ protected getFieldsCacheKey(dataId: number): string { return this.getDatabaseDataPrefixCacheKey(dataId) + ':fields'; @@ -814,10 +814,10 @@ export class AddonModDataProvider { * Invalidate the prefetched content. * To invalidate files, use AddonModDataProvider#invalidateFiles. * - * @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(); @@ -842,9 +842,9 @@ export class AddonModDataProvider { /** * Invalidates database access information data. * - * @param {number} dataId Data ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param dataId Data ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateDatabaseAccessInformationData(dataId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -855,9 +855,9 @@ export class AddonModDataProvider { /** * Invalidates database entries data. * - * @param {number} dataId Data ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param dataId Data ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateEntriesData(dataId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -868,9 +868,9 @@ export class AddonModDataProvider { /** * 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, AddonModDataProvider.COMPONENT, moduleId); @@ -879,9 +879,9 @@ export class AddonModDataProvider { /** * Invalidates database 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. */ invalidateDatabaseData(courseId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -892,9 +892,9 @@ export class AddonModDataProvider { /** * Invalidates database data except files and module info. * - * @param {number} databaseId Data ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param databaseId Data ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateDatabaseWSData(databaseId: number, siteId: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -905,10 +905,10 @@ export class AddonModDataProvider { /** * Invalidates database entry data. * - * @param {number} dataId Data ID for caching purposes. - * @param {number} entryId Entry ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param dataId Data ID for caching purposes. + * @param entryId Entry ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateEntryData(dataId: number, entryId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -919,8 +919,8 @@ export class AddonModDataProvider { /** * Return whether or not the plugin is enabled in a certain site. Plugin is enabled if the database WS are available. * - * @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. * @since 3.3 */ isPluginEnabled(siteId?: string): Promise { @@ -932,10 +932,10 @@ export class AddonModDataProvider { /** * Report the database as being viewed. * - * @param {number} id Module ID. - * @param {string} [name] Name of the data. - * @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 data. + * @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 = { @@ -949,16 +949,16 @@ export class AddonModDataProvider { /** * Performs search over a database. * - * @param {number} dataId The data instance id. - * @param {number} [groupId=0] Group id, 0 means that the function will determine the user group. - * @param {string} [search] Search text. It will be used if advSearch is not defined. - * @param {any[]} [advSearch] Advanced search data. - * @param {string} [sort] Sort by this field. - * @param {string} [order] The direction of the sorting. - * @param {number} [page=0] Page of records to return. - * @param {number} [perPage=PER_PAGE] Records per page to return. Default on AddonModDataProvider.PER_PAGE. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the action is done. + * @param dataId The data instance id. + * @param groupId Group id, 0 means that the function will determine the user group. + * @param search Search text. It will be used if advSearch is not defined. + * @param advSearch Advanced search data. + * @param sort Sort by this field. + * @param order The direction of the sorting. + * @param page Page of records to return. + * @param perPage Records per page to return. Default on AddonModDataProvider.PER_PAGE. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the action is done. */ searchEntries(dataId: number, groupId: number = 0, search?: string, advSearch?: any, sort?: string, order?: string, page: number = 0, perPage: number = AddonModDataProvider.PER_PAGE, siteId?: string): Promise { diff --git a/src/addon/mod/data/providers/default-field-handler.ts b/src/addon/mod/data/providers/default-field-handler.ts index 2160d8a9e..d28bba2c8 100644 --- a/src/addon/mod/data/providers/default-field-handler.ts +++ b/src/addon/mod/data/providers/default-field-handler.ts @@ -25,9 +25,9 @@ export class AddonModDataDefaultFieldHandler 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 { return false; @@ -36,9 +36,9 @@ export class AddonModDataDefaultFieldHandler 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 { return false; @@ -47,10 +47,10 @@ export class AddonModDataDefaultFieldHandler 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 { return false; @@ -59,8 +59,8 @@ export class AddonModDataDefaultFieldHandler 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, inputData: any, originalFieldData: any): any { return []; @@ -69,9 +69,9 @@ export class AddonModDataDefaultFieldHandler 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 { return false; @@ -80,10 +80,10 @@ export class AddonModDataDefaultFieldHandler 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 { return originalContent; @@ -92,7 +92,7 @@ export class AddonModDataDefaultFieldHandler 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/providers/delete-link-handler.ts b/src/addon/mod/data/providers/delete-link-handler.ts index 8da37ba6b..64a585026 100644 --- a/src/addon/mod/data/providers/delete-link-handler.ts +++ b/src/addon/mod/data/providers/delete-link-handler.ts @@ -35,11 +35,11 @@ export class AddonModDataDeleteLinkHandler 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 { @@ -57,11 +57,11 @@ export class AddonModDataDeleteLinkHandler 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 (typeof params.d == 'undefined' || typeof params.delete == 'undefined') { diff --git a/src/addon/mod/data/providers/edit-link-handler.ts b/src/addon/mod/data/providers/edit-link-handler.ts index b6857f3ab..8d724b864 100644 --- a/src/addon/mod/data/providers/edit-link-handler.ts +++ b/src/addon/mod/data/providers/edit-link-handler.ts @@ -38,11 +38,11 @@ export class AddonModDataEditLinkHandler 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 { @@ -75,11 +75,11 @@ export class AddonModDataEditLinkHandler 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 (typeof params.d == 'undefined') { diff --git a/src/addon/mod/data/providers/fields-delegate.ts b/src/addon/mod/data/providers/fields-delegate.ts index 544d9f349..0c935d7e9 100644 --- a/src/addon/mod/data/providers/fields-delegate.ts +++ b/src/addon/mod/data/providers/fields-delegate.ts @@ -27,7 +27,6 @@ export interface AddonModDataFieldHandler extends CoreDelegateHandler { /** * Name of the type of data field the handler supports. E.g. 'checkbox'. - * @type {string} */ type: string; @@ -35,64 +34,64 @@ export interface AddonModDataFieldHandler extends CoreDelegateHandler { * 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; /** * 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; /** * 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; /** * 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; /** * 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, inputData: any, originalFieldData: any): any; /** * 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; /** * 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; } @@ -113,9 +112,9 @@ export class AddonModDataFieldsDelegate extends CoreDelegate { /** * Get the component to use for a certain field field. * - * @param {Injector} injector Injector. - * @param {any} field The field object. - * @return {Promise} Promise resolved with the component to use, undefined if not found. + * @param injector Injector. + * @param field The field object. + * @return Promise resolved with the component to use, undefined if not found. */ getComponentForField(injector: Injector, field: any): Promise { return Promise.resolve(this.executeFunctionOnEnabled(field.type, 'getComponent', [injector, field])); @@ -124,9 +123,9 @@ export class AddonModDataFieldsDelegate extends CoreDelegate { /** * Get database data in the input data to search. * - * @param {any} field Defines the field to be rendered. - * @param {any} inputData Data entered in the search form. - * @return {any} Name and data field. + * @param field Defines the field to be rendered. + * @param inputData Data entered in the search form. + * @return Name and data field. */ getFieldSearchData(field: any, inputData: any): any { return this.executeFunctionOnEnabled(field.type, 'getFieldSearchData', [field, inputData]); @@ -135,10 +134,10 @@ export class AddonModDataFieldsDelegate extends CoreDelegate { /** * Get database data in the input data to add or update entry. * - * @param {any} field Defines the field to be rendered. - * @param {any} inputData Data entered in the search form. - * @param {any} originalFieldData Original field entered data. - * @return {any} Name and data field. + * @param field Defines the field to be rendered. + * @param inputData Data entered in the search form. + * @param originalFieldData Original field entered data. + * @return Name and data field. */ getFieldEditData(field: any, inputData: any, originalFieldData: any): any { return this.executeFunctionOnEnabled(field.type, 'getFieldEditData', [field, inputData, originalFieldData]); @@ -147,10 +146,10 @@ export class AddonModDataFieldsDelegate extends CoreDelegate { /** * Get database data in the input files to add or update entry. * - * @param {any} field Defines the field to be rendered. - * @param {any} inputData Data entered in the search form. - * @param {any} originalFieldData Original field entered data. - * @return {any} Name and data field. + * @param field Defines the field to be rendered. + * @param inputData Data entered in the search form. + * @param originalFieldData Original field entered data. + * @return Name and data field. */ getFieldEditFiles(field: any, inputData: any, originalFieldData: any): any { return this.executeFunctionOnEnabled(field.type, 'getFieldEditFiles', [field, inputData, originalFieldData]); @@ -159,9 +158,9 @@ export class AddonModDataFieldsDelegate extends CoreDelegate { /** * 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} 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 { return this.executeFunctionOnEnabled(field.type, 'getFieldsNotifications', [field, inputData]); @@ -170,8 +169,8 @@ export class AddonModDataFieldsDelegate extends CoreDelegate { /** * Check if field type manage files or not. * - * @param {any} field Defines the field to be checked. - * @return {boolean} If the field type manages files. + * @param field Defines the field to be checked. + * @return If the field type manages files. */ hasFiles(field: any): boolean { return this.hasFunction(field.type, 'getFieldEditFiles'); @@ -180,10 +179,10 @@ export class AddonModDataFieldsDelegate extends CoreDelegate { /** * Check if the data has changed for a certain field. * - * @param {any} field Defines the field to be rendered. - * @param {any} inputData Data entered in the search form. - * @param {any} originalFieldData Original field entered data. - * @return {Promise} Promise rejected if has changed, resolved if no changes. + * @param field Defines the field to be rendered. + * @param inputData Data entered in the search form. + * @param originalFieldData Original field entered data. + * @return Promise rejected if has changed, resolved if no changes. */ hasFieldDataChanged(field: any, inputData: any, originalFieldData: any): Promise { return Promise.resolve(this.executeFunctionOnEnabled(field.type, 'hasFieldDataChanged', @@ -195,8 +194,8 @@ export class AddonModDataFieldsDelegate extends CoreDelegate { /** * Check if a field plugin is supported. * - * @param {string} pluginType Type of the plugin. - * @return {boolean} True if supported, false otherwise. + * @param pluginType Type of the plugin. + * @return True if supported, false otherwise. */ isPluginSupported(pluginType: string): boolean { return this.hasHandler(pluginType, true); @@ -205,11 +204,11 @@ export class AddonModDataFieldsDelegate extends CoreDelegate { /** * Override field content data with offline submission. * - * @param {any} field Defines the field to be rendered. - * @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 field Defines the field to be rendered. + * @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(field: any, originalContent: any, offlineContent: any, offlineFiles?: any): any { originalContent = originalContent || {}; diff --git a/src/addon/mod/data/providers/helper.ts b/src/addon/mod/data/providers/helper.ts index 2938c6c72..0bcb81bbe 100644 --- a/src/addon/mod/data/providers/helper.ts +++ b/src/addon/mod/data/providers/helper.ts @@ -43,10 +43,10 @@ export class AddonModDataHelperProvider { /** * Returns the record with the offline actions applied. * - * @param {AddonModDataEntry} record Entry to modify. - * @param {AddonModDataOfflineAction[]} offlineActions Offline data with the actions done. - * @param {any[]} fields Entry defined fields indexed by fieldid. - * @return {Promise} Promise resolved when done. + * @param record Entry to modify. + * @param offlineActions Offline data with the actions done. + * @param fields Entry defined fields indexed by fieldid. + * @return Promise resolved when done. */ applyOfflineActions(record: AddonModDataEntry, offlineActions: AddonModDataOfflineAction[], fields: any[]): Promise { @@ -113,11 +113,11 @@ export class AddonModDataHelperProvider { /** * Approve or disapprove a database entry. * - * @param {number} dataId Database ID. - * @param {number} entryId Entry ID. - * @param {boolaen} approve True to approve, false to disapprove. - * @param {number} [courseId] Course ID. It not defined, it will be fetched. - * @param {string} [siteId] Site ID. If not defined, current site. + * @param dataId Database ID. + * @param entryId Entry ID. + * @param approve True to approve, false to disapprove. + * @param courseId Course ID. It not defined, it will be fetched. + * @param siteId Site ID. If not defined, current site. */ approveOrDisapproveEntry(dataId: number, entryId: number, approve: boolean, courseId?: number, siteId?: string): void { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -153,13 +153,13 @@ export class AddonModDataHelperProvider { /** * Displays fields for being shown. * - * @param {string} template Template HMTL. - * @param {any[]} fields Fields that defines every content in the entry. - * @param {any} entry Entry. - * @param {number} offset Entry offset. - * @param {string} mode Mode list or show. - * @param {{[name: string]: boolean}} actions Actions that can be performed to the record. - * @return {string} Generated HTML. + * @param template Template HMTL. + * @param fields Fields that defines every content in the entry. + * @param entry Entry. + * @param offset Entry offset. + * @param mode Mode list or show. + * @param actions Actions that can be performed to the record. + * @return Generated HTML. */ displayShowFields(template: string, fields: any[], entry: any, offset: number, mode: string, actions: {[name: string]: boolean}): string { @@ -208,24 +208,24 @@ export class AddonModDataHelperProvider { /** * Get online and offline entries, or search entries. * - * @param {any} data Database object. - * @param {any[]} fields The fields that define the contents. - * @param {number} [groupId=0] Group ID. - * @param {string} [search] Search text. It will be used if advSearch is not defined. - * @param {any[]} [advSearch] Advanced search data. - * @param {string} [sort=0] Sort the records by this field id, reserved ids are: - * 0: timeadded - * -1: firstname - * -2: lastname - * -3: approved - * -4: timemodified. - * Empty for using the default database setting. - * @param {string} [order=DESC] The direction of the sorting: 'ASC' or 'DESC'. - * Empty for using the default database setting. - * @param {number} [page=0] Page of records to return. - * @param {number} [perPage=PER_PAGE] Records per page to return. Default on PER_PAGE. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the database is retrieved. + * @param data Database object. + * @param fields The fields that define the contents. + * @param groupId Group ID. + * @param search Search text. It will be used if advSearch is not defined. + * @param advSearch Advanced search data. + * @param sort Sort the records by this field id, reserved ids are: + * 0: timeadded + * -1: firstname + * -2: lastname + * -3: approved + * -4: timemodified. + * Empty for using the default database setting. + * @param order The direction of the sorting: 'ASC' or 'DESC'. + * Empty for using the default database setting. + * @param page Page of records to return. + * @param perPage Records per page to return. Default on PER_PAGE. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the database is retrieved. */ fetchEntries(data: any, fields: any[], groupId: number = 0, search?: string, advSearch?: any[], sort: string = '0', order: string = 'DESC', page: number = 0, perPage: number = AddonModDataProvider.PER_PAGE, siteId?: string): @@ -310,11 +310,11 @@ export class AddonModDataHelperProvider { /** * Fetch an online or offline entry. * - * @param {any} data Database. - * @param {any[]} fields List of database fields. - * @param {number} entryId Entry ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise<{entry: AddonModDataEntry, ratinginfo?: CoreRatingInfo}>} Promise resolved with the entry. + * @param data Database. + * @param fields List of database fields. + * @param entryId Entry ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the entry. */ fetchEntry(data: any, fields: any[], entryId: number, siteId?: string): Promise<{entry: AddonModDataEntry, ratinginfo?: CoreRatingInfo}> { @@ -355,10 +355,10 @@ export class AddonModDataHelperProvider { /** * Returns an object with all the actions that the user can do over the record. * - * @param {any} database Database activity. - * @param {any} accessInfo Access info to the activity. - * @param {any} record Entry or record where the actions will be performed. - * @return {{[name: string]: boolean}} Keyed with the action names and boolean to evalute if it can or cannot be done. + * @param database Database activity. + * @param accessInfo Access info to the activity. + * @param record Entry or record where the actions will be performed. + * @return Keyed with the action names and boolean to evalute if it can or cannot be done. */ getActions(database: any, accessInfo: any, record: any): {[name: string]: boolean} { return { @@ -387,10 +387,10 @@ export class AddonModDataHelperProvider { /** * Convenience function to get the course id of the database. * - * @param {number} dataId Database id. - * @param {number} [courseId] Course id, if known. - * @param {string} [siteId] Site id, if not set, current site will be used. - * @return {Promise} Resolved with course Id when done. + * @param dataId Database id. + * @param courseId Course id, if known. + * @param siteId Site id, if not set, current site will be used. + * @return Resolved with course Id when done. */ protected getActivityCourseIdIfNotSet(dataId: number, courseId?: number, siteId?: string): Promise { if (courseId) { @@ -407,9 +407,9 @@ export class AddonModDataHelperProvider { * * Based on Moodle function data_generate_default_template. * - * @param {string} type Type of template. - * @param {any[]} fields List of database fields. - * @return {string} Template HTML. + * @param type Type of template. + * @param fields List of database fields. + * @return Template HTML. */ getDefaultTemplate(type: string, fields: any[]): string { if (type == 'listtemplateheader' || type == 'listtemplatefooter') { @@ -483,14 +483,14 @@ export class AddonModDataHelperProvider { * Retrieve the entered data in the edit form. * We don't use ng-model because it doesn't detect changes done by JavaScript. * - * @param {any} inputData Array with the entered form values. - * @param {Array} fields Fields that defines every content in the entry. - * @param {number} [dataId] Database Id. If set, files will be uploaded and itemId set. - * @param {number} entryId Entry Id. - * @param {AddonModDataEntryFields} entryContents Original entry contents. - * @param {boolean} offline True to prepare the data for an offline uploading, false otherwise. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} That contains object with the answers. + * @param inputData Array with the entered form values. + * @param fields Fields that defines every content in the entry. + * @param dataId Database Id. If set, files will be uploaded and itemId set. + * @param entryId Entry Id. + * @param entryContents Original entry contents. + * @param offline True to prepare the data for an offline uploading, false otherwise. + * @param siteId Site ID. If not defined, current site. + * @return That contains object with the answers. */ getEditDataFromForm(inputData: any, fields: any, dataId: number, entryId: number, entryContents: AddonModDataEntryFields, offline: boolean = false, siteId?: string): Promise { @@ -549,11 +549,11 @@ export class AddonModDataHelperProvider { /** * Retrieve the temp files to be updated. * - * @param {any} inputData Array with the entered form values. - * @param {any[]} fields Fields that defines every content in the entry. - * @param {number} [dataId] Database Id. If set, fils will be uploaded and itemId set. - * @param {AddonModDataEntryFields} entryContents Original entry contents indexed by field id. - * @return {Promise} That contains object with the files. + * @param inputData Array with the entered form values. + * @param fields Fields that defines every content in the entry. + * @param dataId Database Id. If set, fils will be uploaded and itemId set. + * @param entryContents Original entry contents indexed by field id. + * @return That contains object with the files. */ getEditTmpFiles(inputData: any, fields: any[], dataId: number, entryContents: AddonModDataEntryFields): Promise { if (!inputData) { @@ -573,11 +573,11 @@ export class AddonModDataHelperProvider { /** * Get a list of stored attachment files for a new entry. See $mmaModDataHelper#storeFiles. * - * @param {number} dataId Database ID. - * @param {number} entryId Entry ID or, if creating, timemodified. - * @param {number} fieldId Field ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the files. + * @param dataId Database ID. + * @param entryId Entry ID or, if creating, timemodified. + * @param fieldId Field ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the files. */ getStoredFiles(dataId: number, entryId: number, fieldId: number, siteId?: string): Promise { return this.dataOffline.getEntryFieldFolder(dataId, entryId, fieldId, siteId).then((folderPath) => { @@ -591,10 +591,10 @@ export class AddonModDataHelperProvider { /** * Returns the template of a certain type. * - * @param {any} data Database object. - * @param {string} type Type of template. - * @param {any[]} fields List of database fields. - * @return {string} Template HTML. + * @param data Database object. + * @param type Type of template. + * @param fields List of database fields. + * @return Template HTML. */ getTemplate(data: any, type: string, fields: any[]): string { let template = data[type] || this.getDefaultTemplate(type, fields); @@ -613,11 +613,11 @@ export class AddonModDataHelperProvider { /** * Check if data has been changed by the user. * - * @param {any} inputData Object with the entered form values. - * @param {any[]} fields Fields that defines every content in the entry. - * @param {number} [dataId] Database Id. If set, fils will be uploaded and itemId set. - * @param {AddonModDataEntryFields} entryContents Original entry contents indexed by field id. - * @return {Promise} True if changed, false if not. + * @param inputData Object with the entered form values. + * @param fields Fields that defines every content in the entry. + * @param dataId Database Id. If set, fils will be uploaded and itemId set. + * @param entryContents Original entry contents indexed by field id. + * @return True if changed, false if not. */ hasEditDataChanged(inputData: any, fields: any[], dataId: number, entryContents: AddonModDataEntryFields): Promise { const promises = fields.map((field) => { @@ -637,10 +637,10 @@ export class AddonModDataHelperProvider { /** * Displays a confirmation modal for deleting an entry. * - * @param {number} dataId Database ID. - * @param {number} entryId Entry ID. - * @param {number} [courseId] Course ID. It not defined, it will be fetched. - * @param {string} [siteId] Site ID. If not defined, current site. + * @param dataId Database ID. + * @param entryId Entry ID. + * @param courseId Course ID. It not defined, it will be fetched. + * @param siteId Site ID. If not defined, current site. */ showDeleteEntryModal(dataId: number, entryId: number, courseId?: number, siteId?: string): void { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -677,12 +677,12 @@ export class AddonModDataHelperProvider { * 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} dataId Database ID. - * @param {number} entryId Entry ID or, if creating, timemodified. - * @param {number} fieldId Field ID. - * @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 dataId Database ID. + * @param entryId Entry ID or, if creating, timemodified. + * @param fieldId Field ID. + * @param files List of files. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if success, rejected otherwise. */ storeFiles(dataId: number, entryId: number, fieldId: number, files: any[], siteId?: string): Promise { // Get the folder where to store the files. @@ -694,14 +694,14 @@ export class AddonModDataHelperProvider { /** * Upload or store some files, depending if the user is offline or not. * - * @param {number} dataId Database ID. - * @param {number} [itemId=0] Draft ID to use. Undefined or 0 to create a new draft ID. - * @param {number} entryId Entry ID or, if creating, timemodified. - * @param {number} fieldId Field ID. - * @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 dataId Database ID. + * @param itemId Draft ID to use. Undefined or 0 to create a new draft ID. + * @param entryId Entry ID or, if creating, timemodified. + * @param fieldId Field ID. + * @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(dataId: number, itemId: number = 0, entryId: number, fieldId: number, files: any[], offline: boolean, siteId?: string): Promise { diff --git a/src/addon/mod/data/providers/list-link-handler.ts b/src/addon/mod/data/providers/list-link-handler.ts index 6ca26006a..fa2ec5b8c 100644 --- a/src/addon/mod/data/providers/list-link-handler.ts +++ b/src/addon/mod/data/providers/list-link-handler.ts @@ -33,7 +33,7 @@ export class AddonModDataListLinkHandler extends CoreContentLinksModuleListHandl /** * Check if the handler is enabled on a site level. * - * @return {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(): Promise { return this.dataProvider.isPluginEnabled(); diff --git a/src/addon/mod/data/providers/module-handler.ts b/src/addon/mod/data/providers/module-handler.ts index 162a9923b..4e2297a32 100644 --- a/src/addon/mod/data/providers/module-handler.ts +++ b/src/addon/mod/data/providers/module-handler.ts @@ -47,7 +47,7 @@ export class AddonModDataModuleHandler implements CoreCourseModuleHandler { /** * Check if the handler is enabled on a site level. * - * @return {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(): Promise { return this.dataProvider.isPluginEnabled(); @@ -56,10 +56,10 @@ export class AddonModDataModuleHandler 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 { @@ -81,9 +81,9 @@ export class AddonModDataModuleHandler 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 AddonModDataIndexComponent; diff --git a/src/addon/mod/data/providers/offline.ts b/src/addon/mod/data/providers/offline.ts index 2d6d6ca4a..5eb640c3b 100644 --- a/src/addon/mod/data/providers/offline.ts +++ b/src/addon/mod/data/providers/offline.ts @@ -111,10 +111,10 @@ export class AddonModDataOfflineProvider { /** * Delete all the actions of an entry. * - * @param {number} dataId Database ID. - * @param {number} entryId Database entry ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if deleted, rejected if failure. + * @param dataId Database ID. + * @param entryId Database entry ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if deleted, rejected if failure. */ deleteAllEntryActions(dataId: number, entryId: number, siteId?: string): Promise { return this.getEntryActions(dataId, entryId, siteId).then((actions) => { @@ -131,11 +131,11 @@ export class AddonModDataOfflineProvider { /** * Delete an stored entry. * - * @param {number} dataId Database ID. - * @param {number} entryId Database entry Id. - * @param {string} action Action to be done - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if deleted, rejected if failure. + * @param dataId Database ID. + * @param entryId Database entry Id. + * @param action Action to be done + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if deleted, rejected if failure. */ deleteEntry(dataId: number, entryId: number, action: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -149,11 +149,11 @@ export class AddonModDataOfflineProvider { /** * Delete entry offline files. * - * @param {number} dataId Database ID. - * @param {number} entryId Database entry ID. - * @param {string} action Action to be done. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if deleted, rejected if failure. + * @param dataId Database ID. + * @param entryId Database entry ID. + * @param action Action to be done. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if deleted, rejected if failure. */ protected deleteEntryFiles(dataId: number, entryId: number, action: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -191,8 +191,8 @@ export class AddonModDataOfflineProvider { /** * Get all the stored entry data from all the databases. * - * @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. */ getAllEntries(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -205,9 +205,9 @@ export class AddonModDataOfflineProvider { /** * Get all the stored entry actions from a certain database, sorted by modification time. * - * @param {number} dataId Database ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with entries. + * @param dataId Database ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with entries. */ getDatabaseEntries(dataId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -220,11 +220,11 @@ export class AddonModDataOfflineProvider { /** * Get an stored entry data. * - * @param {number} dataId Database ID. - * @param {number} entryId Database entry Id. - * @param {string} action Action to be done - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with entry. + * @param dataId Database ID. + * @param entryId Database entry Id. + * @param action Action to be done + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with entry. */ getEntry(dataId: number, entryId: number, action: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -238,10 +238,10 @@ export class AddonModDataOfflineProvider { /** * Get an all stored entry actions data. * - * @param {number} dataId Database ID. - * @param {number} entryId Database entry Id. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with entry actions. + * @param dataId Database ID. + * @param entryId Database entry Id. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with entry actions. */ getEntryActions(dataId: number, entryId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -254,9 +254,9 @@ export class AddonModDataOfflineProvider { /** * Check if there are offline entries to send. * - * @param {number} dataId Database ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean: true if has offline answers, false otherwise. + * @param dataId Database ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: true if has offline answers, false otherwise. */ hasOfflineData(dataId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -269,9 +269,9 @@ export class AddonModDataOfflineProvider { /** * Get the path to the folder where to store files for offline files in a database. * - * @param {number} dataId Database ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the path. + * @param dataId Database ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the path. */ protected getDatabaseFolder(dataId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -286,11 +286,11 @@ export class AddonModDataOfflineProvider { /** * Get the path to the folder where to store files for a new offline entry. * - * @param {number} dataId Database ID. - * @param {number} entryId The ID of the entry. - * @param {number} fieldId Field ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the path. + * @param dataId Database ID. + * @param entryId The ID of the entry. + * @param fieldId Field ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the path. */ getEntryFieldFolder(dataId: number, entryId: number, fieldId: number, siteId?: string): Promise { return this.getDatabaseFolder(dataId, siteId).then((folderPath) => { @@ -301,8 +301,8 @@ export class AddonModDataOfflineProvider { /** * Parse "fields" of an offline record. * - * @param {any} record Record object - * @return {AddonModDataOfflineAction} Record object with columns parsed. + * @param record Record object + * @return Record object with columns parsed. */ protected parseRecord(record: any): AddonModDataOfflineAction { record.fields = this.textUtils.parseJSON(record.fields); @@ -313,15 +313,15 @@ export class AddonModDataOfflineProvider { /** * Save an entry data to be sent later. * - * @param {number} dataId Database ID. - * @param {number} entryId Database entry Id. If action is add entryId should be 0 and -timemodified will be used. - * @param {string} action Action to be done to the entry: [add, edit, delete, approve, disapprove] - * @param {number} courseId Course ID of the database. - * @param {number} [groupId] Group ID. Only provided when adding. - * @param {any[]} [fields] Array of field data of the entry if needed. - * @param {number} [timemodified] The time the entry was modified. If not defined, current time. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if stored, rejected if failure. + * @param dataId Database ID. + * @param entryId Database entry Id. If action is add entryId should be 0 and -timemodified will be used. + * @param action Action to be done to the entry: [add, edit, delete, approve, disapprove] + * @param courseId Course ID of the database. + * @param groupId Group ID. Only provided when adding. + * @param fields Array of field data of the entry if needed. + * @param timemodified The time the entry was modified. If not defined, current time. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if stored, rejected if failure. */ saveEntry(dataId: number, entryId: number, action: string, courseId: number, groupId?: number, fields?: AddonModDataSubfieldData[], timemodified?: number, siteId?: string): Promise { diff --git a/src/addon/mod/data/providers/prefetch-handler.ts b/src/addon/mod/data/providers/prefetch-handler.ts index eeb51206f..91635ea8b 100644 --- a/src/addon/mod/data/providers/prefetch-handler.ts +++ b/src/addon/mod/data/providers/prefetch-handler.ts @@ -51,12 +51,12 @@ export class AddonModDataPrefetchHandler extends CoreCourseActivityPrefetchHandl /** * Retrieves all the entries for all the groups and then returns only unique entries. * - * @param {number} dataId Database Id. - * @param {any[]} groups Array of groups in the activity. - * @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false. - * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. - * @return {Promise} All unique entries. + * @param dataId Database Id. + * @param groups Array of groups in the activity. + * @param forceCache True to always get the value from cache, false otherwise. Default false. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. + * @return All unique entries. */ protected getAllUniqueEntries(dataId: number, groups: any[], forceCache: boolean = false, ignoreCache: boolean = false, siteId?: string): Promise { @@ -81,13 +81,13 @@ export class AddonModDataPrefetchHandler extends CoreCourseActivityPrefetchHandl /** * Helper function to get all database info just once. * - * @param {any} module Module to get the files. - * @param {number} courseId Course ID the module belongs to. - * @param {boolean} [omitFail] True to always return even if fails. Default false. - * @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false. - * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). - * @param {string} siteId Site ID. - * @return {Promise} Promise resolved with the info fetched. + * @param module Module to get the files. + * @param courseId Course ID the module belongs to. + * @param omitFail True to always return even if fails. Default false. + * @param forceCache True to always get the value from cache, false otherwise. Default false. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @param siteId Site ID. + * @return Promise resolved with the info fetched. */ protected getDatabaseInfoHelper(module: any, courseId: number, omitFail: boolean = false, forceCache: boolean = false, ignoreCache: boolean = false, siteId?: string): Promise { @@ -138,8 +138,8 @@ export class AddonModDataPrefetchHandler extends CoreCourseActivityPrefetchHandl /** * Returns the file contained in the entries. * - * @param {AddonModDataEntry[]} entries List of entries to get files from. - * @return {any[]} List of files. + * @param entries List of entries to get files from. + * @return List of files. */ protected getEntriesFiles(entries: AddonModDataEntry[]): any[] { let files = []; @@ -156,10 +156,10 @@ export class AddonModDataPrefetchHandler extends CoreCourseActivityPrefetchHandl /** * Get the list of downloadable files. * - * @param {any} module Module to get the files. - * @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 to get the files. + * @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.getDatabaseInfoHelper(module, courseId, true).then((info) => { @@ -170,9 +170,9 @@ export class AddonModDataPrefetchHandler extends CoreCourseActivityPrefetchHandl /** * Returns data 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.dataProvider.getDatabase(courseId, module.id).catch(() => { @@ -185,9 +185,9 @@ export class AddonModDataPrefetchHandler extends CoreCourseActivityPrefetchHandl /** * 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.dataProvider.invalidateContent(moduleId, courseId); @@ -196,9 +196,9 @@ export class AddonModDataPrefetchHandler 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 { const promises = []; @@ -212,9 +212,9 @@ export class AddonModDataPrefetchHandler extends CoreCourseActivityPrefetchHandl * Check if a database is downloadable. * A database isn't downloadable if it's not open yet. * - * @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): boolean | Promise { return this.dataProvider.getDatabase(courseId, module.id, undefined, true).then((database) => { @@ -240,7 +240,7 @@ export class AddonModDataPrefetchHandler 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.dataProvider.isPluginEnabled(); @@ -249,11 +249,11 @@ export class AddonModDataPrefetchHandler 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 { return this.prefetchPackage(module, courseId, single, this.prefetchDatabase.bind(this)); @@ -262,11 +262,11 @@ export class AddonModDataPrefetchHandler extends CoreCourseActivityPrefetchHandl /** * Prefetch a database. * - * @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. - * @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. + * @return Promise resolved when done. */ protected prefetchDatabase(module: any, courseId: number, single: boolean, siteId: string): Promise { @@ -302,10 +302,10 @@ export class AddonModDataPrefetchHandler 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 { const promises = [ diff --git a/src/addon/mod/data/providers/show-link-handler.ts b/src/addon/mod/data/providers/show-link-handler.ts index 3685df23e..3418245b4 100644 --- a/src/addon/mod/data/providers/show-link-handler.ts +++ b/src/addon/mod/data/providers/show-link-handler.ts @@ -38,11 +38,11 @@ export class AddonModDataShowLinkHandler 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 { @@ -83,11 +83,11 @@ export class AddonModDataShowLinkHandler 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 (typeof params.d == 'undefined') { diff --git a/src/addon/mod/data/providers/sync-cron-handler.ts b/src/addon/mod/data/providers/sync-cron-handler.ts index c67e7a102..88fd2441d 100644 --- a/src/addon/mod/data/providers/sync-cron-handler.ts +++ b/src/addon/mod/data/providers/sync-cron-handler.ts @@ -29,9 +29,9 @@ export class AddonModDataSyncCronHandler 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.dataSync.syncAllDatabases(siteId, force); @@ -40,7 +40,7 @@ export class AddonModDataSyncCronHandler 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.dataSync.syncInterval; diff --git a/src/addon/mod/data/providers/sync.ts b/src/addon/mod/data/providers/sync.ts index 0553511c4..defc326f7 100644 --- a/src/addon/mod/data/providers/sync.ts +++ b/src/addon/mod/data/providers/sync.ts @@ -55,9 +55,9 @@ export class AddonModDataSyncProvider extends CoreSyncBaseProvider { /** * Check if a database has data to synchronize. * - * @param {number} dataId Database ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean: true if has data to sync, false otherwise. + * @param dataId Database ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: true if has data to sync, false otherwise. */ hasDataToSync(dataId: number, siteId?: string): Promise { return this.dataOffline.hasOfflineData(dataId, siteId); @@ -66,9 +66,9 @@ export class AddonModDataSyncProvider extends CoreSyncBaseProvider { /** * Try to synchronize all the databases 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. */ syncAllDatabases(siteId?: string, force?: boolean): Promise { return this.syncOnSites('all databases', this.syncAllDatabasesFunc.bind(this), [force], siteId); @@ -77,9 +77,9 @@ export class AddonModDataSyncProvider extends CoreSyncBaseProvider { /** * Sync all pending databases on a site. * - * @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. - * @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 force Wether to force sync not depending on last execution. + * @param Promise resolved if sync is successful, rejected if sync fails. */ protected syncAllDatabasesFunc(siteId?: string, force?: boolean): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -122,9 +122,9 @@ export class AddonModDataSyncProvider extends CoreSyncBaseProvider { /** * Sync a database only if a certain time has passed since the last time. * - * @param {number} dataId Database ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is synced or if it doesn't need to be synced. + * @param dataId Database ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is synced or if it doesn't need to be synced. */ syncDatabaseIfNeeded(dataId: number, siteId?: string): Promise { return this.isSyncNeeded(dataId, siteId).then((needed) => { @@ -137,9 +137,9 @@ export class AddonModDataSyncProvider extends CoreSyncBaseProvider { /** * Synchronize a data. * - * @param {number} dataId Data ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if sync is successful, rejected otherwise. + * @param dataId Data ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if sync is successful, rejected otherwise. */ syncDatabase(dataId: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -226,11 +226,11 @@ export class AddonModDataSyncProvider extends CoreSyncBaseProvider { /** * Synchronize an entry. * - * @param {any} data Database. - * @param {AddonModDataOfflineAction[]} entryActions Entry actions. - * @param {any} result Object with the result of the sync. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if success, rejected otherwise. + * @param data Database. + * @param entryActions Entry actions. + * @param result Object with the result of the sync. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if success, rejected otherwise. */ protected syncEntry(data: any, entryActions: AddonModDataOfflineAction[], result: any, siteId?: string): Promise { let discardError, @@ -388,10 +388,10 @@ export class AddonModDataSyncProvider extends CoreSyncBaseProvider { /** * Synchronize offline ratings. * - * @param {number} [cmId] Course module to be synced. If not defined, sync all databases. - * @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 databases. + * @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(); diff --git a/src/addon/mod/data/providers/tag-area-handler.ts b/src/addon/mod/data/providers/tag-area-handler.ts index f7fb15098..70e19045f 100644 --- a/src/addon/mod/data/providers/tag-area-handler.ts +++ b/src/addon/mod/data/providers/tag-area-handler.ts @@ -30,7 +30,7 @@ export class AddonModDataTagAreaHandler 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 this.dataProvider.isPluginEnabled(); @@ -39,8 +39,8 @@ export class AddonModDataTagAreaHandler 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); @@ -49,8 +49,8 @@ export class AddonModDataTagAreaHandler 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/feedback/components/index/index.ts b/src/addon/mod/feedback/components/index/index.ts index eefc27a7e..88e1537d3 100644 --- a/src/addon/mod/feedback/components/index/index.ts +++ b/src/addon/mod/feedback/components/index/index.ts @@ -124,7 +124,7 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity /** * Perform the invalidate content function. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected invalidateContent(): Promise { const promises = []; @@ -147,8 +147,8 @@ export class AddonModFeedbackIndexComponent 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 { if (this.feedback && syncEventData.feedbackId == this.feedback.id) { @@ -164,10 +164,10 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity /** * Download feedback contents. * - * @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 { return this.feedbackProvider.getFeedback(this.courseId, this.module.id).then((feedback) => { @@ -209,8 +209,8 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity /** * Convenience function to get feedback overview data. * - * @param {any} accessData Retrieved access data. - * @return {Promise} Resolved when done. + * @param accessData Retrieved access data. + * @return Resolved when done. */ protected fetchFeedbackOverviewData(accessData: any): Promise { const promises = []; @@ -240,8 +240,8 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity /** * Convenience function to get feedback analysis data. * - * @param {any} accessData Retrieved access data. - * @return {Promise} Resolved when done. + * @param accessData Retrieved access data. + * @return Resolved when done. */ protected fetchFeedbackAnalysisData(accessData: any): Promise { let promise; @@ -262,8 +262,8 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity /** * Fetch Group info data. * - * @param {number} cmId Course module ID. - * @return {Promise} Resolved when done. + * @param cmId Course module ID. + * @return Resolved when done. */ protected fetchGroupInfo(cmId: number): Promise { return this.groupsProvider.getActivityGroupInfo(cmId).then((groupInfo) => { @@ -276,8 +276,8 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity /** * Parse the analysis info to show the info correctly formatted. * - * @param {any} item Item to parse. - * @return {any} Parsed item. + * @param item Item to parse. + * @return Parsed item. */ protected parseAnalysisInfo(item: any): any { switch (item.typ) { @@ -356,7 +356,7 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity /** * Function to go to the questions form. * - * @param {boolean} preview Preview or edit the form. + * @param preview Preview or edit the form. */ gotoAnswerQuestions(preview: boolean = false): void { const stateParams = { @@ -389,7 +389,7 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity /** * Function to link implemented features. * - * @param {string} feature Feature to navigate. + * @param feature Feature to navigate. */ openFeature(feature: string): void { this.feedbackHelper.openFeature(feature, this.navCtrl, this.module, this.courseId, this.group); @@ -398,7 +398,7 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity /** * Tab changed, fetch content again. * - * @param {string} tabName New tab name. + * @param tabName New tab name. */ tabChanged(tabName: string): void { this.tab = tabName; @@ -411,8 +411,8 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity /** * Set group to see the analysis. * - * @param {number} groupId Group ID. - * @return {Promise} Resolved when done. + * @param groupId Group ID. + * @return Resolved when done. */ setGroup(groupId: number): Promise { this.group = groupId; @@ -452,7 +452,7 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity /** * Performs the sync of the activity. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected sync(): Promise { return this.feedbackSync.syncFeedback(this.feedback.id); @@ -461,8 +461,8 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity /** * 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 result.updated; diff --git a/src/addon/mod/feedback/pages/attempt/attempt.ts b/src/addon/mod/feedback/pages/attempt/attempt.ts index 3ec17f95e..07d1dad49 100644 --- a/src/addon/mod/feedback/pages/attempt/attempt.ts +++ b/src/addon/mod/feedback/pages/attempt/attempt.ts @@ -58,7 +58,7 @@ export class AddonModFeedbackAttemptPage { /** * Fetch all the data required for the view. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ fetchData(): Promise { // Get the feedback to be able to now if questions should be autonumbered. diff --git a/src/addon/mod/feedback/pages/form/form.ts b/src/addon/mod/feedback/pages/form/form.ts index f1da26ca8..edfd72c16 100644 --- a/src/addon/mod/feedback/pages/form/form.ts +++ b/src/addon/mod/feedback/pages/form/form.ts @@ -113,7 +113,7 @@ export class AddonModFeedbackFormPage 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 { if (this.forceLeave) { @@ -137,7 +137,7 @@ export class AddonModFeedbackFormPage implements OnDestroy { /** * Fetch all the data required for the view. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchData(): Promise { this.offline = !this.appProvider.isOnline(); @@ -183,7 +183,7 @@ export class AddonModFeedbackFormPage implements OnDestroy { /** * Fetch access information. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchAccessData(): Promise { return this.feedbackProvider.getFeedbackAccessInformation(this.feedback.id, this.offline, true).catch((error) => { @@ -246,8 +246,8 @@ export class AddonModFeedbackFormPage implements OnDestroy { /** * Function to allow page navigation through the questions form. * - * @param {boolean} goPrevious If true it will go back to the previous page, if false, it will go forward. - * @return {Promise} Resolved when done. + * @param goPrevious If true it will go back to the previous page, if false, it will go forward. + * @return Resolved when done. */ gotoPage(goPrevious: boolean): Promise { this.domUtils.scrollToTop(this.content); diff --git a/src/addon/mod/feedback/pages/index/index.ts b/src/addon/mod/feedback/pages/index/index.ts index a6b937d91..2de96f1b1 100644 --- a/src/addon/mod/feedback/pages/index/index.ts +++ b/src/addon/mod/feedback/pages/index/index.ts @@ -44,7 +44,7 @@ export class AddonModFeedbackIndexPage { /** * Update some data based on the feedback instance. * - * @param {any} feedback Feedback instance. + * @param feedback Feedback instance. */ updateData(feedback: any): void { this.title = feedback.name || this.title; diff --git a/src/addon/mod/feedback/pages/nonrespondents/nonrespondents.ts b/src/addon/mod/feedback/pages/nonrespondents/nonrespondents.ts index 4f8c210b7..b42fd38f4 100644 --- a/src/addon/mod/feedback/pages/nonrespondents/nonrespondents.ts +++ b/src/addon/mod/feedback/pages/nonrespondents/nonrespondents.ts @@ -68,8 +68,8 @@ export class AddonModFeedbackNonRespondentsPage { /** * Fetch all the data required for 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. */ fetchData(refresh: boolean = false): Promise { this.page = 0; @@ -96,8 +96,8 @@ export class AddonModFeedbackNonRespondentsPage { /** * Load Group responses. * - * @param {number} [groupId] If defined it will change group if not, it will load more users for the same group. - * @return {Promise} Resolved with the attempts loaded. + * @param groupId If defined it will change group if not, it will load more users for the same group. + * @return Resolved with the attempts loaded. */ protected loadGroupUsers(groupId?: number): Promise { if (typeof groupId == 'undefined') { @@ -130,7 +130,7 @@ export class AddonModFeedbackNonRespondentsPage { /** * Change selected group or load more users. * - * @param {number} [groupId] Group ID selected. If not defined, it will load more users. + * @param groupId Group ID selected. If not defined, it will load more users. */ loadAttempts(groupId?: number): void { this.loadGroupUsers(groupId).catch((message) => { @@ -141,7 +141,7 @@ export class AddonModFeedbackNonRespondentsPage { /** * Refresh the attempts. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshFeedback(refresher: any): void { if (this.feedbackLoaded) { diff --git a/src/addon/mod/feedback/pages/respondents/respondents.ts b/src/addon/mod/feedback/pages/respondents/respondents.ts index 4a4a669ea..74e5ea7b4 100644 --- a/src/addon/mod/feedback/pages/respondents/respondents.ts +++ b/src/addon/mod/feedback/pages/respondents/respondents.ts @@ -87,8 +87,8 @@ export class AddonModFeedbackRespondentsPage { /** * Fetch all the data required for 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. */ fetchData(refresh: boolean = false): Promise { this.page = 0; @@ -117,8 +117,8 @@ export class AddonModFeedbackRespondentsPage { /** * Load Group attempts. * - * @param {number} [groupId] If defined it will change group if not, it will load more attempts for the same group. - * @return {Promise} Resolved with the attempts loaded. + * @param groupId If defined it will change group if not, it will load more attempts for the same group. + * @return Resolved with the attempts loaded. */ protected loadGroupAttempts(groupId?: number): Promise { if (typeof groupId == 'undefined') { @@ -158,7 +158,7 @@ export class AddonModFeedbackRespondentsPage { /** * Navigate to a particular attempt. * - * @param {any} attempt Attempt object to load. + * @param attempt Attempt object to load. */ gotoAttempt(attempt: any): void { this.attemptId = attempt.id; @@ -174,7 +174,7 @@ export class AddonModFeedbackRespondentsPage { /** * Change selected group or load more attempts. * - * @param {number} [groupId] Group ID selected. If not defined, it will load more attempts. + * @param groupId Group ID selected. If not defined, it will load more attempts. */ loadAttempts(groupId?: number): void { this.loadGroupAttempts(groupId).catch((message) => { @@ -185,7 +185,7 @@ export class AddonModFeedbackRespondentsPage { /** * Refresh the attempts. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshFeedback(refresher: any): void { if (this.feedbackLoaded) { diff --git a/src/addon/mod/feedback/providers/analysis-link-handler.ts b/src/addon/mod/feedback/providers/analysis-link-handler.ts index 8f8fa3c39..4f11146e2 100644 --- a/src/addon/mod/feedback/providers/analysis-link-handler.ts +++ b/src/addon/mod/feedback/providers/analysis-link-handler.ts @@ -38,11 +38,11 @@ export class AddonModFeedbackAnalysisLinkHandler extends 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 { @@ -70,11 +70,11 @@ export class AddonModFeedbackAnalysisLinkHandler extends 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 this.feedbackProvider.isPluginEnabled(siteId).then((enabled) => { diff --git a/src/addon/mod/feedback/providers/complete-link-handler.ts b/src/addon/mod/feedback/providers/complete-link-handler.ts index af4b1153a..0a5d0af25 100644 --- a/src/addon/mod/feedback/providers/complete-link-handler.ts +++ b/src/addon/mod/feedback/providers/complete-link-handler.ts @@ -38,11 +38,11 @@ export class AddonModFeedbackCompleteLinkHandler extends 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 { @@ -73,11 +73,11 @@ export class AddonModFeedbackCompleteLinkHandler extends 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 { if (typeof params.id == 'undefined') { diff --git a/src/addon/mod/feedback/providers/feedback.ts b/src/addon/mod/feedback/providers/feedback.ts index a9aed0880..10670e5f8 100644 --- a/src/addon/mod/feedback/providers/feedback.ts +++ b/src/addon/mod/feedback/providers/feedback.ts @@ -47,9 +47,9 @@ export class AddonModFeedbackProvider { /** * Check dependency of a question item. * - * @param {any[]} items All question items to check dependency. - * @param {any} item Item to check. - * @return {boolean} Return true if dependency is acomplished and it can be shown. False, otherwise. + * @param items All question items to check dependency. + * @param item Item to check. + * @return Return true if dependency is acomplished and it can be shown. False, otherwise. */ protected checkDependencyItem(items: any[], item: any): boolean { const depend = items.find((itemFind) => { @@ -77,9 +77,9 @@ export class AddonModFeedbackProvider { /** * Check dependency item of type Multichoice. * - * @param {any} item Item to check. - * @param {string} dependValue Value to compare. - * @return {boolean} Return true if dependency is acomplished and it can be shown. False, otherwise. + * @param item Item to check. + * @param dependValue Value to compare. + * @return Return true if dependency is acomplished and it can be shown. False, otherwise. */ protected compareDependItemMultichoice(item: any, dependValue: string): boolean { let values, choices; @@ -128,12 +128,12 @@ export class AddonModFeedbackProvider { /** * Fill values of item questions. * - * @param {number} feedbackId Feedback ID. - * @param {any[]} items Item to fill the value. - * @param {boolean} offline 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. - * @return {Promise} Resolved with values when done. + * @param feedbackId Feedback ID. + * @param items Item to fill the value. + * @param offline 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. + * @return Resolved with values when done. */ protected fillValues(feedbackId: number, items: any[], offline: boolean, ignoreCache: boolean, siteId: string): Promise { return this.getCurrentValues(feedbackId, offline, ignoreCache, siteId).then((valuesArray) => { @@ -202,12 +202,12 @@ export class AddonModFeedbackProvider { /** * Returns all the feedback non respondents users. * - * @param {number} feedbackId Feedback ID. - * @param {number} groupId Group id, 0 means that the function will determine the user group. - * @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 {any} [previous] Only for recurrent use. Object with the previous fetched info. - * @return {Promise} Promise resolved when the info is retrieved. + * @param feedbackId Feedback ID. + * @param groupId Group id, 0 means that the function will determine the user group. + * @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 previous Only for recurrent use. Object with the previous fetched info. + * @return Promise resolved when the info is retrieved. */ getAllNonRespondents(feedbackId: number, groupId: number, ignoreCache?: boolean, siteId?: string, previous?: any) : Promise { @@ -240,12 +240,12 @@ export class AddonModFeedbackProvider { /** * Returns all the feedback user responses. * - * @param {number} feedbackId Feedback ID. - * @param {number} groupId Group id, 0 means that the function will determine the user group. - * @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 {any} [previous] Only for recurrent use. Object with the previous fetched info. - * @return {Promise} Promise resolved when the info is retrieved. + * @param feedbackId Feedback ID. + * @param groupId Group id, 0 means that the function will determine the user group. + * @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 previous Only for recurrent use. Object with the previous fetched info. + * @return Promise resolved when the info is retrieved. */ getAllResponsesAnalysis(feedbackId: number, groupId: number, ignoreCache?: boolean, siteId?: string, previous?: any) : Promise { @@ -285,11 +285,11 @@ export class AddonModFeedbackProvider { /** * Get analysis information for a given feedback. * - * @param {number} feedbackId Feedback ID. - * @param {number} [groupId] Group ID. - * @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 feedback is retrieved. + * @param feedbackId Feedback ID. + * @param groupId Group 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 feedback is retrieved. */ getAnalysis(feedbackId: number, groupId?: number, ignoreCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -316,9 +316,9 @@ export class AddonModFeedbackProvider { /** * Get cache key for feedback analysis data WS calls. * - * @param {number} feedbackId Feedback ID. - * @param {number} [groupId=0] Group ID. - * @return {string} Cache key. + * @param feedbackId Feedback ID. + * @param groupId Group ID. + * @return Cache key. */ protected getAnalysisDataCacheKey(feedbackId: number, groupId: number = 0): string { return this.getAnalysisDataPrefixCacheKey(feedbackId) + groupId; @@ -327,8 +327,8 @@ export class AddonModFeedbackProvider { /** * Get prefix cache key for feedback analysis data WS calls. * - * @param {number} feedbackId Feedback ID. - * @return {string} Cache key. + * @param feedbackId Feedback ID. + * @return Cache key. */ protected getAnalysisDataPrefixCacheKey(feedbackId: number): string { return this.getFeedbackDataPrefixCacheKey(feedbackId) + ':analysis:'; @@ -337,12 +337,12 @@ export class AddonModFeedbackProvider { /** * Find an attempt in all responses analysis. * - * @param {number} feedbackId Feedback ID. - * @param {number} attemptId Attempt id to find. - * @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 {any} [previous] Only for recurrent use. Object with the previous fetched info. - * @return {Promise} Promise resolved when the info is retrieved. + * @param feedbackId Feedback ID. + * @param attemptId Attempt id to find. + * @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 previous Only for recurrent use. Object with the previous fetched info. + * @return Promise resolved when the info is retrieved. */ getAttempt(feedbackId: number, attemptId: number, ignoreCache?: boolean, siteId?: string, previous?: any): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -396,8 +396,8 @@ export class AddonModFeedbackProvider { /** * Get prefix cache key for feedback completion data WS calls. * - * @param {number} feedbackId Feedback ID. - * @return {string} Cache key. + * @param feedbackId Feedback ID. + * @return Cache key. */ protected getCompletedDataCacheKey(feedbackId: number): string { return this.getFeedbackDataPrefixCacheKey(feedbackId) + ':completed:'; @@ -406,10 +406,10 @@ export class AddonModFeedbackProvider { /** * Returns the temporary completion timemodified for the current user. * - * @param {number} feedbackId Feedback ID. - * @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 info is retrieved. + * @param feedbackId Feedback 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 info is retrieved. */ getCurrentCompletedTimeModified(feedbackId: number, ignoreCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -441,8 +441,8 @@ export class AddonModFeedbackProvider { /** * Get prefix cache key for feedback current completed temp data WS calls. * - * @param {number} feedbackId Feedback ID. - * @return {string} Cache key. + * @param feedbackId Feedback ID. + * @return Cache key. */ protected getCurrentCompletedTimeModifiedDataCacheKey(feedbackId: number): string { return this.getFeedbackDataPrefixCacheKey(feedbackId) + ':completedtime:'; @@ -451,11 +451,11 @@ export class AddonModFeedbackProvider { /** * Returns the temporary responses or responses of the last submission for the current user. * - * @param {number} feedbackId Feedback ID. - * @param {boolean} [offline=false] True if it should return cached data. Has priority over ignoreCache. - * @param {boolean} [ignoreCache=false] True if it should ignore cached data (it always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the info is retrieved. + * @param feedbackId Feedback ID. + * @param offline True if it should return cached data. Has priority over ignoreCache. + * @param ignoreCache True if it should ignore cached data (it always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the info is retrieved. */ getCurrentValues(feedbackId: number, offline: boolean = false, ignoreCache: boolean = false, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -497,8 +497,8 @@ export class AddonModFeedbackProvider { /** * Get cache key for get current values feedback data WS calls. * - * @param {number} feedbackId Feedback ID. - * @return {string} Cache key. + * @param feedbackId Feedback ID. + * @return Cache key. */ protected getCurrentValuesDataCacheKey(feedbackId: number): string { return this.getFeedbackDataPrefixCacheKey(feedbackId) + ':currentvalues'; @@ -507,11 +507,11 @@ export class AddonModFeedbackProvider { /** * Get access information for a given feedback. * - * @param {number} feedbackId Feedback ID. - * @param {boolean} [offline=false] True if it should return cached data. Has priority over ignoreCache. - * @param {boolean} [ignoreCache=false] True if it should ignore cached data (it always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the feedback is retrieved. + * @param feedbackId Feedback ID. + * @param offline True if it should return cached data. Has priority over ignoreCache. + * @param ignoreCache True if it should ignore cached data (it always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the feedback is retrieved. */ getFeedbackAccessInformation(feedbackId: number, offline: boolean = false, ignoreCache: boolean = false, siteId?: string): Promise { @@ -537,8 +537,8 @@ export class AddonModFeedbackProvider { /** * Get cache key for feedback access information data WS calls. * - * @param {number} feedbackId Feedback ID. - * @return {string} Cache key. + * @param feedbackId Feedback ID. + * @return Cache key. */ protected getFeedbackAccessInformationDataCacheKey(feedbackId: number): string { return this.getFeedbackDataPrefixCacheKey(feedbackId) + ':access'; @@ -547,8 +547,8 @@ export class AddonModFeedbackProvider { /** * Get cache key for feedback data WS calls. * - * @param {number} courseId Course ID. - * @return {string} Cache key. + * @param courseId Course ID. + * @return Cache key. */ protected getFeedbackCacheKey(courseId: number): string { return this.ROOT_CACHE_KEY + 'feedback:' + courseId; @@ -557,8 +557,8 @@ export class AddonModFeedbackProvider { /** * Get prefix cache key for all feedback activity data WS calls. * - * @param {number} feedbackId Feedback ID. - * @return {string} Cache key. + * @param feedbackId Feedback ID. + * @return Cache key. */ protected getFeedbackDataPrefixCacheKey(feedbackId: number): string { return this.ROOT_CACHE_KEY + feedbackId; @@ -567,13 +567,13 @@ export class AddonModFeedbackProvider { /** * Get a feedback 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. - * @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false. - * @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 feedback 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. + * @param forceCache True to always get the value from cache, false otherwise. Default false. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @return Promise resolved when the feedback is retrieved. */ protected getFeedbackDataByKey(courseId: number, key: string, value: any, siteId?: string, forceCache?: boolean, ignoreCache?: boolean): Promise { @@ -612,12 +612,12 @@ export class AddonModFeedbackProvider { /** * Get a feedback 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. - * @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false. - * @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 feedback is retrieved. + * @param courseId Course ID. + * @param cmId Course module ID. + * @param siteId Site ID. If not defined, current site. + * @param forceCache True to always get the value from cache, false otherwise. Default false. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @return Promise resolved when the feedback is retrieved. */ getFeedback(courseId: number, cmId: number, siteId?: string, forceCache?: boolean, ignoreCache?: boolean): Promise { return this.getFeedbackDataByKey(courseId, 'coursemodule', cmId, siteId, forceCache, ignoreCache); @@ -626,12 +626,12 @@ export class AddonModFeedbackProvider { /** * Get a feedback by ID. * - * @param {number} courseId Course ID. - * @param {number} id Feedback ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false. - * @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 feedback is retrieved. + * @param courseId Course ID. + * @param id Feedback ID. + * @param siteId Site ID. If not defined, current site. + * @param forceCache True to always get the value from cache, false otherwise. Default false. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @return Promise resolved when the feedback is retrieved. */ getFeedbackById(courseId: number, id: number, siteId?: string, forceCache?: boolean, ignoreCache?: boolean): Promise { return this.getFeedbackDataByKey(courseId, 'id', id, siteId, forceCache, ignoreCache); @@ -640,10 +640,10 @@ export class AddonModFeedbackProvider { /** * Returns the items (questions) in the given feedback. * - * @param {number} feedbackId Feedback ID. - * @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 info is retrieved. + * @param feedbackId Feedback 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 info is retrieved. */ getItems(feedbackId: number, ignoreCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -667,8 +667,8 @@ export class AddonModFeedbackProvider { /** * Get cache key for get items feedback data WS calls. * - * @param {number} feedbackId Feedback ID. - * @return {string} Cache key. + * @param feedbackId Feedback ID. + * @return Cache key. */ protected getItemsDataCacheKey(feedbackId: number): string { return this.getFeedbackDataPrefixCacheKey(feedbackId) + ':items'; @@ -677,12 +677,12 @@ export class AddonModFeedbackProvider { /** * Retrieves a list of students who didn't submit the feedback. * - * @param {number} feedbackId Feedback ID. - * @param {number} [groupId=0] Group id, 0 means that the function will determine the user group. - * @param {number} [page=0] The page of records to return. - * @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 info is retrieved. + * @param feedbackId Feedback ID. + * @param groupId Group id, 0 means that the function will determine the user group. + * @param page The page of records to return. + * @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 info is retrieved. */ getNonRespondents(feedbackId: number, groupId: number = 0, page: number = 0, ignoreCache?: boolean, siteId?: string) : Promise { @@ -709,9 +709,9 @@ export class AddonModFeedbackProvider { /** * Get cache key for non respondents feedback data WS calls. * - * @param {number} feedbackId Feedback ID. - * @param {number} [groupId=0] Group id, 0 means that the function will determine the user group. - * @return {string} Cache key. + * @param feedbackId Feedback ID. + * @param groupId Group id, 0 means that the function will determine the user group. + * @return Cache key. */ protected getNonRespondentsDataCacheKey(feedbackId: number, groupId: number = 0): string { return this.getNonRespondentsDataPrefixCacheKey(feedbackId) + groupId; @@ -720,8 +720,8 @@ export class AddonModFeedbackProvider { /** * Get prefix cache key for feedback non respondents data WS calls. * - * @param {number} feedbackId Feedback ID. - * @return {string} Cache key. + * @param feedbackId Feedback ID. + * @return Cache key. */ protected getNonRespondentsDataPrefixCacheKey(feedbackId: number): string { return this.getFeedbackDataPrefixCacheKey(feedbackId) + ':nonrespondents:'; @@ -730,10 +730,10 @@ export class AddonModFeedbackProvider { /** * Get a single feedback page items. This function is not cached, use AddonModFeedbackHelperProvider#getPageItems instead. * - * @param {number} feedbackId Feedback ID. - * @param {number} page The page to get. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the info is retrieved. + * @param feedbackId Feedback ID. + * @param page The page to get. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the info is retrieved. */ getPageItems(feedbackId: number, page: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -749,12 +749,12 @@ export class AddonModFeedbackProvider { /** * Get a single feedback page items. If offline or server down it will use getItems to calculate dependencies. * - * @param {number} feedbackId Feedback ID. - * @param {number} page The page to get. - * @param {boolean} [offline=false] True if it should return cached data. Has priority over ignoreCache. - * @param {boolean} [ignoreCache=false] 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 info is retrieved. + * @param feedbackId Feedback ID. + * @param page The page to get. + * @param offline 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, current site. + * @return Promise resolved when the info is retrieved. */ getPageItemsWithValues(feedbackId: number, page: number, offline: boolean = false, ignoreCache: boolean = false, siteId?: string): Promise { @@ -816,11 +816,11 @@ export class AddonModFeedbackProvider { /** * Convenience function to get the page we can jump. * - * @param {number} feedbackId Feedback ID. - * @param {number} page Page where we want to jump. - * @param {number} changePage If page change is forward (1) or backward (-1). - * @param {string} siteId Site ID. - * @return {Promise} Page number where to jump. Or false if completed or first page. + * @param feedbackId Feedback ID. + * @param page Page where we want to jump. + * @param changePage If page change is forward (1) or backward (-1). + * @param siteId Site ID. + * @return Page number where to jump. Or false if completed or first page. */ protected getPageJumpTo(feedbackId: number, page: number, changePage: number, siteId: string): Promise { return this.getPageItemsWithValues(feedbackId, page, true, false, siteId).then((resp) => { @@ -842,12 +842,12 @@ export class AddonModFeedbackProvider { /** * Returns the feedback user responses. * - * @param {number} feedbackId Feedback ID. - * @param {number} groupId Group id, 0 means that the function will determine the user group. - * @param {number} page The page of records to return. - * @param {boolean} [ignoreCache=false] 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 info is retrieved. + * @param feedbackId Feedback ID. + * @param groupId Group id, 0 means that the function will determine the user group. + * @param page The page of records to return. + * @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 info is retrieved. */ getResponsesAnalysis(feedbackId: number, groupId: number, page: number, ignoreCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -872,9 +872,9 @@ export class AddonModFeedbackProvider { /** * Get cache key for responses analysis feedback data WS calls. * - * @param {number} feedbackId Feedback ID. - * @param {number} [groupId=0] Group id, 0 means that the function will determine the user group. - * @return {string} Cache key. + * @param feedbackId Feedback ID. + * @param groupId Group id, 0 means that the function will determine the user group. + * @return Cache key. */ protected getResponsesAnalysisDataCacheKey(feedbackId: number, groupId: number = 0): string { return this.getResponsesAnalysisDataPrefixCacheKey(feedbackId) + groupId; @@ -883,8 +883,8 @@ export class AddonModFeedbackProvider { /** * Get prefix cache key for feedback responses analysis data WS calls. * - * @param {number} feedbackId Feedback ID. - * @return {string} Cache key. + * @param feedbackId Feedback ID. + * @return Cache key. */ protected getResponsesAnalysisDataPrefixCacheKey(feedbackId: number): string { return this.getFeedbackDataPrefixCacheKey(feedbackId) + ':responsesanalysis:'; @@ -893,11 +893,11 @@ export class AddonModFeedbackProvider { /** * Gets the resume page information. * - * @param {number} feedbackId Feedback ID. - * @param {boolean} [offline=false] True if it should return cached data. Has priority over ignoreCache. - * @param {boolean} [ignoreCache=false] True if it should ignore cached data (it always fail in offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the info is retrieved. + * @param feedbackId Feedback ID. + * @param offline True if it should return cached data. Has priority over ignoreCache. + * @param ignoreCache True if it should ignore cached data (it always fail in offline or server down). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the info is retrieved. */ getResumePage(feedbackId: number, offline: boolean = false, ignoreCache: boolean = false, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -929,8 +929,8 @@ export class AddonModFeedbackProvider { /** * Get prefix cache key for resume feedback page data WS calls. * - * @param {number} feedbackId Feedback ID. - * @return {string} Cache key. + * @param feedbackId Feedback ID. + * @return Cache key. */ protected getResumePageDataCacheKey(feedbackId: number): string { return this.getFeedbackDataPrefixCacheKey(feedbackId) + ':launch'; @@ -939,9 +939,9 @@ export class AddonModFeedbackProvider { /** * Invalidates feedback data except files and module info. * - * @param {number} feedbackId Feedback ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param feedbackId Feedback ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateAllFeedbackData(feedbackId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -952,9 +952,9 @@ export class AddonModFeedbackProvider { /** * Invalidates feedback analysis data. * - * @param {number} feedbackId Feedback ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param feedbackId Feedback ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateAnalysisData(feedbackId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -966,10 +966,10 @@ export class AddonModFeedbackProvider { * Invalidate the prefetched content. * To invalidate files, use AddonFeedbackProvider#invalidateFiles. * - * @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(); @@ -994,9 +994,9 @@ export class AddonModFeedbackProvider { /** * Invalidates temporary completion record data. * - * @param {number} feedbackId Feedback ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param feedbackId Feedback ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateCurrentValuesData(feedbackId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1007,9 +1007,9 @@ export class AddonModFeedbackProvider { /** * Invalidates feedback access information data. * - * @param {number} feedbackId Feedback ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param feedbackId Feedback ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateFeedbackAccessInformationData(feedbackId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1020,9 +1020,9 @@ export class AddonModFeedbackProvider { /** * Invalidates feedback 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. */ invalidateFeedbackData(courseId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1033,9 +1033,9 @@ export class AddonModFeedbackProvider { /** * 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, AddonModFeedbackProvider.COMPONENT, moduleId); @@ -1044,9 +1044,9 @@ export class AddonModFeedbackProvider { /** * Invalidates feedback non respondents record data. * - * @param {number} feedbackId Feedback ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param feedbackId Feedback ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateNonRespondentsData(feedbackId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1058,9 +1058,9 @@ export class AddonModFeedbackProvider { /** * Invalidates feedback user responses record data. * - * @param {number} feedbackId Feedback ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param feedbackId Feedback ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateResponsesAnalysisData(feedbackId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1072,9 +1072,9 @@ export class AddonModFeedbackProvider { /** * Invalidates launch feedback data. * - * @param {number} feedbackId Feedback ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param feedbackId Feedback ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateResumePageData(feedbackId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1085,10 +1085,10 @@ export class AddonModFeedbackProvider { /** * Returns if feedback has been completed * - * @param {number} feedbackId Feedback ID. - * @param {boolean} [ignoreCache=false] 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 info is retrieved. + * @param feedbackId Feedback 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 info is retrieved. */ isCompleted(feedbackId: number, ignoreCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1111,8 +1111,8 @@ export class AddonModFeedbackProvider { /** * Return whether or not the plugin is enabled in a certain site. Plugin is enabled if the feedback WS are available. * - * @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. * @since 3.3 */ isPluginEnabled(siteId?: string): Promise { @@ -1125,11 +1125,11 @@ export class AddonModFeedbackProvider { /** * Report the feedback as being viewed. * - * @param {number} id Module ID. - * @param {string} [name] Name of the feedback. - * @param {boolean} [formViewed=false] True if form was viewed. - * @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 feedback. + * @param formViewed True if form was viewed. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the WS call is successful. */ logView(id: number, name?: string, formViewed: boolean = false, siteId?: string): Promise { const params = { @@ -1144,14 +1144,14 @@ export class AddonModFeedbackProvider { /** * Process a jump between pages. * - * @param {number} feedbackId Feedback ID. - * @param {number} page The page being processed. - * @param {any} responses The data to be processed the key is the field name (usually type[index]_id). - * @param {boolean} goPrevious Whether we want to jump to previous page. - * @param {boolean} formHasErrors Whether the form we sent has required but empty fields (only used in offline). - * @param {number} courseId Course ID the feedback belongs to. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the info is retrieved. + * @param feedbackId Feedback ID. + * @param page The page being processed. + * @param responses The data to be processed the key is the field name (usually type[index]_id). + * @param goPrevious Whether we want to jump to previous page. + * @param formHasErrors Whether the form we sent has required but empty fields (only used in offline). + * @param courseId Course ID the feedback belongs to. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the info is retrieved. */ processPage(feedbackId: number, page: number, responses: any, goPrevious: boolean, formHasErrors: boolean, courseId: number, siteId?: string): Promise { @@ -1231,12 +1231,12 @@ export class AddonModFeedbackProvider { /** * Process a jump between pages. * - * @param {number} feedbackId Feedback ID. - * @param {number} page The page being processed. - * @param {any} responses The data to be processed the key is the field name (usually type[index]_id). - * @param {boolean} goPrevious Whether we want to jump to previous page. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the info is retrieved. + * @param feedbackId Feedback ID. + * @param page The page being processed. + * @param responses The data to be processed the key is the field name (usually type[index]_id). + * @param goPrevious Whether we want to jump to previous page. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the info is retrieved. */ processPageOnline(feedbackId: number, page: number, responses: any, goPrevious: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { diff --git a/src/addon/mod/feedback/providers/helper.ts b/src/addon/mod/feedback/providers/helper.ts index e66a24287..64b3f5f2c 100644 --- a/src/addon/mod/feedback/providers/helper.ts +++ b/src/addon/mod/feedback/providers/helper.ts @@ -45,12 +45,12 @@ export class AddonModFeedbackHelperProvider { /** * Check if the page we are going to open is in the history and returns the view controller in the stack to go back. * - * @param {string} pageName Name of the page we want to navigate. - * @param {number} instance Activity instance Id. I.e FeedbackId. - * @param {string} paramName Param name where to find the instance number. - * @param {string} prefix Prefix to check if we are out of the activity context. - * @param {NavController} navCtrl Nav Controller of the view. - * @return {ViewController} Returns view controller found or null. + * @param pageName Name of the page we want to navigate. + * @param instance Activity instance Id. I.e FeedbackId. + * @param paramName Param name where to find the instance number. + * @param prefix Prefix to check if we are out of the activity context. + * @param navCtrl Nav Controller of the view. + * @return Returns view controller found or null. */ protected getPageView(pageName: string, instance: number, paramName: string, prefix: string, navCtrl: NavController): ViewController { @@ -85,10 +85,10 @@ export class AddonModFeedbackHelperProvider { /** * Retrieves a list of students who didn't submit the feedback with extra info. * - * @param {number} feedbackId Feedback ID. - * @param {number} groupId Group id, 0 means that the function will determine the user group. - * @param {number} page The page of records to return. - * @return {Promise} Promise resolved when the info is retrieved. + * @param feedbackId Feedback ID. + * @param groupId Group id, 0 means that the function will determine the user group. + * @param page The page of records to return. + * @return Promise resolved when the info is retrieved. */ getNonRespondents(feedbackId: number, groupId: number, page: number): Promise { return this.feedbackProvider.getNonRespondents(feedbackId, groupId, page).then((responses) => { @@ -103,8 +103,8 @@ export class AddonModFeedbackHelperProvider { /** * Get page items responses to be sent. * - * @param {any[]} items Items where the values are. - * @return {any} Responses object to be sent. + * @param items Items where the values are. + * @return Responses object to be sent. */ getPageItemsResponses(items: any[]): any { const responses = {}; @@ -185,10 +185,10 @@ export class AddonModFeedbackHelperProvider { /** * Returns the feedback user responses with extra info. * - * @param {number} feedbackId Feedback ID. - * @param {number} groupId Group id, 0 means that the function will determine the user group. - * @param {number} page The page of records to return. - * @return {Promise} Promise resolved when the info is retrieved. + * @param feedbackId Feedback ID. + * @param groupId Group id, 0 means that the function will determine the user group. + * @param page The page of records to return. + * @return Promise resolved when the info is retrieved. */ getResponsesAnalysis(feedbackId: number, groupId: number, page: number): Promise { return this.feedbackProvider.getResponsesAnalysis(feedbackId, groupId, page).then((responses) => { @@ -203,10 +203,10 @@ export class AddonModFeedbackHelperProvider { /** * Handle a show entries link. * - * @param {NavController} navCtrl Nav controller to use to navigate. Can be undefined/null. - * @param {any} params URL params. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param navCtrl Nav controller to use to navigate. Can be undefined/null. + * @param params URL params. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ handleShowEntriesLink(navCtrl: NavController, params: any, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -245,8 +245,8 @@ export class AddonModFeedbackHelperProvider { /** * Add Image profile url field on attempts * - * @param {any} attempts Attempts array to get profile from. - * @return {Promise} Returns the same array with the profileimageurl added if found. + * @param attempts Attempts array to get profile from. + * @return Returns the same array with the profileimageurl added if found. */ protected addImageProfileToAttempts(attempts: any): Promise { const promises = attempts.map((attempt) => { @@ -265,12 +265,12 @@ export class AddonModFeedbackHelperProvider { /** * Helper function to open a feature in the app. * - * @param {string} feature Name of the feature to open. - * @param {NavController} navCtrl NavController. - * @param {any} module Course module activity object. - * @param {number} courseId Course Id. - * @param {number} [group=0] Course module activity object. - * @return {Promise} Resolved when navigation animation is done. + * @param feature Name of the feature to open. + * @param navCtrl NavController. + * @param module Course module activity object. + * @param courseId Course Id. + * @param group Course module activity object. + * @return Resolved when navigation animation is done. */ openFeature(feature: string, navCtrl: NavController, module: any, courseId: number, group: number = 0): Promise { const pageName = feature && feature != 'analysis' ? 'AddonModFeedback' + feature + 'Page' : 'AddonModFeedbackIndexPage', @@ -300,8 +300,8 @@ export class AddonModFeedbackHelperProvider { /** * Helper funtion for item type Label. * - * @param {any} item Item to process. - * @return {any} Item processed to show form. + * @param item Item to process. + * @return Item processed to show form. */ protected getItemFormLabel(item: any): any { item.template = 'label'; @@ -314,8 +314,8 @@ export class AddonModFeedbackHelperProvider { /** * Helper funtion for item type Info. * - * @param {any} item Item to process. - * @return {any} Item processed to show form. + * @param item Item to process. + * @return Item processed to show form. */ protected getItemFormInfo(item: any): any { item.template = 'label'; @@ -340,8 +340,8 @@ export class AddonModFeedbackHelperProvider { /** * Helper funtion for item type Numeric. * - * @param {any} item Item to process. - * @return {any} Item processed to show form. + * @param item Item to process. + * @return Item processed to show form. */ protected getItemFormNumeric(item: any): any { item.template = 'numeric'; @@ -361,8 +361,8 @@ export class AddonModFeedbackHelperProvider { /** * Helper funtion for item type Text field. * - * @param {any} item Item to process. - * @return {any} Item processed to show form. + * @param item Item to process. + * @return Item processed to show form. */ protected getItemFormTextfield(item: any): any { item.template = 'textfield'; @@ -375,8 +375,8 @@ export class AddonModFeedbackHelperProvider { /** * Helper funtion for item type Textarea. * - * @param {any} item Item to process. - * @return {any} Item processed to show form. + * @param item Item to process. + * @return Item processed to show form. */ protected getItemFormTextarea(item: any): any { item.template = 'textarea'; @@ -388,8 +388,8 @@ export class AddonModFeedbackHelperProvider { /** * Helper funtion for item type Multichoice. * - * @param {any} item Item to process. - * @return {any} Item processed to show form. + * @param item Item to process. + * @return Item processed to show form. */ protected getItemFormMultichoice(item: any): any { let parts = item.presentation.split(AddonModFeedbackProvider.MULTICHOICE_TYPE_SEP) || []; @@ -443,8 +443,8 @@ export class AddonModFeedbackHelperProvider { /** * Helper funtion for item type Captcha. * - * @param {any} item Item to process. - * @return {any} Item processed to show form. + * @param item Item to process. + * @return Item processed to show form. */ protected getItemFormCaptcha(item: any): any { const data = this.textUtils.parseJSON(item.otherdata); @@ -462,9 +462,9 @@ export class AddonModFeedbackHelperProvider { /** * Process and returns item to print form. * - * @param {any} item Item to process. - * @param {boolean} preview Previewing options. - * @return {any} Item processed to show form. + * @param item Item to process. + * @param preview Previewing options. + * @return Item processed to show form. */ getItemForm(item: any, preview: boolean): any { switch (item.typ) { @@ -502,9 +502,9 @@ export class AddonModFeedbackHelperProvider { * Returns human-readable boundaries (min - max). * Based on Moodle's get_boundaries_for_display. * - * @param {number} rangeFrom Range from. - * @param {number} rangeTo Range to. - * @return {string} Human-readable boundaries. + * @param rangeFrom Range from. + * @param rangeTo Range to. + * @return Human-readable boundaries. */ protected getNumericBoundariesForDisplay(rangeFrom: number, rangeTo: number): string { const rangeFromSet = typeof rangeFrom == 'number', diff --git a/src/addon/mod/feedback/providers/list-link-handler.ts b/src/addon/mod/feedback/providers/list-link-handler.ts index 8d6750e0b..b9ecdd7a5 100644 --- a/src/addon/mod/feedback/providers/list-link-handler.ts +++ b/src/addon/mod/feedback/providers/list-link-handler.ts @@ -33,7 +33,7 @@ export class AddonModFeedbackListLinkHandler extends CoreContentLinksModuleListH /** * Check if the handler is enabled on a site level. * - * @return {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(): Promise { return this.feedbackProvider.isPluginEnabled(); diff --git a/src/addon/mod/feedback/providers/module-handler.ts b/src/addon/mod/feedback/providers/module-handler.ts index 7744ce1b3..b5b6da733 100644 --- a/src/addon/mod/feedback/providers/module-handler.ts +++ b/src/addon/mod/feedback/providers/module-handler.ts @@ -45,7 +45,7 @@ export class AddonModFeedbackModuleHandler implements CoreCourseModuleHandler { /** * Check if the handler is enabled on a site level. * - * @return {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(): Promise { return this.feedbackProvider.isPluginEnabled(); @@ -54,10 +54,10 @@ export class AddonModFeedbackModuleHandler 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 AddonModFeedbackModuleHandler 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 AddonModFeedbackIndexComponent; diff --git a/src/addon/mod/feedback/providers/offline.ts b/src/addon/mod/feedback/providers/offline.ts index b9fd588ef..86d604e16 100644 --- a/src/addon/mod/feedback/providers/offline.ts +++ b/src/addon/mod/feedback/providers/offline.ts @@ -70,10 +70,10 @@ export class AddonModFeedbackOfflineProvider { /** * Delete the stored for a certain feedback page. * - * @param {number} feedbackId Feedback ID. - * @param {number} page Page of the form to delete responses from. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if deleted, rejected if failure. + * @param feedbackId Feedback ID. + * @param page Page of the form to delete responses from. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if deleted, rejected if failure. */ deleteFeedbackPageResponses(feedbackId: number, page: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -84,8 +84,8 @@ export class AddonModFeedbackOfflineProvider { /** * Get all the stored feedback responses data from all the feedback. * - * @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. */ getAllFeedbackResponses(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -102,9 +102,9 @@ export class AddonModFeedbackOfflineProvider { /** * Get all the stored responses from a certain feedback. * - * @param {number} feedbackId Feedback ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with responses. + * @param feedbackId Feedback ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with responses. */ getFeedbackResponses(feedbackId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -121,10 +121,10 @@ export class AddonModFeedbackOfflineProvider { /** * Get the stored responses for a certain feedback page. * - * @param {number} feedbackId Feedback ID. - * @param {number} page Page of the form to get responses from. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with responses. + * @param feedbackId Feedback ID. + * @param page Page of the form to get responses from. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with responses. */ getFeedbackPageResponses(feedbackId: number, page: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -139,9 +139,9 @@ export class AddonModFeedbackOfflineProvider { /** * Get if the feedback have something to be synced. * - * @param {number} feedbackId Feedback ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with true if the feedback have something to be synced. + * @param feedbackId Feedback ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with true if the feedback have something to be synced. */ hasFeedbackOfflineData(feedbackId: number, siteId?: string): Promise { return this.getFeedbackResponses(feedbackId, siteId).then((responses) => { @@ -152,12 +152,12 @@ export class AddonModFeedbackOfflineProvider { /** * Save page responses to be sent later. * - * @param {number} feedbackId Feedback ID. - * @param {number} page The page being processed. - * @param {any} responses The data to be processed the key is the field name (usually type[index]_id) - * @param {number} courseId Course ID the feedback belongs to. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if stored, rejected if failure. + * @param feedbackId Feedback ID. + * @param page The page being processed. + * @param responses The data to be processed the key is the field name (usually type[index]_id) + * @param courseId Course ID the feedback belongs to. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if stored, rejected if failure. */ saveResponses(feedbackId: number, page: number, responses: any, courseId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { diff --git a/src/addon/mod/feedback/providers/prefetch-handler.ts b/src/addon/mod/feedback/providers/prefetch-handler.ts index 097e31331..07d3d0b84 100644 --- a/src/addon/mod/feedback/providers/prefetch-handler.ts +++ b/src/addon/mod/feedback/providers/prefetch-handler.ts @@ -52,10 +52,10 @@ export class AddonModFeedbackPrefetchHandler extends CoreCourseActivityPrefetchH /** * Get the list of downloadable files. * - * @param {any} module Module to get the files. - * @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 to get the files. + * @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 { let files = []; @@ -82,9 +82,9 @@ export class AddonModFeedbackPrefetchHandler extends CoreCourseActivityPrefetchH /** * Returns feedback 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.feedbackProvider.getFeedback(courseId, module.id).catch(() => { @@ -97,9 +97,9 @@ export class AddonModFeedbackPrefetchHandler extends CoreCourseActivityPrefetchH /** * 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.feedbackProvider.invalidateContent(moduleId, courseId); @@ -108,9 +108,9 @@ export class AddonModFeedbackPrefetchHandler extends CoreCourseActivityPrefetchH /** * 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 { return this.feedbackProvider.invalidateFeedbackData(courseId); @@ -121,9 +121,9 @@ export class AddonModFeedbackPrefetchHandler extends CoreCourseActivityPrefetchH * A feedback isn't downloadable if it's not open yet. * Closed feedback are downloadable because teachers can always see the results. * - * @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): boolean | Promise { return this.feedbackProvider.getFeedback(courseId, module.id, undefined, true).then((feedback) => { @@ -146,7 +146,7 @@ export class AddonModFeedbackPrefetchHandler extends CoreCourseActivityPrefetchH /** * 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.feedbackProvider.isPluginEnabled(); @@ -155,11 +155,11 @@ export class AddonModFeedbackPrefetchHandler 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.prefetchFeedback.bind(this)); @@ -168,11 +168,11 @@ export class AddonModFeedbackPrefetchHandler extends CoreCourseActivityPrefetchH /** * Prefetch a feedback. * - * @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. - * @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. + * @return Promise resolved when done. */ protected prefetchFeedback(module: any, courseId: number, single: boolean, siteId: string): Promise { // Prefetch the feedback data. @@ -232,10 +232,10 @@ export class AddonModFeedbackPrefetchHandler 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 { if (!this.syncProvider) { diff --git a/src/addon/mod/feedback/providers/print-link-handler.ts b/src/addon/mod/feedback/providers/print-link-handler.ts index 3be523950..978398fb0 100644 --- a/src/addon/mod/feedback/providers/print-link-handler.ts +++ b/src/addon/mod/feedback/providers/print-link-handler.ts @@ -38,11 +38,11 @@ export class AddonModFeedbackPrintLinkHandler 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 { @@ -71,11 +71,11 @@ export class AddonModFeedbackPrintLinkHandler 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 { if (typeof params.id == 'undefined') { diff --git a/src/addon/mod/feedback/providers/push-click-handler.ts b/src/addon/mod/feedback/providers/push-click-handler.ts index 675e7d0dd..27f288438 100644 --- a/src/addon/mod/feedback/providers/push-click-handler.ts +++ b/src/addon/mod/feedback/providers/push-click-handler.ts @@ -36,8 +36,8 @@ export class AddonModFeedbackPushClickHandler implements CorePushNotificationsCl /** * 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 == 'mod_feedback' && @@ -52,8 +52,8 @@ export class AddonModFeedbackPushClickHandler implements CorePushNotificationsCl /** * 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/feedback/providers/show-entries-link-handler.ts b/src/addon/mod/feedback/providers/show-entries-link-handler.ts index 498703386..195e3f33d 100644 --- a/src/addon/mod/feedback/providers/show-entries-link-handler.ts +++ b/src/addon/mod/feedback/providers/show-entries-link-handler.ts @@ -35,11 +35,11 @@ export class AddonModFeedbackShowEntriesLinkHandler extends CoreContentLinksHand /** * 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 AddonModFeedbackShowEntriesLinkHandler extends CoreContentLinksHand * 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.feedbackProvider.isPluginEnabled(siteId).then((enabled) => { diff --git a/src/addon/mod/feedback/providers/show-non-respondents-link-handler.ts b/src/addon/mod/feedback/providers/show-non-respondents-link-handler.ts index cb88ecaa3..8ed384cd6 100644 --- a/src/addon/mod/feedback/providers/show-non-respondents-link-handler.ts +++ b/src/addon/mod/feedback/providers/show-non-respondents-link-handler.ts @@ -38,11 +38,11 @@ export class AddonModFeedbackShowNonRespondentsLinkHandler extends CoreContentLi /** * 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 { @@ -70,11 +70,11 @@ export class AddonModFeedbackShowNonRespondentsLinkHandler extends CoreContentLi * 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.feedbackProvider.isPluginEnabled(siteId).then((enabled) => { diff --git a/src/addon/mod/feedback/providers/sync-cron-handler.ts b/src/addon/mod/feedback/providers/sync-cron-handler.ts index 94d43e905..2e455497a 100644 --- a/src/addon/mod/feedback/providers/sync-cron-handler.ts +++ b/src/addon/mod/feedback/providers/sync-cron-handler.ts @@ -29,9 +29,9 @@ export class AddonModFeedbackSyncCronHandler 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.feedbackSync.syncAllFeedbacks(siteId, force); @@ -40,7 +40,7 @@ export class AddonModFeedbackSyncCronHandler 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.feedbackSync.syncInterval; diff --git a/src/addon/mod/feedback/providers/sync.ts b/src/addon/mod/feedback/providers/sync.ts index c643f237b..72072a3cf 100644 --- a/src/addon/mod/feedback/providers/sync.ts +++ b/src/addon/mod/feedback/providers/sync.ts @@ -56,11 +56,11 @@ export class AddonModFeedbackSyncProvider extends CoreCourseActivitySyncBaseProv /** * 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 and timers. - * @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 and timers. + * @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$|^timers/; @@ -71,9 +71,9 @@ export class AddonModFeedbackSyncProvider extends CoreCourseActivitySyncBaseProv /** * Try to synchronize all the feedbacks 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. */ syncAllFeedbacks(siteId?: string, force?: boolean): Promise { return this.syncOnSites('all feedbacks', this.syncAllFeedbacksFunc.bind(this), [force], siteId); @@ -82,9 +82,9 @@ export class AddonModFeedbackSyncProvider extends CoreCourseActivitySyncBaseProv /** * Sync all pending feedbacks on a site. * - * @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. - * @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 force Wether to force sync not depending on last execution. + * @param Promise resolved if sync is successful, rejected if sync fails. */ protected syncAllFeedbacksFunc(siteId?: string, force?: boolean): Promise { // Sync all new responses. @@ -122,9 +122,9 @@ export class AddonModFeedbackSyncProvider extends CoreCourseActivitySyncBaseProv /** * Sync a feedback only if a certain time has passed since the last time. * - * @param {number} feedbackId Feedback ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the feedback is synced or if it doesn't need to be synced. + * @param feedbackId Feedback ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the feedback is synced or if it doesn't need to be synced. */ syncFeedbackIfNeeded(feedbackId: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -139,9 +139,9 @@ export class AddonModFeedbackSyncProvider extends CoreCourseActivitySyncBaseProv /** * Synchronize all offline responses of a feedback. * - * @param {number} feedbackId Feedback 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 feedbackId Feedback ID to be synced. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if sync is successful, rejected otherwise. */ syncFeedback(feedbackId: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -260,12 +260,12 @@ export class AddonModFeedbackSyncProvider extends CoreCourseActivitySyncBaseProv /** * Convenience function to sync process page calls. * - * @param {any} feedback Feedback object. - * @param {any} data Response data. - * @param {string} siteId Site Id. - * @param {number} timemodified Current completed modification time. - * @param {any} result Result object to be modified. - * @return {Promise} Resolve when done or rejected with error. + * @param feedback Feedback object. + * @param data Response data. + * @param siteId Site Id. + * @param timemodified Current completed modification time. + * @param result Result object to be modified. + * @return Resolve when done or rejected with error. */ protected processPage(feedback: any, data: any, siteId: string, timemodified: number, result: any): Promise { // Delete all pages that are submitted before changing website. diff --git a/src/addon/mod/folder/components/index/index.ts b/src/addon/mod/folder/components/index/index.ts index d8c296b00..d23b70e38 100644 --- a/src/addon/mod/folder/components/index/index.ts +++ b/src/addon/mod/folder/components/index/index.ts @@ -70,7 +70,7 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo /** * Perform the invalidate content function. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected invalidateContent(): Promise { return this.folderProvider.invalidateContent(this.module.id, this.courseId); @@ -78,7 +78,7 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo /** * Convenience function to set scope data using module. - * @param {any} module Module to show. + * @param module Module to show. */ protected showModuleData(module: any): void { this.description = module.intro || module.description; @@ -96,8 +96,8 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo /** * Download folder 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 promise; diff --git a/src/addon/mod/folder/pages/index/index.ts b/src/addon/mod/folder/pages/index/index.ts index e0b187c19..2131aa82e 100644 --- a/src/addon/mod/folder/pages/index/index.ts +++ b/src/addon/mod/folder/pages/index/index.ts @@ -42,7 +42,7 @@ export class AddonModFolderIndexPage { /** * Update some data based on the folder instance. * - * @param {any} folder Folder instance. + * @param folder Folder instance. */ updateData(folder: any): void { this.title = folder.name || this.title; diff --git a/src/addon/mod/folder/providers/folder.ts b/src/addon/mod/folder/providers/folder.ts index ce3336634..989a0c69a 100644 --- a/src/addon/mod/folder/providers/folder.ts +++ b/src/addon/mod/folder/providers/folder.ts @@ -38,10 +38,10 @@ export class AddonModFolderProvider { /** * Get a folder 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 book 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 book is retrieved. */ getFolder(courseId: number, cmId: number, siteId?: string): Promise { return this.getFolderByKey(courseId, 'coursemodule', cmId, siteId); @@ -50,11 +50,11 @@ export class AddonModFolderProvider { /** * Get a folder. * - * @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 book 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 book is retrieved. */ protected getFolderByKey(courseId: number, key: string, value: any, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -84,8 +84,8 @@ export class AddonModFolderProvider { /** * Get cache key for folder data WS calls. * - * @param {number} courseId Course ID. - * @return {string} Cache key. + * @param courseId Course ID. + * @return Cache key. */ protected getFolderCacheKey(courseId: number): string { return this.ROOT_CACHE_KEY + 'folder:' + courseId; @@ -94,10 +94,9 @@ export class AddonModFolderProvider { /** * 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} + * @param moduleId The module ID. + * @param courseId Course ID of the module. + * @param siteId Site ID. If not defined, current site. */ invalidateContent(moduleId: number, courseId: number, siteId?: string): Promise { const promises = []; @@ -111,9 +110,9 @@ export class AddonModFolderProvider { /** * Invalidates folder 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. */ invalidateFolderData(courseId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -124,7 +123,7 @@ export class AddonModFolderProvider { /** * Returns whether or not getFolder WS available or not. * - * @return {boolean} If WS is avalaible. + * @return If WS is avalaible. * @since 3.3 */ isGetFolderWSAvailable(): boolean { @@ -134,10 +133,10 @@ export class AddonModFolderProvider { /** * Report a folder as being viewed. * - * @param {number} id Module ID. - * @param {string} [name] Name of the folder. - * @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 folder. + * @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 = { diff --git a/src/addon/mod/folder/providers/helper.ts b/src/addon/mod/folder/providers/helper.ts index 9715a6a8a..38e4a4ae8 100644 --- a/src/addon/mod/folder/providers/helper.ts +++ b/src/addon/mod/folder/providers/helper.ts @@ -29,8 +29,8 @@ export class AddonModFolderHelperProvider { * Folders found in filepaths are added to the array. Each folder has the properties: name, fileicon, * type (folder), filepath and contents (array with files and subfolders). * - * @param {any[]} contents Folder contents. - * @return {any[]} Formatted contents. + * @param contents Folder contents. + * @return Formatted contents. */ formatContents(contents: any[]): any[] { const files = [], diff --git a/src/addon/mod/folder/providers/module-handler.ts b/src/addon/mod/folder/providers/module-handler.ts index 5d372a12c..b0a66e1ae 100644 --- a/src/addon/mod/folder/providers/module-handler.ts +++ b/src/addon/mod/folder/providers/module-handler.ts @@ -44,7 +44,7 @@ export class AddonModFolderModuleHandler 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 true; @@ -53,10 +53,10 @@ export class AddonModFolderModuleHandler 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 { @@ -78,9 +78,9 @@ export class AddonModFolderModuleHandler 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 AddonModFolderIndexComponent; diff --git a/src/addon/mod/folder/providers/pluginfile-handler.ts b/src/addon/mod/folder/providers/pluginfile-handler.ts index 943d2b0d4..ea5b6a576 100644 --- a/src/addon/mod/folder/providers/pluginfile-handler.ts +++ b/src/addon/mod/folder/providers/pluginfile-handler.ts @@ -26,8 +26,8 @@ export class AddonModFolderPluginFileHandler 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,8 +40,8 @@ export class AddonModFolderPluginFileHandler 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 diff --git a/src/addon/mod/folder/providers/prefetch-handler.ts b/src/addon/mod/folder/providers/prefetch-handler.ts index 77197e136..f03180806 100644 --- a/src/addon/mod/folder/providers/prefetch-handler.ts +++ b/src/addon/mod/folder/providers/prefetch-handler.ts @@ -42,13 +42,13 @@ export class AddonModFolderPrefetchHandler extends CoreCourseResourcePrefetchHan /** * 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 promises = []; @@ -65,9 +65,9 @@ export class AddonModFolderPrefetchHandler extends CoreCourseResourcePrefetchHan /** * 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.folderProvider.invalidateContent(moduleId, courseId); @@ -76,9 +76,9 @@ export class AddonModFolderPrefetchHandler extends CoreCourseResourcePrefetchHan /** * 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/forum/components/index/index.ts b/src/addon/mod/forum/components/index/index.ts index b3a4209b6..9d3f84803 100644 --- a/src/addon/mod/forum/components/index/index.ts +++ b/src/addon/mod/forum/components/index/index.ts @@ -163,10 +163,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; @@ -253,7 +253,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 +299,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 +371,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 +387,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 +408,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 +431,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 +440,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 +450,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 +461,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 +499,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 +515,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 +532,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) { diff --git a/src/addon/mod/forum/components/post/post.ts b/src/addon/mod/forum/components/post/post.ts index d6a9ca54d..d940e20da 100644 --- a/src/addon/mod/forum/components/post/post.ts +++ b/src/addon/mod/forum/components/post/post.ts @@ -83,12 +83,12 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy { /** * 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. + * @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 setReplyData(replyingTo?: number, isEditing?: boolean, subject?: string, message?: string, files?: any[], isPrivate?: boolean): void { @@ -172,7 +172,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; @@ -338,7 +338,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/pages/discussion/discussion.ts b/src/addon/mod/forum/pages/discussion/discussion.ts index 7c395fc57..278f21f7d 100644 --- a/src/addon/mod/forum/pages/discussion/discussion.ts +++ b/src/addon/mod/forum/pages/discussion/discussion.ts @@ -188,7 +188,7 @@ export class AddonModForumDiscussionPage 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; @@ -209,7 +209,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 +225,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; @@ -405,8 +405,8 @@ export class AddonModForumDiscussionPage implements OnDestroy { /** * 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 +446,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 +465,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); @@ -491,8 +491,8 @@ 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; @@ -505,7 +505,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 +532,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 +559,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); diff --git a/src/addon/mod/forum/pages/index/index.ts b/src/addon/mod/forum/pages/index/index.ts index 102e0b88f..97b78dc3a 100644 --- a/src/addon/mod/forum/pages/index/index.ts +++ b/src/addon/mod/forum/pages/index/index.ts @@ -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.ts b/src/addon/mod/forum/pages/new-discussion/new-discussion.ts index 337966e1d..1291edfaa 100644 --- a/src/addon/mod/forum/pages/new-discussion/new-discussion.ts +++ b/src/addon/mod/forum/pages/new-discussion/new-discussion.ts @@ -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.ts b/src/addon/mod/forum/pages/sort-order-selector/sort-order-selector.ts index 0d83646a6..eb69c3270 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 @@ -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..3b304cf5f 100644 --- a/src/addon/mod/forum/providers/discussion-link-handler.ts +++ b/src/addon/mod/forum/providers/discussion-link-handler.ts @@ -34,12 +34,12 @@ 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 { @@ -67,11 +67,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..2bb8872e2 100644 --- a/src/addon/mod/forum/providers/forum.ts +++ b/src/addon/mod/forum/providers/forum.ts @@ -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; @@ -75,8 +75,8 @@ export class AddonModForumProvider { /** * Get common part of cache key for can add discussion WS calls. * - * @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 + ':'; @@ -85,8 +85,8 @@ export class AddonModForumProvider { /** * 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 getForumDataCacheKey(courseId: number): string { return this.ROOT_CACHE_KEY + 'forum:' + courseId; @@ -95,8 +95,8 @@ export class AddonModForumProvider { /** * Get cache key for forum access information WS calls. * - * @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; @@ -105,8 +105,8 @@ export class AddonModForumProvider { /** * Get cache key for forum discussion posts WS calls. * - * @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 +115,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 +132,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 +168,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,11 +208,11 @@ 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); @@ -221,8 +221,8 @@ export class AddonModForumProvider { /** * 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. @@ -238,7 +238,7 @@ export class AddonModForumProvider { /** * 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']); @@ -247,9 +247,9 @@ export class AddonModForumProvider { /** * 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 +288,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) => { @@ -309,10 +309,10 @@ export class AddonModForumProvider { /** * 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 +328,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 +347,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 +375,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 +403,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 +422,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 +434,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 +475,14 @@ 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. + * - 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 +555,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 +606,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 +620,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. @@ -659,9 +659,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,9 +672,9 @@ 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 siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateDiscussionPosts(discussionId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -685,9 +685,9 @@ export class AddonModForumProvider { /** * 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 +700,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 +712,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 +722,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 +739,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 +757,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 +811,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 +840,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 resvoled when done. * @since 3.7 */ setLockState(forumId: number, discussionId: number, locked: boolean, siteId?: string): Promise { @@ -862,8 +862,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 +875,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 resvoled when done. * @since 3.7 */ setPinState(discussionId: number, pinned: boolean, siteId?: string): Promise { @@ -895,10 +895,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 resvoled when done. * @since 3.7 */ toggleFavouriteState(discussionId: number, starred: boolean, siteId?: string): Promise { @@ -915,7 +915,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 = {}; diff --git a/src/addon/mod/forum/providers/helper.ts b/src/addon/mod/forum/providers/helper.ts index fb46fab35..202241d23 100644 --- a/src/addon/mod/forum/providers/helper.ts +++ b/src/addon/mod/forum/providers/helper.ts @@ -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(); @@ -299,10 +299,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 +313,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 +328,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 +352,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 +363,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 +375,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 +392,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 +409,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 +428,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..fce6e746f 100644 --- a/src/addon/mod/forum/providers/index-link-handler.ts +++ b/src/addon/mod/forum/providers/index-link-handler.ts @@ -39,11 +39,11 @@ export class AddonModForumIndexLinkHandler extends CoreContentLinksModuleIndexHa * 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; @@ -52,11 +52,11 @@ export class AddonModForumIndexLinkHandler extends CoreContentLinksModuleIndexHa /** * 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/forum/providers/module-handler.ts b/src/addon/mod/forum/providers/module-handler.ts index f87f93d03..860dea961 100644 --- a/src/addon/mod/forum/providers/module-handler.ts +++ b/src/addon/mod/forum/providers/module-handler.ts @@ -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..2a1f161a3 100644 --- a/src/addon/mod/forum/providers/offline.ts +++ b/src/addon/mod/forum/providers/offline.ts @@ -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..9dba78fb0 100644 --- a/src/addon/mod/forum/providers/post-link-handler.ts +++ b/src/addon/mod/forum/providers/post-link-handler.ts @@ -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..78ddd67d3 100644 --- a/src/addon/mod/forum/providers/prefetch-handler.ts +++ b/src/addon/mod/forum/providers/prefetch-handler.ts @@ -54,10 +54,10 @@ export class AddonModForumPrefetchHandler extends CoreCourseActivityPrefetchHand /** * 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 +77,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 = []; @@ -101,8 +101,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 +145,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 +157,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 +174,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 +187,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 +239,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 +299,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..6c9bdced2 100644 --- a/src/addon/mod/forum/providers/push-click-handler.ts +++ b/src/addon/mod/forum/providers/push-click-handler.ts @@ -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), diff --git a/src/addon/mod/forum/providers/sync-cron-handler.ts b/src/addon/mod/forum/providers/sync-cron-handler.ts index 53a2406e7..65a9bc73c 100644 --- a/src/addon/mod/forum/providers/sync-cron-handler.ts +++ b/src/addon/mod/forum/providers/sync-cron-handler.ts @@ -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..65441da66 100644 --- a/src/addon/mod/forum/providers/sync.ts +++ b/src/addon/mod/forum/providers/sync.ts @@ -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(); @@ -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(); @@ -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..528cc8236 100644 --- a/src/addon/mod/forum/providers/tag-area-handler.ts +++ b/src/addon/mod/forum/providers/tag-area-handler.ts @@ -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/index/index.ts b/src/addon/mod/glossary/components/index/index.ts index ed3d807e9..7572f19b6 100644 --- a/src/addon/mod/glossary/components/index/index.ts +++ b/src/addon/mod/glossary/components/index/index.ts @@ -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) => { @@ -167,8 +167,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 +196,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 +217,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 +226,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 +236,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 +247,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 +321,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 +336,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 +371,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 +385,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 +401,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 +413,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..d8a22f237 100644 --- a/src/addon/mod/glossary/components/mode-picker/mode-picker.ts +++ b/src/addon/mod/glossary/components/mode-picker/mode-picker.ts @@ -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/pages/edit/edit.ts b/src/addon/mod/glossary/pages/edit/edit.ts index 837b0e760..d61f40e96 100644 --- a/src/addon/mod/glossary/pages/edit/edit.ts +++ b/src/addon/mod/glossary/pages/edit/edit.ts @@ -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.ts b/src/addon/mod/glossary/pages/entry/entry.ts index a5b38641d..238185d53 100644 --- a/src/addon/mod/glossary/pages/entry/entry.ts +++ b/src/addon/mod/glossary/pages/entry/entry.ts @@ -66,8 +66,8 @@ 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 { return this.glossaryProvider.invalidateEntry(this.entry.id).catch(() => { @@ -82,8 +82,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.ts b/src/addon/mod/glossary/pages/index/index.ts index a812235fe..f653663ba 100644 --- a/src/addon/mod/glossary/pages/index/index.ts +++ b/src/addon/mod/glossary/pages/index/index.ts @@ -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..78da8a40d 100644 --- a/src/addon/mod/glossary/providers/edit-link-handler.ts +++ b/src/addon/mod/glossary/providers/edit-link-handler.ts @@ -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..9ae7f72b4 100644 --- a/src/addon/mod/glossary/providers/entry-link-handler.ts +++ b/src/addon/mod/glossary/providers/entry-link-handler.ts @@ -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..b31e09900 100644 --- a/src/addon/mod/glossary/providers/glossary.ts +++ b/src/addon/mod/glossary/providers/glossary.ts @@ -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..4f9c86636 100644 --- a/src/addon/mod/glossary/providers/helper.ts +++ b/src/addon/mod/glossary/providers/helper.ts @@ -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/module-handler.ts b/src/addon/mod/glossary/providers/module-handler.ts index 88b952b13..9e2df7a4b 100644 --- a/src/addon/mod/glossary/providers/module-handler.ts +++ b/src/addon/mod/glossary/providers/module-handler.ts @@ -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..b50c3a34c 100644 --- a/src/addon/mod/glossary/providers/offline.ts +++ b/src/addon/mod/glossary/providers/offline.ts @@ -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..5b852178c 100644 --- a/src/addon/mod/glossary/providers/prefetch-handler.ts +++ b/src/addon/mod/glossary/providers/prefetch-handler.ts @@ -50,10 +50,10 @@ export class AddonModGlossaryPrefetchHandler extends CoreCourseActivityPrefetchH /** * 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 +70,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); @@ -96,9 +96,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 +107,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 +120,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(); @@ -193,10 +193,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..52ed49b68 100644 --- a/src/addon/mod/glossary/providers/sync-cron-handler.ts +++ b/src/addon/mod/glossary/providers/sync-cron-handler.ts @@ -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..da90b1f65 100644 --- a/src/addon/mod/glossary/providers/sync.ts +++ b/src/addon/mod/glossary/providers/sync.ts @@ -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..fc1d3345e 100644 --- a/src/addon/mod/glossary/providers/tag-area-handler.ts +++ b/src/addon/mod/glossary/providers/tag-area-handler.ts @@ -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/index/index.ts b/src/addon/mod/imscp/components/index/index.ts index 3b793da19..37edb9b87 100644 --- a/src/addon/mod/imscp/components/index/index.ts +++ b/src/addon/mod/imscp/components/index/index.ts @@ -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,8 +71,8 @@ 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; @@ -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/pages/index/index.ts b/src/addon/mod/imscp/pages/index/index.ts index b94d81b8b..8300d2629 100644 --- a/src/addon/mod/imscp/pages/index/index.ts +++ b/src/addon/mod/imscp/pages/index/index.ts @@ -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.ts b/src/addon/mod/imscp/pages/toc/toc.ts index 1880e4813..80f67f1b7 100644 --- a/src/addon/mod/imscp/pages/toc/toc.ts +++ b/src/addon/mod/imscp/pages/toc/toc.ts @@ -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..366cb7bf6 100644 --- a/src/addon/mod/imscp/providers/imscp.ts +++ b/src/addon/mod/imscp/providers/imscp.ts @@ -39,8 +39,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 +53,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 +72,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 +93,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 +114,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 +131,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 +141,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,11 +151,11 @@ 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 { return this.sitesProvider.getSite(siteId).then((site) => { @@ -183,10 +183,10 @@ 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 { return this.getImscpByKey(courseId, 'coursemodule', cmId, siteId); @@ -195,9 +195,9 @@ export class AddonModImscpProvider { /** * 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 +218,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) { @@ -254,10 +254,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 +274,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 +288,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,8 +298,8 @@ 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 { return this.sitesProvider.getSite(siteId).then((site) => { @@ -310,10 +310,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 = { diff --git a/src/addon/mod/imscp/providers/list-link-handler.ts b/src/addon/mod/imscp/providers/list-link-handler.ts index 21bb3dbc4..eec7da06e 100644 --- a/src/addon/mod/imscp/providers/list-link-handler.ts +++ b/src/addon/mod/imscp/providers/list-link-handler.ts @@ -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..dc3abc139 100644 --- a/src/addon/mod/imscp/providers/module-handler.ts +++ b/src/addon/mod/imscp/providers/module-handler.ts @@ -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..d15124b18 100644 --- a/src/addon/mod/imscp/providers/pluginfile-handler.ts +++ b/src/addon/mod/imscp/providers/pluginfile-handler.ts @@ -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,8 +47,8 @@ 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 diff --git a/src/addon/mod/imscp/providers/prefetch-handler.ts b/src/addon/mod/imscp/providers/prefetch-handler.ts index 6f8a10c9e..65d49dbbc 100644 --- a/src/addon/mod/imscp/providers/prefetch-handler.ts +++ b/src/addon/mod/imscp/providers/prefetch-handler.ts @@ -42,13 +42,13 @@ export class AddonModImscpPrefetchHandler extends CoreCourseResourcePrefetchHand /** * 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 +66,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 +81,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 +92,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 +108,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 +117,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/providers/label.ts b/src/addon/mod/label/providers/label.ts index 2b4e79534..ec75856b6 100644 --- a/src/addon/mod/label/providers/label.ts +++ b/src/addon/mod/label/providers/label.ts @@ -33,8 +33,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,13 +43,13 @@ 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 { @@ -86,12 +86,12 @@ 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 { return this.getLabelByField(courseId, 'coursemodule', cmId, forceCache, ignoreCache, siteId); @@ -100,12 +100,12 @@ export class AddonModLabelProvider { /** * 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 { return this.getLabelByField(courseId, 'id', labelId, forceCache, ignoreCache, siteId); @@ -114,9 +114,9 @@ export class AddonModLabelProvider { /** * 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 +127,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 +147,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 +160,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 { diff --git a/src/addon/mod/label/providers/module-handler.ts b/src/addon/mod/label/providers/module-handler.ts index 8c8ecfeb8..6a8f72a75 100644 --- a/src/addon/mod/label/providers/module-handler.ts +++ b/src/addon/mod/label/providers/module-handler.ts @@ -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..a52ad9ab8 100644 --- a/src/addon/mod/label/providers/prefetch-handler.ts +++ b/src/addon/mod/label/providers/prefetch-handler.ts @@ -44,10 +44,10 @@ export class AddonModLabelPrefetchHandler extends CoreCourseResourcePrefetchHand /** * 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 +66,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 +77,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/index/index.ts b/src/addon/mod/lesson/components/index/index.ts index b179e7993..d236da208 100644 --- a/src/addon/mod/lesson/components/index/index.ts +++ b/src/addon/mod/lesson/components/index/index.ts @@ -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 { @@ -217,7 +217,7 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo /** * 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 +232,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 +292,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 +316,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 +326,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; @@ -355,8 +355,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 +426,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 +498,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 +508,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 +547,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 +591,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 +611,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/pages/index/index.ts b/src/addon/mod/lesson/pages/index/index.ts index 838cbe8a3..8899c8077 100644 --- a/src/addon/mod/lesson/pages/index/index.ts +++ b/src/addon/mod/lesson/pages/index/index.ts @@ -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.ts b/src/addon/mod/lesson/pages/menu-modal/menu-modal.ts index a9406ceb4..e9548064c 100644 --- a/src/addon/mod/lesson/pages/menu-modal/menu-modal.ts +++ b/src/addon/mod/lesson/pages/menu-modal/menu-modal.ts @@ -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.ts b/src/addon/mod/lesson/pages/password-modal/password-modal.ts index d748163eb..b1e7699a5 100644 --- a/src/addon/mod/lesson/pages/password-modal/password-modal.ts +++ b/src/addon/mod/lesson/pages/password-modal/password-modal.ts @@ -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.ts b/src/addon/mod/lesson/pages/player/player.ts index a19c4e48f..8c96a6721 100644 --- a/src/addon/mod/lesson/pages/player/player.ts +++ b/src/addon/mod/lesson/pages/player/player.ts @@ -130,7 +130,7 @@ export class AddonModLessonPlayerPage 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) { @@ -157,7 +157,7 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy { /** * A button was clicked. * - * @param {any} data Button data. + * @param data Button data. */ buttonClicked(data: any): void { this.processPage(data); @@ -166,11 +166,11 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy { /** * Call a function and go offline if allowed and the call fails. * - * @param {Function} func Function to call. - * @param {any[]} args Arguments to pass to the function. - * @param {number} offlineParamPos Position of the offline parameter in the args. - * @param {number} [jumpsParamPos] Position of the jumps parameter in the args. - * @return {Promise} Promise resolved in success, rejected otherwise. + * @param func Function to call. + * @param args Arguments to pass to the function. + * @param offlineParamPos Position of the offline parameter in the args. + * @param jumpsParamPos Position of the jumps parameter in the args. + * @return Promise resolved in success, rejected otherwise. */ protected callFunction(func: Function, args: any[], offlineParamPos: number, jumpsParamPos?: number): Promise { return func.apply(func, args).catch((error) => { @@ -200,8 +200,8 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy { /** * Change the page from menu or when continuing from a feedback page. * - * @param {number} pageId Page to load. - * @param {boolean} [ignoreCurrent] If true, allow loading current page. + * @param pageId Page to load. + * @param ignoreCurrent If true, allow loading current page. */ changePage(pageId: number, ignoreCurrent?: boolean): void { if (!ignoreCurrent && !this.eolData && this.currentPage == pageId) { @@ -222,7 +222,7 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy { /** * Get the lesson data and load the page. * - * @return {Promise} Promise resolved with true if success, resolved with false otherwise. + * @return Promise resolved with true if success, resolved with false otherwise. */ protected fetchLessonData(): Promise { // Wait for any ongoing sync to finish. We won't sync a lesson while it's being played. @@ -315,8 +315,8 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy { /** * Finish the retake. * - * @param {boolean} [outOfTime] Whether the retake is finished because the user ran out of time. - * @return {Promise} Promise resolved when done. + * @param outOfTime Whether the retake is finished because the user ran out of time. + * @return Promise resolved when done. */ protected finishRetake(outOfTime?: boolean): Promise { let promise; @@ -386,8 +386,8 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy { /** * Jump to a certain page after performing an action. * - * @param {number} pageId The page to load. - * @return {Promise} Promise resolved when done. + * @param pageId The page to load. + * @return Promise resolved when done. */ protected jumpToPage(pageId: number): Promise { if (pageId === 0) { @@ -411,8 +411,8 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy { /** * Start or continue a retake. * - * @param {number} pageId The page to load. - * @return {Promise} Promise resolved when done. + * @param pageId The page to load. + * @return Promise resolved when done. */ protected launchRetake(pageId: number): Promise { let promise; @@ -453,7 +453,7 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy { /** * Load the lesson menu. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected loadMenu(): Promise { if (this.loadingMenu) { @@ -479,8 +479,8 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy { /** * Load a certain page. * - * @param {number} pageId The page to load. - * @return {Promise} Promise resolved when done. + * @param pageId The page to load. + * @return Promise resolved when done. */ protected loadPage(pageId: number): Promise { if (pageId == AddonModLessonProvider.LESSON_EOL) { @@ -535,8 +535,8 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy { /** * Process a page, sending some data. * - * @param {any} data The data to send. - * @return {Promise} Promise resolved when done. + * @param data The data to send. + * @return Promise resolved when done. */ protected processPage(data: any): Promise { this.loaded = false; @@ -605,7 +605,7 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy { /** * Review the lesson. * - * @param {number} pageId Page to load. + * @param pageId Page to load. */ reviewLesson(pageId: number): void { this.loaded = false; @@ -622,7 +622,7 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy { /** * Submit a question. * - * @param {Event} e Event. + * @param e Event. */ submitQuestion(e: Event): void { e.preventDefault(); diff --git a/src/addon/mod/lesson/pages/user-retake/user-retake.ts b/src/addon/mod/lesson/pages/user-retake/user-retake.ts index 394f78176..48a8ff7e9 100644 --- a/src/addon/mod/lesson/pages/user-retake/user-retake.ts +++ b/src/addon/mod/lesson/pages/user-retake/user-retake.ts @@ -72,7 +72,7 @@ export class AddonModLessonUserRetakePage implements OnInit { /** * Change the retake displayed. * - * @param {number} retakeNumber The new retake number. + * @param retakeNumber The new retake number. */ changeRetake(retakeNumber: number): void { this.loaded = false; @@ -88,7 +88,7 @@ export class AddonModLessonUserRetakePage implements OnInit { /** * Pull to refresh. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ doRefresh(refresher: any): void { this.refreshData().finally(() => { @@ -99,7 +99,7 @@ export class AddonModLessonUserRetakePage implements OnInit { /** * Get lesson and retake data. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchData(): Promise { return this.lessonProvider.getLessonById(this.courseId, this.lessonId).then((lessonData) => { @@ -166,7 +166,7 @@ export class AddonModLessonUserRetakePage implements OnInit { /** * Refreshes data. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected refreshData(): Promise { const promises = []; @@ -187,8 +187,8 @@ export class AddonModLessonUserRetakePage implements OnInit { /** * Set the retake to view and load its data. * - * @param {number}retakeNumber Retake number to set. - * @return {Promise} Promise resolved when done. + * @param retakeNumber Retake number to set. + * @return Promise resolved when done. */ protected setRetake(retakeNumber: number): Promise { this.selectedRetake = retakeNumber; diff --git a/src/addon/mod/lesson/providers/grade-link-handler.ts b/src/addon/mod/lesson/providers/grade-link-handler.ts index f76eb8b52..27bf35d6e 100644 --- a/src/addon/mod/lesson/providers/grade-link-handler.ts +++ b/src/addon/mod/lesson/providers/grade-link-handler.ts @@ -39,12 +39,12 @@ export class AddonModLessonGradeLinkHandler extends CoreContentLinksModuleGradeH /** * 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 { @@ -83,11 +83,11 @@ export class AddonModLessonGradeLinkHandler extends CoreContentLinksModuleGradeH * 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.lessonProvider.isPluginEnabled(); diff --git a/src/addon/mod/lesson/providers/helper.ts b/src/addon/mod/lesson/providers/helper.ts index 234c0fd45..0fadbf3f5 100644 --- a/src/addon/mod/lesson/providers/helper.ts +++ b/src/addon/mod/lesson/providers/helper.ts @@ -33,8 +33,8 @@ export class AddonModLessonHelperProvider { /** * Given the HTML of next activity link, format it to extract the href and the text. * - * @param {string} activityLink HTML of the activity link. - * @return {{formatted: boolean, label: string, href: string}} Formatted data. + * @param activityLink HTML of the activity link. + * @return Formatted data. */ formatActivityLink(activityLink: string): {formatted: boolean, label: string, href: string} { const element = this.domUtils.convertToElement(activityLink), @@ -59,8 +59,8 @@ export class AddonModLessonHelperProvider { /** * Given the HTML of an answer from a content page, extract the data to render the answer. * - * @param {String} html Answer's HTML. - * @return {{buttonText: string, content: string}} Data to render the answer. + * @param html Answer's HTML. + * @return Data to render the answer. */ getContentPageAnswerDataFromHtml(html: string): {buttonText: string, content: string} { const data = { @@ -86,8 +86,8 @@ export class AddonModLessonHelperProvider { /** * Get the buttons to change pages. * - * @param {string} html Page's HTML. - * @return {any[]} List of buttons. + * @param html Page's HTML. + * @return List of buttons. */ getPageButtonsFromHtml(html: string): any[] { const buttons = [], @@ -138,8 +138,8 @@ export class AddonModLessonHelperProvider { /** * Given a page data (result of getPageData), get the page contents. * - * @param {any} data Page data. - * @return {string} Page contents. + * @param data Page data. + * @return Page contents. */ getPageContentsFromPageData(data: any): string { // Search the page contents inside the whole page HTML. Use data.pagecontent because it's filtered. @@ -164,9 +164,9 @@ export class AddonModLessonHelperProvider { /** * Get a question and all the data required to render it from the page data (result of AddonModLessonProvider.getPageData). * - * @param {FormGroup} questionForm The form group where to add the controls. - * @param {any} pageData Page data (result of $mmaModLesson#getPageData). - * @return {any} Question data. + * @param questionForm The form group where to add the controls. + * @param pageData Page data (result of $mmaModLesson#getPageData). + * @return Question data. */ getQuestionFromPageData(questionForm: FormGroup, pageData: any): any { const question: any = {}, @@ -359,9 +359,9 @@ export class AddonModLessonHelperProvider { /** * Given the HTML of an answer from a question page, extract the data to render the answer. * - * @param {string} html Answer's HTML. - * @return {any} Object with the data to render the answer. If the answer doesn't require any parsing, return a string with - * the HTML. + * @param html Answer's HTML. + * @return Object with the data to render the answer. If the answer doesn't require any parsing, return a string with + * the HTML. */ getQuestionPageAnswerDataFromHtml(html: string): any { const data: any = {}, @@ -419,9 +419,9 @@ export class AddonModLessonHelperProvider { /** * Get a label to identify a retake (lesson attempt). * - * @param {any} retake Retake object. - * @param {boolean} [includeDuration] Whether to include the duration of the retake. - * @return {string} Retake label. + * @param retake Retake object. + * @param includeDuration Whether to include the duration of the retake. + * @return Retake label. */ getRetakeLabel(retake: any, includeDuration?: boolean): string { const data = { @@ -455,9 +455,9 @@ export class AddonModLessonHelperProvider { /** * Prepare the question data to be sent to server. * - * @param {any} question Question to prepare. - * @param {any} data Data to prepare. - * @return {any} Data to send. + * @param question Question to prepare. + * @param data Data to prepare. + * @return Data to send. */ prepareQuestionData(question: any, data: any): any { if (question.template == 'essay' && question.textarea) { @@ -478,8 +478,8 @@ export class AddonModLessonHelperProvider { /** * Given the feedback of a process page in HTML, remove the question text. * - * @param {string} html Feedback's HTML. - * @return {string} Feedback without the question text. + * @param html Feedback's HTML. + * @return Feedback without the question text. */ removeQuestionFromFeedback(html: string): string { const element = this.domUtils.convertToElement(html); diff --git a/src/addon/mod/lesson/providers/index-link-handler.ts b/src/addon/mod/lesson/providers/index-link-handler.ts index 16d77cd6e..6228dcbf4 100644 --- a/src/addon/mod/lesson/providers/index-link-handler.ts +++ b/src/addon/mod/lesson/providers/index-link-handler.ts @@ -36,11 +36,11 @@ export class AddonModLessonIndexLinkHandler extends CoreContentLinksModuleIndexH /** * 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 { @@ -65,11 +65,11 @@ export class AddonModLessonIndexLinkHandler extends CoreContentLinksModuleIndexH * 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.lessonProvider.isPluginEnabled(); @@ -78,12 +78,12 @@ export class AddonModLessonIndexLinkHandler extends CoreContentLinksModuleIndexH /** * Navigate to a lesson module (index page) with a fixed password. * - * @param {number} moduleId Module ID. - * @param {number} courseId Course ID. - * @param {string} password Password. - * @param {string} siteId Site ID. - * @param {NavController} navCtrl Navigation controller. - * @return {Promise} Promise resolved when navigated. + * @param moduleId Module ID. + * @param courseId Course ID. + * @param password Password. + * @param siteId Site ID. + * @param navCtrl Navigation controller. + * @return Promise resolved when navigated. */ protected navigateToModuleWithPassword(moduleId: number, courseId: number, password: string, siteId: string, navCtrl?: NavController): Promise { diff --git a/src/addon/mod/lesson/providers/lesson-offline.ts b/src/addon/mod/lesson/providers/lesson-offline.ts index 1d7882461..7cfc872fb 100644 --- a/src/addon/mod/lesson/providers/lesson-offline.ts +++ b/src/addon/mod/lesson/providers/lesson-offline.ts @@ -138,12 +138,12 @@ export class AddonModLessonOfflineProvider { /** * Delete an offline attempt. * - * @param {number} lessonId Lesson ID. - * @param {number} retake Lesson retake number. - * @param {number} pageId Page ID. - * @param {number} timemodified The timemodified of the attempt. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param lessonId Lesson ID. + * @param retake Lesson retake number. + * @param pageId Page ID. + * @param timemodified The timemodified of the attempt. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ deleteAttempt(lessonId: number, retake: number, pageId: number, timemodified: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -159,9 +159,9 @@ export class AddonModLessonOfflineProvider { /** * Delete offline lesson retake. * - * @param {number} lessonId Lesson ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param lessonId Lesson ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ deleteRetake(lessonId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -172,11 +172,11 @@ export class AddonModLessonOfflineProvider { /** * Delete offline attempts for a retake and page. * - * @param {number} lessonId Lesson ID. - * @param {number} retake Lesson retake number. - * @param {number} pageId Page ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param lessonId Lesson ID. + * @param retake Lesson retake number. + * @param pageId Page ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ deleteRetakeAttemptsForPage(lessonId: number, retake: number, pageId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -188,13 +188,13 @@ export class AddonModLessonOfflineProvider { /** * Mark a retake as finished. * - * @param {number} lessonId Lesson ID. - * @param {number} courseId Course ID the lesson belongs to. - * @param {number} retake Retake number. - * @param {boolean} finished Whether retake is finished. - * @param {boolean} outOfTime If the user ran out of time. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved in success, rejected otherwise. + * @param lessonId Lesson ID. + * @param courseId Course ID the lesson belongs to. + * @param retake Retake number. + * @param finished Whether retake is finished. + * @param outOfTime If the user ran out of time. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved in success, rejected otherwise. */ finishRetake(lessonId: number, courseId: number, retake: number, finished?: boolean, outOfTime?: boolean, siteId?: string) : Promise { @@ -214,8 +214,8 @@ export class AddonModLessonOfflineProvider { /** * Get all the offline page attempts in a certain site. * - * @param {string} [siteId] Site ID. If not set, use current site. - * @return {Promise} Promise resolved when the offline attempts are retrieved. + * @param siteId Site ID. If not set, use current site. + * @return Promise resolved when the offline attempts are retrieved. */ getAllAttempts(siteId?: string): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { @@ -228,8 +228,8 @@ export class AddonModLessonOfflineProvider { /** * Get all the lessons that have offline data in a certain site. * - * @param {string} [siteId] Site ID. If not set, use current site. - * @return {Promise} Promise resolved with an object containing the lessons. + * @param siteId Site ID. If not set, use current site. + * @return Promise resolved with an object containing the lessons. */ getAllLessonsWithData(siteId?: string): Promise { const promises = [], @@ -257,8 +257,8 @@ export class AddonModLessonOfflineProvider { /** * Get all the offline retakes in a certain site. * - * @param {string} [siteId] Site ID. If not set, use current site. - * @return {Promise} Promise resolved when the offline retakes are retrieved. + * @param siteId Site ID. If not set, use current site. + * @return Promise resolved when the offline retakes are retrieved. */ getAllRetakes(siteId?: string): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { @@ -269,10 +269,10 @@ export class AddonModLessonOfflineProvider { /** * Retrieve the last offline attempt stored in a retake. * - * @param {number} lessonId Lesson ID. - * @param {number} retake Retake number. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the attempt (undefined if no attempts). + * @param lessonId Lesson ID. + * @param retake Retake number. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the attempt (undefined if no attempts). */ getLastQuestionPageAttempt(lessonId: number, retake: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -297,9 +297,9 @@ export class AddonModLessonOfflineProvider { /** * Retrieve all offline attempts for a lesson. * - * @param {number} lessonId Lesson ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the attempts. + * @param lessonId Lesson ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the attempts. */ getLessonAttempts(lessonId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -312,8 +312,8 @@ export class AddonModLessonOfflineProvider { /** * Given a list of DB entries (either retakes or page attempts), get the list of lessons. * - * @param {any} lessons Object where to store the lessons. - * @param {any[]} entries List of DB entries. + * @param lessons Object where to store the lessons. + * @param entries List of DB entries. */ protected getLessonsFromEntries(lessons: any, entries: any[]): void { entries.forEach((entry) => { @@ -329,12 +329,12 @@ export class AddonModLessonOfflineProvider { /** * Get attempts for question pages and retake in a lesson. * - * @param {number} lessonId Lesson ID. - * @param {number} retake Retake number. - * @param {boolean} [correct] True to only fetch correct attempts, false to get them all. - * @param {number} [pageId] If defined, only get attempts on this page. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the attempts. + * @param lessonId Lesson ID. + * @param retake Retake number. + * @param correct True to only fetch correct attempts, false to get them all. + * @param pageId If defined, only get attempts on this page. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the attempts. */ getQuestionsAttempts(lessonId: number, retake: number, correct?: boolean, pageId?: number, siteId?: string): Promise { let promise; @@ -361,9 +361,9 @@ export class AddonModLessonOfflineProvider { /** * Retrieve a retake from site DB. * - * @param {number} lessonId Lesson ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the retake. + * @param lessonId Lesson ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the retake. */ getRetake(lessonId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -374,10 +374,10 @@ export class AddonModLessonOfflineProvider { /** * Retrieve all offline attempts for a retake. * - * @param {number} lessonId Lesson ID. - * @param {number} retake Retake number. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the retake attempts. + * @param lessonId Lesson ID. + * @param retake Retake number. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the retake attempts. */ getRetakeAttempts(lessonId: number, retake: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -390,11 +390,11 @@ export class AddonModLessonOfflineProvider { /** * Retrieve offline attempts for a retake and page. * - * @param {number} lessonId Lesson ID. - * @param {number} retake Lesson retake number. - * @param {number} pageId Page ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the retake attempts. + * @param lessonId Lesson ID. + * @param retake Lesson retake number. + * @param pageId Page ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the retake attempts. */ getRetakeAttemptsForPage(lessonId: number, retake: number, pageId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -408,11 +408,11 @@ export class AddonModLessonOfflineProvider { /** * Retrieve offline attempts for certain pages for a retake. * - * @param {number} lessonId Lesson ID. - * @param {number} retake Retake number. - * @param {number} type Type of the pages to get: TYPE_QUESTION or TYPE_STRUCTURE. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the retake attempts. + * @param lessonId Lesson ID. + * @param retake Retake number. + * @param type Type of the pages to get: TYPE_QUESTION or TYPE_STRUCTURE. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the retake attempts. */ getRetakeAttemptsForType(lessonId: number, retake: number, type: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -426,11 +426,11 @@ export class AddonModLessonOfflineProvider { /** * Get stored retake. If not found or doesn't match the retake number, return a new one. * - * @param {number} lessonId Lesson ID. - * @param {number} courseId Course ID the lesson belongs to. - * @param {number} retake Retake number. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the retake. + * @param lessonId Lesson ID. + * @param courseId Course ID the lesson belongs to. + * @param retake Retake number. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the retake. */ protected getRetakeWithFallback(lessonId: number, courseId: number, retake: number, siteId?: string): Promise { // Get current stored retake. @@ -455,9 +455,9 @@ export class AddonModLessonOfflineProvider { /** * Check if there is a finished retake for a certain lesson. * - * @param {number} lessonId Lesson ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean. + * @param lessonId Lesson ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean. */ hasFinishedRetake(lessonId: number, siteId?: string): Promise { return this.getRetake(lessonId, siteId).then((retake) => { @@ -470,9 +470,9 @@ export class AddonModLessonOfflineProvider { /** * Check if a lesson has offline data. * - * @param {number} lessonId Lesson ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean. + * @param lessonId Lesson ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean. */ hasOfflineData(lessonId: number, siteId?: string): Promise { const promises = []; @@ -498,10 +498,10 @@ export class AddonModLessonOfflineProvider { /** * Check if there are offline attempts for a retake. * - * @param {number} lessonId Lesson ID. - * @param {number} retake Retake number. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with a boolean. + * @param lessonId Lesson ID. + * @param retake Retake number. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with a boolean. */ hasRetakeAttempts(lessonId: number, retake: number, siteId?: string): Promise { return this.getRetakeAttempts(lessonId, retake, siteId).then((list) => { @@ -514,8 +514,8 @@ export class AddonModLessonOfflineProvider { /** * Parse some properties of a page attempt. * - * @param {any} attempt The attempt to treat. - * @return {any} The treated attempt. + * @param attempt The attempt to treat. + * @return The treated attempt. */ protected parsePageAttempt(attempt: any): any { attempt.data = this.textUtils.parseJSON(attempt.data); @@ -527,8 +527,8 @@ export class AddonModLessonOfflineProvider { /** * Parse some properties of some page attempts. * - * @param {any[]} attempts The attempts to treat. - * @return {any[]} The treated attempts. + * @param attempts The attempts to treat. + * @return The treated attempts. */ protected parsePageAttempts(attempts: any[]): any[] { attempts.forEach((attempt) => { @@ -541,17 +541,17 @@ export class AddonModLessonOfflineProvider { /** * Process a lesson page, saving its data. * - * @param {number} lessonId Lesson ID. - * @param {number} courseId Course ID the lesson belongs to. - * @param {number} retake Retake number. - * @param {any} page Page. - * @param {any} data Data to save. - * @param {number} newPageId New page ID (calculated). - * @param {number} [answerId] The answer ID that the user answered. - * @param {boolean} [correct] If answer is correct. Only for question pages. - * @param {any} [userAnswer] The user's answer (userresponse from checkAnswer). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved in success, rejected otherwise. + * @param lessonId Lesson ID. + * @param courseId Course ID the lesson belongs to. + * @param retake Retake number. + * @param page Page. + * @param data Data to save. + * @param newPageId New page ID (calculated). + * @param answerId The answer ID that the user answered. + * @param correct If answer is correct. Only for question pages. + * @param userAnswer The user's answer (userresponse from checkAnswer). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved in success, rejected otherwise. */ processPage(lessonId: number, courseId: number, retake: number, page: any, data: any, newPageId: number, answerId?: number, correct?: boolean, userAnswer?: any, siteId?: string): Promise { @@ -583,12 +583,12 @@ export class AddonModLessonOfflineProvider { /** * Set the last question page attempted in a retake. * - * @param {number} lessonId Lesson ID. - * @param {number} courseId Course ID the lesson belongs to. - * @param {number} retake Retake number. - * @param {number} lastPage ID of the last question page attempted. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved in success, rejected otherwise. + * @param lessonId Lesson ID. + * @param courseId Course ID the lesson belongs to. + * @param retake Retake number. + * @param lastPage ID of the last question page attempted. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved in success, rejected otherwise. */ setLastQuestionPageAttempted(lessonId: number, courseId: number, retake: number, lastPage: number, siteId?: string) : Promise { diff --git a/src/addon/mod/lesson/providers/lesson-sync.ts b/src/addon/mod/lesson/providers/lesson-sync.ts index 1a1abdaf6..d17c76200 100644 --- a/src/addon/mod/lesson/providers/lesson-sync.ts +++ b/src/addon/mod/lesson/providers/lesson-sync.ts @@ -37,13 +37,11 @@ import { AddonModLessonPrefetchHandler } from './prefetch-handler'; export interface AddonModLessonSyncResult { /** * List of warnings. - * @type {string[]} */ warnings: string[]; /** * Whether some data was sent to the server or offline data was updated. - * @type {boolean} */ updated: boolean; } @@ -108,9 +106,9 @@ export class AddonModLessonSyncProvider extends CoreCourseActivitySyncBaseProvid /** * Unmark a retake as finished in a synchronization. * - * @param {number} lessonId Lesson ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param lessonId Lesson ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ deleteRetakeFinishedInSync(lessonId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -123,9 +121,9 @@ export class AddonModLessonSyncProvider extends CoreCourseActivitySyncBaseProvid /** * Get a retake finished in a synchronization for a certain lesson (if any). * - * @param {number} lessonId Lesson ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the retake entry (undefined if no retake). + * @param lessonId Lesson ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the retake entry (undefined if no retake). */ getRetakeFinishedInSync(lessonId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -138,10 +136,10 @@ export class AddonModLessonSyncProvider extends CoreCourseActivitySyncBaseProvid /** * Check if a lesson has data to synchronize. * - * @param {number} lessonId Lesson ID. - * @param {number} retake Retake number. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean: whether it has data to sync. + * @param lessonId Lesson ID. + * @param retake Retake number. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: whether it has data to sync. */ hasDataToSync(lessonId: number, retake: number, siteId?: string): Promise { const promises = []; @@ -165,11 +163,11 @@ export class AddonModLessonSyncProvider extends CoreCourseActivitySyncBaseProvid /** * Mark a retake as finished in a synchronization. * - * @param {number} lessonId Lesson ID. - * @param {number} retake The retake number. - * @param {number} pageId The page ID to start reviewing from. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param lessonId Lesson ID. + * @param retake The retake number. + * @param pageId The page ID to start reviewing from. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ setRetakeFinishedInSync(lessonId: number, retake: number, pageId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -185,9 +183,9 @@ export class AddonModLessonSyncProvider extends CoreCourseActivitySyncBaseProvid /** * Try to synchronize all the lessons 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. */ syncAllLessons(siteId?: string, force?: boolean): Promise { return this.syncOnSites('all lessons', this.syncAllLessonsFunc.bind(this), [force], siteId); @@ -196,9 +194,9 @@ export class AddonModLessonSyncProvider extends CoreCourseActivitySyncBaseProvid /** * Sync all lessons 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 syncAllLessonsFunc(siteId: string, force?: boolean): Promise { // Get all the lessons that have something to be synchronized. @@ -228,10 +226,10 @@ export class AddonModLessonSyncProvider extends CoreCourseActivitySyncBaseProvid /** * Sync a lesson only if a certain time has passed since the last time. * - * @param {any} lessonId Lesson ID. - * @param {boolean} [askPreflight] Whether we should ask for password if needed. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the lesson is synced or if it doesn't need to be synced. + * @param lessonId Lesson ID. + * @param askPreflight Whether we should ask for password if needed. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the lesson is synced or if it doesn't need to be synced. */ syncLessonIfNeeded(lessonId: number, askPassword?: boolean, siteId?: string): Promise { return this.isSyncNeeded(lessonId, siteId).then((needed) => { @@ -244,11 +242,11 @@ export class AddonModLessonSyncProvider extends CoreCourseActivitySyncBaseProvid /** * Try to synchronize a lesson. * - * @param {number} lessonId Lesson ID. - * @param {boolean} askPassword True if we should ask for password if needed, false otherwise. - * @param {boolean} ignoreBlock True to ignore the sync block setting. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved in success. + * @param lessonId Lesson ID. + * @param askPassword True if we should ask for password if needed, false otherwise. + * @param ignoreBlock True to ignore the sync block setting. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved in success. */ syncLesson(lessonId: number, askPassword?: boolean, ignoreBlock?: boolean, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -458,12 +456,12 @@ export class AddonModLessonSyncProvider extends CoreCourseActivitySyncBaseProvid /** * Send an attempt to the site and delete it afterwards. * - * @param {any} lesson Lesson. - * @param {string} password Password (if any). - * @param {any} attempt Attempt to send. - * @param {AddonModLessonSyncResult} result Result where to store the data. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param lesson Lesson. + * @param password Password (if any). + * @param attempt Attempt to send. + * @param result Result where to store the data. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ protected sendAttempt(lesson: any, password: string, attempt: any, result: AddonModLessonSyncResult, siteId?: string) : Promise { diff --git a/src/addon/mod/lesson/providers/lesson.ts b/src/addon/mod/lesson/providers/lesson.ts index 72feffd7f..69f5daca7 100644 --- a/src/addon/mod/lesson/providers/lesson.ts +++ b/src/addon/mod/lesson/providers/lesson.ts @@ -58,43 +58,36 @@ export interface AddonModLessonRecordAttemptResult extends AddonModLessonCheckAn export interface AddonModLessonGrade { /** * Number of questions answered. - * @type {number} */ nquestions: number; /** * Number of question attempts. - * @type {number} */ attempts: number; /** * Max points possible. - * @type {number} */ total: number; /** * Points earned by the student. - * @type {number} */ earned: number; /** * Calculated percentage grade. - * @type {number} */ grade: number; /** * Numer of manually graded questions. - * @type {number} */ nmanual: number; /** * Point value for manually graded questions. - * @type {number} */ manualpoints: number; } @@ -200,12 +193,12 @@ export class AddonModLessonProvider { /** * Add an answer and its response to a feedback string (HTML). * - * @param {string} feedback The current feedback. - * @param {string} answer Student answer. - * @param {number} answerFormat Answer format. - * @param {string} response Response. - * @param {string} className Class to add to the response. - * @return {string} New feedback. + * @param feedback The current feedback. + * @param answer Student answer. + * @param answerFormat Answer format. + * @param response Response. + * @param className Class to add to the response. + * @return New feedback. */ protected addAnswerAndResponseToFeedback(feedback: string, answer: string, answerFormat: number, response: string, className: string): string { @@ -229,9 +222,9 @@ export class AddonModLessonProvider { /** * Add a message to a list of messages, following the format of the messages returned by WS. * - * @param {any[]} messages List of messages where to add the message. - * @param {string} stringName The ID of the message to be translated. E.g. 'addon.mod_lesson.numberofpagesviewednotice'. - * @param {any} [stringParams] The params of the message (if any). + * @param messages List of messages where to add the message. + * @param stringName The ID of the message to be translated. E.g. 'addon.mod_lesson.numberofpagesviewednotice'. + * @param stringParams The params of the message (if any). */ protected addMessage(messages: any[], stringName: string, stringParams?: any): void { messages.push({ @@ -242,10 +235,10 @@ export class AddonModLessonProvider { /** * Add a property to the result of the "process EOL page" simulation in offline. * - * @param {any} result Result where to add the value. - * @param {string} name Name of the property. - * @param {any} value Value to add. - * @param {boolean} addMessage Whether to add a message related to the value. + * @param result Result where to add the value. + * @param name Name of the property. + * @param value Value to add. + * @param addMessage Whether to add a message related to the value. */ protected addResultValueEolPage(result: any, name: string, value: any, addMessage?: boolean): void { let message = ''; @@ -265,8 +258,8 @@ export class AddonModLessonProvider { /** * Check if an answer page (from getUserRetake) is a content page. * - * @param {any} page Answer page. - * @return {boolean} Whether it's a content page. + * @param page Answer page. + * @return Whether it's a content page. */ answerPageIsContent(page: any): boolean { // The page doesn't have any reliable field to use for checking this. Check qtype first (translated string). @@ -291,8 +284,8 @@ export class AddonModLessonProvider { /** * Check if an answer page (from getUserRetake) is a question page. * - * @param {any} page Answer page. - * @return {boolean} Whether it's a question page. + * @param page Answer page. + * @return Whether it's a question page. */ answerPageIsQuestion(page: any): boolean { if (!page.answerdata) { @@ -320,13 +313,13 @@ export class AddonModLessonProvider { /** * Calculate some offline data like progress and ongoingscore. * - * @param {any} lesson Lesson. - * @param {any} accessInfo Result of get access info. - * @param {string} [password] Lesson password (if any). - * @param {boolean} [review] If the user wants to review just after finishing (1 hour margin). - * @param {any} [pageIndex] Object containing all the pages indexed by ID. If not defined, it will be calculated. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise<{reviewmode: boolean, progress: number, ongoingscore: string}>} Promise resolved with the data. + * @param lesson Lesson. + * @param accessInfo Result of get access info. + * @param password Lesson password (if any). + * @param review If the user wants to review just after finishing (1 hour margin). + * @param pageIndex Object containing all the pages indexed by ID. If not defined, it will be calculated. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the data. */ protected calculateOfflineData(lesson: any, accessInfo?: any, password?: string, review?: boolean, pageIndex?: any, siteId?: string): Promise<{reviewmode: boolean, progress: number, ongoingscore: string}> { @@ -365,13 +358,13 @@ export class AddonModLessonProvider { * Calculate the progress of the current user in the lesson. * Based on Moodle's calculate_progress. * - * @param {number} lessonId Lesson ID. - * @param {any} accessInfo Result of get access info. - * @param {string} [password] Lesson password (if any). - * @param {boolean} [review] If the user wants to review just after finishing (1 hour margin). - * @param {any} [pageIndex] Object containing all the pages indexed by ID. If not defined, it will be calculated. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with a number: the progress (scale 0-100). + * @param lessonId Lesson ID. + * @param accessInfo Result of get access info. + * @param password Lesson password (if any). + * @param review If the user wants to review just after finishing (1 hour margin). + * @param pageIndex Object containing all the pages indexed by ID. If not defined, it will be calculated. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with a number: the progress (scale 0-100). */ calculateProgress(lessonId: number, accessInfo: any, password?: string, review?: boolean, pageIndex?: any, siteId?: string) : Promise { @@ -429,12 +422,12 @@ export class AddonModLessonProvider { * Check if the answer provided by the user is correct or not and return the result object. * This method is based on the check_answer implementation of all page types (Moodle). * - * @param {any} lesson Lesson. - * @param {any} pageData Result of getPageData for the page to process. - * @param {any} data Data containing the user answer. - * @param {any} jumps Result of get pages possible jumps. - * @param {any} pageIndex Object containing all the pages indexed by ID. - * @return {AddonModLessonCheckAnswerResult} Result. + * @param lesson Lesson. + * @param pageData Result of getPageData for the page to process. + * @param data Data containing the user answer. + * @param jumps Result of get pages possible jumps. + * @param pageIndex Object containing all the pages indexed by ID. + * @return Result. */ protected checkAnswer(lesson: any, pageData: any, data: any, jumps: any, pageIndex: any): AddonModLessonCheckAnswerResult { // Default result. @@ -492,9 +485,9 @@ export class AddonModLessonProvider { /** * Check an essay answer. * - * @param {any} pageData Result of getPageData for the page to process. - * @param {any} data Data containing the user answer. - * @param {AddonModLessonCheckAnswerResult} result Object where to store the result. + * @param pageData Result of getPageData for the page to process. + * @param data Data containing the user answer. + * @param result Object where to store the result. */ protected checkAnswerEssay(pageData: any, data: any, result: AddonModLessonCheckAnswerResult): void { let studentAnswer; @@ -549,9 +542,9 @@ export class AddonModLessonProvider { /** * Check a matching answer. * - * @param {any} pageData Result of getPageData for the page to process. - * @param {any} data Data containing the user answer. - * @param {AddonModLessonCheckAnswerResult} result Object where to store the result. + * @param pageData Result of getPageData for the page to process. + * @param data Data containing the user answer. + * @param result Object where to store the result. */ protected checkAnswerMatching(pageData: any, data: any, result: AddonModLessonCheckAnswerResult): void { if (!data) { @@ -620,11 +613,11 @@ export class AddonModLessonProvider { /** * Check a multichoice answer. * - * @param {any} lesson Lesson. - * @param {any} pageData Result of getPageData for the page to process. - * @param {any} data Data containing the user answer. - * @param {any} pageIndex Object containing all the pages indexed by ID. - * @param {AddonModLessonCheckAnswerResult} result Object where to store the result. + * @param lesson Lesson. + * @param pageData Result of getPageData for the page to process. + * @param data Data containing the user answer. + * @param pageIndex Object containing all the pages indexed by ID. + * @param result Object where to store the result. */ protected checkAnswerMultichoice(lesson: any, pageData: any, data: any, pageIndex: any, result: AddonModLessonCheckAnswerResult): void { @@ -751,11 +744,11 @@ export class AddonModLessonProvider { /** * Check a numerical answer. * - * @param {any} lesson Lesson. - * @param {any} pageData Result of getPageData for the page to process. - * @param {any} data Data containing the user answer. - * @param {any} pageIndex Object containing all the pages indexed by ID. - * @param {any} result Object where to store the result. + * @param lesson Lesson. + * @param pageData Result of getPageData for the page to process. + * @param data Data containing the user answer. + * @param pageIndex Object containing all the pages indexed by ID. + * @param result Object where to store the result. */ protected checkAnswerNumerical(lesson: any, pageData: any, data: any, pageIndex: any, result: AddonModLessonCheckAnswerResult) : void { @@ -807,11 +800,11 @@ export class AddonModLessonProvider { /** * Check a short answer. * - * @param {any} lesson Lesson. - * @param {any} pageData Result of getPageData for the page to process. - * @param {any} data Data containing the user answer. - * @param {any} pageIndex Object containing all the pages indexed by ID. - * @param {any} result Object where to store the result. + * @param lesson Lesson. + * @param pageData Result of getPageData for the page to process. + * @param data Data containing the user answer. + * @param pageIndex Object containing all the pages indexed by ID. + * @param result Object where to store the result. */ protected checkAnswerShort(lesson: any, pageData: any, data: any, pageIndex: any, result: AddonModLessonCheckAnswerResult) : void { @@ -925,11 +918,11 @@ export class AddonModLessonProvider { /** * Check a truefalse answer. * - * @param {any} lesson Lesson. - * @param {any} pageData Result of getPageData for the page to process. - * @param {any} data Data containing the user answer. - * @param {any} pageIndex Object containing all the pages indexed by ID. - * @param {any} result Object where to store the result. + * @param lesson Lesson. + * @param pageData Result of getPageData for the page to process. + * @param data Data containing the user answer. + * @param pageIndex Object containing all the pages indexed by ID. + * @param result Object where to store the result. */ protected checkAnswerTruefalse(lesson: any, pageData: any, data: any, pageIndex: any, result: AddonModLessonCheckAnswerResult) : void { @@ -959,9 +952,9 @@ export class AddonModLessonProvider { /** * Check the "other answers" value. * - * @param {any} lesson Lesson. - * @param {any} pageData Result of getPageData for the page to process. - * @param {AddonModLessonCheckAnswerResult} result Object where to store the result. + * @param lesson Lesson. + * @param pageData Result of getPageData for the page to process. + * @param result Object where to store the result. */ protected checkOtherAnswers(lesson: any, pageData: any, result: AddonModLessonCheckAnswerResult): void { // We could check here to see if we have a wrong answer jump to use. @@ -986,8 +979,8 @@ export class AddonModLessonProvider { /** * Create a list of pages indexed by page ID based on a list of pages. * - * @param {Object[]} pageList Result of get pages. - * @return {any} Pages index. + * @param pageList Result of get pages. + * @return Pages index. */ protected createPagesIndex(pageList: any[]): any { // Index the pages by page ID. @@ -1003,15 +996,15 @@ export class AddonModLessonProvider { /** * Finishes a retake. * - * @param {any} lesson Lesson. - * @param {number} courseId Course ID the lesson belongs to. - * @param {string} [password] Lesson password (if any). - * @param {boolean} [outOfTime] If the user ran out of time. - * @param {boolean} [review] If the user wants to review just after finishing (1 hour margin). - * @param {boolean} [offline] Whether it's offline mode. - * @param {any} [accessInfo] Result of get access info. Required if offline is true. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved in success, rejected otherwise. + * @param lesson Lesson. + * @param courseId Course ID the lesson belongs to. + * @param password Lesson password (if any). + * @param outOfTime If the user ran out of time. + * @param review If the user wants to review just after finishing (1 hour margin). + * @param offline Whether it's offline mode. + * @param accessInfo Result of get access info. Required if offline is true. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved in success, rejected otherwise. */ finishRetake(lesson: any, courseId: number, password?: string, outOfTime?: boolean, review?: boolean, offline?: boolean, accessInfo?: any, siteId?: string): Promise { @@ -1144,12 +1137,12 @@ export class AddonModLessonProvider { /** * Finishes a retake. It will fail if offline or cannot connect. * - * @param {number} lessonId Lesson ID. - * @param {string} [password] Lesson password (if any). - * @param {boolean} [outOfTime] If the user ran out of time. - * @param {boolean} [review] If the user wants to review just after finishing (1 hour margin). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved in success, rejected otherwise. + * @param lessonId Lesson ID. + * @param password Lesson password (if any). + * @param outOfTime If the user ran out of time. + * @param review If the user wants to review just after finishing (1 hour margin). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved in success, rejected otherwise. */ finishRetakeOnline(lessonId: number, password?: string, outOfTime?: boolean, review?: boolean, siteId?: string): Promise { @@ -1186,11 +1179,11 @@ export class AddonModLessonProvider { /** * Get the access information of a certain lesson. * - * @param {number} lessonId Lesson ID. - * @param {boolean} [forceCache] Whether it should always 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 lessonId Lesson ID. + * @param forceCache Whether it should always 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. */ getAccessInformation(lessonId: number, forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1215,8 +1208,8 @@ export class AddonModLessonProvider { /** * Get cache key for access information WS calls. * - * @param {number} lessonId Lesson ID. - * @return {string} Cache key. + * @param lessonId Lesson ID. + * @return Cache key. */ protected getAccessInformationCacheKey(lessonId: number): string { return this.ROOT_CACHE_KEY + 'accessInfo:' + lessonId; @@ -1225,10 +1218,10 @@ export class AddonModLessonProvider { /** * Get content pages viewed in online and offline. * - * @param {number} lessonId Lesson ID. - * @param {number} retake Retake number. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise<{online: any[], offline: any[]}>} Promise resolved with an object with the online and offline viewed pages. + * @param lessonId Lesson ID. + * @param retake Retake number. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with an object with the online and offline viewed pages. */ getContentPagesViewed(lessonId: number, retake: number, siteId?: string): Promise<{online: any[], offline: any[]}> { const promises = [], @@ -1258,9 +1251,9 @@ export class AddonModLessonProvider { /** * Get cache key for get content pages viewed WS calls. * - * @param {number} lessonId Lesson ID. - * @param {number} retake Retake number. - * @return {string} Cache key. + * @param lessonId Lesson ID. + * @param retake Retake number. + * @return Cache key. */ protected getContentPagesViewedCacheKey(lessonId: number, retake: number): string { return this.getContentPagesViewedCommonCacheKey(lessonId) + ':' + retake; @@ -1269,8 +1262,8 @@ export class AddonModLessonProvider { /** * Get common cache key for get content pages viewed WS calls. * - * @param {number} lessonId Lesson ID. - * @return {string} Cache key. + * @param lessonId Lesson ID. + * @return Cache key. */ protected getContentPagesViewedCommonCacheKey(lessonId: number): string { return this.ROOT_CACHE_KEY + 'contentPagesViewed:' + lessonId; @@ -1279,10 +1272,10 @@ export class AddonModLessonProvider { /** * Get IDS of content pages viewed in online and offline. * - * @param {number} lessonId Lesson ID. - * @param {number} retake Retake number. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with list of IDs. + * @param lessonId Lesson ID. + * @param retake Retake number. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with list of IDs. */ getContentPagesViewedIds(lessonId: number, retake: number, siteId?: string): Promise { return this.getContentPagesViewed(lessonId, retake, siteId).then((result) => { @@ -1304,12 +1297,12 @@ export class AddonModLessonProvider { /** * Get the list of content pages viewed in the site for a certain retake. * - * @param {number} lessonId Lesson ID. - * @param {number} retake Retake number. - * @param {boolean} [forceCache] Whether it should always 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 viewed pages. + * @param lessonId Lesson ID. + * @param retake Retake number. + * @param forceCache Whether it should always 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 viewed pages. */ getContentPagesViewedOnline(lessonId: number, retake: number, forceCache?: boolean, ignoreCache?: boolean, siteId?: string) : Promise { @@ -1339,10 +1332,10 @@ export class AddonModLessonProvider { /** * Get the last content page viewed. * - * @param {number} lessonId Lesson ID. - * @param {number} retake Retake number. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the last content page viewed. + * @param lessonId Lesson ID. + * @param retake Retake number. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the last content page viewed. */ getLastContentPageViewed(lessonId: number, retake: number, siteId?: string): Promise { return this.getContentPagesViewed(lessonId, retake, siteId).then((data) => { @@ -1373,10 +1366,10 @@ export class AddonModLessonProvider { * Get the last page seen. * Based on Moodle's get_last_page_seen. * - * @param {number} lessonId Lesson ID. - * @param {number} retake Retake number. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the last page seen. + * @param lessonId Lesson ID. + * @param retake Retake number. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the last page seen. */ getLastPageSeen(lessonId: number, retake: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -1411,12 +1404,12 @@ export class AddonModLessonProvider { /** * Get a Lesson 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 lesson 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 lesson is retrieved. */ getLesson(courseId: number, cmId: number, forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise { return this.getLessonByField(courseId, 'coursemodule', cmId, forceCache, ignoreCache, siteId); @@ -1425,13 +1418,13 @@ export class AddonModLessonProvider { /** * Get a Lesson 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 lesson 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 lesson is retrieved. */ protected getLessonByField(courseId: number, key: string, value: any, forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise { @@ -1471,12 +1464,12 @@ export class AddonModLessonProvider { /** * Get a Lesson by lesson ID. * - * @param {number} courseId Course ID. - * @param {number} id Lesson 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 lesson is retrieved. + * @param courseId Course ID. + * @param id Lesson 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 lesson is retrieved. */ getLessonById(courseId: number, id: number, forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise { return this.getLessonByField(courseId, 'id', id, forceCache, ignoreCache, siteId); @@ -1485,8 +1478,8 @@ export class AddonModLessonProvider { /** * Get cache key for Lesson data WS calls. * - * @param {number} courseId Course ID. - * @return {string} Cache key. + * @param courseId Course ID. + * @return Cache key. */ protected getLessonDataCacheKey(courseId: number): string { return this.ROOT_CACHE_KEY + 'lesson:' + courseId; @@ -1495,14 +1488,14 @@ export class AddonModLessonProvider { /** * Get a lesson protected with password. * - * @param {number} lessonId Lesson ID. - * @param {string} [password] Password. - * @param {boolean} [validatePassword=true] If true, the function will fail if the password is wrong. - * If false, it will return a lesson with the basic data if password is wrong. - * @param {boolean} [forceCache] Whether it should always 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 lesson. + * @param lessonId Lesson ID. + * @param password Password. + * @param validatePassword If true, the function will fail if the password is wrong. + * If false, it will return a lesson with the basic data if password is wrong. + * @param forceCache Whether it should always 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 lesson. */ getLessonWithPassword(lessonId: number, password?: string, validatePassword: boolean = true, forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise { @@ -1549,8 +1542,8 @@ export class AddonModLessonProvider { /** * Get cache key for get lesson with password WS calls. * - * @param {number} lessonId Lesson ID. - * @return {string} Cache key. + * @param lessonId Lesson ID. + * @return Cache key. */ protected getLessonWithPasswordCacheKey(lessonId: number): string { return this.ROOT_CACHE_KEY + 'lessonWithPswrd:' + lessonId; @@ -1559,10 +1552,10 @@ export class AddonModLessonProvider { /** * Given a page ID, a jumpto and all the possible jumps, calcualate the new page ID. * - * @param {number} pageId Current page ID. - * @param {number} jumpTo The jumpto. - * @param {any} jumps Result of get pages possible jumps. - * @return {number} New page ID. + * @param pageId Current page ID. + * @param jumpTo The jumpto. + * @param jumps Result of get pages possible jumps. + * @return New page ID. */ protected getNewPageId(pageId: number, jumpTo: number, jumps: any): number { // If jump not found, return current jumpTo. @@ -1579,13 +1572,13 @@ export class AddonModLessonProvider { /** * Get the ongoing score message for the user (depending on the user permission and lesson settings). * - * @param {any} lesson Lesson. - * @param {any} accessInfo Result of get access info. - * @param {string} [password] Lesson password (if any). - * @param {boolean} [review] If the user wants to review just after finishing (1 hour margin). - * @param {any} [pageIndex] Object containing all the pages indexed by ID. If not provided, it will be calculated. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the ongoing score message. + * @param lesson Lesson. + * @param accessInfo Result of get access info. + * @param password Lesson password (if any). + * @param review If the user wants to review just after finishing (1 hour margin). + * @param pageIndex Object containing all the pages indexed by ID. If not provided, it will be calculated. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the ongoing score message. */ getOngoingScoreMessage(lesson: any, accessInfo: any, password?: string, review?: boolean, pageIndex?: any, siteId?: string) : Promise { @@ -1619,12 +1612,12 @@ export class AddonModLessonProvider { /** * Get the possible answers from a page. * - * @param {any} lesson Lesson. - * @param {number} pageId Page ID. - * @param {string} [password] Lesson password (if any). - * @param {boolean} [review] If the user wants to review just after finishing (1 hour margin). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the list of possible answers. + * @param lesson Lesson. + * @param pageId Page ID. + * @param password Lesson password (if any). + * @param review If the user wants to review just after finishing (1 hour margin). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the list of possible answers. */ protected getPageAnswers(lesson: any, pageId: number, password?: string, review?: boolean, siteId?: string): Promise { return this.getPageData(lesson, pageId, password, review, true, true, false, undefined, undefined, siteId).then((data) => { @@ -1635,12 +1628,12 @@ export class AddonModLessonProvider { /** * Get all the possible answers from a list of pages, indexed by answerId. * - * @param {any} lesson Lesson. - * @param {number[]} pageIds List of page IDs. - * @param {string} [password] Lesson password (if any). - * @param {boolean} [review] If the user wants to review just after finishing (1 hour margin). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with an object containing the answers. + * @param lesson Lesson. + * @param pageIds List of page IDs. + * @param password Lesson password (if any). + * @param review If the user wants to review just after finishing (1 hour margin). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with an object containing the answers. */ protected getPagesAnswers(lesson: any, pageIds: number[], password?: string, review?: boolean, siteId?: string) : Promise { @@ -1666,17 +1659,17 @@ export class AddonModLessonProvider { /** * Get page data. * - * @param {any} lesson Lesson. - * @param {number} pageId Page ID. - * @param {string} [password] Lesson password (if any). - * @param {boolean} [review] If the user wants to review just after finishing (1 hour margin). - * @param {boolean} [includeContents] Include the page rendered contents. - * @param {boolean} [forceCache] Whether it should always 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 {any} [accessInfo] Result of get access info. Required if offline is true. - * @param {any} [jumps] Result of get pages possible jumps. Required if offline is true. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the page data. + * @param lesson Lesson. + * @param pageId Page ID. + * @param password Lesson password (if any). + * @param review If the user wants to review just after finishing (1 hour margin). + * @param includeContents Include the page rendered contents. + * @param forceCache Whether it should always 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 accessInfo Result of get access info. Required if offline is true. + * @param jumps Result of get pages possible jumps. Required if offline is true. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the page data. */ getPageData(lesson: any, pageId: number, password?: string, review?: boolean, includeContents?: boolean, forceCache?: boolean, ignoreCache?: boolean, accessInfo?: any, jumps?: any, siteId?: string): Promise { @@ -1732,9 +1725,9 @@ export class AddonModLessonProvider { /** * Get cache key for get page data WS calls. * - * @param {number} lessonId Lesson ID. - * @param {number} pageId Page ID. - * @return {string} Cache key. + * @param lessonId Lesson ID. + * @param pageId Page ID. + * @return Cache key. */ protected getPageDataCacheKey(lessonId: number, pageId: number): string { return this.getPageDataCommonCacheKey(lessonId) + ':' + pageId; @@ -1743,8 +1736,8 @@ export class AddonModLessonProvider { /** * Get common cache key for get page data WS calls. * - * @param {number} lessonId Lesson ID. - * @return {string} Cache key. + * @param lessonId Lesson ID. + * @return Cache key. */ protected getPageDataCommonCacheKey(lessonId: number): string { return this.ROOT_CACHE_KEY + 'pageData:' + lessonId; @@ -1753,12 +1746,12 @@ export class AddonModLessonProvider { /** * Get lesson pages. * - * @param {number} lessonId Lesson ID. - * @param {string} [password] Lesson password (if any). - * @param {boolean} [forceCache] Whether it should always 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 pages. + * @param lessonId Lesson ID. + * @param password Lesson password (if any). + * @param forceCache Whether it should always 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 pages. */ getPages(lessonId: number, password?: string, forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise { @@ -1791,8 +1784,8 @@ export class AddonModLessonProvider { /** * Get cache key for get pages WS calls. * - * @param {number} lessonId Lesson ID. - * @return {string} Cache key. + * @param lessonId Lesson ID. + * @return Cache key. */ protected getPagesCacheKey(lessonId: number): string { return this.ROOT_CACHE_KEY + 'pages:' + lessonId; @@ -1801,11 +1794,11 @@ export class AddonModLessonProvider { /** * Get possible jumps for a lesson. * - * @param {number} lessonId Lesson ID. - * @param {boolean} [forceCache] Whether it should always 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 jumps. + * @param lessonId Lesson ID. + * @param forceCache Whether it should always 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 jumps. */ getPagesPossibleJumps(lessonId: number, forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise { @@ -1847,8 +1840,8 @@ export class AddonModLessonProvider { /** * Get cache key for get pages possible jumps WS calls. * - * @param {number} lessonId Lesson ID. - * @return {string} Cache key. + * @param lessonId Lesson ID. + * @return Cache key. */ protected getPagesPossibleJumpsCacheKey(lessonId: number): string { return this.ROOT_CACHE_KEY + 'pagesJumps:' + lessonId; @@ -1859,12 +1852,12 @@ export class AddonModLessonProvider { * Please try to use WS response messages instead of this function if possible. * Based on Moodle's add_messages_on_page_process. * - * @param {any} lesson Lesson. - * @param {any} accessInfo Result of get access info. - * @param {any} result Result of process page. - * @param {boolean} review If the user wants to review just after finishing (1 hour margin). - * @param {any} jumps Result of get pages possible jumps. - * @return {any[]} Array with the messages. + * @param lesson Lesson. + * @param accessInfo Result of get access info. + * @param result Result of process page. + * @param review If the user wants to review just after finishing (1 hour margin). + * @param jumps Result of get pages possible jumps. + * @return Array with the messages. */ getPageProcessMessages(lesson: any, accessInfo: any, result: any, review: boolean, jumps: any): any[] { const messages = []; @@ -1894,12 +1887,12 @@ export class AddonModLessonProvider { /** * Get the IDs of all the pages that have at least 1 question attempt. * - * @param {number} lessonId Lesson ID. - * @param {number} retake Retake number. - * @param {boolean} [correct] True to only fetch correct attempts, false to get them all. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User ID. If not defined, site's user. - * @return {Promise} Promise resolved with the IDs. + * @param lessonId Lesson ID. + * @param retake Retake number. + * @param correct True to only fetch correct attempts, false to get them all. + * @param siteId Site ID. If not defined, current site. + * @param userId User ID. If not defined, site's user. + * @return Promise resolved with the IDs. */ getPagesIdsWithQuestionAttempts(lessonId: number, retake: number, correct?: boolean, siteId?: string, userId?: number) : Promise { @@ -1925,14 +1918,14 @@ export class AddonModLessonProvider { * Please try to use WS response messages instead of this function if possible. * Based on Moodle's add_messages_on_page_view. * - * @param {any} lesson Lesson. - * @param {any} accessInfo Result of get access info. Required if offline is true. - * @param {any} page Page loaded. - * @param {boolean} review If the user wants to review just after finishing (1 hour margin). - * @param {any} jumps Result of get pages possible jumps. - * @param {string} [password] Lesson password (if any). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the list of messages. + * @param lesson Lesson. + * @param accessInfo Result of get access info. Required if offline is true. + * @param page Page loaded. + * @param review If the user wants to review just after finishing (1 hour margin). + * @param jumps Result of get pages possible jumps. + * @param password Lesson password (if any). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the list of messages. */ getPageViewMessages(lesson: any, accessInfo: any, page: any, review: boolean, jumps: any, password?: string, siteId?: string) : Promise { @@ -1991,13 +1984,13 @@ export class AddonModLessonProvider { /** * Get questions attempts, including offline attempts. * - * @param {number} lessonId Lesson ID. - * @param {number} retake Retake number. - * @param {boolean} [correct] True to only fetch correct attempts, false to get them all. - * @param {number} [pageId] If defined, only get attempts on this page. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User ID. If not defined, site's user. - * @return {Promise<{online: any[], offline: any[]}>} Promise resolved with the questions attempts. + * @param lessonId Lesson ID. + * @param retake Retake number. + * @param correct True to only fetch correct attempts, false to get them all. + * @param pageId If defined, only get attempts on this page. + * @param siteId Site ID. If not defined, current site. + * @param userId User ID. If not defined, site's user. + * @return Promise resolved with the questions attempts. */ getQuestionsAttempts(lessonId: number, retake: number, correct?: boolean, pageId?: number, siteId?: string, userId?: number) : Promise<{online: any[], offline: any[]}> { @@ -2028,10 +2021,10 @@ export class AddonModLessonProvider { /** * Get cache key for get questions attempts WS calls. * - * @param {number} lessonId Lesson ID. - * @param {number} retake Retake number. - * @param {number} userId User ID. - * @return {string} Cache key. + * @param lessonId Lesson ID. + * @param retake Retake number. + * @param userId User ID. + * @return Cache key. */ protected getQuestionsAttemptsCacheKey(lessonId: number, retake: number, userId: number): string { return this.getQuestionsAttemptsCommonCacheKey(lessonId) + ':' + userId + ':' + retake; @@ -2040,8 +2033,8 @@ export class AddonModLessonProvider { /** * Get common cache key for get questions attempts WS calls. * - * @param {number} lessonId Lesson ID. - * @return {string} Cache key. + * @param lessonId Lesson ID. + * @return Cache key. */ protected getQuestionsAttemptsCommonCacheKey(lessonId: number): string { return this.ROOT_CACHE_KEY + 'questionsAttempts:' + lessonId; @@ -2050,15 +2043,15 @@ export class AddonModLessonProvider { /** * Get questions attempts from the site. * - * @param {number} lessonId Lesson ID. - * @param {number} retake Retake number. - * @param {boolean} [correct] True to only fetch correct attempts, false to get them all. - * @param {number} [pageId] If defined, only get attempts on this page. - * @param {boolean} [forceCache] Whether it should always 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, site's user. - * @return {Promise} Promise resolved with the questions attempts. + * @param lessonId Lesson ID. + * @param retake Retake number. + * @param correct True to only fetch correct attempts, false to get them all. + * @param pageId If defined, only get attempts on this page. + * @param forceCache Whether it should always 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, site's user. + * @return Promise resolved with the questions attempts. */ getQuestionsAttemptsOnline(lessonId: number, retake: number, correct?: boolean, pageId?: number, forceCache?: boolean, ignoreCache?: boolean, siteId?: string, userId?: number): Promise { @@ -2107,12 +2100,12 @@ export class AddonModLessonProvider { /** * Get the overview of retakes in a lesson (named "attempts overview" in Moodle). * - * @param {number} lessonId Lesson ID. - * @param {number} [groupId] The group to get. If not defined, all participants. - * @param {boolean} [forceCache] Whether it should always 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 retakes overview. + * @param lessonId Lesson ID. + * @param groupId The group to get. If not defined, all participants. + * @param forceCache Whether it should always 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 retakes overview. */ getRetakesOverview(lessonId: number, groupId?: number, forceCache?: boolean, ignoreCache?: boolean, siteId?: string) : Promise { @@ -2145,9 +2138,9 @@ export class AddonModLessonProvider { /** * Get cache key for get retakes overview WS calls. * - * @param {number} lessonId Lesson ID. - * @param {number} groupId Group ID. - * @return {string} Cache key. + * @param lessonId Lesson ID. + * @param groupId Group ID. + * @return Cache key. */ protected getRetakesOverviewCacheKey(lessonId: number, groupId: number): string { return this.getRetakesOverviewCommonCacheKey(lessonId) + ':' + groupId; @@ -2156,8 +2149,8 @@ export class AddonModLessonProvider { /** * Get common cache key for get retakes overview WS calls. * - * @param {number} lessonId Lesson ID. - * @return {string} Cache key. + * @param lessonId Lesson ID. + * @return Cache key. */ protected getRetakesOverviewCommonCacheKey(lessonId: number): string { return this.ROOT_CACHE_KEY + 'retakesOverview:' + lessonId; @@ -2166,9 +2159,9 @@ export class AddonModLessonProvider { /** * Get a password stored in DB. * - * @param {number} lessonId Lesson ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with password on success, rejected otherwise. + * @param lessonId Lesson ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with password on success, rejected otherwise. */ getStoredPassword(lessonId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -2183,10 +2176,10 @@ export class AddonModLessonProvider { * encountered or no more pages exist. * Based on Moodle's get_sub_pages_of. * - * @param {any} pages Index of lesson pages, indexed by page ID. See createPagesIndex. - * @param {number} pageId Page ID to get subpages of. - * @param {number[]} end An array of LESSON_PAGE_* types that signify an end of the subtype. - * @return {Object[]} List of subpages. + * @param pages Index of lesson pages, indexed by page ID. See createPagesIndex. + * @param pageId Page ID to get subpages of. + * @param end An array of LESSON_PAGE_* types that signify an end of the subtype. + * @return List of subpages. */ getSubpagesOf(pages: any, pageId: number, ends: number[]): any[] { const subPages = []; @@ -2210,12 +2203,12 @@ export class AddonModLessonProvider { /** * Get lesson timers. * - * @param {number} lessonId Lesson ID. - * @param {boolean} [forceCache] Whether it should always 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, site's current user. - * @return {Promise} Promise resolved with the pages. + * @param lessonId Lesson ID. + * @param forceCache Whether it should always 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, site's current user. + * @return Promise resolved with the pages. */ getTimers(lessonId: number, forceCache?: boolean, ignoreCache?: boolean, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -2245,9 +2238,9 @@ export class AddonModLessonProvider { /** * Get cache key for get timers WS calls. * - * @param {number} lessonId Lesson ID. - * @param {number} userId User ID. - * @return {string} Cache key. + * @param lessonId Lesson ID. + * @param userId User ID. + * @return Cache key. */ protected getTimersCacheKey(lessonId: number, userId: number): string { return this.getTimersCommonCacheKey(lessonId) + ':' + userId; @@ -2256,8 +2249,8 @@ export class AddonModLessonProvider { /** * Get common cache key for get timers WS calls. * - * @param {number} lessonId Lesson ID. - * @return {string} Cache key. + * @param lessonId Lesson ID. + * @return Cache key. */ protected getTimersCommonCacheKey(lessonId: number): string { return this.ROOT_CACHE_KEY + 'timers:' + lessonId; @@ -2266,8 +2259,8 @@ export class AddonModLessonProvider { /** * Get the list of used answers (with valid answer) in a multichoice question page. * - * @param {any} pageData Result of getPageData for the page to process. - * @return {any[]} List of used answers. + * @param pageData Result of getPageData for the page to process. + * @return List of used answers. */ protected getUsedAnswersMultichoice(pageData: any): any[] { const answers = this.utils.clone(pageData.answers); @@ -2280,8 +2273,8 @@ export class AddonModLessonProvider { /** * Get the user's response in a matching question page. * - * @param {any} data Data containing the user answer. - * @return {any} User response. + * @param data Data containing the user answer. + * @return User response. */ protected getUserResponseMatching(data: any): any { if (data.response) { @@ -2306,8 +2299,8 @@ export class AddonModLessonProvider { /** * Get the user's response in a multichoice page if multiple answers are allowed. * - * @param {any} data Data containing the user answer. - * @return {any[]} User response. + * @param data Data containing the user answer. + * @return User response. */ protected getUserResponseMultichoice(data: any): any[] { if (data.answer) { @@ -2336,13 +2329,13 @@ export class AddonModLessonProvider { /** * Get a user's retake. * - * @param {number} lessonId Lesson ID. - * @param {number} retake Retake number - * @param {number} [userId] User ID. Undefined for current user. - * @param {boolean} [forceCache] Whether it should always 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 retake data. + * @param lessonId Lesson ID. + * @param retake Retake number + * @param userId User ID. Undefined for current user. + * @param forceCache Whether it should always 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 retake data. */ getUserRetake(lessonId: number, retake: number, userId?: number, forceCache?: boolean, ignoreCache?: boolean, siteId?: string) : Promise { @@ -2374,10 +2367,10 @@ export class AddonModLessonProvider { /** * Get cache key for get user retake WS calls. * - * @param {number} lessonId Lesson ID. - * @param {number} userId User ID. - * @param {number} retake Retake number - * @return {string} Cache key. + * @param lessonId Lesson ID. + * @param userId User ID. + * @param retake Retake number + * @return Cache key. */ protected getUserRetakeCacheKey(lessonId: number, userId: number, retake: number): string { return this.getUserRetakeUserCacheKey(lessonId, userId) + ':' + retake; @@ -2386,9 +2379,9 @@ export class AddonModLessonProvider { /** * Get user cache key for get user retake WS calls. * - * @param {number} lessonId Lesson ID. - * @param {number} userId User ID. - * @return {string} Cache key. + * @param lessonId Lesson ID. + * @param userId User ID. + * @return Cache key. */ protected getUserRetakeUserCacheKey(lessonId: number, userId: number): string { return this.getUserRetakeLessonCacheKey(lessonId) + ':' + userId; @@ -2397,8 +2390,8 @@ export class AddonModLessonProvider { /** * Get lesson cache key for get user retake WS calls. * - * @param {number} lessonId Lesson ID. - * @return {string} Cache key. + * @param lessonId Lesson ID. + * @return Cache key. */ protected getUserRetakeLessonCacheKey(lessonId: number): string { return this.ROOT_CACHE_KEY + 'userRetake:' + lessonId; @@ -2407,10 +2400,10 @@ export class AddonModLessonProvider { /** * Get the prevent access reason to display for a certain lesson. * - * @param {any} info Lesson access info. - * @param {boolean} [ignorePassword] Whether password protected reason should be ignored (user already entered the password). - * @param {boolean} [isReview] Whether user is reviewing a retake. - * @return {any} Prevent access reason. + * @param info Lesson access info. + * @param ignorePassword Whether password protected reason should be ignored (user already entered the password). + * @param isReview Whether user is reviewing a retake. + * @return Prevent access reason. */ getPreventAccessReason(info: any, ignorePassword?: boolean, isReview?: boolean): any { let result; @@ -2443,10 +2436,10 @@ export class AddonModLessonProvider { * Check if a jump is correct. * Based in Moodle's jumpto_is_correct. * - * @param {number} pageId ID of the page from which you are jumping from. - * @param {number} jumpTo The jumpto number. - * @param {any} pageIndex Object containing all the pages indexed by ID. See createPagesIndex. - * @return {boolean} Whether jump is correct. + * @param pageId ID of the page from which you are jumping from. + * @param jumpTo The jumpto number. + * @param pageIndex Object containing all the pages indexed by ID. See createPagesIndex. + * @return Whether jump is correct. */ jumptoIsCorrect(pageId: number, jumpTo: number, pageIndex: any): boolean { // First test the special values. @@ -2480,9 +2473,9 @@ export class AddonModLessonProvider { /** * Invalidates Lesson data. * - * @param {number} lessonId Lesson ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param lessonId Lesson ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateAccessInformation(lessonId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -2493,9 +2486,9 @@ export class AddonModLessonProvider { /** * Invalidates content pages viewed for all retakes. * - * @param {number} lessonId Lesson ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param lessonId Lesson ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateContentPagesViewed(lessonId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -2506,10 +2499,10 @@ export class AddonModLessonProvider { /** * Invalidates content pages viewed for a certain retake. * - * @param {number} lessonId Lesson ID. - * @param {number} retake Retake number. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param lessonId Lesson ID. + * @param retake Retake number. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateContentPagesViewedForRetake(lessonId: number, retake: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -2520,9 +2513,9 @@ export class AddonModLessonProvider { /** * Invalidates Lesson 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. */ invalidateLessonData(courseId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -2533,9 +2526,9 @@ export class AddonModLessonProvider { /** * Invalidates lesson with password. * - * @param {number} lessonId Lesson ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param lessonId Lesson ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateLessonWithPassword(lessonId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -2546,9 +2539,9 @@ export class AddonModLessonProvider { /** * Invalidates page data for all pages. * - * @param {number} lessonId Lesson ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param lessonId Lesson ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidatePageData(lessonId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -2559,10 +2552,10 @@ export class AddonModLessonProvider { /** * Invalidates page data for a certain page. * - * @param {number} lessonId Lesson ID. - * @param {number} pageId Page ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param lessonId Lesson ID. + * @param pageId Page ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidatePageDataForPage(lessonId: number, pageId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -2573,9 +2566,9 @@ export class AddonModLessonProvider { /** * Invalidates pages. * - * @param {number} lessonId Lesson ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param lessonId Lesson ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidatePages(lessonId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -2586,9 +2579,9 @@ export class AddonModLessonProvider { /** * Invalidates pages possible jumps. * - * @param {number} lessonId Lesson ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param lessonId Lesson ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidatePagesPossibleJumps(lessonId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -2599,9 +2592,9 @@ export class AddonModLessonProvider { /** * Invalidates questions attempts for all retakes. * - * @param {number} lessonId Lesson ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param lessonId Lesson ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateQuestionsAttempts(lessonId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -2612,11 +2605,11 @@ export class AddonModLessonProvider { /** * Invalidates question attempts for a certain retake and user. * - * @param {number} lessonId Lesson ID. - * @param {number} retake Retake number. - * @param {string} [siteId] Site ID. If not defined, current site.. - * @param {number} [userId] User ID. If not defined, site's user. - * @return {Promise} Promise resolved when the data is invalidated. + * @param lessonId Lesson ID. + * @param retake Retake number. + * @param siteId Site ID. If not defined, current site.. + * @param userId User ID. If not defined, site's user. + * @return Promise resolved when the data is invalidated. */ invalidateQuestionsAttemptsForRetake(lessonId: number, retake: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -2629,9 +2622,9 @@ export class AddonModLessonProvider { /** * Invalidates retakes overview for all groups in a lesson. * - * @param {number} lessonId Lesson ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param lessonId Lesson ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateRetakesOverview(lessonId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -2642,10 +2635,10 @@ export class AddonModLessonProvider { /** * Invalidates retakes overview for a certain group in a lesson. * - * @param {number} lessonId Lesson ID. - * @param {number} groupId Group ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param lessonId Lesson ID. + * @param groupId Group ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateRetakesOverviewForGroup(lessonId: number, groupId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -2656,9 +2649,9 @@ export class AddonModLessonProvider { /** * Invalidates timers for all users in a lesson. * - * @param {number} lessonId Lesson ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param lessonId Lesson ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateTimers(lessonId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -2669,10 +2662,10 @@ export class AddonModLessonProvider { /** * Invalidates timers for a certain user. * - * @param {number} lessonId Lesson ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User ID. If not defined, site's current user. - * @return {Promise} Promise resolved when the data is invalidated. + * @param lessonId Lesson ID. + * @param siteId Site ID. If not defined, current site. + * @param userId User ID. If not defined, site's current user. + * @return Promise resolved when the data is invalidated. */ invalidateTimersForUser(lessonId: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -2685,11 +2678,11 @@ export class AddonModLessonProvider { /** * Invalidates a certain retake for a certain user. * - * @param {number} lessonId Lesson ID. - * @param {number} retake Retake number. - * @param {number} [userId] User ID. Undefined for current user. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param lessonId Lesson ID. + * @param retake Retake number. + * @param userId User ID. Undefined for current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateUserRetake(lessonId: number, retake: number, userId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -2702,9 +2695,9 @@ export class AddonModLessonProvider { /** * Invalidates all retakes for all users in a lesson. * - * @param {number} lessonId Lesson ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param lessonId Lesson ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateUserRetakesForLesson(lessonId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -2715,10 +2708,10 @@ export class AddonModLessonProvider { /** * Invalidates all retakes for a certain user in a lesson. * - * @param {number} lessonId Lesson ID. - * @param {number} [userId] User ID. Undefined for current user. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param lessonId Lesson ID. + * @param userId User ID. Undefined for current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateUserRetakesForUser(lessonId: number, userId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -2731,11 +2724,11 @@ export class AddonModLessonProvider { /** * Check if a page answer is correct. * - * @param {any} lesson Lesson. - * @param {number} pageId The page ID. - * @param {any} answer The answer to check. - * @param {any} pageIndex Object containing all the pages indexed by ID. - * @return {boolean} Whether the answer is correct. + * @param lesson Lesson. + * @param pageId The page ID. + * @param answer The answer to check. + * @param pageIndex Object containing all the pages indexed by ID. + * @return Whether the answer is correct. */ protected isAnswerCorrect(lesson: any, pageId: number, answer: any, pageIndex: any): boolean { if (lesson.custom) { @@ -2749,8 +2742,8 @@ export class AddonModLessonProvider { /** * Check if a lesson is enabled to be used in offline. * - * @param {any} lesson Lesson. - * @return {boolean} Whether offline is enabled. + * @param lesson Lesson. + * @return Whether offline is enabled. */ isLessonOffline(lesson: any): boolean { return !!lesson.allowofflineattempts; @@ -2759,8 +2752,8 @@ export class AddonModLessonProvider { /** * Check if a lesson is password protected based in the access info. * - * @param {any} info Lesson access info. - * @return {boolean} Whether the lesson is password protected. + * @param info Lesson access info. + * @return Whether the lesson is password protected. */ isPasswordProtected(info: any): boolean { if (info && info.preventaccessreasons) { @@ -2779,8 +2772,8 @@ export class AddonModLessonProvider { /** * Return whether or not the plugin is enabled in a certain site. Plugin is enabled if the lesson WS are available. * - * @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) => { @@ -2792,8 +2785,8 @@ export class AddonModLessonProvider { /** * Check if a page is a question page or a content page. * - * @param {number} type Type of the page. - * @return {boolean} True if question page, false if content page. + * @param type Type of the page. + * @return True if question page, false if content page. */ isQuestionPage(type: number): boolean { return type == AddonModLessonProvider.TYPE_QUESTION; @@ -2802,12 +2795,12 @@ export class AddonModLessonProvider { /** * Start or continue a retake. * - * @param {string} id Lesson ID. - * @param {string} [password] Lesson password (if any). - * @param {number} [pageId] Page id to continue from (only when continuing a retake). - * @param {boolean} [review] If the user wants to review just after finishing (1 hour margin). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the WS call is successful. + * @param id Lesson ID. + * @param password Lesson password (if any). + * @param pageId Page id to continue from (only when continuing a retake). + * @param review If the user wants to review just after finishing (1 hour margin). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the WS call is successful. */ launchRetake(id: number, password?: string, pageId?: number, review?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -2837,8 +2830,8 @@ export class AddonModLessonProvider { /** * Check if the user left during a timed session. * - * @param {any} info Lesson access info. - * @return {boolean} True if left during timed, false otherwise. + * @param info Lesson access info. + * @return True if left during timed, false otherwise. */ leftDuringTimed(info: any): boolean { return info && info.lastpageseen && info.lastpageseen != AddonModLessonProvider.LESSON_EOL && info.leftduringtimedsession; @@ -2848,8 +2841,8 @@ export class AddonModLessonProvider { * Checks to see if a LESSON_CLUSTERJUMP or a LESSON_UNSEENBRANCHPAGE is used in a lesson. * Based on Moodle's lesson_display_teacher_warning. * - * @param {any} jumps Result of get pages possible jumps. - * @return {boolean} Whether the lesson uses one of those jumps. + * @param jumps Result of get pages possible jumps. + * @return Whether the lesson uses one of those jumps. */ lessonDisplayTeacherWarning(jumps: any): boolean { if (!jumps) { @@ -2875,14 +2868,14 @@ export class AddonModLessonProvider { * Calculates a user's grade for a lesson. * Based on Moodle's lesson_grade. * - * @param {any} lesson Lesson. - * @param {number} retake Retake number. - * @param {string} [password] Lesson password (if any). - * @param {boolean} [review] If the user wants to review just after finishing (1 hour margin). - * @param {any} [pageIndex] Object containing all the pages indexed by ID. If not provided, it will be calculated. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User ID. If not defined, site's user. - * @return {Promise} Promise resolved with the grade data. + * @param lesson Lesson. + * @param retake Retake number. + * @param password Lesson password (if any). + * @param review If the user wants to review just after finishing (1 hour margin). + * @param pageIndex Object containing all the pages indexed by ID. If not provided, it will be calculated. + * @param siteId Site ID. If not defined, current site. + * @param userId User ID. If not defined, site's user. + * @return Promise resolved with the grade data. */ lessonGrade(lesson: any, retake: number, password?: string, review?: boolean, pageIndex?: any, siteId?: string, userId?: number): Promise { @@ -3024,11 +3017,11 @@ export class AddonModLessonProvider { /** * Report a lesson as being viewed. * - * @param {string} id Module ID. - * @param {string} [password] Lesson password (if any). - * @param {string} [name] Name of the assign. - * @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 password Lesson password (if any). + * @param name Name of the assign. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the WS call is successful. */ logViewLesson(id: number, password?: string, name?: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -3049,17 +3042,17 @@ export class AddonModLessonProvider { /** * Process a lesson page, saving its data. * - * @param {any} lesson Lesson. - * @param {number} courseId Course ID the lesson belongs to. - * @param {any} pageData Result of getPageData for the page to process. - * @param {any} data Data to save. - * @param {string} [password] Lesson password (if any). - * @param {boolean} [review] If the user wants to review just after finishing (1 hour margin). - * @param {boolean} [offline] Whether it's offline mode. - * @param {any} [accessInfo] Result of get access info. Required if offline is true. - * @param {any} [jumps] Result of get pages possible jumps. Required if offline is true. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param lesson Lesson. + * @param courseId Course ID the lesson belongs to. + * @param pageData Result of getPageData for the page to process. + * @param data Data to save. + * @param password Lesson password (if any). + * @param review If the user wants to review just after finishing (1 hour margin). + * @param offline Whether it's offline mode. + * @param accessInfo Result of get access info. Required if offline is true. + * @param jumps Result of get pages possible jumps. Required if offline is true. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ processPage(lesson: any, courseId: number, pageData: any, data: any, password?: string, review?: boolean, offline?: boolean, accessInfo?: boolean, jumps?: any, siteId?: string): Promise { @@ -3118,13 +3111,13 @@ export class AddonModLessonProvider { /** * Process a lesson page, saving its data. It will fail if offline or cannot connect. * - * @param {number} lessonId Lesson ID. - * @param {number} pageId Page ID. - * @param {any} data Data to save. - * @param {string} [password] Lesson password (if any). - * @param {boolean} [review] If the user wants to review just after finishing (1 hour margin). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved in success, rejected otherwise. + * @param lessonId Lesson ID. + * @param pageId Page ID. + * @param data Data to save. + * @param password Lesson password (if any). + * @param review If the user wants to review just after finishing (1 hour margin). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved in success, rejected otherwise. */ processPageOnline(lessonId: number, pageId: number, data: any, password?: string, review?: boolean, siteId?: string) : Promise { @@ -3149,16 +3142,16 @@ export class AddonModLessonProvider { * Records an attempt on a certain page. * Based on Moodle's record_attempt. * - * @param {any} lesson Lesson. - * @param {number} courseId Course ID the lesson belongs to. - * @param {any} pageData Result of getPageData for the page to process. - * @param {any} data Data to save. - * @param {boolean} review If the user wants to review just after finishing (1 hour margin). - * @param {any} accessInfo Result of get access info. - * @param {any} jumps Result of get pages possible jumps. - * @param {any} pageIndex Object containing all the pages indexed by ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the result. + * @param lesson Lesson. + * @param courseId Course ID the lesson belongs to. + * @param pageData Result of getPageData for the page to process. + * @param data Data to save. + * @param review If the user wants to review just after finishing (1 hour margin). + * @param accessInfo Result of get access info. + * @param jumps Result of get pages possible jumps. + * @param pageIndex Object containing all the pages indexed by ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the result. */ protected recordAttempt(lesson: any, courseId: number, pageData: any, data: any, review: boolean, accessInfo: any, jumps: any, pageIndex: any, siteId?: string): Promise { @@ -3333,9 +3326,9 @@ export class AddonModLessonProvider { /** * Remove a password stored in DB. * - * @param {number} lessonId Lesson ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when removed. + * @param lessonId Lesson ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when removed. */ removeStoredPassword(lessonId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -3346,10 +3339,10 @@ export class AddonModLessonProvider { /** * Store a password in DB. * - * @param {number} lessonId Lesson ID. - * @param {string} password Password to store. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when stored. + * @param lessonId Lesson ID. + * @param password Password to store. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when stored. */ storePassword(lessonId: number, password: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -3368,11 +3361,11 @@ export class AddonModLessonProvider { * modify the list of viewedPagesIds for cluster pages. * Based on Moodle's valid_page_and_view. * - * @param {any} pages Index of lesson pages, indexed by page ID. See createPagesIndex. - * @param {any} page Page to check. - * @param {any} validPages Valid pages, indexed by page ID. - * @param {number[]} viewedPagesIds List of viewed pages IDs. - * @return {number} Next page ID. + * @param pages Index of lesson pages, indexed by page ID. See createPagesIndex. + * @param page Page to check. + * @param validPages Valid pages, indexed by page ID. + * @param viewedPagesIds List of viewed pages IDs. + * @return Next page ID. */ validPageAndView(pages: any, page: any, validPages: any, viewedPagesIds: number[]): number { diff --git a/src/addon/mod/lesson/providers/list-link-handler.ts b/src/addon/mod/lesson/providers/list-link-handler.ts index d208a3bba..273e6d6f8 100644 --- a/src/addon/mod/lesson/providers/list-link-handler.ts +++ b/src/addon/mod/lesson/providers/list-link-handler.ts @@ -33,7 +33,7 @@ export class AddonModLessonListLinkHandler extends CoreContentLinksModuleListHan /** * Check if the handler is enabled on a site level. * - * @return {Promise} Promise resolved with boolean: whether or not the handler is enabled on a site level. + * @return Promise resolved with boolean: whether or not the handler is enabled on a site level. */ isEnabled(): Promise { return this.lessonProvider.isPluginEnabled(); diff --git a/src/addon/mod/lesson/providers/module-handler.ts b/src/addon/mod/lesson/providers/module-handler.ts index ec28dc40c..a2e706cd0 100644 --- a/src/addon/mod/lesson/providers/module-handler.ts +++ b/src/addon/mod/lesson/providers/module-handler.ts @@ -45,7 +45,7 @@ export class AddonModLessonModuleHandler implements CoreCourseModuleHandler { /** * Check if the handler is enabled on a site level. * - * @return {Promise} Promise resolved with boolean: whether or not the handler is enabled on a site level. + * @return Promise resolved with boolean: whether or not the handler is enabled on a site level. */ isEnabled(): Promise { return this.lessonProvider.isPluginEnabled(); @@ -54,10 +54,10 @@ export class AddonModLessonModuleHandler 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 AddonModLessonModuleHandler 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 AddonModLessonIndexComponent; diff --git a/src/addon/mod/lesson/providers/prefetch-handler.ts b/src/addon/mod/lesson/providers/prefetch-handler.ts index bbd7666c6..80a32c44b 100644 --- a/src/addon/mod/lesson/providers/prefetch-handler.ts +++ b/src/addon/mod/lesson/providers/prefetch-handler.ts @@ -50,8 +50,8 @@ export class AddonModLessonPrefetchHandler extends CoreCourseActivityPrefetchHan /** * Ask password. * - * @param {any} info Lesson access info. - * @return {Promise} Promise resolved with the password. + * @param info Lesson access info. + * @return Promise resolved with the password. */ protected askUserPassword(info: any): Promise { // Create and show the modal. @@ -74,11 +74,11 @@ export class AddonModLessonPrefetchHandler extends CoreCourseActivityPrefetchHan /** * 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: any, single?: boolean): Promise<{ size: number, total: boolean }> { const siteId = this.sitesProvider.getCurrentSiteId(); @@ -115,12 +115,12 @@ export class AddonModLessonPrefetchHandler extends CoreCourseActivityPrefetchHan /** * Get the lesson password if needed. If not stored, it can ask the user to enter it. * - * @param {number} lessonId Lesson ID. - * @param {boolean} [forceCache] 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} [askPassword] True if we should ask for password if needed, false otherwise. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise<{password?: string, lesson?: any, accessInfo: any}>} Promise resolved when done. + * @param lessonId Lesson ID. + * @param forceCache 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 askPassword True if we should ask for password if needed, false otherwise. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ getLessonPassword(lessonId: number, forceCache?: boolean, ignoreCache?: boolean, askPassword?: boolean, siteId?: string) : Promise<{password?: string, lesson?: any, accessInfo: any}> { @@ -167,9 +167,9 @@ export class AddonModLessonPrefetchHandler extends CoreCourseActivityPrefetchHan /** * 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 { // Only invalidate the data that doesn't ignore cache when prefetching. @@ -185,9 +185,9 @@ export class AddonModLessonPrefetchHandler extends CoreCourseActivityPrefetchHan /** * 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 siteId = this.sitesProvider.getCurrentSiteId(); @@ -206,9 +206,9 @@ export class AddonModLessonPrefetchHandler extends CoreCourseActivityPrefetchHan /** * 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 { const siteId = this.sitesProvider.getCurrentSiteId(); @@ -230,7 +230,7 @@ export class AddonModLessonPrefetchHandler extends CoreCourseActivityPrefetchHan /** * 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.lessonProvider.isPluginEnabled(); @@ -239,11 +239,11 @@ export class AddonModLessonPrefetchHandler extends CoreCourseActivityPrefetchHan /** * 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.prefetchLesson.bind(this)); @@ -252,11 +252,11 @@ export class AddonModLessonPrefetchHandler extends CoreCourseActivityPrefetchHan /** * Prefetch a lesson. * - * @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. - * @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. + * @return Promise resolved when done. */ protected prefetchLesson(module: any, courseId: number, single: boolean, siteId: string): Promise { let lesson, @@ -427,13 +427,13 @@ export class AddonModLessonPrefetchHandler extends CoreCourseActivityPrefetchHan /** * Validate the password. * - * @param {number} lessonId Lesson ID. - * @param {any} info Lesson access info. - * @param {string} pwd Password to check. - * @param {boolean} [forceCache] 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<{password: string, lesson: any, accessInfo: any}>} Promise resolved when done. + * @param lessonId Lesson ID. + * @param info Lesson access info. + * @param pwd Password to check. + * @param forceCache 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 when done. */ protected validatePassword(lessonId: number, info: any, pwd: string, forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise<{password: string, lesson: any, accessInfo: any}> { @@ -455,10 +455,10 @@ export class AddonModLessonPrefetchHandler extends CoreCourseActivityPrefetchHan /** * 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/lesson/providers/push-click-handler.ts b/src/addon/mod/lesson/providers/push-click-handler.ts index 649436683..db47c4b56 100644 --- a/src/addon/mod/lesson/providers/push-click-handler.ts +++ b/src/addon/mod/lesson/providers/push-click-handler.ts @@ -33,8 +33,8 @@ export class AddonModLessonPushClickHandler implements CorePushNotificationsClic /** * 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 == 'mod_lesson' && @@ -49,8 +49,8 @@ export class AddonModLessonPushClickHandler implements CorePushNotificationsClic /** * 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/mod/lesson/providers/report-link-handler.ts b/src/addon/mod/lesson/providers/report-link-handler.ts index 76d8703ac..790f110b2 100644 --- a/src/addon/mod/lesson/providers/report-link-handler.ts +++ b/src/addon/mod/lesson/providers/report-link-handler.ts @@ -40,11 +40,11 @@ export class AddonModLessonReportLinkHandler 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 { @@ -67,11 +67,11 @@ export class AddonModLessonReportLinkHandler 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.action == 'reportdetail' && !params.userid) { @@ -85,12 +85,12 @@ export class AddonModLessonReportLinkHandler extends CoreContentLinksHandlerBase /** * Open report overview. * - * @param {number} moduleId Module ID. - * @param {number} courseId Course ID. - * @param {string} groupId Group ID. - * @param {string} siteId Site ID. - * @param {NavController} [navCtrl] The NavController to use to navigate. - * @return {Promise} Promise resolved when done. + * @param moduleId Module ID. + * @param courseId Course ID. + * @param groupId Group ID. + * @param siteId Site ID. + * @param navCtrl The NavController to use to navigate. + * @return Promise resolved when done. */ protected openReportOverview(moduleId: number, courseId?: number, groupId?: number, siteId?: string, navCtrl?: NavController) : Promise { @@ -119,14 +119,14 @@ export class AddonModLessonReportLinkHandler extends CoreContentLinksHandlerBase /** * Open a user's retake. * - * @param {number} moduleId Module ID. - * @param {number} userId User ID. - * @param {number} courseId Course ID. - * @param {number} retake Retake to open. - * @param {string} groupId Group ID. - * @param {string} siteId Site ID. - * @param {NavController} [navCtrl] The NavController to use to navigate. - * @return {Promise} Promise resolved when done. + * @param moduleId Module ID. + * @param userId User ID. + * @param courseId Course ID. + * @param retake Retake to open. + * @param groupId Group ID. + * @param siteId Site ID. + * @param navCtrl The NavController to use to navigate. + * @return Promise resolved when done. */ protected openUserRetake(moduleId: number, userId: number, courseId: number, retake: number, siteId: string, navCtrl?: NavController): Promise { diff --git a/src/addon/mod/lesson/providers/sync-cron-handler.ts b/src/addon/mod/lesson/providers/sync-cron-handler.ts index 3d204645a..446eb712c 100644 --- a/src/addon/mod/lesson/providers/sync-cron-handler.ts +++ b/src/addon/mod/lesson/providers/sync-cron-handler.ts @@ -29,9 +29,9 @@ export class AddonModLessonSyncCronHandler 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.lessonSync.syncAllLessons(siteId, force); @@ -40,7 +40,7 @@ export class AddonModLessonSyncCronHandler 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.lessonSync.syncInterval; diff --git a/src/addon/mod/lti/components/index/index.ts b/src/addon/mod/lti/components/index/index.ts index f61c1e50f..53819e792 100644 --- a/src/addon/mod/lti/components/index/index.ts +++ b/src/addon/mod/lti/components/index/index.ts @@ -57,10 +57,10 @@ export class AddonModLtiIndexComponent extends CoreCourseModuleMainActivityCompo /** * Get the LTI 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 { return this.ltiProvider.getLti(this.courseId, this.module.id).then((ltiData) => { @@ -76,7 +76,7 @@ export class AddonModLtiIndexComponent extends CoreCourseModuleMainActivityCompo /** * Perform the invalidate content function. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected invalidateContent(): Promise { const promises = []; diff --git a/src/addon/mod/lti/pages/index/index.ts b/src/addon/mod/lti/pages/index/index.ts index ea6ab0034..7109d0d7d 100644 --- a/src/addon/mod/lti/pages/index/index.ts +++ b/src/addon/mod/lti/pages/index/index.ts @@ -40,7 +40,7 @@ export class AddonModLtiIndexPage { /** * Update some data based on the LTI instance. * - * @param {any} lti LTI instance. + * @param lti LTI instance. */ updateData(lti: any): void { this.title = lti.name || this.title; diff --git a/src/addon/mod/lti/providers/lti.ts b/src/addon/mod/lti/providers/lti.ts index b9c1eea85..73eef5121 100644 --- a/src/addon/mod/lti/providers/lti.ts +++ b/src/addon/mod/lti/providers/lti.ts @@ -50,7 +50,7 @@ export class AddonModLtiProvider { /** * Delete launcher. * - * @return {Promise} Promise resolved when the launcher file is deleted. + * @return Promise resolved when the launcher file is deleted. */ deleteLauncher(): Promise { return this.fileProvider.removeFile(this.LAUNCHER_FILE_NAME); @@ -59,9 +59,9 @@ export class AddonModLtiProvider { /** * Generates a launcher file. * - * @param {string} url Launch URL. - * @param {AddonModLtiParam[]} params Launch params. - * @return {Promise} Promise resolved with the file URL. + * @param url Launch URL. + * @param params Launch params. + * @return Promise resolved with the file URL. */ generateLauncher(url: string, params: AddonModLtiParam[]): Promise { if (!this.fileProvider.isAvailable()) { @@ -100,9 +100,9 @@ export class AddonModLtiProvider { /** * Get a LTI. * - * @param {number} courseId Course ID. - * @param {number} cmId Course module ID. - * @return {Promise} Promise resolved when the LTI is retrieved. + * @param courseId Course ID. + * @param cmId Course module ID. + * @return Promise resolved when the LTI is retrieved. */ getLti(courseId: number, cmId: number): Promise { const params: any = { @@ -128,8 +128,8 @@ export class AddonModLtiProvider { /** * Get cache key for LTI data WS calls. * - * @param {number} courseId Course ID. - * @return {string} Cache key. + * @param courseId Course ID. + * @return Cache key. */ protected getLtiCacheKey(courseId: number): string { return this.ROOT_CACHE_KEY + 'lti:' + courseId; @@ -138,8 +138,8 @@ export class AddonModLtiProvider { /** * Get a LTI launch data. * - * @param {number} id LTI id. - * @return {Promise} Promise resolved when the launch data is retrieved. + * @param id LTI id. + * @return Promise resolved when the launch data is retrieved. */ getLtiLaunchData(id: number): Promise { const params: any = { @@ -166,8 +166,8 @@ export class AddonModLtiProvider { /** * Get cache key for LTI launch data WS calls. * - * @param {number} id LTI id. - * @return {string} Cache key. + * @param id LTI id. + * @return Cache key. */ protected getLtiLaunchDataCacheKey(id: number): string { return this.ROOT_CACHE_KEY + 'launch:' + id; @@ -176,8 +176,8 @@ export class AddonModLtiProvider { /** * Invalidates LTI 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. */ invalidateLti(courseId: number): Promise { return this.sitesProvider.getCurrentSite().invalidateWsCacheForKey(this.getLtiCacheKey(courseId)); @@ -186,8 +186,8 @@ export class AddonModLtiProvider { /** * Invalidates options. * - * @param {number} id LTI id. - * @return {Promise} Promise resolved when the data is invalidated. + * @param id LTI id. + * @return Promise resolved when the data is invalidated. */ invalidateLtiLaunchData(id: number): Promise { return this.sitesProvider.getCurrentSite().invalidateWsCacheForKey(this.getLtiLaunchDataCacheKey(id)); @@ -196,9 +196,9 @@ export class AddonModLtiProvider { /** * Launch LTI. * - * @param {string} url Launch URL. - * @param {AddonModLtiParam[]} params Launch params. - * @return {Promise} Promise resolved when the WS call is successful. + * @param url Launch URL. + * @param params Launch params. + * @return Promise resolved when the WS call is successful. */ launch(url: string, params: AddonModLtiParam[]): Promise { if (!this.urlUtils.isHttpURL(url)) { @@ -214,10 +214,10 @@ export class AddonModLtiProvider { /** * Report the LTI as being viewed. * - * @param {string} id LTI id. - * @param {string} [name] Name of the lti. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the WS call is successful. + * @param id LTI id. + * @param name Name of the lti. + * @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: any = { diff --git a/src/addon/mod/lti/providers/module-handler.ts b/src/addon/mod/lti/providers/module-handler.ts index 351fd9020..36757b9a8 100644 --- a/src/addon/mod/lti/providers/module-handler.ts +++ b/src/addon/mod/lti/providers/module-handler.ts @@ -55,7 +55,7 @@ export class AddonModLtiModuleHandler 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 true; @@ -64,10 +64,10 @@ export class AddonModLtiModuleHandler 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 = { @@ -137,9 +137,9 @@ export class AddonModLtiModuleHandler 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 AddonModLtiIndexComponent; diff --git a/src/addon/mod/page/components/index/index.ts b/src/addon/mod/page/components/index/index.ts index 254bc0f38..bc6b76671 100644 --- a/src/addon/mod/page/components/index/index.ts +++ b/src/addon/mod/page/components/index/index.ts @@ -64,7 +64,7 @@ export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComp /** * Perform the invalidate content function. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected invalidateContent(): Promise { return this.pageProvider.invalidateContent(this.module.id, this.courseId); @@ -73,8 +73,8 @@ export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComp /** * Download page 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; diff --git a/src/addon/mod/page/pages/index/index.ts b/src/addon/mod/page/pages/index/index.ts index 089c14afb..8b224d3df 100644 --- a/src/addon/mod/page/pages/index/index.ts +++ b/src/addon/mod/page/pages/index/index.ts @@ -40,7 +40,7 @@ export class AddonModPageIndexPage { /** * Update some data based on the page instance. * - * @param {any} page Page instance. + * @param page Page instance. */ updateData(page: any): void { this.title = page.name || this.title; diff --git a/src/addon/mod/page/providers/helper.ts b/src/addon/mod/page/providers/helper.ts index 0c15ecdff..13f7209a4 100644 --- a/src/addon/mod/page/providers/helper.ts +++ b/src/addon/mod/page/providers/helper.ts @@ -39,9 +39,9 @@ export class AddonModPageHelperProvider { /** * Gets the page HTML. * - * @param {any} contents The module contents. - * @param {number} moduleId The module ID. - * @return {Promise} The HTML of the page. + * @param contents The module contents. + * @param moduleId The module ID. + * @return The HTML of the page. */ getPageHtml(contents: any, moduleId: number): Promise { let indexUrl, @@ -100,8 +100,8 @@ export class AddonModPageHelperProvider { /** * Returns whether the file is the main page of the module. * - * @param {any} file An object returned from WS containing file info. - * @return {boolean} Whether the file is the main page or not. + * @param file An object returned from WS containing file info. + * @return Whether the file is the main page or not. */ protected isMainPage(file: any): boolean { const filename = file.filename || '', diff --git a/src/addon/mod/page/providers/list-link-handler.ts b/src/addon/mod/page/providers/list-link-handler.ts index 67358bb95..d56ba9d85 100644 --- a/src/addon/mod/page/providers/list-link-handler.ts +++ b/src/addon/mod/page/providers/list-link-handler.ts @@ -33,7 +33,7 @@ export class AddonModPageListLinkHandler extends CoreContentLinksModuleListHandl /** * 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.pageProvider.isPluginEnabled(); diff --git a/src/addon/mod/page/providers/module-handler.ts b/src/addon/mod/page/providers/module-handler.ts index 34b827807..f54293b51 100644 --- a/src/addon/mod/page/providers/module-handler.ts +++ b/src/addon/mod/page/providers/module-handler.ts @@ -45,7 +45,7 @@ export class AddonModPageModuleHandler 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.pageProvider.isPluginEnabled(); @@ -54,10 +54,10 @@ export class AddonModPageModuleHandler 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 AddonModPageModuleHandler 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 AddonModPageIndexComponent; diff --git a/src/addon/mod/page/providers/page.ts b/src/addon/mod/page/providers/page.ts index 95ac2f311..ba42a2412 100644 --- a/src/addon/mod/page/providers/page.ts +++ b/src/addon/mod/page/providers/page.ts @@ -40,10 +40,10 @@ export class AddonModPageProvider { /** * Get a page 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 book 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 book is retrieved. */ getPageData(courseId: number, cmId: number, siteId?: string): Promise { return this.getPageByKey(courseId, 'coursemodule', cmId, siteId); @@ -52,11 +52,11 @@ export class AddonModPageProvider { /** * Get a page. * - * @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 book 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 book is retrieved. */ protected getPageByKey(courseId: number, key: string, value: any, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -86,8 +86,8 @@ export class AddonModPageProvider { /** * Get cache key for page data WS calls. * - * @param {number} courseId Course ID. - * @return {string} Cache key. + * @param courseId Course ID. + * @return Cache key. */ protected getPageCacheKey(courseId: number): string { return this.ROOT_CACHE_KEY + 'page:' + courseId; @@ -96,10 +96,9 @@ export class AddonModPageProvider { /** * 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} + * @param moduleId The module ID. + * @param courseId Course ID of the module. + * @param siteId Site ID. If not defined, current site. */ invalidateContent(moduleId: number, courseId: number, siteId?: string): Promise { const promises = []; @@ -114,9 +113,9 @@ export class AddonModPageProvider { /** * Invalidates page 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. */ invalidatePageData(courseId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -127,7 +126,7 @@ export class AddonModPageProvider { /** * Returns whether or not getPage WS available or not. * - * @return {boolean} If WS is avalaible. + * @return If WS is avalaible. * @since 3.3 */ isGetPageWSAvailable(): boolean { @@ -137,8 +136,8 @@ export class AddonModPageProvider { /** * 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) => { @@ -149,10 +148,10 @@ export class AddonModPageProvider { /** * Report a page as being viewed. * - * @param {number} id Module ID. - * @param {string} [name] Name of the page. - * @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 page. + * @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 = { diff --git a/src/addon/mod/page/providers/pluginfile-handler.ts b/src/addon/mod/page/providers/pluginfile-handler.ts index 4fa49fcdc..dd7e928af 100644 --- a/src/addon/mod/page/providers/pluginfile-handler.ts +++ b/src/addon/mod/page/providers/pluginfile-handler.ts @@ -26,8 +26,8 @@ export class AddonModPagePluginFileHandler 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,8 +40,8 @@ export class AddonModPagePluginFileHandler 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 diff --git a/src/addon/mod/page/providers/prefetch-handler.ts b/src/addon/mod/page/providers/prefetch-handler.ts index 87000313c..69043da50 100644 --- a/src/addon/mod/page/providers/prefetch-handler.ts +++ b/src/addon/mod/page/providers/prefetch-handler.ts @@ -45,13 +45,13 @@ export class AddonModPagePrefetchHandler extends CoreCourseResourcePrefetchHandl /** * 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 page. - * @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 page. + * @return Promise resolved when all content is downloaded. Data returned is not reliable. */ downloadOrPrefetch(module: any, courseId: number, prefetch?: boolean, dirPath?: string): Promise { const promises = []; @@ -68,9 +68,9 @@ export class AddonModPagePrefetchHandler extends CoreCourseResourcePrefetchHandl /** * 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.pageProvider.invalidateContent(moduleId, courseId); @@ -79,9 +79,9 @@ export class AddonModPagePrefetchHandler extends CoreCourseResourcePrefetchHandl /** * 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/quiz/accessrules/delaybetweenattempts/providers/handler.ts b/src/addon/mod/quiz/accessrules/delaybetweenattempts/providers/handler.ts index d102e9c52..518904872 100644 --- a/src/addon/mod/quiz/accessrules/delaybetweenattempts/providers/handler.ts +++ b/src/addon/mod/quiz/accessrules/delaybetweenattempts/providers/handler.ts @@ -31,7 +31,7 @@ export class AddonModQuizAccessDelayBetweenAttemptsHandler implements AddonModQu /** * 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 AddonModQuizAccessDelayBetweenAttemptsHandler implements AddonModQu /** * 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 { return false; diff --git a/src/addon/mod/quiz/accessrules/ipaddress/providers/handler.ts b/src/addon/mod/quiz/accessrules/ipaddress/providers/handler.ts index ca2d1f3d0..e8d4616f0 100644 --- a/src/addon/mod/quiz/accessrules/ipaddress/providers/handler.ts +++ b/src/addon/mod/quiz/accessrules/ipaddress/providers/handler.ts @@ -31,7 +31,7 @@ export class AddonModQuizAccessIpAddressHandler implements AddonModQuizAccessRul /** * 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 AddonModQuizAccessIpAddressHandler implements AddonModQuizAccessRul /** * 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 { return false; diff --git a/src/addon/mod/quiz/accessrules/numattempts/providers/handler.ts b/src/addon/mod/quiz/accessrules/numattempts/providers/handler.ts index 125bb2e8c..e2027019d 100644 --- a/src/addon/mod/quiz/accessrules/numattempts/providers/handler.ts +++ b/src/addon/mod/quiz/accessrules/numattempts/providers/handler.ts @@ -31,7 +31,7 @@ export class AddonModQuizAccessNumAttemptsHandler implements AddonModQuizAccessR /** * 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 AddonModQuizAccessNumAttemptsHandler implements AddonModQuizAccessR /** * 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 { return false; diff --git a/src/addon/mod/quiz/accessrules/offlineattempts/providers/handler.ts b/src/addon/mod/quiz/accessrules/offlineattempts/providers/handler.ts index 5b7988b06..b5b84382a 100644 --- a/src/addon/mod/quiz/accessrules/offlineattempts/providers/handler.ts +++ b/src/addon/mod/quiz/accessrules/offlineattempts/providers/handler.ts @@ -33,12 +33,12 @@ export class AddonModQuizAccessOfflineAttemptsHandler implements AddonModQuizAcc /** * 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 { preflightData.confirmdatasaved = 1; @@ -49,8 +49,8 @@ export class AddonModQuizAccessOfflineAttemptsHandler implements AddonModQuizAcc * 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 { return AddonModQuizAccessOfflineAttemptsComponent; @@ -59,7 +59,7 @@ export class AddonModQuizAccessOfflineAttemptsHandler implements AddonModQuizAcc /** * 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; @@ -68,11 +68,11 @@ export class AddonModQuizAccessOfflineAttemptsHandler implements AddonModQuizAcc /** * 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 { if (prefetch) { diff --git a/src/addon/mod/quiz/accessrules/openclosedate/providers/handler.ts b/src/addon/mod/quiz/accessrules/openclosedate/providers/handler.ts index b801b0e24..09c5d8af2 100644 --- a/src/addon/mod/quiz/accessrules/openclosedate/providers/handler.ts +++ b/src/addon/mod/quiz/accessrules/openclosedate/providers/handler.ts @@ -32,7 +32,7 @@ export class AddonModQuizAccessOpenCloseDateHandler implements AddonModQuizAcces /** * 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 AddonModQuizAccessOpenCloseDateHandler implements AddonModQuizAcces /** * 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 { return false; @@ -54,10 +54,10 @@ export class AddonModQuizAccessOpenCloseDateHandler implements AddonModQuizAcces /** * 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 { // If this is a teacher preview after the close date, do not show the time. diff --git a/src/addon/mod/quiz/accessrules/password/providers/handler.ts b/src/addon/mod/quiz/accessrules/password/providers/handler.ts index be42e7591..ac3c7a970 100644 --- a/src/addon/mod/quiz/accessrules/password/providers/handler.ts +++ b/src/addon/mod/quiz/accessrules/password/providers/handler.ts @@ -60,12 +60,12 @@ export class AddonModQuizAccessPasswordHandler implements AddonModQuizAccessRule /** * 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 { if (quiz && quiz.id && typeof preflightData.quizpassword == 'undefined') { @@ -81,9 +81,9 @@ export class AddonModQuizAccessPasswordHandler implements AddonModQuizAccessRule /** * Get a password stored in DB. * - * @param {number} quizId Quiz ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the DB entry on success. + * @param quizId Quiz ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the DB entry on success. */ protected getPasswordEntry(quizId: number, siteId?: string): Promise { @@ -97,8 +97,8 @@ export class AddonModQuizAccessPasswordHandler implements AddonModQuizAccessRule * 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 { return AddonModQuizAccessPasswordComponent; @@ -107,7 +107,7 @@ export class AddonModQuizAccessPasswordHandler implements AddonModQuizAccessRule /** * 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; @@ -116,11 +116,11 @@ export class AddonModQuizAccessPasswordHandler implements AddonModQuizAccessRule /** * 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 { // If there's a password stored don't require the preflight since we'll use the stored one. @@ -135,12 +135,12 @@ export class AddonModQuizAccessPasswordHandler implements AddonModQuizAccessRule /** * 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 { @@ -154,12 +154,12 @@ export class AddonModQuizAccessPasswordHandler implements AddonModQuizAccessRule /** * 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 { @@ -173,9 +173,9 @@ export class AddonModQuizAccessPasswordHandler implements AddonModQuizAccessRule /** * Remove a password from DB. * - * @param {number} quizId Quiz ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param quizId Quiz ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ protected removePassword(quizId: number, siteId?: string): Promise { @@ -187,10 +187,10 @@ export class AddonModQuizAccessPasswordHandler implements AddonModQuizAccessRule /** * Store a password in DB. * - * @param {number} quizId Quiz ID. - * @param {string} password Password. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param quizId Quiz ID. + * @param password Password. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ protected storePassword(quizId: number, password: string, siteId?: string): Promise { diff --git a/src/addon/mod/quiz/accessrules/safebrowser/providers/handler.ts b/src/addon/mod/quiz/accessrules/safebrowser/providers/handler.ts index b5ec88157..b17e263ea 100644 --- a/src/addon/mod/quiz/accessrules/safebrowser/providers/handler.ts +++ b/src/addon/mod/quiz/accessrules/safebrowser/providers/handler.ts @@ -31,7 +31,7 @@ export class AddonModQuizAccessSafeBrowserHandler implements AddonModQuizAccessR /** * 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 AddonModQuizAccessSafeBrowserHandler implements AddonModQuizAccessR /** * 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 { return false; diff --git a/src/addon/mod/quiz/accessrules/securewindow/providers/handler.ts b/src/addon/mod/quiz/accessrules/securewindow/providers/handler.ts index d5bb5334b..218061e1f 100644 --- a/src/addon/mod/quiz/accessrules/securewindow/providers/handler.ts +++ b/src/addon/mod/quiz/accessrules/securewindow/providers/handler.ts @@ -31,7 +31,7 @@ export class AddonModQuizAccessSecureWindowHandler implements AddonModQuizAccess /** * 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 AddonModQuizAccessSecureWindowHandler implements AddonModQuizAccess /** * 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 { return false; diff --git a/src/addon/mod/quiz/accessrules/timelimit/providers/handler.ts b/src/addon/mod/quiz/accessrules/timelimit/providers/handler.ts index 8ddc8083d..41a23fe40 100644 --- a/src/addon/mod/quiz/accessrules/timelimit/providers/handler.ts +++ b/src/addon/mod/quiz/accessrules/timelimit/providers/handler.ts @@ -34,8 +34,8 @@ export class AddonModQuizAccessTimeLimitHandler implements AddonModQuizAccessRul * 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 { return AddonModQuizAccessTimeLimitComponent; @@ -44,7 +44,7 @@ export class AddonModQuizAccessTimeLimitHandler implements AddonModQuizAccessRul /** * 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; @@ -53,11 +53,11 @@ export class AddonModQuizAccessTimeLimitHandler implements AddonModQuizAccessRul /** * 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 { // Warning only required if the attempt is not already started. @@ -67,10 +67,10 @@ export class AddonModQuizAccessTimeLimitHandler implements AddonModQuizAccessRul /** * 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 { // If this is a teacher preview after the time limit expires, don't show the time left. diff --git a/src/addon/mod/quiz/classes/auto-save.ts b/src/addon/mod/quiz/classes/auto-save.ts index 9d31a9ab3..8a7be1f77 100644 --- a/src/addon/mod/quiz/classes/auto-save.ts +++ b/src/addon/mod/quiz/classes/auto-save.ts @@ -38,12 +38,12 @@ export class AddonModQuizAutoSave { /** * Constructor. * - * @param {string} formName Name of the form where the answers are stored. - * @param {string} buttonSelector Selector to find the button to show the connection error. - * @param {CoreLoggerProvider} loggerProvider CoreLoggerProvider instance. - * @param {PopoverController} popoverCtrl PopoverController instance. - * @param {CoreQuestionHelperProvider} questionHelper CoreQuestionHelperProvider instance. - * @param {AddonModQuizProvider} quizProvider AddonModQuizProvider instance. + * @param formName Name of the form where the answers are stored. + * @param buttonSelector Selector to find the button to show the connection error. + * @param loggerProvider CoreLoggerProvider instance. + * @param popoverCtrl PopoverController instance. + * @param questionHelper CoreQuestionHelperProvider instance. + * @param quizProvider AddonModQuizProvider instance. */ constructor(protected formName: string, protected buttonSelector: string, loggerProvider: CoreLoggerProvider, protected popoverCtrl: PopoverController, protected questionHelper: CoreQuestionHelperProvider, @@ -72,10 +72,10 @@ export class AddonModQuizAutoSave { /** * Check if the answers have changed in a page. * - * @param {any} quiz Quiz. - * @param {any} attempt Attempt. - * @param {any} preflightData Preflight data. - * @param {boolean} [offline] Whether the quiz is being attempted in offline mode. + * @param quiz Quiz. + * @param attempt Attempt. + * @param preflightData Preflight data. + * @param offline Whether the quiz is being attempted in offline mode. */ checkChanges(quiz: any, attempt: any, preflightData: any, offline?: boolean): void { if (this.autoSaveTimeout) { @@ -110,7 +110,7 @@ export class AddonModQuizAutoSave { /** * Get answers from a form. * - * @return {any} Answers. + * @return Answers. */ protected getAnswers(): any { return this.questionHelper.getAnswersFromForm(document.forms[this.formName]); @@ -128,7 +128,7 @@ export class AddonModQuizAutoSave { * Returns an observable that will notify when an error happens or stops. * It will send true when there's an error, and false when the error has been ammended. * - * @return {BehaviorSubject} Observable. + * @return Observable. */ onError(): BehaviorSubject { return this.errorObservable; @@ -137,10 +137,10 @@ export class AddonModQuizAutoSave { /** * Schedule an auto save process if it's not scheduled already. * - * @param {any} quiz Quiz. - * @param {any} attempt Attempt. - * @param {any} preflightData Preflight data. - * @param {boolean} [offline] Whether the quiz is being attempted in offline mode. + * @param quiz Quiz. + * @param attempt Attempt. + * @param preflightData Preflight data. + * @param offline Whether the quiz is being attempted in offline mode. */ setAutoSaveTimer(quiz: any, attempt: any, preflightData: any, offline?: boolean): void { // Don't schedule if already shceduled or quiz is almost closed. @@ -190,10 +190,10 @@ export class AddonModQuizAutoSave { /** * Start a process to periodically check changes in answers. * - * @param {any} quiz Quiz. - * @param {any} attempt Attempt. - * @param {any} preflightData Preflight data. - * @param {boolean} [offline] Whether the quiz is being attempted in offline mode. + * @param quiz Quiz. + * @param attempt Attempt. + * @param preflightData Preflight data. + * @param offline Whether the quiz is being attempted in offline mode. */ startCheckChangesProcess(quiz: any, attempt: any, preflightData: any, offline?: boolean): void { if (this.checkChangesInterval || !quiz.autosaveperiod) { diff --git a/src/addon/mod/quiz/components/index/index.ts b/src/addon/mod/quiz/components/index/index.ts index cabdb8406..5aae59e52 100644 --- a/src/addon/mod/quiz/components/index/index.ts +++ b/src/addon/mod/quiz/components/index/index.ts @@ -148,10 +148,10 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp /** * Get the quiz 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 { @@ -235,7 +235,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp /** * Get the user attempts in the quiz and the result info. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected getAttempts(): Promise { @@ -314,7 +314,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp /** * Get result info to show. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected getResultInfo(): Promise { @@ -370,7 +370,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp /** * Go to review an attempt that has just been finished. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected goToAutoReview(): Promise { // If we go to auto review it means an attempt was finished. Check completion status. @@ -393,8 +393,8 @@ export class AddonModQuizIndexComponent 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 { if (result.attemptFinished) { @@ -459,7 +459,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp /** * Perform the invalidate content function. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected invalidateContent(): Promise { const promises = []; @@ -482,8 +482,8 @@ export class AddonModQuizIndexComponent 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 (syncEventData.attemptFinished) { @@ -510,8 +510,8 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp /** * 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.showStatusSpinner = status == CoreConstants.DOWNLOADING; @@ -526,7 +526,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp /** * Performs the sync of the activity. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected sync(): Promise { return this.quizSync.syncQuiz(this.quizData, true); @@ -535,8 +535,8 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp /** * Treat user attempts. * - * @param {any} attempts The attempts to treat. - * @return {Promise} Promise resolved when done. + * @param attempts The attempts to treat. + * @return Promise resolved when done. */ protected treatAttempts(attempts: any): Promise { if (!attempts || !attempts.length) { diff --git a/src/addon/mod/quiz/pages/attempt/attempt.ts b/src/addon/mod/quiz/pages/attempt/attempt.ts index 8119dbea7..ea7befc58 100644 --- a/src/addon/mod/quiz/pages/attempt/attempt.ts +++ b/src/addon/mod/quiz/pages/attempt/attempt.ts @@ -57,7 +57,7 @@ export class AddonModQuizAttemptPage implements OnInit { /** * Refresh the data. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ doRefresh(refresher: any): void { this.refreshData().finally(() => { @@ -68,7 +68,7 @@ export class AddonModQuizAttemptPage implements OnInit { /** * Get quiz data and attempt data. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchQuizData(): Promise { return this.quizProvider.getQuizById(this.courseId, this.quizId).then((quizData) => { @@ -84,7 +84,7 @@ export class AddonModQuizAttemptPage implements OnInit { /** * Get the attempt data. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchAttempt(): Promise { const promises = []; @@ -158,7 +158,7 @@ export class AddonModQuizAttemptPage implements OnInit { /** * Refresh the data. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected refreshData(): Promise { const promises = []; diff --git a/src/addon/mod/quiz/pages/index/index.ts b/src/addon/mod/quiz/pages/index/index.ts index be398552e..2ad9ec51d 100644 --- a/src/addon/mod/quiz/pages/index/index.ts +++ b/src/addon/mod/quiz/pages/index/index.ts @@ -40,7 +40,7 @@ export class AddonModQuizIndexPage { /** * Update some data based on the quiz instance. * - * @param {any} quiz Quiz instance. + * @param quiz Quiz instance. */ updateData(quiz: any): void { this.title = quiz.name || this.title; diff --git a/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.ts b/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.ts index a01013fed..298153a42 100644 --- a/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.ts +++ b/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.ts @@ -30,7 +30,6 @@ export class AddonModQuizNavigationModalPage { * - Some attributes can change dynamically, and we don't want to create the modal everytime the user opens it. * - 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; @@ -51,8 +50,8 @@ export class AddonModQuizNavigationModalPage { /** * Load a certain page. * - * @param {number} page The page to load. - * @param {number} [slot] Slot of the question to scroll to. + * @param page The page to load. + * @param slot Slot of the question to scroll to. */ loadPage(page: number, slot: number): void { this.pageInstance.changePage && this.pageInstance.changePage(page, true, slot); diff --git a/src/addon/mod/quiz/pages/player/player.ts b/src/addon/mod/quiz/pages/player/player.ts index af65f378a..dc4a38a64 100644 --- a/src/addon/mod/quiz/pages/player/player.ts +++ b/src/addon/mod/quiz/pages/player/player.ts @@ -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.ts b/src/addon/mod/quiz/pages/preflight-modal/preflight-modal.ts index 7317b528d..f832b208e 100644 --- a/src/addon/mod/quiz/pages/preflight-modal/preflight-modal.ts +++ b/src/addon/mod/quiz/pages/preflight-modal/preflight-modal.ts @@ -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.ts b/src/addon/mod/quiz/pages/review/review.ts index 218d7cd60..fead003f9 100644 --- a/src/addon/mod/quiz/pages/review/review.ts +++ b/src/addon/mod/quiz/pages/review/review.ts @@ -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..788f2a191 100644 --- a/src/addon/mod/quiz/providers/access-rules-delegate.ts +++ b/src/addon/mod/quiz/providers/access-rules-delegate.ts @@ -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..1775139e2 100644 --- a/src/addon/mod/quiz/providers/grade-link-handler.ts +++ b/src/addon/mod/quiz/providers/grade-link-handler.ts @@ -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..96683ce00 100644 --- a/src/addon/mod/quiz/providers/helper.ts +++ b/src/addon/mod/quiz/providers/helper.ts @@ -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..cb39371ad 100644 --- a/src/addon/mod/quiz/providers/index-link-handler.ts +++ b/src/addon/mod/quiz/providers/index-link-handler.ts @@ -32,11 +32,11 @@ export class AddonModQuizIndexLinkHandler extends CoreContentLinksModuleIndexHan * 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/module-handler.ts b/src/addon/mod/quiz/providers/module-handler.ts index f3b484ce9..cf8ea190f 100644 --- a/src/addon/mod/quiz/providers/module-handler.ts +++ b/src/addon/mod/quiz/providers/module-handler.ts @@ -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..a06d15541 100644 --- a/src/addon/mod/quiz/providers/prefetch-handler.ts +++ b/src/addon/mod/quiz/providers/prefetch-handler.ts @@ -53,12 +53,12 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl /** * 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 +68,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 +91,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. @@ -128,13 +128,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 +165,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 +176,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 +193,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 +222,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 +231,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 +254,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 +385,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 +462,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 +523,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 +567,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..05958945f 100644 --- a/src/addon/mod/quiz/providers/push-click-handler.ts +++ b/src/addon/mod/quiz/providers/push-click-handler.ts @@ -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..e52cc1da6 100644 --- a/src/addon/mod/quiz/providers/quiz-offline.ts +++ b/src/addon/mod/quiz/providers/quiz-offline.ts @@ -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..881cb4f81 100644 --- a/src/addon/mod/quiz/providers/quiz-sync.ts +++ b/src/addon/mod/quiz/providers/quiz-sync.ts @@ -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..b13182ef8 100644 --- a/src/addon/mod/quiz/providers/quiz.ts +++ b/src/addon/mod/quiz/providers/quiz.ts @@ -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..c903756fd 100644 --- a/src/addon/mod/quiz/providers/review-link-handler.ts +++ b/src/addon/mod/quiz/providers/review-link-handler.ts @@ -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..439db41e3 100644 --- a/src/addon/mod/quiz/providers/sync-cron-handler.ts +++ b/src/addon/mod/quiz/providers/sync-cron-handler.ts @@ -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/resource/components/index/index.ts b/src/addon/mod/resource/components/index/index.ts index ab8ccffac..fd52daa25 100644 --- a/src/addon/mod/resource/components/index/index.ts +++ b/src/addon/mod/resource/components/index/index.ts @@ -64,7 +64,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 +73,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. diff --git a/src/addon/mod/resource/pages/index/index.ts b/src/addon/mod/resource/pages/index/index.ts index 0bc1de345..b1ae19404 100644 --- a/src/addon/mod/resource/pages/index/index.ts +++ b/src/addon/mod/resource/pages/index/index.ts @@ -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..cf315075a 100644 --- a/src/addon/mod/resource/providers/helper.ts +++ b/src/addon/mod/resource/providers/helper.ts @@ -46,9 +46,9 @@ 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, @@ -76,8 +76,8 @@ export class AddonModResourceHelperProvider { /** * 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) { @@ -108,9 +108,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 +132,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 +155,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 +169,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/list-link-handler.ts b/src/addon/mod/resource/providers/list-link-handler.ts index 8acc2d435..161816640 100644 --- a/src/addon/mod/resource/providers/list-link-handler.ts +++ b/src/addon/mod/resource/providers/list-link-handler.ts @@ -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..f4a9888bf 100644 --- a/src/addon/mod/resource/providers/module-handler.ts +++ b/src/addon/mod/resource/providers/module-handler.ts @@ -57,7 +57,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 +66,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 +116,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,9 +140,9 @@ 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 = []; @@ -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..bfda5c467 100644 --- a/src/addon/mod/resource/providers/pluginfile-handler.ts +++ b/src/addon/mod/resource/providers/pluginfile-handler.ts @@ -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,8 +40,8 @@ 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 diff --git a/src/addon/mod/resource/providers/prefetch-handler.ts b/src/addon/mod/resource/providers/prefetch-handler.ts index 7c3e7be65..eee3a26ef 100644 --- a/src/addon/mod/resource/providers/prefetch-handler.ts +++ b/src/addon/mod/resource/providers/prefetch-handler.ts @@ -45,10 +45,10 @@ export class AddonModResourcePrefetchHandler extends CoreCourseResourcePrefetchH /** * 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 +72,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 +105,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 +116,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 +132,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 +151,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..05c8e4620 100644 --- a/src/addon/mod/resource/providers/resource.ts +++ b/src/addon/mod/resource/providers/resource.ts @@ -40,8 +40,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,11 +50,11 @@ 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 { return this.sitesProvider.getSite(siteId).then((site) => { @@ -84,10 +84,10 @@ 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 { return this.getResourceDataByKey(courseId, 'coursemodule', cmId, siteId); @@ -96,10 +96,10 @@ export class AddonModResourceProvider { /** * 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 +116,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 +129,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 +139,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 +151,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 = { diff --git a/src/addon/mod/scorm/classes/data-model-12.ts b/src/addon/mod/scorm/classes/data-model-12.ts index 196d36fda..4c0fd345a 100644 --- a/src/addon/mod/scorm/classes/data-model-12.ts +++ b/src/addon/mod/scorm/classes/data-model-12.ts @@ -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/index/index.ts b/src/addon/mod/scorm/components/index/index.ts index 6672548b7..b93fe28d9 100644 --- a/src/addon/mod/scorm/components/index/index.ts +++ b/src/addon/mod/scorm/components/index/index.ts @@ -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 { @@ -263,7 +263,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 +285,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 +302,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 +351,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 +412,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 +429,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 +455,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 +490,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 +534,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 +549,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 +574,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/pages/index/index.ts b/src/addon/mod/scorm/pages/index/index.ts index 9179fc6d1..1ed77747d 100644 --- a/src/addon/mod/scorm/pages/index/index.ts +++ b/src/addon/mod/scorm/pages/index/index.ts @@ -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.ts b/src/addon/mod/scorm/pages/player/player.ts index 93024042c..f4c0528a1 100644 --- a/src/addon/mod/scorm/pages/player/player.ts +++ b/src/addon/mod/scorm/pages/player/player.ts @@ -163,7 +163,7 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy { /** * Calculate the next and previous SCO. * - * @param {number} scoId Current SCO ID. + * @param scoId Current SCO ID. */ protected calculateNextAndPreviousSco(scoId: number): void { this.previousSco = this.scormHelper.getPreviousScoFromToc(this.toc, scoId); @@ -173,8 +173,8 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy { /** * Determine the attempt to use, the mode (normal/preview) and if it's offline or online. * - * @param {AddonModScormAttemptCountResult} attemptsData Attempts count. - * @return {Promise} Promise resolved when done. + * @param attemptsData Attempts count. + * @return Promise resolved when done. */ protected determineAttemptAndMode(attemptsData: AddonModScormAttemptCountResult): Promise { let result; @@ -223,7 +223,7 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy { /** * Fetch data needed to play the SCORM. * - * @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 SCORM while it's being played. @@ -251,7 +251,7 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy { /** * Fetch the TOC. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchToc(): Promise { this.loadingToc = true; @@ -312,7 +312,7 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy { /** * Load a SCO. * - * @param {any} sco The SCO to load. + * @param sco The SCO to load. */ protected loadSco(sco: any): void { if (!this.dataModel) { @@ -385,7 +385,7 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy { /** * Show the TOC. * - * @param {MouseEvent} event Event. + * @param event Event. */ openToc(event: MouseEvent): void { const modal = this.modalCtrl.create('AddonModScormTocPage', { @@ -414,7 +414,7 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy { /** * Refresh the TOC. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected refreshToc(): Promise { return this.scormProvider.invalidateAllScormData(this.scorm.id).catch(() => { @@ -429,8 +429,8 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy { /** * Set SCORM start time. * - * @param {number} scoId SCO ID. - * @return {Promise} Promise resolved when done. + * @param scoId SCO ID. + * @return Promise resolved when done. */ protected setStartTime(scoId: number): Promise { const tracks = [{ diff --git a/src/addon/mod/scorm/pages/toc/toc.ts b/src/addon/mod/scorm/pages/toc/toc.ts index 036196b6c..3d1074c12 100644 --- a/src/addon/mod/scorm/pages/toc/toc.ts +++ b/src/addon/mod/scorm/pages/toc/toc.ts @@ -45,7 +45,7 @@ export class AddonModScormTocPage { /** * Function called when a SCO is clicked. * - * @param {any} sco Clicked SCO. + * @param sco Clicked SCO. */ loadSco(sco: any): void { if (!sco.prereq || !sco.isvisible || !sco.launch) { diff --git a/src/addon/mod/scorm/providers/helper.ts b/src/addon/mod/scorm/providers/helper.ts index ec0e1523b..17b9ddf3f 100644 --- a/src/addon/mod/scorm/providers/helper.ts +++ b/src/addon/mod/scorm/providers/helper.ts @@ -37,9 +37,9 @@ export class AddonModScormHelperProvider { /** * Show a confirm dialog if needed. If SCORM doesn't have size, try to calculate it. * - * @param {any} scorm SCORM to download. - * @param {boolean} [isOutdated] True if package outdated, false if not outdated, undefined to calculate it. - * @return {Promise} Promise resolved if the user confirms or no confirmation needed. + * @param scorm SCORM to download. + * @param isOutdated True if package outdated, false if not outdated, undefined to calculate it. + * @return Promise resolved if the user confirms or no confirmation needed. */ confirmDownload(scorm: any, isOutdated?: boolean): Promise { // Check if file should be downloaded. @@ -69,10 +69,10 @@ export class AddonModScormHelperProvider { /** * Creates a new offline attempt based on an existing online attempt. * - * @param {any} scorm SCORM. - * @param {number} attempt Number of the online attempt. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the attempt is created. + * @param scorm SCORM. + * @param attempt Number of the online attempt. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the attempt is created. */ convertAttemptToOffline(scorm: any, attempt: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -121,11 +121,11 @@ export class AddonModScormHelperProvider { /** * Creates a new offline attempt. * - * @param {any} scorm SCORM. - * @param {number} newAttempt Number of the new attempt. - * @param {number} lastOnline Number of the last online attempt. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the attempt is created. + * @param scorm SCORM. + * @param newAttempt Number of the new attempt. + * @param lastOnline Number of the last online attempt. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the attempt is created. */ createOfflineAttempt(scorm: any, newAttempt: number, lastOnline: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -158,10 +158,10 @@ export class AddonModScormHelperProvider { * - The last incomplete online attempt if it hasn't been continued in offline and all offline attempts are complete. * - The attempt with highest number without surpassing max attempts otherwise. * - * @param {any} scorm SCORM object. - * @param {AddonModScormAttemptCountResult} attempts Attempts count. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise<{number: number, offline: boolean}>} Promise resolved with the attempt data. + * @param scorm SCORM object. + * @param attempts Attempts count. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the attempt data. */ determineAttemptToContinue(scorm: any, attempts: AddonModScormAttemptCountResult, siteId?: string) : Promise<{number: number, offline: boolean}> { @@ -195,14 +195,14 @@ export class AddonModScormHelperProvider { /** * Get the first SCO to load in a SCORM: the first valid and incomplete SCO. * - * @param {number} scormId Scorm ID. - * @param {number} attempt Attempt number. - * @param {any[]} [toc] SCORM's TOC. If not provided, it will be calculated. - * @param {string} [organization] Organization to use. - * @param {string} [mode] Mode. - * @param {boolean} [offline] Whether the attempt is offline. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the first SCO. + * @param scormId Scorm ID. + * @param attempt Attempt number. + * @param toc SCORM's TOC. If not provided, it will be calculated. + * @param organization Organization to use. + * @param mode Mode. + * @param offline Whether the attempt is offline. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the first SCO. */ getFirstSco(scormId: number, attempt: number, toc?: any[], organization?: string, mode?: string, offline?: boolean, siteId?: string): Promise { @@ -239,9 +239,9 @@ export class AddonModScormHelperProvider { * Get the last attempt (number and whether it's offline). * It'll be the highest number as long as it doesn't surpass the max number of attempts. * - * @param {any} scorm SCORM object. - * @param {AddonModScormAttemptCountResult} attempts Attempts count. - * @return {{number: number, offline: boolean}} Last attempt data. + * @param scorm SCORM object. + * @param attempts Attempts count. + * @return Last attempt data. */ protected getLastBeforeMax(scorm: any, attempts: AddonModScormAttemptCountResult): {number: number, offline: boolean} { if (scorm.maxattempt != 0 && attempts.lastAttempt.number > scorm.maxattempt) { @@ -260,9 +260,9 @@ export class AddonModScormHelperProvider { /** * Given a TOC in array format and a scoId, return the next available SCO. * - * @param {any[]} toc SCORM's TOC. - * @param {number} scoId SCO ID. - * @return {any} Next SCO. + * @param toc SCORM's TOC. + * @param scoId SCO ID. + * @return Next SCO. */ getNextScoFromToc(toc: any, scoId: number): any { for (let i = 0; i < toc.length; i++) { @@ -281,9 +281,9 @@ export class AddonModScormHelperProvider { /** * Given a TOC in array format and a scoId, return the previous available SCO. * - * @param {any[]} toc SCORM's TOC. - * @param {number} scoId SCO ID. - * @return {any} Previous SCO. + * @param toc SCORM's TOC. + * @param scoId SCO ID. + * @return Previous SCO. */ getPreviousScoFromToc(toc: any, scoId: number): any { for (let i = 0; i < toc.length; i++) { @@ -302,9 +302,9 @@ export class AddonModScormHelperProvider { /** * Given a TOC in array format and a scoId, return the SCO. * - * @param {any[]} toc SCORM's TOC. - * @param {number} scoId SCO ID. - * @return {any} SCO. + * @param toc SCORM's TOC. + * @param scoId SCO ID. + * @return SCO. */ getScoFromToc(toc: any[], scoId: number): any { for (let i = 0; i < toc.length; i++) { @@ -317,10 +317,10 @@ export class AddonModScormHelperProvider { /** * Searches user data for an online attempt. If the data can't be retrieved, re-try with the previous online attempt. * - * @param {number} scormId SCORM ID. - * @param {number} attempt Online attempt to get the data. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with user data. + * @param scormId SCORM ID. + * @param attempt Online attempt to get the data. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with user data. */ searchOnlineAttemptUserData(scormId: number, attempt: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); diff --git a/src/addon/mod/scorm/providers/module-handler.ts b/src/addon/mod/scorm/providers/module-handler.ts index 1f1df4c8d..1fabceed2 100644 --- a/src/addon/mod/scorm/providers/module-handler.ts +++ b/src/addon/mod/scorm/providers/module-handler.ts @@ -44,7 +44,7 @@ export class AddonModScormModuleHandler 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; @@ -53,10 +53,10 @@ export class AddonModScormModuleHandler 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 { @@ -78,9 +78,9 @@ export class AddonModScormModuleHandler 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 AddonModScormIndexComponent; diff --git a/src/addon/mod/scorm/providers/pluginfile-handler.ts b/src/addon/mod/scorm/providers/pluginfile-handler.ts index 58e16d725..e9ed78e91 100644 --- a/src/addon/mod/scorm/providers/pluginfile-handler.ts +++ b/src/addon/mod/scorm/providers/pluginfile-handler.ts @@ -26,8 +26,8 @@ export class AddonModScormPluginFileHandler 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,8 +40,8 @@ export class AddonModScormPluginFileHandler 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 diff --git a/src/addon/mod/scorm/providers/prefetch-handler.ts b/src/addon/mod/scorm/providers/prefetch-handler.ts index 4b4dd8038..175d1f8fa 100644 --- a/src/addon/mod/scorm/providers/prefetch-handler.ts +++ b/src/addon/mod/scorm/providers/prefetch-handler.ts @@ -32,19 +32,16 @@ import { AddonModScormSyncProvider } from './scorm-sync'; export interface AddonModScormProgressEvent { /** * Whether the event is due to the download of a chunk of data. - * @type {boolean} */ downloading?: boolean; /** * Progress event sent by the download. - * @type {ProgressEvent} */ progress?: ProgressEvent; /** * A message related to the progress. This is usually used to notify that a certain step of the download has started. - * @type {string} */ message?: string; } @@ -72,11 +69,11 @@ export class AddonModScormPrefetchHandler extends CoreCourseActivityPrefetchHand /** * 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 {Function} [onProgress] Function to call on progress. - * @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 onProgress Function to call on progress. + * @return Promise resolved when all content is downloaded. */ download(module: any, courseId: number, dirPath?: string, onProgress?: (event: AddonModScormProgressEvent) => any) : Promise { @@ -89,13 +86,13 @@ export class AddonModScormPrefetchHandler extends CoreCourseActivityPrefetchHand /** * Download or prefetch a SCORM. * - * @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} prefetch True to prefetch, false to download right away. - * @param {Function} [onProgress] Function to call on progress. - * @return {Promise} Promise resolved with the "extra" data to store: the hash of the file. + * @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 prefetch True to prefetch, false to download right away. + * @param onProgress Function to call on progress. + * @return Promise resolved with the "extra" data to store: the hash of the file. */ protected downloadOrPrefetchScorm(module: any, courseId: number, single: boolean, siteId: string, prefetch: boolean, onProgress?: (event: AddonModScormProgressEvent) => any): Promise { @@ -136,11 +133,11 @@ export class AddonModScormPrefetchHandler extends CoreCourseActivityPrefetchHand /** * Downloads/Prefetches and unzips the SCORM package. * - * @param {any} scorm SCORM object. - * @param {boolean} [prefetch] True if prefetch, false otherwise. - * @param {Function} [onProgress] Function to call on progress. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the file is downloaded and unzipped. + * @param scorm SCORM object. + * @param prefetch True if prefetch, false otherwise. + * @param onProgress Function to call on progress. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the file is downloaded and unzipped. */ protected downloadOrPrefetchMainFile(scorm: any, prefetch?: boolean, onProgress?: (event: AddonModScormProgressEvent) => any, siteId?: string): Promise { @@ -187,11 +184,11 @@ export class AddonModScormPrefetchHandler extends CoreCourseActivityPrefetchHand /** * Downloads/Prefetches and unzips the SCORM package if it should be downloaded. * - * @param {any} scorm SCORM object. - * @param {boolean} [prefetch] True if prefetch, false otherwise. - * @param {Function} [onProgress] Function to call on progress. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the file is downloaded and unzipped. + * @param scorm SCORM object. + * @param prefetch True if prefetch, false otherwise. + * @param onProgress Function to call on progress. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the file is downloaded and unzipped. */ protected downloadOrPrefetchMainFileIfNeeded(scorm: any, prefetch?: boolean, onProgress?: (event: AddonModScormProgressEvent) => any, siteId?: string): Promise { @@ -216,8 +213,8 @@ export class AddonModScormPrefetchHandler extends CoreCourseActivityPrefetchHand /** * Function that converts a regular ProgressEvent into a AddonModScormProgressEvent. * - * @param {Function} [onProgress] Function to call on progress. - * @param {ProgressEvent} [progress] Event returned by the download function. + * @param onProgress Function to call on progress. + * @param progress Event returned by the download function. */ protected downloadProgress(downloading: boolean, onProgress?: (event: AddonModScormProgressEvent) => any, progress?: ProgressEvent): void { @@ -233,9 +230,9 @@ export class AddonModScormPrefetchHandler extends CoreCourseActivityPrefetchHand /** * Get WS data for SCORM. * - * @param {any} scorm SCORM object. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is prefetched. + * @param scorm SCORM object. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is prefetched. */ fetchWSData(scorm: any, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -276,11 +273,11 @@ export class AddonModScormPrefetchHandler extends CoreCourseActivityPrefetchHand /** * 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: any, single?: boolean): Promise<{ size: number, total: boolean }> { return this.scormProvider.getScorm(courseId, module.id, module.url).then((scorm) => { @@ -300,9 +297,9 @@ export class AddonModScormPrefetchHandler extends CoreCourseActivityPrefetchHand /** * 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 { return this.scormProvider.getScorm(courseId, module.id, module.url).then((scorm) => { @@ -316,10 +313,10 @@ export class AddonModScormPrefetchHandler extends CoreCourseActivityPrefetchHand /** * 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.scormProvider.getScorm(courseId, module.id, module.url).then((scorm) => { @@ -333,9 +330,9 @@ export class AddonModScormPrefetchHandler 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.scormProvider.invalidateContent(moduleId, courseId); @@ -344,9 +341,9 @@ export class AddonModScormPrefetchHandler extends CoreCourseActivityPrefetchHand /** * 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 SCORM is downloadable. @@ -356,9 +353,9 @@ export class AddonModScormPrefetchHandler extends CoreCourseActivityPrefetchHand /** * 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 { return this.scormProvider.getScorm(courseId, module.id, module.url).then((scorm) => { @@ -378,12 +375,12 @@ export class AddonModScormPrefetchHandler 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. - * @param {Function} [onProgress] Function to call on progress. - * @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 onProgress Function to call on progress. + * @return Promise resolved when done. */ prefetch(module: any, courseId?: number, single?: boolean, dirPath?: string, onProgress?: (event: AddonModScormProgressEvent) => any): Promise { @@ -396,9 +393,9 @@ export class AddonModScormPrefetchHandler extends CoreCourseActivityPrefetchHand /** * 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 { const siteId = this.sitesProvider.getCurrentSiteId(); @@ -433,10 +430,10 @@ export class AddonModScormPrefetchHandler 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 { if (!this.syncProvider) { diff --git a/src/addon/mod/scorm/providers/scorm-offline.ts b/src/addon/mod/scorm/providers/scorm-offline.ts index 74af07260..9c605f2dd 100644 --- a/src/addon/mod/scorm/providers/scorm-offline.ts +++ b/src/addon/mod/scorm/providers/scorm-offline.ts @@ -134,12 +134,12 @@ export class AddonModScormOfflineProvider { * This function is used to convert attempts into new attempts, so the stored snapshot will be removed and * entries will be marked as not synced. * - * @param {number} scormId SCORM ID. - * @param {number} attempt Number of the attempt to change. - * @param {number} newAttempt New attempt number. - * @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 attempt number changes. + * @param scormId SCORM ID. + * @param attempt Number of the attempt to change. + * @param newAttempt New attempt number. + * @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 attempt number changes. */ changeAttemptNumber(scormId: number, attempt: number, newAttempt: number, siteId?: string, userId?: number): Promise { @@ -186,13 +186,13 @@ export class AddonModScormOfflineProvider { /** * Creates a new offline attempt. It can be created from scratch or as a copy of another attempt. * - * @param {any} scorm SCORM. - * @param {number} attempt Number of the new attempt. - * @param {any} userData User data to store in the attempt. - * @param {any} [snapshot] Optional. Snapshot to store in the attempt. - * @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 new attempt is created. + * @param scorm SCORM. + * @param attempt Number of the new attempt. + * @param userData User data to store in the attempt. + * @param snapshot Optional. Snapshot to store in the attempt. + * @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 new attempt is created. */ createNewAttempt(scorm: any, attempt: number, userData: any, snapshot?: any, siteId?: string, userId?: number): Promise { @@ -248,11 +248,11 @@ export class AddonModScormOfflineProvider { /** * Delete all the stored data from an attempt. * - * @param {number} scormId SCORM ID. - * @param {number} attempt Attempt number. - * @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 all the data has been deleted. + * @param scormId SCORM ID. + * @param attempt Attempt number. + * @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 all the data has been deleted. */ deleteAttempt(scormId: number, attempt: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -279,8 +279,8 @@ export class AddonModScormOfflineProvider { * Helper function to return a formatted list of interactions for reports. * This function is based in Moodle's scorm_format_interactions. * - * @param {any} scoUserData Userdata from a certain SCO. - * @return {any} Formatted userdata. + * @param scoUserData Userdata from a certain SCO. + * @return Formatted userdata. */ protected formatInteractions(scoUserData: any): any { const formatted: any = {}; @@ -334,8 +334,8 @@ export class AddonModScormOfflineProvider { /** * Get all the offline attempts in a certain site. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the offline attempts are retrieved. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the offline attempts are retrieved. */ getAllAttempts(siteId?: string): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { @@ -352,11 +352,11 @@ export class AddonModScormOfflineProvider { /** * Get an offline attempt. * - * @param {number} scormId SCORM ID. - * @param {number} attempt Attempt number. - * @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 attempt. + * @param scormId SCORM ID. + * @param attempt Attempt number. + * @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 attempt. */ getAttempt(scormId: number, attempt: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -374,11 +374,11 @@ export class AddonModScormOfflineProvider { /** * Get the creation time of an attempt. * - * @param {number} scormId SCORM ID. - * @param {number} attempt Attempt number. - * @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 time the attempt was created. + * @param scormId SCORM ID. + * @param attempt Attempt number. + * @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 time the attempt was created. */ getAttemptCreationTime(scormId: number, attempt: number, siteId?: string, userId?: number): Promise { return this.getAttempt(scormId, attempt, siteId, userId).catch(() => { @@ -391,10 +391,10 @@ export class AddonModScormOfflineProvider { /** * Get the offline attempts done by a user in the given SCORM. * - * @param {number} scormId SCORM 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 offline attempts are retrieved. + * @param scormId SCORM 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 offline attempts are retrieved. */ getAttempts(scormId: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -413,11 +413,11 @@ export class AddonModScormOfflineProvider { /** * Get the snapshot of an attempt. * - * @param {number} scormId SCORM ID. - * @param {number} attempt Attempt number. - * @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 snapshot or undefined if no snapshot. + * @param scormId SCORM ID. + * @param attempt Attempt number. + * @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 snapshot or undefined if no snapshot. */ getAttemptSnapshot(scormId: number, attempt: number, siteId?: string, userId?: number): Promise { return this.getAttempt(scormId, attempt, siteId, userId).catch(() => { @@ -430,8 +430,8 @@ export class AddonModScormOfflineProvider { /** * Get launch URLs from a list of SCOs, indexing them by SCO ID. * - * @param {any[]} scos List of SCOs. Each SCO needs to have 'id' and 'launch' properties. - * @return {{[scoId: number]: string}} Launch URLs indexed by SCO ID. + * @param scos List of SCOs. Each SCO needs to have 'id' and 'launch' properties. + * @return Launch URLs indexed by SCO ID. */ protected getLaunchUrlsFromScos(scos: any[]): {[scoId: number]: string} { scos = scos || []; @@ -448,13 +448,13 @@ export class AddonModScormOfflineProvider { /** * Get data stored in local DB for a certain scorm and attempt. * - * @param {number} scormId SCORM ID. - * @param {number} attempt Attempt number. - * @param {boolean} [excludeSynced] Whether it should only return not synced entries. - * @param {boolean} [excludeNotSynced] Whether it should only return synced entries. - * @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 entries. + * @param scormId SCORM ID. + * @param attempt Attempt number. + * @param excludeSynced Whether it should only return not synced entries. + * @param excludeNotSynced Whether it should only return synced entries. + * @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 entries. */ getScormStoredData(scormId: number, attempt: number, excludeSynced?: boolean, excludeNotSynced?: boolean, siteId?: string, userId?: number): Promise { @@ -491,13 +491,13 @@ export class AddonModScormOfflineProvider { /** * Get the user data for a certain SCORM and offline attempt. * - * @param {number} scormId SCORM ID. - * @param {number} attempt Attempt number. - * @param {any[]} scos SCOs returned by AddonModScormProvider.getScos. If not supplied, this function will only return the - * SCOs that have something stored and cmi.launch_data will be undefined. - * @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 user data is retrieved. + * @param scormId SCORM ID. + * @param attempt Attempt number. + * @param scos SCOs returned by AddonModScormProvider.getScos. If not supplied, this function will only return the + * SCOs that have something stored and cmi.launch_data will be undefined. + * @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 user data is retrieved. */ getScormUserData(scormId: number, attempt: number, scos: any[], siteId?: string, userId?: number): Promise { scos = scos || []; @@ -627,16 +627,16 @@ export class AddonModScormOfflineProvider { * Insert a track in the offline tracks store. * This function is based on Moodle's scorm_insert_track. * - * @param {number} scormId SCORM ID. - * @param {number} scoId SCO ID. - * @param {number} attempt Attempt number. - * @param {string} element Name of the element to insert. - * @param {any} value Value to insert. - * @param {boolean} [forceCompleted] True if SCORM forces completed. - * @param {any} [scoData] User data for the given SCO. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User ID. If not set use site's current user. - * @return {Promise} Promise resolved when the insert is done. + * @param scormId SCORM ID. + * @param scoId SCO ID. + * @param attempt Attempt number. + * @param element Name of the element to insert. + * @param value Value to insert. + * @param forceCompleted True if SCORM forces completed. + * @param scoData User data for the given SCO. + * @param siteId Site ID. If not defined, current site. + * @param userId User ID. If not set use site's current user. + * @return Promise resolved when the insert is done. */ protected insertTrack(scormId: number, scoId: number, attempt: number, element: string, value: any, forceCompleted?: boolean, scoData?: any, siteId?: string, userId?: number): Promise { @@ -692,15 +692,15 @@ export class AddonModScormOfflineProvider { /** * Insert a track in the DB. * - * @param {SQLiteDB} db Site's DB. - * @param {number} userId User ID. - * @param {number} scormId SCORM ID. - * @param {number} scoId SCO ID. - * @param {number} attempt Attempt number. - * @param {string} element Name of the element to insert. - * @param {any} value Value of the element to insert. - * @param {boolean} synchronous True if insert should NOT return a promise. Please use it only if synchronous is a must. - * @return {boolean|Promise} Returns a promise if synchronous=false, otherwise returns a boolean. + * @param db Site's DB. + * @param userId User ID. + * @param scormId SCORM ID. + * @param scoId SCO ID. + * @param attempt Attempt number. + * @param element Name of the element to insert. + * @param value Value of the element to insert. + * @param synchronous True if insert should NOT return a promise. Please use it only if synchronous is a must. + * @return Returns a promise if synchronous=false, otherwise returns a boolean. */ protected insertTrackToDB(db: SQLiteDB, userId: number, scormId: number, scoId: number, attempt: number, element: string, value: any, synchronous?: boolean): boolean | Promise { @@ -731,15 +731,15 @@ export class AddonModScormOfflineProvider { * Please use this function only if synchronous is a must. It's recommended to use insertTrack. * This function is based on Moodle's scorm_insert_track. * - * @param {number} scormId SCORM ID. - * @param {number} scoId SCO ID. - * @param {number} attempt Attempt number. - * @param {string} element Name of the element to insert. - * @param {any} value Value of the element to insert. - * @param {boolean} [forceCompleted] True if SCORM forces completed. - * @param {any} [scoData] User data for the given SCO. - * @param {number} [userId] User ID. If not set use current user. - * @return {boolean} Promise resolved when the insert is done. + * @param scormId SCORM ID. + * @param scoId SCO ID. + * @param attempt Attempt number. + * @param element Name of the element to insert. + * @param value Value of the element to insert. + * @param forceCompleted True if SCORM forces completed. + * @param scoData User data for the given SCO. + * @param userId User ID. If not set use current user. + * @return Promise resolved when the insert is done. */ protected insertTrackSync(scormId: number, scoId: number, attempt: number, element: string, value: any, forceCompleted?: boolean, scoData?: any, userId?: number): boolean { @@ -791,12 +791,12 @@ export class AddonModScormOfflineProvider { /** * Mark all the entries from a SCO and attempt as synced. * - * @param {number} scormId SCORM ID. - * @param {number} attempt Attempt number. - * @param {number} scoId SCO 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 marked. + * @param scormId SCORM ID. + * @param attempt Attempt number. + * @param scoId SCO 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 marked. */ markAsSynced(scormId: number, attempt: number, scoId: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -817,8 +817,8 @@ export class AddonModScormOfflineProvider { /** * Removes the default data form user data. * - * @param {any} userData User data returned by AddonModScormProvider.getScormUserData. - * @return {any} User data without default data. + * @param userData User data returned by AddonModScormProvider.getScormUserData. + * @return User data without default data. */ protected removeDefaultData(userData: any): any { const result = this.utils.clone(userData); @@ -833,14 +833,14 @@ export class AddonModScormOfflineProvider { /** * Saves a SCORM tracking record in offline. * - * @param {any} scorm SCORM. - * @param {number} scoId Sco ID. - * @param {number} attempt Attempt number. - * @param {any[]} tracks Tracking data to store. - * @param {any} userData User data for this attempt and SCO. - * @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 data is saved. + * @param scorm SCORM. + * @param scoId Sco ID. + * @param attempt Attempt number. + * @param tracks Tracking data to store. + * @param userData User data for this attempt and SCO. + * @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 data is saved. */ saveTracks(scorm: any, scoId: number, attempt: number, tracks: any[], userData: any, siteId?: string, userId?: number) : Promise { @@ -869,13 +869,13 @@ export class AddonModScormOfflineProvider { * Saves a SCORM tracking record in offline returning a synchronous value. * Please use this function only if synchronous is a must. It's recommended to use saveTracks. * - * @param {any} scorm SCORM. - * @param {number} scoId Sco ID. - * @param {number} attempt Attempt number. - * @param {Object[]} tracks Tracking data to store. - * @param {any} userData User data for this attempt and SCO. - * @return {boolean} True if data to insert is valid, false otherwise. Returning true doesn't mean that the data - * has been stored, this function can return true but the insertion can still fail somehow. + * @param scorm SCORM. + * @param scoId Sco ID. + * @param attempt Attempt number. + * @param tracks Tracking data to store. + * @param userData User data for this attempt and SCO. + * @return True if data to insert is valid, false otherwise. Returning true doesn't mean that the data + * has been stored, this function can return true but the insertion can still fail somehow. */ saveTracksSync(scorm: any, scoId: number, attempt: number, tracks: any[], userData: any, userId?: number): boolean { userId = userId || this.sitesProvider.getCurrentSiteUserId(); @@ -895,10 +895,10 @@ export class AddonModScormOfflineProvider { * Check for a parameter in userData and return it if it's set or return 'ifempty' if it's empty. * Based on Moodle's scorm_isset function. * - * @param {any} userData Contains user's data. - * @param {string} param Name of parameter that should be checked. - * @param {any} [ifEmpty] Value to be replaced with if param is not set. - * @return {any} Value from userData[param] if set, ifEmpty otherwise. + * @param userData Contains user's data. + * @param param Name of parameter that should be checked. + * @param ifEmpty Value to be replaced with if param is not set. + * @return Value from userData[param] if set, ifEmpty otherwise. */ protected scormIsset(userData: any, param: string, ifEmpty: any = ''): any { if (typeof userData[param] != 'undefined') { @@ -911,12 +911,12 @@ export class AddonModScormOfflineProvider { /** * Set an attempt's snapshot. * - * @param {number} scormId SCORM ID. - * @param {number} attempt Attempt number. - * @param {any} userData User data to store as snapshot. - * @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 snapshot has been stored. + * @param scormId SCORM ID. + * @param attempt Attempt number. + * @param userData User data to store as snapshot. + * @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 snapshot has been stored. */ setAttemptSnapshot(scormId: number, attempt: number, userData: any, siteId?: string, userId?: number): Promise { diff --git a/src/addon/mod/scorm/providers/scorm-sync.ts b/src/addon/mod/scorm/providers/scorm-sync.ts index bb6074179..9a5cc7259 100644 --- a/src/addon/mod/scorm/providers/scorm-sync.ts +++ b/src/addon/mod/scorm/providers/scorm-sync.ts @@ -36,19 +36,16 @@ import { AddonModScormPrefetchHandler } from './prefetch-handler'; export interface AddonModScormSyncResult { /** * List of warnings. - * @type {string[]} */ warnings: string[]; /** * Whether an attempt was finished in the site due to the sync, - * @type {boolean} */ attemptFinished: boolean; /** * Whether some data was sent to the site. - * @type {boolean} */ updated: boolean; } @@ -81,16 +78,16 @@ export class AddonModScormSyncProvider extends CoreCourseActivitySyncBaseProvide * Add an offline attempt to the right of the new attempts array if possible. * If the attempt cannot be created as a new attempt then it will be deleted. * - * @param {number} scormId SCORM ID. - * @param {number} attempt The offline attempt to treat. - * @param {number} lastOffline Last offline attempt number. - * @param {number[]} newAttemptsSameOrder Attempts that'll be created as new attempts but keeping the current order. - * @param {any} newAttemptsAtEnd Object with attempts that'll be created at the end of the list of attempts (should be max 1). - * @param {number} lastOfflineCreated Time when the last offline attempt was created. - * @param {boolean} lastOfflineIncomplete Whether the last offline attempt is incomplete. - * @param {string[]} warnings Array where to add the warnings. - * @param {string} siteId Site ID. - * @return {Promise} Promise resolved when done. + * @param scormId SCORM ID. + * @param attempt The offline attempt to treat. + * @param lastOffline Last offline attempt number. + * @param newAttemptsSameOrder Attempts that'll be created as new attempts but keeping the current order. + * @param newAttemptsAtEnd Object with attempts that'll be created at the end of the list of attempts (should be max 1). + * @param lastOfflineCreated Time when the last offline attempt was created. + * @param lastOfflineIncomplete Whether the last offline attempt is incomplete. + * @param warnings Array where to add the warnings. + * @param siteId Site ID. + * @return Promise resolved when done. */ protected addToNewOrDelete(scormId: number, attempt: number, lastOffline: number, newAttemptsSameOrder: number[], newAttemptsAtEnd: any, lastOfflineCreated: number, lastOfflineIncomplete: boolean, warnings: string[], @@ -129,11 +126,11 @@ export class AddonModScormSyncProvider extends CoreCourseActivitySyncBaseProvide /** * Check if can retry an attempt synchronization. * - * @param {number} scormId SCORM ID. - * @param {number} attempt Attempt number. - * @param {number} lastOnline Last online attempt number. - * @param {string} siteId Site ID. - * @return {Promise} Promise resolved if can retry the synchronization, rejected otherwise. + * @param scormId SCORM ID. + * @param attempt Attempt number. + * @param lastOnline Last online attempt number. + * @param siteId Site ID. + * @return Promise resolved if can retry the synchronization, rejected otherwise. */ protected canRetrySync(scormId: number, attempt: number, lastOnline: number, siteId: string): Promise { // If it's the last attempt we don't need to ignore cache because we already did it. @@ -153,11 +150,11 @@ export class AddonModScormSyncProvider extends CoreCourseActivitySyncBaseProvide /** * Create new attempts at the end of the offline attempts list. * - * @param {number} scormId SCORM ID. - * @param {any} newAttempts Object with the attempts to create. The keys are the timecreated, the values are the attempt number. - * @param {number} lastOffline Number of last offline attempt. - * @param {string} siteId Site ID. - * @return {Promise} Promise resolved when done. + * @param scormId SCORM ID. + * @param newAttempts Object with the attempts to create. The keys are the timecreated, the values are the attempt number. + * @param lastOffline Number of last offline attempt. + * @param siteId Site ID. + * @return Promise resolved when done. */ protected createNewAttemptsAtEnd(scormId: number, newAttempts: any, lastOffline: number, siteId: string): Promise { const times = Object.keys(newAttempts).sort(), // Sort in ASC order. @@ -179,14 +176,14 @@ export class AddonModScormSyncProvider extends CoreCourseActivitySyncBaseProvide /** * Finish a sync process: remove offline data if needed, prefetch SCORM data, set sync time and return the result. * - * @param {string} siteId Site ID. - * @param {any} scorm SCORM. - * @param {string[]} warnings List of warnings generated by the sync. - * @param {number} [lastOnline] Last online attempt number before the sync. - * @param {boolean} [lastOnlineWasFinished] Whether the last online attempt was finished before the sync. - * @param {AddonModScormAttemptCountResult} [initialCount] Attempt count before the sync. - * @param {boolean} [updated] Whether some data was sent to the site. - * @return {Promise} Promise resolved on success. + * @param siteId Site ID. + * @param scorm SCORM. + * @param warnings List of warnings generated by the sync. + * @param lastOnline Last online attempt number before the sync. + * @param lastOnlineWasFinished Whether the last online attempt was finished before the sync. + * @param initialCount Attempt count before the sync. + * @param updated Whether some data was sent to the site. + * @return Promise resolved on success. */ protected finishSync(siteId: string, scorm: any, warnings: string[], lastOnline?: number, lastOnlineWasFinished?: boolean, initialCount?: AddonModScormAttemptCountResult, updated?: boolean): Promise { @@ -239,10 +236,10 @@ export class AddonModScormSyncProvider extends CoreCourseActivitySyncBaseProvide /** * Get the creation time and the status (complete/incomplete) of an offline attempt. * - * @param {number} scormId SCORM ID. - * @param {number} attempt Attempt number. - * @param {string} siteId Site ID. - * @return {Promise<{incomplete: boolean, timecreated: number}>} Promise resolved with the data. + * @param scormId SCORM ID. + * @param attempt Attempt number. + * @param siteId Site ID. + * @return Promise resolved with the data. */ protected getOfflineAttemptData(scormId: number, attempt: number, siteId: string) : Promise<{incomplete: boolean, timecreated: number}> { @@ -264,13 +261,13 @@ export class AddonModScormSyncProvider extends CoreCourseActivitySyncBaseProvide * Example: We have offline attempts 1, 2 and 3. #1 and #2 have collisions. #1 can be synced, but #2 needs * to be a new attempt. #3 will now be #4, and #2 will now be #3. * - * @param {number} scormId SCORM ID. - * @param {number[]} newAttempts Attempts that need to be converted into new attempts. - * @param {number} lastOnline Last online attempt. - * @param {number} lastCollision Last attempt with collision (exists in online and offline). - * @param {number[]} offlineAttempts Numbers of offline attempts. - * @param {string} siteId Site ID. - * @return {Promise} Promise resolved when attempts have been moved. + * @param scormId SCORM ID. + * @param newAttempts Attempts that need to be converted into new attempts. + * @param lastOnline Last online attempt. + * @param lastCollision Last attempt with collision (exists in online and offline). + * @param offlineAttempts Numbers of offline attempts. + * @param siteId Site ID. + * @return Promise resolved when attempts have been moved. */ protected moveNewAttempts(scormId: any, newAttempts: number[], lastOnline: number, lastCollision: number, offlineAttempts: number[], siteId: string): Promise { @@ -364,10 +361,10 @@ export class AddonModScormSyncProvider extends CoreCourseActivitySyncBaseProvide /** * Save a snapshot from a synchronization. * - * @param {number} scormId SCORM ID. - * @param {number} attempt Attemot number. - * @param {string} siteId Site ID. - * @return {Promise} Promise resolved when the snapshot is stored. + * @param scormId SCORM ID. + * @param attempt Attemot number. + * @param siteId Site ID. + * @return Promise resolved when the snapshot is stored. */ protected saveSyncSnapshot(scormId: number, attempt: number, siteId: string): Promise { // Try to get current state from the site. @@ -404,9 +401,9 @@ export class AddonModScormSyncProvider extends CoreCourseActivitySyncBaseProvide * It only compares elements with dot notation. This means that, if some SCO has been added to Moodle web * but the user hasn't generated data for it, then the snapshot will be detected as equal. * - * @param {any} snapshot Attempt's snapshot. - * @param {any} userData Data retrieved from the site. - * @return {boolean} True if snapshot is equal to the user data, false otherwise. + * @param snapshot Attempt's snapshot. + * @param userData Data retrieved from the site. + * @return True if snapshot is equal to the user data, false otherwise. */ protected snapshotEquals(snapshot: any, userData: any): boolean { // Check that snapshot contains the data from the site. @@ -443,9 +440,9 @@ export class AddonModScormSyncProvider extends CoreCourseActivitySyncBaseProvide /** * Try to synchronize all the SCORMs 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. */ syncAllScorms(siteId?: string, force?: boolean): Promise { return this.syncOnSites('all SCORMs', this.syncAllScormsFunc.bind(this), [force], siteId); @@ -454,9 +451,9 @@ export class AddonModScormSyncProvider extends CoreCourseActivitySyncBaseProvide /** * Sync all SCORMs 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 syncAllScormsFunc(siteId: string, force?: boolean): Promise { @@ -507,10 +504,10 @@ export class AddonModScormSyncProvider extends CoreCourseActivitySyncBaseProvide /** * Send data from a SCORM offline attempt to the site. * - * @param {number} scormId SCORM ID. - * @param {number} attempt Attempt number. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the attempt is successfully synced. + * @param scormId SCORM ID. + * @param attempt Attempt number. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the attempt is successfully synced. */ protected syncAttempt(scormId: number, attempt: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -583,9 +580,9 @@ export class AddonModScormSyncProvider extends CoreCourseActivitySyncBaseProvide /** * Sync a SCORM only if a certain time has passed since the last time. * - * @param {any} scorm SCORM. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the SCORM is synced or if it doesn't need to be synced. + * @param scorm SCORM. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the SCORM is synced or if it doesn't need to be synced. */ syncScormIfNeeded(scorm: any, siteId?: string): Promise { return this.isSyncNeeded(scorm.id, siteId).then((needed) => { @@ -600,9 +597,9 @@ export class AddonModScormSyncProvider extends CoreCourseActivitySyncBaseProvide * The promise returned will be resolved with an array with warnings if the synchronization is successful. A successful * synchronization doesn't mean that all the data has been sent to the site, it's possible that some attempt can't be sent. * - * @param {any} scorm SCORM. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved in success. + * @param scorm SCORM. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved in success. */ syncScorm(scorm: any, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -729,12 +726,12 @@ export class AddonModScormSyncProvider extends CoreCourseActivitySyncBaseProvide /** * Treat collisions found in a SCORM synchronization process. * - * @param {number} scormId SCORM ID. - * @param {number[]} collisions Numbers of attempts that exist both in online and offline. - * @param {number} lastOnline Last online attempt. - * @param {number[]} offlineAttempts Numbers of offline attempts. - * @param {string} siteId Site ID. - * @return {Promise} Promise resolved with the SCORM size. + * @param scorm SCORM. + * @return Promise resolved with the SCORM size. */ calculateScormSize(scorm: any): Promise { if (scorm.packagesize) { @@ -195,9 +191,9 @@ export class AddonModScormProvider { /** * Count the attempts left for the given scorm. * - * @param {any} scorm SCORM. - * @param {number} attemptsCount Number of attempts performed. - * @return {number} Number of attempts left. + * @param scorm SCORM. + * @param attemptsCount Number of attempts performed. + * @return Number of attempts left. */ countAttemptsLeft(scorm: any, attemptsCount: number): number { if (scorm.maxattempt == 0) { @@ -216,12 +212,12 @@ export class AddonModScormProvider { * Returns the mode and attempt number to use based on mode selected and SCORM data. * This function is based on Moodle's scorm_check_mode. * - * @param {any} scorm SCORM. - * @param {string} mode Selected mode. - * @param {number} attempt Current attempt. - * @param {boolean} [newAttempt] Whether it should start a new attempt. - * @param {boolean} [incomplete] Whether current attempt is incomplete. - * @return {{mode: string, attempt: number, newAttempt: boolean}} Mode, attempt number and whether to start a new attempt. + * @param scorm SCORM. + * @param mode Selected mode. + * @param attempt Current attempt. + * @param newAttempt Whether it should start a new attempt. + * @param incomplete Whether current attempt is incomplete. + * @return Mode, attempt number and whether to start a new attempt. */ determineAttemptAndMode(scorm: any, mode: string, attempt: number, newAttempt?: boolean, incomplete?: boolean) : {mode: string, attempt: number, newAttempt: boolean} { @@ -289,8 +285,8 @@ export class AddonModScormProvider { /** * Check if TOC should be displayed in the player. * - * @param {any} scorm SCORM. - * @return {boolean} Whether it should display TOC. + * @param scorm SCORM. + * @return Whether it should display TOC. */ displayTocInPlayer(scorm: any): boolean { return scorm.hidetoc !== 3; @@ -301,9 +297,9 @@ export class AddonModScormProvider { * Evaluates the expression and returns a boolean answer. * See 2.3.2.5.1. Sequencing/Navigation Today - from the SCORM 1.2 spec (CAM). * - * @param {string} prerequisites The AICC_SCRIPT prerequisites expression. - * @param {any} trackData The tracked user data of each SCO. - * @return {boolean} Whether the prerequisites are fulfilled. + * @param prerequisites The AICC_SCRIPT prerequisites expression. + * @param trackData The tracked user data of each SCO. + * @return Whether the prerequisites are fulfilled. */ evalPrerequisites(prerequisites: string, trackData: any): boolean { @@ -404,9 +400,9 @@ export class AddonModScormProvider { /** * Formats a grade to be displayed. * - * @param {any} scorm SCORM. - * @param {number} grade Grade. - * @return {string} Grade to display. + * @param scorm SCORM. + * @param grade Grade. + * @return Grade to display. */ formatGrade(scorm: any, grade: number): string { if (typeof grade == 'undefined' || grade == -1) { @@ -425,9 +421,9 @@ export class AddonModScormProvider { /** * Formats a tree-like TOC into an array. * - * @param {any[]} toc SCORM's TOC (tree format). - * @param {number} [level=0] The level of the TOC we're right now. 0 by default. - * @return {any[]} SCORM's TOC (array format). + * @param toc SCORM's TOC (tree format). + * @param level The level of the TOC we're right now. 0 by default. + * @return SCORM's TOC (array format). */ formatTocToArray(toc: any[], level: number = 0): any[] { if (!toc || !toc.length) { @@ -449,10 +445,10 @@ export class AddonModScormProvider { /** * Get access information for a given SCORM. * - * @param {number} scormId SCORM 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 scormId SCORM 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(scormId: number, forceCache?: boolean, siteId?: string): Promise { @@ -477,8 +473,8 @@ export class AddonModScormProvider { /** * Get cache key for access information WS calls. * - * @param {number} scormId SCORM ID. - * @return {string} Cache key. + * @param scormId SCORM ID. + * @return Cache key. */ protected getAccessInformationCacheKey(scormId: number): string { return this.ROOT_CACHE_KEY + 'accessInfo:' + scormId; @@ -487,12 +483,12 @@ export class AddonModScormProvider { /** * Get the number of attempts done by a user in the given SCORM. * - * @param {number} scormId SCORM ID. - * @param {boolean} [ignoreMissing] Whether it should ignore attempts without grade/completion. Only for online attempts. - * @param {boolean} [ignoreCache] Whether it should ignore cached data for online attempts. - * @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 done. + * @param scormId SCORM ID. + * @param ignoreMissing Whether it should ignore attempts without grade/completion. Only for online attempts. + * @param ignoreCache Whether it should ignore cached data for online attempts. + * @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 done. */ getAttemptCount(scormId: number, ignoreMissing?: boolean, ignoreCache?: boolean, siteId?: string, userId?: number) : Promise { @@ -559,9 +555,9 @@ export class AddonModScormProvider { /** * Get cache key for SCORM attempt count WS calls. * - * @param {number} scormId SCORM ID. - * @param {number} [userId] User ID. If not defined, current user. - * @return {string} Cache key. + * @param scormId SCORM ID. + * @param userId User ID. If not defined, current user. + * @return Cache key. */ protected getAttemptCountCacheKey(scormId: number, userId: number): string { return this.ROOT_CACHE_KEY + 'attemptcount:' + scormId + ':' + userId; @@ -570,12 +566,12 @@ export class AddonModScormProvider { /** * Get the number of attempts done by a user in the given SCORM in online. * - * @param {number} scormId SCORM ID. - * @param {boolean} [ignoreMissing] Whether it should ignore attempts that haven't reported a grade/completion. - * @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 when the attempt count is retrieved. + * @param scormId SCORM ID. + * @param ignoreMissing Whether it should ignore attempts that haven't reported a grade/completion. + * @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 when the attempt count is retrieved. */ getAttemptCountOnline(scormId: number, ignoreMissing?: boolean, ignoreCache?: boolean, siteId?: string, userId?: number) : Promise { @@ -612,11 +608,11 @@ export class AddonModScormProvider { * Get the grade for a certain SCORM and attempt. * Based on Moodle's scorm_grade_user_attempt. * - * @param {any} scorm SCORM. - * @param {number} attempt Attempt number. - * @param {boolean} [offline] Whether the attempt is offline. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the grade. If the attempt hasn't reported grade/completion, it will be -1. + * @param scorm SCORM. + * @param attempt Attempt number. + * @param offline Whether the attempt is offline. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the grade. If the attempt hasn't reported grade/completion, it will be -1. */ getAttemptGrade(scorm: any, attempt: number, offline?: boolean, siteId?: string): Promise { const attemptScore = { @@ -680,9 +676,9 @@ export class AddonModScormProvider { /** * Get the list of a organizations defined in a SCORM package. * - * @param {number} scormId SCORM ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the list of organizations. + * @param scormId SCORM ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the list of organizations. */ getOrganizations(scormId: number, siteId?: string): Promise { return this.getScos(scormId, undefined, false, siteId).then((scos) => { @@ -706,12 +702,12 @@ export class AddonModScormProvider { /** * Get the organization Toc any * - * @param {number} scormId SCORM ID. - * @param {number} attempt The attempt number (to populate SCO track data). - * @param {string} [organization] Organization identifier. - * @param {boolean} [offline] Whether the attempt is offline. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the toc object. + * @param scormId SCORM ID. + * @param attempt The attempt number (to populate SCO track data). + * @param organization Organization identifier. + * @param offline Whether the attempt is offline. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the toc object. */ getOrganizationToc(scormId: number, attempt: number, organization?: string, offline?: boolean, siteId?: string) : Promise { @@ -742,8 +738,8 @@ export class AddonModScormProvider { /** * Get the package URL of a given SCORM. * - * @param {any} scorm SCORM. - * @return {string} Package URL. + * @param scorm SCORM. + * @return Package URL. */ getPackageUrl(scorm: any): string { if (scorm.packageurl) { @@ -759,13 +755,13 @@ export class AddonModScormProvider { /** * Get the user data for a certain SCORM and attempt. * - * @param {number} scormId SCORM ID. - * @param {number} attempt Attempt number. - * @param {any[]} [scos] SCOs returned by getScos. Recommended if offline=true. - * @param {boolean} [offline] Whether the attempt is offline. - * @param {boolean} [ignoreCache] Whether it should ignore cached data for online attempts. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the user data is retrieved. + * @param scormId SCORM ID. + * @param attempt Attempt number. + * @param scos SCOs returned by getScos. Recommended if offline=true. + * @param offline Whether the attempt is offline. + * @param ignoreCache Whether it should ignore cached data for online attempts. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the user data is retrieved. */ getScormUserData(scormId: number, attempt: number, scos?: any[], offline?: boolean, ignoreCache?: boolean, siteId?: string) : Promise { @@ -787,9 +783,9 @@ export class AddonModScormProvider { /** * Get cache key for SCORM user data WS calls. * - * @param {number} scormId SCORM ID. - * @param {number} attempt Attempt number. - * @return {string} Cache key. + * @param scormId SCORM ID. + * @param attempt Attempt number. + * @return Cache key. */ protected getScormUserDataCacheKey(scormId: number, attempt: number): string { return this.getScormUserDataCommonCacheKey(scormId) + ':' + attempt; @@ -798,8 +794,8 @@ export class AddonModScormProvider { /** * Get common cache key for SCORM user data WS calls. * - * @param {number} scormId SCORM ID. - * @return {string} Cache key. + * @param scormId SCORM ID. + * @return Cache key. */ protected getScormUserDataCommonCacheKey(scormId: number): string { return this.ROOT_CACHE_KEY + 'userdata:' + scormId; @@ -808,11 +804,11 @@ export class AddonModScormProvider { /** * Get the user data for a certain SCORM and attempt in online. * - * @param {number} scormId SCORM ID. - * @param {number} attempt Attempt number. - * @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 user data is retrieved. + * @param scormId SCORM ID. + * @param attempt Attempt number. + * @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 user data is retrieved. */ getScormUserDataOnline(scormId: number, attempt: number, ignoreCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -852,8 +848,8 @@ export class AddonModScormProvider { /** * Get cache key for get SCORM scos WS calls. * - * @param {number} scormId SCORM ID. - * @return {string} Cache key. + * @param scormId SCORM ID. + * @return Cache key. */ protected getScosCacheKey(scormId: number): string { return this.ROOT_CACHE_KEY + 'scos:' + scormId; @@ -862,11 +858,11 @@ export class AddonModScormProvider { /** * Retrieves the list of SCO objects for a given SCORM and organization. * - * @param {number} scormId SCORM ID. - * @param {string} [organization] Organization. - * @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail if offline or server down). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -909,13 +905,13 @@ export class AddonModScormProvider { * Retrieves the list of SCO objects for a given SCORM and organization, including data about * a certain attempt (status, isvisible, ...). * - * @param {number} scormId SCORM ID. - * @param {number} attempt Attempt number. - * @param {string} [organization] Organization ID. - * @param {boolean} [offline] Whether the attempt is offline. - * @param {boolean} [ignoreCache] Whether it should ignore cached data for online attempts. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with a list of SCO objects. + * @param scormId SCORM ID. + * @param attempt Attempt number. + * @param organization Organization ID. + * @param offline Whether the attempt is offline. + * @param ignoreCache Whether it should ignore cached data for online attempts. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with a list of SCO objects. */ getScosWithData(scormId: number, attempt: number, organization?: string, offline?: boolean, ignoreCache?: boolean, siteId?: string): Promise { @@ -963,10 +959,10 @@ export class AddonModScormProvider { /** * Given a SCORM and a SCO, returns the full launch URL for the SCO. * - * @param {any} scorm SCORM. - * @param {any} sco SCO. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the URL. + * @param scorm SCORM. + * @param sco SCO. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the URL. */ getScoSrc(scorm: any, sco: any, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -1008,9 +1004,9 @@ export class AddonModScormProvider { /** * Get the path to the folder where a SCORM is downloaded. * - * @param {string} moduleUrl Module URL (returned by get_course_contents). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the folder path. + * @param moduleUrl Module URL (returned by get_course_contents). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the folder path. */ getScormFolder(moduleUrl: string, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -1022,8 +1018,8 @@ export class AddonModScormProvider { * Gets a list of files to downlaod for a SCORM, using a format similar to module.contents from get_course_contents. * It will only return one file: the ZIP package. * - * @param {any} scorm SCORM. - * @return {any[]} File list. + * @param scorm SCORM. + * @return File list. */ getScormFileList(scorm: any): any[] { const files = []; @@ -1045,9 +1041,9 @@ export class AddonModScormProvider { /** * Get the URL and description of the status icon. * - * @param {any} sco SCO. - * @param {boolean} [incomplete] Whether the SCORM is incomplete. - * @return {{url: string, description: string}} Image URL and description. + * @param sco SCO. + * @param incomplete Whether the SCORM is incomplete. + * @return Image URL and description. */ getScoStatusIcon(sco: any, incomplete?: boolean): {url: string, description: string} { let imageName = '', @@ -1088,8 +1084,8 @@ export class AddonModScormProvider { /** * Get cache key for SCORM data WS calls. * - * @param {number} courseId Course ID. - * @return {string} Cache key. + * @param courseId Course ID. + * @return Cache key. */ protected getScormDataCacheKey(courseId: number): string { return this.ROOT_CACHE_KEY + 'scorm:' + courseId; @@ -1098,13 +1094,13 @@ export class AddonModScormProvider { /** * Get a SCORM 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} [moduleUrl] Module URL. - * @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 SCORM is retrieved. + * @param courseId Course ID. + * @param key Name of the property to check. + * @param value Value to search. + * @param moduleUrl Module URL. + * @param forceCache Whether it should always return cached data. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the SCORM is retrieved. */ protected getScormByField(courseId: number, key: string, value: any, moduleUrl?: string, forceCache?: boolean, siteId?: string) : Promise { @@ -1154,12 +1150,12 @@ export class AddonModScormProvider { /** * Get a SCORM by module ID. * - * @param {number} courseId Course ID. - * @param {number} cmId Course module ID. - * @param {string} [moduleUrl] Module URL. - * @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 SCORM is retrieved. + * @param courseId Course ID. + * @param cmId Course module ID. + * @param moduleUrl Module URL. + * @param forceCache Whether it should always return cached data. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the SCORM is retrieved. */ getScorm(courseId: number, cmId: number, moduleUrl?: string, forceCache?: boolean, siteId?: string): Promise { return this.getScormByField(courseId, 'coursemodule', cmId, moduleUrl, forceCache, siteId); @@ -1168,12 +1164,12 @@ export class AddonModScormProvider { /** * Get a SCORM by SCORM ID. * - * @param {number} courseId Course ID. - * @param {number} id SCORM ID. - * @param {string} [moduleUrl] Module URL. - * @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 SCORM is retrieved. + * @param courseId Course ID. + * @param id SCORM ID. + * @param moduleUrl Module URL. + * @param forceCache Whether it should always return cached data. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the SCORM is retrieved. */ getScormById(courseId: number, id: number, moduleUrl?: string, forceCache?: boolean, siteId?: string): Promise { return this.getScormByField(courseId, 'id', id, moduleUrl, forceCache, siteId); @@ -1182,8 +1178,8 @@ export class AddonModScormProvider { /** * Get a readable SCORM grade method. * - * @param {any} scorm SCORM. - * @return {string} Grading method. + * @param scorm SCORM. + * @return Grading method. */ getScormGradeMethod(scorm: any): string { if (scorm.maxattempt == 1) { @@ -1224,9 +1220,9 @@ export class AddonModScormProvider { /** * Invalidates access information. * - * @param {number} forumId SCORM ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param forumId SCORM ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateAccessInformation(scormId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1237,10 +1233,10 @@ export class AddonModScormProvider { /** * Invalidates all the data related to a certain SCORM. * - * @param {number} scormId SCORM 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 scormId SCORM 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. */ invalidateAllScormData(scormId: number, siteId?: string, userId?: number): Promise { const promises = []; @@ -1256,10 +1252,10 @@ export class AddonModScormProvider { /** * Invalidates attempt count. * - * @param {number} scormId SCORM 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 scormId SCORM 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. */ invalidateAttemptCount(scormId: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1272,11 +1268,11 @@ export class AddonModScormProvider { /** * 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. - * @param {number} [userId] User ID. If not defined use site's current user. - * @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. + * @param userId User ID. If not defined use site's current user. + * @return Promise resolved when the data is invalidated. */ invalidateContent(moduleId: number, courseId: number, siteId?: string, userId?: number): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -1295,9 +1291,9 @@ export class AddonModScormProvider { /** * Invalidates SCORM 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. */ invalidateScormData(courseId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1308,9 +1304,9 @@ export class AddonModScormProvider { /** * Invalidates SCORM user data for all attempts. * - * @param {number} scormId SCORM ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param scormId SCORM ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateScormUserData(scormId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1321,9 +1317,9 @@ export class AddonModScormProvider { /** * Invalidates SCORM scos for all organizations. * - * @param {number} scormId SCORM ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param scormId SCORM ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateScos(scormId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1334,12 +1330,12 @@ export class AddonModScormProvider { /** * Check if a SCORM's attempt is incomplete. * - * @param {any} scormId SCORM ID. - * @param {number} attempt Attempt. - * @param {boolean} offline Whether the attempt is offline. - * @param {boolean} ignoreCache Whether it should ignore cached data for online attempts. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with a boolean: true if incomplete, false otherwise. + * @param scormId SCORM ID. + * @param attempt Attempt. + * @param offline Whether the attempt is offline. + * @param ignoreCache Whether it should ignore cached data for online attempts. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with a boolean: true if incomplete, false otherwise. */ isAttemptIncomplete(scormId: number, attempt: number, offline?: boolean, ignoreCache?: boolean, siteId?: string) : Promise { @@ -1365,8 +1361,8 @@ export class AddonModScormProvider { * Given a launch URL, check if it's a external link. * Based on Moodle's scorm_external_link. * - * @param {string} link Link to check. - * @return {boolean} Whether it's an external link. + * @param link Link to check. + * @return Whether it's an external link. */ protected isExternalLink(link: string): boolean { link = link.toLowerCase(); @@ -1383,8 +1379,8 @@ export class AddonModScormProvider { /** * Check if the given SCORM is closed. * - * @param {any} scorm SCORM to check. - * @return {boolean} Whether the SCORM is closed. + * @param scorm SCORM to check. + * @return Whether the SCORM is closed. */ isScormClosed(scorm: any): boolean { const timeNow = this.timeUtils.timestamp(); @@ -1399,8 +1395,8 @@ export class AddonModScormProvider { /** * Check if the given SCORM is downloadable. * - * @param {any} scorm SCORM to check. - * @return {boolean} Whether the SCORM is downloadable. + * @param scorm SCORM to check. + * @return Whether the SCORM is downloadable. */ isScormDownloadable(scorm: any): boolean { return typeof scorm.protectpackagedownloads != 'undefined' && scorm.protectpackagedownloads === false; @@ -1409,8 +1405,8 @@ export class AddonModScormProvider { /** * Check if the given SCORM is open. * - * @param {any} scorm SCORM to check. - * @return {boolean} Whether the SCORM is open. + * @param scorm SCORM to check. + * @return Whether the SCORM is open. */ isScormOpen(scorm: any): boolean { const timeNow = this.timeUtils.timestamp(); @@ -1425,8 +1421,8 @@ export class AddonModScormProvider { /** * Check if a SCORM is unsupported in the app. If it's not, returns the error code to show. * - * @param {any} scorm SCORM to check. - * @return {string} String with error code if unsupported, undefined if supported. + * @param scorm SCORM to check. + * @return String with error code if unsupported, undefined if supported. */ isScormUnsupported(scorm: any): string { if (!this.isScormValidVersion(scorm)) { @@ -1441,8 +1437,8 @@ export class AddonModScormProvider { /** * Check if it's a valid SCORM 1.2. * - * @param {any} scorm SCORM to check. - * @return {boolean} Whether the SCORM is valid. + * @param scorm SCORM to check. + * @return Whether the SCORM is valid. */ isScormValidVersion(scorm: any): boolean { return scorm.version == 'SCORM_1.2'; @@ -1451,8 +1447,8 @@ export class AddonModScormProvider { /** * Check if a SCO status is incomplete. * - * @param {string} status SCO status. - * @return {boolean} Whether it's incomplete. + * @param status SCO status. + * @return Whether it's incomplete. */ isStatusIncomplete(status: any): boolean { return !status || status == 'notattempted' || status == 'incomplete' || status == 'browsed'; @@ -1461,8 +1457,8 @@ export class AddonModScormProvider { /** * Check if a package URL is valid. * - * @param {string} packageUrl Package URL. - * @return {boolean} Whether it's valid. + * @param packageUrl Package URL. + * @return Whether it's valid. */ isValidPackageUrl(packageUrl: string): boolean { if (!packageUrl) { @@ -1478,11 +1474,11 @@ export class AddonModScormProvider { /** * Report a SCO as being launched. * - * @param {number} scormId SCORM ID. - * @param {number} scoId SCO ID. - * @param {string} [name] Name of the SCORM. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the WS call is successful. + * @param scormId SCORM ID. + * @param scoId SCO ID. + * @param name Name of the SCORM. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the WS call is successful. */ logLaunchSco(scormId: number, scoId: number, name?: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1499,10 +1495,10 @@ export class AddonModScormProvider { /** * Report a SCORM as being viewed. * - * @param {string} id Module ID. - * @param {string} [name] Name of the SCORM. - * @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 SCORM. + * @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 = { @@ -1516,13 +1512,13 @@ export class AddonModScormProvider { /** * Saves a SCORM tracking record. * - * @param {number} scoId Sco ID. - * @param {number} attempt Attempt number. - * @param {any[]} tracks Tracking data to store. - * @param {any} scorm SCORM. - * @param {boolean} offline Whether the attempt is offline. - * @param {any} [userData] User data for this attempt and SCO. If not defined, it will be retrieved from DB. Recommended. - * @return {Promise} Promise resolved when data is saved. + * @param scoId Sco ID. + * @param attempt Attempt number. + * @param tracks Tracking data to store. + * @param scorm SCORM. + * @param offline Whether the attempt is offline. + * @param userData User data for this attempt and SCO. If not defined, it will be retrieved from DB. Recommended. + * @return Promise resolved when data is saved. */ saveTracks(scoId: number, attempt: number, tracks: any[], scorm: any, offline?: boolean, userData?: any, siteId?: string) : Promise { @@ -1553,12 +1549,12 @@ export class AddonModScormProvider { /** * Saves a SCORM tracking record. * - * @param {number} scormId SCORM ID. - * @param {number} scoId Sco ID. - * @param {number} attempt Attempt number. - * @param {any[]} tracks Tracking data. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when data is saved. + * @param scormId SCORM ID. + * @param scoId Sco ID. + * @param attempt Attempt number. + * @param tracks Tracking data. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when data is saved. */ saveTracksOnline(scormId: number, scoId: number, attempt: number, tracks: any[], siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1590,15 +1586,15 @@ export class AddonModScormProvider { * Saves a SCORM tracking record using a synchronous call. * Please use this function only if synchronous is a must. It's recommended to use saveTracks. * - * @param {number} scoId Sco ID. - * @param {number} attempt Attempt number. - * @param {any[]} tracks Tracking data to store. - * @param {any} scorm SCORM. - * @param {boolean} [offline] Whether the attempt is offline. - * @param {any} [userData] User data for this attempt and SCO. Required if offline=true. - * @return {boolean} In online returns true if data is inserted, false otherwise. - * In offline returns true if data to insert is valid, false otherwise. True doesn't mean that the - * data has been stored, this function can return true but the insertion can still fail somehow. + * @param scoId Sco ID. + * @param attempt Attempt number. + * @param tracks Tracking data to store. + * @param scorm SCORM. + * @param offline Whether the attempt is offline. + * @param userData User data for this attempt and SCO. Required if offline=true. + * @return In online returns true if data is inserted, false otherwise. + * In offline returns true if data to insert is valid, false otherwise. True doesn't mean that the + * data has been stored, this function can return true but the insertion can still fail somehow. */ saveTracksSync(scoId: number, attempt: number, tracks: any[], scorm: any, offline?: boolean, userData?: any): boolean { if (offline) { @@ -1625,10 +1621,10 @@ export class AddonModScormProvider { * Saves a SCORM tracking record using a synchronous call. * Please use this function only if synchronous is a must. It's recommended to use saveTracksOnline. * - * @param {number} scoId Sco ID. - * @param {number} attempt Attempt number. - * @param {any[]} tracks Tracking data. - * @return {boolean} True if success, false otherwise. + * @param scoId Sco ID. + * @param attempt Attempt number. + * @param tracks Tracking data. + * @return True if success, false otherwise. */ saveTracksSyncOnline(scoId: number, attempt: number, tracks: any[]): boolean { const params = { @@ -1671,10 +1667,10 @@ export class AddonModScormProvider { * Check if the SCORM main file should be downloaded. * This function should only be called if the SCORM can be downloaded (not downloaded or outdated). * - * @param {any} scorm SCORM to check. - * @param {boolean} [isOutdated] True if package outdated, false if not downloaded, undefined to calculate it. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with true if it should be downloaded, false otherwise. + * @param scorm SCORM to check. + * @param isOutdated True if package outdated, false if not downloaded, undefined to calculate it. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with true if it should be downloaded, false otherwise. */ shouldDownloadMainFile(scorm: any, isOutdated?: boolean, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -1712,11 +1708,11 @@ export class AddonModScormProvider { /** * If needed, updates cached user data after saving tracks in online. * - * @param {number} scormId SCORM ID. - * @param {number} attempt Attempt number. - * @param {any[]} tracks Tracking data saved. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when updated. + * @param scormId SCORM ID. + * @param attempt Attempt number. + * @param tracks Tracking data saved. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when updated. */ protected updateUserDataAfterSave(scormId: number, attempt: number, tracks: any[], siteId?: string): Promise { if (!tracks || !tracks.length) { diff --git a/src/addon/mod/scorm/providers/sync-cron-handler.ts b/src/addon/mod/scorm/providers/sync-cron-handler.ts index 33999d0cb..e0fef8e95 100644 --- a/src/addon/mod/scorm/providers/sync-cron-handler.ts +++ b/src/addon/mod/scorm/providers/sync-cron-handler.ts @@ -29,9 +29,9 @@ export class AddonModScormSyncCronHandler 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.scormSync.syncAllScorms(siteId, force); @@ -40,7 +40,7 @@ export class AddonModScormSyncCronHandler 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.scormSync.syncInterval; diff --git a/src/addon/mod/survey/components/index/index.ts b/src/addon/mod/survey/components/index/index.ts index 8b6cfbb65..23f479553 100644 --- a/src/addon/mod/survey/components/index/index.ts +++ b/src/addon/mod/survey/components/index/index.ts @@ -64,7 +64,7 @@ export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityCo /** * Perform the invalidate content function. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected invalidateContent(): Promise { const promises = []; @@ -80,8 +80,8 @@ export class AddonModSurveyIndexComponent 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 { if (this.survey && syncEventData.surveyId == this.survey.id && syncEventData.userId == this.userId) { @@ -94,10 +94,10 @@ export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityCo /** * Download survey contents. * - * @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 { return this.surveyProvider.getSurvey(this.courseId, this.module.id).then((survey) => { @@ -135,7 +135,7 @@ export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityCo /** * Convenience function to get survey questions. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchQuestions(): Promise { return this.surveyProvider.getQuestions(this.survey.id).then((questions) => { @@ -161,7 +161,7 @@ export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityCo /** * Check if answers are valid to be submitted. * - * @return {boolean} If answers are valid + * @return If answers are valid */ isValidResponse(): boolean { for (const x in this.answers) { @@ -213,7 +213,7 @@ export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityCo /** * Performs the sync of the activity. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected sync(): Promise { return this.surveySync.syncSurvey(this.survey.id, this.userId); @@ -222,8 +222,8 @@ export class AddonModSurveyIndexComponent 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 { return result.answersSent; diff --git a/src/addon/mod/survey/pages/index/index.ts b/src/addon/mod/survey/pages/index/index.ts index 3c18ae76f..ce2546b82 100644 --- a/src/addon/mod/survey/pages/index/index.ts +++ b/src/addon/mod/survey/pages/index/index.ts @@ -40,7 +40,7 @@ export class AddonModSurveyIndexPage { /** * Update some data based on the survey instance. * - * @param {any} survey Survey instance. + * @param survey Survey instance. */ updateData(survey: any): void { this.title = survey.name || this.title; diff --git a/src/addon/mod/survey/providers/helper.ts b/src/addon/mod/survey/providers/helper.ts index cc3d48e31..23d7dc9b4 100644 --- a/src/addon/mod/survey/providers/helper.ts +++ b/src/addon/mod/survey/providers/helper.ts @@ -26,8 +26,8 @@ export class AddonModSurveyHelperProvider { /** * Turns a string with values separated by commas into an array. * - * @param {any} value Value to convert. - * @return {string[]} Array. + * @param value Value to convert. + * @return Array. */ protected commaStringToArray(value: any): string[] { if (typeof value == 'string') { @@ -44,8 +44,8 @@ export class AddonModSurveyHelperProvider { /** * Gets the parent questions and puts them in an object: ID -> question. * - * @param {Object[]} questions Questions. - * @return {any} Object with parent questions. + * @param questions Questions. + * @return Object with parent questions. */ protected getParentQuestions(questions: any[]): any { const parents = {}; @@ -63,8 +63,8 @@ export class AddonModSurveyHelperProvider { * Format a questions list, turning "multi" and "options" strings into arrays and adding the properties * 'num' and 'name'. * - * @param {any[]} questions Questions. - * @return {any[]} Promise resolved with the formatted questions. + * @param questions Questions. + * @return Promise resolved with the formatted questions. */ formatQuestions(questions: any[]): any[] { diff --git a/src/addon/mod/survey/providers/module-handler.ts b/src/addon/mod/survey/providers/module-handler.ts index ed3368ccb..14db300e5 100644 --- a/src/addon/mod/survey/providers/module-handler.ts +++ b/src/addon/mod/survey/providers/module-handler.ts @@ -44,7 +44,7 @@ export class AddonModSurveyModuleHandler 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; @@ -53,10 +53,10 @@ export class AddonModSurveyModuleHandler 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 { @@ -78,9 +78,9 @@ export class AddonModSurveyModuleHandler 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 AddonModSurveyIndexComponent; diff --git a/src/addon/mod/survey/providers/offline.ts b/src/addon/mod/survey/providers/offline.ts index 5e3185315..b9709ca0b 100644 --- a/src/addon/mod/survey/providers/offline.ts +++ b/src/addon/mod/survey/providers/offline.ts @@ -72,10 +72,10 @@ export class AddonModSurveyOfflineProvider { /** * Delete a survey answers. * - * @param {number} surveyId Survey ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User the answers belong to. If not defined, current user in site. - * @return {Promise} Promise resolved if deleted, rejected if failure. + * @param surveyId Survey ID. + * @param siteId Site ID. If not defined, current site. + * @param userId User the answers belong to. If not defined, current user in site. + * @return Promise resolved if deleted, rejected if failure. */ deleteSurveyAnswers(surveyId: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -88,8 +88,8 @@ export class AddonModSurveyOfflineProvider { /** * Get all the stored data from all the surveys. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with answers. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with answers. */ getAllData(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -106,10 +106,10 @@ export class AddonModSurveyOfflineProvider { /** * Get a survey stored answers. * - * @param {number} surveyId Survey ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User the answers belong to. If not defined, current user in site. - * @return {Promise} Promise resolved with the answers. + * @param surveyId Survey ID. + * @param siteId Site ID. If not defined, current site. + * @param userId User the answers belong to. If not defined, current user in site. + * @return Promise resolved with the answers. */ getSurveyAnswers(surveyId: number, siteId?: string, userId?: number): Promise { return this.getSurveyData(surveyId, siteId, userId).then((entry) => { @@ -122,10 +122,10 @@ export class AddonModSurveyOfflineProvider { /** * Get a survey stored data. * - * @param {number} surveyId Survey ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User the answers belong to. If not defined, current user in site. - * @return {Promise} Promise resolved with the data. + * @param surveyId Survey ID. + * @param siteId Site ID. If not defined, current site. + * @param userId User the answers belong to. If not defined, current user in site. + * @return Promise resolved with the data. */ getSurveyData(surveyId: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -142,10 +142,10 @@ export class AddonModSurveyOfflineProvider { /** * Check if there are offline answers to send. * - * @param {number} surveyId Survey ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User the answers belong to. If not defined, current user in site. - * @return {Promise} Promise resolved with boolean: true if has offline answers, false otherwise. + * @param surveyId Survey ID. + * @param siteId Site ID. If not defined, current site. + * @param userId User the answers belong to. If not defined, current user in site. + * @return Promise resolved with boolean: true if has offline answers, false otherwise. */ hasAnswers(surveyId: number, siteId?: string, userId?: number): Promise { return this.getSurveyAnswers(surveyId, siteId, userId).then((answers) => { @@ -156,13 +156,13 @@ export class AddonModSurveyOfflineProvider { /** * Save answers to be sent later. * - * @param {number} surveyId Survey ID. - * @param {string} name Survey name. - * @param {number} courseId Course ID the survey belongs to. - * @param {any[]} answers Answers. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [userId] User the answers belong to. If not defined, current user in site. - * @return {Promise} Promise resolved if stored, rejected if failure. + * @param surveyId Survey ID. + * @param name Survey name. + * @param courseId Course ID the survey belongs to. + * @param answers Answers. + * @param siteId Site ID. If not defined, current site. + * @param userId User the answers belong to. If not defined, current user in site. + * @return Promise resolved if stored, rejected if failure. */ saveAnswers(surveyId: number, name: string, courseId: number, answers: any[], siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { diff --git a/src/addon/mod/survey/providers/prefetch-handler.ts b/src/addon/mod/survey/providers/prefetch-handler.ts index 83945c844..b56636f8f 100644 --- a/src/addon/mod/survey/providers/prefetch-handler.ts +++ b/src/addon/mod/survey/providers/prefetch-handler.ts @@ -48,9 +48,9 @@ export class AddonModSurveyPrefetchHandler extends CoreCourseActivityPrefetchHan /** * Returns survey 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.surveyProvider.getSurvey(courseId, module.id).catch(() => { @@ -63,9 +63,9 @@ export class AddonModSurveyPrefetchHandler extends CoreCourseActivityPrefetchHan /** * 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.surveyProvider.invalidateContent(moduleId, courseId); @@ -74,9 +74,9 @@ export class AddonModSurveyPrefetchHandler extends CoreCourseActivityPrefetchHan /** * 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 { return this.surveyProvider.invalidateSurveyData(courseId); @@ -85,7 +85,7 @@ export class AddonModSurveyPrefetchHandler extends CoreCourseActivityPrefetchHan /** * 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; @@ -94,11 +94,11 @@ export class AddonModSurveyPrefetchHandler extends CoreCourseActivityPrefetchHan /** * 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.prefetchSurvey.bind(this)); @@ -107,11 +107,11 @@ export class AddonModSurveyPrefetchHandler extends CoreCourseActivityPrefetchHan /** * Prefetch a survey. * - * @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. - * @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. + * @return Promise resolved when done. */ protected prefetchSurvey(module: any, courseId: number, single: boolean, siteId: string): Promise { return this.surveyProvider.getSurvey(courseId, module.id, true, siteId).then((survey) => { @@ -133,10 +133,10 @@ export class AddonModSurveyPrefetchHandler extends CoreCourseActivityPrefetchHan /** * 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/survey/providers/survey.ts b/src/addon/mod/survey/providers/survey.ts index 824b7bd33..078fae02c 100644 --- a/src/addon/mod/survey/providers/survey.ts +++ b/src/addon/mod/survey/providers/survey.ts @@ -41,10 +41,10 @@ export class AddonModSurveyProvider { /** * Get a survey's questions. * - * @param {number} surveyId Survey ID. - * @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 questions are retrieved. + * @param surveyId Survey 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 questions are retrieved. */ getQuestions(surveyId: number, ignoreCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -74,8 +74,8 @@ export class AddonModSurveyProvider { /** * Get cache key for survey questions WS calls. * - * @param {number} surveyId Survey ID. - * @return {string} Cache key. + * @param surveyId Survey ID. + * @return Cache key. */ protected getQuestionsCacheKey(surveyId: number): string { return this.ROOT_CACHE_KEY + 'questions:' + surveyId; @@ -84,8 +84,8 @@ export class AddonModSurveyProvider { /** * Get cache key for survey data WS calls. * - * @param {number} courseId Course ID. - * @return {string} Cache key. + * @param courseId Course ID. + * @return Cache key. */ protected getSurveyCacheKey(courseId: number): string { return this.ROOT_CACHE_KEY + 'survey:' + courseId; @@ -94,12 +94,12 @@ export class AddonModSurveyProvider { /** * Get a survey data. * - * @param {number} courseId Course ID. - * @param {string} key Name of the property to check. - * @param {any} value Value to search. - * @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 survey is retrieved. + * @param courseId Course ID. + * @param key Name of the property to check. + * @param value Value to search. + * @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 survey is retrieved. */ protected getSurveyDataByKey(courseId: number, key: string, value: any, ignoreCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -134,11 +134,11 @@ export class AddonModSurveyProvider { /** * Get a survey by course module ID. * - * @param {number} courseId Course ID. - * @param {number} cmId Course module ID. - * @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 survey is retrieved. + * @param courseId Course ID. + * @param cmId Course module 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 survey is retrieved. */ getSurvey(courseId: number, cmId: number, ignoreCache?: boolean, siteId?: string): Promise { return this.getSurveyDataByKey(courseId, 'coursemodule', cmId, ignoreCache, siteId); @@ -147,11 +147,11 @@ export class AddonModSurveyProvider { /** * Get a survey by ID. * - * @param {number} courseId Course ID. - * @param {number} id Survey ID. - * @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 survey is retrieved. + * @param courseId Course ID. + * @param id Survey 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 survey is retrieved. */ getSurveyById(courseId: number, id: number, ignoreCache?: boolean, siteId?: string): Promise { return this.getSurveyDataByKey(courseId, 'id', id, ignoreCache, siteId); @@ -160,10 +160,10 @@ export class AddonModSurveyProvider { /** * 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(); @@ -188,9 +188,9 @@ export class AddonModSurveyProvider { /** * Invalidates survey questions. * - * @param {number} surveyId Survey ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param surveyId Survey ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateQuestions(surveyId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -201,9 +201,9 @@ export class AddonModSurveyProvider { /** * Invalidates survey 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. */ invalidateSurveyData(courseId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -214,10 +214,10 @@ export class AddonModSurveyProvider { /** * Report the survey as being viewed. * - * @param {number} id Module ID. - * @param {string} [name] Name of the assign. - * @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 assign. + * @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 = { @@ -231,13 +231,13 @@ export class AddonModSurveyProvider { /** * Send survey answers. If cannot send them to Moodle, they'll be stored in offline to be sent later. * - * @param {number} surveyId Survey ID. - * @param {string} name Survey name. - * @param {number} courseId Course ID the survey belongs to. - * @param {any[]} answers Answers. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean if success: true if answers were sent to server, - * false if stored in device. + * @param surveyId Survey ID. + * @param name Survey name. + * @param courseId Course ID the survey belongs to. + * @param answers Answers. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean if success: true if answers were sent to server, + * false if stored in device. */ submitAnswers(surveyId: number, name: string, courseId: number, answers: any[], siteId?: string): Promise { // Convenience function to store a survey to be synchronized later. @@ -274,11 +274,11 @@ export class AddonModSurveyProvider { /** * Send survey answers to Moodle. * - * @param {number} surveyId Survey ID. - * @param {any[]} answers Answers. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when answers are successfully submitted. Rejected with object containing - * the error message (if any) and a boolean indicating if the error was returned by WS. + * @param surveyId Survey ID. + * @param answers Answers. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when answers are successfully submitted. Rejected with object containing + * the error message (if any) and a boolean indicating if the error was returned by WS. */ submitAnswersOnline(surveyId: number, answers: any[], siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { diff --git a/src/addon/mod/survey/providers/sync-cron-handler.ts b/src/addon/mod/survey/providers/sync-cron-handler.ts index f3e2e9552..e3b731569 100644 --- a/src/addon/mod/survey/providers/sync-cron-handler.ts +++ b/src/addon/mod/survey/providers/sync-cron-handler.ts @@ -29,9 +29,9 @@ export class AddonModSurveySyncCronHandler 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.surveySync.syncAllSurveys(siteId, force); @@ -40,7 +40,7 @@ export class AddonModSurveySyncCronHandler 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.surveySync.syncInterval; diff --git a/src/addon/mod/survey/providers/sync.ts b/src/addon/mod/survey/providers/sync.ts index 0a57a0258..4ffc79cd0 100644 --- a/src/addon/mod/survey/providers/sync.ts +++ b/src/addon/mod/survey/providers/sync.ts @@ -55,9 +55,9 @@ export class AddonModSurveySyncProvider extends CoreCourseActivitySyncBaseProvid /** * Get the ID of a survey sync. * - * @param {number} surveyId Survey ID. - * @param {number} userId User the answers belong to. - * @return {string} Sync ID. + * @param surveyId Survey ID. + * @param userId User the answers belong to. + * @return Sync ID. * @protected */ getSyncId(surveyId: number, userId: number): string { @@ -67,9 +67,9 @@ export class AddonModSurveySyncProvider extends CoreCourseActivitySyncBaseProvid /** * Try to synchronize all the surveys 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. */ syncAllSurveys(siteId?: string, force?: boolean): Promise { return this.syncOnSites('all surveys', this.syncAllSurveysFunc.bind(this), [force], siteId); @@ -78,9 +78,9 @@ export class AddonModSurveySyncProvider extends CoreCourseActivitySyncBaseProvid /** * Sync all pending surveys 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 syncAllSurveysFunc(siteId: string, force?: boolean): Promise { // Get all survey answers pending to be sent in the site. @@ -109,10 +109,10 @@ export class AddonModSurveySyncProvider extends CoreCourseActivitySyncBaseProvid /** * Sync a survey only if a certain time has passed since the last time. * - * @param {number} surveyId Survey ID. - * @param {number} userId User the answers belong to. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the survey is synced or if it doesn't need to be synced. + * @param surveyId Survey ID. + * @param userId User the answers belong to. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the survey is synced or if it doesn't need to be synced. */ syncSurveyIfNeeded(surveyId: number, userId: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -129,10 +129,10 @@ export class AddonModSurveySyncProvider extends CoreCourseActivitySyncBaseProvid /** * Synchronize a survey. * - * @param {number} surveyId Survey ID. - * @param {number} [userId] User the answers belong to. If not defined, current user. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if sync is successful, rejected otherwise. + * @param surveyId Survey ID. + * @param userId User the answers belong to. If not defined, current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if sync is successful, rejected otherwise. */ syncSurvey(surveyId: number, userId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { diff --git a/src/addon/mod/url/components/index/index.ts b/src/addon/mod/url/components/index/index.ts index 842d14194..e7bcd6206 100644 --- a/src/addon/mod/url/components/index/index.ts +++ b/src/addon/mod/url/components/index/index.ts @@ -68,7 +68,7 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo /** * Perform the invalidate content function. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected invalidateContent(): Promise { return this.urlProvider.invalidateContent(this.module.id, this.courseId); @@ -77,8 +77,8 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo /** * Download url 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 canGetUrl = this.canGetUrl, @@ -138,8 +138,8 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo /** * Calculate the display options to determine how the URL should be rendered. * - * @param {any} url Object with the URL data. - * @return {Promise} Promise resolved when done. + * @param url Object with the URL data. + * @return Promise resolved when done. */ protected calculateDisplayOptions(url: any): Promise { const displayType = this.urlProvider.getFinalDisplayType(url); @@ -175,7 +175,7 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo /** * Log view into the site and checks module completion. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected logView(): Promise { return this.urlProvider.logView(this.module.instance, this.module.name).then(() => { diff --git a/src/addon/mod/url/pages/index/index.ts b/src/addon/mod/url/pages/index/index.ts index 1e0ec28c6..1b5fde2db 100644 --- a/src/addon/mod/url/pages/index/index.ts +++ b/src/addon/mod/url/pages/index/index.ts @@ -40,7 +40,7 @@ export class AddonModUrlIndexPage { /** * Update some data based on the url instance. * - * @param {any} url Url instance. + * @param url Url instance. */ updateData(url: any): void { this.title = url.name || this.title; diff --git a/src/addon/mod/url/providers/helper.ts b/src/addon/mod/url/providers/helper.ts index 4620fce98..25a9f99e5 100644 --- a/src/addon/mod/url/providers/helper.ts +++ b/src/addon/mod/url/providers/helper.ts @@ -29,7 +29,7 @@ export class AddonModUrlHelperProvider { /** * Opens a URL. * - * @param {string} url The URL to go to. + * @param url The URL to go to. */ open(url: string): void { const modal = this.domUtils.showModalLoading(); diff --git a/src/addon/mod/url/providers/module-handler.ts b/src/addon/mod/url/providers/module-handler.ts index fb51ea3bc..05f475d59 100644 --- a/src/addon/mod/url/providers/module-handler.ts +++ b/src/addon/mod/url/providers/module-handler.ts @@ -50,7 +50,7 @@ export class AddonModUrlModuleHandler 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; @@ -59,10 +59,10 @@ export class AddonModUrlModuleHandler 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 { // tslint:disable: no-this-assignment @@ -140,9 +140,9 @@ export class AddonModUrlModuleHandler implements CoreCourseModuleHandler { /** * Returns if contents are loaded to show link 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 hideLinkButton(module: any, courseId: number): Promise { return this.courseProvider.loadModuleContents(module, courseId, undefined, false, false, undefined, this.modName) @@ -158,9 +158,9 @@ export class AddonModUrlModuleHandler 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 AddonModUrlIndexComponent; @@ -169,8 +169,8 @@ export class AddonModUrlModuleHandler implements CoreCourseModuleHandler { /** * Open the URL. * - * @param {any} module The module object. - * @param {number} courseId The course ID. + * @param module The module object. + * @param courseId The course ID. */ protected openUrl(module: any, courseId: number): void { this.urlProvider.logView(module.instance, module.name).then(() => { diff --git a/src/addon/mod/url/providers/url.ts b/src/addon/mod/url/providers/url.ts index 13261f104..28ffe8da8 100644 --- a/src/addon/mod/url/providers/url.ts +++ b/src/addon/mod/url/providers/url.ts @@ -41,8 +41,8 @@ export class AddonModUrlProvider { /** * Get the final display type for a certain URL. Based on Moodle's url_get_final_display_type. * - * @param {any} url URL data. - * @return {number} Final display type. + * @param url URL data. + * @return Final display type. */ getFinalDisplayType(url: any): number { if (!url) { @@ -93,8 +93,8 @@ export class AddonModUrlProvider { /** * Get cache key for url data WS calls. * - * @param {number} courseId Course ID. - * @return {string} Cache key. + * @param courseId Course ID. + * @return Cache key. */ protected getUrlCacheKey(courseId: number): string { return this.ROOT_CACHE_KEY + 'url:' + courseId; @@ -103,11 +103,11 @@ export class AddonModUrlProvider { /** * Get a url 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 url 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 url is retrieved. */ protected getUrlDataByKey(courseId: number, key: string, value: any, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -137,10 +137,10 @@ export class AddonModUrlProvider { /** * Get a url 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 url 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 url is retrieved. */ getUrl(courseId: number, cmId: number, siteId?: string): Promise { return this.getUrlDataByKey(courseId, 'coursemodule', cmId, siteId); @@ -149,8 +149,8 @@ export class AddonModUrlProvider { /** * Guess the icon for a certain URL. Based on Moodle's url_guess_icon. * - * @param {string} url URL to check. - * @return {string} Icon, empty if it should use the default icon. + * @param url URL to check. + * @return Icon, empty if it should use the default icon. */ guessIcon(url: string): string { url = url || ''; @@ -176,10 +176,10 @@ export class AddonModUrlProvider { /** * 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(); @@ -195,9 +195,9 @@ export class AddonModUrlProvider { /** * Invalidates url 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. */ invalidateUrlData(courseId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -208,7 +208,7 @@ export class AddonModUrlProvider { /** * Returns whether or not getUrl WS available or not. * - * @return {boolean} If WS is abalaible. + * @return If WS is abalaible. * @since 3.3 */ isGetUrlWSAvailable(): boolean { @@ -218,10 +218,10 @@ export class AddonModUrlProvider { /** * Report the url as being viewed. * - * @param {number} id Module ID. - * @param {string} [name] Name of the assign. - * @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 assign. + * @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 = { diff --git a/src/addon/mod/wiki/components/index/index.ts b/src/addon/mod/wiki/components/index/index.ts index cb91ef072..7296709d2 100644 --- a/src/addon/mod/wiki/components/index/index.ts +++ b/src/addon/mod/wiki/components/index/index.ts @@ -144,7 +144,7 @@ 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) { @@ -182,7 +182,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp /** * Construct the map of pages. * - * @param {any[]} subwikiPages List of pages. + * @param subwikiPages List of pages. */ constructMap(subwikiPages: any[]): void { let letter, @@ -216,10 +216,10 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp /** * 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 { @@ -315,8 +315,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) { @@ -361,7 +361,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; @@ -409,7 +409,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 +424,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 +458,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 { @@ -580,7 +580,7 @@ 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 { if (!page.id) { @@ -621,10 +621,10 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp /** * 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. @@ -648,7 +648,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 +657,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 +676,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 +693,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 +706,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; @@ -755,7 +755,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 +783,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 +806,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 +828,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 +847,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 +938,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, 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..858125544 100644 --- a/src/addon/mod/wiki/components/subwiki-picker/subwiki-picker.ts +++ b/src/addon/mod/wiki/components/subwiki-picker/subwiki-picker.ts @@ -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.ts b/src/addon/mod/wiki/pages/edit/edit.ts index 3d56004ff..9bcc6817b 100644 --- a/src/addon/mod/wiki/pages/edit/edit.ts +++ b/src/addon/mod/wiki/pages/edit/edit.ts @@ -125,7 +125,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy { /** * Convenience function to get wiki page data. * - * @return {Promise} Promise resolved with boolean: whether it was successful. + * @return Promise resolved with boolean: whether it was successful. */ protected fetchWikiPageData(): Promise { let promise, @@ -247,7 +247,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy { /** * Navigate to a new offline page. * - * @param {string} title Page title. + * @param title Page title. */ protected goToNewOfflinePage(title: string): void { if (this.courseId && (this.module.id || this.wikiId)) { @@ -274,8 +274,8 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy { /** * Check if we need to navigate to a new state. * - * @param {string} title Page title. - * @return {Promise} Promise resolved when done. + * @param title Page title. + * @return Promise resolved when done. */ protected gotoPage(title: string): Promise { return this.retrieveModuleInfo(this.wikiId).then(() => { @@ -316,7 +316,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy { /** * Check if data has changed. * - * @return {boolean} Whether data has changed. + * @return Whether data has changed. */ protected hasDataChanged(): boolean { const values = this.pageForm.value; @@ -327,7 +327,7 @@ export class AddonModWikiEditPage 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) { @@ -355,7 +355,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy { /** * In case we are NOT editing an offline page, check if the page loaded in previous view is different than this view. * - * @return {boolean} Whether previous view wiki page is different than current page. + * @return Whether previous view wiki page is different than current page. */ protected previousViewIsDifferentPageOnline(): boolean { // We cannot precisely detect when the state is the same but this is close to it. @@ -368,8 +368,8 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy { /** * In case we're editing an offline page, check if the page loaded in previous view is different than this view. * - * @param {string} title The current page title. - * @return {boolean} Whether previous view wiki page is different than current page. + * @param title The current page title. + * @return Whether previous view wiki page is different than current page. */ protected previousViewPageIsDifferentOffline(title: string): boolean { // We cannot precisely detect when the state is the same but this is close to it. @@ -501,8 +501,8 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy { /** * Fetch module information to redirect when needed. * - * @param {number} wikiId Wiki ID. - * @return {Promise} Promise resolved when done. + * @param wikiId Wiki ID. + * @return Promise resolved when done. */ protected retrieveModuleInfo(wikiId: number): Promise { if (this.module.id && this.courseId) { diff --git a/src/addon/mod/wiki/pages/index/index.ts b/src/addon/mod/wiki/pages/index/index.ts index 8acca3028..b3cef20a7 100644 --- a/src/addon/mod/wiki/pages/index/index.ts +++ b/src/addon/mod/wiki/pages/index/index.ts @@ -55,7 +55,7 @@ export class AddonModWikiIndexPage { /** * Update some data based on the data received. * - * @param {any} data The data received. + * @param data The data received. */ updateData(data: any): void { if (typeof data == 'string') { diff --git a/src/addon/mod/wiki/providers/create-link-handler.ts b/src/addon/mod/wiki/providers/create-link-handler.ts index 7eb5287ab..048d41822 100644 --- a/src/addon/mod/wiki/providers/create-link-handler.ts +++ b/src/addon/mod/wiki/providers/create-link-handler.ts @@ -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..4373bc024 100644 --- a/src/addon/mod/wiki/providers/edit-link-handler.ts +++ b/src/addon/mod/wiki/providers/edit-link-handler.ts @@ -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/module-handler.ts b/src/addon/mod/wiki/providers/module-handler.ts index 20a94af39..1963d5def 100644 --- a/src/addon/mod/wiki/providers/module-handler.ts +++ b/src/addon/mod/wiki/providers/module-handler.ts @@ -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..bcbe26978 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 @@ -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..0097af2f6 100644 --- a/src/addon/mod/wiki/providers/prefetch-handler.ts +++ b/src/addon/mod/wiki/providers/prefetch-handler.ts @@ -52,12 +52,12 @@ export class AddonModWikiPrefetchHandler extends CoreCourseActivityPrefetchHandl /** * 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,11 +75,11 @@ 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 = [], @@ -112,11 +112,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 +137,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 +148,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 +170,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 +211,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..5d31464c8 100644 --- a/src/addon/mod/wiki/providers/sync-cron-handler.ts +++ b/src/addon/mod/wiki/providers/sync-cron-handler.ts @@ -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..2d06b73b2 100644 --- a/src/addon/mod/wiki/providers/tag-area-handler.ts +++ b/src/addon/mod/wiki/providers/tag-area-handler.ts @@ -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..0c11d14d1 100644 --- a/src/addon/mod/wiki/providers/wiki-offline.ts +++ b/src/addon/mod/wiki/providers/wiki-offline.ts @@ -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..4bd73581a 100644 --- a/src/addon/mod/wiki/providers/wiki-sync.ts +++ b/src/addon/mod/wiki/providers/wiki-sync.ts @@ -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..f4fa688e2 100644 --- a/src/addon/mod/wiki/providers/wiki.ts +++ b/src/addon/mod/wiki/providers/wiki.ts @@ -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/workshop/assessment/accumulative/providers/handler.ts b/src/addon/mod/workshop/assessment/accumulative/providers/handler.ts index 40505af47..b5b9e0962 100644 --- a/src/addon/mod/workshop/assessment/accumulative/providers/handler.ts +++ b/src/addon/mod/workshop/assessment/accumulative/providers/handler.ts @@ -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/comments/providers/handler.ts b/src/addon/mod/workshop/assessment/comments/providers/handler.ts index 132e0f38b..83922cc0e 100644 --- a/src/addon/mod/workshop/assessment/comments/providers/handler.ts +++ b/src/addon/mod/workshop/assessment/comments/providers/handler.ts @@ -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/providers/handler.ts b/src/addon/mod/workshop/assessment/numerrors/providers/handler.ts index 5e77dc7e0..94e361d83 100644 --- a/src/addon/mod/workshop/assessment/numerrors/providers/handler.ts +++ b/src/addon/mod/workshop/assessment/numerrors/providers/handler.ts @@ -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/providers/handler.ts b/src/addon/mod/workshop/assessment/rubric/providers/handler.ts index d9dc5a38b..41a9eb6b1 100644 --- a/src/addon/mod/workshop/assessment/rubric/providers/handler.ts +++ b/src/addon/mod/workshop/assessment/rubric/providers/handler.ts @@ -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/components/assessment-strategy/assessment-strategy.ts b/src/addon/mod/workshop/components/assessment-strategy/assessment-strategy.ts index 1516b9c60..6fe11499b 100644 --- a/src/addon/mod/workshop/components/assessment-strategy/assessment-strategy.ts +++ b/src/addon/mod/workshop/components/assessment-strategy/assessment-strategy.ts @@ -138,7 +138,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 +224,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 +254,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 +332,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/index/index.ts b/src/addon/mod/workshop/components/index/index.ts index 6e96123a2..b4f88d644 100644 --- a/src/addon/mod/workshop/components/index/index.ts +++ b/src/addon/mod/workshop/components/index/index.ts @@ -118,7 +118,7 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity /** * Function called when we receive an event of submission changes. * - * @param {any} data Data received by the event. + * @param data Data received by the event. */ protected eventReceived(data: any): void { if ((this.workshop && this.workshop.id === data.workshopId) || data.cmId === this.module.id) { @@ -132,7 +132,7 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity /** * Perform the invalidate content function. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected invalidateContent(): Promise { const promises = []; @@ -161,8 +161,8 @@ export class AddonModWorkshopIndexComponent 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 { if (this.workshop && syncEventData.workshopId == this.workshop.id) { @@ -178,10 +178,10 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity /** * Download feedback contents. * - * @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 { return this.workshopProvider.getWorkshop(this.courseId, this.module.id).then((workshop) => { @@ -240,8 +240,8 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity /** * Retrieves and shows submissions grade page. * - * @param {number} page Page number to be retrieved. - * @return {Promise} Resolved when done. + * @param page Page number to be retrieved. + * @return Resolved when done. */ gotoSubmissionsPage(page: number): Promise { return this.workshopProvider.getGradesReport(this.workshop.id, this.group, page).then((report) => { @@ -269,7 +269,7 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity /** * Open task. * - * @param {any} task Task to be done. + * @param task Task to be done. */ runTask(task: any): void { if (task.code == 'submit') { @@ -315,8 +315,8 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity /** * Set group to see the workshop. - * @param {number} groupId Group Id. - * @return {Promise} Promise resolved when done. + * @param groupId Group Id. + * @return Promise resolved when done. */ setGroup(groupId: number): Promise { this.group = groupId; @@ -327,7 +327,7 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity /** * Convenience function to set current phase information. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected setPhaseInfo(): Promise { this.submission = false; @@ -417,7 +417,7 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity /** * Performs the sync of the activity. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected sync(): Promise { return this.workshopSync.syncWorkshop(this.workshop.id); @@ -426,8 +426,8 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity /** * 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 result.updated; diff --git a/src/addon/mod/workshop/pages/assessment/assessment.ts b/src/addon/mod/workshop/pages/assessment/assessment.ts index 6c1783e35..e49d720a7 100644 --- a/src/addon/mod/workshop/pages/assessment/assessment.ts +++ b/src/addon/mod/workshop/pages/assessment/assessment.ts @@ -116,7 +116,7 @@ export class AddonModWorkshopAssessmentPage 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 || !this.evaluating) { @@ -134,7 +134,7 @@ export class AddonModWorkshopAssessmentPage implements OnInit, OnDestroy { /** * Fetch the assessment data. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected fetchAssessmentData(): Promise { return this.workshopProvider.getWorkshopById(this.courseId, this.workshopId).then((workshopData) => { @@ -247,7 +247,7 @@ export class AddonModWorkshopAssessmentPage implements OnInit, OnDestroy { /** * Check if data has changed. * - * @return {boolean} True if changed, false otherwise. + * @return True if changed, false otherwise. */ protected hasEvaluationChanged(): boolean { if (!this.loaded || !this.evaluating) { @@ -276,7 +276,7 @@ export class AddonModWorkshopAssessmentPage implements OnInit, OnDestroy { /** * Convenience function to refresh all the data. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected refreshAllData(): Promise { const promises = []; @@ -300,7 +300,7 @@ export class AddonModWorkshopAssessmentPage implements OnInit, OnDestroy { /** * Pull to refresh. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshAssessment(refresher: any): void { if (this.loaded) { @@ -328,7 +328,7 @@ export class AddonModWorkshopAssessmentPage implements OnInit, OnDestroy { /** * Sends the evaluation to be saved on the server. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected sendEvaluation(): Promise { const modal = this.domUtils.showModalLoading('core.sending', true), diff --git a/src/addon/mod/workshop/pages/edit-submission/edit-submission.ts b/src/addon/mod/workshop/pages/edit-submission/edit-submission.ts index 3409680a1..5e82ed2fa 100644 --- a/src/addon/mod/workshop/pages/edit-submission/edit-submission.ts +++ b/src/addon/mod/workshop/pages/edit-submission/edit-submission.ts @@ -103,7 +103,7 @@ export class AddonModWorkshopEditSubmissionPage 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) { @@ -131,7 +131,7 @@ export class AddonModWorkshopEditSubmissionPage implements OnInit, OnDestroy { /** * Fetch the submission data. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected fetchSubmissionData(): Promise { return this.workshopProvider.getWorkshop(this.courseId, this.module.id).then((workshopData) => { @@ -225,7 +225,7 @@ export class AddonModWorkshopEditSubmissionPage implements OnInit, OnDestroy { /** * Get the form input data. * - * @return {any} Object with all the info. + * @return Object with all the info. */ protected getInputData(): any { const submissionId = this.submission.id || 'newsub'; @@ -250,7 +250,7 @@ export class AddonModWorkshopEditSubmissionPage implements OnInit, OnDestroy { /** * Check if data has changed. * - * @return {boolean} True if changed or false if not. + * @return True if changed or false if not. */ protected hasDataChanged(): boolean { if (!this.loaded) { @@ -277,7 +277,7 @@ export class AddonModWorkshopEditSubmissionPage implements OnInit, OnDestroy { /** * Pull to refresh. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshSubmission(refresher: any): void { if (this.loaded) { @@ -315,7 +315,7 @@ export class AddonModWorkshopEditSubmissionPage implements OnInit, OnDestroy { /** * Send submission and save. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected saveSubmission(): Promise { const inputData = this.getInputData(); diff --git a/src/addon/mod/workshop/pages/index/index.ts b/src/addon/mod/workshop/pages/index/index.ts index db244a7d1..7ad7677da 100644 --- a/src/addon/mod/workshop/pages/index/index.ts +++ b/src/addon/mod/workshop/pages/index/index.ts @@ -42,7 +42,7 @@ export class AddonModWorkshopIndexPage { /** * Update some data based on the workshop instance. * - * @param {any} workshop Workshop instance. + * @param workshop Workshop instance. */ updateData(workshop: any): void { this.title = workshop.name || this.title; diff --git a/src/addon/mod/workshop/pages/phase/phase.ts b/src/addon/mod/workshop/pages/phase/phase.ts index a0d88a01f..3773fa727 100644 --- a/src/addon/mod/workshop/pages/phase/phase.ts +++ b/src/addon/mod/workshop/pages/phase/phase.ts @@ -58,7 +58,7 @@ export class AddonModWorkshopPhaseInfoPage { /** * Open task. * - * @param {any} task Task to be done. + * @param task Task to be done. */ runTask(task: any): void { if (task.code == 'submit') { diff --git a/src/addon/mod/workshop/pages/submission/submission.ts b/src/addon/mod/workshop/pages/submission/submission.ts index 1158e5df5..63580cdc8 100644 --- a/src/addon/mod/workshop/pages/submission/submission.ts +++ b/src/addon/mod/workshop/pages/submission/submission.ts @@ -141,7 +141,7 @@ export class AddonModWorkshopSubmissionPage 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 { const assessmentHasChanged = this.assessmentStrategy && this.assessmentStrategy.hasDataChanged(); @@ -170,7 +170,7 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy { /** * Function called when we receive an event of submission changes. * - * @param {any} data Event data received. + * @param data Event data received. */ protected eventReceived(data: any): void { if (this.workshopId === data.workshopId) { @@ -184,7 +184,7 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy { /** * Fetch the submission data. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected fetchSubmissionData(): Promise { return this.workshopHelper.getSubmissionById(this.workshopId, this.submissionId).then((submissionData) => { @@ -332,7 +332,7 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy { /** * Check if data has changed. * - * @return {boolean} True if changed, false otherwise. + * @return True if changed, false otherwise. */ protected hasEvaluationChanged(): boolean { if (!this.loaded || !this.access.canoverridegrades) { @@ -359,7 +359,7 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy { /** * Convenience function to refresh all the data. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected refreshAllData(): Promise { const promises = []; @@ -383,7 +383,7 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy { /** * Pull to refresh. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshSubmission(refresher: any): void { if (this.loaded) { @@ -427,7 +427,7 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy { /** * Sends the evaluation to be saved on the server. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected sendEvaluation(): Promise { const modal = this.domUtils.showModalLoading('core.sending', true); @@ -490,7 +490,7 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy { /** * Undo the submission delete action. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ undoDeleteSubmission(): Promise { return this.workshopOffline.deleteSubmissionAction(this.workshopId, this.submissionId, 'delete').finally(() => { diff --git a/src/addon/mod/workshop/providers/assessment-strategy-delegate.ts b/src/addon/mod/workshop/providers/assessment-strategy-delegate.ts index 3002f6deb..d403d3cb6 100644 --- a/src/addon/mod/workshop/providers/assessment-strategy-delegate.ts +++ b/src/addon/mod/workshop/providers/assessment-strategy-delegate.ts @@ -24,7 +24,6 @@ import { CoreSitesProvider } from '@providers/sites'; export interface AddonWorkshopAssessmentStrategyHandler extends CoreDelegateHandler { /** * The name of the assessment strategy. E.g. 'accumulative'. - * @type {string} */ strategyName: string; @@ -32,35 +31,35 @@ export interface AddonWorkshopAssessmentStrategyHandler extends CoreDelegateHand * 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; /** * 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; /** * 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; /** * 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; } @@ -82,8 +81,8 @@ export interface AddonWorkshopAssessmentStrategyHandler extends CoreDelegateHand /** * Check if an assessment strategy plugin is supported. * - * @param {string} workshopStrategy Assessment strategy name. - * @return {boolean} True if supported, false otherwise. + * @param workshopStrategy Assessment strategy name. + * @return True if supported, false otherwise. */ isPluginSupported(workshopStrategy: string): boolean { return this.hasHandler(workshopStrategy, true); @@ -92,9 +91,9 @@ export interface AddonWorkshopAssessmentStrategyHandler extends CoreDelegateHand /** * Get the directive to use for a certain assessment strategy plugin. * - * @param {Injector} injector Injector. - * @param {string} workshopStrategy Assessment strategy name. - * @return {any} The component, undefined if not found. + * @param injector Injector. + * @param workshopStrategy Assessment strategy name. + * @return The component, undefined if not found. */ getComponentForPlugin(injector: Injector, workshopStrategy: string): Promise { return this.executeFunctionOnEnabled(workshopStrategy, 'getComponent', [injector]); @@ -103,10 +102,10 @@ export interface AddonWorkshopAssessmentStrategyHandler extends CoreDelegateHand /** * Prepare original values to be shown and compared depending on the strategy selected. * - * @param {string} workshopStrategy Workshop strategy. - * @param {any} form Original data of the form. - * @param {number} workshopId Workshop ID. - * @return {Promise} Resolved with original values sorted. + * @param workshopStrategy Workshop strategy. + * @param form Original data of the form. + * @param workshopId Workshop ID. + * @return Resolved with original values sorted. */ getOriginalValues(workshopStrategy: string, form: any, workshopId: number): Promise { return Promise.resolve(this.executeFunctionOnEnabled(workshopStrategy, 'getOriginalValues', [form, workshopId]) || []); @@ -115,10 +114,10 @@ export interface AddonWorkshopAssessmentStrategyHandler extends CoreDelegateHand /** * Check if the assessment data has changed for a certain submission and workshop for a this strategy plugin. * - * @param {any} workshop Workshop. - * @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 workshop Workshop. + * @param originalValues Original values of the form. + * @param currentValues Current values of the form. + * @return True if data has changed, false otherwise. */ hasDataChanged(workshop: any, originalValues: any[], currentValues: any[]): boolean { return this.executeFunctionOnEnabled(workshop.strategy, 'hasDataChanged', [originalValues, currentValues]) || false; @@ -127,10 +126,10 @@ export interface AddonWorkshopAssessmentStrategyHandler extends CoreDelegateHand /** * Prepare assessment data to be sent to the server depending on the strategy selected. * - * @param {string} workshopStrategy Workshop strategy to follow. - * @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 workshopStrategy Workshop strategy to follow. + * @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(workshopStrategy: string, currentValues: any, form: any): Promise { return Promise.resolve(this.executeFunctionOnEnabled(workshopStrategy, 'prepareAssessmentData', [currentValues, form])); diff --git a/src/addon/mod/workshop/providers/helper.ts b/src/addon/mod/workshop/providers/helper.ts index c4d6560b0..9236845b2 100644 --- a/src/addon/mod/workshop/providers/helper.ts +++ b/src/addon/mod/workshop/providers/helper.ts @@ -43,9 +43,9 @@ export class AddonModWorkshopHelperProvider { /** * Get a task by code. * - * @param {any[]} tasks Array of tasks. - * @param {string} taskCode Unique task code. - * @return {any} Task requested + * @param tasks Array of tasks. + * @param taskCode Unique task code. + * @return Task requested */ getTask(tasks: any[], taskCode: string): any { for (const x in tasks) { @@ -60,9 +60,9 @@ export class AddonModWorkshopHelperProvider { /** * Check is task code is done. * - * @param {any[]} tasks Array of tasks. - * @param {string} taskCode Unique task code. - * @return {boolean} True if task is completed. + * @param tasks Array of tasks. + * @param taskCode Unique task code. + * @return True if task is completed. */ isTaskDone(tasks: any[], taskCode: string): boolean { const task = this.getTask(tasks, taskCode); @@ -78,10 +78,10 @@ export class AddonModWorkshopHelperProvider { /** * Return if a user can submit a workshop. * - * @param {any} workshop Workshop info. - * @param {any} access Access information. - * @param {any[]} tasks Array of tasks. - * @return {boolean} True if the user can submit the workshop. + * @param workshop Workshop info. + * @param access Access information. + * @param tasks Array of tasks. + * @return True if the user can submit the workshop. */ canSubmit(workshop: any, access: any, tasks: any[]): boolean { const examplesMust = workshop.useexamples && workshop.examplesmode == AddonModWorkshopProvider.EXAMPLES_BEFORE_SUBMISSION; @@ -94,9 +94,9 @@ export class AddonModWorkshopHelperProvider { /** * Return if a user can assess a workshop. * - * @param {any} workshop Workshop info. - * @param {any} access Access information. - * @return {boolean} True if the user can assess the workshop. + * @param workshop Workshop info. + * @param access Access information. + * @return True if the user can assess the workshop. */ canAssess(workshop: any, access: any): boolean { const examplesMust = workshop.useexamples && workshop.examplesmode == AddonModWorkshopProvider.EXAMPLES_BEFORE_ASSESSMENT; @@ -108,9 +108,9 @@ export class AddonModWorkshopHelperProvider { /** * Return a particular user submission from the submission list. * - * @param {number} workshopId Workshop ID. - * @param {number} [userId] User ID. If not defined current user Id. - * @return {Promise} Resolved with the submission, resolved with false if not found. + * @param workshopId Workshop ID. + * @param userId User ID. If not defined current user Id. + * @return Resolved with the submission, resolved with false if not found. */ getUserSubmission(workshopId: number, userId: number = 0): Promise { return this.workshopProvider.getSubmissions(workshopId).then((submissions) => { @@ -129,10 +129,10 @@ export class AddonModWorkshopHelperProvider { /** * Return a particular submission. It will use prefetched data if fetch fails. * - * @param {number} workshopId Workshop ID. - * @param {number} submissionId Submission ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Resolved with the submission, resolved with false if not found. + * @param workshopId Workshop ID. + * @param submissionId Submission ID. + * @param siteId Site ID. If not defined, current site. + * @return Resolved with the submission, resolved with false if not found. */ getSubmissionById(workshopId: number, submissionId: number, siteId?: string): Promise { return this.workshopProvider.getSubmission(workshopId, submissionId, siteId).catch(() => { @@ -152,11 +152,11 @@ export class AddonModWorkshopHelperProvider { /** * Return a particular assesment. It will use prefetched data if fetch fails. It will add assessment form data. * - * @param {number} workshopId Workshop ID. - * @param {number} assessmentId Assessment ID. - * @param {number} [userId] User ID. If not defined, current user. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Resolved with the assessment. + * @param workshopId Workshop ID. + * @param assessmentId Assessment ID. + * @param userId User ID. If not defined, current user. + * @param siteId Site ID. If not defined, current site. + * @return Resolved with the assessment. */ getReviewerAssessmentById(workshopId: number, assessmentId: number, userId: number = 0, siteId?: string): Promise { return this.workshopProvider.getAssessment(workshopId, assessmentId, siteId).catch((error) => { @@ -184,12 +184,12 @@ export class AddonModWorkshopHelperProvider { /** * Retrieves the assessment of the given user and all the related data. * - * @param {number} workshopId Workshop ID. - * @param {number} [userId=0] User ID. If not defined, current user. - * @param {boolean} [offline=false] True if it should return cached data. Has priority over ignoreCache. - * @param {boolean} [ignoreCache=false] 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 workshop data is retrieved. + * @param workshopId Workshop ID. + * @param userId User ID. If not defined, current user. + * @param offline 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, current site. + * @return Promise resolved when the workshop data is retrieved. */ getReviewerAssessments(workshopId: number, userId: number = 0, offline: boolean = false, ignoreCache: boolean = false, siteId?: string): Promise { @@ -212,11 +212,11 @@ export class AddonModWorkshopHelperProvider { /** * Delete stored attachment files for a submission. * - * @param {number} workshopId Workshop ID. - * @param {number} submissionId If not editing, it will refer to timecreated. - * @param {boolean} editing If the submission is being edited or added otherwise. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when deleted. + * @param workshopId Workshop ID. + * @param submissionId If not editing, it will refer to timecreated. + * @param editing If the submission is being edited or added otherwise. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when deleted. */ deleteSubmissionStoredFiles(workshopId: number, submissionId: number, editing: boolean, siteId?: string): Promise { return this.workshopOffline.getSubmissionFolder(workshopId, submissionId, editing, siteId).then((folderPath) => { @@ -230,12 +230,12 @@ export class AddonModWorkshopHelperProvider { * 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} workshopId Workshop ID. - * @param {number} submissionId If not editing, it will refer to timecreated. - * @param {boolean} editing If the submission is being edited or added otherwise. - * @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 workshopId Workshop ID. + * @param submissionId If not editing, it will refer to timecreated. + * @param editing If the submission is being edited or added otherwise. + * @param files List of files. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if success, rejected otherwise. */ storeSubmissionFiles(workshopId: number, submissionId: number, editing: boolean, files: any[], siteId?: string): Promise { // Get the folder where to store the files. @@ -247,13 +247,13 @@ export class AddonModWorkshopHelperProvider { /** * Upload or store some files for a submission, depending if the user is offline or not. * - * @param {number} workshopId Workshop ID. - * @param {number} submissionId If not editing, it will refer to timecreated. - * @param {any[]} files List of files. - * @param {boolean} editing If the submission is being edited or added otherwise. - * @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 workshopId Workshop ID. + * @param submissionId If not editing, it will refer to timecreated. + * @param files List of files. + * @param editing If the submission is being edited or added otherwise. + * @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. */ uploadOrStoreSubmissionFiles(workshopId: number, submissionId: number, files: any[], editing: boolean, offline: boolean, siteId?: string): Promise { @@ -267,11 +267,11 @@ export class AddonModWorkshopHelperProvider { /** * Get a list of stored attachment files for a submission. See AddonModWorkshopHelperProvider#storeFiles. * - * @param {number} workshopId Workshop ID. - * @param {number} submissionId If not editing, it will refer to timecreated. - * @param {boolean} editing If the submission is being edited or added otherwise. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the files. + * @param workshopId Workshop ID. + * @param submissionId If not editing, it will refer to timecreated. + * @param editing If the submission is being edited or added otherwise. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the files. */ getStoredSubmissionFiles(workshopId: number, submissionId: number, editing: boolean, siteId?: string): Promise { return this.workshopOffline.getSubmissionFolder(workshopId, submissionId, editing, siteId).then((folderPath) => { @@ -285,12 +285,12 @@ export class AddonModWorkshopHelperProvider { /** * Get a list of stored attachment files for a submission and online files also. See AddonModWorkshopHelperProvider#storeFiles. * - * @param {any} filesObject Files object combining offline and online information. - * @param {number} workshopId Workshop ID. - * @param {number} submissionId If not editing, it will refer to timecreated. - * @param {boolean} editing If the submission is being edited or added otherwise. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the files. + * @param filesObject Files object combining offline and online information. + * @param workshopId Workshop ID. + * @param submissionId If not editing, it will refer to timecreated. + * @param editing If the submission is being edited or added otherwise. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the files. */ getSubmissionFilesFromOfflineFilesObject(filesObject: any, workshopId: number, submissionId: number, editing: boolean, siteId?: string): Promise { @@ -302,10 +302,10 @@ export class AddonModWorkshopHelperProvider { /** * Delete stored attachment files for an assessment. * - * @param {number} workshopId Workshop ID. - * @param {number} assessmentId Assessment ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when deleted. + * @param workshopId Workshop ID. + * @param assessmentId Assessment ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when deleted. */ deleteAssessmentStoredFiles(workshopId: number, assessmentId: number, siteId?: string): Promise { return this.workshopOffline.getAssessmentFolder(workshopId, assessmentId, siteId).then((folderPath) => { @@ -319,11 +319,11 @@ export class AddonModWorkshopHelperProvider { * 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} workshopId Workshop ID. - * @param {number} assessmentId Assessment ID. - * @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 workshopId Workshop ID. + * @param assessmentId Assessment ID. + * @param files List of files. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if success, rejected otherwise. */ storeAssessmentFiles(workshopId: number, assessmentId: number, files: any[], siteId?: string): Promise { // Get the folder where to store the files. @@ -335,12 +335,12 @@ export class AddonModWorkshopHelperProvider { /** * Upload or store some files for an assessment, depending if the user is offline or not. * - * @param {number} workshopId Workshop ID. - * @param {number} assessmentId ID. - * @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 workshopId Workshop ID. + * @param assessmentId ID. + * @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. */ uploadOrStoreAssessmentFiles(workshopId: number, assessmentId: number, files: any[], offline: boolean, siteId?: string): Promise { @@ -354,10 +354,10 @@ export class AddonModWorkshopHelperProvider { /** * Get a list of stored attachment files for an assessment. See AddonModWorkshopHelperProvider#storeFiles. * - * @param {number} workshopId Workshop ID. - * @param {number} assessmentId Assessment ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the files. + * @param workshopId Workshop ID. + * @param assessmentId Assessment ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the files. */ getStoredAssessmentFiles(workshopId: number, assessmentId: number, siteId?: string): Promise { return this.workshopOffline.getAssessmentFolder(workshopId, assessmentId, siteId).then((folderPath) => { @@ -371,11 +371,11 @@ export class AddonModWorkshopHelperProvider { /** * Get a list of stored attachment files for an assessment and online files also. See AddonModWorkshopHelperProvider#storeFiles. * - * @param {object} filesObject Files object combining offline and online information. - * @param {number} workshopId Workshop ID. - * @param {number} assessmentId Assessment ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the files. + * @param filesObject Files object combining offline and online information. + * @param workshopId Workshop ID. + * @param assessmentId Assessment ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the files. */ getAssessmentFilesFromOfflineFilesObject(filesObject: any, workshopId: number, assessmentId: number, siteId?: string): Promise { @@ -387,9 +387,9 @@ export class AddonModWorkshopHelperProvider { /** * Returns the action of a given submission. * - * @param {any[]} actions Offline actions to be applied to the given submission. - * @param {number} submissionId ID of the submission to filter by or false. - * @return {any[]} Promise resolved with the files. + * @param actions Offline actions to be applied to the given submission. + * @param submissionId ID of the submission to filter by or false. + * @return Promise resolved with the files. */ filterSubmissionActions(actions: any[], submissionId: number): any[] { return actions.filter((action) => { @@ -404,9 +404,9 @@ export class AddonModWorkshopHelperProvider { /** * Applies offline data to submission. * - * @param {any} submission Submission object to be modified. - * @param {any[]} actions Offline actions to be applied to the given submission. - * @return {Promise} Promise resolved with the files. + * @param submission Submission object to be modified. + * @param actions Offline actions to be applied to the given submission. + * @return Promise resolved with the files. */ applyOfflineData(submission: any, actions: any[]): Promise { if (actions.length && !submission) { @@ -460,13 +460,13 @@ export class AddonModWorkshopHelperProvider { /** * Prepare assessment data to be sent to the server. * - * @param {any} workshop Workshop object. - * @param {any[]} selectedValues Assessment current values - * @param {string} feedbackText Feedback text. - * @param {any[]} feedbackFiles Feedback attachments. - * @param {any} form Assessment form original data. - * @param {number} attachmentsId The draft file area id for attachments. - * @return {Promise} Promise resolved with the data to be sent. Or rejected with the input errors object. + * @param workshop Workshop object. + * @param selectedValues Assessment current values + * @param feedbackText Feedback text. + * @param feedbackFiles Feedback attachments. + * @param form Assessment form original data. + * @param attachmentsId The draft file area id for attachments. + * @return Promise resolved with the data to be sent. Or rejected with the input errors object. */ prepareAssessmentData(workshop: any, selectedValues: any[], feedbackText: string, feedbackFiles: any[], form: any, attachmentsId: number): Promise { @@ -486,10 +486,10 @@ export class AddonModWorkshopHelperProvider { /** * Calculates the real value of a grade based on real_grade_value. * - * @param {number} value Percentual value from 0 to 100. - * @param {number} max The maximal grade. - * @param {number} decimals Decimals to show in the formatted grade. - * @return {string} Real grade formatted. + * @param value Percentual value from 0 to 100. + * @param max The maximal grade. + * @param decimals Decimals to show in the formatted grade. + * @return Real grade formatted. */ protected realGradeValueHelper(value: number, max: number, decimals: number): string { if (value == null) { @@ -506,9 +506,9 @@ export class AddonModWorkshopHelperProvider { /** * Calculates the real value of a grades of an assessment. * - * @param {any} workshop Workshop object. - * @param {any} assessment Assessment data. - * @return {any} Assessment with real grades. + * @param workshop Workshop object. + * @param assessment Assessment data. + * @return Assessment with real grades. */ realGradeValue(workshop: any, assessment: any): any { assessment.grade = this.realGradeValueHelper(assessment.grade, workshop.grade, workshop.gradedecimals); @@ -522,8 +522,8 @@ export class AddonModWorkshopHelperProvider { /** * Check grade should be shown * - * @param {any} grade Grade to be shown - * @return {boolean} If grade should be shown or not. + * @param grade Grade to be shown + * @return If grade should be shown or not. */ showGrade(grade: any): boolean { return typeof grade !== 'undefined' && grade !== null; diff --git a/src/addon/mod/workshop/providers/list-link-handler.ts b/src/addon/mod/workshop/providers/list-link-handler.ts index db2bfb9cc..3614949ec 100644 --- a/src/addon/mod/workshop/providers/list-link-handler.ts +++ b/src/addon/mod/workshop/providers/list-link-handler.ts @@ -33,7 +33,7 @@ export class AddonModWorkshopListLinkHandler extends CoreContentLinksModuleListH /** * Check if the handler is enabled on a site level. * - * @return {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(): Promise { return this.workshopProvider.isPluginEnabled(); diff --git a/src/addon/mod/workshop/providers/module-handler.ts b/src/addon/mod/workshop/providers/module-handler.ts index addfe132d..4b25139ec 100644 --- a/src/addon/mod/workshop/providers/module-handler.ts +++ b/src/addon/mod/workshop/providers/module-handler.ts @@ -44,7 +44,7 @@ export class AddonModWorkshopModuleHandler implements CoreCourseModuleHandler { /** * Check if the handler is enabled on a site level. * - * @return {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(): Promise { return this.workshopProvider.isPluginEnabled(); @@ -53,10 +53,10 @@ export class AddonModWorkshopModuleHandler 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 { @@ -78,9 +78,9 @@ export class AddonModWorkshopModuleHandler 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 AddonModWorkshopIndexComponent; diff --git a/src/addon/mod/workshop/providers/offline.ts b/src/addon/mod/workshop/providers/offline.ts index dcd7f1adc..734611ae9 100644 --- a/src/addon/mod/workshop/providers/offline.ts +++ b/src/addon/mod/workshop/providers/offline.ts @@ -179,8 +179,8 @@ export class AddonModWorkshopOfflineProvider { /** * Get all the workshops ids that have something to be synced. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with workshops id that have something to be synced. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with workshops id that have something to be synced. */ getAllWorkshops(siteId?: string): Promise { const promises = [ @@ -207,9 +207,9 @@ export class AddonModWorkshopOfflineProvider { /** * Check if there is an offline data to be synced. * - * @param {number} workshopId Workshop ID to remove. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean: true if has offline data, false otherwise. + * @param workshopId Workshop ID to remove. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: true if has offline data, false otherwise. */ hasWorkshopOfflineData(workshopId: number, siteId?: string): Promise { const promises = [ @@ -230,11 +230,11 @@ export class AddonModWorkshopOfflineProvider { /** * Delete workshop submission action. * - * @param {number} workshopId Workshop ID. - * @param {number} submissionId Submission ID. - * @param {string} action Action to be done. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if stored, rejected if failure. + * @param workshopId Workshop ID. + * @param submissionId Submission ID. + * @param action Action to be done. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if stored, rejected if failure. */ deleteSubmissionAction(workshopId: number, submissionId: number, action: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -251,10 +251,10 @@ export class AddonModWorkshopOfflineProvider { /** * Delete all workshop submission actions. * - * @param {number} workshopId Workshop ID. - * @param {number} submissionId Submission ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if stored, rejected if failure. + * @param workshopId Workshop ID. + * @param submissionId Submission ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if stored, rejected if failure. */ deleteAllSubmissionActions(workshopId: number, submissionId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -270,8 +270,8 @@ export class AddonModWorkshopOfflineProvider { /** * Get the all the submissions to be synced. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the objects to be synced. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the objects to be synced. */ getAllSubmissions(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -286,9 +286,9 @@ export class AddonModWorkshopOfflineProvider { /** * Get the submissions of a workshop to be synced. * - * @param {number} workshopId ID of the workshop. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the object to be synced. + * @param workshopId ID of the workshop. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the object to be synced. */ getSubmissions(workshopId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -307,10 +307,10 @@ export class AddonModWorkshopOfflineProvider { /** * Get all actions of a submission of a workshop to be synced. * - * @param {number} workshopId ID of the workshop. - * @param {number} submissionId ID of the submission. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the object to be synced. + * @param workshopId ID of the workshop. + * @param submissionId ID of the submission. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the object to be synced. */ getSubmissionActions(workshopId: number, submissionId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -330,11 +330,11 @@ export class AddonModWorkshopOfflineProvider { /** * Get an specific action of a submission of a workshop to be synced. * - * @param {number} workshopId ID of the workshop. - * @param {number} submissionId ID of the submission. - * @param {string} action Action to be done. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the object to be synced. + * @param workshopId ID of the workshop. + * @param submissionId ID of the submission. + * @param action Action to be done. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the object to be synced. */ getSubmissionAction(workshopId: number, submissionId: number, action: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -355,16 +355,16 @@ export class AddonModWorkshopOfflineProvider { /** * Offline version for adding a submission action to a workshop. * - * @param {number} workshopId Workshop ID. - * @param {number} courseId Course ID the workshop belongs to. - * @param {string} title The submission title. - * @param {string} content The submission text content. - * @param {any} attachmentsId Stored attachments. - * @param {number} submissionId Submission Id, if action is add, the time the submission was created. - * If set to 0, current time is used. - * @param {string} action Action to be done. ['add', 'update', 'delete'] - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when submission action is successfully saved. + * @param workshopId Workshop ID. + * @param courseId Course ID the workshop belongs to. + * @param title The submission title. + * @param content The submission text content. + * @param attachmentsId Stored attachments. + * @param submissionId Submission Id, if action is add, the time the submission was created. + * If set to 0, current time is used. + * @param action Action to be done. ['add', 'update', 'delete'] + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when submission action is successfully saved. */ saveSubmission(workshopId: number, courseId: number, title: string, content: string, attachmentsId: any, submissionId: number, action: string, siteId?: string): Promise { @@ -388,7 +388,7 @@ export class AddonModWorkshopOfflineProvider { /** * Parse "attachments" column of a submission record. * - * @param {any} record Submission record, modified in place. + * @param record Submission record, modified in place. */ protected parseSubmissionRecord(record: any): void { record.attachmentsid = this.textUtils.parseJSON(record.attachmentsid); @@ -397,10 +397,10 @@ export class AddonModWorkshopOfflineProvider { /** * Delete workshop assessment. * - * @param {number} workshopId Workshop ID. - * @param {number} assessmentId Assessment ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if stored, rejected if failure. + * @param workshopId Workshop ID. + * @param assessmentId Assessment ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if stored, rejected if failure. */ deleteAssessment(workshopId: number, assessmentId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -416,8 +416,8 @@ export class AddonModWorkshopOfflineProvider { /** * Get the all the assessments to be synced. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the objects to be synced. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the objects to be synced. */ getAllAssessments(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -432,9 +432,9 @@ export class AddonModWorkshopOfflineProvider { /** * Get the assessments of a workshop to be synced. * - * @param {number} workshopId ID of the workshop. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the object to be synced. + * @param workshopId ID of the workshop. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the object to be synced. */ getAssessments(workshopId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -453,10 +453,10 @@ export class AddonModWorkshopOfflineProvider { /** * Get an specific assessment of a workshop to be synced. * - * @param {number} workshopId ID of the workshop. - * @param {number} assessmentId Assessment ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the object to be synced. + * @param workshopId ID of the workshop. + * @param assessmentId Assessment ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the object to be synced. */ getAssessment(workshopId: number, assessmentId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -476,12 +476,12 @@ export class AddonModWorkshopOfflineProvider { /** * Offline version for adding an assessment to a workshop. * - * @param {number} workshopId Workshop ID. - * @param {number} assessmentId Assessment ID. - * @param {number} courseId Course ID the workshop belongs to. - * @param {any} inputData Assessment data. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when assessment is successfully saved. + * @param workshopId Workshop ID. + * @param assessmentId Assessment ID. + * @param courseId Course ID the workshop belongs to. + * @param inputData Assessment data. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when assessment is successfully saved. */ saveAssessment(workshopId: number, assessmentId: number, courseId: number, inputData: any, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -500,7 +500,7 @@ export class AddonModWorkshopOfflineProvider { /** * Parse "inpudata" column of an assessment record. * - * @param {any} record Assessnent record, modified in place. + * @param record Assessnent record, modified in place. */ protected parseAssessmentRecord(record: any): void { record.inputdata = this.textUtils.parseJSON(record.inputdata); @@ -509,10 +509,10 @@ export class AddonModWorkshopOfflineProvider { /** * Delete workshop evaluate submission. * - * @param {number} workshopId Workshop ID. - * @param {number} submissionId Submission ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if stored, rejected if failure. + * @param workshopId Workshop ID. + * @param submissionId Submission ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if stored, rejected if failure. */ deleteEvaluateSubmission(workshopId: number, submissionId: number, siteId?: string): Promise { const conditions = { @@ -528,8 +528,8 @@ export class AddonModWorkshopOfflineProvider { /** * Get the all the evaluate submissions to be synced. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the objects to be synced. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the objects to be synced. */ getAllEvaluateSubmissions(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -544,9 +544,9 @@ export class AddonModWorkshopOfflineProvider { /** * Get the evaluate submissions of a workshop to be synced. * - * @param {number} workshopId ID of the workshop. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the object to be synced. + * @param workshopId ID of the workshop. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the object to be synced. */ getEvaluateSubmissions(workshopId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -566,10 +566,10 @@ export class AddonModWorkshopOfflineProvider { /** * Get an specific evaluate submission of a workshop to be synced. * - * @param {number} workshopId ID of the workshop. - * @param {number} submissionId Submission ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the object to be synced. + * @param workshopId ID of the workshop. + * @param submissionId Submission ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the object to be synced. */ getEvaluateSubmission(workshopId: number, submissionId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -589,14 +589,14 @@ export class AddonModWorkshopOfflineProvider { /** * Offline version for evaluation a submission to a workshop. * - * @param {number} workshopId Workshop ID. - * @param {number} submissionId Submission ID. - * @param {number} courseId Course ID the workshop belongs to. - * @param {string} feedbackText The feedback for the author. - * @param {boolean} published Whether to publish the submission for other users. - * @param {any} gradeOver The new submission grade (empty for no overriding the grade). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when submission evaluation is successfully saved. + * @param workshopId Workshop ID. + * @param submissionId Submission ID. + * @param courseId Course ID the workshop belongs to. + * @param feedbackText The feedback for the author. + * @param published Whether to publish the submission for other users. + * @param gradeOver The new submission grade (empty for no overriding the grade). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when submission evaluation is successfully saved. */ saveEvaluateSubmission(workshopId: number, submissionId: number, courseId: number, feedbackText: string, published: boolean, gradeOver: any, siteId?: string): Promise { @@ -618,7 +618,7 @@ export class AddonModWorkshopOfflineProvider { /** * Parse "published" and "gradeover" columns of an evaluate submission record. * - * @param {any} record Evaluate submission record, modified in place. + * @param record Evaluate submission record, modified in place. */ protected parseEvaluateSubmissionRecord(record: any): void { record.published = Boolean(record.published); @@ -628,10 +628,10 @@ export class AddonModWorkshopOfflineProvider { /** * Delete workshop evaluate assessment. * - * @param {number} workshopId Workshop ID. - * @param {number} assessmentId Assessment ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if stored, rejected if failure. + * @param workshopId Workshop ID. + * @param assessmentId Assessment ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if stored, rejected if failure. */ deleteEvaluateAssessment(workshopId: number, assessmentId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -647,8 +647,8 @@ export class AddonModWorkshopOfflineProvider { /** * Get the all the evaluate assessments to be synced. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the objects to be synced. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the objects to be synced. */ getAllEvaluateAssessments(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -663,9 +663,9 @@ export class AddonModWorkshopOfflineProvider { /** * Get the evaluate assessments of a workshop to be synced. * - * @param {number} workshopId ID of the workshop. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the object to be synced. + * @param workshopId ID of the workshop. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the object to be synced. */ getEvaluateAssessments(workshopId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -685,10 +685,10 @@ export class AddonModWorkshopOfflineProvider { /** * Get an specific evaluate assessment of a workshop to be synced. * - * @param {number} workshopId ID of the workshop. - * @param {number} assessmentId Assessment ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the object to be synced. + * @param workshopId ID of the workshop. + * @param assessmentId Assessment ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the object to be synced. */ getEvaluateAssessment(workshopId: number, assessmentId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -708,14 +708,14 @@ export class AddonModWorkshopOfflineProvider { /** * Offline version for evaluating an assessment to a workshop. * - * @param {number} workshopId Workshop ID. - * @param {number} assessmentId Assessment ID. - * @param {number} courseId Course ID the workshop belongs to. - * @param {string} feedbackText The feedback for the reviewer. - * @param {number} weight The new weight for the assessment. - * @param {any} gradingGradeOver The new grading grade (empty for no overriding the grade). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when assessment evaluation is successfully saved. + * @param workshopId Workshop ID. + * @param assessmentId Assessment ID. + * @param courseId Course ID the workshop belongs to. + * @param feedbackText The feedback for the reviewer. + * @param weight The new weight for the assessment. + * @param gradingGradeOver The new grading grade (empty for no overriding the grade). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when assessment evaluation is successfully saved. */ saveEvaluateAssessment(workshopId: number, assessmentId: number, courseId: number, feedbackText: string, weight: number, gradingGradeOver: any, siteId?: string): Promise { @@ -737,7 +737,7 @@ export class AddonModWorkshopOfflineProvider { /** * Parse "gradinggradeover" column of an evaluate assessment record. * - * @param {any} record Evaluate assessment record, modified in place. + * @param record Evaluate assessment record, modified in place. */ protected parseEvaluateAssessmentRecord(record: any): void { record.gradinggradeover = this.textUtils.parseJSON(record.gradinggradeover); @@ -746,9 +746,9 @@ export class AddonModWorkshopOfflineProvider { /** * Get the path to the folder where to store files for offline attachments in a workshop. * - * @param {number} workshopId Workshop ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the path. + * @param workshopId Workshop ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the path. */ getWorkshopFolder(workshopId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -763,11 +763,11 @@ export class AddonModWorkshopOfflineProvider { /** * Get the path to the folder where to store files for offline submissions. * - * @param {number} workshopId Workshop ID. - * @param {number} submissionId If not editing, it will refer to timecreated. - * @param {boolean} editing If the submission is being edited or added otherwise. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the path. + * @param workshopId Workshop ID. + * @param submissionId If not editing, it will refer to timecreated. + * @param editing If the submission is being edited or added otherwise. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the path. */ getSubmissionFolder(workshopId: number, submissionId: number, editing: boolean, siteId?: string): Promise { return this.getWorkshopFolder(workshopId, siteId).then((folderPath) => { @@ -781,10 +781,10 @@ export class AddonModWorkshopOfflineProvider { /** * Get the path to the folder where to store files for offline assessment. * - * @param {number} workshopId Workshop ID. - * @param {number} assessmentId Assessment ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the path. + * @param workshopId Workshop ID. + * @param assessmentId Assessment ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the path. */ getAssessmentFolder(workshopId: number, assessmentId: number, siteId?: string): Promise { return this.getWorkshopFolder(workshopId, siteId).then((folderPath) => { diff --git a/src/addon/mod/workshop/providers/prefetch-handler.ts b/src/addon/mod/workshop/providers/prefetch-handler.ts index 49cc62055..460923c06 100644 --- a/src/addon/mod/workshop/providers/prefetch-handler.ts +++ b/src/addon/mod/workshop/providers/prefetch-handler.ts @@ -57,10 +57,10 @@ export class AddonModWorkshopPrefetchHandler extends CoreCourseActivityPrefetchH /** * 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.getWorkshopInfoHelper(module, courseId, true).then((info) => { @@ -71,13 +71,13 @@ export class AddonModWorkshopPrefetchHandler extends CoreCourseActivityPrefetchH /** * Helper function to get all workshop info just once. * - * @param {any} module Module to get the files. - * @param {number} courseId Course ID the module belongs to. - * @param {boolean} [omitFail=false] True to always return even if fails. Default false. - * @param {boolean} [forceCache=false] True to always get the value from cache, false otherwise. Default false. - * @param {boolean} [ignoreCache=false] 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 info fetched. + * @param module Module to get the files. + * @param courseId Course ID the module belongs to. + * @param omitFail True to always return even if fails. Default false. + * @param forceCache True to always get the value from cache, false otherwise. Default false. + * @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 info fetched. */ protected getWorkshopInfoHelper(module: any, courseId: number, omitFail: boolean = false, forceCache: boolean = false, ignoreCache: boolean = false, siteId?: string): Promise { @@ -174,9 +174,9 @@ export class AddonModWorkshopPrefetchHandler 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.workshopProvider.invalidateContent(moduleId, courseId); @@ -185,9 +185,9 @@ export class AddonModWorkshopPrefetchHandler extends CoreCourseActivityPrefetchH /** * 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 { return this.workshopProvider.getWorkshop(courseId, module.id, undefined, true).then((workshop) => { @@ -201,7 +201,7 @@ export class AddonModWorkshopPrefetchHandler extends CoreCourseActivityPrefetchH /** * 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.workshopProvider.isPluginEnabled(); @@ -210,11 +210,11 @@ export class AddonModWorkshopPrefetchHandler 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.prefetchWorkshop.bind(this)); @@ -223,10 +223,10 @@ export class AddonModWorkshopPrefetchHandler extends CoreCourseActivityPrefetchH /** * Retrieves all the grades reports for all the groups and then returns only unique grades. * - * @param {number} workshopId Workshop ID. - * @param {any[]} groups Array of groups in the activity. - * @param {string} siteId Site ID. If not defined, current site. - * @return {Promise} All unique entries. + * @param workshopId Workshop ID. + * @param groups Array of groups in the activity. + * @param siteId Site ID. If not defined, current site. + * @return All unique entries. */ protected getAllGradesReport(workshopId: number, groups: any[], siteId: string): Promise { const promises = []; @@ -254,11 +254,11 @@ export class AddonModWorkshopPrefetchHandler extends CoreCourseActivityPrefetchH /** * Prefetch a workshop. * - * @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 prefetchWorkshop(module: any, courseId: number, single: boolean, siteId: string): Promise { const userIds = []; @@ -379,10 +379,10 @@ export class AddonModWorkshopPrefetchHandler 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 { return this.syncProvider.syncWorkshop(module.instance, siteId); diff --git a/src/addon/mod/workshop/providers/sync-cron-handler.ts b/src/addon/mod/workshop/providers/sync-cron-handler.ts index 3202ff253..94886e298 100644 --- a/src/addon/mod/workshop/providers/sync-cron-handler.ts +++ b/src/addon/mod/workshop/providers/sync-cron-handler.ts @@ -29,9 +29,9 @@ export class AddonModWorkshopSyncCronHandler 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.workshopSync.syncAllWorkshops(siteId, force); @@ -40,7 +40,7 @@ export class AddonModWorkshopSyncCronHandler 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.workshopSync.syncInterval; diff --git a/src/addon/mod/workshop/providers/sync.ts b/src/addon/mod/workshop/providers/sync.ts index e30f6c387..6e3366205 100644 --- a/src/addon/mod/workshop/providers/sync.ts +++ b/src/addon/mod/workshop/providers/sync.ts @@ -64,9 +64,9 @@ export class AddonModWorkshopSyncProvider extends CoreSyncBaseProvider { /** * Check if an workshop has data to synchronize. * - * @param {number} workshopId Workshop ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean: true if has data to sync, false otherwise. + * @param workshopId Workshop ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: true if has data to sync, false otherwise. */ hasDataToSync(workshopId: number, siteId?: string): Promise { return this.workshopOffline.hasWorkshopOfflineData(workshopId, siteId); @@ -75,9 +75,9 @@ export class AddonModWorkshopSyncProvider extends CoreSyncBaseProvider { /** * Try to synchronize all workshops that need it and haven't been synchronized in a while. * - * @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 when the sync is done. + * @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 when the sync is done. */ syncAllWorkshops(siteId?: string, force?: boolean): Promise { return this.syncOnSites('all workshops', this.syncAllWorkshopsFunc.bind(this), [force], siteId); @@ -86,9 +86,9 @@ export class AddonModWorkshopSyncProvider extends CoreSyncBaseProvider { /** * Sync all workshops 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 syncAllWorkshopsFunc(siteId: string, force?: boolean): Promise { return this.workshopOffline.getAllWorkshops(siteId).then((workshopIds) => { @@ -114,9 +114,9 @@ export class AddonModWorkshopSyncProvider extends CoreSyncBaseProvider { /** * Sync a workshop only if a certain time has passed since the last time. * - * @param {number} workshopId Workshop ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the workshop is synced or if it doesn't need to be synced. + * @param workshopId Workshop ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the workshop is synced or if it doesn't need to be synced. */ syncWorkshopIfNeeded(workshopId: number, siteId?: string): Promise { return this.isSyncNeeded(workshopId, siteId).then((needed) => { @@ -129,9 +129,9 @@ export class AddonModWorkshopSyncProvider extends CoreSyncBaseProvider { /** * Try to synchronize a workshop. * - * @param {number} workshopId Workshop ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if sync is successful, rejected otherwise. + * @param workshopId Workshop ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if sync is successful, rejected otherwise. */ syncWorkshop(workshopId: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -269,11 +269,11 @@ export class AddonModWorkshopSyncProvider extends CoreSyncBaseProvider { /** * Synchronize a submission. * - * @param {any} workshop Workshop. - * @param {any[]} submissionActions Submission actions offline data. - * @param {any} result Object with the result of the sync. - * @param {string} siteId Site ID. - * @return {Promise} Promise resolved if success, rejected otherwise. + * @param workshop Workshop. + * @param submissionActions Submission actions offline data. + * @param result Object with the result of the sync. + * @param siteId Site ID. + * @return Promise resolved if success, rejected otherwise. */ protected syncSubmission(workshop: any, submissionActions: any, result: any, siteId: string): Promise { let discardError; @@ -393,11 +393,11 @@ export class AddonModWorkshopSyncProvider extends CoreSyncBaseProvider { /** * Synchronize an assessment. * - * @param {any} workshop Workshop. - * @param {any} assessment Assessment offline data. - * @param {any} result Object with the result of the sync. - * @param {string} siteId Site ID. - * @return {Promise} Promise resolved if success, rejected otherwise. + * @param workshop Workshop. + * @param assessment Assessment offline data. + * @param result Object with the result of the sync. + * @param siteId Site ID. + * @return Promise resolved if success, rejected otherwise. */ protected syncAssessment(workshop: any, assessmentData: any, result: any, siteId: string): Promise { let discardError; @@ -471,11 +471,11 @@ export class AddonModWorkshopSyncProvider extends CoreSyncBaseProvider { /** * Synchronize a submission evaluation. * - * @param {any} workshop Workshop. - * @param {any} evaluate Submission evaluation offline data. - * @param {any} result Object with the result of the sync. - * @param {string} siteId Site ID. - * @return {Promise} Promise resolved if success, rejected otherwise. + * @param workshop Workshop. + * @param evaluate Submission evaluation offline data. + * @param result Object with the result of the sync. + * @param siteId Site ID. + * @return Promise resolved if success, rejected otherwise. */ protected syncEvaluateSubmission(workshop: any, evaluate: any, result: any, siteId: string): Promise { let discardError; @@ -530,11 +530,11 @@ export class AddonModWorkshopSyncProvider extends CoreSyncBaseProvider { /** * Synchronize a assessment evaluation. * - * @param {any} workshop Workshop. - * @param {any} evaluate Assessment evaluation offline data. - * @param {any} result Object with the result of the sync. - * @param {string} siteId Site ID. - * @return {Promise} Promise resolved if success, rejected otherwise. + * @param workshop Workshop. + * @param evaluate Assessment evaluation offline data. + * @param result Object with the result of the sync. + * @param siteId Site ID. + * @return Promise resolved if success, rejected otherwise. */ protected syncEvaluateAssessment(workshop: any, evaluate: any, result: any, siteId: string): Promise { let discardError; diff --git a/src/addon/mod/workshop/providers/workshop.ts b/src/addon/mod/workshop/providers/workshop.ts index bae7854f0..b356698ce 100644 --- a/src/addon/mod/workshop/providers/workshop.ts +++ b/src/addon/mod/workshop/providers/workshop.ts @@ -57,8 +57,8 @@ export class AddonModWorkshopProvider { /** * Get cache key for workshop data WS calls. * - * @param {number} courseId Course ID. - * @return {string} Cache key. + * @param courseId Course ID. + * @return Cache key. */ protected getWorkshopDataCacheKey(courseId: number): string { return this.ROOT_CACHE_KEY + 'workshop:' + courseId; @@ -67,8 +67,8 @@ export class AddonModWorkshopProvider { /** * Get prefix cache key for all workshop activity data WS calls. * - * @param {number} workshopId Workshop ID. - * @return {string} Cache key. + * @param workshopId Workshop ID. + * @return Cache key. */ protected getWorkshopDataPrefixCacheKey(workshopId: number): string { return this.ROOT_CACHE_KEY + workshopId; @@ -77,8 +77,8 @@ export class AddonModWorkshopProvider { /** * Get cache key for workshop access information data WS calls. * - * @param {number} workshopId Workshop ID. - * @return {string} Cache key. + * @param workshopId Workshop ID. + * @return Cache key. */ protected getWorkshopAccessInformationDataCacheKey(workshopId: number): string { return this.getWorkshopDataPrefixCacheKey(workshopId) + ':access'; @@ -87,8 +87,8 @@ export class AddonModWorkshopProvider { /** * Get cache key for workshop user plan data WS calls. * - * @param {number} workshopId Workshop ID. - * @return {string} Cache key. + * @param workshopId Workshop ID. + * @return Cache key. */ protected getUserPlanDataCacheKey(workshopId: number): string { return this.getWorkshopDataPrefixCacheKey(workshopId) + ':userplan'; @@ -97,10 +97,10 @@ export class AddonModWorkshopProvider { /** * Get cache key for workshop submissions data WS calls. * - * @param {number} workshopId Workshop ID. - * @param {number} [userId=0] User ID. - * @param {number} [groupId=0] Group ID. - * @return {string} Cache key. + * @param workshopId Workshop ID. + * @param userId User ID. + * @param groupId Group ID. + * @return Cache key. */ protected getSubmissionsDataCacheKey(workshopId: number, userId: number = 0, groupId: number = 0): string { return this.getWorkshopDataPrefixCacheKey(workshopId) + ':submissions:' + userId + ':' + groupId; @@ -109,9 +109,9 @@ export class AddonModWorkshopProvider { /** * Get cache key for a workshop submission data WS calls. * - * @param {number} workshopId Workshop ID. - * @param {number} submissionId Submission ID. - * @return {string} Cache key. + * @param workshopId Workshop ID. + * @param submissionId Submission ID. + * @return Cache key. */ protected getSubmissionDataCacheKey(workshopId: number, submissionId: number): string { return this.getWorkshopDataPrefixCacheKey(workshopId) + ':submission:' + submissionId; @@ -120,8 +120,8 @@ export class AddonModWorkshopProvider { /** * Get cache key for workshop grades data WS calls. * - * @param {number} workshopId Workshop ID. - * @return {string} Cache key. + * @param workshopId Workshop ID. + * @return Cache key. */ protected getGradesDataCacheKey(workshopId: number): string { return this.getWorkshopDataPrefixCacheKey(workshopId) + ':grades'; @@ -130,9 +130,9 @@ export class AddonModWorkshopProvider { /** * Get cache key for workshop grade report data WS calls. * - * @param {number} workshopId Workshop ID. - * @param {number} [groupId=0] Group ID. - * @return {string} Cache key. + * @param workshopId Workshop ID. + * @param groupId Group ID. + * @return Cache key. */ protected getGradesReportDataCacheKey(workshopId: number, groupId: number = 0): string { return this.getWorkshopDataPrefixCacheKey(workshopId) + ':report:' + groupId; @@ -141,9 +141,9 @@ export class AddonModWorkshopProvider { /** * Get cache key for workshop submission assessments data WS calls. * - * @param {number} workshopId Workshop ID. - * @param {number} submissionId Submission ID. - * @return {string} Cache key. + * @param workshopId Workshop ID. + * @param submissionId Submission ID. + * @return Cache key. */ protected getSubmissionAssessmentsDataCacheKey(workshopId: number, submissionId: number): string { return this.getWorkshopDataPrefixCacheKey(workshopId) + ':assessments:' + submissionId; @@ -152,9 +152,9 @@ export class AddonModWorkshopProvider { /** * Get cache key for workshop reviewer assessments data WS calls. * - * @param {number} workshopId Workshop ID. - * @param {number} [userId=0] User ID or current user. - * @return {string} Cache key. + * @param workshopId Workshop ID. + * @param userId User ID or current user. + * @return Cache key. */ protected getReviewerAssessmentsDataCacheKey(workshopId: number, userId: number = 0): string { return this.getWorkshopDataPrefixCacheKey(workshopId) + ':reviewerassessments:' + userId; @@ -163,9 +163,9 @@ export class AddonModWorkshopProvider { /** * Get cache key for a workshop assessment data WS calls. * - * @param {number} workshopId Workshop ID. - * @param {number} assessmentId Assessment ID. - * @return {string} Cache key. + * @param workshopId Workshop ID. + * @param assessmentId Assessment ID. + * @return Cache key. */ protected getAssessmentDataCacheKey(workshopId: number, assessmentId: number): string { return this.getWorkshopDataPrefixCacheKey(workshopId) + ':assessment:' + assessmentId; @@ -174,10 +174,10 @@ export class AddonModWorkshopProvider { /** * Get cache key for workshop assessment form data WS calls. * - * @param {number} workshopId Workshop ID. - * @param {number} assessmentId Assessment ID. - * @param {string} [mode='assessment'] Mode assessment (default) or preview. - * @return {string} Cache key. + * @param workshopId Workshop ID. + * @param assessmentId Assessment ID. + * @param mode Mode assessment (default) or preview. + * @return Cache key. */ protected getAssessmentFormDataCacheKey(workshopId: number, assessmentId: number, mode: string = 'assessment'): string { return this.getWorkshopDataPrefixCacheKey(workshopId) + ':assessmentsform:' + assessmentId + ':' + mode; @@ -186,8 +186,8 @@ export class AddonModWorkshopProvider { /** * Return whether or not the plugin is enabled in a certain site. Plugin is enabled if the workshop WS are available. * - * @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) => { @@ -199,12 +199,12 @@ export class AddonModWorkshopProvider { /** * Get a workshop 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. - * @param {boolean} [forceCache=false] True to always get the value from cache, false otherwise. Default false. - * @return {Promise} Promise resolved when the workshop 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. + * @param forceCache True to always get the value from cache, false otherwise. Default false. + * @return Promise resolved when the workshop is retrieved. */ protected getWorkshopByKey(courseId: number, key: string, value: any, siteId?: string, forceCache: boolean = false): Promise { @@ -250,11 +250,11 @@ export class AddonModWorkshopProvider { /** * Get a workshop 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. - * @param {boolean} [forceCache=false] True to always get the value from cache, false otherwise. Default false. - * @return {Promise} Promise resolved when the workshop is retrieved. + * @param courseId Course ID. + * @param cmId Course module ID. + * @param siteId Site ID. If not defined, current site. + * @param forceCache True to always get the value from cache, false otherwise. Default false. + * @return Promise resolved when the workshop is retrieved. */ getWorkshop(courseId: number, cmId: number, siteId?: string, forceCache: boolean = false): Promise { return this.getWorkshopByKey(courseId, 'coursemodule', cmId, siteId, forceCache); @@ -263,11 +263,11 @@ export class AddonModWorkshopProvider { /** * Get a workshop by ID. * - * @param {number} courseId Course ID. - * @param {number} id Workshop ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {boolean} [forceCache=false] True to always get the value from cache, false otherwise. Default false. - * @return {Promise} Promise resolved when the workshop is retrieved. + * @param courseId Course ID. + * @param id Workshop ID. + * @param siteId Site ID. If not defined, current site. + * @param forceCache True to always get the value from cache, false otherwise. Default false. + * @return Promise resolved when the workshop is retrieved. */ getWorkshopById(courseId: number, id: number, siteId?: string, forceCache: boolean = false): Promise { return this.getWorkshopByKey(courseId, 'id', id, siteId, forceCache); @@ -276,9 +276,9 @@ export class AddonModWorkshopProvider { /** * Invalidates workshop data. * - * @param {number} courseId Course ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the workshop is invalidated. + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the workshop is invalidated. */ invalidateWorkshopData(courseId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -289,9 +289,9 @@ export class AddonModWorkshopProvider { /** * Invalidates workshop data except files and module info. * - * @param {number} workshopId Workshop ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the workshop is invalidated. + * @param workshopId Workshop ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the workshop is invalidated. */ invalidateWorkshopWSData(workshopId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -302,11 +302,11 @@ export class AddonModWorkshopProvider { /** * Get access information for a given workshop. * - * @param {number} workshopId Workshop ID. - * @param {boolean} [offline=false] True if it should return cached data. Has priority over ignoreCache. - * @param {boolean} [ignoreCache=false] 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 workshop is retrieved. + * @param workshopId Workshop ID. + * @param offline 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, current site. + * @return Promise resolved when the workshop is retrieved. */ getWorkshopAccessInformation(workshopId: number, offline: boolean = false, ignoreCache: boolean = false, siteId?: string): Promise { @@ -332,9 +332,9 @@ export class AddonModWorkshopProvider { /** * Invalidates workshop access information data. * - * @param {number} workshopId Workshop ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param workshopId Workshop ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateWorkshopAccessInformationData(workshopId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -345,11 +345,11 @@ export class AddonModWorkshopProvider { /** * Return the planner information for the given user. * - * @param {number} workshopId Workshop ID. - * @param {boolean} [offline=false] True if it should return cached data. Has priority over ignoreCache. - * @param {boolean} [ignoreCache=false] 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 workshop data is retrieved. + * @param workshopId Workshop ID. + * @param offline 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, current site. + * @return Promise resolved when the workshop data is retrieved. */ getUserPlanPhases(workshopId: number, offline: boolean = false, ignoreCache: boolean = false, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -381,9 +381,9 @@ export class AddonModWorkshopProvider { /** * Invalidates workshop user plan data. * - * @param {number} workshopId Workshop ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param workshopId Workshop ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateUserPlanPhasesData(workshopId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -394,13 +394,13 @@ export class AddonModWorkshopProvider { /** * Retrieves all the workshop submissions visible by the current user or the one done by the given user. * - * @param {number} workshopId Workshop ID. - * @param {number} [userId=0] User ID, 0 means the current user. - * @param {number} [groupId=0] Group id, 0 means that the function will determine the user group. - * @param {boolean} [offline=false] True if it should return cached data. Has priority over ignoreCache. - * @param {boolean} [ignoreCache=false] 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 workshop submissions are retrieved. + * @param workshopId Workshop ID. + * @param userId User ID, 0 means the current user. + * @param groupId Group id, 0 means that the function will determine the user group. + * @param offline 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, current site. + * @return Promise resolved when the workshop submissions are retrieved. */ getSubmissions(workshopId: number, userId: number = 0, groupId: number = 0, offline: boolean = false, ignoreCache: boolean = false, siteId?: string): Promise { @@ -435,11 +435,11 @@ export class AddonModWorkshopProvider { /** * Invalidates workshop submissions data. * - * @param {number} workshopId Workshop ID. - * @param {number} [userId=0] User ID. - * @param {number} [groupId=0] Group ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param workshopId Workshop ID. + * @param userId User ID. + * @param groupId Group ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateSubmissionsData(workshopId: number, userId: number = 0, groupId: number = 0, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -450,10 +450,10 @@ export class AddonModWorkshopProvider { /** * Retrieves the given submission. * - * @param {number} workshopId Workshop ID. - * @param {number} submissionId Submission ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the workshop submission data is retrieved. + * @param workshopId Workshop ID. + * @param submissionId Submission ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the workshop submission data is retrieved. */ getSubmission(workshopId: number, submissionId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -477,10 +477,10 @@ export class AddonModWorkshopProvider { /** * Invalidates workshop submission data. * - * @param {number} workshopId Workshop ID. - * @param {number} submissionId Submission ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param workshopId Workshop ID. + * @param submissionId Submission ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateSubmissionData(workshopId: number, submissionId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -491,9 +491,9 @@ export class AddonModWorkshopProvider { /** * Returns the grades information for the given workshop and user. * - * @param {number} workshopId Workshop ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the workshop grades data is retrieved. + * @param workshopId Workshop ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the workshop grades data is retrieved. */ getGrades(workshopId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -511,9 +511,9 @@ export class AddonModWorkshopProvider { /** * Invalidates workshop grades data. * - * @param {number} workshopId Workshop ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param workshopId Workshop ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateGradesData(workshopId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -524,14 +524,14 @@ export class AddonModWorkshopProvider { /** * Retrieves the assessment grades report. * - * @param {number} workshopId Workshop ID. - * @param {number} [groupId] Group id, 0 means that the function will determine the user group. - * @param {number} [page=0] Page of records to return. Default 0. - * @param {number} [perPage=0] Records per page to return. Default AddonModWorkshopProvider.PER_PAGE. - * @param {boolean} [offline=false] True if it should return cached data. Has priority over ignoreCache. - * @param {boolean} [ignoreCache=false] 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 workshop data is retrieved. + * @param workshopId Workshop ID. + * @param groupId Group id, 0 means that the function will determine the user group. + * @param page Page of records to return. Default 0. + * @param perPage Records per page to return. Default AddonModWorkshopProvider.PER_PAGE. + * @param offline 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, current site. + * @return Promise resolved when the workshop data is retrieved. */ getGradesReport(workshopId: number, groupId: number = 0, page: number = 0, perPage: number = 0, offline: boolean = false, ignoreCache: boolean = false, siteId?: string): Promise { @@ -567,14 +567,14 @@ export class AddonModWorkshopProvider { /** * Performs the whole fetch of the grade reports in the workshop. * - * @param {number} workshopId Workshop ID. - * @param {number} [groupId=0] Group ID. - * @param {number} [perPage=0] Records per page to fetch. It has to match with the prefetch. - * Default on AddonModWorkshopProvider.PER_PAGE. - * @param {boolean} [forceCache=false] True to always get the value from cache, false otherwise. Default false. - * @param {boolean} [ignoreCache=false] 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 done. + * @param workshopId Workshop ID. + * @param groupId Group ID. + * @param perPage Records per page to fetch. It has to match with the prefetch. + * Default on AddonModWorkshopProvider.PER_PAGE. + * @param forceCache True to always get the value from cache, false otherwise. Default false. + * @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 done. */ fetchAllGradeReports(workshopId: number, groupId: number = 0, perPage: number = 0, forceCache: boolean = false, ignoreCache: boolean = false, siteId?: string): Promise { @@ -587,15 +587,15 @@ export class AddonModWorkshopProvider { /** * Recursive call on fetch all grade reports. * - * @param {number} workshopId Workshop ID. - * @param {number} groupId Group ID. - * @param {number} perPage Records per page to fetch. It has to match with the prefetch. - * @param {boolean} forceCache True to always get the value from cache, false otherwise. Default false. - * @param {boolean} ignoreCache True if it should ignore cached data (it will always fail in offline or server down). - * @param {any[]} grades Grades already fetched (just to concatenate them). - * @param {number} page Page of records to return. - * @param {string} siteId Site ID. - * @return {Promise} Promise resolved when done. + * @param workshopId Workshop ID. + * @param groupId Group ID. + * @param perPage Records per page to fetch. It has to match with the prefetch. + * @param forceCache True to always get the value from cache, false otherwise. Default false. + * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). + * @param grades Grades already fetched (just to concatenate them). + * @param page Page of records to return. + * @param siteId Site ID. + * @return Promise resolved when done. */ protected fetchGradeReportsRecursive(workshopId: number, groupId: number, perPage: number, forceCache: boolean, ignoreCache: boolean, grades: any[], page: number, siteId: string): Promise { @@ -615,10 +615,10 @@ export class AddonModWorkshopProvider { /** * Invalidates workshop grade report data. * - * @param {number} workshopId Workshop ID. - * @param {number} [groupId=0] Group ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param workshopId Workshop ID. + * @param groupId Group ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateGradeReportData(workshopId: number, groupId: number = 0, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -629,12 +629,12 @@ export class AddonModWorkshopProvider { /** * Retrieves the given submission assessment. * - * @param {number} workshopId Workshop ID. - * @param {number} submissionId Submission ID. - * @param {boolean} [offline=false] True if it should return cached data. Has priority over ignoreCache. - * @param {boolean} [ignoreCache=false] 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 workshop data is retrieved. + * @param workshopId Workshop ID. + * @param submissionId Submission ID. + * @param offline 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, current site. + * @return Promise resolved when the workshop data is retrieved. */ getSubmissionAssessments(workshopId: number, submissionId: number, offline: boolean = false, ignoreCache: boolean = false, siteId?: string): Promise { @@ -666,10 +666,10 @@ export class AddonModWorkshopProvider { /** * Invalidates workshop submission assessments data. * - * @param {number} workshopId Workshop ID. - * @param {number} submissionId Submission ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param workshopId Workshop ID. + * @param submissionId Submission ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateSubmissionAssesmentsData(workshopId: number, submissionId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -680,15 +680,15 @@ export class AddonModWorkshopProvider { /** * Add a new submission to a given workshop. * - * @param {number} workshopId Workshop ID. - * @param {number} courseId Course ID the workshop belongs to. - * @param {string} title The submission title. - * @param {string} content The submission text content. - * @param {number} [attachmentsId] The draft file area id for attachments. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [timecreated] The time the submission was created. Only used when editing an offline discussion. - * @param {boolean} [allowOffline=false] True if it can be stored in offline, false otherwise. - * @return {Promise} Promise resolved with submission ID if sent online or false if stored offline. + * @param workshopId Workshop ID. + * @param courseId Course ID the workshop belongs to. + * @param title The submission title. + * @param content The submission text content. + * @param attachmentsId The draft file area id for attachments. + * @param siteId Site ID. If not defined, current site. + * @param timecreated The time the submission was created. Only used when editing an offline discussion. + * @param allowOffline True if it can be stored in offline, false otherwise. + * @return Promise resolved with submission ID if sent online or false if stored offline. */ addSubmission(workshopId: number, courseId: number, title: string, content: string, attachmentsId?: number, siteId?: string, timecreated?: number, allowOffline: boolean = false): Promise { @@ -727,12 +727,12 @@ export class AddonModWorkshopProvider { /** * Add a new submission to a given workshop. It will fail if offline or cannot connect. * - * @param {number} workshopId Workshop ID. - * @param {string} title The submission title. - * @param {string} content The submission text content. - * @param {number} [attachmentsId] The draft file area id for attachments. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the submission is created. + * @param workshopId Workshop ID. + * @param title The submission title. + * @param content The submission text content. + * @param attachmentsId The draft file area id for attachments. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the submission is created. */ addSubmissionOnline(workshopId: number, title: string, content: string, attachmentsId: number, siteId?: string): Promise { @@ -758,15 +758,15 @@ export class AddonModWorkshopProvider { /** * Updates the given submission. * - * @param {number} workshopId Workshop ID. - * @param {number} submissionId Submission ID. - * @param {number} courseId Course ID the workshop belongs to. - * @param {string} title The submission title. - * @param {string} content The submission text content. - * @param {number} [attachmentsId] The draft file area id for attachments. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {boolean} [allowOffline=false] True if it can be stored in offline, false otherwise. - * @return {Promise} Promise resolved with submission ID if sent online or false if stored offline. + * @param workshopId Workshop ID. + * @param submissionId Submission ID. + * @param courseId Course ID the workshop belongs to. + * @param title The submission title. + * @param content The submission text content. + * @param attachmentsId The draft file area id for 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 submission ID if sent online or false if stored offline. */ updateSubmission(workshopId: number, submissionId: number, courseId: number, title: string, content: string, attachmentsId?: number, siteId?: string, allowOffline: boolean = false): Promise { @@ -802,12 +802,12 @@ export class AddonModWorkshopProvider { /** * Updates the given submission. It will fail if offline or cannot connect. * - * @param {number} submissionId Submission ID. - * @param {string} title The submission title. - * @param {string} content The submission text content. - * @param {number} [attachmentsId] The draft file area id for attachments. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the submission is updated. + * @param submissionId Submission ID. + * @param title The submission title. + * @param content The submission text content. + * @param attachmentsId The draft file area id for attachments. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the submission is updated. */ updateSubmissionOnline(submissionId: number, title: string, content: string, attachmentsId?: number, siteId?: string): Promise { @@ -834,11 +834,11 @@ export class AddonModWorkshopProvider { /** * Deletes the given submission. * - * @param {number} workshopId Workshop ID. - * @param {number} submissionId Submission ID. - * @param {number} courseId Course ID the workshop belongs to. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with submission ID if sent online, resolved with false if stored offline. + * @param workshopId Workshop ID. + * @param submissionId Submission ID. + * @param courseId Course ID the workshop belongs to. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with submission ID if sent online, resolved with false if stored offline. */ deleteSubmission(workshopId: number, submissionId: number, courseId: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -872,9 +872,9 @@ export class AddonModWorkshopProvider { /** * Deletes the given submission. It will fail if offline or cannot connect. * - * @param {number} submissionId Submission ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the submission is deleted. + * @param submissionId Submission ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the submission is deleted. */ deleteSubmissionOnline(submissionId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -897,12 +897,12 @@ export class AddonModWorkshopProvider { /** * Retrieves all the assessments reviewed by the given user. * - * @param {number} workshopId Workshop ID. - * @param {number} [userId] User ID. If not defined, current user. - * @param {boolean} [offline=false] True if it should return cached data. Has priority over ignoreCache. - * @param {boolean} [ignoreCache=false] 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 workshop data is retrieved. + * @param workshopId Workshop ID. + * @param userId User ID. If not defined, current user. + * @param offline 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, current site. + * @return Promise resolved when the workshop data is retrieved. */ getReviewerAssessments(workshopId: number, userId?: number, offline: boolean = false, ignoreCache: boolean = false, siteId?: string): Promise { @@ -938,10 +938,10 @@ export class AddonModWorkshopProvider { /** * Invalidates workshop user assessments data. * - * @param {number} workshopId Workshop ID. - * @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 data is invalidated. + * @param workshopId Workshop ID. + * @param userId User ID. If not defined, current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateReviewerAssesmentsData(workshopId: number, userId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -952,10 +952,10 @@ export class AddonModWorkshopProvider { /** * Retrieves the given assessment. * - * @param {number} workshopId Workshop ID. - * @param {number} assessmentId Assessment ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the workshop data is retrieved. + * @param workshopId Workshop ID. + * @param assessmentId Assessment ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the workshop data is retrieved. */ getAssessment(workshopId: number, assessmentId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -979,10 +979,10 @@ export class AddonModWorkshopProvider { /** * Invalidates workshop assessment data. * - * @param {number} workshopId Workshop ID. - * @param {number} assessmentId Assessment ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param workshopId Workshop ID. + * @param assessmentId Assessment ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateAssessmentData(workshopId: number, assessmentId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -993,13 +993,13 @@ export class AddonModWorkshopProvider { /** * Retrieves the assessment form definition (data required to be able to display the assessment form). * - * @param {number} workshopId Workshop ID. - * @param {number} assessmentId Assessment ID. - * @param {string} [mode='assessment'] Mode assessment (default) or preview. - * @param {boolean} [offline=false] True if it should return cached data. Has priority over ignoreCache. - * @param {boolean} [ignoreCache=false] 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 workshop data is retrieved. + * @param workshopId Workshop ID. + * @param assessmentId Assessment ID. + * @param mode Mode assessment (default) or preview. + * @param offline 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, current site. + * @return Promise resolved when the workshop data is retrieved. */ getAssessmentForm(workshopId: number, assessmentId: number, mode: string = 'assessment', offline: boolean = false, ignoreCache: boolean = false, siteId?: string): Promise { @@ -1037,8 +1037,8 @@ export class AddonModWorkshopProvider { /** * Parse fieldes into a more handful format. * - * @param {any[]} fields Fields to parse - * @return {any[]} Parsed fields + * @param fields Fields to parse + * @return Parsed fields */ parseFields(fields: any[]): any[] { const parsedFields = []; @@ -1078,11 +1078,11 @@ export class AddonModWorkshopProvider { /** * Invalidates workshop assessments form data. * - * @param {number} workshopId Workshop ID. - * @param {number} assessmentId Assessment ID. - * @param {string} [mode='assessment'] Mode assessment (default) or preview. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param workshopId Workshop ID. + * @param assessmentId Assessment ID. + * @param mode Mode assessment (default) or preview. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateAssessmentFormData(workshopId: number, assessmentId: number, mode: string = 'assessment', siteId?: string): Promise { @@ -1094,14 +1094,14 @@ export class AddonModWorkshopProvider { /** * Updates the given assessment. * - * @param {number} workshopId Workshop ID. - * @param {number} assessmentId Assessment ID. - * @param {number} courseId Course ID the workshop belongs to. - * @param {any} inputData Assessment data. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {boolean} [allowOffline=false] True if it can be stored in offline, false otherwise. - * @return {Promise} Promise resolved with the grade of the submission if sent online, - * resolved with false if stored offline. + * @param workshopId Workshop ID. + * @param assessmentId Assessment ID. + * @param courseId Course ID the workshop belongs to. + * @param inputData Assessment data. + * @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 the grade of the submission if sent online, + * resolved with false if stored offline. */ updateAssessment(workshopId: number, assessmentId: number, courseId: number, inputData: any, siteId?: any, allowOffline: boolean = false): Promise { @@ -1136,10 +1136,10 @@ export class AddonModWorkshopProvider { /** * Updates the given assessment. It will fail if offline or cannot connect. * - * @param {number} assessmentId Assessment ID. - * @param {any} inputData Assessment data. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the grade of the submission. + * @param assessmentId Assessment ID. + * @param inputData Assessment data. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the grade of the submission. */ updateAssessmentOnline(assessmentId: number, inputData: any, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1163,15 +1163,15 @@ export class AddonModWorkshopProvider { /** * Evaluates a submission (used by teachers for provide feedback or override the submission grade). * - * @param {number} workshopId Workshop ID. - * @param {number} submissionId The submission id. - * @param {number} courseId Course ID the workshop belongs to. - * @param {string} feedbackText The feedback for the author. - * @param {boolean} published Whether to publish the submission for other users. - * @param {any} gradeOver The new submission grade (empty for no overriding the grade). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when submission is evaluated if sent online, - * resolved with false if stored offline. + * @param workshopId Workshop ID. + * @param submissionId The submission id. + * @param courseId Course ID the workshop belongs to. + * @param feedbackText The feedback for the author. + * @param published Whether to publish the submission for other users. + * @param gradeOver The new submission grade (empty for no overriding the grade). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when submission is evaluated if sent online, + * resolved with false if stored offline. */ evaluateSubmission(workshopId: number, submissionId: number, courseId: number, feedbackText: string, published: boolean, gradeOver: any, siteId?: string): Promise { @@ -1208,12 +1208,12 @@ export class AddonModWorkshopProvider { * Evaluates a submission (used by teachers for provide feedback or override the submission grade). * It will fail if offline or cannot connect. * - * @param {number} submissionId The submission id. - * @param {string} feedbackText The feedback for the author. - * @param {boolean} published Whether to publish the submission for other users. - * @param {any} gradeOver The new submission grade (empty for no overriding the grade). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the submission is evaluated. + * @param submissionId The submission id. + * @param feedbackText The feedback for the author. + * @param published Whether to publish the submission for other users. + * @param gradeOver The new submission grade (empty for no overriding the grade). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the submission is evaluated. */ evaluateSubmissionOnline(submissionId: number, feedbackText: string, published: boolean, gradeOver: any, siteId?: string): Promise { @@ -1241,15 +1241,15 @@ export class AddonModWorkshopProvider { /** * Evaluates an assessment (used by teachers for provide feedback to the reviewer). * - * @param {number} workshopId Workshop ID. - * @param {number} assessmentId The assessment id. - * @param {number} courseId Course ID the workshop belongs to. - * @param {string} feedbackText The feedback for the reviewer. - * @param {boolean} weight The new weight for the assessment. - * @param {any} gradingGradeOver The new grading grade (empty for no overriding the grade). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when assessment is evaluated if sent online, - * resolved with false if stored offline. + * @param workshopId Workshop ID. + * @param assessmentId The assessment id. + * @param courseId Course ID the workshop belongs to. + * @param feedbackText The feedback for the reviewer. + * @param weight The new weight for the assessment. + * @param gradingGradeOver The new grading grade (empty for no overriding the grade). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when assessment is evaluated if sent online, + * resolved with false if stored offline. */ evaluateAssessment(workshopId: number, assessmentId: number, courseId: number, feedbackText: string, weight: number, gradingGradeOver: any, siteId?: string): Promise { @@ -1285,12 +1285,12 @@ export class AddonModWorkshopProvider { /** * Evaluates an assessment (used by teachers for provide feedback to the reviewer). It will fail if offline or cannot connect. * - * @param {number} assessmentId The assessment id. - * @param {string} feedbackText The feedback for the reviewer. - * @param {number} weight The new weight for the assessment. - * @param {any} gradingGradeOver The new grading grade (empty for no overriding the grade). - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the assessment is evaluated. + * @param assessmentId The assessment id. + * @param feedbackText The feedback for the reviewer. + * @param weight The new weight for the assessment. + * @param gradingGradeOver The new grading grade (empty for no overriding the grade). + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the assessment is evaluated. */ evaluateAssessmentOnline(assessmentId: number, feedbackText: string, weight: number, gradingGradeOver: any, siteId?: string): Promise { @@ -1319,10 +1319,10 @@ export class AddonModWorkshopProvider { * Invalidate the prefetched content except files. * To invalidate files, use AddonModWorkshopProvider#invalidateFiles. * - * @param {number} moduleId The module ID. - * @param {number} courseId Course ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promised resolved when content is invalidated. + * @param moduleId The module ID. + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @return Promised resolved when content is invalidated. */ invalidateContent(moduleId: number, courseId: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -1336,10 +1336,10 @@ export class AddonModWorkshopProvider { * Invalidate the prefetched content except files using the activityId. * To invalidate files, use AdddonModWorkshop#invalidateFiles. * - * @param {number} workshopId Workshop ID. - * @param {number} courseId Course ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when content is invalidated. + * @param workshopId Workshop ID. + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when content is invalidated. */ invalidateContentById(workshopId: number, courseId: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -1356,9 +1356,9 @@ export class AddonModWorkshopProvider { /** * 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, AddonModWorkshopProvider.COMPONENT, moduleId); @@ -1367,10 +1367,10 @@ export class AddonModWorkshopProvider { /** * Report the workshop as being viewed. * - * @param {number} id Workshop ID. - * @param {string} [name] Name of the workshop. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the WS call is successful. + * @param id Workshop ID. + * @param name Name of the workshop. + * @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 = { @@ -1384,11 +1384,11 @@ export class AddonModWorkshopProvider { /** * Report the workshop submission as being viewed. * - * @param {number} id Submission ID. - * @param {number} workshopId Workshop ID. - * @param {string} [name] Name of the workshop. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the WS call is successful. + * @param id Submission ID. + * @param workshopId Workshop ID. + * @param name Name of the workshop. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the WS call is successful. */ logViewSubmission(id: number, workshopId: number, name?: string, siteId?: string): Promise { const params = { diff --git a/src/addon/notes/components/list/list.ts b/src/addon/notes/components/list/list.ts index cf6f54afa..2011957ed 100644 --- a/src/addon/notes/components/list/list.ts +++ b/src/addon/notes/components/list/list.ts @@ -90,9 +90,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(); @@ -141,8 +141,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,7 +173,7 @@ 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(); @@ -198,8 +198,8 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { /** * 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 { e.preventDefault(); @@ -223,8 +223,8 @@ 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 { e.preventDefault(); @@ -245,8 +245,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 +263,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/pages/add/add.ts b/src/addon/notes/pages/add/add.ts index 98c770c8f..aed8e5364 100644 --- a/src/addon/notes/pages/add/add.ts +++ b/src/addon/notes/pages/add/add.ts @@ -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/providers/course-option-handler.ts b/src/addon/notes/providers/course-option-handler.ts index f00761ae8..9d5672961 100644 --- a/src/addon/notes/providers/course-option-handler.ts +++ b/src/addon/notes/providers/course-option-handler.ts @@ -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..3b844062c 100644 --- a/src/addon/notes/providers/notes-offline.ts +++ b/src/addon/notes/providers/notes-offline.ts @@ -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..2cf8538c8 100644 --- a/src/addon/notes/providers/notes-sync.ts +++ b/src/addon/notes/providers/notes-sync.ts @@ -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..3753f9404 100644 --- a/src/addon/notes/providers/notes.ts +++ b/src/addon/notes/providers/notes.ts @@ -41,12 +41,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 +80,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,10 +114,10 @@ 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 { if (!notes || !notes.length) { @@ -136,11 +136,11 @@ 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 { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -178,11 +178,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) => { @@ -205,8 +205,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 +217,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 +249,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 +260,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 +270,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,12 +281,12 @@ 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 { this.logger.debug('Get notes for course ' + courseId); @@ -336,10 +336,10 @@ 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 [description] */ setOfflineDeletedNotes(notes: any[], courseId: number, siteId?: string): Promise { return this.notesOffline.getCourseDeletedNotes(courseId, siteId).then((deletedNotes) => { @@ -354,9 +354,9 @@ 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 { const promises = notes.map((note) => { @@ -377,10 +377,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,10 +395,10 @@ 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 { return this.sitesProvider.getSite(siteId).then((site) => { diff --git a/src/addon/notes/providers/sync-cron-handler.ts b/src/addon/notes/providers/sync-cron-handler.ts index dcd1ad5d6..97f50167c 100644 --- a/src/addon/notes/providers/sync-cron-handler.ts +++ b/src/addon/notes/providers/sync-cron-handler.ts @@ -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..fe713086d 100644 --- a/src/addon/notes/providers/user-handler.ts +++ b/src/addon/notes/providers/user-handler.ts @@ -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..da5eece16 100644 --- a/src/addon/notifications/components/actions/actions.ts +++ b/src/addon/notifications/components/actions/actions.ts @@ -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/pages/list/list.ts b/src/addon/notifications/pages/list/list.ts index 1ab97b2be..db9153bd8 100644 --- a/src/addon/notifications/pages/list/list.ts +++ b/src/addon/notifications/pages/list/list.ts @@ -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,7 +128,7 @@ 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 { let promise; @@ -173,7 +173,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 +189,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,7 +200,7 @@ 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 { const text = notification.mobiletext.replace(/-{4,}/ig, ''); diff --git a/src/addon/notifications/pages/settings/settings.ts b/src/addon/notifications/pages/settings/settings.ts index 58ab72395..5754f18a2 100644 --- a/src/addon/notifications/pages/settings/settings.ts +++ b/src/addon/notifications/pages/settings/settings.ts @@ -78,7 +78,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) => { @@ -116,7 +116,7 @@ export class AddonNotificationsSettingsPage implements OnDestroy { /** * Load a processor. * - * @param {any} processor Processor object. + * @param processor Processor object. */ protected loadProcessor(processor: any): void { if (!processor) { @@ -151,7 +151,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 +164,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 +177,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,8 +188,8 @@ 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]; @@ -243,7 +243,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(() => { diff --git a/src/addon/notifications/providers/cron-handler.ts b/src/addon/notifications/providers/cron-handler.ts index e7ae59f17..d191985b0 100644 --- a/src/addon/notifications/providers/cron-handler.ts +++ b/src/addon/notifications/providers/cron-handler.ts @@ -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..442a0b1e1 100644 --- a/src/addon/notifications/providers/helper.ts +++ b/src/addon/notifications/providers/helper.ts @@ -28,13 +28,13 @@ 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}> { diff --git a/src/addon/notifications/providers/mainmenu-handler.ts b/src/addon/notifications/providers/mainmenu-handler.ts index a73fd6065..056ce4650 100644 --- a/src/addon/notifications/providers/mainmenu-handler.ts +++ b/src/addon/notifications/providers/mainmenu-handler.ts @@ -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..6e0af0129 100644 --- a/src/addon/notifications/providers/notifications.ts +++ b/src/addon/notifications/providers/notifications.ts @@ -47,9 +47,9 @@ 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 { const promises = notifications.map((notification) => { @@ -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,8 +114,8 @@ 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 { this.logger.debug('Get notification preferences'); @@ -135,7 +135,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,14 +144,14 @@ 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 { @@ -199,13 +199,13 @@ 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, @@ -260,13 +260,13 @@ 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 { @@ -276,13 +276,13 @@ export class AddonNotificationsProvider { /** * 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 { @@ -292,9 +292,9 @@ export class AddonNotificationsProvider { /** * 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 +334,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,7 +346,7 @@ export class AddonNotificationsProvider { /** * Mark all message notification as read. * - * @returns {Promise} Resolved when done. + * @return Resolved when done. * @since 3.2 */ markAllNotificationsAsRead(): Promise { @@ -360,9 +360,9 @@ 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 Resolved when done. * @since 3.5 */ markNotificationRead(notificationId: number, siteId?: string): Promise { @@ -385,8 +385,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 +397,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 +409,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 +419,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,7 +429,7 @@ 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 { diff --git a/src/addon/notifications/providers/push-click-handler.ts b/src/addon/notifications/providers/push-click-handler.ts index ce39f5d17..546d151fb 100644 --- a/src/addon/notifications/providers/push-click-handler.ts +++ b/src/addon/notifications/providers/push-click-handler.ts @@ -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..dea003ab7 100644 --- a/src/addon/notifications/providers/settings-handler.ts +++ b/src/addon/notifications/providers/settings-handler.ts @@ -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/providers/handler.ts b/src/addon/qbehaviour/adaptive/providers/handler.ts index 90ad015b8..1d40c1c4f 100644 --- a/src/addon/qbehaviour/adaptive/providers/handler.ts +++ b/src/addon/qbehaviour/adaptive/providers/handler.ts @@ -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/providers/handler.ts b/src/addon/qbehaviour/adaptivenopenalty/providers/handler.ts index a8ebebb0b..f8e3ba977 100644 --- a/src/addon/qbehaviour/adaptivenopenalty/providers/handler.ts +++ b/src/addon/qbehaviour/adaptivenopenalty/providers/handler.ts @@ -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/providers/handler.ts b/src/addon/qbehaviour/deferredcbm/providers/handler.ts index fb11cb208..fcb15cf3f 100644 --- a/src/addon/qbehaviour/deferredcbm/providers/handler.ts +++ b/src/addon/qbehaviour/deferredcbm/providers/handler.ts @@ -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/providers/handler.ts b/src/addon/qbehaviour/deferredfeedback/providers/handler.ts index da9052a14..e2d39cb2b 100644 --- a/src/addon/qbehaviour/deferredfeedback/providers/handler.ts +++ b/src/addon/qbehaviour/deferredfeedback/providers/handler.ts @@ -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/providers/handler.ts b/src/addon/qbehaviour/immediatecbm/providers/handler.ts index 4e1aa9ce4..4d26069ed 100644 --- a/src/addon/qbehaviour/immediatecbm/providers/handler.ts +++ b/src/addon/qbehaviour/immediatecbm/providers/handler.ts @@ -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/providers/handler.ts b/src/addon/qbehaviour/immediatefeedback/providers/handler.ts index 2103bba6c..0040878ea 100644 --- a/src/addon/qbehaviour/immediatefeedback/providers/handler.ts +++ b/src/addon/qbehaviour/immediatefeedback/providers/handler.ts @@ -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/providers/handler.ts b/src/addon/qbehaviour/informationitem/providers/handler.ts index 21d7181ed..f8f46f6e6 100644 --- a/src/addon/qbehaviour/informationitem/providers/handler.ts +++ b/src/addon/qbehaviour/informationitem/providers/handler.ts @@ -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/providers/handler.ts b/src/addon/qbehaviour/interactive/providers/handler.ts index 5504da1cc..6c25c2923 100644 --- a/src/addon/qbehaviour/interactive/providers/handler.ts +++ b/src/addon/qbehaviour/interactive/providers/handler.ts @@ -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/providers/handler.ts b/src/addon/qbehaviour/interactivecountback/providers/handler.ts index 54646e427..0cebc7103 100644 --- a/src/addon/qbehaviour/interactivecountback/providers/handler.ts +++ b/src/addon/qbehaviour/interactivecountback/providers/handler.ts @@ -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/providers/handler.ts b/src/addon/qbehaviour/manualgraded/providers/handler.ts index 91d767af4..62c08d2b1 100644 --- a/src/addon/qbehaviour/manualgraded/providers/handler.ts +++ b/src/addon/qbehaviour/manualgraded/providers/handler.ts @@ -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/qtype/calculated/providers/handler.ts b/src/addon/qtype/calculated/providers/handler.ts index 0874fbc82..12ab450e8 100644 --- a/src/addon/qtype/calculated/providers/handler.ts +++ b/src/addon/qtype/calculated/providers/handler.ts @@ -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/providers/handler.ts b/src/addon/qtype/calculatedmulti/providers/handler.ts index 3cbe18057..9baf71fc0 100644 --- a/src/addon/qtype/calculatedmulti/providers/handler.ts +++ b/src/addon/qtype/calculatedmulti/providers/handler.ts @@ -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/providers/handler.ts b/src/addon/qtype/calculatedsimple/providers/handler.ts index 2aaaf1c9c..2e9e85062 100644 --- a/src/addon/qtype/calculatedsimple/providers/handler.ts +++ b/src/addon/qtype/calculatedsimple/providers/handler.ts @@ -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..df0b3eb89 100644 --- a/src/addon/qtype/ddimageortext/classes/ddimageortext.ts +++ b/src/addon/qtype/ddimageortext/classes/ddimageortext.ts @@ -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/providers/handler.ts b/src/addon/qtype/ddimageortext/providers/handler.ts index 6cdf63057..73b571027 100644 --- a/src/addon/qtype/ddimageortext/providers/handler.ts +++ b/src/addon/qtype/ddimageortext/providers/handler.ts @@ -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..2e279a5fb 100644 --- a/src/addon/qtype/ddmarker/classes/ddmarker.ts +++ b/src/addon/qtype/ddmarker/classes/ddmarker.ts @@ -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..ae75138fc 100644 --- a/src/addon/qtype/ddmarker/classes/graphics_api.ts +++ b/src/addon/qtype/ddmarker/classes/graphics_api.ts @@ -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/providers/handler.ts b/src/addon/qtype/ddmarker/providers/handler.ts index 38ecfd1ba..c2220a45e 100644 --- a/src/addon/qtype/ddmarker/providers/handler.ts +++ b/src/addon/qtype/ddmarker/providers/handler.ts @@ -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..48f869ec3 100644 --- a/src/addon/qtype/ddwtos/classes/ddwtos.ts +++ b/src/addon/qtype/ddwtos/classes/ddwtos.ts @@ -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/providers/handler.ts b/src/addon/qtype/ddwtos/providers/handler.ts index 6a8a6eae4..2f1a9ded0 100644 --- a/src/addon/qtype/ddwtos/providers/handler.ts +++ b/src/addon/qtype/ddwtos/providers/handler.ts @@ -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/providers/handler.ts b/src/addon/qtype/description/providers/handler.ts index 49bba134c..fbb98071c 100644 --- a/src/addon/qtype/description/providers/handler.ts +++ b/src/addon/qtype/description/providers/handler.ts @@ -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/providers/handler.ts b/src/addon/qtype/essay/providers/handler.ts index 3babbd6e1..61aa89c44 100644 --- a/src/addon/qtype/essay/providers/handler.ts +++ b/src/addon/qtype/essay/providers/handler.ts @@ -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/providers/handler.ts b/src/addon/qtype/gapselect/providers/handler.ts index e18b357cd..c0afd80b1 100644 --- a/src/addon/qtype/gapselect/providers/handler.ts +++ b/src/addon/qtype/gapselect/providers/handler.ts @@ -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/providers/handler.ts b/src/addon/qtype/match/providers/handler.ts index d9519ad04..9b52e4979 100644 --- a/src/addon/qtype/match/providers/handler.ts +++ b/src/addon/qtype/match/providers/handler.ts @@ -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/providers/handler.ts b/src/addon/qtype/multianswer/providers/handler.ts index b02a4eb70..a05fdbc59 100644 --- a/src/addon/qtype/multianswer/providers/handler.ts +++ b/src/addon/qtype/multianswer/providers/handler.ts @@ -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/providers/handler.ts b/src/addon/qtype/multichoice/providers/handler.ts index 440ab80e8..ca4d0f413 100644 --- a/src/addon/qtype/multichoice/providers/handler.ts +++ b/src/addon/qtype/multichoice/providers/handler.ts @@ -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/randomsamatch/providers/handler.ts b/src/addon/qtype/randomsamatch/providers/handler.ts index eca943805..67d8d23ae 100644 --- a/src/addon/qtype/randomsamatch/providers/handler.ts +++ b/src/addon/qtype/randomsamatch/providers/handler.ts @@ -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/shortanswer/providers/handler.ts b/src/addon/qtype/shortanswer/providers/handler.ts index abbb694dc..a4daa2823 100644 --- a/src/addon/qtype/shortanswer/providers/handler.ts +++ b/src/addon/qtype/shortanswer/providers/handler.ts @@ -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/truefalse/providers/handler.ts b/src/addon/qtype/truefalse/providers/handler.ts index 3be03abde..24f2efdfe 100644 --- a/src/addon/qtype/truefalse/providers/handler.ts +++ b/src/addon/qtype/truefalse/providers/handler.ts @@ -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/remotethemes/providers/remotethemes.ts b/src/addon/remotethemes/providers/remotethemes.ts index 7b30b31ce..f4d863740 100644 --- a/src/addon/remotethemes/providers/remotethemes.ts +++ b/src/addon/remotethemes/providers/remotethemes.ts @@ -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/storagemanager/pages/course-storage/course-storage.ts b/src/addon/storagemanager/pages/course-storage/course-storage.ts index 18769c1c9..800ac1edf 100644 --- a/src/addon/storagemanager/pages/course-storage/course-storage.ts +++ b/src/addon/storagemanager/pages/course-storage/course-storage.ts @@ -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..b3e0f6b57 100644 --- a/src/addon/storagemanager/providers/coursemenu-handler.ts +++ b/src/addon/storagemanager/providers/coursemenu-handler.ts @@ -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/userprofilefield/checkbox/providers/handler.ts b/src/addon/userprofilefield/checkbox/providers/handler.ts index 42297a1a2..5981f6f95 100644 --- a/src/addon/userprofilefield/checkbox/providers/handler.ts +++ b/src/addon/userprofilefield/checkbox/providers/handler.ts @@ -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/providers/handler.ts b/src/addon/userprofilefield/datetime/providers/handler.ts index 29d3d321e..6076dd63c 100644 --- a/src/addon/userprofilefield/datetime/providers/handler.ts +++ b/src/addon/userprofilefield/datetime/providers/handler.ts @@ -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/providers/handler.ts b/src/addon/userprofilefield/menu/providers/handler.ts index 6516c0559..928d84db7 100644 --- a/src/addon/userprofilefield/menu/providers/handler.ts +++ b/src/addon/userprofilefield/menu/providers/handler.ts @@ -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/providers/handler.ts b/src/addon/userprofilefield/text/providers/handler.ts index 2cdcac9ee..644faf0df 100644 --- a/src/addon/userprofilefield/text/providers/handler.ts +++ b/src/addon/userprofilefield/text/providers/handler.ts @@ -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/textarea/providers/handler.ts b/src/addon/userprofilefield/textarea/providers/handler.ts index fc079ffb7..a3ae461ce 100644 --- a/src/addon/userprofilefield/textarea/providers/handler.ts +++ b/src/addon/userprofilefield/textarea/providers/handler.ts @@ -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/app/app.component.ts b/src/app/app.component.ts index 7df4951b2..e9bd35add 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -266,7 +266,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 +298,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/classes/base-sync.ts b/src/classes/base-sync.ts index d247783da..dbf8358bf 100644 --- a/src/classes/base-sync.ts +++ b/src/classes/base-sync.ts @@ -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..f4989b2d6 100644 --- a/src/classes/cache.ts +++ b/src/classes/cache.ts @@ -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..560e81645 100644 --- a/src/classes/delegate.ts +++ b/src/classes/delegate.ts @@ -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,24 +71,22 @@ 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}} = {}; /** * 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) { @@ -113,10 +104,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 +117,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 +130,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 +146,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 +158,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 +174,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 +188,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 +200,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,8 +214,8 @@ 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') { @@ -242,9 +233,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(), @@ -289,9 +280,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 +291,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 = [], diff --git a/src/classes/interceptor.ts b/src/classes/interceptor.ts index 35e5c4597..9399c0a76 100644 --- a/src/classes/interceptor.ts +++ b/src/classes/interceptor.ts @@ -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/site.ts b/src/classes/site.ts index 4f885fe93..f3c2ff9b8 100644 --- a/src/classes/site.ts +++ b/src/classes/site.ts @@ -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; } @@ -255,14 +234,14 @@ export class CoreSite { /** * 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 +279,7 @@ export class CoreSite { /** * Get site ID. * - * @return {string} Site ID. + * @return Site ID. */ getId(): string { return this.id; @@ -309,7 +288,7 @@ export class CoreSite { /** * Get site URL. * - * @return {string} Site URL. + * @return Site URL. */ getURL(): string { return this.siteUrl; @@ -318,7 +297,7 @@ export class CoreSite { /** * Get site token. * - * @return {string} Site token. + * @return Site token. */ getToken(): string { return this.token; @@ -327,7 +306,7 @@ export class CoreSite { /** * Get site info. * - * @return {any} Site info. + * @return Site info. */ getInfo(): any { return this.infos; @@ -336,7 +315,7 @@ export class CoreSite { /** * Get site private token. * - * @return {string} Site private token. + * @return Site private token. */ getPrivateToken(): string { return this.privateToken; @@ -345,7 +324,7 @@ export class CoreSite { /** * Get site DB. * - * @return {SQLiteDB} Site DB. + * @return Site DB. */ getDb(): SQLiteDB { return this.db; @@ -354,7 +333,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 +344,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 +353,7 @@ export class CoreSite { /** * Get site name. * - * @return {string} Site name. + * @return Site name. */ getSiteName(): string { if (CoreConfigConstants.sitename) { @@ -388,7 +367,7 @@ export class CoreSite { /** * Set site ID. * - * @param {string} New ID. + * @param New ID. */ setId(id: string): void { this.id = id; @@ -398,7 +377,7 @@ export class CoreSite { /** * Set site token. * - * @param {string} New token. + * @param New token. */ setToken(token: string): void { this.token = token; @@ -407,7 +386,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 +395,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 +404,7 @@ export class CoreSite { /** * Set site info. * - * @param {any} New info. + * @param New info. */ setInfo(infos: any): void { this.infos = infos; @@ -442,7 +421,7 @@ export class CoreSite { /** * Set site config. * - * @param {any} Config. + * @param Config. */ setConfig(config: any): void { if (config) { @@ -456,7 +435,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 +444,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 +455,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 +466,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 +492,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 +503,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 +522,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 +545,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 +568,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 @@ -805,11 +784,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 +825,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 +936,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 +960,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 +971,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 +982,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 { @@ -1092,11 +1071,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 +1114,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 +1137,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 +1156,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) { @@ -1192,8 +1171,8 @@ export class CoreSite { /** * 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 +1190,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 +1214,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) { @@ -1257,8 +1236,8 @@ export class CoreSite { * 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); @@ -1267,7 +1246,7 @@ export class CoreSite { /** * 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 +1255,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 +1272,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 +1289,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; @@ -1322,8 +1301,8 @@ export class CoreSite { /** * 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 +1358,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 +1379,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 +1402,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 +1419,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 +1458,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 +1469,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 +1480,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 +1492,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 +1504,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 +1543,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 +1568,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 +1607,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 +1616,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 +1625,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 +1643,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 +1667,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 +1677,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 +1725,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 +1772,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 +1794,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 +1813,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 +1839,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 +1849,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,9 +1868,9 @@ 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 }); diff --git a/src/classes/sqlitedb.ts b/src/classes/sqlitedb.ts index 9bc24272b..011690982 100644 --- a/src/classes/sqlitedb.ts +++ b/src/classes/sqlitedb.ts @@ -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..6f5f2ea16 100644 --- a/src/components/attachments/attachments.ts +++ b/src/components/attachments/attachments.ts @@ -106,8 +106,8 @@ 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; @@ -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/chart/chart.ts b/src/components/chart/chart.ts index 55ec191ca..f34d43242 100644 --- a/src/components/chart/chart.ts +++ b/src/components/chart/chart.ts @@ -117,8 +117,8 @@ export class CoreChartComponent implements OnDestroy, OnInit, OnChanges { /** * 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/context-menu/context-menu-item.ts b/src/components/context-menu/context-menu-item.ts index 0870cbcd3..9c9b6c182 100644 --- a/src/components/context-menu/context-menu-item.ts +++ b/src/components/context-menu/context-menu-item.ts @@ -85,9 +85,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..070e4362d 100644 --- a/src/components/context-menu/context-menu-popover.ts +++ b/src/components/context-menu/context-menu-popover.ts @@ -47,9 +47,9 @@ export class CoreContextMenuPopoverComponent { /** * 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) { diff --git a/src/components/context-menu/context-menu.ts b/src/components/context-menu/context-menu.ts index 42fca4504..3a8daf25c 100644 --- a/src/components/context-menu/context-menu.ts +++ b/src/components/context-menu/context-menu.ts @@ -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,7 +173,7 @@ export class CoreContextMenuComponent implements OnInit, OnDestroy { /** * Show the context menu. * - * @param {MouseEvent} event Event. + * @param event Event. */ showContextMenu(event: MouseEvent): void { if (!this.expanded) { 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..8a6fd955a 100644 --- a/src/components/course-picker-menu/course-picker-menu-popover.ts +++ b/src/components/course-picker-menu/course-picker-menu-popover.ts @@ -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..b231514ca 100644 --- a/src/components/download-refresh/download-refresh.ts +++ b/src/components/download-refresh/download-refresh.ts @@ -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..58bb372ac 100644 --- a/src/components/dynamic-component/dynamic-component.ts +++ b/src/components/dynamic-component/dynamic-component.ts @@ -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/file/file.ts b/src/components/file/file.ts index 22d0c98af..30aa5b588 100644 --- a/src/components/file/file.ts +++ b/src/components/file/file.ts @@ -104,7 +104,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 +118,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,8 +134,8 @@ 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(); @@ -198,7 +198,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/icon/icon.ts b/src/components/icon/icon.ts index 82e53d165..38c96f1fb 100644 --- a/src/components/icon/icon.ts +++ b/src/components/icon/icon.ts @@ -111,8 +111,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/iframe.ts b/src/components/iframe/iframe.ts index 722d9eae6..aa7584337 100644 --- a/src/components/iframe/iframe.ts +++ b/src/components/iframe/iframe.ts @@ -22,8 +22,6 @@ import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreIframeUtilsProvider } from '@providers/utils/iframe'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; -/** - */ @Component({ selector: 'core-iframe', templateUrl: 'core-iframe.html' diff --git a/src/components/infinite-loading/infinite-loading.ts b/src/components/infinite-loading/infinite-loading.ts index 977eba6b7..5c5a3ff18 100644 --- a/src/components/infinite-loading/infinite-loading.ts +++ b/src/components/infinite-loading/infinite-loading.ts @@ -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/ion-tabs/ion-tabs.ts b/src/components/ion-tabs/ion-tabs.ts index 914d97ab6..f410ad3fe 100644 --- a/src/components/ion-tabs/ion-tabs.ts +++ b/src/components/ion-tabs/ion-tabs.ts @@ -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/local-file/local-file.ts b/src/components/local-file/local-file.ts index b5f3140ec..025ffe751 100644 --- a/src/components/local-file/local-file.ts +++ b/src/components/local-file/local-file.ts @@ -93,7 +93,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 +113,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 +125,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,7 +165,7 @@ export class CoreLocalFileComponent implements OnInit { /** * Delete the file. * - * @param {Event} e Click event. + * @param e Click event. */ deleteFile(e: Event): void { e.preventDefault(); diff --git a/src/components/navbar-buttons/navbar-buttons.ts b/src/components/navbar-buttons/navbar-buttons.ts index c38931322..c3332006e 100644 --- a/src/components/navbar-buttons/navbar-buttons.ts +++ b/src/components/navbar-buttons/navbar-buttons.ts @@ -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/recaptcha/recaptchamodal.ts b/src/components/recaptcha/recaptchamodal.ts index 3739a74b5..b84e98f70 100644 --- a/src/components/recaptcha/recaptchamodal.ts +++ b/src/components/recaptcha/recaptchamodal.ts @@ -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.ts b/src/components/rich-text-editor/rich-text-editor.ts index e9c8bc2b5..68c4651e0 100644 --- a/src/components/rich-text-editor/rich-text-editor.ts +++ b/src/components/rich-text-editor/rich-text-editor.ts @@ -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(); @@ -210,8 +210,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 +246,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 +279,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 +302,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 +340,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 +404,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 +481,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 +497,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 +530,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); @@ -568,7 +568,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/search-box.ts b/src/components/search-box/search-box.ts index e978908d1..36a42cb0d 100644 --- a/src/components/search-box/search-box.ts +++ b/src/components/search-box/search-box.ts @@ -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/send-message-form.ts b/src/components/send-message-form/send-message-form.ts index ae7a25ff9..48a7a87c6 100644 --- a/src/components/send-message-form/send-message-form.ts +++ b/src/components/send-message-form/send-message-form.ts @@ -66,7 +66,7 @@ export class CoreSendMessageFormComponent implements OnInit { /** * Form submitted. * - * @param {Event} $event Mouse event. + * @param $event Mouse event. */ submitForm($event: Event): void { $event.preventDefault(); @@ -95,8 +95,8 @@ 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.sendOnEnter && !other) { diff --git a/src/components/show-password/show-password.ts b/src/components/show-password/show-password.ts index 68abdbf70..61dea32ef 100644 --- a/src/components/show-password/show-password.ts +++ b/src/components/show-password/show-password.ts @@ -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/split-view/split-view.ts b/src/components/split-view/split-view.ts index ca807f10d..4a63bfe72 100644 --- a/src/components/split-view/split-view.ts +++ b/src/components/split-view/split-view.ts @@ -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..344327cd0 100644 --- a/src/components/style/style.ts +++ b/src/components/style/style.ts @@ -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..763376374 100644 --- a/src/components/tabs/tab.ts +++ b/src/components/tabs/tab.ts @@ -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..e60ea9b10 100644 --- a/src/components/tabs/tabs.ts +++ b/src/components/tabs/tabs.ts @@ -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. @@ -248,8 +248,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 +265,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]; @@ -415,7 +415,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) { @@ -460,7 +460,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 +472,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/core/block/classes/base-block-component.ts b/src/core/block/classes/base-block-component.ts index 46dc2c33b..e3c7e75f6 100644 --- a/src/core/block/classes/base-block-component.ts +++ b/src/core/block/classes/base-block-component.ts @@ -49,10 +49,10 @@ export class CoreBlockBaseComponent implements OnInit { /** * 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 +68,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 +94,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 +103,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 +131,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..8e5447d79 100644 --- a/src/core/block/classes/base-block-handler.ts +++ b/src/core/block/classes/base-block-handler.ts @@ -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.ts b/src/core/block/components/block/block.ts index eebf4fd4d..3632c972f 100644 --- a/src/core/block/components/block/block.ts +++ b/src/core/block/components/block/block.ts @@ -128,10 +128,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 +144,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/course-blocks/course-blocks.ts b/src/core/block/components/course-blocks/course-blocks.ts index c1b849c56..e973eac72 100644 --- a/src/core/block/components/course-blocks/course-blocks.ts +++ b/src/core/block/components/course-blocks/course-blocks.ts @@ -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/providers/delegate.ts b/src/core/block/providers/delegate.ts index 7f0f245f3..2f9d96fdc 100644 --- a/src/core/block/providers/delegate.ts +++ b/src/core/block/providers/delegate.ts @@ -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..9272bca16 100644 --- a/src/core/block/providers/helper.ts +++ b/src/core/block/providers/helper.ts @@ -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/components/comments/comments.ts b/src/core/comments/components/comments/comments.ts index 6bbe34fc4..d37e325c8 100644 --- a/src/core/comments/components/comments/comments.ts +++ b/src/core/comments/components/comments/comments.ts @@ -117,7 +117,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 +128,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, @@ -170,9 +170,9 @@ export class CoreCommentsCommentsComponent implements OnChanges, OnDestroy { /** * 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/pages/add/add.ts b/src/core/comments/pages/add/add.ts index 66510fb79..bfabd4535 100644 --- a/src/core/comments/pages/add/add.ts +++ b/src/core/comments/pages/add/add.ts @@ -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.ts b/src/core/comments/pages/viewer/viewer.ts index e84d27e0c..b1925c26d 100644 --- a/src/core/comments/pages/viewer/viewer.ts +++ b/src/core/comments/pages/viewer/viewer.ts @@ -113,9 +113,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; @@ -204,8 +204,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,9 +219,9 @@ 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.refreshIcon = 'spinner'; @@ -241,7 +241,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 +253,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 +272,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(); @@ -302,8 +302,8 @@ export class CoreCommentsViewerPage implements OnDestroy { /** * Delete a comment. * - * @param {Event} e Click event. - * @param {any} comment Comment to delete. + * @param e Click event. + * @param comment Comment to delete. */ deleteComment(e: Event, comment: any): void { e.preventDefault(); @@ -336,8 +336,8 @@ export class CoreCommentsViewerPage implements OnDestroy { /** * 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..24475b69a 100644 --- a/src/core/comments/providers/comments.ts +++ b/src/core/comments/providers/comments.ts @@ -37,14 +37,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 +79,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 +114,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 +136,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 +148,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,10 +160,10 @@ 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, 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 { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -204,15 +204,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 +233,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 +251,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 +266,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 +277,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 { @@ -316,13 +316,13 @@ 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 { @@ -371,13 +371,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 +396,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) => { diff --git a/src/core/comments/providers/offline.ts b/src/core/comments/providers/offline.ts index 94caf9fb3..3979c9cb7 100644 --- a/src/core/comments/providers/offline.ts +++ b/src/core/comments/providers/offline.ts @@ -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..76d9e1529 100644 --- a/src/core/comments/providers/sync-cron-handler.ts +++ b/src/core/comments/providers/sync-cron-handler.ts @@ -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..6e1003e98 100644 --- a/src/core/comments/providers/sync.ts +++ b/src/core/comments/providers/sync.ts @@ -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 { @@ -216,12 +216,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/components/compile-html/compile-html.ts b/src/core/compile/components/compile-html/compile-html.ts index 82c52cd8e..cf0bb0863 100644 --- a/src/core/compile/components/compile-html/compile-html.ts +++ b/src/core/compile/components/compile-html/compile-html.ts @@ -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..1cb152a84 100644 --- a/src/core/compile/providers/compile.ts +++ b/src/core/compile/providers/compile.ts @@ -154,10 +154,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 +190,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 +201,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 +216,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) @@ -284,10 +284,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/contentlinks/classes/base-handler.ts b/src/core/contentlinks/classes/base-handler.ts index 9f79ddbda..ca23fd706 100644 --- a/src/core/contentlinks/classes/base-handler.ts +++ b/src/core/contentlinks/classes/base-handler.ts @@ -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..865a32d9e 100644 --- a/src/core/contentlinks/classes/module-grade-handler.ts +++ b/src/core/contentlinks/classes/module-grade-handler.ts @@ -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..2ada678f5 100644 --- a/src/core/contentlinks/classes/module-index-handler.ts +++ b/src/core/contentlinks/classes/module-index-handler.ts @@ -24,16 +24,15 @@ 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, ...). */ constructor(protected courseHelper: CoreCourseHelperProvider, public addon: string, public modName: string) { super(); @@ -46,11 +45,11 @@ export class CoreContentLinksModuleIndexHandler 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 { diff --git a/src/core/contentlinks/classes/module-list-handler.ts b/src/core/contentlinks/classes/module-list-handler.ts index 37e44e7d7..6365a1ad9 100644 --- a/src/core/contentlinks/classes/module-list-handler.ts +++ b/src/core/contentlinks/classes/module-list-handler.ts @@ -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/pages/choose-site/choose-site.ts b/src/core/contentlinks/pages/choose-site/choose-site.ts index b10a36bc7..0808e130d 100644 --- a/src/core/contentlinks/pages/choose-site/choose-site.ts +++ b/src/core/contentlinks/pages/choose-site/choose-site.ts @@ -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..340f1eb17 100644 --- a/src/core/contentlinks/providers/delegate.ts +++ b/src/core/contentlinks/providers/delegate.ts @@ -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..dd2f6e35b 100644 --- a/src/core/contentlinks/providers/helper.ts +++ b/src/core/contentlinks/providers/helper.ts @@ -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..54f19c06b 100644 --- a/src/core/course/classes/activity-prefetch-handler.ts +++ b/src/core/course/classes/activity-prefetch-handler.ts @@ -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 { @@ -128,10 +128,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 +142,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 +155,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..ae076f069 100644 --- a/src/core/course/classes/activity-sync.ts +++ b/src/core/course/classes/activity-sync.ts @@ -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..6e1f0b23c 100644 --- a/src/core/course/classes/main-activity-component.ts +++ b/src/core/course/classes/main-activity-component.ts @@ -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..854d194f6 100644 --- a/src/core/course/classes/main-resource-component.ts +++ b/src/core/course/classes/main-resource-component.ts @@ -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) { @@ -242,7 +242,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, /** * 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 +251,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..0bae87e2b 100644 --- a/src/core/course/classes/module-prefetch-handler.ts +++ b/src/core/course/classes/module-prefetch-handler.ts @@ -29,39 +29,33 @@ 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 } } = {}; @@ -72,10 +66,10 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref /** * 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 +90,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 +103,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,11 +123,11 @@ 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) => { @@ -146,9 +140,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 +153,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 +166,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,9 +178,9 @@ 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) { @@ -207,9 +201,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 +219,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 +229,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 +242,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 +253,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 +265,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 +278,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 +287,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 +297,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 +310,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 +324,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..f3ce201e1 100644 --- a/src/core/course/classes/resource-prefetch-handler.ts +++ b/src/core/course/classes/resource-prefetch-handler.ts @@ -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()) { @@ -96,10 +96,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 +113,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 +130,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 +142,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/format/format.ts b/src/core/course/components/format/format.ts index 8455d4487..f15589083 100644 --- a/src/core/course/components/format/format.ts +++ b/src/core/course/components/format/format.ts @@ -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..7d4141d43 100644 --- a/src/core/course/components/module-completion/module-completion.ts +++ b/src/core/course/components/module-completion/module-completion.ts @@ -58,7 +58,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) { diff --git a/src/core/course/components/module/module.ts b/src/core/course/components/module/module.ts index 3bdb1a898..8acfd8dc2 100644 --- a/src/core/course/components/module/module.ts +++ b/src/core/course/components/module/module.ts @@ -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..fa37a678e 100644 --- a/src/core/course/components/tag-area/tag-area.ts +++ b/src/core/course/components/tag-area/tag-area.ts @@ -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/formats/singleactivity/components/singleactivity.ts b/src/core/course/formats/singleactivity/components/singleactivity.ts index fb2a21924..946b9ea8d 100644 --- a/src/core/course/formats/singleactivity/components/singleactivity.ts +++ b/src/core/course/formats/singleactivity/components/singleactivity.ts @@ -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..d85d68ed7 100644 --- a/src/core/course/formats/singleactivity/providers/handler.ts +++ b/src/core/course/formats/singleactivity/providers/handler.ts @@ -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/topics/providers/handler.ts b/src/core/course/formats/topics/providers/handler.ts index 0f6d49e3c..d4a3c4457 100644 --- a/src/core/course/formats/topics/providers/handler.ts +++ b/src/core/course/formats/topics/providers/handler.ts @@ -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/weeks/providers/handler.ts b/src/core/course/formats/weeks/providers/handler.ts index ccaef9d44..66245bbbe 100644 --- a/src/core/course/formats/weeks/providers/handler.ts +++ b/src/core/course/formats/weeks/providers/handler.ts @@ -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/pages/list-mod-type/list-mod-type.ts b/src/core/course/pages/list-mod-type/list-mod-type.ts index 37220ae4a..759c3d47d 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 @@ -63,7 +63,7 @@ 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. @@ -113,7 +113,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.ts b/src/core/course/pages/section-selector/section-selector.ts index 6186fe8ca..381132fba 100644 --- a/src/core/course/pages/section-selector/section-selector.ts +++ b/src/core/course/pages/section-selector/section-selector.ts @@ -69,7 +69,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.ts b/src/core/course/pages/section/section.ts index 85f3ab89e..f5e61c060 100644 --- a/src/core/course/pages/section/section.ts +++ b/src/core/course/pages/section/section.ts @@ -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. @@ -348,8 +348,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 +402,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 +428,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 +460,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 +479,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/providers/course-offline.ts b/src/core/course/providers/course-offline.ts index 4de3ac671..4a5db05af 100644 --- a/src/core/course/providers/course-offline.ts +++ b/src/core/course/providers/course-offline.ts @@ -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..6a40f6371 100644 --- a/src/core/course/providers/course-tag-area-handler.ts +++ b/src/core/course/providers/course-tag-area-handler.ts @@ -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..1b1af7c17 100644 --- a/src/core/course/providers/course.ts +++ b/src/core/course/providers/course.ts @@ -113,7 +113,7 @@ export class CoreCourseProvider { /** * Check if the get course blocks WS is available in current site. * - * @return {boolean} Whether it's available. + * @return Whether it's available. * @since 3.7 */ canGetCourseBlocks(): boolean { @@ -124,8 +124,8 @@ export class CoreCourseProvider { /** * 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 +138,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 +152,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 +168,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 +185,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 +254,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 +265,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 +290,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 +300,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 +319,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 +334,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 +457,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 +488,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 +514,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 +545,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 +556,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 +566,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 +576,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 +598,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 +612,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 +640,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 +705,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 +715,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 +736,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 +749,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 +771,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 +785,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 +810,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 +836,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 +866,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 +911,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 +930,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 +947,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 +1000,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 +1013,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 +1050,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 +1107,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 +1124,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, { diff --git a/src/core/course/providers/default-format.ts b/src/core/course/providers/default-format.ts index e03341376..56a0c2840 100644 --- a/src/core/course/providers/default-format.ts +++ b/src/core/course/providers/default-format.ts @@ -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..1e5af3d8d 100644 --- a/src/core/course/providers/default-module.ts +++ b/src/core/course/providers/default-module.ts @@ -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. @@ -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..9fecdf9a3 100644 --- a/src/core/course/providers/format-delegate.ts +++ b/src/core/course/providers/format-delegate.ts @@ -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..a82b1508b 100644 --- a/src/core/course/providers/helper.ts +++ b/src/core/course/providers/helper.ts @@ -45,37 +45,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 +80,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; }; @@ -136,11 +126,11 @@ 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. + * @return Whether the sections have content. */ addHandlerDataForModules(sections: any[], courseId: number, completionStatus?: any, courseName?: string): boolean { let hasContent = false; @@ -184,11 +174,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 +218,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 +264,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 +331,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,9 +389,9 @@ 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(() => { @@ -416,11 +406,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, @@ -472,11 +462,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 +493,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 +520,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 { @@ -633,13 +623,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}> { @@ -729,13 +719,13 @@ export class CoreCourseHelperProvider { /** * 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 +750,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 +786,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 +814,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 +837,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 +856,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 +885,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) => { @@ -944,9 +934,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 +954,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 +967,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 +980,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 +1006,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 +1024,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 { @@ -1102,8 +1092,8 @@ 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; @@ -1112,16 +1102,16 @@ export class CoreCourseHelperProvider { /** * 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) @@ -1201,12 +1191,12 @@ 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) { @@ -1225,12 +1215,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 { @@ -1300,12 +1290,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 +1315,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 +1360,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) { @@ -1422,10 +1412,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 +1444,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 +1464,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..deb0a7474 100644 --- a/src/core/course/providers/log-cron-handler.ts +++ b/src/core/course/providers/log-cron-handler.ts @@ -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..8e69c34c3 100644 --- a/src/core/course/providers/log-helper.ts +++ b/src/core/course/providers/log-helper.ts @@ -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,11 +177,11 @@ 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) => { @@ -201,15 +201,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 +222,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 +241,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 +266,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 +297,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,9 +329,9 @@ 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) => { diff --git a/src/core/course/providers/module-delegate.ts b/src/core/course/providers/module-delegate.ts index 4a3824784..eacadec21 100644 --- a/src/core/course/providers/module-delegate.ts +++ b/src/core/course/providers/module-delegate.ts @@ -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,17 +36,16 @@ 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. + * @return Data to render the module. */ getData(module: any, courseId: number, sectionId: number): CoreCourseModuleHandlerData; @@ -56,10 +54,10 @@ export interface CoreCourseModuleHandler extends CoreDelegateHandler { * 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 +65,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 +80,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 +92,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 +124,35 @@ 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} */ spinner?: 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 +169,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 +182,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 +232,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,11 +247,11 @@ 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. + * @return Data to render the module. */ getModuleDataFor(modname: string, module: any, courseId: number, sectionId: number): CoreCourseModuleHandlerData { return this.executeFunctionOnEnabled(modname, 'getData', [module, courseId, sectionId]); @@ -276,9 +260,9 @@ 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 {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,9 +273,9 @@ 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); @@ -309,8 +293,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 +303,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 +314,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..65278fe82 100644 --- a/src/core/course/providers/module-prefetch-delegate.ts +++ b/src/core/course/providers/module-prefetch-delegate.ts @@ -34,13 +34,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 +46,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 +56,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 +118,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 +128,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 +167,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; } @@ -282,7 +275,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 +284,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 +314,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 +361,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 +392,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 +484,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 +501,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 +511,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 +542,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 +585,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 +657,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 +680,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 +783,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 +868,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), @@ -923,11 +916,11 @@ 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 { @@ -980,9 +973,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 +984,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 +994,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 +1004,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 +1033,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 +1045,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 +1059,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 +1071,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 +1111,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 +1144,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 +1165,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 +1183,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 +1208,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 +1287,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 +1327,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 +1343,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 +1359,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 +1392,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..a3285e10e 100644 --- a/src/core/course/providers/modules-tag-area-handler.ts +++ b/src/core/course/providers/modules-tag-area-handler.ts @@ -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..0fb67a195 100644 --- a/src/core/course/providers/options-delegate.ts +++ b/src/core/course/providers/options-delegate.ts @@ -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..4b07c449e 100644 --- a/src/core/course/providers/sync-cron-handler.ts +++ b/src/core/course/providers/sync-cron-handler.ts @@ -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..c7cb473ce 100644 --- a/src/core/course/providers/sync.ts +++ b/src/core/course/providers/sync.ts @@ -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/course-list-item/course-list-item.ts b/src/core/courses/components/course-list-item/course-list-item.ts index c11f17f69..7c5e34f62 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 @@ -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..99abb36a9 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 @@ -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/course-progress.ts b/src/core/courses/components/course-progress/course-progress.ts index 814b00c55..1db3c9098 100644 --- a/src/core/courses/components/course-progress/course-progress.ts +++ b/src/core/courses/components/course-progress/course-progress.ts @@ -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.ts b/src/core/courses/components/my-courses/my-courses.ts index fc0817350..3135892c8 100644 --- a/src/core/courses/components/my-courses/my-courses.ts +++ b/src/core/courses/components/my-courses/my-courses.ts @@ -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/pages/available-courses/available-courses.ts b/src/core/courses/pages/available-courses/available-courses.ts index 5e244e7cb..9a69fe49b 100644 --- a/src/core/courses/pages/available-courses/available-courses.ts +++ b/src/core/courses/pages/available-courses/available-courses.ts @@ -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.ts b/src/core/courses/pages/categories/categories.ts index ab9a35cb1..10b84803e 100644 --- a/src/core/courses/pages/categories/categories.ts +++ b/src/core/courses/pages/categories/categories.ts @@ -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.ts b/src/core/courses/pages/course-preview/course-preview.ts index 6e1569c2d..0be65494d 100644 --- a/src/core/courses/pages/course-preview/course-preview.ts +++ b/src/core/courses/pages/course-preview/course-preview.ts @@ -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.ts b/src/core/courses/pages/dashboard/dashboard.ts index 94bd5243a..2c2ccd993 100644 --- a/src/core/courses/pages/dashboard/dashboard.ts +++ b/src/core/courses/pages/dashboard/dashboard.ts @@ -139,7 +139,7 @@ export class CoreCoursesDashboardPage implements OnDestroy { /** * Convenience function to fetch the dashboard data. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected loadDashboardContent(): Promise { return this.dashboardProvider.isAvailable().then((available) => { @@ -171,7 +171,7 @@ export class CoreCoursesDashboardPage implements OnDestroy { /** * Refresh the dashboard data. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshDashboard(refresher: any): void { const promises = []; @@ -195,7 +195,7 @@ export class CoreCoursesDashboardPage implements OnDestroy { /** * Refresh the dashboard data and My Courses. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshMyCourses(refresher: any): void { // First of all, refresh dashboard blocks, maybe a new block was added and now we can display the dashboard. @@ -222,7 +222,7 @@ export class CoreCoursesDashboardPage implements OnDestroy { /** * Convenience function to switch download enabled. * - * @param {boolean} enable If enable or disable. + * @param enable If enable or disable. */ protected switchDownload(enable: boolean): void { this.downloadEnabled = (this.downloadCourseEnabled || this.downloadCoursesEnabled) && enable; diff --git a/src/core/courses/pages/search/search.ts b/src/core/courses/pages/search/search.ts index e73c88b90..c64795460 100644 --- a/src/core/courses/pages/search/search.ts +++ b/src/core/courses/pages/search/search.ts @@ -39,7 +39,7 @@ export class CoreCoursesSearchPage { /** * Search a new text. * - * @param {string} text The text to search. + * @param text The text to search. */ search(text: string): void { this.currentSearch = text; @@ -55,7 +55,7 @@ export class CoreCoursesSearchPage { /** * 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. */ loadMoreResults(infiniteComplete?: any): void { this.searchCourses().finally(() => { @@ -66,7 +66,7 @@ export class CoreCoursesSearchPage { /** * Search courses or load the next page of current search. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected searchCourses(): Promise { this.loadMoreError = false; diff --git a/src/core/courses/pages/self-enrol-password/self-enrol-password.ts b/src/core/courses/pages/self-enrol-password/self-enrol-password.ts index 276cf7718..e304ad8b5 100644 --- a/src/core/courses/pages/self-enrol-password/self-enrol-password.ts +++ b/src/core/courses/pages/self-enrol-password/self-enrol-password.ts @@ -36,8 +36,8 @@ export class CoreCoursesSelfEnrolPasswordPage { /** * Submit password. * - * @param {Event} e Event. - * @param {string} password Password to submit. + * @param e Event. + * @param password Password to submit. */ submitPassword(e: Event, password: string): void { e.preventDefault(); diff --git a/src/core/courses/providers/course-link-handler.ts b/src/core/courses/providers/course-link-handler.ts index 5ffe35cb6..249ef5594 100644 --- a/src/core/courses/providers/course-link-handler.ts +++ b/src/core/courses/providers/course-link-handler.ts @@ -49,11 +49,11 @@ export class CoreCoursesCourseLinkHandler 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 { @@ -104,11 +104,11 @@ export class CoreCoursesCourseLinkHandler 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); @@ -126,12 +126,12 @@ export class CoreCoursesCourseLinkHandler extends CoreContentLinksHandlerBase { /** * Action to perform when an enrol link is clicked. * - * @param {number} courseId Course ID. - * @param {string} url Treated URL. - * @param {any} pageParams Params to send to the new page. - * @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 courseId Course ID. + * @param url Treated URL. + * @param pageParams Params to send to the new page. + * @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. */ protected actionEnrol(courseId: number, url: string, pageParams: any, navCtrl?: NavController): Promise { const modal = this.domUtils.showModalLoading(), @@ -216,8 +216,8 @@ export class CoreCoursesCourseLinkHandler extends CoreContentLinksHandlerBase { /** * Check if a user can be "automatically" self enrolled in a course. * - * @param {number} courseId Course ID. - * @return {Promise} Promise resolved if user can be enrolled in a course, rejected otherwise. + * @param courseId Course ID. + * @return Promise resolved if user can be enrolled in a course, rejected otherwise. */ protected canSelfEnrol(courseId: number): Promise { // Check that the course has self enrolment enabled. @@ -242,9 +242,9 @@ export class CoreCoursesCourseLinkHandler extends CoreContentLinksHandlerBase { /** * Try to self enrol a user in a course. * - * @param {number} courseId Course ID. - * @param {string} [password] Password. - * @return {Promise} Promise resolved when the user is enrolled, rejected otherwise. + * @param courseId Course ID. + * @param password Password. + * @return Promise resolved when the user is enrolled, rejected otherwise. */ protected selfEnrol(courseId: number, password?: string): Promise { const modal = this.domUtils.showModalLoading(); @@ -285,9 +285,9 @@ export class CoreCoursesCourseLinkHandler extends CoreContentLinksHandlerBase { /** * Wait for the user to be enrolled in a course. * - * @param {number} courseId The course ID. - * @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 courseId The course ID. + * @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(courseId: number, first?: boolean): Promise { if (first) { diff --git a/src/core/courses/providers/courses-index-link-handler.ts b/src/core/courses/providers/courses-index-link-handler.ts index bbea7ec34..150fb9421 100644 --- a/src/core/courses/providers/courses-index-link-handler.ts +++ b/src/core/courses/providers/courses-index-link-handler.ts @@ -34,11 +34,11 @@ export class CoreCoursesIndexLinkHandler 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/core/courses/providers/courses.ts b/src/core/courses/providers/courses.ts index c63780c5c..160fe5b5d 100644 --- a/src/core/courses/providers/courses.ts +++ b/src/core/courses/providers/courses.ts @@ -41,7 +41,7 @@ export class CoreCoursesProvider { /** * Whether current site supports getting course options. * - * @return {boolean} Whether current site supports getting course options. + * @return Whether current site supports getting course options. */ canGetAdminAndNavOptions(): boolean { return this.sitesProvider.wsAvailableInCurrentSite('core_course_get_user_navigation_options') && @@ -51,10 +51,10 @@ export class CoreCoursesProvider { /** * Get categories. They can be filtered by id. * - * @param {number} categoryId Category ID to get. - * @param {boolean} [addSubcategories] If it should add subcategories to the list. - * @param {string} [siteId] Site to get the courses from. If not defined, use current site. - * @return {Promise} Promise resolved with the categories. + * @param categoryId Category ID to get. + * @param addSubcategories If it should add subcategories to the list. + * @param siteId Site to get the courses from. If not defined, use current site. + * @return Promise resolved with the categories. */ getCategories(categoryId: number, addSubcategories?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -78,9 +78,9 @@ export class CoreCoursesProvider { /** * Get cache key for get categories methods WS call. * - * @param {number} categoryId Category ID to get. - * @param {boolean} [addSubcategories] If add subcategories to the list. - * @return {string} Cache key. + * @param categoryId Category ID to get. + * @param addSubcategories If add subcategories to the list. + * @return Cache key. */ protected getCategoriesCacheKey(categoryId: number, addSubcategories?: boolean): string { return this.ROOT_CACHE_KEY + 'categories:' + categoryId + ':' + !!addSubcategories; @@ -89,9 +89,9 @@ export class CoreCoursesProvider { /** * Given a list of course IDs to get course admin and nav options, return the list of courseIds to use. * - * @param {number[]} courseIds Course IDs. - * @param {string} [siteId] Site Id. If not defined, use current site. - * @return {Promise} Promise resolved with the list of course IDs. + * @param courseIds Course IDs. + * @param siteId Site Id. If not defined, use current site. + * @return Promise resolved with the list of course IDs. */ protected getCourseIdsForAdminAndNavOptions(courseIds: number[], siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -153,8 +153,8 @@ export class CoreCoursesProvider { /** * Check if download a whole course 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. */ isDownloadCourseDisabled(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -165,8 +165,8 @@ export class CoreCoursesProvider { /** * Check if download a whole course 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. */ isDownloadCourseDisabledInSite(site?: CoreSite): boolean { site = site || this.sitesProvider.getCurrentSite(); @@ -177,8 +177,8 @@ export class CoreCoursesProvider { /** * Check if download all courses 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. */ isDownloadCoursesDisabled(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -189,8 +189,8 @@ export class CoreCoursesProvider { /** * Check if download all courses 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. */ isDownloadCoursesDisabledInSite(site?: CoreSite): boolean { site = site || this.sitesProvider.getCurrentSite(); @@ -201,8 +201,8 @@ export class CoreCoursesProvider { /** * Check if My Courses 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. */ isMyCoursesDisabled(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -213,8 +213,8 @@ export class CoreCoursesProvider { /** * Check if My Courses 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. */ isMyCoursesDisabledInSite(site?: CoreSite): boolean { site = site || this.sitesProvider.getCurrentSite(); @@ -225,8 +225,8 @@ export class CoreCoursesProvider { /** * Check if Search Courses 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. */ isSearchCoursesDisabled(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -237,8 +237,8 @@ export class CoreCoursesProvider { /** * Check if Search Courses 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. */ isSearchCoursesDisabledInSite(site?: CoreSite): boolean { site = site || this.sitesProvider.getCurrentSite(); @@ -249,9 +249,9 @@ export class CoreCoursesProvider { /** * Get course. * - * @param {number} id ID of the course to get. - * @param {string} [siteId] Site to get the courses from. If not defined, use current site. - * @return {Promise} Promise resolved with the course. + * @param id ID of the course to get. + * @param siteId Site to get the courses from. If not defined, use current site. + * @return Promise resolved with the course. */ getCourse(id: number, siteId?: string): Promise { return this.getCourses([id], siteId).then((courses) => { @@ -266,9 +266,9 @@ export class CoreCoursesProvider { /** * Get the enrolment methods from a course. * - * @param {number} id ID of the course. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -287,8 +287,8 @@ export class CoreCoursesProvider { /** * Get cache key for get course enrolment methods WS call. * - * @param {number} id Course ID. - * @return {string} Cache key. + * @param id Course ID. + * @return Cache key. */ protected getCourseEnrolmentMethodsCacheKey(id: number): string { return this.ROOT_CACHE_KEY + 'enrolmentmethods:' + id; @@ -297,9 +297,9 @@ export class CoreCoursesProvider { /** * Get info from a course guest enrolment method. * - * @param {number} instanceId Guest instance ID. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Promise resolved when the info is retrieved. + * @param instanceId Guest instance ID. + * @param siteId Site ID. If not defined, use current site. + * @return Promise resolved when the info is retrieved. */ getCourseGuestEnrolmentInfo(instanceId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -320,8 +320,8 @@ export class CoreCoursesProvider { /** * Get cache key for get course guest enrolment methods WS call. * - * @param {number} instanceId Guest instance ID. - * @return {string} Cache key. + * @param instanceId Guest instance ID. + * @return Cache key. */ protected getCourseGuestEnrolmentInfoCacheKey(instanceId: number): string { return this.ROOT_CACHE_KEY + 'guestinfo:' + instanceId; @@ -332,9 +332,9 @@ export class CoreCoursesProvider { * Warning: if the user doesn't have permissions to view some of the courses passed the WS call will fail. * The user must be able to view ALL the courses passed. * - * @param {number[]} ids List of IDs of the courses to get. - * @param {string} [siteId] Site to get the courses from. If not defined, use current site. - * @return {Promise} Promise resolved with the courses. + * @param ids List of IDs of the courses to get. + * @param siteId Site to get the courses from. If not defined, use current site. + * @return Promise resolved with the courses. */ getCourses(ids: number[], siteId?: string): Promise { if (!Array.isArray(ids)) { @@ -361,8 +361,8 @@ export class CoreCoursesProvider { /** * Get cache key for get courses WS call. * - * @param {number[]} ids Courses IDs. - * @return {string} Cache key. + * @param ids Courses IDs. + * @return Cache key. */ protected getCoursesCacheKey(ids: number[]): string { return this.ROOT_CACHE_KEY + 'course:' + JSON.stringify(ids); @@ -373,10 +373,10 @@ export class CoreCoursesProvider { * When requesting a single course that belongs to enrolled courses, request all enrolled courses because * the WS call is probably cached. * - * @param {string} [field] The field to search. - * @param {any} [value] The value to match. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise<{field: string, value: any}>} Promise resolved with the field and value to use. + * @param field The field to search. + * @param value The value to match. + * @param siteId Site ID. If not defined, use current site. + * @return Promise resolved with the field and value to use. */ protected fixCoursesByFieldParams(field?: string, value?: any, siteId?: string): Promise<{field: string, value: any}> { @@ -400,15 +400,15 @@ export class CoreCoursesProvider { /** * Get the first course returned by getCoursesByField. * - * @param {string} [field] The field to search. Can be left empty for all courses or: - * id: course id. - * ids: comma separated course ids. - * shortname: course short name. - * idnumber: course id number. - * category: category id the course belongs to. - * @param {any} [value] The value to match. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Promise resolved with the first course. + * @param field The field to search. Can be left empty for all courses or: + * id: course id. + * ids: comma separated course ids. + * shortname: course short name. + * idnumber: course id number. + * category: category id the course belongs to. + * @param value The value to match. + * @param siteId Site ID. If not defined, use current site. + * @return Promise resolved with the first course. * @since 3.2 */ getCourseByField(field?: string, value?: any, siteId?: string): Promise { @@ -424,15 +424,15 @@ export class CoreCoursesProvider { /** * Get courses. They can be filtered by field. * - * @param {string} [field] The field to search. Can be left empty for all courses or: - * id: course id. - * ids: comma separated course ids. - * shortname: course short name. - * idnumber: course id number. - * category: category id the course belongs to. - * @param {any} [value] The value to match. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Promise resolved with the courses. + * @param field The field to search. Can be left empty for all courses or: + * id: course id. + * ids: comma separated course ids. + * shortname: course short name. + * idnumber: course id number. + * category: category id the course belongs to. + * @param value The value to match. + * @param siteId Site ID. If not defined, use current site. + * @return Promise resolved with the courses. * @since 3.2 */ getCoursesByField(field?: string, value?: any, siteId?: string): Promise { @@ -503,9 +503,9 @@ export class CoreCoursesProvider { /** * Get cache key for get courses WS call. * - * @param {string} [field] The field to search. - * @param {any} [value] The value to match. - * @return {string} Cache key. + * @param field The field to search. + * @param value The value to match. + * @return Cache key. */ protected getCoursesByFieldCacheKey(field?: string, value?: any): string { field = field || ''; @@ -517,8 +517,8 @@ export class CoreCoursesProvider { /** * Check if get courses by field WS is available in a certain site. * - * @param {CoreSite} [site] Site to check. - * @return {boolean} Whether get courses by field is available. + * @param site Site to check. + * @return Whether get courses by field is available. * @since 3.2 */ isGetCoursesByFieldAvailable(site?: CoreSite): boolean { @@ -530,8 +530,8 @@ export class CoreCoursesProvider { /** * Check if get courses by field WS is available in a certain site, by site ID. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean: whether get courses by field is available. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: whether get courses by field is available. * @since 3.2 */ isGetCoursesByFieldAvailableInSite(siteId?: string): Promise { @@ -543,9 +543,9 @@ export class CoreCoursesProvider { /** * Get the navigation and administration options for the given courses. * - * @param {number[]} courseIds IDs of courses to get. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise<{navOptions: any, admOptions: any}>} Promise resolved with the options for each course. + * @param courseIds IDs of courses to get. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the options for each course. */ getCoursesAdminAndNavOptions(courseIds: number[], siteId?: string): Promise<{ navOptions: any, admOptions: any }> { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -580,7 +580,7 @@ export class CoreCoursesProvider { /** * Get the common part of the cache keys for user administration options WS calls. * - * @return {string} Cache key. + * @return Cache key. */ protected getUserAdministrationOptionsCommonCacheKey(): string { return this.ROOT_CACHE_KEY + 'administrationOptions:'; @@ -589,8 +589,8 @@ export class CoreCoursesProvider { /** * Get cache key for get user administration options WS call. * - * @param {number[]} courseIds IDs of courses to get. - * @return {string} Cache key. + * @param courseIds IDs of courses to get. + * @return Cache key. */ protected getUserAdministrationOptionsCacheKey(courseIds: number[]): string { return this.getUserAdministrationOptionsCommonCacheKey() + courseIds.join(','); @@ -599,9 +599,9 @@ export class CoreCoursesProvider { /** * Get user administration options for a set of courses. * - * @param {number[]} courseIds IDs of courses to get. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with administration options for each course. + * @param courseIds IDs of courses to get. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with administration options for each course. */ getUserAdministrationOptions(courseIds: number[], siteId?: string): Promise { if (!courseIds || courseIds.length == 0) { @@ -627,8 +627,8 @@ export class CoreCoursesProvider { /** * Get the common part of the cache keys for user navigation options WS calls. * - * @param {number[]} courseIds IDs of courses to get. - * @return {string} Cache key. + * @param courseIds IDs of courses to get. + * @return Cache key. */ protected getUserNavigationOptionsCommonCacheKey(): string { return this.ROOT_CACHE_KEY + 'navigationOptions:'; @@ -637,7 +637,7 @@ export class CoreCoursesProvider { /** * Get cache key for get user navigation options WS call. * - * @return {string} Cache key. + * @return Cache key. */ protected getUserNavigationOptionsCacheKey(courseIds: number[]): string { return this.getUserNavigationOptionsCommonCacheKey() + courseIds.join(','); @@ -646,9 +646,9 @@ export class CoreCoursesProvider { /** * Get user navigation options for a set of courses. * - * @param {number[]} courseIds IDs of courses to get. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with navigation options for each course. + * @param courseIds IDs of courses to get. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with navigation options for each course. */ getUserNavigationOptions(courseIds: number[], siteId?: string): Promise { if (!courseIds || courseIds.length == 0) { @@ -674,8 +674,8 @@ export class CoreCoursesProvider { /** * Format user navigation or administration options. * - * @param {any[]} courses Navigation or administration options for each course. - * @return {any} Formatted options. + * @param courses Navigation or administration options for each course. + * @return Formatted options. */ protected formatUserAdminOrNavOptions(courses: any[]): any { const result = {}; @@ -699,10 +699,10 @@ export class CoreCoursesProvider { * Get a course the user is enrolled in. This function relies on getUserCourses. * preferCache=true will try to speed up the response, but the data returned might not be updated. * - * @param {number} id ID of the course to get. - * @param {boolean} [preferCache] True if shouldn't call WS if data is cached, false otherwise. - * @param {string} [siteId] Site to get the courses from. If not defined, use current site. - * @return {Promise} Promise resolved with the course. + * @param id ID of the course to get. + * @param preferCache True if shouldn't call WS if data is cached, false otherwise. + * @param siteId Site to get the courses from. If not defined, use current site. + * @return Promise resolved with the course. */ getUserCourse(id: number, preferCache?: boolean, siteId?: string): Promise { if (!id) { @@ -725,9 +725,9 @@ export class CoreCoursesProvider { /** * Get user courses. * - * @param {boolean} [preferCache] True if shouldn't call WS if data is cached, false otherwise. - * @param {string} [siteId] Site to get the courses from. If not defined, use current site. - * @return {Promise} Promise resolved with the courses. + * @param preferCache True if shouldn't call WS if data is cached, false otherwise. + * @param siteId Site to get the courses from. If not defined, use current site. + * @return Promise resolved with the courses. */ getUserCourses(preferCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -800,7 +800,7 @@ export class CoreCoursesProvider { /** * Get cache key for get user courses WS call. * - * @return {string} Cache key. + * @return Cache key. */ protected getUserCoursesCacheKey(): string { return this.ROOT_CACHE_KEY + 'usercourses'; @@ -809,10 +809,10 @@ export class CoreCoursesProvider { /** * Invalidates get categories WS call. * - * @param {number} categoryId Category ID to get. - * @param {boolean} [addSubcategories] If it should add subcategories to the list. - * @param {string} [siteId] Site Id. If not defined, use current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param categoryId Category ID to get. + * @param addSubcategories If it should add subcategories to the list. + * @param siteId Site Id. If not defined, use current site. + * @return Promise resolved when the data is invalidated. */ invalidateCategories(categoryId: number, addSubcategories?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -823,9 +823,9 @@ export class CoreCoursesProvider { /** * Invalidates get course WS call. * - * @param {number} id Course ID. - * @param {string} [siteId] Site Id. If not defined, use current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param id Course ID. + * @param siteId Site Id. If not defined, use current site. + * @return Promise resolved when the data is invalidated. */ invalidateCourse(id: number, siteId?: string): Promise { return this.invalidateCourses([id], siteId); @@ -834,9 +834,9 @@ export class CoreCoursesProvider { /** * Invalidates get course enrolment methods WS call. * - * @param {number} id Course ID. - * @param {string} [siteId] Site Id. If not defined, use current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param id Course ID. + * @param siteId Site Id. If not defined, use current site. + * @return Promise resolved when the data is invalidated. */ invalidateCourseEnrolmentMethods(id: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -847,9 +847,9 @@ export class CoreCoursesProvider { /** * Invalidates get course guest enrolment info WS call. * - * @param {number} instanceId Guest instance ID. - * @param {string} [siteId] Site Id. If not defined, use current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param instanceId Guest instance ID. + * @param siteId Site Id. If not defined, use current site. + * @return Promise resolved when the data is invalidated. */ invalidateCourseGuestEnrolmentInfo(instanceId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -860,9 +860,9 @@ export class CoreCoursesProvider { /** * Invalidates the navigation and administration options for the given courses. * - * @param {number[]} courseIds IDs of courses to get. - * @param {string} [siteId] Site ID to invalidate. If not defined, use current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param courseIds IDs of courses to get. + * @param siteId Site ID to invalidate. If not defined, use current site. + * @return Promise resolved when the data is invalidated. */ invalidateCoursesAdminAndNavOptions(courseIds: number[], siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -880,9 +880,9 @@ export class CoreCoursesProvider { /** * Invalidates get courses WS call. * - * @param {number[]} ids Courses IDs. - * @param {string} [siteId] Site Id. If not defined, use current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param ids Courses IDs. + * @param siteId Site Id. If not defined, use current site. + * @return Promise resolved when the data is invalidated. */ invalidateCourses(ids: number[], siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -893,10 +893,10 @@ export class CoreCoursesProvider { /** * Invalidates get courses by field WS call. * - * @param {string} [field] See getCoursesByField for info. - * @param {any} [value] The value to match. - * @param {string} [siteId] Site Id. If not defined, use current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param field See getCoursesByField for info. + * @param value The value to match. + * @param siteId Site Id. If not defined, use current site. + * @return Promise resolved when the data is invalidated. */ invalidateCoursesByField(field?: string, value?: any, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -914,8 +914,8 @@ export class CoreCoursesProvider { /** * Invalidates all user administration options. * - * @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. */ invalidateUserAdministrationOptions(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -926,9 +926,9 @@ export class CoreCoursesProvider { /** * Invalidates user administration options for certain courses. * - * @param {number[]} courseIds IDs of courses. - * @param {string} [siteId] Site ID to invalidate. If not defined, use current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param courseIds IDs of courses. + * @param siteId Site ID to invalidate. If not defined, use current site. + * @return Promise resolved when the data is invalidated. */ invalidateUserAdministrationOptionsForCourses(courseIds: number[], siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -939,8 +939,8 @@ export class CoreCoursesProvider { /** * Invalidates get user courses 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. */ invalidateUserCourses(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -951,8 +951,8 @@ export class CoreCoursesProvider { /** * Invalidates all user navigation options. * - * @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. */ invalidateUserNavigationOptions(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -963,9 +963,9 @@ export class CoreCoursesProvider { /** * Invalidates user navigation options for certain courses. * - * @param {number[]} courseIds IDs of courses. - * @param {string} [siteId] Site ID to invalidate. If not defined, use current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param courseIds IDs of courses. + * @param siteId Site ID to invalidate. If not defined, use current site. + * @return Promise resolved when the data is invalidated. */ invalidateUserNavigationOptionsForCourses(courseIds: number[], siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -976,7 +976,7 @@ export class CoreCoursesProvider { /** * Check if WS to retrieve guest enrolment data is available. * - * @return {boolean} Whether guest WS is available. + * @return Whether guest WS is available. */ isGuestWSAvailable(): boolean { const currentSite = this.sitesProvider.getCurrentSite(); @@ -987,11 +987,11 @@ export class CoreCoursesProvider { /** * Search courses. * - * @param {string} text Text to search. - * @param {number} [page=0] Page to get. - * @param {number} [perPage] Number of courses per page. Defaults to CoreCoursesProvider.SEARCH_PER_PAGE. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise<{total: number, courses: any[]}>} Promise resolved with the courses and the total of matches. + * @param text Text to search. + * @param page Page to get. + * @param perPage Number of courses per page. Defaults to CoreCoursesProvider.SEARCH_PER_PAGE. + * @param siteId Site ID. If not defined, use current site. + * @return Promise resolved with the courses and the total of matches. */ search(text: string, page: number = 0, perPage?: number, siteId?: string): Promise<{ total: number, courses: any[] }> { perPage = perPage || CoreCoursesProvider.SEARCH_PER_PAGE; @@ -1016,12 +1016,12 @@ export class CoreCoursesProvider { /** * Self enrol current user in a certain course. * - * @param {number} courseId Course ID. - * @param {string} [password] Password to use. - * @param {number} [instanceId] Enrol instance ID. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Promise resolved if the user is enrolled. If the password is invalid, the promise is rejected - * with an object with code = CoreCoursesProvider.ENROL_INVALID_KEY. + * @param courseId Course ID. + * @param password Password to use. + * @param instanceId Enrol instance ID. + * @param siteId Site ID. If not defined, use current site. + * @return Promise resolved if the user is enrolled. If the password is invalid, the promise is rejected + * with an object with code = CoreCoursesProvider.ENROL_INVALID_KEY. */ selfEnrol(courseId: number, password: string = '', instanceId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1064,10 +1064,10 @@ export class CoreCoursesProvider { /** * Set favourite property on a course. * - * @param {number} courseId Course ID. - * @param {boolean} favourite If favourite or unfavourite. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Promise resolved when done. + * @param courseId Course ID. + * @param favourite If favourite or unfavourite. + * @param siteId Site ID. If not defined, use current site. + * @return Promise resolved when done. */ setFavouriteCourse(courseId: number, favourite: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { diff --git a/src/core/courses/providers/dashboard-link-handler.ts b/src/core/courses/providers/dashboard-link-handler.ts index aaa0d7ab5..32c25891b 100644 --- a/src/core/courses/providers/dashboard-link-handler.ts +++ b/src/core/courses/providers/dashboard-link-handler.ts @@ -33,11 +33,11 @@ export class CoreCoursesDashboardLinkHandler 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 { @@ -52,11 +52,11 @@ export class CoreCoursesDashboardLinkHandler extends CoreContentLinksHandlerBase /** * Check if the handler is enabled for a certain site (site + user) and a URL. * - * @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.mainMenuHandler.isEnabledForSite(siteId); diff --git a/src/core/courses/providers/dashboard.ts b/src/core/courses/providers/dashboard.ts index beb6204d0..ebf5c538b 100644 --- a/src/core/courses/providers/dashboard.ts +++ b/src/core/courses/providers/dashboard.ts @@ -29,8 +29,8 @@ export class CoreCoursesDashboardProvider { /** * Get cache key for dashboard blocks WS calls. * - * @param {number} [userId] User ID. Default, 0 means current user. - * @return {string} Cache key. + * @param userId User ID. Default, 0 means current user. + * @return Cache key. */ protected getDashboardBlocksCacheKey(userId: number = 0): string { return this.ROOT_CACHE_KEY + 'blocks:' + userId; @@ -39,9 +39,9 @@ export class CoreCoursesDashboardProvider { /** * Get dashboard blocks. * - * @param {number} [userId] User ID. Default, current user. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the list of blocks. + * @param userId User ID. Default, current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the list of blocks. * @since 3.6 */ getDashboardBlocks(userId?: number, siteId?: string): Promise { @@ -67,9 +67,9 @@ export class CoreCoursesDashboardProvider { /** * Invalidates dashboard blocks WS call. * - * @param {number} [userId] User ID. Default, current user. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param userId User ID. Default, current user. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateDashboardBlocks(userId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -80,8 +80,8 @@ export class CoreCoursesDashboardProvider { /** * Returns whether or not block based Dashboard 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. * @since 3.6 */ isAvailable(siteId?: string): Promise { @@ -98,8 +98,8 @@ export class CoreCoursesDashboardProvider { /** * Check if Site Home 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) => { @@ -110,8 +110,8 @@ export class CoreCoursesDashboardProvider { /** * Check if Site Home 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(); diff --git a/src/core/courses/providers/enrol-push-click-handler.ts b/src/core/courses/providers/enrol-push-click-handler.ts index 795b0c603..93016330f 100644 --- a/src/core/courses/providers/enrol-push-click-handler.ts +++ b/src/core/courses/providers/enrol-push-click-handler.ts @@ -33,8 +33,8 @@ export class CoreCoursesEnrolPushClickHandler implements CorePushNotificationsCl /** * 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.indexOf('enrol_') === 0 && @@ -44,8 +44,8 @@ export class CoreCoursesEnrolPushClickHandler implements CorePushNotificationsCl /** * 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 courseId = Number(notification.courseid), diff --git a/src/core/courses/providers/helper.ts b/src/core/courses/providers/helper.ts index abd9634fe..7289734d3 100644 --- a/src/core/courses/providers/helper.ts +++ b/src/core/courses/providers/helper.ts @@ -35,8 +35,8 @@ export class CoreCoursesHelperProvider { /** * Get the courses to display the course picker popover. If a courseId is specified, it will also return its categoryId. * - * @param {number} [courseId] Course ID to get the category. - * @return {Promise<{courses: any[], categoryId: number}>} Promise resolved with the list of courses and the category. + * @param courseId Course ID to get the category. + * @return Promise resolved with the list of courses and the category. */ getCoursesForPopover(courseId?: number): Promise<{courses: any[], categoryId: number}> { return this.coursesProvider.getUserCourses(false).then((courses) => { @@ -71,8 +71,8 @@ export class CoreCoursesHelperProvider { * Given a course object returned by core_enrol_get_users_courses and another one returned by core_course_get_courses_by_field, * load some extra data to the first one. * - * @param {any} course Course returned by core_enrol_get_users_courses. - * @param {any} courseByField Course returned by core_course_get_courses_by_field. + * @param course Course returned by core_enrol_get_users_courses. + * @param courseByField Course returned by core_course_get_courses_by_field. */ loadCourseExtraInfo(course: any, courseByField: any): void { if (courseByField) { @@ -93,8 +93,8 @@ export class CoreCoursesHelperProvider { * Given a list of courses returned by core_enrol_get_users_courses, load some extra data using the WebService * core_course_get_courses_by_field if available. * - * @param {any[]} courses List of courses. - * @return {Promise} Promise resolved when done. + * @param courses List of courses. + * @return Promise resolved when done. */ loadCoursesExtraInfo(courses: any[]): Promise { if (courses[0] && typeof courses[0].overviewfiles != 'undefined' && typeof courses[0].displayname != 'undefined') { @@ -128,10 +128,10 @@ export class CoreCoursesHelperProvider { /** * Get user courses with admin and nav options. * - * @param {string} [sort=fullname] Sort courses after get them. If sort is not defined it won't be sorted. - * @param {number} [slice=0] Slice results to get the X first one. If slice > 0 it will be done after sorting. - * @param {string} [filter] Filter using some field. - * @return {Promise} Courses filled with options. + * @param sort Sort courses after get them. If sort is not defined it won't be sorted. + * @param slice Slice results to get the X first one. If slice > 0 it will be done after sorting. + * @param filter Filter using some field. + * @return Courses filled with options. */ getUserCoursesWithOptions(sort: string = 'fullname', slice: number = 0, filter?: string): Promise { return this.coursesProvider.getUserCourses().then((courses) => { @@ -220,10 +220,10 @@ export class CoreCoursesHelperProvider { * Show a context menu to select a course, and return the courseId and categoryId of the selected course (-1 for all courses). * Returns an empty object if popover closed without picking a course. * - * @param {MouseEvent} event Click event. - * @param {any[]} courses List of courses, from CoreCoursesHelperProvider.getCoursesForPopover. - * @param {number} courseId The course to select at start. - * @return {Promise<{courseId?: number, categoryId?: number}>} Promise resolved with the course ID and category ID. + * @param event Click event. + * @param courses List of courses, from CoreCoursesHelperProvider.getCoursesForPopover. + * @param courseId The course to select at start. + * @return Promise resolved with the course ID and category ID. */ selectCourse(event: MouseEvent, courses: any[], courseId: number): Promise<{courseId?: number, categoryId?: number}> { return new Promise((resolve, reject): any => { diff --git a/src/core/courses/providers/mainmenu-handler.ts b/src/core/courses/providers/mainmenu-handler.ts index 4b32ced23..81f5f3e01 100644 --- a/src/core/courses/providers/mainmenu-handler.ts +++ b/src/core/courses/providers/mainmenu-handler.ts @@ -36,7 +36,7 @@ export class CoreDashboardMainMenuHandler implements CoreMainMenuHandler { /** * 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.isEnabledForSite(); @@ -45,8 +45,8 @@ export class CoreDashboardMainMenuHandler implements CoreMainMenuHandler { /** * Check if the handler is enabled on a certain site. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {boolean | Promise} Whether or not the handler is enabled on a site level. + * @param siteId Site ID. If not defined, current site. + * @return Whether or not the handler is enabled on a site level. */ isEnabledForSite(siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -92,7 +92,7 @@ export class CoreDashboardMainMenuHandler 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/core/courses/providers/request-push-click-handler.ts b/src/core/courses/providers/request-push-click-handler.ts index af8688605..4f9246595 100644 --- a/src/core/courses/providers/request-push-click-handler.ts +++ b/src/core/courses/providers/request-push-click-handler.ts @@ -39,8 +39,8 @@ export class CoreCoursesRequestPushClickHandler 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 { // Don't support 'courserequestrejected', that way the app will open the notifications page. @@ -51,8 +51,8 @@ export class CoreCoursesRequestPushClickHandler 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 { const courseId = Number(notification.courseid); diff --git a/src/core/emulator/classes/filesystem.ts b/src/core/emulator/classes/filesystem.ts index d750073dc..8aa3333dc 100644 --- a/src/core/emulator/classes/filesystem.ts +++ b/src/core/emulator/classes/filesystem.ts @@ -34,10 +34,10 @@ export class EntryMock { /** * Copy the file or directory. * - * @param {Entry} parent The folder where to move the file to. - * @param {string} newName The new name for the file. - * @param {Function} successCallback Success callback. - * @param {Function} errorCallback Error callback. + * @param parent The folder where to move the file to. + * @param newName The new name for the file. + * @param successCallback Success callback. + * @param errorCallback Error callback. */ copyTo(parent: Entry, newName: string, successCallback: Function, errorCallback: Function): void { newName = newName || this.name; @@ -69,8 +69,8 @@ export class EntryMock { /** * Get the entry's metadata. * - * @param {Function} successCallback Success callback. - * @param {Function} errorCallback Error callback. + * @param successCallback Success callback. + * @param errorCallback Error callback. */ getMetadata(successCallback: Function, errorCallback: Function): void { this.fs.stat(this.fullPath, (err, stats) => { @@ -88,8 +88,8 @@ export class EntryMock { /** * Get the parent directory. * - * @param {Function} successCallback Success callback. - * @param {Function} errorCallback Error callback. + * @param successCallback Success callback. + * @param errorCallback Error callback. */ getParent(successCallback: Function, errorCallback: Function): void { // Remove last slash if present and get the path of the parent. @@ -111,10 +111,10 @@ export class EntryMock { /** * Move the file or directory. * - * @param {Entry} parent The folder where to move the file to. - * @param {string} newName The new name for the file. - * @param {Function} successCallback Success callback. - * @param {Function} errorCallback Error callback. + * @param parent The folder where to move the file to. + * @param newName The new name for the file. + * @param successCallback Success callback. + * @param errorCallback Error callback. */ moveTo(parent: Entry, newName: string, successCallback: Function, errorCallback: Function): void { newName = newName || this.name; @@ -136,8 +136,8 @@ export class EntryMock { /** * Remove the entry. * - * @param {Function} successCallback Success callback. - * @param {Function} errorCallback Error callback. + * @param successCallback Success callback. + * @param errorCallback Error callback. */ remove(successCallback: Function, errorCallback: Function): void { const removeFn = this.isDirectory ? this.fs.rmdir : this.fs.unlink; @@ -153,9 +153,9 @@ export class EntryMock { /** * Set the entry's metadata. * - * @param {Function} successCallback Success callback. - * @param {Function} errorCallback Error callback. - * @param {any} metadataObject The metadata to set. + * @param successCallback Success callback. + * @param errorCallback Error callback. + * @param metadataObject The metadata to set. */ setMetadata(successCallback: Function, errorCallback: Function, metadataObject: any): void { // Not supported. @@ -165,7 +165,7 @@ export class EntryMock { /** * Get the internal URL of the Entry. * - * @return {string} Internal URL. + * @return Internal URL. */ toInternalURL(): string { return 'file://' + this.fullPath; @@ -174,7 +174,7 @@ export class EntryMock { /** * Get the URL of the Entry. * - * @return {string} URL. + * @return URL. */ toURL(): string { return this.fullPath; @@ -203,7 +203,7 @@ export class DirectoryEntryMock extends EntryMock { /** * Create reader. * - * @return {DirectoryReader} Reader. + * @return Reader. */ createReader(): DirectoryReader { return new DirectoryReaderMock(this.textUtils, this.mimeUtils, this.fullPath); @@ -212,9 +212,9 @@ export class DirectoryEntryMock extends EntryMock { /** * Delete an empty folder. * - * @param {string} path Path of the folder. - * @param {Function} successCallback Success callback. - * @param {Function} errorCallback Error callback. + * @param path Path of the folder. + * @param successCallback Success callback. + * @param errorCallback Error callback. */ protected deleteEmptyFolder(path: string, successCallback: Function, errorCallback: Function): void { this.fs.rmdir(path, (err) => { @@ -230,10 +230,10 @@ export class DirectoryEntryMock extends EntryMock { /** * Get a directory inside this directory entry. * - * @param {string} path Path of the dir. - * @param {any} options Options. - * @param {Function} successCallback Success callback. - * @param {Function} errorCallback Error callback. + * @param path Path of the dir. + * @param options Options. + * @param successCallback Success callback. + * @param errorCallback Error callback. */ getDirectory(path: string, options: any, successCallback: Function, errorCallback: Function): void { this.getDirOrFile(true, path, options, successCallback, errorCallback); @@ -242,11 +242,11 @@ export class DirectoryEntryMock extends EntryMock { /** * Helper function for getDirectory and getFile. * - * @param {boolean} isDir True if getting a directory, false if getting a file. - * @param {string} path Path of the file or dir. - * @param {any} options Options. - * @param {Function} successCallback Success callback. - * @param {Function} errorCallback Error callback. + * @param isDir True if getting a directory, false if getting a file. + * @param path Path of the file or dir. + * @param options Options. + * @param successCallback Success callback. + * @param errorCallback Error callback. */ protected getDirOrFile(isDir: boolean, path: string, options: any, successCallback: Function, errorCallback: Function): void { @@ -315,10 +315,10 @@ export class DirectoryEntryMock extends EntryMock { /** * Get a file inside this directory entry. * - * @param {string} path Path of the dir. - * @param {any} options Options. - * @param {Function} successCallback Success callback. - * @param {Function} errorCallback Error callback. + * @param path Path of the dir. + * @param options Options. + * @param successCallback Success callback. + * @param errorCallback Error callback. */ getFile(path: string, options: any, successCallback: Function, errorCallback: Function): void { this.getDirOrFile(false, path, options, successCallback, errorCallback); @@ -327,8 +327,8 @@ export class DirectoryEntryMock extends EntryMock { /** * Remove the directory and all its contents. * - * @param {Function} successCallback Success callback. - * @param {Function} errorCallback Error callback. + * @param successCallback Success callback. + * @param errorCallback Error callback. */ removeRecursively(successCallback: Function, errorCallback: Function): void { // Use a promise to make sure only one callback is called. @@ -344,9 +344,9 @@ export class DirectoryEntryMock extends EntryMock { /** * Delete a file or folder recursively. * - * @param {string} path Path of the folder. - * @param {Function} successCallback Success callback. - * @param {Function} errorCallback Error callback. + * @param path Path of the folder. + * @param successCallback Success callback. + * @param errorCallback Error callback. */ protected removeRecursiveFn(path: string, successCallback: Function, errorCallback: Function): void { // Check if it exists. @@ -415,8 +415,8 @@ export class FileEntryMock extends EntryMock { /** * Create writer. * - * @param {Function} successCallback Success callback. - * @param {Function} errorCallback Error callback. + * @param successCallback Success callback. + * @param errorCallback Error callback. */ createWriter(successCallback: Function, errorCallback: Function): void { this.file((file) => { @@ -427,8 +427,8 @@ export class FileEntryMock extends EntryMock { /** * Get the file data. * - * @param {Function} successCallback Success callback. - * @param {Function} errorCallback Error callback. + * @param successCallback Success callback. + * @param errorCallback Error callback. */ file(successCallback: Function, errorCallback: Function): void { // Get the metadata to know the time modified. @@ -469,8 +469,8 @@ export class DirectoryReaderMock implements DirectoryReader { /** * Read entries inside a folder. * - * @param {Function} successCallback Success callback. - * @param {Function} errorCallback Error callback. + * @param successCallback Success callback. + * @param errorCallback Error callback. */ readEntries(successCallback: Function, errorCallback: Function): void { this.fs.readdir(this.localURL, (err, files) => { @@ -545,8 +545,8 @@ export class FileWriterMock { /** * The file position at which the next write will occur. * - * @param {number} offset If nonnegative, an absolute byte offset into the file. - * If negative, an offset back from the end of the file. + * @param offset If nonnegative, an absolute byte offset into the file. + * If negative, an offset back from the end of the file. */ seek(offset: number): void { this.position = offset; @@ -556,7 +556,7 @@ export class FileWriterMock { * Changes the length of the file to that specified. If shortening the file, data beyond the new length * will be discarded. If extending the file, the existing data will be zero-padded up to the new length. * - * @param {number} size The size to which the length of the file is to be adjusted, measured in bytes. + * @param size The size to which the length of the file is to be adjusted, measured in bytes. */ truncate(size: number): void { this.size = size; @@ -565,7 +565,7 @@ export class FileWriterMock { /** * Write some data into the file. * - * @param {any} data The data to write. + * @param data The data to write. */ write(data: any): void { if (data && data.toString() == '[object Blob]') { @@ -589,7 +589,7 @@ export class FileWriterMock { /** * Write some data into the file. * - * @param {Buffer} data The data to write. + * @param data The data to write. */ protected writeFile(data: Buffer): void { /* Create a write stream so we can specify where to start writing. Node's Writable stream doesn't allow specifying the diff --git a/src/core/emulator/classes/inappbrowserobject.ts b/src/core/emulator/classes/inappbrowserobject.ts index 1f45c7ba1..513061406 100644 --- a/src/core/emulator/classes/inappbrowserobject.ts +++ b/src/core/emulator/classes/inappbrowserobject.ts @@ -109,8 +109,8 @@ export class InAppBrowserObjectMock { /** * Execute a JS script. * - * @param {any} details Details of the script to run, specifying either a file or code key. - * @return {Promise} Promise resolved when done. + * @param details Details of the script to run, specifying either a file or code key. + * @return Promise resolved when done. */ executeScript(details: any): Promise { return new Promise((resolve, reject): void => { @@ -129,8 +129,8 @@ export class InAppBrowserObjectMock { /** * Recursive function to get the launch URL from the contents of a BrowserWindow. * - * @param {number} [retry=0] Retry number. - * @return {Promise} Promise resolved with the launch URL. + * @param retry Retry number. + * @return Promise resolved with the launch URL. */ protected getLaunchUrl(retry: number = 0): Promise { @@ -172,8 +172,8 @@ export class InAppBrowserObjectMock { /** * Insert CSS. * - * @param {any} details Details of the CSS to insert, specifying either a file or code key. - * @return {Promise} Promise resolved when done. + * @param details Details of the CSS to insert, specifying either a file or code key. + * @return Promise resolved when done. */ insertCSS(details: any): Promise { return new Promise((resolve, reject): void => { @@ -194,9 +194,9 @@ export class InAppBrowserObjectMock { /** * Listen to events happening. * - * @param {string} name Name of the event. - * @return {Observable} Observable that will listen to the event on subscribe, and will stop listening - * to the event on unsubscribe. + * @param name Name of the event. + * @return Observable that will listen to the event on subscribe, and will stop listening + * to the event on unsubscribe. */ on(name: string): Observable { // Create the observable. diff --git a/src/core/emulator/classes/sqlitedb.ts b/src/core/emulator/classes/sqlitedb.ts index 5aedfcfe3..fd39efa59 100644 --- a/src/core/emulator/classes/sqlitedb.ts +++ b/src/core/emulator/classes/sqlitedb.ts @@ -25,7 +25,7 @@ export class SQLiteDBMock extends SQLiteDB { /** * Create and open the database. * - * @param {string} name Database name. + * @param name Database name. */ constructor(public name: string) { super(name, null, null); @@ -34,7 +34,7 @@ export class SQLiteDBMock extends SQLiteDB { /** * Close the database. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ close(): Promise { // WebSQL databases aren't closed. @@ -44,7 +44,7 @@ export class SQLiteDBMock extends SQLiteDB { /** * Drop all the data in the database. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ emptyDatabase(): Promise { return new Promise((resolve, reject): void => { @@ -84,9 +84,9 @@ export class SQLiteDBMock extends 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 new Promise((resolve, reject): void => { @@ -107,8 +107,8 @@ export class SQLiteDBMock extends 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 new Promise((resolve, reject): void => { @@ -156,7 +156,7 @@ export class SQLiteDBMock extends SQLiteDB { /** * Open the database. Only needed if it was closed before, a database is automatically opened when created. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ open(): Promise { // WebSQL databases can't closed, so the open method isn't needed. diff --git a/src/core/emulator/pages/capture-media/capture-media.ts b/src/core/emulator/pages/capture-media/capture-media.ts index 360183441..5c9377569 100644 --- a/src/core/emulator/pages/capture-media/capture-media.ts +++ b/src/core/emulator/pages/capture-media/capture-media.ts @@ -157,7 +157,7 @@ export class CoreEmulatorCaptureMediaPage implements OnInit, OnDestroy { * Initialize the audio drawer. This code has been extracted from MDN's example on MediaStream Recording: * https://github.com/mdn/web-dictaphone * - * @param {MediaStream} stream Stream returned by getUserMedia. + * @param stream Stream returned by getUserMedia. */ protected initAudioDrawer(stream: MediaStream): void { let skip = true, @@ -318,7 +318,7 @@ export class CoreEmulatorCaptureMediaPage implements OnInit, OnDestroy { /** * Close the modal, returning some data (success). * - * @param {any} data Data to return. + * @param data Data to return. */ dismissWithData(data: any): void { this.viewCtrl.dismiss(data, 'success'); @@ -327,9 +327,9 @@ export class CoreEmulatorCaptureMediaPage implements OnInit, OnDestroy { /** * Close the modal, returning an error. * - * @param {number} code Error code. Will not be used if it's a Camera capture. - * @param {string} message Error message. - * @param {string} [cameraMessage] A specific message to use if it's a Camera capture. If not set, message will be used. + * @param code Error code. Will not be used if it's a Camera capture. + * @param message Error message. + * @param cameraMessage A specific message to use if it's a Camera capture. If not set, message will be used. */ dismissWithError(code: number, message: string, cameraMessage?: string): void { const isCamera = this.isImage && !this.isCaptureImage, diff --git a/src/core/emulator/providers/badge.ts b/src/core/emulator/providers/badge.ts index 713ef67a1..f3d843c9f 100644 --- a/src/core/emulator/providers/badge.ts +++ b/src/core/emulator/providers/badge.ts @@ -26,8 +26,6 @@ export class BadgeMock implements Badge { /** * Clear the badge of the app icon. - * - * @returns {Promise} */ clear(): Promise { return Promise.reject('clear is only supported in mobile devices'); @@ -35,8 +33,7 @@ export class BadgeMock implements Badge { /** * Set the badge of the app icon. - * @param {number} badgeNumber The new badge number. - * @returns {Promise} + * @param badgeNumber The new badge number. */ set(badgeNumber: number): Promise { if (!this.appProvider.isDesktop()) { @@ -57,8 +54,6 @@ export class BadgeMock implements Badge { /** * Get the badge of the app icon. - * - * @returns {Promise} */ get(): Promise { if (!this.appProvider.isDesktop()) { @@ -77,8 +72,7 @@ export class BadgeMock implements Badge { /** * Increase the badge number. * - * @param {number} increaseBy Count to add to the current badge number - * @returns {Promise} + * @param increaseBy Count to add to the current badge number */ increase(increaseBy: number): Promise { return Promise.reject('increase is only supported in mobile devices'); @@ -87,8 +81,7 @@ export class BadgeMock implements Badge { /** * Decrease the badge number. * - * @param {number} decreaseBy Count to subtract from the current badge number - * @returns {Promise} + * @param decreaseBy Count to subtract from the current badge number */ decrease(decreaseBy: number): Promise { return Promise.reject('decrease is only supported in mobile devices'); @@ -96,8 +89,6 @@ export class BadgeMock implements Badge { /** * Check support to show badges. - * - * @returns {Promise} */ isSupported(): Promise { return Promise.reject('isSupported is only supported in mobile devices'); @@ -105,8 +96,6 @@ export class BadgeMock implements Badge { /** * Determine if the app has permission to show badges. - * - * @returns {Promise} */ hasPermission(): Promise { return Promise.reject('hasPermission is only supported in mobile devices'); @@ -114,8 +103,6 @@ export class BadgeMock implements Badge { /** * Register permission to set badge notifications - * - * @returns {Promise} */ requestPermission(): Promise { return Promise.reject('requestPermission is only supported in mobile devices'); diff --git a/src/core/emulator/providers/camera.ts b/src/core/emulator/providers/camera.ts index 323b6d7fe..4fe7eaa00 100644 --- a/src/core/emulator/providers/camera.ts +++ b/src/core/emulator/providers/camera.ts @@ -29,7 +29,7 @@ export class CameraMock extends Camera { /** * Remove intermediate image files that are kept in temporary storage after calling camera.getPicture. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ cleanup(): Promise { // This function is iOS only, nothing to do. @@ -39,8 +39,8 @@ export class CameraMock extends Camera { /** * Take a picture. * - * @param {CameraOptions} options Options that you want to pass to the camera. - * @return {Promise} Promise resolved when captured. + * @param options Options that you want to pass to the camera. + * @return Promise resolved when captured. */ getPicture(options: CameraOptions): Promise { return this.captureHelper.captureMedia('image', options); diff --git a/src/core/emulator/providers/capture-helper.ts b/src/core/emulator/providers/capture-helper.ts index cf66fbf92..33d174d09 100644 --- a/src/core/emulator/providers/capture-helper.ts +++ b/src/core/emulator/providers/capture-helper.ts @@ -44,9 +44,9 @@ export class CoreEmulatorCaptureHelperProvider { /** * Capture media (image, audio, video). * - * @param {string} type Type of media: image, audio, video. - * @param {any} [options] Optional options. - * @return {Promise} Promise resolved when captured, rejected if error. + * @param type Type of media: image, audio, video. + * @param options Optional options. + * @return Promise resolved when captured, rejected if error. */ captureMedia(type: string, options: any): Promise { options = options || {}; @@ -118,9 +118,9 @@ export class CoreEmulatorCaptureHelperProvider { /** * Get the mimetype and extension to capture media. * - * @param {string} type Type of media: image, audio, video. - * @param {string[]} [mimetypes] List of supported mimetypes. If undefined, all mimetypes supported. - * @return {{extension: string, mimetype: string}} An object with mimetype and extension to use. + * @param type Type of media: image, audio, video. + * @param mimetypes List of supported mimetypes. If undefined, all mimetypes supported. + * @return An object with mimetype and extension to use. */ protected getMimeTypeAndExtension(type: string, mimetypes: string[]): { extension: string, mimetype: string } { const result: any = {}; @@ -157,7 +157,7 @@ export class CoreEmulatorCaptureHelperProvider { /** * Init the getUserMedia function, using a deprecated function as fallback if the new one doesn't exist. * - * @return {boolean} Whether the function is supported. + * @return Whether the function is supported. */ protected initGetUserMedia(): boolean { const nav = navigator; @@ -209,7 +209,7 @@ export class CoreEmulatorCaptureHelperProvider { /** * Load the Mocks that need it. * - * @return {Promise} Promise resolved when loaded. + * @return Promise resolved when loaded. */ load(): Promise { if (typeof this.win.MediaRecorder != 'undefined' && this.initGetUserMedia()) { diff --git a/src/core/emulator/providers/clipboard.ts b/src/core/emulator/providers/clipboard.ts index 40390a883..bf3a8e081 100644 --- a/src/core/emulator/providers/clipboard.ts +++ b/src/core/emulator/providers/clipboard.ts @@ -43,8 +43,8 @@ export class ClipboardMock extends Clipboard { /** * Copy some text to the clipboard. * - * @param {string} text The text to copy. - * @return {Promise} Promise resolved when copied. + * @param text The text to copy. + * @return Promise resolved when copied. */ copy(text: string): Promise { return new Promise((resolve, reject): void => { @@ -74,7 +74,7 @@ export class ClipboardMock extends Clipboard { /* * Get the text stored in the clipboard. * - * @return {Promise} Promise resolved with the text. + * @return Promise resolved with the text. */ paste(): Promise { return new Promise((resolve, reject): void => { diff --git a/src/core/emulator/providers/file-opener.ts b/src/core/emulator/providers/file-opener.ts index e5ae16579..d739950c5 100644 --- a/src/core/emulator/providers/file-opener.ts +++ b/src/core/emulator/providers/file-opener.ts @@ -29,8 +29,8 @@ export class FileOpenerMock extends FileOpener { /** * Check if an app is already installed. * - * @param {string} packageId Package ID. - * @returns {Promise} Promise resolved when done. + * @param packageId Package ID. + * @return Promise resolved when done. */ appIsInstalled(packageId: string): Promise { return Promise.reject('appIsInstalled not supported in browser or dekstop.'); @@ -39,9 +39,9 @@ export class FileOpenerMock extends FileOpener { /** * Open an file. * - * @param {string} filePath File path. - * @param {string} fileMIMEType File MIME type. - * @returns {Promise} Promise resolved when done. + * @param filePath File path. + * @param fileMIMEType File MIME type. + * @return Promise resolved when done. */ open(filePath: string, fileMIMEType: string): Promise { if (this.appProvider.isDesktop()) { @@ -61,8 +61,8 @@ export class FileOpenerMock extends FileOpener { /** * Uninstalls a package. * - * @param {string} packageId Package ID. - * @returns {Promise} Promise resolved when done. + * @param packageId Package ID. + * @return Promise resolved when done. */ uninstall(packageId: string): Promise { return Promise.reject('uninstall not supported in browser or dekstop.'); diff --git a/src/core/emulator/providers/file-transfer.ts b/src/core/emulator/providers/file-transfer.ts index f8ab362c9..c3b250c9c 100644 --- a/src/core/emulator/providers/file-transfer.ts +++ b/src/core/emulator/providers/file-transfer.ts @@ -44,8 +44,6 @@ export class FileTransferMock extends FileTransfer { /** * Creates a new FileTransferObjectMock object. - * - * @return {FileTransferObjectMock} */ create(): FileTransferObjectMock { return new FileTransferObjectMock(this.appProvider, this.fileProvider); @@ -80,11 +78,11 @@ export class FileTransferObjectMock extends FileTransferObject { /** * Downloads a file from server. * - * @param {string} source URL of the server to download the file, as encoded by encodeURI(). - * @param {string} target Filesystem url representing the file on the device. - * @param {boolean} [trustAllHosts] If set to true, it accepts all security certificates. - * @param {object} [options] Optional parameters, currently only supports headers. - * @returns {Promise} Returns a Promise that resolves to a FileEntry object. + * @param source URL of the server to download the file, as encoded by encodeURI(). + * @param target Filesystem url representing the file on the device. + * @param trustAllHosts If set to true, it accepts all security certificates. + * @param options Optional parameters, currently only supports headers. + * @return Returns a Promise that resolves to a FileEntry object. */ download(source: string, target: string, trustAllHosts?: boolean, options?: { [s: string]: any; }): Promise { return new Promise((resolve, reject): void => { @@ -165,8 +163,8 @@ export class FileTransferObjectMock extends FileTransferObject { * Given a URL, check if it has a credentials in it and, if so, return them in a header object. * This code is extracted from Cordova FileTransfer plugin. * - * @param {string} urlString The URL to get the credentials from. - * @return {any} The header with the credentials, null if no credentials. + * @param urlString The URL to get the credentials from. + * @return The header with the credentials, null if no credentials. */ protected getBasicAuthHeader(urlString: string): any { let header = null; @@ -191,8 +189,8 @@ export class FileTransferObjectMock extends FileTransferObject { /** * Given an instance of XMLHttpRequest, get the response headers as an object. * - * @param {XMLHttpRequest} xhr XMLHttpRequest instance. - * @return {{[s: string]: any}} Object with the headers. + * @param xhr XMLHttpRequest instance. + * @return Object with the headers. */ protected getHeadersAsObject(xhr: XMLHttpRequest): { [s: string]: any } { const headersString = xhr.getAllResponseHeaders(), @@ -216,8 +214,8 @@ export class FileTransferObjectMock extends FileTransferObject { * Get the credentials from a URL. * This code is extracted from Cordova FileTransfer plugin. * - * @param {string} urlString The URL to get the credentials from. - * @return {string} Retrieved credentials. + * @param urlString The URL to get the credentials from. + * @return Retrieved credentials. */ protected getUrlCredentials(urlString: string): string { const credentialsPattern = /^https?\:\/\/(?:(?:(([^:@\/]*)(?::([^@\/]*))?)?@)?([^:\/?#]*)(?::(\d*))?).*$/, @@ -229,7 +227,7 @@ export class FileTransferObjectMock extends FileTransferObject { /** * Registers a listener that gets called whenever a new chunk of data is transferred. * - * @param {Function} listener Listener that takes a progress event. + * @param listener Listener that takes a progress event. */ onProgress(listener: (event: ProgressEvent) => any): void { this.progressListener = listener; @@ -238,8 +236,8 @@ export class FileTransferObjectMock extends FileTransferObject { /** * Same as Javascript's JSON.parse, but it will handle errors. * - * @param {string} json JSON text. - * @return {any} JSON parsed as object or what it gets. + * @param json JSON text. + * @return JSON parsed as object or what it gets. */ protected parseJSON(json: string): any { try { @@ -254,8 +252,8 @@ export class FileTransferObjectMock extends FileTransferObject { /** * Parse a response, converting it into text and the into an object if needed. * - * @param {any} response The response to parse. - * @return {Promise} Promise resolved with the parsed response. + * @param response The response to parse. + * @return Promise resolved with the parsed response. */ protected parseResponse(response: any): Promise { return new Promise((resolve, reject): void => { @@ -283,11 +281,11 @@ export class FileTransferObjectMock extends FileTransferObject { /** * Sends a file to a server. * - * @param {string} fileUrl Filesystem URL representing the file on the device or a data URI. - * @param {string} url URL of the server to receive the file, as encoded by encodeURI(). - * @param {FileUploadOptions} [options] Optional parameters. - * @param {boolean} [trustAllHosts] If set to true, it accepts all security certificates. - * @returns {Promise} Promise that resolves to a FileUploadResult and rejects with FileTransferError. + * @param fileUrl Filesystem URL representing the file on the device or a data URI. + * @param url URL of the server to receive the file, as encoded by encodeURI(). + * @param options Optional parameters. + * @param trustAllHosts If set to true, it accepts all security certificates. + * @return Promise that resolves to a FileUploadResult and rejects with FileTransferError. */ upload(fileUrl: string, url: string, options?: FileUploadOptions, trustAllHosts?: boolean): Promise { return new Promise((resolve, reject): void => { diff --git a/src/core/emulator/providers/file.ts b/src/core/emulator/providers/file.ts index 891d983be..46c1b89e9 100644 --- a/src/core/emulator/providers/file.ts +++ b/src/core/emulator/providers/file.ts @@ -35,9 +35,9 @@ export class FileMock extends File { /** * Check if a directory exists in a certain path, directory. * - * @param {string} path Base FileSystem. - * @param {string} dir Name of directory to check - * @returns {Promise} Returns a Promise that resolves to true if the directory exists or rejects with an error. + * @param path Base FileSystem. + * @param dir Name of directory to check + * @return Returns a Promise that resolves to true if the directory exists or rejects with an error. */ checkDir(path: string, dir: string): Promise { const fullPath = this.textUtils.concatenatePaths(path, dir); @@ -50,9 +50,9 @@ export class FileMock extends File { /** * Check if a file exists in a certain path, directory. * - * @param {string} path Base FileSystem. - * @param {string} file Name of file to check. - * @returns {Promise} Returns a Promise that resolves with a boolean or rejects with an error. + * @param path Base FileSystem. + * @param file Name of file to check. + * @return Returns a Promise that resolves with a boolean or rejects with an error. */ checkFile(path: string, file: string): Promise { return this.resolveLocalFilesystemUrl(this.textUtils.concatenatePaths(path, file)).then((fse) => { @@ -70,10 +70,10 @@ export class FileMock extends File { /** * Copy a file or directory. * - * @param {Entry} srce The Entry to copy. - * @param {DirectoryEntry} destDir The directory where to put the copy. - * @param {string} newName New name of the file/dir. - * @returns {Promise} Returns a Promise that resolves to the new Entry object or rejects with an error. + * @param srce The Entry to copy. + * @param destDir The directory where to put the copy. + * @param newName New name of the file/dir. + * @return Returns a Promise that resolves to the new Entry object or rejects with an error. */ private copyMock(srce: Entry, destDir: DirectoryEntry, newName: string): Promise { return new Promise((resolve, reject): void => { @@ -91,11 +91,11 @@ export class FileMock extends File { /** * Copy a directory in various methods. If destination directory exists, will fail to copy. * - * @param {string} path Base FileSystem. Please refer to the iOS and Android filesystems above. - * @param {string} dirName Name of directory to copy. - * @param {string} newPath Base FileSystem of new location. - * @param {string} newDirName New name of directory to copy to (leave blank to remain the same). - * @returns {Promise} Returns a Promise that resolves to the new Entry object or rejects with an error. + * @param path Base FileSystem. Please refer to the iOS and Android filesystems above. + * @param dirName Name of directory to copy. + * @param newPath Base FileSystem of new location. + * @param newDirName New name of directory to copy to (leave blank to remain the same). + * @return Returns a Promise that resolves to the new Entry object or rejects with an error. */ copyDir(path: string, dirName: string, newPath: string, newDirName: string): Promise { return this.resolveDirectoryUrl(path).then((fse) => { @@ -110,11 +110,11 @@ export class FileMock extends File { /** * Copy a file in various methods. If file exists, will fail to copy. * - * @param {string} path Base FileSystem. Please refer to the iOS and Android filesystems above - * @param {string} fileName Name of file to copy - * @param {string} newPath Base FileSystem of new location - * @param {string} newFileName New name of file to copy to (leave blank to remain the same) - * @returns {Promise} Returns a Promise that resolves to an Entry or rejects with an error. + * @param path Base FileSystem. Please refer to the iOS and Android filesystems above + * @param fileName Name of file to copy + * @param newPath Base FileSystem of new location + * @param newFileName New name of file to copy to (leave blank to remain the same) + * @return Returns a Promise that resolves to an Entry or rejects with an error. */ copyFile(path: string, fileName: string, newPath: string, newFileName: string): Promise { newFileName = newFileName || fileName; @@ -133,10 +133,10 @@ export class FileMock extends File { * The replace boolean value determines whether to replace an existing directory with the same name. * If an existing directory exists and the replace value is false, the promise will fail and return an error. * - * @param {string} path Base FileSystem. - * @param {string} dirName Name of directory to create - * @param {boolean} replace If true, replaces file with same name. If false returns error - * @returns {Promise} Returns a Promise that resolves with a DirectoryEntry or rejects with an error. + * @param path Base FileSystem. + * @param dirName Name of directory to create + * @param replace If true, replaces file with same name. If false returns error + * @return Returns a Promise that resolves with a DirectoryEntry or rejects with an error. */ createDir(path: string, dirName: string, replace: boolean): Promise { const options: any = { @@ -157,10 +157,10 @@ export class FileMock extends File { * The replace boolean value determines whether to replace an existing file with the same name. * If an existing file exists and the replace value is false, the promise will fail and return an error. * - * @param {string} path Base FileSystem. - * @param {string} fileName Name of file to create. - * @param {boolean} replace If true, replaces file with same name. If false returns error. - * @returns {Promise} Returns a Promise that resolves to a FileEntry or rejects with an error. + * @param path Base FileSystem. + * @param fileName Name of file to create. + * @param replace If true, replaces file with same name. If false returns error. + * @return Returns a Promise that resolves to a FileEntry or rejects with an error. */ createFile(path: string, fileName: string, replace: boolean): Promise { const options: any = { @@ -179,8 +179,8 @@ export class FileMock extends File { /** * Create a file writer for a certain file. * - * @param {FileEntry} fe File entry object. - * @returns {Promise} Promise resolved with the FileWriter. + * @param fe File entry object. + * @return Promise resolved with the FileWriter. */ private createWriterMock(fe: FileEntry): Promise { return new Promise((resolve, reject): void => { @@ -197,7 +197,7 @@ export class FileMock extends File { * Emulate Cordova file plugin using NodeJS functions. This is only for NodeJS environments, * browser works with the default resolveLocalFileSystemURL. * - * @param {any} fs Node module 'fs'. + * @param fs Node module 'fs'. */ protected emulateCordovaFileForDesktop(fs: any): void { if (!this.appProvider.isDesktop()) { @@ -224,7 +224,7 @@ export class FileMock extends File { /** * Fill the message for an error. * - * @param {any} err Error. + * @param err Error. */ private fillErrorMessageMock(err: any): void { try { @@ -237,10 +237,9 @@ export class FileMock extends File { /** * Get a directory. * - * @param directoryEntry {DirectoryEntry} Directory entry, obtained by resolveDirectoryUrl method - * @param directoryName {string} Directory name - * @param flags {Flags} Options - * @returns {Promise} + * @param directoryEntry Directory entry, obtained by resolveDirectoryUrl method + * @param directoryName Directory name + * @param flags Options */ getDirectory(directoryEntry: DirectoryEntry, directoryName: string, flags: Flags): Promise { return new Promise((resolve, reject): void => { @@ -262,10 +261,9 @@ export class FileMock extends File { /** * Get a file - * @param directoryEntry {DirectoryEntry} Directory entry, obtained by resolveDirectoryUrl method - * @param fileName {string} File name - * @param flags {Flags} Options - * @returns {Promise} + * @param directoryEntry Directory entry, obtained by resolveDirectoryUrl method + * @param fileName File name + * @param flags Options */ getFile(directoryEntry: DirectoryEntry, fileName: string, flags: Flags): Promise { return new Promise((resolve, reject): void => { @@ -286,7 +284,7 @@ export class FileMock extends File { /** * Get free disk space. * - * @return {Promise} Promise resolved with the free space. + * @return Promise resolved with the free space. */ getFreeDiskSpace(): Promise { // FRequest a file system instance with a minimum size until we get an error. @@ -329,9 +327,9 @@ export class FileMock extends File { /** * List files and directory from a given path. * - * @param {string} path Base FileSystem. Please refer to the iOS and Android filesystems above - * @param {string} dirName Name of directory - * @returns {Promise} Returns a Promise that resolves to an array of Entry objects or rejects with an error. + * @param path Base FileSystem. Please refer to the iOS and Android filesystems above + * @param dirName Name of directory + * @return Returns a Promise that resolves to an array of Entry objects or rejects with an error. */ listDir(path: string, dirName: string): Promise { return this.resolveDirectoryUrl(path).then((fse) => { @@ -346,7 +344,7 @@ export class FileMock extends File { /** * Loads an initialize the API for browser and desktop. * - * @return {Promise} Promise resolved when loaded. + * @return Promise resolved when loaded. */ load(): Promise { return new Promise((resolve, reject): void => { @@ -404,10 +402,10 @@ export class FileMock extends File { /** * Move a file or directory. * - * @param {Entry} srce The Entry to copy. - * @param {DirectoryEntry} destDir The directory where to move the file/dir. - * @param {string} newName New name of the file/dir. - * @returns {Promise} Returns a Promise that resolves to the new Entry object or rejects with an error. + * @param srce The Entry to copy. + * @param destDir The directory where to move the file/dir. + * @param newName New name of the file/dir. + * @return Returns a Promise that resolves to the new Entry object or rejects with an error. */ private moveMock(srce: Entry, destDir: DirectoryEntry, newName: string): Promise { return new Promise((resolve, reject): void => { @@ -425,12 +423,12 @@ export class FileMock extends File { /** * Move a directory to a given path. * - * @param {string} path The source path to the directory. - * @param {string} dirName The source directory name. - * @param {string} newPath The destionation path to the directory. - * @param {string} newDirName The destination directory name. - * @returns {Promise} Returns a Promise that resolves to the new DirectoryEntry object or rejects with - * an error. + * @param path The source path to the directory. + * @param dirName The source directory name. + * @param newPath The destionation path to the directory. + * @param newDirName The destination directory name. + * @return Returns a Promise that resolves to the new DirectoryEntry object or rejects with + * an error. */ moveDir(path: string, dirName: string, newPath: string, newDirName: string): Promise { return this.resolveDirectoryUrl(path).then((fse) => { @@ -445,11 +443,11 @@ export class FileMock extends File { /** * Move a file to a given path. * - * @param {string} path Base FileSystem. Please refer to the iOS and Android filesystems above - * @param {string} fileName Name of file to move - * @param {string} newPath Base FileSystem of new location - * @param {string} newFileName New name of file to move to (leave blank to remain the same) - * @returns {Promise} Returns a Promise that resolves to the new Entry or rejects with an error. + * @param path Base FileSystem. Please refer to the iOS and Android filesystems above + * @param fileName Name of file to move + * @param newPath Base FileSystem of new location + * @param newFileName New name of file to move to (leave blank to remain the same) + * @return Returns a Promise that resolves to the new Entry or rejects with an error. */ moveFile(path: string, fileName: string, newPath: string, newFileName: string): Promise { newFileName = newFileName || fileName; @@ -465,10 +463,10 @@ export class FileMock extends File { /** * Read file and return data as an ArrayBuffer. - * @param {string} path Base FileSystem. - * @param {string} file Name of file, relative to path. - * @returns {Promise} Returns a Promise that resolves with the contents of the file as ArrayBuffer or rejects - * with an error. + * @param path Base FileSystem. + * @param file Name of file, relative to path. + * @return Returns a Promise that resolves with the contents of the file as ArrayBuffer or rejects + * with an error. */ readAsArrayBuffer(path: string, file: string): Promise { return this.readFileMock(path, file, 'ArrayBuffer'); @@ -476,9 +474,9 @@ export class FileMock extends File { /** * Read file and return data as a binary data. - * @param {string} path Base FileSystem. - * @param {string} file Name of file, relative to path. - * @returns {Promise} Returns a Promise that resolves with the contents of the file as string rejects with an error. + * @param path Base FileSystem. + * @param file Name of file, relative to path. + * @return Returns a Promise that resolves with the contents of the file as string rejects with an error. */ readAsBinaryString(path: string, file: string): Promise { return this.readFileMock(path, file, 'BinaryString'); @@ -488,10 +486,10 @@ export class FileMock extends File { * Read file and return data as a base64 encoded data url. * A data url is of the form: * data: [][;base64], - * @param {string} path Base FileSystem. - * @param {string} file Name of file, relative to path. - * @returns {Promise} Returns a Promise that resolves with the contents of the file as data URL or rejects - * with an error. + * @param path Base FileSystem. + * @param file Name of file, relative to path. + * @return Returns a Promise that resolves with the contents of the file as data URL or rejects + * with an error. */ readAsDataURL(path: string, file: string): Promise { return this.readFileMock(path, file, 'DataURL'); @@ -500,9 +498,9 @@ export class FileMock extends File { /** * Read the contents of a file as text. * - * @param {string} path Base FileSystem. - * @param {string} file Name of file, relative to path. - * @returns {Promise} Returns a Promise that resolves with the contents of the file as string or rejects with an error. + * @param path Base FileSystem. + * @param file Name of file, relative to path. + * @return Returns a Promise that resolves with the contents of the file as string or rejects with an error. */ readAsText(path: string, file: string): Promise { return this.readFileMock(path, file, 'Text'); @@ -511,8 +509,8 @@ export class FileMock extends File { /** * Read all the files and directories inside a directory. * - * @param {DirectoryReader} dr The directory reader. - * @return {Promise} Promise resolved with the list of files/dirs. + * @param dr The directory reader. + * @return Promise resolved with the list of files/dirs. */ private readEntriesMock(dr: DirectoryReader): Promise { return new Promise((resolve, reject): void => { @@ -528,10 +526,10 @@ export class FileMock extends File { /** * Read the contents of a file. * - * @param {string} path Base FileSystem. - * @param {string} file Name of file, relative to path. - * @param {string} readAs Format to read as. - * @returns {Promise} Returns a Promise that resolves with the contents of the file or rejects with an error. + * @param path Base FileSystem. + * @param file Name of file, relative to path. + * @param readAs Format to read as. + * @return Returns a Promise that resolves with the contents of the file or rejects with an error. */ private readFileMock(path: string, file: string, readAs: 'ArrayBuffer' | 'BinaryString' | 'DataURL' | 'Text'): Promise { return this.resolveDirectoryUrl(path).then((directoryEntry: DirectoryEntry) => { @@ -562,8 +560,8 @@ export class FileMock extends File { /** * Delete a file. * - * @param {Entry} fe The file to remove. - * @return {Promise} Promise resolved when done. + * @param fe The file to remove. + * @return Promise resolved when done. */ private removeMock(fe: Entry): Promise { return new Promise((resolve, reject): void => { @@ -579,9 +577,9 @@ export class FileMock extends File { /** * Remove a directory at a given path. * - * @param {string} path The path to the directory. - * @param {string} dirName The directory name. - * @returns {Promise} Returns a Promise that resolves to a RemoveResult or rejects with an error. + * @param path The path to the directory. + * @param dirName The directory name. + * @return Returns a Promise that resolves to a RemoveResult or rejects with an error. */ removeDir(path: string, dirName: string): Promise { return this.resolveDirectoryUrl(path).then((fse) => { @@ -594,9 +592,9 @@ export class FileMock extends File { /** * Removes a file from a desired location. * - * @param {string} path Base FileSystem. - * @param {string} fileName Name of file to remove. - * @returns {Promise} Returns a Promise that resolves to a RemoveResult or rejects with an error. + * @param path Base FileSystem. + * @param fileName Name of file to remove. + * @return Returns a Promise that resolves to a RemoveResult or rejects with an error. */ removeFile(path: string, fileName: string): Promise { return this.resolveDirectoryUrl(path).then((fse) => { @@ -609,9 +607,9 @@ export class FileMock extends File { /** * Removes all files and the directory from a desired location. * - * @param {string} path Base FileSystem. Please refer to the iOS and Android filesystems above - * @param {string} dirName Name of directory - * @returns {Promise} Returns a Promise that resolves with a RemoveResult or rejects with an error. + * @param path Base FileSystem. Please refer to the iOS and Android filesystems above + * @param dirName Name of directory + * @return Returns a Promise that resolves with a RemoveResult or rejects with an error. */ removeRecursively(path: string, dirName: string): Promise { return this.resolveDirectoryUrl(path).then((fse) => { @@ -623,8 +621,7 @@ export class FileMock extends File { /** * Resolves a local directory url - * @param directoryUrl {string} directory system url - * @returns {Promise} + * @param directoryUrl directory system url */ resolveDirectoryUrl(directoryUrl: string): Promise { return this.resolveLocalFilesystemUrl(directoryUrl).then((de) => { @@ -641,8 +638,7 @@ export class FileMock extends File { /** * Resolves a local file system URL - * @param fileUrl {string} file system url - * @returns {Promise} + * @param fileUrl file system url */ resolveLocalFilesystemUrl(fileUrl: string): Promise { return new Promise((resolve, reject): void => { @@ -663,8 +659,8 @@ export class FileMock extends File { /** * Remove a directory and all its contents. * - * @param {DirectoryEntry} de Directory to remove. - * @return {Promise} Promise resolved when done. + * @param de Directory to remove. + * @return Promise resolved when done. */ private rimrafMock(de: DirectoryEntry): Promise { return new Promise((resolve, reject): void => { @@ -680,9 +676,9 @@ export class FileMock extends File { /** * Write some data in a file. * - * @param {FileWriter} writer File writer. - * @param {any} data The data to write. - * @return {Promise} Promise resolved when done. + * @param writer File writer. + * @param data The data to write. + * @return Promise resolved when done. */ private writeMock(writer: FileWriter, data: any): Promise { if (data instanceof Blob) { @@ -704,10 +700,10 @@ export class FileMock extends File { /** * Write to an existing file. * - * @param {string} path Base FileSystem. - * @param {string} fileName path relative to base path. - * @param {string | Blob} text content or blob to write. - * @returns {Promise} Returns a Promise that resolves or rejects with an error. + * @param path Base FileSystem. + * @param fileName path relative to base path. + * @param text content or blob to write. + * @return Returns a Promise that resolves or rejects with an error. */ writeExistingFile(path: string, fileName: string, text: string | Blob): Promise { return this.writeFile(path, fileName, text, { replace: true }); @@ -716,11 +712,11 @@ export class FileMock extends File { /** * Write a new file to the desired location. * - * @param {string} path Base FileSystem. Please refer to the iOS and Android filesystems above - * @param {string} fileName path relative to base path - * @param {string | Blob} text content or blob to write - * @param {any} options replace file if set to true. See WriteOptions for more information. - * @returns {Promise} Returns a Promise that resolves to updated file entry or rejects with an error. + * @param path Base FileSystem. Please refer to the iOS and Android filesystems above + * @param fileName path relative to base path + * @param text content or blob to write + * @param options replace file if set to true. See WriteOptions for more information. + * @return Returns a Promise that resolves to updated file entry or rejects with an error. */ writeFile(path: string, fileName: string, text: string | Blob | ArrayBuffer, options: IWriteOptions = {}): Promise { const getFileOpts: any = { @@ -738,10 +734,10 @@ export class FileMock extends File { /** * Write content to FileEntry. * - * @param {FileEntry} fe File entry object. - * @param {string | Blob} text Content or blob to write. - * @param {IWriteOptions} options replace file if set to true. See WriteOptions for more information. - * @returns {Promise} Returns a Promise that resolves to updated file entry or rejects with an error. + * @param fe File entry object. + * @param text Content or blob to write. + * @param options replace file if set to true. See WriteOptions for more information. + * @return Returns a Promise that resolves to updated file entry or rejects with an error. */ private writeFileEntryMock(fe: FileEntry, text: string | Blob | ArrayBuffer, options: IWriteOptions): Promise { return this.createWriterMock(fe).then((writer) => { @@ -760,9 +756,9 @@ export class FileMock extends File { /** * Write a file in chunks. * - * @param {FileWriter} writer File writer. - * @param {Blob} data Data to write. - * @return {Promise} Promise resolved when done. + * @param writer File writer. + * @param data Data to write. + * @return Promise resolved when done. */ private writeFileInChunksMock(writer: FileWriter, data: Blob): Promise { let writtenSize = 0; diff --git a/src/core/emulator/providers/globalization.ts b/src/core/emulator/providers/globalization.ts index 9cd70aa17..c5b902c54 100644 --- a/src/core/emulator/providers/globalization.ts +++ b/src/core/emulator/providers/globalization.ts @@ -41,9 +41,9 @@ export class GlobalizationMock extends Globalization { /** * Converts date to string. * - * @param {Date} date Date you wish to convert + * @param date Date you wish to convert * @param options Options for the converted date. Length, selector. - * @returns {Promise<{value: string}>} Returns a promise when the date has been converted. + * @return Returns a promise when the date has been converted. */ dateToString(date: Date, options: GlobalizationOptions): Promise<{ value: string; }> { return Promise.reject(new GlobalizationErrorMock(GlobalizationErrorMock.UNKNOWN_ERROR, 'Not supported.')); @@ -53,8 +53,7 @@ export class GlobalizationMock extends Globalization { * Returns a pattern string to format and parse currency values according to the client's user preferences and ISO 4217 * currency code. * - * @param {string} currencyCode Currency Code. - * @returns {Promise} + * @param currencyCode Currency Code. */ getCurrencyPattern(currencyCode: string): Promise { return Promise.reject(new GlobalizationErrorMock(GlobalizationErrorMock.UNKNOWN_ERROR, 'Not supported.')); @@ -63,7 +62,7 @@ export class GlobalizationMock extends Globalization { /** * Get the current locale. * - * @return {string} Locale name. + * @return Locale name. */ private getCurrentlocale(): string { // Get browser language. @@ -85,7 +84,7 @@ export class GlobalizationMock extends Globalization { * Returns an array of the names of the months or days of the week, depending on the client's user preferences and calendar. * * @param options Object with type (narrow or wide) and item (month or days). - * @returns {Promise<{value: Array}>} Returns a promise. + * @return Returns a promise. */ getDateNames(options: { type: string; item: string; }): Promise<{ value: Array; }> { return Promise.reject(new GlobalizationErrorMock(GlobalizationErrorMock.UNKNOWN_ERROR, 'Not supported.')); @@ -95,7 +94,7 @@ export class GlobalizationMock extends Globalization { * Returns a pattern string to format and parse dates according to the client's user preferences. * * @param options Object with the format length and selector - * @returns {Promise} Returns a promise. + * @return Returns a promise. */ getDatePattern(options: GlobalizationOptions): Promise { return Promise.reject(new GlobalizationErrorMock(GlobalizationErrorMock.UNKNOWN_ERROR, 'Not supported.')); @@ -104,7 +103,7 @@ export class GlobalizationMock extends Globalization { /** * Returns the first day of the week according to the client's user preferences and calendar. * - * @returns {Promise<{value: string}>} returns a promise with the value + * @return returns a promise with the value */ getFirstDayOfWeek(): Promise<{ value: string; }> { return Promise.reject(new GlobalizationErrorMock(GlobalizationErrorMock.UNKNOWN_ERROR, 'Not supported.')); @@ -113,7 +112,7 @@ export class GlobalizationMock extends Globalization { /** * Get the current locale name. * - * @return {Promise<{value: string}>} Promise resolved with an object with the language string. + * @return Promise resolved with an object with the language string. */ getLocaleName(): Promise<{ value: string }> { const locale = this.getCurrentlocale(); @@ -129,7 +128,6 @@ export class GlobalizationMock extends Globalization { /** * Returns a pattern string to format and parse numbers according to the client's user preferences. * @param options Can be decimal, percent, or currency. - * @returns {Promise} */ getNumberPattern(options: { type: string; }): Promise { return Promise.reject(new GlobalizationErrorMock(GlobalizationErrorMock.UNKNOWN_ERROR, 'Not supported.')); @@ -138,7 +136,7 @@ export class GlobalizationMock extends Globalization { /* * Get the current preferred language. * - * @return {Promise<{value: string}>} Promise resolved with an object with the language string. + * @return Promise resolved with an object with the language string. */ getPreferredLanguage(): Promise<{ value: string }> { return this.getLocaleName(); @@ -147,8 +145,8 @@ export class GlobalizationMock extends Globalization { /** * Indicates whether daylight savings time is in effect for a given date using the client's time zone and calendar. * - * @param {data} date Date to process. - * @returns {Promise<{dst: string}>} reutrns a promise with the value + * @param date Date to process. + * @return reutrns a promise with the value */ isDayLightSavingsTime(date: Date): Promise<{ dst: string; }> { return Promise.reject(new GlobalizationErrorMock(GlobalizationErrorMock.UNKNOWN_ERROR, 'Not supported.')); @@ -156,8 +154,8 @@ export class GlobalizationMock extends Globalization { /** * Returns a number formatted as a string according to the client's user preferences. - * @param numberToConvert {Number} The number to convert - * @param options {Object} Object with property `type` that can be set to: decimal, percent, or currency. + * @param numberToConvert The number to convert + * @param options Object with property `type` that can be set to: decimal, percent, or currency. */ numberToString(numberToConvert: number, options: { type: string; }): Promise<{ value: string; }> { return Promise.reject(new GlobalizationErrorMock(GlobalizationErrorMock.UNKNOWN_ERROR, 'Not supported.')); @@ -167,9 +165,9 @@ export class GlobalizationMock extends Globalization { * Parses a date formatted as a string, according to the client's user preferences and calendar using the time zone of the * client, and returns the corresponding date object. * - * @param {string} dateString Date as a string to be converted + * @param dateString Date as a string to be converted * @param options Options for the converted date. Length, selector. - * @returns {Promise} Returns a promise when the date has been converted. + * @return Returns a promise when the date has been converted. */ stringToDate(dateString: string, options: GlobalizationOptions): Promise { return Promise.reject(new GlobalizationErrorMock(GlobalizationErrorMock.UNKNOWN_ERROR, 'Not supported.')); @@ -177,10 +175,10 @@ export class GlobalizationMock extends Globalization { /** * - * @param {string} stringToConvert String you want to conver to a number. + * @param stringToConvert String you want to conver to a number. * * @param options The type of number you want to return. Can be decimal, percent, or currency. - * @returns {Promise<{ value: number | string }>} Returns a promise with the value. + * @return Returns a promise with the value. */ stringToNumber(stringToConvert: string, options: { type: string; }): Promise<{ value: number | string; }> { return Promise.reject(new GlobalizationErrorMock(GlobalizationErrorMock.UNKNOWN_ERROR, 'Not supported.')); diff --git a/src/core/emulator/providers/helper.ts b/src/core/emulator/providers/helper.ts index fc64ed0eb..f389d3458 100644 --- a/src/core/emulator/providers/helper.ts +++ b/src/core/emulator/providers/helper.ts @@ -76,7 +76,7 @@ export class CoreEmulatorHelperProvider implements CoreInitHandler { /** * Load the Mocks that need it. * - * @return {Promise} Promise resolved when loaded. + * @return Promise resolved when loaded. */ load(): Promise { const promises = []; @@ -96,11 +96,11 @@ export class CoreEmulatorHelperProvider implements CoreInitHandler { * Check if there are new notifications, triggering a local notification if found. * Only for desktop apps since they don't support push notifications. * - * @param {string} component Component to check. - * @param {Function} fetchFn Function that receives a site ID and returns a Promise resolved with an array of notifications. - * @param {Function} getDataFn Function that receives a notification and returns a promise resolved with the title and text. - * @param {string} [siteId] Site ID to check. If not defined, check all sites. - * @return {Promise} Promise resolved when done. + * @param component Component to check. + * @param fetchFn Function that receives a site ID and returns a Promise resolved with an array of notifications. + * @param getDataFn Function that receives a notification and returns a promise resolved with the title and text. + * @param siteId Site ID to check. If not defined, check all sites. + * @return Promise resolved when done. */ checkNewNotifications(component: string, fetchFn: Function, getDataFn: Function, siteId?: string): Promise { if (!this.appProvider.isDesktop() || !this.localNotifProvider.isAvailable()) { @@ -134,11 +134,11 @@ export class CoreEmulatorHelperProvider implements CoreInitHandler { /** * Check if there are new notifications for a certain site, triggering a local notification if found. * - * @param {string} component Component to check. - * @param {Function} fetchFn Function that receives a site ID and returns a Promise resolved with an array of notifications. - * @param {Function} getDataFn Function that receives a notification and returns a promise resolved with the title and text. - * @param {string} siteId Site ID to check. - * @return {Promise} Promise resolved when done. + * @param component Component to check. + * @param fetchFn Function that receives a site ID and returns a Promise resolved with an array of notifications. + * @param getDataFn Function that receives a notification and returns a promise resolved with the title and text. + * @param siteId Site ID to check. + * @return Promise resolved when done. */ protected checkNewNotificationsForSite(component: string, fetchFn: Function, getDataFn: Function, siteId: string) : Promise { @@ -181,9 +181,9 @@ export class CoreEmulatorHelperProvider implements CoreInitHandler { /** * Get the last notification received in a certain site for a certain component. * - * @param {string} component Component of the notification to get. - * @param {string} siteId Site ID of the notification. - * @return {Promise} Promise resolved with the notification or false if not found. + * @param component Component of the notification to get. + * @param siteId Site ID of the notification. + * @return Promise resolved with the notification or false if not found. */ getLastReceivedNotification(component: string, siteId: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -196,10 +196,10 @@ export class CoreEmulatorHelperProvider implements CoreInitHandler { /** * Store the last notification received in a certain site. * - * @param {string} component Component of the notification to store. - * @param {any} notification Notification to store. - * @param {string} siteId Site ID of the notification. - * @return {Promise} Promise resolved when done. + * @param component Component of the notification to store. + * @param notification Notification to store. + * @param siteId Site ID of the notification. + * @return Promise resolved when done. */ storeLastReceivedNotification(component: string, notification: any, siteId: string): Promise { if (!notification) { diff --git a/src/core/emulator/providers/inappbrowser.ts b/src/core/emulator/providers/inappbrowser.ts index e991e6a90..3c151cebb 100644 --- a/src/core/emulator/providers/inappbrowser.ts +++ b/src/core/emulator/providers/inappbrowser.ts @@ -33,10 +33,10 @@ export class InAppBrowserMock extends InAppBrowser { /** * Opens a URL in a new InAppBrowser instance, the current browser instance, or the system browser. * - * @param {string} url The URL to load. - * @param {string} [target] The target in which to load the URL, an optional parameter that defaults to _self. - * @param {string} [options] Options for the InAppBrowser. - * @return {any} The new instance. + * @param url The URL to load. + * @param target The target in which to load the URL, an optional parameter that defaults to _self. + * @param options Options for the InAppBrowser. + * @return The new instance. */ create(url: string, target?: string, options: string = 'location=yes'): any { if (options && typeof options !== 'string') { diff --git a/src/core/emulator/providers/local-notifications.ts b/src/core/emulator/providers/local-notifications.ts index 73d6ebfc4..e8f506255 100644 --- a/src/core/emulator/providers/local-notifications.ts +++ b/src/core/emulator/providers/local-notifications.ts @@ -135,9 +135,8 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Adds a group of actions. * - * @param {any} groupId The id of the action group - * @param {ILocalNotificationAction[]} actions The actions of this group - * @returns {Promise} + * @param groupId The id of the action group + * @param actions The actions of this group */ addActions(groupId: any, actions: ILocalNotificationAction[]): Promise { return Promise.reject('Not supported in desktop apps.'); @@ -146,8 +145,8 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Cancels single or multiple notifications. * - * @param {any} notificationId A single notification id, or an array of notification ids. - * @returns {Promise} Returns a promise when the notification is canceled + * @param notificationId A single notification id, or an array of notification ids. + * @return Returns a promise when the notification is canceled */ cancel(notificationId: any): Promise { const promises = []; @@ -168,7 +167,7 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Cancels all notifications. * - * @returns {Promise} Returns a promise when all notifications are canceled. + * @return Returns a promise when all notifications are canceled. */ cancelAll(): Promise { return this.cancel(Object.keys(this.scheduled)).then(() => { @@ -183,10 +182,9 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Cancel a local notification. * - * @param {number} id Notification ID. - * @param {boolean} omitEvent If true, the clear/cancel event won't be triggered. - * @param {string} eventName Name of the event to trigger. - * @return {Void} + * @param id Notification ID. + * @param omitEvent If true, the clear/cancel event won't be triggered. + * @param eventName Name of the event to trigger. */ protected cancelNotification(id: number, omitEvent: boolean, eventName: string): void { if (!this.scheduled[id]) { @@ -210,8 +208,8 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Clears single or multiple notifications. * - * @param {any} notificationId A single notification id, or an array of notification ids. - * @returns {Promise} Returns a promise when the notification had been cleared. + * @param notificationId A single notification id, or an array of notification ids. + * @return Returns a promise when the notification had been cleared. */ clear(notificationId: any): Promise { const promises = []; @@ -233,7 +231,7 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Clears all notifications. * - * @returns {Promise} Returns a promise when all notifications have cleared + * @return Returns a promise when all notifications have cleared */ clearAll(): Promise { return this.clear(Object.keys(this.scheduled)).then(() => { @@ -249,8 +247,8 @@ export class LocalNotificationsMock extends LocalNotifications { * Convert a list of IDs to numbers. * Code extracted from the Cordova plugin. * - * @param {any[]} ids List of IDs. - * @return {number[]} List of IDs as numbers. + * @param ids List of IDs. + * @return List of IDs as numbers. */ protected convertIds(ids: any[]): number[] { const convertedIds = []; @@ -266,8 +264,8 @@ export class LocalNotificationsMock extends LocalNotifications { * Convert the notification options to their required type. * Code extracted from the Cordova plugin. * - * @param {ILocalNotification} notification Notification. - * @return {ILocalNotification} Converted notification. + * @param notification Notification. + * @return Converted notification. */ protected convertProperties(notification: ILocalNotification): ILocalNotification { if (notification.id) { @@ -306,9 +304,9 @@ export class LocalNotificationsMock extends LocalNotifications { * Parse a property to number, returning the default value if not valid. * Code extracted from the Cordova plugin. * - * @param {string} prop Name of property to convert. - * @param {any} notification Notification where to search the property. - * @return {number} Converted number or default value. + * @param prop Name of property to convert. + * @param notification Notification where to search the property. + * @return Converted number or default value. */ protected parseToInt(prop: string, notification: any): number { if (isNaN(notification[prop])) { @@ -322,8 +320,8 @@ export class LocalNotificationsMock extends LocalNotifications { * Convert the priority of a notification. * Code extracted from the Cordova plugin. * - * @param {any} notification Notification. - * @return {any} Notification. + * @param notification Notification. + * @return Notification. */ protected convertPriority(notification: any): any { let prio = notification.priority || notification.prio || 0; @@ -349,8 +347,8 @@ export class LocalNotificationsMock extends LocalNotifications { * Convert the actions of a notification. * Code extracted from the Cordova plugin. * - * @param {any} notification Notification. - * @return {any} Notification. + * @param notification Notification. + * @return Notification. */ protected convertActions(notification: any): any { const actions = []; @@ -381,8 +379,8 @@ export class LocalNotificationsMock extends LocalNotifications { * Convert the trigger of a notification. * Code extracted from the Cordova plugin. * - * @param {any} notification Notification. - * @return {any} Notification. + * @param notification Notification. + * @return Notification. */ protected convertTrigger(notification: any): any { const trigger = notification.trigger || {}; @@ -461,8 +459,8 @@ export class LocalNotificationsMock extends LocalNotifications { * Convert the progress bar of a notification. * Code extracted from the Cordova plugin. * - * @param {any} notification Notification. - * @return {any} Notification. + * @param notification Notification. + * @return Notification. */ protected convertProgressBar(notification: any): any { let cfg = notification.progressBar; @@ -493,9 +491,9 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Not an official interface, however its possible to manually fire events. * - * @param {string} eventName The name of the event. Available events: schedule, trigger, click, update, clear, clearall, cancel, + * @param eventName The name of the event. Available events: schedule, trigger, click, update, clear, clearall, cancel, * cancelall. Custom event names are possible for actions - * @param {any} args Optional arguments + * @param args Optional arguments */ fireEvent(eventName: string, args: any): void { if (this.observers[eventName]) { @@ -505,8 +503,6 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Fire queued events once the device is ready and all listeners are registered. - * - * @returns {Promise} */ fireQueuedEvents(): Promise { return Promise.resolve(); @@ -515,8 +511,7 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Get a notification object. * - * @param {any} notificationId The id of the notification to get. - * @returns {Promise} + * @param notificationId The id of the notification to get. */ get(notificationId: any): Promise { return Promise.resolve(this.getNotifications([Number(notificationId)], true, true)[0]); @@ -524,8 +519,6 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Get all notification objects. - * - * @returns {Promise>} */ getAll(): Promise> { return Promise.resolve(this.getNotifications(undefined, true, true)); @@ -534,7 +527,7 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Gets the (platform specific) default settings. * - * @returns {Promise} An object with all default settings + * @return An object with all default settings */ getDefaults(): Promise { return Promise.resolve(this.defaults); @@ -542,8 +535,6 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Get all the notification ids. - * - * @returns {Promise>} */ getIds(): Promise> { let ids = this.utils.mergeArraysWithoutDuplicates(Object.keys(this.scheduled), Object.keys(this.triggered)); @@ -557,7 +548,7 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Get all the notification stored in local DB. * - * @return {Promise} Promise resolved with the notifications. + * @return Promise resolved with the notifications. */ protected getAllNotifications(): Promise { return this.appDB.getAllRecords(this.DESKTOP_NOTIFS_TABLE).then((notifications) => { @@ -577,8 +568,6 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Get all scheduled notification objects. - * - * @returns {Promise>} */ getScheduled(): Promise> { return Promise.resolve(this.getNotifications(undefined, true, false)); @@ -586,8 +575,6 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Get all triggered notification objects. - * - * @returns {Promise>} */ getTriggered(): Promise> { return Promise.resolve(this.getNotifications(undefined, false, true)); @@ -596,10 +583,10 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Get a set of notifications. If ids isn't specified, return all the notifications. * - * @param {number[]} [ids] Ids of notifications to get. If not specified, get all notifications. - * @param {boolean} [getScheduled] Get scheduled notifications. - * @param {boolean} [getTriggered] Get triggered notifications. - * @return {ILocalNotification[]} List of notifications. + * @param ids Ids of notifications to get. If not specified, get all notifications. + * @param getScheduled Get scheduled notifications. + * @param getTriggered Get triggered notifications. + * @return List of notifications. */ protected getNotifications(ids?: number[], getScheduled?: boolean, getTriggered?: boolean): ILocalNotification[] { const notifications = []; @@ -626,8 +613,8 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Get the trigger "at" in milliseconds. * - * @param {ILocalNotification} notification Notification to get the trigger from. - * @return {number} Trigger time. + * @param notification Notification to get the trigger from. + * @return Trigger time. */ protected getNotificationTriggerAt(notification: ILocalNotification): number { const triggerAt = (notification.trigger && notification.trigger.at) || new Date(); @@ -642,7 +629,7 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Get the ids of scheduled notifications. * - * @returns {Promise>} Returns a promise + * @return Returns a promise */ getScheduledIds(): Promise> { const ids = Object.keys(this.scheduled).map((id) => { @@ -654,8 +641,6 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Get the ids of triggered notifications. - * - * @returns {Promise>} */ getTriggeredIds(): Promise> { const ids = Object.keys(this.triggered).map((id) => { @@ -668,8 +653,7 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Get the type (triggered, scheduled) for the notification. * - * @param {number} id The ID of the notification. - * @return {Promise} + * @param id The ID of the notification. */ getType(id: number): Promise { if (this.scheduled[id]) { @@ -685,9 +669,9 @@ export class LocalNotificationsMock extends LocalNotifications { * Given an object of options and a list of properties, return the first property that exists. * Code extracted from the Cordova plugin. * - * @param {ILocalNotification} notification Notification. - * @param {any[]} ...args List of keys to check. - * @return {any} First value found. + * @param notification Notification. + * @param ...args List of keys to check. + * @return First value found. */ protected getValueFor(notification: ILocalNotification, ...args: any[]): any { for (const i in args) { @@ -701,8 +685,8 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Checks if a group of actions is defined. * - * @param {any} groupId The id of the action group - * @returns {Promise} Whether the group is defined. + * @param groupId The id of the action group + * @return Whether the group is defined. */ hasActions(groupId: any): Promise { return Promise.resolve(false); @@ -710,8 +694,6 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Informs if the app has the permission to show notifications. - * - * @returns {Promise} */ hasPermission(): Promise { return Promise.resolve(true); @@ -720,9 +702,9 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Check if a notification has a given type. * - * @param {number} id The ID of the notification. - * @param {string} type The type of the notification. - * @returns {Promise} Promise resolved with boolean: whether it has the type. + * @param id The ID of the notification. + * @param type The type of the notification. + * @return Promise resolved with boolean: whether it has the type. */ hasType(id: number, type: string): Promise { return this.getType(id).then((notifType) => { @@ -733,8 +715,7 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Checks presence of a notification. * - * @param {number} notificationId Notification ID. - * @returns {Promise} + * @param notificationId Notification ID. */ isPresent(notificationId: number): Promise { return Promise.resolve(!!this.scheduled[notificationId] || !!this.triggered[notificationId]); @@ -743,8 +724,7 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Checks is a notification is scheduled. * - * @param {number} notificationId Notification ID. - * @returns {Promise} + * @param notificationId Notification ID. */ isScheduled(notificationId: number): Promise { return Promise.resolve(!!this.scheduled[notificationId]); @@ -752,8 +732,7 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Checks if a notification is triggered. * - * @param {number} notificationId Notification ID. - * @returns {Promise} + * @param notificationId Notification ID. */ isTriggered(notificationId: number): Promise { return Promise.resolve(!!this.triggered[notificationId]); @@ -762,7 +741,7 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Loads an initialize the API for desktop. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ load(): Promise { if (!this.appProvider.isDesktop()) { @@ -805,8 +784,8 @@ export class LocalNotificationsMock extends LocalNotifications { * Merge notification options with default values. * Code extracted from the Cordova plugin. * - * @param {ILocalNotification} notification Notification. - * @return {ILocalNotification} Treated notification. + * @param notification Notification. + * @return Treated notification. */ protected mergeWithDefaults(notification: ILocalNotification): ILocalNotification { const values = this.getDefaults(); @@ -835,7 +814,7 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Function called when a notification is clicked. * - * @param {ILocalNotification} notification Clicked notification. + * @param notification Clicked notification. */ protected notificationClicked(notification: ILocalNotification): void { this.fireEvent('click', notification); @@ -846,9 +825,9 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Sets a callback for a specific event. * - * @param {string} eventName The name of the event. Events: schedule, trigger, click, update, clear, clearall, cancel, - * cancelall. Custom event names are possible for actions. - * @return {Observable} Observable + * @param eventName The name of the event. Events: schedule, trigger, click, update, clear, clearall, cancel, + * cancelall. Custom event names are possible for actions. + * @return Observable */ on(eventName: string): Observable { return this.observers[eventName]; @@ -857,8 +836,8 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Parse a interval and convert it to a number of milliseconds (0 if not valid). * - * @param {string} every Interval to convert. - * @return {number} Number of milliseconds of the interval- + * @param every Interval to convert. + * @return Number of milliseconds of the interval- */ protected parseInterval(every: string): number { let interval; @@ -898,8 +877,7 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Removes a group of actions. * - * @param {any} groupId The id of the action group - * @returns {Promise} + * @param groupId The id of the action group */ removeActions(groupId: any): Promise { return Promise.reject('Not supported in desktop apps.'); @@ -908,8 +886,8 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Remove a notification from local DB. * - * @param {number} id ID of the notification. - * @return {Promise} Promise resolved when done. + * @param id ID of the notification. + * @return Promise resolved when done. */ protected removeNotification(id: number): Promise { return this.appDB.deleteRecords(this.DESKTOP_NOTIFS_TABLE, { id: id }); @@ -917,8 +895,6 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Request permission to show notifications if not already granted. - * - * @returns {Promise} */ requestPermission(): Promise { return Promise.resolve(true); @@ -927,7 +903,7 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Schedules a single or multiple notifications. * - * @param {ILocalNotification | Array} [options] Notification or notifications. + * @param options Notification or notifications. */ schedule(options?: ILocalNotification | Array): void { this.scheduleOrUpdate(options); @@ -938,8 +914,8 @@ export class LocalNotificationsMock extends LocalNotifications { * We only support using the "at" property to trigger the notification. Other properties like "in" or "every" * aren't supported yet. * - * @param {ILocalNotification | Array} [options] Notification or notifications. - * @param {string} [eventName] Name of the event: schedule or update. + * @param options Notification or notifications. + * @param eventName Name of the event: schedule or update. */ protected scheduleOrUpdate(options?: ILocalNotification | Array, eventName: string = 'schedule'): void { options = Array.isArray(options) ? options : [options]; @@ -988,8 +964,7 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Overwrites the (platform specific) default settings. * - * @param {any} defaults The defaults to set. - * @returns {Promise} + * @param defaults The defaults to set. */ setDefaults(defaults: any): Promise { this.defaults = defaults; @@ -1000,9 +975,9 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Store a notification in local DB. * - * @param {ILocalNotification} notification Notification to store. - * @param {boolean} triggered Whether the notification has been triggered. - * @return {Promise} Promise resolved when stored. + * @param notification Notification to store. + * @param triggered Whether the notification has been triggered. + * @return Promise resolved when stored. */ protected storeNotification(notification: ILocalNotification, triggered: boolean): Promise { // Only store some of the properties. @@ -1021,7 +996,7 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Trigger a notification, using the best method depending on the OS. * - * @param {ILocalNotification} notification Notification to trigger. + * @param notification Notification to trigger. */ protected triggerNotification(notification: ILocalNotification): void { if (this.winNotif) { @@ -1070,7 +1045,7 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Updates a previously scheduled notification. Must include the id in the options parameter. * - * @param {ILocalNotification} [options] Notification. + * @param options Notification. */ update(options?: ILocalNotification): void { return this.scheduleOrUpdate(options, 'update'); diff --git a/src/core/emulator/providers/media-capture.ts b/src/core/emulator/providers/media-capture.ts index 568cd4beb..9abd354d2 100644 --- a/src/core/emulator/providers/media-capture.ts +++ b/src/core/emulator/providers/media-capture.ts @@ -29,8 +29,8 @@ export class MediaCaptureMock extends MediaCapture { /** * Start the audio recorder application and return information about captured audio clip files. * - * @param {CaptureAudioOptions} options Options. - * @return {Promise} Promise resolved when captured. + * @param options Options. + * @return Promise resolved when captured. */ captureAudio(options: CaptureAudioOptions): Promise { return this.captureHelper.captureMedia('audio', options); @@ -39,8 +39,8 @@ export class MediaCaptureMock extends MediaCapture { /** * Start the camera application and return information about captured image files. * - * @param {CaptureImageOptions} options Options. - * @return {Promise} Promise resolved when captured. + * @param options Options. + * @return Promise resolved when captured. */ captureImage(options: CaptureImageOptions): Promise { return this.captureHelper.captureMedia('captureimage', options); @@ -49,8 +49,8 @@ export class MediaCaptureMock extends MediaCapture { /** * Start the video recorder application and return information about captured video clip files. * - * @param {CaptureVideoOptions} options Options. - * @return {Promise} Promise resolved when captured. + * @param options Options. + * @return Promise resolved when captured. */ captureVideo(options: CaptureVideoOptions): Promise { return this.captureHelper.captureMedia('video', options); diff --git a/src/core/emulator/providers/network.ts b/src/core/emulator/providers/network.ts index 39f8b91c0..df4c523f4 100644 --- a/src/core/emulator/providers/network.ts +++ b/src/core/emulator/providers/network.ts @@ -41,7 +41,7 @@ export class NetworkMock extends Network { /** * Returns an observable to watch connection changes. * - * @return {Observable} Observable. + * @return Observable. */ onchange(): Observable { return Observable.merge(this.onConnect(), this.onDisconnect()); @@ -50,7 +50,7 @@ export class NetworkMock extends Network { /** * Returns an observable to notify when the app is connected. * - * @return {Observable} Observable. + * @return Observable. */ onConnect(): Observable { const observable = new Subject(); @@ -65,7 +65,7 @@ export class NetworkMock extends Network { /** * Returns an observable to notify when the app is disconnected. * - * @return {Observable} Observable. + * @return Observable. */ onDisconnect(): Observable { const observable = new Subject(); diff --git a/src/core/emulator/providers/push.ts b/src/core/emulator/providers/push.ts index e801b01bf..6ae083d5f 100644 --- a/src/core/emulator/providers/push.ts +++ b/src/core/emulator/providers/push.ts @@ -29,8 +29,7 @@ export class PushMock implements Push { /** * Init push notifications * - * @param {PushOptions} options - * @return {PushObject} + * @param options */ init(options: PushOptions): PushObject { return new PushObjectMock(this.appProvider); @@ -39,8 +38,8 @@ export class PushMock implements Push { /** * Check whether the push notification permission has been granted. * - * @return {Promise<{isEnabled: boolean}>} Returns a Promise that resolves with an object with one property: isEnabled, a - * boolean that indicates if permission has been granted. + * @return Returns a Promise that resolves with an object with one property: isEnabled, a + * boolean that indicates if permission has been granted. */ hasPermission(): Promise<{isEnabled: boolean}> { return Promise.reject('hasPermission is only supported in mobile devices'); @@ -49,7 +48,7 @@ export class PushMock implements Push { /** * Create a new notification channel for Android O and above. * - * @param {Channel} channel + * @param channel */ createChannel(channel?: Channel): Promise { return Promise.reject('createChannel is only supported in mobile devices'); @@ -58,7 +57,7 @@ export class PushMock implements Push { /** * Delete a notification channel for Android O and above. * - * @param {string} id + * @param id */ deleteChannel(id?: string): Promise { return Promise.reject('deleteChannel is only supported in mobile devices'); @@ -66,8 +65,6 @@ export class PushMock implements Push { /** * Returns a list of currently configured channels. - * - * @return {Promise} */ listChannels(): Promise { return Promise.reject('listChannels is only supported in mobile devices'); @@ -85,8 +82,7 @@ export class PushObjectMock extends PushObject { /** * Adds an event listener - * @param event {string} - * @return {Observable} + * @param event */ on(event: PushEvent): Observable { return Observable.empty(); @@ -164,8 +160,7 @@ export class PushObjectMock extends PushObject { /** * The subscribe method is used when the application wants to subscribe a new topic to receive push notifications. - * @param topic {string} Topic to subscribe to. - * @return {Promise} + * @param topic Topic to subscribe to. */ subscribe(topic: string): Promise { return Promise.reject('subscribe is only supported in mobile devices'); @@ -175,8 +170,7 @@ export class PushObjectMock extends PushObject { * The unsubscribe method is used when the application no longer wants to receive push notifications from a specific topic but * continue to receive other push messages. * - * @param topic {string} Topic to unsubscribe from. - * @return {Promise} + * @param topic Topic to unsubscribe from. */ unsubscribe(topic: string): Promise { return Promise.reject('unsubscribe is only supported in mobile devices'); diff --git a/src/core/emulator/providers/zip.ts b/src/core/emulator/providers/zip.ts index f7d93f4c1..ae5fdc204 100644 --- a/src/core/emulator/providers/zip.ts +++ b/src/core/emulator/providers/zip.ts @@ -31,9 +31,9 @@ export class ZipMock extends Zip { /** * Create a directory. It creates all the foldes in dirPath 1 by 1 to prevent errors. * - * @param {string} destination Destination parent folder. - * @param {string} dirPath Relative path to the folder. - * @return {Promise} Promise resolved when done. + * @param destination Destination parent folder. + * @param dirPath Relative path to the folder. + * @return Promise resolved when done. */ protected createDir(destination: string, dirPath: string): Promise { // Create all the folders 1 by 1 in order, otherwise it fails. @@ -57,10 +57,10 @@ export class ZipMock extends Zip { /** * Extracts files from a ZIP archive. * - * @param {string} source Path to the source ZIP file. - * @param {string} destination Destination folder. - * @param {Function} [onProgress] Optional callback to be called on progress update - * @return {Promise} Promise that resolves with a number. 0 is success, -1 is error. + * @param source Path to the source ZIP file. + * @param destination Destination folder. + * @param onProgress Optional callback to be called on progress update + * @return Promise that resolves with a number. 0 is success, -1 is error. */ unzip(source: string, destination: string, onProgress?: Function): Promise { diff --git a/src/core/fileuploader/providers/album-handler.ts b/src/core/fileuploader/providers/album-handler.ts index 729539db1..c1e7ca08f 100644 --- a/src/core/fileuploader/providers/album-handler.ts +++ b/src/core/fileuploader/providers/album-handler.ts @@ -31,7 +31,7 @@ export class CoreFileUploaderAlbumHandler implements CoreFileUploaderHandler { /** * 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 this.appProvider.isMobile(); @@ -40,8 +40,8 @@ export class CoreFileUploaderAlbumHandler implements CoreFileUploaderHandler { /** * Given a list of mimetypes, return the ones that are supported by the handler. * - * @param {string[]} [mimetypes] List of mimetypes. - * @return {string[]} Supported mimetypes. + * @param mimetypes List of mimetypes. + * @return Supported mimetypes. */ getSupportedMimetypes(mimetypes: string[]): string[] { // Album allows picking images and videos. @@ -51,7 +51,7 @@ export class CoreFileUploaderAlbumHandler implements CoreFileUploaderHandler { /** * Get the data to display the handler. * - * @return {CoreFileUploaderHandlerData} Data. + * @return Data. */ getData(): CoreFileUploaderHandlerData { return { diff --git a/src/core/fileuploader/providers/audio-handler.ts b/src/core/fileuploader/providers/audio-handler.ts index c6c73b9c2..c7cb5ac2c 100644 --- a/src/core/fileuploader/providers/audio-handler.ts +++ b/src/core/fileuploader/providers/audio-handler.ts @@ -32,7 +32,7 @@ export class CoreFileUploaderAudioHandler implements CoreFileUploaderHandler { /** * 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 this.appProvider.isMobile() || (this.appProvider.canGetUserMedia() && this.appProvider.canRecordMedia()); @@ -41,8 +41,8 @@ export class CoreFileUploaderAudioHandler implements CoreFileUploaderHandler { /** * Given a list of mimetypes, return the ones that are supported by the handler. * - * @param {string[]} [mimetypes] List of mimetypes. - * @return {string[]} Supported mimetypes. + * @param mimetypes List of mimetypes. + * @return Supported mimetypes. */ getSupportedMimetypes(mimetypes: string[]): string[] { if (this.platform.is('ios')) { @@ -69,7 +69,7 @@ export class CoreFileUploaderAudioHandler implements CoreFileUploaderHandler { /** * Get the data to display the handler. * - * @return {CoreFileUploaderHandlerData} Data. + * @return Data. */ getData(): CoreFileUploaderHandlerData { return { diff --git a/src/core/fileuploader/providers/camera-handler.ts b/src/core/fileuploader/providers/camera-handler.ts index c4a9db2d3..fb290aab5 100644 --- a/src/core/fileuploader/providers/camera-handler.ts +++ b/src/core/fileuploader/providers/camera-handler.ts @@ -31,7 +31,7 @@ export class CoreFileUploaderCameraHandler implements CoreFileUploaderHandler { /** * 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 this.appProvider.isMobile() || this.appProvider.canGetUserMedia(); @@ -40,8 +40,8 @@ export class CoreFileUploaderCameraHandler implements CoreFileUploaderHandler { /** * Given a list of mimetypes, return the ones that are supported by the handler. * - * @param {string[]} [mimetypes] List of mimetypes. - * @return {string[]} Supported mimetypes. + * @param mimetypes List of mimetypes. + * @return Supported mimetypes. */ getSupportedMimetypes(mimetypes: string[]): string[] { // Camera only supports JPEG and PNG. @@ -51,7 +51,7 @@ export class CoreFileUploaderCameraHandler implements CoreFileUploaderHandler { /** * Get the data to display the handler. * - * @return {CoreFileUploaderHandlerData} Data. + * @return Data. */ getData(): CoreFileUploaderHandlerData { return { diff --git a/src/core/fileuploader/providers/delegate.ts b/src/core/fileuploader/providers/delegate.ts index b8e7371a7..3bfea08b7 100644 --- a/src/core/fileuploader/providers/delegate.ts +++ b/src/core/fileuploader/providers/delegate.ts @@ -24,22 +24,21 @@ import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate'; export interface CoreFileUploaderHandler extends CoreDelegateHandler { /** * Handler's priority. The highest priority, the highest position. - * @type {string} */ priority?: number; /** * Given a list of mimetypes, return the ones that are supported by the handler. * - * @param {string[]} [mimetypes] List of mimetypes. - * @return {string[]} Supported mimetypes. + * @param mimetypes List of mimetypes. + * @return Supported mimetypes. */ getSupportedMimetypes(mimetypes: string[]): string[]; /** * Get the data to display the handler. * - * @return {CoreFileUploaderHandlerData} Data. + * @return Data. */ getData(): CoreFileUploaderHandlerData; } @@ -50,30 +49,27 @@ export interface CoreFileUploaderHandler extends CoreDelegateHandler { export interface CoreFileUploaderHandlerData { /** * The title to display in the handler. - * @type {string} */ title: string; /** * The icon to display in the handler. - * @type {string} */ icon?: string; /** * The class to assign to the handler item. - * @type {string} */ class?: string; /** * Action to perform when the handler is clicked. * - * @param {number} [maxSize] Max size of the file. If not defined or -1, no max size. - * @param {boolean} [upload] Whether the file should be uploaded. - * @param {boolean} [allowOffline] True to allow selecting in offline, false to require connection. - * @param {string[]} [mimetypes] List of supported mimetypes. If undefined, all mimetypes supported. - * @return {Promise} Promise resolved with the result of picking/uploading the file. + * @param maxSize Max size of the file. If not defined or -1, no max size. + * @param upload Whether the file should be uploaded. + * @param allowOffline True to allow selecting in offline, false to require connection. + * @param mimetypes List of supported mimetypes. If undefined, all mimetypes supported. + * @return Promise resolved with the result of picking/uploading the file. */ action?(maxSize?: number, upload?: boolean, allowOffline?: boolean, mimetypes?: string[]) : Promise; @@ -81,10 +77,10 @@ export interface CoreFileUploaderHandlerData { /** * Function called after the handler is rendered. * - * @param {number} [maxSize] Max size of the file. If not defined or -1, no max size. - * @param {boolean} [upload] Whether the file should be uploaded. - * @param {boolean} [allowOffline] True to allow selecting in offline, false to require connection. - * @param {string[]} [mimetypes] List of supported mimetypes. If undefined, all mimetypes supported. + * @param maxSize Max size of the file. If not defined or -1, no max size. + * @param upload Whether the file should be uploaded. + * @param allowOffline True to allow selecting in offline, false to require connection. + * @param mimetypes List of supported mimetypes. If undefined, all mimetypes supported. */ afterRender?(maxSize: number, upload: boolean, allowOffline: boolean, mimetypes: string[]): void; } @@ -95,31 +91,26 @@ export interface CoreFileUploaderHandlerData { export interface CoreFileUploaderHandlerResult { /** * Whether the file was treated (uploaded or copied to tmp folder). - * @type {boolean} */ treated: boolean; /** * The path of the file picked. Required if treated=false and fileEntry is not set. - * @type {string} */ path?: string; /** * The fileEntry of the file picked. Required if treated=false and path is not set. - * @type {any} */ fileEntry?: any; /** * Whether the file should be deleted after the upload. Ignored if treated=true. - * @type {boolean} */ delete?: boolean; /** * The result of picking/uploading the file. Ignored if treated=false. - * @type {any} */ result?: any; } @@ -130,13 +121,11 @@ export interface CoreFileUploaderHandlerResult { export interface CoreFileUploaderHandlerDataToReturn extends CoreFileUploaderHandlerData { /** * Handler's priority. - * @type {number} */ priority?: number; /** * Supported mimetypes. - * @type {string[]} */ mimetypes?: string[]; } @@ -163,8 +152,8 @@ export class CoreFileUploaderDelegate extends CoreDelegate { /** * Get the handlers for the current site. * - * @param {string[]} [mimetypes] List of supported mimetypes. If undefined, all mimetypes supported. - * @return {CoreFileUploaderHandlerDataToReturn[]} List of handlers data. + * @param mimetypes List of supported mimetypes. If undefined, all mimetypes supported. + * @return List of handlers data. */ getHandlers(mimetypes: string[]): CoreFileUploaderHandlerDataToReturn[] { const handlers = []; diff --git a/src/core/fileuploader/providers/file-handler.ts b/src/core/fileuploader/providers/file-handler.ts index 36bda2bf4..45449cc00 100644 --- a/src/core/fileuploader/providers/file-handler.ts +++ b/src/core/fileuploader/providers/file-handler.ts @@ -35,7 +35,7 @@ export class CoreFileUploaderFileHandler implements CoreFileUploaderHandler { /** * 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 this.platform.is('android') || !this.appProvider.isMobile() || @@ -45,8 +45,8 @@ export class CoreFileUploaderFileHandler implements CoreFileUploaderHandler { /** * Given a list of mimetypes, return the ones that are supported by the handler. * - * @param {string[]} [mimetypes] List of mimetypes. - * @return {string[]} Supported mimetypes. + * @param mimetypes List of mimetypes. + * @return Supported mimetypes. */ getSupportedMimetypes(mimetypes: string[]): string[] { return mimetypes; @@ -55,7 +55,7 @@ export class CoreFileUploaderFileHandler implements CoreFileUploaderHandler { /** * Get the data to display the handler. * - * @return {CoreFileUploaderHandlerData} Data. + * @return Data. */ getData(): CoreFileUploaderHandlerData { const isIOS = this.platform.is('ios'); diff --git a/src/core/fileuploader/providers/fileuploader.ts b/src/core/fileuploader/providers/fileuploader.ts index a43b4d3b4..90c172b1e 100644 --- a/src/core/fileuploader/providers/fileuploader.ts +++ b/src/core/fileuploader/providers/fileuploader.ts @@ -34,7 +34,6 @@ import { Subject } from 'rxjs'; export interface CoreFileUploaderOptions extends CoreWSFileUploadOptions { /** * Whether the file should be deleted after the upload (if success). - * @type {boolean} */ deleteAfterUpload?: boolean; } @@ -65,8 +64,8 @@ export class CoreFileUploaderProvider { /** * Add a dot to the beginning of an extension. * - * @param {string} extension Extension. - * @return {string} Treated extension. + * @param extension Extension. + * @return Treated extension. */ protected addDot(extension: string): string { return '.' + extension; @@ -75,9 +74,9 @@ export class CoreFileUploaderProvider { /** * Compares two file lists and returns if they are different. * - * @param {any[]} a First file list. - * @param {any[]} b Second file list. - * @return {boolean} Whether both lists are different. + * @param a First file list. + * @param b Second file list. + * @return Whether both lists are different. */ areFileListDifferent(a: any[], b: any[]): boolean { a = a || []; @@ -100,8 +99,8 @@ export class CoreFileUploaderProvider { /** * Start the audio recorder application and return information about captured audio clip files. * - * @param {CaptureAudioOptions} options Options. - * @return {Promise} Promise resolved with the result. + * @param options Options. + * @return Promise resolved with the result. */ captureAudio(options: CaptureAudioOptions): Promise { this.onAudioCapture.next(true); @@ -114,8 +113,8 @@ export class CoreFileUploaderProvider { /** * Start the video recorder application and return information about captured video clip files. * - * @param {CaptureVideoOptions} options Options. - * @return {Promise} Promise resolved with the result. + * @param options Options. + * @return Promise resolved with the result. */ captureVideo(options: CaptureVideoOptions): Promise { this.onVideoCapture.next(true); @@ -129,7 +128,7 @@ export class CoreFileUploaderProvider { * Clear temporary attachments to be uploaded. * Attachments already saved in an offline store will NOT be deleted. * - * @param {any[]} files List of files. + * @param files List of files. */ clearTmpFiles(files: any[]): void { // Delete the local files. @@ -146,9 +145,9 @@ export class CoreFileUploaderProvider { /** * Get the upload options for a file taken with the Camera Cordova plugin. * - * @param {string} uri File URI. - * @param {boolean} [isFromAlbum] True if the image was taken from album, false if it's a new image taken with camera. - * @return {CoreFileUploaderOptions} Options. + * @param uri File URI. + * @param isFromAlbum True if the image was taken from album, false if it's a new image taken with camera. + * @return Options. */ getCameraUploadOptions(uri: string, isFromAlbum?: boolean): CoreFileUploaderOptions { const extension = this.mimeUtils.getExtension(uri), @@ -183,13 +182,13 @@ export class CoreFileUploaderProvider { /** * Get the upload options for a file of any type. * - * @param {string} uri File URI. - * @param {string} name File name. - * @param {string} type File type. - * @param {boolean} [deleteAfterUpload] Whether the file should be deleted after upload. - * @param {string} [fileArea] File area to upload the file to. It defaults to 'draft'. - * @param {number} [itemId] Draft ID to upload the file to, 0 to create new. - * @return {CoreFileUploaderOptions} Options. + * @param uri File URI. + * @param name File name. + * @param type File type. + * @param deleteAfterUpload Whether the file should be deleted after upload. + * @param fileArea File area to upload the file to. It defaults to 'draft'. + * @param itemId Draft ID to upload the file to, 0 to create new. + * @return Options. */ getFileUploadOptions(uri: string, name: string, type: string, deleteAfterUpload?: boolean, fileArea?: string, itemId?: number) : CoreFileUploaderOptions { @@ -206,8 +205,8 @@ export class CoreFileUploaderProvider { /** * Get the upload options for a file taken with the media capture Cordova plugin. * - * @param {MediaFile} mediaFile File object to upload. - * @return {CoreFileUploaderOptions} Options. + * @param mediaFile File object to upload. + * @return Options. */ getMediaUploadOptions(mediaFile: MediaFile): CoreFileUploaderOptions { const options: CoreFileUploaderOptions = {}; @@ -233,8 +232,8 @@ export class CoreFileUploaderProvider { /** * Take a picture or video, or load one from the library. * - * @param {CameraOptions} options Options. - * @return {Promise} Promise resolved with the result. + * @param options Options. + * @return Promise resolved with the result. */ getPicture(options: CameraOptions): Promise { this.onGetPicture.next(true); @@ -247,8 +246,8 @@ export class CoreFileUploaderProvider { /** * Get the files stored in a folder, marking them as offline. * - * @param {string} folderPath Folder where to get the files. - * @return {Promise} Promise resolved with the list of files. + * @param folderPath Folder where to get the files. + * @return Promise resolved with the list of files. */ getStoredFiles(folderPath: string): Promise { return this.fileProvider.getDirectoryContents(folderPath).then((files) => { @@ -259,9 +258,9 @@ export class CoreFileUploaderProvider { /** * Get stored files from combined online and offline file object. * - * @param {{online: any[], offline: number}} filesObject The combined offline and online files object. - * @param {string} folderPath Folder path to get files from. - * @return {Promise} Promise resolved with files. + * @param filesObject The combined offline and online files object. + * @param folderPath Folder path to get files from. + * @return Promise resolved with files. */ getStoredFilesFromOfflineFilesObject(filesObject: { online: any[], offline: number }, folderPath: string): Promise { let files = []; @@ -288,10 +287,10 @@ export class CoreFileUploaderProvider { * Check if a file's mimetype is invalid based on the list of accepted mimetypes. This function needs either the file's * mimetype or the file's path/name. * - * @param {string[]} [mimetypes] List of supported mimetypes. If undefined, all mimetypes supported. - * @param {string} [path] File's path or name. - * @param {string} [mimetype] File's mimetype. - * @return {string} Undefined if file is valid, error message if file is invalid. + * @param mimetypes List of supported mimetypes. If undefined, all mimetypes supported. + * @param path File's path or name. + * @param mimetype File's mimetype. + * @return Undefined if file is valid, error message if file is invalid. */ isInvalidMimetype(mimetypes?: string[], path?: string, mimetype?: string): string { let extension; @@ -316,8 +315,8 @@ export class CoreFileUploaderProvider { /** * Mark files as offline. * - * @param {any[]} files Files to mark as offline. - * @return {any[]} Files marked as offline. + * @param files Files to mark as offline. + * @return Files marked as offline. */ markOfflineFiles(files: any[]): any[] { // Mark the files as pending offline. @@ -332,8 +331,8 @@ export class CoreFileUploaderProvider { /** * Parse filetypeList to get the list of allowed mimetypes and the data to render information. * - * @param {string} filetypeList Formatted string list where the mimetypes can be checked. - * @return {{info: any[], mimetypes: string[]}} Mimetypes and the filetypes informations. Undefined if all types supported. + * @param filetypeList Formatted string list where the mimetypes can be checked. + * @return Mimetypes and the filetypes informations. Undefined if all types supported. */ prepareFiletypeList(filetypeList: string): { info: any[], mimetypes: string[] } { filetypeList = filetypeList && filetypeList.trim(); @@ -414,9 +413,9 @@ export class CoreFileUploaderProvider { * Given a list of files (either online files or local files), store the local files in a local folder * to be uploaded later. * - * @param {string} folderPath Path of the folder where to store the files. - * @param {any[]} files List of files. - * @return {Promise<{online: any[], offline: number}>} Promise resolved if success. + * @param folderPath Path of the folder where to store the files. + * @param files List of files. + * @return Promise resolved if success. */ storeFilesToUpload(folderPath: string, files: any[]): Promise<{ online: any[], offline: number }> { const result = { @@ -463,11 +462,11 @@ export class CoreFileUploaderProvider { /** * Upload a file. * - * @param {string} uri File URI. - * @param {CoreFileUploaderOptions} [options] Options for the upload. - * @param {Function} [onProgress] Function to call on progress. - * @param {string} [siteId] Id of the site to upload the file to. If not defined, use current site. - * @return {Promise} Promise resolved when done. + * @param uri File URI. + * @param options Options for the upload. + * @param onProgress Function to call on progress. + * @param siteId Id of the site to upload the file to. If not defined, use current site. + * @return Promise resolved when done. */ uploadFile(uri: string, options?: CoreFileUploaderOptions, onProgress?: (event: ProgressEvent) => any, siteId?: string): Promise { @@ -498,12 +497,12 @@ export class CoreFileUploaderProvider { * If the file is an online file it will be downloaded and then re-uploaded. * If the file is a local file it will not be deleted from the device after upload. * - * @param {any} file Online file or local FileEntry. - * @param {number} [itemId] Draft ID to use. Undefined or 0 to create a new draft ID. - * @param {string} [component] The component to set to the downloaded files. - * @param {string|number} [componentId] An ID to use in conjunction with the component. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the itemId. + * @param file Online file or local FileEntry. + * @param itemId Draft ID to use. Undefined or 0 to create a new draft ID. + * @param component The component to set to the downloaded files. + * @param componentId An ID to use in conjunction with the component. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the itemId. */ uploadOrReuploadFile(file: any, itemId?: number, component?: string, componentId?: string | number, siteId?: string): Promise { @@ -544,11 +543,11 @@ export class CoreFileUploaderProvider { * Local files are not deleted from the device after upload. * If there are no files to upload it will return a fake draft ID (1). * - * @param {any[]} files List of files. - * @param {string} [component] The component to set to the downloaded files. - * @param {string|number} [componentId] An ID to use in conjunction with the component. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the itemId. + * @param files List of files. + * @param component The component to set to the downloaded files. + * @param componentId An ID to use in conjunction with the component. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the itemId. */ uploadOrReuploadFiles(files: any[], component?: string, componentId?: string | number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); diff --git a/src/core/fileuploader/providers/helper.ts b/src/core/fileuploader/providers/helper.ts index d530bd4be..8ae78e070 100644 --- a/src/core/fileuploader/providers/helper.ts +++ b/src/core/fileuploader/providers/helper.ts @@ -47,12 +47,12 @@ export class CoreFileUploaderHelperProvider { /** * Show a confirmation modal to the user if the size of the file is bigger than the allowed threshold. * - * @param {number} size File size. - * @param {boolean} [alwaysConfirm] True to show a confirm even if the size isn't high. - * @param {boolean} [allowOffline] True to allow uploading in offline. - * @param {number} [wifiThreshold] Threshold for WiFi connection. Default: CoreFileUploaderProvider.WIFI_SIZE_WARNING. - * @param {number} [limitedThreshold] Threshold for limited connection. Default: CoreFileUploaderProvider.LIMITED_SIZE_WARNING. - * @return {Promise} Promise resolved when the user confirms or if there's no need to show a modal. + * @param size File size. + * @param alwaysConfirm True to show a confirm even if the size isn't high. + * @param allowOffline True to allow uploading in offline. + * @param wifiThreshold Threshold for WiFi connection. Default: CoreFileUploaderProvider.WIFI_SIZE_WARNING. + * @param limitedThreshold Threshold for limited connection. Default: CoreFileUploaderProvider.LIMITED_SIZE_WARNING. + * @return Promise resolved when the user confirms or if there's no need to show a modal. */ confirmUploadFile(size: number, alwaysConfirm?: boolean, allowOffline?: boolean, wifiThreshold?: number, limitedThreshold?: number): Promise { @@ -84,10 +84,10 @@ export class CoreFileUploaderHelperProvider { /** * Create a temporary copy of a file and upload it. * - * @param {any} file File to copy and upload. - * @param {boolean} [upload] True if the file should be uploaded, false to return the copy of the file. - * @param {string} [name] Name to use when uploading the file. If not defined, use the file's name. - * @return {Promise} Promise resolved when the file is uploaded. + * @param file File to copy and upload. + * @param upload True if the file should be uploaded, false to return the copy of the file. + * @param name Name to use when uploading the file. If not defined, use the file's name. + * @return Promise resolved when the file is uploaded. */ copyAndUploadFile(file: any, upload?: boolean, name?: string): Promise { name = name || file.name; @@ -122,11 +122,11 @@ export class CoreFileUploaderHelperProvider { /** * Copy or move a file to the app temporary folder. * - * @param {string} path Path of the file. - * @param {boolean} shouldDelete True if original file should be deleted (move), false otherwise (copy). - * @param {number} [maxSize] Max size of the file. If not defined or -1, no max size. - * @param {string} [defaultExt] Defaut extension to use if the file doesn't have any. - * @return {Promise} Promise resolved with the copied file. + * @param path Path of the file. + * @param shouldDelete True if original file should be deleted (move), false otherwise (copy). + * @param maxSize Max size of the file. If not defined or -1, no max size. + * @param defaultExt Defaut extension to use if the file doesn't have any. + * @return Promise resolved with the copied file. */ protected copyToTmpFolder(path: string, shouldDelete: boolean, maxSize?: number, defaultExt?: string): Promise { let fileName = this.fileProvider.getFileAndDirectoryFromPath(path).name, @@ -173,9 +173,9 @@ export class CoreFileUploaderHelperProvider { /** * Function called when trying to upload a file bigger than max size. Shows an error. * - * @param {number} maxSize Max size (bytes). - * @param {string} fileName Name of the file. - * @return {Promise} Rejected promise. + * @param maxSize Max size (bytes). + * @param fileName Name of the file. + * @return Rejected promise. */ protected errorMaxBytes(maxSize: number, fileName: string): Promise { const errorMessage = this.translate.instant('core.fileuploader.maxbytesfile', { @@ -203,7 +203,7 @@ export class CoreFileUploaderHelperProvider { /** * Function to call once a file is uploaded using the file picker. * - * @param {any} result Result of the upload process. + * @param result Result of the upload process. */ fileUploaded(result: any): void { if (this.filePickerDeferred) { @@ -219,11 +219,11 @@ export class CoreFileUploaderHelperProvider { /** * Open the "file picker" to select and upload a file. * - * @param {number} [maxSize] Max size of the file to upload. If not defined or -1, no max size. - * @param {string} [title] File picker title. - * @param {string[]} [mimetypes] List of supported mimetypes. If undefined, all mimetypes supported. - * @return {Promise} Promise resolved when a file is uploaded, rejected if file picker is closed without a file uploaded. - * The resolve value is the response of the upload request. + * @param maxSize Max size of the file to upload. If not defined or -1, no max size. + * @param title File picker title. + * @param mimetypes List of supported mimetypes. If undefined, all mimetypes supported. + * @return Promise resolved when a file is uploaded, rejected if file picker is closed without a file uploaded. + * The resolve value is the response of the upload request. */ selectAndUploadFile(maxSize?: number, title?: string, mimetypes?: string[]): Promise { return this.selectFileWithPicker(maxSize, false, title, mimetypes, true); @@ -232,12 +232,12 @@ export class CoreFileUploaderHelperProvider { /** * Open the "file picker" to select a file without uploading it. * - * @param {number} [maxSize] Max size of the file. If not defined or -1, no max size. - * @param {boolean} [allowOffline] True to allow selecting in offline, false to require connection. - * @param {string} [title] File picker title. - * @param {string[]} [mimetypes] List of supported mimetypes. If undefined, all mimetypes supported. - * @return {Promise} Promise resolved when a file is selected, rejected if file picker is closed without selecting a file. - * The resolve value is the FileEntry of a copy of the picked file, so it can be deleted afterwards. + * @param maxSize Max size of the file. If not defined or -1, no max size. + * @param allowOffline True to allow selecting in offline, false to require connection. + * @param title File picker title. + * @param mimetypes List of supported mimetypes. If undefined, all mimetypes supported. + * @return Promise resolved when a file is selected, rejected if file picker is closed without selecting a file. + * The resolve value is the FileEntry of a copy of the picked file, so it can be deleted afterwards. */ selectFile(maxSize?: number, allowOffline?: boolean, title?: string, mimetypes?: string[]) : Promise { @@ -247,12 +247,12 @@ export class CoreFileUploaderHelperProvider { /** * Open the "file picker" to select a file and maybe uploading it. * - * @param {number} [maxSize] Max size of the file. If not defined or -1, no max size. - * @param {boolean} [allowOffline] True to allow selecting in offline, false to require connection. - * @param {string} [title] File picker title. - * @param {string[]} [mimetypes] List of supported mimetypes. If undefined, all mimetypes supported. - * @param {boolean} [upload] Whether the file should be uploaded. - * @return {Promise} Promise resolved when a file is selected/uploaded, rejected if file picker is closed. + * @param maxSize Max size of the file. If not defined or -1, no max size. + * @param allowOffline True to allow selecting in offline, false to require connection. + * @param title File picker title. + * @param mimetypes List of supported mimetypes. If undefined, all mimetypes supported. + * @param upload Whether the file should be uploaded. + * @return Promise resolved when a file is selected/uploaded, rejected if file picker is closed. */ protected selectFileWithPicker(maxSize?: number, allowOffline?: boolean, title?: string, mimetypes?: string[], upload?: boolean): Promise { @@ -352,10 +352,10 @@ export class CoreFileUploaderHelperProvider { /** * Convenience function to upload a file on a certain site, showing a confirm if needed. * - * @param {any} fileEntry FileEntry of the file to upload. - * @param {boolean} [deleteAfterUpload] Whether the file should be deleted after upload. - * @param {string} [siteId] Id of the site to upload the file to. If not defined, use current site. - * @return {Promise} Promise resolved when the file is uploaded. + * @param fileEntry FileEntry of the file to upload. + * @param deleteAfterUpload Whether the file should be deleted after upload. + * @param siteId Id of the site to upload the file to. If not defined, use current site. + * @return Promise resolved when the file is uploaded. */ showConfirmAndUploadInSite(fileEntry: any, deleteAfterUpload?: boolean, siteId?: string): Promise { return this.fileProvider.getFileObjectFromFileEntry(fileEntry).then((file) => { @@ -380,9 +380,9 @@ export class CoreFileUploaderHelperProvider { /** * Treat a capture audio/video error. * - * @param {any} error Error returned by the Cordova plugin. Can be a string or an object. - * @param {string} defaultMessage Key of the default message to show. - * @return {Promise} Rejected promise. If it doesn't have an error message it means it was cancelled. + * @param error Error returned by the Cordova plugin. Can be a string or an object. + * @param defaultMessage Key of the default message to show. + * @return Rejected promise. If it doesn't have an error message it means it was cancelled. */ protected treatCaptureError(error: any, defaultMessage: string): Promise { // Cancelled or error. If cancelled, error is an object with code = 3. @@ -413,9 +413,9 @@ export class CoreFileUploaderHelperProvider { /** * Treat a capture image or browse album error. * - * @param {string} error Error returned by the Cordova plugin. - * @param {string} defaultMessage Key of the default message to show. - * @return {Promise} Rejected promise. If it doesn't have an error message it means it was cancelled. + * @param error Error returned by the Cordova plugin. + * @param defaultMessage Key of the default message to show. + * @return Rejected promise. If it doesn't have an error message it means it was cancelled. */ protected treatImageError(error: string, defaultMessage: string): Promise { // Cancelled or error. @@ -440,11 +440,11 @@ export class CoreFileUploaderHelperProvider { /** * Convenient helper for the user to record and upload a video. * - * @param {boolean} isAudio True if uploading an audio, false if it's a video. - * @param {number} maxSize Max size of the upload. -1 for no max size. - * @param {boolean} [upload] True if the file should be uploaded, false to return the picked file. - * @param {string[]} [mimetypes] List of supported mimetypes. If undefined, all mimetypes supported. - * @return {Promise} Promise resolved when done. + * @param isAudio True if uploading an audio, false if it's a video. + * @param maxSize Max size of the upload. -1 for no max size. + * @param upload True if the file should be uploaded, false to return the picked file. + * @param mimetypes List of supported mimetypes. If undefined, all mimetypes supported. + * @return Promise resolved when done. */ uploadAudioOrVideo(isAudio: boolean, maxSize: number, upload?: boolean, mimetypes?: string[]): Promise { this.logger.debug('Trying to record a ' + (isAudio ? 'audio' : 'video') + ' file'); @@ -485,12 +485,12 @@ export class CoreFileUploaderHelperProvider { * Uploads a file of any type. * This function will not check the size of the file, please check it before calling this function. * - * @param {string} uri File URI. - * @param {string} name File name. - * @param {string} type File type. - * @param {boolean} [deleteAfterUpload] Whether the file should be deleted after upload. - * @param {string} [siteId] Id of the site to upload the file to. If not defined, use current site. - * @return {Promise} Promise resolved when the file is uploaded. + * @param uri File URI. + * @param name File name. + * @param type File type. + * @param deleteAfterUpload Whether the file should be deleted after upload. + * @param siteId Id of the site to upload the file to. If not defined, use current site. + * @return Promise resolved when the file is uploaded. */ uploadGenericFile(uri: string, name: string, type: string, deleteAfterUpload?: boolean, siteId?: string): Promise { const options = this.fileUploaderProvider.getFileUploadOptions(uri, name, type, deleteAfterUpload); @@ -501,11 +501,11 @@ export class CoreFileUploaderHelperProvider { /** * Convenient helper for the user to upload an image, either from the album or taking it with the camera. * - * @param {boolean} fromAlbum True if the image should be selected from album, false if it should be taken with camera. - * @param {number} maxSize Max size of the upload. -1 for no max size. - * @param {boolean} [upload] True if the file should be uploaded, false to return the picked file. - * @param {string[]} [mimetypes] List of supported mimetypes. If undefined, all mimetypes supported. - * @return {Promise} Promise resolved when done. + * @param fromAlbum True if the image should be selected from album, false if it should be taken with camera. + * @param maxSize Max size of the upload. -1 for no max size. + * @param upload True if the file should be uploaded, false to return the picked file. + * @param mimetypes List of supported mimetypes. If undefined, all mimetypes supported. + * @return Promise resolved when done. */ uploadImage(fromAlbum: boolean, maxSize: number, upload?: boolean, mimetypes?: string[]): Promise { this.logger.debug('Trying to capture an image with camera'); @@ -568,13 +568,13 @@ export class CoreFileUploaderHelperProvider { /** * Upload a file given the file entry. * - * @param {any} fileEntry The file entry. - * @param {boolean} deleteAfter True if the file should be deleted once treated. - * @param {number} [maxSize] Max size of the file. If not defined or -1, no max size. - * @param {boolean} [upload] True if the file should be uploaded, false to return the picked file. - * @param {boolean} [allowOffline] True to allow selecting in offline, false to require connection. - * @param {string} [name] Name to use when uploading the file. If not defined, use the file's name. - * @return {Promise} Promise resolved when done. + * @param fileEntry The file entry. + * @param deleteAfter True if the file should be deleted once treated. + * @param maxSize Max size of the file. If not defined or -1, no max size. + * @param upload True if the file should be uploaded, false to return the picked file. + * @param allowOffline True to allow selecting in offline, false to require connection. + * @param name Name to use when uploading the file. If not defined, use the file's name. + * @return Promise resolved when done. */ uploadFileEntry(fileEntry: any, deleteAfter: boolean, maxSize?: number, upload?: boolean, allowOffline?: boolean, name?: string): Promise { @@ -593,12 +593,12 @@ export class CoreFileUploaderHelperProvider { /** * Upload a file given the file object. * - * @param {any} file The file object. - * @param {number} [maxSize] Max size of the file. If not defined or -1, no max size. - * @param {boolean} [upload] True if the file should be uploaded, false to return the picked file. - * @param {boolean} [allowOffline] True to allow selecting in offline, false to require connection. - * @param {string} [name] Name to use when uploading the file. If not defined, use the file's name. - * @return {Promise} Promise resolved when done. + * @param file The file object. + * @param maxSize Max size of the file. If not defined or -1, no max size. + * @param upload True if the file should be uploaded, false to return the picked file. + * @param allowOffline True to allow selecting in offline, false to require connection. + * @param name Name to use when uploading the file. If not defined, use the file's name. + * @return Promise resolved when done. */ uploadFileObject(file: any, maxSize?: number, upload?: boolean, allowOffline?: boolean, name?: string): Promise { if (maxSize != -1 && file.size > maxSize) { @@ -614,12 +614,12 @@ export class CoreFileUploaderHelperProvider { /** * Convenience function to upload a file, allowing to retry if it fails. * - * @param {string} path Absolute path of the file to upload. - * @param {number} maxSize Max size of the upload. -1 for no max size. - * @param {boolean} checkSize True to check size. - * @param {CoreFileUploaderOptions} Options. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if the file is uploaded, rejected otherwise. + * @param path Absolute path of the file to upload. + * @param maxSize Max size of the upload. -1 for no max size. + * @param checkSize True to check size. + * @param Options. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if the file is uploaded, rejected otherwise. */ protected uploadFile(path: string, maxSize: number, checkSize: boolean, options: CoreFileUploaderOptions, siteId?: string) : Promise { @@ -697,9 +697,9 @@ export class CoreFileUploaderHelperProvider { /** * Show a progress modal. * - * @param {Loading} modal The modal where to show the progress. - * @param {string} stringKey The key of the string to display. - * @param {ProgressEvent|CoreFileProgressEvent} progress The progress event. + * @param modal The modal where to show the progress. + * @param stringKey The key of the string to display. + * @param progress The progress event. */ protected showProgressModal(modal: Loading, stringKey: string, progress: ProgressEvent | CoreFileProgressEvent): void { if (progress && progress.lengthComputable) { diff --git a/src/core/fileuploader/providers/video-handler.ts b/src/core/fileuploader/providers/video-handler.ts index 22c70ecac..6d9fb58f8 100644 --- a/src/core/fileuploader/providers/video-handler.ts +++ b/src/core/fileuploader/providers/video-handler.ts @@ -32,7 +32,7 @@ export class CoreFileUploaderVideoHandler implements CoreFileUploaderHandler { /** * 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 this.appProvider.isMobile() || (this.appProvider.canGetUserMedia() && this.appProvider.canRecordMedia()); @@ -41,8 +41,8 @@ export class CoreFileUploaderVideoHandler implements CoreFileUploaderHandler { /** * Given a list of mimetypes, return the ones that are supported by the handler. * - * @param {string[]} [mimetypes] List of mimetypes. - * @return {string[]} Supported mimetypes. + * @param mimetypes List of mimetypes. + * @return Supported mimetypes. */ getSupportedMimetypes(mimetypes: string[]): string[] { if (this.platform.is('ios')) { @@ -69,7 +69,7 @@ export class CoreFileUploaderVideoHandler implements CoreFileUploaderHandler { /** * Get the data to display the handler. * - * @return {CoreFileUploaderHandlerData} Data. + * @return Data. */ getData(): CoreFileUploaderHandlerData { return { diff --git a/src/core/grades/components/course/course.ts b/src/core/grades/components/course/course.ts index 8d66dd320..51703f87b 100644 --- a/src/core/grades/components/course/course.ts +++ b/src/core/grades/components/course/course.ts @@ -64,8 +64,8 @@ export class CoreGradesCourseComponent { /** * Fetch all the data required for the view. * - * @param {boolean} [refresh] Empty events array first. - * @return {Promise} Resolved when done. + * @param refresh Empty events array first. + * @return Resolved when done. */ fetchData(refresh: boolean = false): Promise { return this.gradesProvider.getCourseGradesTable(this.courseId, this.userId).then((table) => { @@ -78,7 +78,7 @@ export class CoreGradesCourseComponent { /** * Refresh data. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshGrades(refresher: any): void { this.gradesProvider.invalidateCourseGradesData(this.courseId, this.userId).finally(() => { @@ -90,7 +90,7 @@ export class CoreGradesCourseComponent { /** * Navigate to the grade of the selected item. - * @param {number} gradeId Grade item ID where to navigate. + * @param gradeId Grade item ID where to navigate. */ gotoGrade(gradeId: number): void { if (gradeId) { diff --git a/src/core/grades/pages/courses/courses.ts b/src/core/grades/pages/courses/courses.ts index 62a67829a..fe186edd8 100644 --- a/src/core/grades/pages/courses/courses.ts +++ b/src/core/grades/pages/courses/courses.ts @@ -66,7 +66,7 @@ export class CoreGradesCoursesPage { /** * Fetch all the data required for the view. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ fetchData(): Promise { return this.gradesProvider.getCoursesGrades().then((grades) => { @@ -81,7 +81,7 @@ export class CoreGradesCoursesPage { /** * Refresh data. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshGrades(refresher: any): void { this.gradesProvider.invalidateCoursesGradesData().finally(() => { @@ -93,7 +93,7 @@ export class CoreGradesCoursesPage { /** * Navigate to the grades of the selected course. - * @param {number} courseId Course Id where to navigate. + * @param courseId Course Id where to navigate. */ gotoCourseGrades(courseId: number): void { this.courseId = courseId; diff --git a/src/core/grades/pages/grade/grade.ts b/src/core/grades/pages/grade/grade.ts index fc94c0497..dcdad5290 100644 --- a/src/core/grades/pages/grade/grade.ts +++ b/src/core/grades/pages/grade/grade.ts @@ -56,7 +56,7 @@ export class CoreGradesGradePage { /** * Fetch all the data required for the view. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ fetchData(): Promise { return this.gradesHelper.getGradeItem(this.courseId, this.gradeId, this.userId).then((grade) => { @@ -69,7 +69,7 @@ export class CoreGradesGradePage { /** * Refresh data. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshGrade(refresher: any): void { this.gradesProvider.invalidateCourseGradesData(this.courseId, this.userId).finally(() => { diff --git a/src/core/grades/providers/course-option-handler.ts b/src/core/grades/providers/course-option-handler.ts index d544e4e85..a927a0d09 100644 --- a/src/core/grades/providers/course-option-handler.ts +++ b/src/core/grades/providers/course-option-handler.ts @@ -32,10 +32,10 @@ export class CoreGradesCourseOptionHandler implements CoreCourseOptionsHandler { /** * 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.grades != 'undefined') { @@ -49,7 +49,7 @@ export class CoreGradesCourseOptionHandler implements CoreCourseOptionsHandler { /** * 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; @@ -58,11 +58,11 @@ export class CoreGradesCourseOptionHandler 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) { @@ -79,9 +79,9 @@ export class CoreGradesCourseOptionHandler implements CoreCourseOptionsHandler { /** * 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 { @@ -94,8 +94,8 @@ export class CoreGradesCourseOptionHandler 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.gradesProvider.getCourseGradesTable(course.id, undefined, undefined, true); diff --git a/src/core/grades/providers/grades.ts b/src/core/grades/providers/grades.ts index 59b25b1d2..b2f35b266 100644 --- a/src/core/grades/providers/grades.ts +++ b/src/core/grades/providers/grades.ts @@ -41,9 +41,9 @@ export class CoreGradesProvider { /** * Get cache key for grade table data WS calls. * - * @param {number} courseId ID of the course to get the grades from. - * @param {number} userId ID of the user to get the grades from. - * @return {string} Cache key. + * @param courseId ID of the course to get the grades from. + * @param userId ID of the user to get the grades from. + * @return Cache key. */ protected getCourseGradesCacheKey(courseId: number, userId: number): string { return this.getCourseGradesPrefixCacheKey(courseId) + userId; @@ -52,10 +52,10 @@ export class CoreGradesProvider { /** * Get cache key for grade items data WS calls. * - * @param {number} courseId ID of the course to get the grades from. - * @param {number} userId ID of the user to get the grades from. - * @param {number} [groupId] ID of the group to get the grades from. Default: 0. - * @return {string} Cache key. + * @param courseId ID of the course to get the grades from. + * @param userId ID of the user to get the grades from. + * @param groupId ID of the group to get the grades from. Default: 0. + * @return Cache key. */ protected getCourseGradesItemsCacheKey(courseId: number, userId: number, groupId: number): string { groupId = groupId || 0; @@ -66,8 +66,8 @@ export class CoreGradesProvider { /** * Get prefix cache key for grade table data WS calls. * - * @param {number} courseId ID of the course to get the grades from. - * @return {string} Cache key. + * @param courseId ID of the course to get the grades from. + * @return Cache key. */ protected getCourseGradesPrefixCacheKey(courseId: number): string { return this.ROOT_CACHE_KEY + 'items:' + courseId + ':'; @@ -76,7 +76,7 @@ export class CoreGradesProvider { /** * Get cache key for courses grade WS calls. * - * @return {string} Cache key. + * @return Cache key. */ protected getCoursesGradesCacheKey(): string { return this.ROOT_CACHE_KEY + 'coursesgrades'; @@ -86,12 +86,12 @@ export class CoreGradesProvider { * Get the grade items for a certain module. Keep in mind that may have more than one item to include outcomes and scales. * Fallback function only used if 'gradereport_user_get_grade_items' WS is not avalaible Moodle < 3.2. * - * @param {number} courseId ID of the course to get the grades from. - * @param {number} [userId] ID of the user to get the grades from. If not defined use site's current user. - * @param {number} [groupId] ID of the group to get the grades from. Not used for old gradebook table. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {boolean} [ignoreCache=false] True if it should ignore cached data (it will always fail in offline or server down). - * @return {Promise} Promise to be resolved when the grades are retrieved. + * @param courseId ID of the course to get the grades from. + * @param userId ID of the user to get the grades from. If not defined use site's current user. + * @param groupId ID of the group to get the grades from. Not used for old gradebook table. + * @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 grades are retrieved. */ getGradeItems(courseId: number, userId?: number, groupId?: number, siteId?: string, ignoreCache: boolean = false): Promise { @@ -116,12 +116,12 @@ export class CoreGradesProvider { /** * Get the grade items for a certain course. * - * @param {number} courseId ID of the course to get the grades from. - * @param {number} [userId] ID of the user to get the grades from. - * @param {number} [groupId] ID of the group to get the grades from. Default 0. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {boolean} [ignoreCache=false] True if it should ignore cached data (it will always fail in offline or server down). - * @return {Promise} Promise to be resolved when the grades table is retrieved. + * @param courseId ID of the course to get the grades from. + * @param userId ID of the user to get the grades from. + * @param groupId ID of the group to get the grades from. Default 0. + * @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 grades table is retrieved. */ getCourseGradesItems(courseId: number, userId?: number, groupId?: number, siteId?: string, ignoreCache: boolean = false): Promise { @@ -158,11 +158,11 @@ export class CoreGradesProvider { /** * Get the grades for a certain course. * - * @param {number} courseId ID of the course to get the grades from. - * @param {number} [userId] ID of the user to get the grades from. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {boolean} [ignoreCache=false] True if it should ignore cached data (it will always fail in offline or server down). - * @return {Promise} Promise to be resolved when the grades table is retrieved. + * @param courseId ID of the course to get the grades from. + * @param userId ID of the user to get the grades from. + * @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 grades table is retrieved. */ getCourseGradesTable(courseId: number, userId?: number, siteId?: string, ignoreCache: boolean = false): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -196,8 +196,8 @@ export class CoreGradesProvider { /** * Get the grades for a certain course. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise to be resolved when the grades are retrieved. + * @param siteId Site ID. If not defined, current site. + * @return Promise to be resolved when the grades are retrieved. */ getCoursesGrades(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -220,9 +220,9 @@ export class CoreGradesProvider { /** * Invalidates courses grade table and items WS calls for all users. * - * @param {number} courseId ID of the course to get the grades from. - * @param {string} [siteId] Site ID (empty for current site). - * @return {Promise} Promise resolved when the data is invalidated. + * @param courseId ID of the course to get the grades from. + * @param siteId Site ID (empty for current site). + * @return Promise resolved when the data is invalidated. */ invalidateAllCourseGradesData(courseId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -233,10 +233,10 @@ export class CoreGradesProvider { /** * Invalidates grade table data WS calls. * - * @param {number} courseId Course ID. - * @param {number} [userId] User ID. - * @param {string} [siteId] Site id (empty for current site). - * @return {Promise} Promise resolved when the data is invalidated. + * @param courseId Course ID. + * @param userId User ID. + * @param siteId Site id (empty for current site). + * @return Promise resolved when the data is invalidated. */ invalidateCourseGradesData(courseId: number, userId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -249,8 +249,8 @@ export class CoreGradesProvider { /** * Invalidates courses grade data WS calls. * - * @param {string} [siteId] Site id (empty for current site). - * @return {Promise} Promise resolved when the data is invalidated. + * @param siteId Site id (empty for current site). + * @return Promise resolved when the data is invalidated. */ invalidateCoursesGradesData(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -261,11 +261,11 @@ export class CoreGradesProvider { /** * Invalidates courses grade items data WS calls. * - * @param {number} courseId ID of the course to get the grades from. - * @param {number} userId ID of the user to get the grades from. - * @param {number} [groupId] ID of the group to get the grades from. Default: 0. - * @param {string} [siteId] Site id (empty for current site). - * @return {Promise} Promise resolved when the data is invalidated. + * @param courseId ID of the course to get the grades from. + * @param userId ID of the user to get the grades from. + * @param groupId ID of the group to get the grades from. Default: 0. + * @param siteId Site id (empty for current site). + * @return Promise resolved when the data is invalidated. */ invalidateCourseGradesItemsData(courseId: number, userId: number, groupId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -276,8 +276,8 @@ export class CoreGradesProvider { /** * Returns whether or not the plugin is enabled for a certain site. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Resolve with true if plugin is enabled, false otherwise. + * @param siteId Site ID. If not defined, current site. + * @return Resolve with true if plugin is enabled, false otherwise. * @since Moodle 3.2 */ isCourseGradesEnabled(siteId?: string): Promise { @@ -295,9 +295,9 @@ export class CoreGradesProvider { /** * Returns whether or not the grade addon is enabled for a certain course. * - * @param {number} courseId Course ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promisee} 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, 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) { @@ -312,8 +312,8 @@ export class CoreGradesProvider { /** * Returns whether or not WS Grade Items is avalaible. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} True if ws is avalaible, false otherwise. + * @param siteId Site ID. If not defined, current site. + * @return True if ws is avalaible, false otherwise. * @since Moodle 3.2 */ isGradeItemsAvalaible(siteId?: string): Promise { @@ -325,10 +325,10 @@ export class CoreGradesProvider { /** * Log Course grades view in Moodle. * - * @param {number} courseId Course ID. - * @param {number} userId User ID. - * @param {string} [name] Course name. If not set, it will be calculated. - * @return {Promise} Promise resolved when done. + * @param courseId Course ID. + * @param userId User ID. + * @param name Course name. If not set, it will be calculated. + * @return Promise resolved when done. */ logCourseGradesView(courseId: number, userId: number, name?: string): Promise { userId = userId || this.sitesProvider.getCurrentSiteUserId(); @@ -354,8 +354,8 @@ export class CoreGradesProvider { /** * Log Courses grades view in Moodle. * - * @param {number} [courseId] Course ID. If not defined, site Home ID. - * @return {Promise} Promise resolved when done. + * @param courseId Course ID. If not defined, site Home ID. + * @return Promise resolved when done. */ logCoursesGradesView(courseId?: number): Promise { if (!courseId) { diff --git a/src/core/grades/providers/helper.ts b/src/core/grades/providers/helper.ts index 22d925f6a..86cf0fa19 100644 --- a/src/core/grades/providers/helper.ts +++ b/src/core/grades/providers/helper.ts @@ -44,8 +44,8 @@ export class CoreGradesHelperProvider { /** * Formats a row from the grades table te be rendered in a page. * - * @param {any} tableRow JSON object representing row of grades table data. - * @return {any} Formatted row object. + * @param tableRow JSON object representing row of grades table data. + * @return Formatted row object. */ protected formatGradeRow(tableRow: any): any { const row = {}; @@ -79,8 +79,8 @@ export class CoreGradesHelperProvider { /** * Formats a row from the grades table to be rendered in one table. * - * @param {any} tableRow JSON object representing row of grades table data. - * @return {any} Formatted row object. + * @param tableRow JSON object representing row of grades table data. + * @return Formatted row object. */ protected formatGradeRowForTable(tableRow: any): any { const row = {}; @@ -119,8 +119,8 @@ export class CoreGradesHelperProvider { /** * Removes suffix formatted to compatibilize data from table and items. * - * @param {any} item Grade item to format. - * @return {any} Grade item formatted. + * @param item Grade item to format. + * @return Grade item formatted. */ protected formatGradeItem(item: any): any { for (const name in item) { @@ -136,8 +136,8 @@ export class CoreGradesHelperProvider { /** * Formats the response of gradereport_user_get_grades_table to be rendered. * - * @param {any} table JSON object representing a table with data. - * @return {any} Formatted HTML table. + * @param table JSON object representing a table with data. + * @return Formatted HTML table. */ formatGradesTable(table: any): any { const maxDepth = table.maxdepth, @@ -195,8 +195,8 @@ export class CoreGradesHelperProvider { /** * Get course data for grades since they only have courseid. * - * @param {any} grades Grades to get the data for. - * @return {Promise} Promise always resolved. Resolve param is the formatted grades. + * @param grades Grades to get the data for. + * @return Promise always resolved. Resolve param is the formatted grades. */ getGradesCourseData(grades: any): Promise { // Using cache for performance reasons. @@ -219,12 +219,12 @@ export class CoreGradesHelperProvider { /** * Get an specific grade item. * - * @param {number} courseId ID of the course to get the grades from. - * @param {number} gradeId Grade ID. - * @param {number} [userId] ID of the user to get the grades from. If not defined use site's current user. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {boolean} [ignoreCache=false] True if it should ignore cached data (it will always fail in offline or server down). - * @return {Promise} Promise to be resolved when the grades are retrieved. + * @param courseId ID of the course to get the grades from. + * @param gradeId Grade ID. + * @param userId ID of the user to get the grades from. If not defined use site's 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 grades are retrieved. */ getGradeItem(courseId: number, gradeId: number, userId?: number, siteId?: string, ignoreCache: boolean = false): Promise { @@ -240,9 +240,9 @@ export class CoreGradesHelperProvider { /** * Returns the label of the selected grade. * - * @param {any[]} grades Array with objects with value and label. - * @param {number} selectedGrade Selected grade value. - * @return {string} Selected grade label. + * @param grades Array with objects with value and label. + * @param selectedGrade Selected grade value. + * @return Selected grade label. */ getGradeLabelFromValue(grades: any[], selectedGrade: number): string { selectedGrade = Number(selectedGrade); @@ -263,13 +263,13 @@ export class CoreGradesHelperProvider { /** * Get the grade items for a certain module. Keep in mind that may have more than one item to include outcomes and scales. * - * @param {number} courseId ID of the course to get the grades from. - * @param {number} moduleId Module ID. - * @param {number} [userId] ID of the user to get the grades from. If not defined use site's current user. - * @param {number} [groupId] ID of the group to get the grades from. Not used for old gradebook table. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {boolean} [ignoreCache=false] True if it should ignore cached data (it will always fail in offline or server down). - * @return {Promise} Promise to be resolved when the grades are retrieved. + * @param courseId ID of the course to get the grades from. + * @param moduleId Module ID. + * @param userId ID of the user to get the grades from. If not defined use site's current user. + * @param groupId ID of the group to get the grades from. Not used for old gradebook table. + * @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 grades are retrieved. */ getGradeModuleItems(courseId: number, moduleId: number, userId?: number, groupId?: number, siteId?: string, ignoreCache: boolean = false): Promise { @@ -295,9 +295,9 @@ export class CoreGradesHelperProvider { /** * Returns the value of the selected grade. * - * @param {any[]} grades Array with objects with value and label. - * @param {string} selectedGrade Selected grade label. - * @return {number} Selected grade value. + * @param grades Array with objects with value and label. + * @param selectedGrade Selected grade label. + * @return Selected grade value. */ getGradeValueFromLabel(grades: any[], selectedGrade: string): number { if (!grades || !selectedGrade) { @@ -316,8 +316,8 @@ export class CoreGradesHelperProvider { /** * Gets the link to the module for the selected grade. * - * @param {string} text HTML where the link is present. - * @return {string | false} URL linking to the module. + * @param text HTML where the link is present. + * @return URL linking to the module. */ protected getModuleLink(text: string): string | false { const el = this.domUtils.toDom(text)[0], @@ -333,9 +333,9 @@ export class CoreGradesHelperProvider { /** * Get a row from the grades table. * - * @param {any} table JSON object representing a table with data. - * @param {number} gradeId Grade Object identifier. - * @return {any} Formatted HTML table. + * @param table JSON object representing a table with data. + * @param gradeId Grade Object identifier. + * @return Formatted HTML table. */ getGradesTableRow(table: any, gradeId: number): any { if (table.tabledata) { @@ -355,9 +355,9 @@ export class CoreGradesHelperProvider { /** * Get the rows related to a module from the grades table. * - * @param {any} table JSON object representing a table with data. - * @param {number} moduleId Grade Object identifier. - * @return {any} Formatted HTML table. + * @param table JSON object representing a table with data. + * @param moduleId Grade Object identifier. + * @return Formatted HTML table. */ getModuleGradesTableRows(table: any, moduleId: number): any { @@ -388,12 +388,12 @@ export class CoreGradesHelperProvider { /** * Go to view grades. * - * @param {number} courseId Course ID t oview. - * @param {number} [userId] User to view. If not defined, current user. - * @param {number} [moduleId] Module to view. If not defined, view all course grades. - * @param {NavController} [navCtrl] NavController to use. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param courseId Course ID t oview. + * @param userId User to view. If not defined, current user. + * @param moduleId Module to view. If not defined, view all course grades. + * @param navCtrl NavController to use. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ goToGrades(courseId: number, userId?: number, moduleId?: number, navCtrl?: NavController, siteId?: string): Promise { @@ -485,11 +485,11 @@ export class CoreGradesHelperProvider { /** * Invalidate the grade items for a certain module. * - * @param {number} courseId ID of the course to invalidate the grades. - * @param {number} [userId] ID of the user to invalidate. If not defined use site's current user. - * @param {number} [groupId] ID of the group to invalidate. Not used for old gradebook table. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise to be resolved when the grades are invalidated. + * @param courseId ID of the course to invalidate the grades. + * @param userId ID of the user to invalidate. If not defined use site's current user. + * @param groupId ID of the group to invalidate. Not used for old gradebook table. + * @param siteId Site ID. If not defined, current site. + * @return Promise to be resolved when the grades are invalidated. */ invalidateGradeModuleItems(courseId: number, userId?: number, groupId?: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -510,9 +510,9 @@ export class CoreGradesHelperProvider { /** * Parses the image and sets it to the row. * - * @param {any} row Formatted grade row object. - * @param {string} text HTML where the image will be rendered. - * @return {any} Row object with the image. + * @param row Formatted grade row object. + * @param text HTML where the image will be rendered. + * @return Row object with the image. */ protected setRowIcon(row: any, text: string): any { text = text.replace('%2F', '/').replace('%2f', '/'); @@ -564,13 +564,13 @@ export class CoreGradesHelperProvider { * * Taken from make_grades_menu on moodlelib.php * - * @param {number} gradingType If positive, max grade you can provide. If negative, scale Id. - * @param {number} [moduleId] Module ID. Used to retrieve the scale items when they are not passed as parameter. - * If the user does not have permision to manage the activity an empty list is returned. - * @param {string} [defaultLabel] Element that will become default option, if not defined, it won't be added. - * @param {any} [defaultValue] Element that will become default option value. Default ''. - * @param {string} [scale] Scale csv list String. If not provided, it will take it from the module grade info. - * @return {Promise} Array with objects with value and label to create a propper HTML select. + * @param gradingType If positive, max grade you can provide. If negative, scale Id. + * @param moduleId Module ID. Used to retrieve the scale items when they are not passed as parameter. + * If the user does not have permision to manage the activity an empty list is returned. + * @param defaultLabel Element that will become default option, if not defined, it won't be added. + * @param defaultValue Element that will become default option value. Default ''. + * @param scale Scale csv list String. If not provided, it will take it from the module grade info. + * @return Array with objects with value and label to create a propper HTML select. */ makeGradesMenu(gradingType: number, moduleId?: number, defaultLabel: string = '', defaultValue: any = '', scale?: string): Promise { diff --git a/src/core/grades/providers/mainmenu-handler.ts b/src/core/grades/providers/mainmenu-handler.ts index fe3b96a77..389c4f3b3 100644 --- a/src/core/grades/providers/mainmenu-handler.ts +++ b/src/core/grades/providers/mainmenu-handler.ts @@ -29,7 +29,7 @@ export class CoreGradesMainMenuHandler implements CoreMainMenuHandler { /** * 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.gradesProvider.isCourseGradesEnabled(); @@ -38,7 +38,7 @@ export class CoreGradesMainMenuHandler 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/core/grades/providers/overview-link-handler.ts b/src/core/grades/providers/overview-link-handler.ts index 1f34a82b0..7794d17d5 100644 --- a/src/core/grades/providers/overview-link-handler.ts +++ b/src/core/grades/providers/overview-link-handler.ts @@ -33,11 +33,11 @@ export class CoreGradesOverviewLinkHandler 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 { @@ -52,11 +52,11 @@ export class CoreGradesOverviewLinkHandler 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.gradesProvider.isCourseGradesEnabled(siteId); diff --git a/src/core/grades/providers/user-handler.ts b/src/core/grades/providers/user-handler.ts index 20d1d3a49..41d79addf 100644 --- a/src/core/grades/providers/user-handler.ts +++ b/src/core/grades/providers/user-handler.ts @@ -35,8 +35,8 @@ export class CoreGradesUserHandler implements CoreUserProfileHandler { * Clear view grades cache. * If a courseId and userId are specified, it will only delete the entry for that user and course. * - * @param {number} [courseId] Course ID. - * @param {number} [userId] User ID. + * @param courseId Course ID. + * @param userId User ID. */ clearViewGradesCache(courseId?: number, userId?: number): void { if (courseId && userId) { @@ -49,9 +49,9 @@ export class CoreGradesUserHandler implements CoreUserProfileHandler { /** * Get a cache key to identify a course and a user. * - * @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 getCacheKey(courseId: number, userId: number): string { return courseId + '#' + userId; @@ -60,7 +60,7 @@ export class CoreGradesUserHandler implements CoreUserProfileHandler { /** * Check if handler is enabled. * - * @return {boolean} Always enabled. + * @return Always enabled. */ isEnabled(): boolean { return true; @@ -69,11 +69,11 @@ export class CoreGradesUserHandler 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 { const cacheKey = this.getCacheKey(courseId, user.id), @@ -98,7 +98,7 @@ export class CoreGradesUserHandler 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/grades/providers/user-link-handler.ts b/src/core/grades/providers/user-link-handler.ts index a6952f917..9235c6d04 100644 --- a/src/core/grades/providers/user-link-handler.ts +++ b/src/core/grades/providers/user-link-handler.ts @@ -33,12 +33,12 @@ export class CoreGradesUserLinkHandler 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 { @@ -59,11 +59,11 @@ export class CoreGradesUserLinkHandler 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 (!courseId && !params.id) { diff --git a/src/core/login/pages/credentials/credentials.ts b/src/core/login/pages/credentials/credentials.ts index fbf6eb574..41c0b541f 100644 --- a/src/core/login/pages/credentials/credentials.ts +++ b/src/core/login/pages/credentials/credentials.ts @@ -100,8 +100,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; @@ -168,7 +168,7 @@ export class CoreLoginCredentialsPage { /** * Tries to authenticate the user. * - * @param {Event} [e] Event. + * @param e Event. */ login(e?: Event): void { if (e) { @@ -245,7 +245,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.ts b/src/core/login/pages/email-signup/email-signup.ts index 128a37f67..25ecd0e2e 100644 --- a/src/core/login/pages/email-signup/email-signup.ts +++ b/src/core/login/pages/email-signup/email-signup.ts @@ -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(); diff --git a/src/core/login/pages/forgotten-password/forgotten-password.ts b/src/core/login/pages/forgotten-password/forgotten-password.ts index e4b3be972..e2e2805a4 100644 --- a/src/core/login/pages/forgotten-password/forgotten-password.ts +++ b/src/core/login/pages/forgotten-password/forgotten-password.ts @@ -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.ts b/src/core/login/pages/init/init.ts index 3cadcccfb..d7691fd04 100644 --- a/src/core/login/pages/init/init.ts +++ b/src/core/login/pages/init/init.ts @@ -81,7 +81,7 @@ 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()) { diff --git a/src/core/login/pages/reconnect/reconnect.ts b/src/core/login/pages/reconnect/reconnect.ts index c653e51a6..5dc8ca1fa 100644 --- a/src/core/login/pages/reconnect/reconnect.ts +++ b/src/core/login/pages/reconnect/reconnect.ts @@ -102,7 +102,7 @@ export class CoreLoginReconnectPage { /** * Cancel reconnect. * - * @param {Event} [e] Event. + * @param e Event. */ cancel(e?: Event): void { if (e) { @@ -116,7 +116,7 @@ export class CoreLoginReconnectPage { /** * Tries to authenticate the user. * - * @param {Event} e Event. + * @param e Event. */ login(e: Event): void { e.preventDefault(); @@ -185,7 +185,7 @@ export class CoreLoginReconnectPage { /** * 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/site-policy/site-policy.ts b/src/core/login/pages/site-policy/site-policy.ts index e97d54be4..265505ad6 100644 --- a/src/core/login/pages/site-policy/site-policy.ts +++ b/src/core/login/pages/site-policy/site-policy.ts @@ -71,7 +71,7 @@ export class CoreLoginSitePolicyPage { /** * Fetch the site policy URL. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected fetchSitePolicy(): Promise { return this.loginHelper.getSitePolicy(this.siteId).then((sitePolicy) => { diff --git a/src/core/login/pages/site/site.ts b/src/core/login/pages/site/site.ts index 856797acd..55aafa984 100644 --- a/src/core/login/pages/site/site.ts +++ b/src/core/login/pages/site/site.ts @@ -65,8 +65,8 @@ export class CoreLoginSitePage { /** * Try to connect to a site. * - * @param {Event} e Event. - * @param {string} url The URL to connect to. + * @param e Event. + * @param url The URL to connect to. */ connect(e: Event, url: string): void { e.preventDefault(); @@ -135,7 +135,7 @@ export class CoreLoginSitePage { /** * 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(); @@ -159,8 +159,8 @@ export class CoreLoginSitePage { /** * Show an error that aims people to solve the issue. * - * @param {string} url The URL the user was trying to connect to. - * @param {any} error Error to display. + * @param url The URL the user was trying to connect to. + * @param error Error to display. */ protected showLoginIssue(url: string, error: any): void { const modal = this.modalCtrl.create('CoreLoginSiteErrorPage', { diff --git a/src/core/login/pages/sites/sites.ts b/src/core/login/pages/sites/sites.ts index 2fcbc96ab..48719413a 100644 --- a/src/core/login/pages/sites/sites.ts +++ b/src/core/login/pages/sites/sites.ts @@ -77,8 +77,8 @@ export class CoreLoginSitesPage { /** * Delete a site. * - * @param {Event} e Click event. - * @param {number} index Position of the site. + * @param e Click event. + * @param index Position of the site. */ deleteSite(e: Event, index: number): void { e.stopPropagation(); @@ -112,7 +112,7 @@ export class CoreLoginSitesPage { /** * Login in a site. * - * @param {string} siteId The site ID. + * @param siteId The site ID. */ login(siteId: string): void { const modal = this.domUtils.showModalLoading(); diff --git a/src/core/login/providers/helper.ts b/src/core/login/providers/helper.ts index 9d46e536a..d46ad4a3e 100644 --- a/src/core/login/providers/helper.ts +++ b/src/core/login/providers/helper.ts @@ -39,31 +39,26 @@ import { Md5 } from 'ts-md5/dist/md5'; export interface CoreLoginSSOData { /** * The site's URL. - * @type {string} */ siteUrl: string; /** * User's token. - * @type {string} */ token?: string; /** * User's private token. - * @type {string} */ privateToken?: string; /** * Name of the page to go after authenticated. - * @type {string} */ pageName?: string; /** * Params to page to the page. - * @type {string} */ pageParams?: any; } @@ -102,8 +97,8 @@ export class CoreLoginHelperProvider { /** * Accept site policy. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if success, rejected if failure. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if success, rejected if failure. */ acceptSitePolicy(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -133,8 +128,8 @@ export class CoreLoginHelperProvider { /** * Function to handle URL received by Custom URL Scheme. If it's a SSO login, perform authentication. * - * @param {string} url URL received. - * @return {boolean} True if it's a SSO URL, false otherwise. + * @param url URL received. + * @return True if it's a SSO URL, false otherwise. * @deprecated Please use CoreCustomURLSchemesProvider.handleCustomURL instead. */ appLaunchedByURL(url: string): boolean { @@ -209,8 +204,8 @@ export class CoreLoginHelperProvider { /** * Check if a site allows requesting a password reset through the app. * - * @param {string} siteUrl URL of the site. - * @return {Promise} Promise resolved with boolean: whether can be done through the app. + * @param siteUrl URL of the site. + * @return Promise resolved with boolean: whether can be done through the app. */ canRequestPasswordReset(siteUrl: string): Promise { return this.requestPasswordReset(siteUrl).then(() => { @@ -236,11 +231,10 @@ export class CoreLoginHelperProvider { /** * Show a confirm modal if needed and open a browser to perform SSO login. * - * @param {string} siteurl URL of the site where the SSO login will be performed. - * @param {number} typeOfLogin CoreConstants.LOGIN_SSO_CODE or CoreConstants.LOGIN_SSO_INAPP_CODE. - * @param {string} [service] The service to use. If not defined, external service will be used. - * @param {string} [launchUrl] The URL to open for SSO. If not defined, local_mobile launch URL will be used. - * @return {Void} + * @param siteurl URL of the site where the SSO login will be performed. + * @param typeOfLogin CoreConstants.LOGIN_SSO_CODE or CoreConstants.LOGIN_SSO_INAPP_CODE. + * @param service The service to use. If not defined, external service will be used. + * @param launchUrl The URL to open for SSO. If not defined, local_mobile launch URL will be used. */ confirmAndOpenBrowserForSSOLogin(siteUrl: string, typeOfLogin: number, service?: string, launchUrl?: string): void { // Show confirm only if it's needed. Treat "false" (string) as false to prevent typing errors. @@ -263,10 +257,10 @@ export class CoreLoginHelperProvider { /** * Helper function to act when the forgotten password is clicked. * - * @param {NavController} navCtrl NavController to use to navigate. - * @param {string} siteUrl Site URL. - * @param {string} username Username. - * @param {any} [siteConfig] Site config. + * @param navCtrl NavController to use to navigate. + * @param siteUrl Site URL. + * @param username Username. + * @param siteConfig Site config. */ forgottenPasswordClicked(navCtrl: NavController, siteUrl: string, username: string, siteConfig?: any): void { if (siteConfig && siteConfig.forgottenpasswordurl) { @@ -295,8 +289,8 @@ export class CoreLoginHelperProvider { /** * Format profile fields, filtering the ones that shouldn't be shown on signup and classifying them in categories. * - * @param {any[]} profileFields Profile fields to format. - * @return {any} Categories with the fields to show in each one. + * @param profileFields Profile fields to format. + * @return Categories with the fields to show in each one. */ formatProfileFieldsForSignup(profileFields: any[]): any { if (!profileFields) { @@ -331,15 +325,15 @@ export class CoreLoginHelperProvider { * Builds an object with error messages for some common errors. * Please notice that this function doesn't support all possible error types. * - * @param {string} [requiredMsg] Code of the string for required error. - * @param {string} [emailMsg] Code of the string for invalid email error. - * @param {string} [patternMsg] Code of the string for pattern not match error. - * @param {string} [urlMsg] Code of the string for invalid url error. - * @param {string} [minlengthMsg] Code of the string for "too short" error. - * @param {string} [maxlengthMsg] Code of the string for "too long" error. - * @param {string} [minMsg] Code of the string for min value error. - * @param {string} [maxMsg] Code of the string for max value error. - * @return {any} Object with the errors. + * @param requiredMsg Code of the string for required error. + * @param emailMsg Code of the string for invalid email error. + * @param patternMsg Code of the string for pattern not match error. + * @param urlMsg Code of the string for invalid url error. + * @param minlengthMsg Code of the string for "too short" error. + * @param maxlengthMsg Code of the string for "too long" error. + * @param minMsg Code of the string for min value error. + * @param maxMsg Code of the string for max value error. + * @return Object with the errors. */ getErrorMessages(requiredMsg?: string, emailMsg?: string, patternMsg?: string, urlMsg?: string, minlengthMsg?: string, maxlengthMsg?: string, minMsg?: string, maxMsg?: string): any { @@ -376,8 +370,8 @@ export class CoreLoginHelperProvider { /** * Get the site policy. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the site policy. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the site policy. */ getSitePolicy(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -397,7 +391,7 @@ export class CoreLoginHelperProvider { /** * Get fixed site or sites. * - * @return {string|any[]} Fixed site or list of fixed sites. + * @return Fixed site or list of fixed sites. */ getFixedSites(): string | any[] { return CoreConfigConstants.siteurl; @@ -406,8 +400,8 @@ export class CoreLoginHelperProvider { /** * Get the valid identity providers from a site config. * - * @param {any} siteConfig Site's public config. - * @return {any[]} Valid identity providers. + * @param siteConfig Site's public config. + * @return Valid identity providers. */ getValidIdentityProviders(siteConfig: any): any[] { const validProviders = [], @@ -429,9 +423,9 @@ export class CoreLoginHelperProvider { * Go to the page to add a new site. * If a fixed URL is configured, go to credentials instead. * - * @param {boolean} [setRoot] True to set the new page as root, false to add it to the stack. - * @param {boolean} [showKeyboard] Whether to show keyboard in the new page. Only if no fixed URL set. - * @return {Promise} Promise resolved when done. + * @param setRoot True to set the new page as root, false to add it to the stack. + * @param showKeyboard Whether to show keyboard in the new page. Only if no fixed URL set. + * @return Promise resolved when done. */ goToAddSite(setRoot?: boolean, showKeyboard?: boolean): Promise { let pageName, @@ -461,10 +455,10 @@ export class CoreLoginHelperProvider { /** * Open a page that doesn't belong to any site. * - * @param {NavController} [navCtrl] Nav Controller. - * @param {string} [page] Page to open. - * @param {any} [params] Params of the page. - * @return {Promise} Promise resolved when done. + * @param navCtrl Nav Controller. + * @param page Page to open. + * @param params Params of the page. + * @return Promise resolved when done. */ goToNoSitePage(navCtrl: NavController, page: string, params?: any): Promise { navCtrl = navCtrl || this.appProvider.getRootNavController(); @@ -498,12 +492,12 @@ export class CoreLoginHelperProvider { /** * Go to the initial page of a site depending on 'userhomepage' setting. * - * @param {NavController} [navCtrl] NavController to use. Defaults to app root NavController. - * @param {string} [page] Name of the page to load after loading the main page. - * @param {any} [params] Params to pass to the page. - * @param {NavOptions} [options] Navigation options. - * @param {string} [url] URL to open once the main menu is loaded. - * @return {Promise} Promise resolved when done. + * @param navCtrl NavController to use. Defaults to app root NavController. + * @param page Name of the page to load after loading the main page. + * @param params Params to pass to the page. + * @param options Navigation options. + * @param url URL to open once the main menu is loaded. + * @return Promise resolved when done. */ goToSiteInitialPage(navCtrl?: NavController, page?: string, params?: any, options?: NavOptions, url?: string): Promise { return this.openMainMenu(navCtrl, page, params, options, url); @@ -513,10 +507,10 @@ export class CoreLoginHelperProvider { * Convenient helper to handle authentication in the app using a token received by SSO login. If it's a new account, * the site is stored and the user is authenticated. If the account already exists, update its token. * - * @param {string} siteUrl Site's URL. - * @param {string} token User's token. - * @param {string} [privateToken] User's private token. - * @return {Promise} Promise resolved when the user is authenticated with the token. + * @param siteUrl Site's URL. + * @param token User's token. + * @param privateToken User's private token. + * @return Promise resolved when the user is authenticated with the token. */ handleSSOLoginAuthentication(siteUrl: string, token: string, privateToken?: string): Promise { // Always create a new site to prevent overriding data if another user credentials were introduced. @@ -526,7 +520,7 @@ export class CoreLoginHelperProvider { /** * Check if the app is configured to use several fixed URLs. * - * @return {boolean} Whether there are several fixed URLs. + * @return Whether there are several fixed URLs. */ hasSeveralFixedSites(): boolean { return CoreConfigConstants.siteurl && Array.isArray(CoreConfigConstants.siteurl) && @@ -536,7 +530,7 @@ export class CoreLoginHelperProvider { /** * Function called when a page starts loading in any InAppBrowser window. * - * @param {string} url Loaded url. + * @param url Loaded url. * @deprecated */ inAppBrowserLoadStart(url: string): void { @@ -546,8 +540,8 @@ export class CoreLoginHelperProvider { /** * Given a site public config, check if email signup is disabled. * - * @param {any} config Site public config. - * @return {boolean} Whether email signup is disabled. + * @param config Site public config. + * @return Whether email signup is disabled. */ isEmailSignupDisabled(config: any): boolean { let disabledFeatures = config && config.tool_mobile_disabledfeatures; @@ -565,7 +559,7 @@ export class CoreLoginHelperProvider { /** * Check if the app is configured to use a fixed URL (only 1). * - * @return {boolean} Whether there is 1 fixed URL. + * @return Whether there is 1 fixed URL. */ isFixedUrlSet(): boolean { if (Array.isArray(CoreConfigConstants.siteurl)) { @@ -578,9 +572,9 @@ export class CoreLoginHelperProvider { /** * Check if current site is logged out, triggering mmCoreEventSessionExpired if it is. * - * @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 {boolean} True if user is logged out, false otherwise. + * @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 True if user is logged out, false otherwise. */ isSiteLoggedOut(pageName?: string, params?: any): boolean { const site = this.sitesProvider.getCurrentSite(); @@ -603,8 +597,8 @@ export class CoreLoginHelperProvider { /** * Check if SSO login should use an embedded browser. * - * @param {number} code Code to check. - * @return {boolean} True if embedded browser, false othwerise. + * @param code Code to check. + * @return True if embedded browser, false othwerise. */ isSSOEmbeddedBrowser(code: number): boolean { if (this.appProvider.isLinux()) { @@ -618,8 +612,8 @@ export class CoreLoginHelperProvider { /** * Check if SSO login is needed based on code returned by the WS. * - * @param {number} code Code to check. - * @return {boolean} True if SSO login is needed, false othwerise. + * @param code Code to check. + * @return True if SSO login is needed, false othwerise. */ isSSOLoginNeeded(code: number): boolean { return code == CoreConstants.LOGIN_SSO_CODE || code == CoreConstants.LOGIN_SSO_INAPP_CODE; @@ -628,10 +622,10 @@ export class CoreLoginHelperProvider { /** * Load a site and load a certain page in that site. * - * @param {string} page Name of the page to load. - * @param {any} params Params to pass to the page. - * @param {string} siteId Site to load. - * @return {Promise} Promise resolved when done. + * @param page Name of the page to load. + * @param params Params to pass to the page. + * @param siteId Site to load. + * @return Promise resolved when done. */ protected loadSiteAndPage(page: string, params: any, siteId: string): Promise { const navCtrl = this.appProvider.getRootNavController(); @@ -658,8 +652,8 @@ export class CoreLoginHelperProvider { /** * Load a certain page in the main menu page. * - * @param {string} page Name of the page to load. - * @param {any} params Params to pass to the page. + * @param page Name of the page to load. + * @param params Params to pass to the page. */ loadPageInMainMenu(page: string, params: any): void { if (!this.appProvider.isMainMenuOpen()) { @@ -684,12 +678,12 @@ export class CoreLoginHelperProvider { /** * Open the main menu, loading a certain page. * - * @param {NavController} navCtrl NavController. - * @param {string} page Name of the page to load. - * @param {any} params Params to pass to the page. - * @param {NavOptions} [options] Navigation options. - * @param {string} [url] URL to open once the main menu is loaded. - * @return {Promise} Promise resolved when done. + * @param navCtrl NavController. + * @param page Name of the page to load. + * @param params Params to pass to the page. + * @param options Navigation options. + * @param url URL to open once the main menu is loaded. + * @return Promise resolved when done. */ protected openMainMenu(navCtrl: NavController, page: string, params: any, options?: NavOptions, url?: string): Promise { navCtrl = navCtrl || this.appProvider.getRootNavController(); @@ -712,12 +706,12 @@ export class CoreLoginHelperProvider { /** * Open a browser to perform OAuth login (Google, Facebook, Microsoft). * - * @param {string} siteUrl URL of the site where the login will be performed. - * @param {any} provider The identity provider. - * @param {string} [launchUrl] The URL to open for SSO. If not defined, tool/mobile launch URL will be used. - * @param {string} [pageName] Name of the page to go once authenticated. If not defined, site initial page. - * @param {any} [pageParams] Params of the state to go once authenticated. - * @return {boolean} True if success, false if error. + * @param siteUrl URL of the site where the login will be performed. + * @param provider The identity provider. + * @param launchUrl The URL to open for SSO. If not defined, tool/mobile launch URL will be used. + * @param pageName Name of the page to go once authenticated. If not defined, site initial page. + * @param pageParams Params of the state to go once authenticated. + * @return True if success, false if error. */ openBrowserForOAuthLogin(siteUrl: string, provider: any, launchUrl?: string, pageName?: string, pageParams?: any): boolean { launchUrl = launchUrl || siteUrl + '/admin/tool/mobile/launch.php'; @@ -752,12 +746,12 @@ export class CoreLoginHelperProvider { /** * Open a browser to perform SSO login. * - * @param {string} siteurl URL of the site where the SSO login will be performed. - * @param {number} typeOfLogin CoreConstants.LOGIN_SSO_CODE or CoreConstants.LOGIN_SSO_INAPP_CODE. - * @param {string} [service] The service to use. If not defined, external service will be used. - * @param {string} [launchUrl] The URL to open for SSO. If not defined, local_mobile launch URL will be used. - * @param {string} [pageName] Name of the page to go once authenticated. If not defined, site initial page. - * @param {any} [pageParams] Params of the state to go once authenticated. + * @param siteurl URL of the site where the SSO login will be performed. + * @param typeOfLogin CoreConstants.LOGIN_SSO_CODE or CoreConstants.LOGIN_SSO_INAPP_CODE. + * @param service The service to use. If not defined, external service will be used. + * @param launchUrl The URL to open for SSO. If not defined, local_mobile launch URL will be used. + * @param pageName Name of the page to go once authenticated. If not defined, site initial page. + * @param pageParams Params of the state to go once authenticated. */ openBrowserForSSOLogin(siteUrl: string, typeOfLogin: number, service?: string, launchUrl?: string, pageName?: string, pageParams?: any): void { @@ -780,8 +774,8 @@ export class CoreLoginHelperProvider { /** * Convenient helper to open change password page. * - * @param {string} siteUrl Site URL to construct change password URL. - * @param {string} error Error message. + * @param siteUrl Site URL to construct change password URL. + * @param error Error message. */ openChangePassword(siteUrl: string, error: string): void { this.domUtils.showAlert(this.translate.instant('core.notice'), error, undefined, 3000).then((alert) => { @@ -796,7 +790,7 @@ export class CoreLoginHelperProvider { /** * Open forgotten password in inappbrowser. * - * @param {string} siteUrl URL of the site. + * @param siteUrl URL of the site. */ openForgottenPassword(siteUrl: string): void { this.utils.openInApp(siteUrl + '/login/forgot_password.php'); @@ -805,10 +799,10 @@ export class CoreLoginHelperProvider { /* * Function to open in app browser to change password or complete user profile. * - * @param {string} siteId The site ID. - * @param {string} path The relative path of the URL to open. - * @param {string} alertMessage The key of the message to display before opening the in app browser. - * @param {boolean} [invalidateCache] Whether to invalidate site's cache (e.g. when the user is forced to change password). + * @param siteId The site ID. + * @param path The relative path of the URL to open. + * @param alertMessage The key of the message to display before opening the in app browser. + * @param invalidateCache Whether to invalidate site's cache (e.g. when the user is forced to change password). */ openInAppForEdit(siteId: string, path: string, alertMessage: string, invalidateCache?: boolean): void { if (!siteId || siteId !== this.sitesProvider.getCurrentSiteId()) { @@ -842,11 +836,11 @@ export class CoreLoginHelperProvider { /** * Prepare the app to perform SSO login. * - * @param {string} siteUrl URL of the site where the SSO login will be performed. - * @param {string} [service] The service to use. If not defined, external service will be used. - * @param {string} [launchUrl] The URL to open for SSO. If not defined, local_mobile launch URL will be used. - * @param {string} [pageName] Name of the page to go once authenticated. If not defined, site initial page. - * @param {any} [pageParams] Params of the state to go once authenticated. + * @param siteUrl URL of the site where the SSO login will be performed. + * @param service The service to use. If not defined, external service will be used. + * @param launchUrl The URL to open for SSO. If not defined, local_mobile launch URL will be used. + * @param pageName Name of the page to go once authenticated. If not defined, site initial page. + * @param pageParams Params of the state to go once authenticated. */ prepareForSSOLogin(siteUrl: string, service?: string, launchUrl?: string, pageName?: string, pageParams?: any): string { service = service || CoreConfigConstants.wsextservice; @@ -873,10 +867,10 @@ export class CoreLoginHelperProvider { /** * Redirect to a new page, setting it as the root page and loading the right site if needed. * - * @param {string} page Name of the page to load. Special cases: OPEN_COURSE (to open course page). - * @param {any} params Params to pass to the page. - * @param {string} [siteId] Site to load. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param page Name of the page to load. Special cases: OPEN_COURSE (to open course page). + * @param params Params to pass to the page. + * @param siteId Site to load. If not defined, current site. + * @return Promise resolved when done. */ redirect(page: string, params?: any, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -911,10 +905,10 @@ export class CoreLoginHelperProvider { /** * Request a password reset. * - * @param {string} siteUrl URL of the site. - * @param {string} [username] Username to search. - * @param {string} [email] Email to search. - * @return {Promise} Promise resolved when done. + * @param siteUrl URL of the site. + * @param username Username to search. + * @param email Email to search. + * @return Promise resolved when done. */ requestPasswordReset(siteUrl: string, username?: string, email?: string): Promise { const params: any = {}; @@ -933,7 +927,7 @@ export class CoreLoginHelperProvider { /** * Function that should be called when the session expires. Reserved for core use. * - * @param {any} data Data received by the SESSION_EXPIRED event. + * @param data Data received by the SESSION_EXPIRED event. */ sessionExpired(data: any): void { const siteId = data && data.siteId, @@ -1015,8 +1009,8 @@ export class CoreLoginHelperProvider { /** * Check if a confirm should be shown to open a SSO authentication. * - * @param {number} typeOfLogin CoreConstants.LOGIN_SSO_CODE or CoreConstants.LOGIN_SSO_INAPP_CODE. - * @return {boolean} True if confirm modal should be shown, false otherwise. + * @param typeOfLogin CoreConstants.LOGIN_SSO_CODE or CoreConstants.LOGIN_SSO_INAPP_CODE. + * @return True if confirm modal should be shown, false otherwise. */ shouldShowSSOConfirm(typeOfLogin: number): boolean { return !this.isSSOEmbeddedBrowser(typeOfLogin) && @@ -1026,7 +1020,7 @@ export class CoreLoginHelperProvider { /** * Show a modal warning the user that he should use the Classic app. * - * @param {string} message The warning message. + * @param message The warning message. */ protected showLegacyNoticeModal(message: string): void { let link; @@ -1048,7 +1042,7 @@ export class CoreLoginHelperProvider { /** * Show a modal warning the user that he should use the Workplace app. * - * @param {string} message The warning message. + * @param message The warning message. */ protected showWorkplaceNoticeModal(message: string): void { let link; @@ -1065,7 +1059,7 @@ export class CoreLoginHelperProvider { /** * Show a modal warning the user that he should use the current Moodle app. * - * @param {string} message The warning message. + * @param message The warning message. */ protected showMoodleAppNoticeModal(message: string): void { let link; @@ -1089,8 +1083,8 @@ export class CoreLoginHelperProvider { /** * Show a modal warning the user that he should use a different app. * - * @param {string} message The warning message. - * @param {string} link Link to the app to download if any. + * @param message The warning message. + * @param link Link to the app to download if any. */ protected showDownloadAppNoticeModal(message: string, link?: string): void { const buttons: any[] = [ @@ -1127,10 +1121,10 @@ export class CoreLoginHelperProvider { /** * Show a modal to inform the user that a confirmation email was sent, and a button to resend the email on 3.6+ sites. * - * @param {string} siteUrl Site URL. - * @param {string} [email] Email of the user. If set displayed in the message. - * @param {string} [username] Username. If not set the button to resend email will not be shown. - * @param {string} [password] User password. If not set the button to resend email will not be shown. + * @param siteUrl Site URL. + * @param email Email of the user. If set displayed in the message. + * @param username Username. If not set the button to resend email will not be shown. + * @param password User password. If not set the button to resend email will not be shown. */ protected showNotConfirmedModal(siteUrl: string, email?: string, username?: string, password?: string): void { const title = this.translate.instant('core.login.mustconfirm'); @@ -1187,7 +1181,7 @@ export class CoreLoginHelperProvider { /** * Function called when site policy is not agreed. Reserved for core use. * - * @param {string} [siteId] Site ID. If not defined, current site. + * @param siteId Site ID. If not defined, current site. */ sitePolicyNotAgreed(siteId?: string): void { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -1215,10 +1209,10 @@ export class CoreLoginHelperProvider { /** * Convenient helper to handle get User Token error. It redirects to change password page if forcepassword is set. * - * @param {string} siteUrl Site URL to construct change password URL. - * @param {any} error Error object containing errorcode and error message. - * @param {string} [username] Username. - * @param {string} [password] User password. + * @param siteUrl Site URL to construct change password URL. + * @param error Error object containing errorcode and error message. + * @param username Username. + * @param password User password. */ treatUserTokenError(siteUrl: string, error: any, username?: string, password?: string): void { if (error.errorcode == 'forcepasswordchangenotice') { @@ -1239,8 +1233,8 @@ export class CoreLoginHelperProvider { /** * Convenient helper to validate a browser SSO login. * - * @param {string} url URL received, to be validated. - * @return {Promise} Promise resolved on success. + * @param url URL received, to be validated. + * @return Promise resolved on success. */ validateBrowserSSOLogin(url: string): Promise { // Split signature:::token diff --git a/src/core/mainmenu/pages/menu/menu.ts b/src/core/mainmenu/pages/menu/menu.ts index d06d413c2..afda6473a 100644 --- a/src/core/mainmenu/pages/menu/menu.ts +++ b/src/core/mainmenu/pages/menu/menu.ts @@ -187,7 +187,7 @@ export class CoreMainMenuPage implements OnDestroy { /** * Handle a redirect. * - * @param {any} data Data received. + * @param data Data received. */ protected handleRedirect(data: any): void { // Check if the redirect page is the root page of any of the tabs. diff --git a/src/core/mainmenu/pages/more/more.ts b/src/core/mainmenu/pages/more/more.ts index c91d984bb..09dd27b06 100644 --- a/src/core/mainmenu/pages/more/more.ts +++ b/src/core/mainmenu/pages/more/more.ts @@ -124,7 +124,7 @@ export class CoreMainMenuMorePage implements OnDestroy { /** * Open a handler. * - * @param {CoreMainMenuHandlerData} handler Handler to open. + * @param handler Handler to open. */ openHandler(handler: CoreMainMenuHandlerData): void { this.navCtrl.push(handler.page, handler.pageParams); @@ -133,7 +133,7 @@ export class CoreMainMenuMorePage implements OnDestroy { /** * Open an embedded custom item. * - * @param {CoreMainMenuCustomItem} item Item to open. + * @param item Item to open. */ openItem(item: CoreMainMenuCustomItem): void { this.navCtrl.push('CoreViewerIframePage', {title: item.label, url: item.url}); diff --git a/src/core/mainmenu/providers/delegate.ts b/src/core/mainmenu/providers/delegate.ts index a32d6e040..f42550d8b 100644 --- a/src/core/mainmenu/providers/delegate.ts +++ b/src/core/mainmenu/providers/delegate.ts @@ -25,14 +25,13 @@ import { Subject, BehaviorSubject } from 'rxjs'; export interface CoreMainMenuHandler extends CoreDelegateHandler { /** * The highest priority is displayed first. - * @type {number} */ priority: number; /** * Returns the data needed to render the handler. * - * @return {CoreMainMenuHandlerData} Data. + * @return Data. */ getDisplayData(): CoreMainMenuHandlerData; } @@ -43,55 +42,46 @@ export interface CoreMainMenuHandler extends CoreDelegateHandler { export interface CoreMainMenuHandlerData { /** * Name of the page to load for the handler. - * @type {string} */ page: string; /** * Title to display for the handler. - * @type {string} */ title: string; /** * Name of the icon to display for the handler. - * @type {string} */ icon: string; // Name of the icon to display in the tab. /** * Class to add to the displayed handler. - * @type {string} */ class?: string; /** * If the handler has badge to show or not. - * @type {boolean} */ showBadge?: boolean; /** * Text to display on the badge. Only used if showBadge is true. - * @type {string} */ badge?: string; /** * If true, the badge number is being loaded. Only used if showBadge is true. - * @type {boolean} */ loading?: boolean; /** * Params to pass to the page. - * @type {any} */ pageParams?: any; /** * Whether the handler should only appear in More menu. - * @type {boolean} */ onlyInMore?: boolean; } @@ -102,19 +92,16 @@ export interface CoreMainMenuHandlerData { export interface CoreMainMenuHandlerToDisplay extends CoreMainMenuHandlerData { /** * Name of the handler. - * @type {string} */ name?: string; /** * Priority of the handler. - * @type {number} */ priority?: number; /** * Hide tab. Used then resizing. - * @type {[type]} */ hide?: boolean; } @@ -139,7 +126,7 @@ export class CoreMainMenuDelegate 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(): boolean { return this.loaded; @@ -156,7 +143,7 @@ export class CoreMainMenuDelegate extends CoreDelegate { /** * Get the handlers for the current site. * - * @return {Subject} An observable that will receive the handlers. + * @return An observable that will receive the handlers. */ getHandlers(): Subject { return this.siteHandlers; diff --git a/src/core/mainmenu/providers/mainmenu.ts b/src/core/mainmenu/providers/mainmenu.ts index d601956c4..8181b1067 100644 --- a/src/core/mainmenu/providers/mainmenu.ts +++ b/src/core/mainmenu/providers/mainmenu.ts @@ -26,25 +26,21 @@ import { CoreMainMenuDelegate, CoreMainMenuHandlerToDisplay } from './delegate'; export interface CoreMainMenuCustomItem { /** * Type of the item: app, inappbrowser, browser or embedded. - * @type {string} */ type: string; /** * Url of the item. - * @type {string} */ url: string; /** * Label to display for the item. - * @type {string} */ label: string; /** * Name of the icon to display for the item. - * @type {string} */ icon: string; } @@ -66,7 +62,7 @@ export class CoreMainMenuProvider { /** * Get the current main menu handlers. * - * @return {Promise} Promise resolved with the current main menu handlers. + * @return Promise resolved with the current main menu handlers. */ getCurrentMainMenuHandlers(): Promise { const deferred = this.utils.promiseDefer(); @@ -89,8 +85,8 @@ export class CoreMainMenuProvider { /** * Get a list of custom menu items for a certain site. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} List of custom menu items. + * @param siteId Site ID. If not defined, current site. + * @return List of custom menu items. */ getCustomMenuItems(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -193,7 +189,7 @@ export class CoreMainMenuProvider { /** * Get the number of items to be shown on the main menu bar. * - * @return {number} Number of items depending on the device width. + * @return Number of items depending on the device width. */ getNumItems(): number { if (!this.isResponsiveMainMenuItemsDisabledInCurrentSite() && window && window.innerWidth) { @@ -219,8 +215,8 @@ export class CoreMainMenuProvider { /** * Get tabs placement depending on the device size. * - * @param {NavController} navCtrl NavController to resize the content. - * @return {string} Tabs placement including side value. + * @param navCtrl NavController to resize the content. + * @return Tabs placement including side value. */ getTabPlacement(navCtrl: NavController): string { const tablet = window && window.innerWidth && window.innerWidth >= 576 && window.innerHeight >= 576; @@ -240,9 +236,9 @@ export class CoreMainMenuProvider { /** * Check if a certain page is the root of a main menu handler currently displayed. * - * @param {string} page Name of the page. - * @param {string} [pageParams] Page params. - * @return {Promise} Promise resolved with boolean: whether it's the root of a main menu handler. + * @param page Name of the page. + * @param pageParams Page params. + * @return Promise resolved with boolean: whether it's the root of a main menu handler. */ isCurrentMainMenuHandler(pageName: string, pageParams?: any): Promise { return this.getCurrentMainMenuHandlers().then((handlers) => { @@ -257,7 +253,7 @@ export class CoreMainMenuProvider { /** * Check if responsive main menu items is disabled in the current site. * - * @return {boolean} Whether it's disabled. + * @return Whether it's disabled. */ protected isResponsiveMainMenuItemsDisabledInCurrentSite(): boolean { const site = this.sitesProvider.getCurrentSite(); diff --git a/src/core/pushnotifications/providers/delegate.ts b/src/core/pushnotifications/providers/delegate.ts index 8f64a4ed5..e15e28c60 100644 --- a/src/core/pushnotifications/providers/delegate.ts +++ b/src/core/pushnotifications/providers/delegate.ts @@ -24,36 +24,33 @@ import { Subject } from 'rxjs'; export interface CorePushNotificationsClickHandler { /** * A name to identify the handler. - * @type {string} */ name: string; /** * Handler's priority. The highest priority is treated first. - * @type {number} */ priority?: number; /** * 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; /** * 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; /** * 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; } @@ -77,8 +74,8 @@ export class CorePushNotificationsDelegate { /** * Function called when a push notification is clicked. Sends notification to handlers. * - * @param {any} notification Notification clicked. - * @return {Promise} Promise resolved when done. + * @param notification Notification clicked. + * @return Promise resolved when done. */ clicked(notification: any): Promise { if (!notification) { @@ -122,9 +119,9 @@ export class CorePushNotificationsDelegate { /** * Check if a handler's feature is disabled for a certain site. * - * @param {CorePushNotificationsClickHandler} handler Handler to check. - * @param {string} siteId The site ID to check. - * @return {Promise} Promise resolved with boolean: whether the handler feature is disabled. + * @param handler Handler to check. + * @param siteId The site ID to check. + * @return Promise resolved with boolean: whether the handler feature is disabled. */ protected isFeatureDisabled(handler: CorePushNotificationsClickHandler, siteId: string): Promise { if (handler.featureName) { @@ -139,7 +136,7 @@ export class CorePushNotificationsDelegate { * Function called when a push notification is received in foreground (cannot tell when it's received in background). * Sends notification to all handlers. * - * @param {any} notification Notification received. + * @param notification Notification received. */ received(notification: any): void { this.observables['receive'].next(notification); @@ -151,8 +148,8 @@ export class CorePushNotificationsDelegate { * ... * observer.unsuscribe(); * - * @param {string} eventName Only receive is permitted. - * @return {Subject} Observer to subscribe. + * @param eventName Only receive is permitted. + * @return Observer to subscribe. */ on(eventName: string): Subject { if (typeof this.observables[eventName] == 'undefined') { @@ -168,8 +165,8 @@ export class CorePushNotificationsDelegate { /** * Register a click handler. * - * @param {CorePushNotificationsClickHandler} 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. */ registerClickHandler(handler: CorePushNotificationsClickHandler): boolean { if (typeof this.clickHandlers[handler.name] !== 'undefined') { @@ -187,7 +184,7 @@ export class CorePushNotificationsDelegate { /** * Register a push notifications handler for update badge counter. * - * @param {string} name Handler's name. + * @param name Handler's name. */ registerCounterHandler(name: string): void { if (typeof this.counterHandlers[name] == 'undefined') { @@ -201,8 +198,8 @@ export class CorePushNotificationsDelegate { /** * Check if a counter handler is present. * - * @param {string} name Handler's name. - * @return {boolean} If handler name is present. + * @param name Handler's name. + * @return If handler name is present. */ isCounterHandlerRegistered(name: string): boolean { return typeof this.counterHandlers[name] != 'undefined'; @@ -211,7 +208,7 @@ export class CorePushNotificationsDelegate { /** * Get all counter badge handlers. * - * @return {any} with all the handler names. + * @return with all the handler names. */ getCounterHandlers(): any { return this.counterHandlers; diff --git a/src/core/pushnotifications/providers/pushnotifications.ts b/src/core/pushnotifications/providers/pushnotifications.ts index 0985c6b18..8e55142a0 100644 --- a/src/core/pushnotifications/providers/pushnotifications.ts +++ b/src/core/pushnotifications/providers/pushnotifications.ts @@ -40,43 +40,36 @@ import { CoreSite } from '@classes/site'; export interface CorePushNotificationsRegisterData { /** * App ID. - * @type {string} */ appid: string; /** * Device UUID. - * @type {string} */ uuid: string; /** * Device name. - * @type {string} */ name: string; /** * Device model. - * @type {string} */ model: string; /** * Device platform. - * @type {string} */ platform: string; /** * Device version. - * @type {string} */ version: string; /** * Push ID. - * @type {string} */ pushid: string; } @@ -203,7 +196,7 @@ export class CorePushNotificationsProvider { /** * Check whether the device can be registered in Moodle to receive push notifications. * - * @return {boolean} Whether the device can be registered in Moodle. + * @return Whether the device can be registered in Moodle. */ canRegisterOnMoodle(): boolean { return this.pushID && this.appProvider.isMobile(); @@ -212,8 +205,8 @@ export class CorePushNotificationsProvider { /** * Delete all badge records for a given site. * - * @param {string} siteId Site ID. - * @return {Promise} Resolved when done. + * @param siteId Site ID. + * @return Resolved when done. */ cleanSiteCounters(siteId: string): Promise { return this.appDB.deleteRecords(CorePushNotificationsProvider.BADGE_TABLE, {siteid: siteId} ).finally(() => { @@ -224,7 +217,7 @@ export class CorePushNotificationsProvider { /** * Create the default push 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')) { @@ -243,8 +236,8 @@ export class CorePushNotificationsProvider { /** * Enable or disable Firebase analytics. * - * @param {boolean} enable Whether to enable or disable. - * @return {Promise} Promise resolved when done. + * @param enable Whether to enable or disable. + * @return Promise resolved when done. */ enableAnalytics(enable: boolean): Promise { const win = window; // This feature is only present in our fork of the plugin. @@ -264,7 +257,7 @@ export class CorePushNotificationsProvider { /** * Returns options for push notifications based on device. * - * @return {Promise} Promise with the push options resolved when done. + * @return Promise with the push options resolved when done. */ protected getOptions(): Promise { let promise; @@ -297,7 +290,7 @@ export class CorePushNotificationsProvider { /** * Get the pushID for this device. * - * @return {string} Push ID. + * @return Push ID. */ getPushId(): string { return this.pushID; @@ -306,7 +299,7 @@ export class CorePushNotificationsProvider { /** * Get data to register the device in Moodle. * - * @return {CorePushNotificationsRegisterData} Data. + * @return Data. */ protected getRegisterData(): CorePushNotificationsRegisterData { return { @@ -323,8 +316,8 @@ export class CorePushNotificationsProvider { /** * Get Sitebadge counter from the database. * - * @param {string} siteId Site ID. - * @return {Promise} Promise resolved with the stored badge counter for the site. + * @param siteId Site ID. + * @return Promise resolved with the stored badge counter for the site. */ getSiteCounter(siteId: string): Promise { return this.getAddonBadge(siteId); @@ -333,10 +326,10 @@ export class CorePushNotificationsProvider { /** * Log a firebase event. * - * @param {string} name Name of the event. - * @param {any} data Data of the event. - * @param {boolean} [filter] Whether to filter the data. This is useful when logging a full notification. - * @return {Promise} Promise resolved when done. This promise is never rejected. + * @param name Name of the event. + * @param data Data of the event. + * @param filter Whether to filter the data. This is useful when logging a full notification. + * @return Promise resolved when done. This promise is never rejected. */ logEvent(name: string, data: any, filter?: boolean): Promise { const win = window; // This feature is only present in our fork of the plugin. @@ -362,13 +355,13 @@ export class CorePushNotificationsProvider { /** * Log a firebase view_item event. * - * @param {number|string} itemId The item ID. - * @param {string} itemName The item name. - * @param {string} itemCategory The item category. - * @param {string} wsName Name of the WS. - * @param {any} [data] Other data to pass to the event. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. This promise is never rejected. + * @param itemId The item ID. + * @param itemName The item name. + * @param itemCategory The item category. + * @param wsName Name of the WS. + * @param data Other data to pass to the event. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. This promise is never rejected. */ logViewEvent(itemId: number | string, itemName: string, itemCategory: string, wsName: string, data?: any, siteId?: string) : Promise { @@ -395,11 +388,11 @@ export class CorePushNotificationsProvider { /** * Log a firebase view_item_list event. * - * @param {string} itemCategory The item category. - * @param {string} wsName Name of the WS. - * @param {any} [data] Other data to pass to the event. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. This promise is never rejected. + * @param itemCategory The item category. + * @param wsName Name of the WS. + * @param data Other data to pass to the event. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. This promise is never rejected. */ logViewListEvent(itemCategory: string, wsName: string, data?: any, siteId?: string): Promise { data = data || {}; @@ -419,7 +412,7 @@ export class CorePushNotificationsProvider { /** * Function called when a push notification is clicked. Redirect the user to the right state. * - * @param {any} notification Notification. + * @param notification Notification. */ notificationClicked(notification: any): void { this.initDelegate.ready().then(() => { @@ -432,7 +425,7 @@ export class CorePushNotificationsProvider { * The app can be in foreground or background, * if we are in background this code is executed when we open the app clicking in the notification bar. * - * @param {any} notification Notification received. + * @param notification Notification received. */ onMessageReceived(notification: any): void { const data = notification ? notification.additionalData : {}; @@ -510,8 +503,8 @@ export class CorePushNotificationsProvider { /** * Unregisters a device from a certain Moodle site. * - * @param {CoreSite} site Site to unregister from. - * @return {Promise} Promise resolved when device is unregistered. + * @param site Site to unregister from. + * @return Promise resolved when device is unregistered. */ unregisterDeviceOnMoodle(site: CoreSite): Promise { if (!site || !this.appProvider.isMobile()) { @@ -564,10 +557,10 @@ export class CorePushNotificationsProvider { * Update Counter for an addon. It will update the refered siteId counter and the total badge. * It will return the updated addon counter. * - * @param {string} addon Registered addon name to set the badge number. - * @param {number} value The number to be stored. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @return {Promise} Promise resolved with the stored badge counter for the addon on the site. + * @param addon Registered addon name to set the badge number. + * @param value The number to be stored. + * @param siteId Site ID. If not defined, use current site. + * @return Promise resolved with the stored badge counter for the addon on the site. */ updateAddonCounter(addon: string, value: number, siteId?: string): Promise { if (this.pushNotificationsDelegate.isCounterHandlerRegistered(addon)) { @@ -586,7 +579,7 @@ export class CorePushNotificationsProvider { /** * Update total badge counter of the app. * - * @return {Promise} Promise resolved with the stored badge counter for the site. + * @return Promise resolved with the stored badge counter for the site. */ updateAppCounter(): Promise { return this.sitesProvider.getSitesIds().then((sites) => { @@ -618,8 +611,8 @@ export class CorePushNotificationsProvider { * Update counter for a site using the stored addon data. It will update the total badge application number. * It will return the updated site counter. * - * @param {string} siteId Site ID. - * @return {Promise} Promise resolved with the stored badge counter for the site. + * @param siteId Site ID. + * @return Promise resolved with the stored badge counter for the site. */ updateSiteCounter(siteId: string): Promise { const addons = this.pushNotificationsDelegate.getCounterHandlers(), @@ -655,7 +648,7 @@ export class CorePushNotificationsProvider { /** * Register a device in Apple APNS or Google GCM. * - * @return {Promise} Promise resolved when the device is registered. + * @return Promise resolved when the device is registered. */ registerDevice(): Promise { try { @@ -701,9 +694,9 @@ export class CorePushNotificationsProvider { /** * Registers a device on a Moodle site if needed. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {boolean} [forceUnregister] Whether to force unregister and register. - * @return {Promise} Promise resolved when device is registered. + * @param siteId Site ID. If not defined, current site. + * @param forceUnregister Whether to force unregister and register. + * @return Promise resolved when device is registered. */ registerDeviceOnMoodle(siteId?: string, forceUnregister?: boolean): Promise { this.logger.debug('Register device on Moodle.'); @@ -756,9 +749,9 @@ export class CorePushNotificationsProvider { /** * Get the addon/site badge counter from the database. * - * @param {string} siteId Site ID. - * @param {string} [addon='site'] Registered addon name. If not defined it will store the site total. - * @return {Promise} Promise resolved with the stored badge counter for the addon or site or 0 if none. + * @param siteId Site ID. + * @param addon Registered addon name. If not defined it will store the site total. + * @return Promise resolved with the stored badge counter for the addon or site or 0 if none. */ protected getAddonBadge(siteId?: string, addon: string = 'site'): Promise { return this.appDB.getRecord(CorePushNotificationsProvider.BADGE_TABLE, {siteid: siteId, addon: addon}).then((entry) => { @@ -771,8 +764,8 @@ export class CorePushNotificationsProvider { /** * Retry pending unregisters. * - * @param {string} [siteId] If defined, retry only for that site if needed. Otherwise, retry all pending unregisters. - * @return {Promise} Promise resolved when done. + * @param siteId If defined, retry only for that site if needed. Otherwise, retry all pending unregisters. + * @return Promise resolved when done. */ retryUnregisters(siteId?: string): Promise { let promise; @@ -803,10 +796,10 @@ export class CorePushNotificationsProvider { /** * Save the addon/site badgecounter on the database. * - * @param {number} value The number to be stored. - * @param {string} [siteId] Site ID. If not defined, use current site. - * @param {string} [addon='site'] Registered addon name. If not defined it will store the site total. - * @return {Promise} Promise resolved with the stored badge counter for the addon or site. + * @param value The number to be stored. + * @param siteId Site ID. If not defined, use current site. + * @param addon Registered addon name. If not defined it will store the site total. + * @return Promise resolved with the stored badge counter for the addon or site. */ protected saveAddonBadge(value: number, siteId?: string, addon: string = 'site'): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -825,9 +818,9 @@ export class CorePushNotificationsProvider { /** * Check if device should be registered (and unregistered first). * - * @param {CorePushNotificationsRegisterData} data Data of the device. - * @param {CoreSite} site Site to use. - * @return {Promise<{register: boolean, unregister: boolean}>} Promise resolved with booleans: whether to register/unregister. + * @param data Data of the device. + * @param site Site to use. + * @return Promise resolved with booleans: whether to register/unregister. */ protected shouldRegister(data: CorePushNotificationsRegisterData, site: CoreSite) : Promise<{register: boolean, unregister: boolean}> { diff --git a/src/core/pushnotifications/providers/register-cron-handler.ts b/src/core/pushnotifications/providers/register-cron-handler.ts index e2bc48b2c..ea03c062f 100644 --- a/src/core/pushnotifications/providers/register-cron-handler.ts +++ b/src/core/pushnotifications/providers/register-cron-handler.ts @@ -28,7 +28,7 @@ export class CorePushNotificationsRegisterCronHandler 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; // Execute the handler when the site is manually synchronized. @@ -38,8 +38,8 @@ export class CorePushNotificationsRegisterCronHandler 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. - * @return {Promise} Promise resolved when done, rejected if failure. + * @param siteId ID of the site affected, undefined for all sites. + * @return Promise resolved when done, rejected if failure. */ execute(siteId?: string): Promise { if (!siteId || !this.pushNotificationsProvider.canRegisterOnMoodle()) { @@ -54,7 +54,7 @@ export class CorePushNotificationsRegisterCronHandler 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 86400000; // 1 day. We won't do anything with automatic execution, so use a big number. @@ -63,7 +63,7 @@ export class CorePushNotificationsRegisterCronHandler 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 { return false; diff --git a/src/core/pushnotifications/providers/unregister-cron-handler.ts b/src/core/pushnotifications/providers/unregister-cron-handler.ts index f533efaa0..a2c87c179 100644 --- a/src/core/pushnotifications/providers/unregister-cron-handler.ts +++ b/src/core/pushnotifications/providers/unregister-cron-handler.ts @@ -29,8 +29,8 @@ export class CorePushNotificationsUnregisterCronHandler implements CoreCronHandl * 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. - * @return {Promise} Promise resolved when done, rejected if failure. + * @param siteId ID of the site affected, undefined for all sites. + * @return Promise resolved when done, rejected if failure. */ execute(siteId?: string): Promise { return this.pushNotificationsProvider.retryUnregisters(siteId); @@ -39,7 +39,7 @@ export class CorePushNotificationsUnregisterCronHandler implements CoreCronHandl /** * 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; diff --git a/src/core/question/classes/base-behaviour-handler.ts b/src/core/question/classes/base-behaviour-handler.ts index 8da0309ac..88bf3ed61 100644 --- a/src/core/question/classes/base-behaviour-handler.ts +++ b/src/core/question/classes/base-behaviour-handler.ts @@ -31,11 +31,11 @@ export class CoreQuestionBehaviourBaseHandler implements CoreQuestionBehaviourHa /** * 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 { @@ -48,10 +48,10 @@ export class CoreQuestionBehaviourBaseHandler implements CoreQuestionBehaviourHa * 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 { // Nothing to do. @@ -61,7 +61,7 @@ export class CoreQuestionBehaviourBaseHandler implements CoreQuestionBehaviourHa /** * 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/question/classes/base-question-component.ts b/src/core/question/classes/base-question-component.ts index c36375f8b..4d3275fc9 100644 --- a/src/core/question/classes/base-question-component.ts +++ b/src/core/question/classes/base-question-component.ts @@ -49,7 +49,7 @@ export class CoreQuestionBaseComponent { /** * Initialize a question component of type calculated or calculated simple. * - * @return {void|HTMLElement} Element containing the question HTML, void if the data is not valid. + * @return Element containing the question HTML, void if the data is not valid. */ initCalculatedComponent(): void | HTMLElement { // Treat the input text first. @@ -165,7 +165,7 @@ export class CoreQuestionBaseComponent { /** * Initialize the component and the question text. * - * @return {void|HTMLElement} Element containing the question HTML, void if the data is not valid. + * @return Element containing the question HTML, void if the data is not valid. */ initComponent(): void | HTMLElement { if (!this.question) { @@ -192,7 +192,7 @@ export class CoreQuestionBaseComponent { /** * Initialize a question component of type essay. * - * @return {void|HTMLElement} Element containing the question HTML, void if the data is not valid. + * @return Element containing the question HTML, void if the data is not valid. */ initEssayComponent(): void | HTMLElement { const questionEl = this.initComponent(); @@ -234,8 +234,8 @@ export class CoreQuestionBaseComponent { /** * Initialize a question component that uses the original question text with some basic treatment. * - * @param {string} contentSelector The selector to find the question content (text). - * @return {void|HTMLElement} Element containing the question HTML, void if the data is not valid. + * @param contentSelector The selector to find the question content (text). + * @return Element containing the question HTML, void if the data is not valid. */ initOriginalTextComponent(contentSelector: string): void | HTMLElement { if (!this.question) { @@ -272,7 +272,7 @@ export class CoreQuestionBaseComponent { /** * Initialize a question component that has an input of type "text". * - * @return {void|HTMLElement} Element containing the question HTML, void if the data is not valid. + * @return Element containing the question HTML, void if the data is not valid. */ initInputTextComponent(): void | HTMLElement { const questionEl = this.initComponent(); @@ -329,7 +329,7 @@ export class CoreQuestionBaseComponent { /** * Initialize a question component with a "match" behaviour. * - * @return {void|HTMLElement} Element containing the question HTML, void if the data is not valid. + * @return Element containing the question HTML, void if the data is not valid. */ initMatchComponent(): void | HTMLElement { const questionEl = this.initComponent(); @@ -421,7 +421,7 @@ export class CoreQuestionBaseComponent { /** * Initialize a question component with a multiple choice (checkbox) or single choice (radio). * - * @return {void|HTMLElement} Element containing the question HTML, void if the data is not valid. + * @return Element containing the question HTML, void if the data is not valid. */ initMultichoiceComponent(): void | HTMLElement { const questionEl = this.initComponent(); diff --git a/src/core/question/classes/base-question-handler.ts b/src/core/question/classes/base-question-handler.ts index e7782c2fb..5cc31dde5 100644 --- a/src/core/question/classes/base-question-handler.ts +++ b/src/core/question/classes/base-question-handler.ts @@ -32,7 +32,7 @@ export class CoreQuestionBaseHandler 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; @@ -42,9 +42,9 @@ export class CoreQuestionBaseHandler 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 { // There is no default component for questions. @@ -54,9 +54,9 @@ export class CoreQuestionBaseHandler 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 behaviour; @@ -66,8 +66,8 @@ export class CoreQuestionBaseHandler 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 { // Never prevent by default. @@ -77,9 +77,9 @@ export class CoreQuestionBaseHandler 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 -1; @@ -89,9 +89,9 @@ export class CoreQuestionBaseHandler 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 -1; @@ -100,10 +100,10 @@ export class CoreQuestionBaseHandler 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 false; @@ -112,11 +112,11 @@ export class CoreQuestionBaseHandler 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 { // Nothing to do. @@ -126,9 +126,9 @@ export class CoreQuestionBaseHandler 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 { return question.sequencecheck == offlineSequenceCheck; diff --git a/src/core/question/components/question/question.ts b/src/core/question/components/question/question.ts index 11b276ce4..8d1ee9f1c 100644 --- a/src/core/question/components/question/question.ts +++ b/src/core/question/components/question/question.ts @@ -156,7 +156,7 @@ export class CoreQuestionComponent implements OnInit { /** * Update the sequence check of the question. * - * @param {any} sequenceChecks Object with sequence checks. The keys are the question slot. + * @param sequenceChecks Object with sequence checks. The keys are the question slot. */ updateSequenceCheck(sequenceChecks: any): void { if (sequenceChecks[this.question.slot]) { diff --git a/src/core/question/providers/behaviour-delegate.ts b/src/core/question/providers/behaviour-delegate.ts index 9333e87f9..77ee65506 100644 --- a/src/core/question/providers/behaviour-delegate.ts +++ b/src/core/question/providers/behaviour-delegate.ts @@ -27,18 +27,17 @@ import { CoreQuestionBehaviourDefaultHandler } from './default-behaviour-handler export interface CoreQuestionBehaviourHandler extends CoreDelegateHandler { /** * Type of the behaviour the handler supports. E.g. 'adaptive'. - * @type {string} */ type: string; /** * 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} 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 State (or promise resolved with state). */ determineNewState?(component: string, attemptId: number, question: any, siteId?: string) : CoreQuestionState | Promise; @@ -48,10 +47,10 @@ export interface CoreQuestionBehaviourHandler extends CoreDelegateHandler { * 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; } @@ -72,11 +71,11 @@ export class CoreQuestionBehaviourDelegate extends CoreDelegate { /** * 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 {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. + * @return Promise resolved with state. */ determineNewState(behaviour: string, component: string, attemptId: number, question: any, siteId?: string) : Promise { @@ -91,10 +90,10 @@ export class CoreQuestionBehaviourDelegate extends CoreDelegate { * 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 a directive to render it. * - * @param {Injector} injector Injector. - * @param {string} behaviour Default behaviour. - * @param {any} question The question. - * @return {Promise} Promise resolved with components to render some extra data in the question. + * @param injector Injector. + * @param behaviour Default behaviour. + * @param question The question. + * @return Promise resolved with components to render some extra data in the question. */ handleQuestion(injector: Injector, behaviour: string, question: any): Promise { behaviour = this.questionDelegate.getBehaviourForQuestion(question, behaviour); @@ -105,8 +104,8 @@ export class CoreQuestionBehaviourDelegate extends CoreDelegate { /** * Check if a question behaviour is supported. * - * @param {string} behaviour Name of the behaviour. - * @return {boolean} Whether it's supported. + * @param behaviour Name of the behaviour. + * @return Whether it's supported. */ isBehaviourSupported(behaviour: string): boolean { return this.hasHandler(behaviour, true); diff --git a/src/core/question/providers/delegate.ts b/src/core/question/providers/delegate.ts index 3dc33326d..5c78d4e95 100644 --- a/src/core/question/providers/delegate.ts +++ b/src/core/question/providers/delegate.ts @@ -25,7 +25,6 @@ import { CoreQuestionDefaultHandler } from './default-question-handler'; export interface CoreQuestionHandler extends CoreDelegateHandler { /** * Type of the question the handler supports. E.g. 'qtype_calculated'. - * @type {string} */ type: string; @@ -33,9 +32,9 @@ export interface CoreQuestionHandler extends CoreDelegateHandler { * 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; @@ -43,9 +42,9 @@ export interface CoreQuestionHandler extends CoreDelegateHandler { * 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; @@ -53,17 +52,17 @@ export interface CoreQuestionHandler extends CoreDelegateHandler { * 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; /** * 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; @@ -71,30 +70,30 @@ export interface CoreQuestionHandler extends CoreDelegateHandler { * 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; /** * 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; /** * 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; @@ -102,18 +101,18 @@ export interface CoreQuestionHandler extends CoreDelegateHandler { * 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; /** * 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[]; } @@ -135,9 +134,9 @@ export class CoreQuestionDelegate extends CoreDelegate { * Get the behaviour to use for a certain question type. * E.g. 'qtype_essay' uses 'manualgraded'. * - * @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. */ getBehaviourForQuestion(question: any, behaviour: string): string { const type = this.getTypeName(question), @@ -149,9 +148,9 @@ export class CoreQuestionDelegate extends CoreDelegate { /** * Get the directive to use for a certain question type. * - * @param {Injector} injector Injector. - * @param {any} question The question to render. - * @return {Promise} Promise resolved with component to use, undefined if not found. + * @param injector Injector. + * @param question The question to render. + * @return Promise resolved with component to use, undefined if not found. */ getComponentForQuestion(injector: Injector, question: any): Promise { const type = this.getTypeName(question); @@ -163,8 +162,8 @@ export class CoreQuestionDelegate extends CoreDelegate { * 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 Question. - * @return {string} Prevent submit message. Undefined or empty if can be submitted. + * @param question Question. + * @return Prevent submit message. Undefined or empty if can be submitted. */ getPreventSubmitMessage(question: any): string { const type = this.getTypeName(question); @@ -175,8 +174,8 @@ export class CoreQuestionDelegate extends CoreDelegate { /** * Given a type name, return the full name of that type. E.g. 'calculated' -> 'qtype_calculated'. * - * @param {string} type Type to treat. - * @return {string} Type full name. + * @param type Type to treat. + * @return Type full name. */ protected getFullTypeName(type: string): string { return 'qtype_' + type; @@ -185,8 +184,8 @@ export class CoreQuestionDelegate extends CoreDelegate { /** * Given a question, return the full name of its question type. * - * @param {any} question Question. - * @return {string} Type name. + * @param question Question. + * @return Type name. */ protected getTypeName(question: any): string { return this.getFullTypeName(question.type); @@ -195,9 +194,9 @@ export class CoreQuestionDelegate extends CoreDelegate { /** * 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 type = this.getTypeName(question); @@ -209,9 +208,9 @@ export class CoreQuestionDelegate extends CoreDelegate { * 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 { const type = this.getTypeName(question); @@ -222,10 +221,10 @@ export class CoreQuestionDelegate extends CoreDelegate { /** * 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 { const type = this.getTypeName(question); @@ -236,8 +235,8 @@ export class CoreQuestionDelegate extends CoreDelegate { /** * Check if a question type is supported. * - * @param {string} type Question type. - * @return {boolean} Whether it's supported. + * @param type Question type. + * @return Whether it's supported. */ isQuestionSupported(type: string): boolean { return this.hasHandler(this.getFullTypeName(type), true); @@ -246,11 +245,11 @@ export class CoreQuestionDelegate extends CoreDelegate { /** * Prepare the answers for a certain question. * - * @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 {Promise} Promise resolved when data has been prepared. + * @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 Promise resolved when data has been prepared. */ prepareAnswersForQuestion(question: any, answers: any, offline: boolean, siteId?: string): Promise { const type = this.getTypeName(question); @@ -261,9 +260,9 @@ export class CoreQuestionDelegate extends CoreDelegate { /** * Validate if an offline sequencecheck is valid compared with the online one. * - * @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 { const type = this.getTypeName(question); @@ -274,9 +273,9 @@ export class CoreQuestionDelegate extends CoreDelegate { /** * 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[] { const type = this.getTypeName(question); diff --git a/src/core/question/providers/helper.ts b/src/core/question/providers/helper.ts index e564a97d8..b69929f1e 100644 --- a/src/core/question/providers/helper.ts +++ b/src/core/question/providers/helper.ts @@ -38,8 +38,8 @@ export class CoreQuestionHelperProvider { /** * Add a behaviour button to the question's "behaviourButtons" property. * - * @param {any} question Question. - * @param {HTMLInputElement} button Behaviour button (DOM element). + * @param question Question. + * @param button Behaviour button (DOM element). */ protected addBehaviourButton(question: any, button: HTMLInputElement): void { if (!button || !question) { @@ -63,8 +63,8 @@ export class CoreQuestionHelperProvider { * Extract question behaviour submit buttons from the question's HTML and add them to "behaviourButtons" property. * The buttons aren't deleted from the content because all the im-controls block will be removed afterwards. * - * @param {any} question Question to treat. - * @param {string} [selector] Selector to search the buttons. By default, '.im-controls input[type="submit"]'. + * @param question Question to treat. + * @param selector Selector to search the buttons. By default, '.im-controls input[type="submit"]'. */ extractQbehaviourButtons(question: any, selector?: string): void { if (this.questionDelegate.getPreventSubmitMessage(question)) { @@ -89,8 +89,8 @@ export class CoreQuestionHelperProvider { * The value of the selected option is stored in question.behaviourCertaintySelected. * We don't remove them from HTML because the whole im-controls block will be removed afterwards. * - * @param {any} question Question to treat. - * @return {boolean} Wether the certainty is found. + * @param question Question to treat. + * @return Wether the certainty is found. */ extractQbehaviourCBM(question: any): boolean { const element = this.domUtils.convertToElement(question.html); @@ -128,7 +128,7 @@ export class CoreQuestionHelperProvider { * Check if the question has a redo button and, if so, add it to "behaviourButtons" property * and remove it from the HTML. * - * @param {any} question Question to treat. + * @param question Question to treat. */ extractQbehaviourRedoButton(question: any): void { // Create a fake div element so we can search using querySelector. @@ -156,8 +156,8 @@ export class CoreQuestionHelperProvider { * Check if the question contains a "seen" input. * If so, add the name and value to a "behaviourSeenInput" property and remove the input. * - * @param {any} question Question to treat. - * @return {boolean} Whether the seen input is found. + * @param question Question to treat. + * @return Whether the seen input is found. */ extractQbehaviourSeenInput(question: any): boolean { const element = this.domUtils.convertToElement(question.html); @@ -182,7 +182,7 @@ export class CoreQuestionHelperProvider { /** * Removes the comment from the question HTML code and adds it in a new "commentHtml" property. * - * @param {any} question Question. + * @param question Question. */ extractQuestionComment(question: any): void { this.extractQuestionLastElementNotInContent(question, '.comment', 'commentHtml'); @@ -191,7 +191,7 @@ export class CoreQuestionHelperProvider { /** * Removes the feedback from the question HTML code and adds it in a new "feedbackHtml" property. * - * @param {any} question Question. + * @param question Question. */ extractQuestionFeedback(question: any): void { this.extractQuestionLastElementNotInContent(question, '.outcome', 'feedbackHtml'); @@ -200,8 +200,8 @@ export class CoreQuestionHelperProvider { /** * Extracts the info box from a question and add it to an "infoHtml" property. * - * @param {any} question Question. - * @param {string} selector Selector to search the element. + * @param question Question. + * @param selector Selector to search the element. */ extractQuestionInfoBox(question: any, selector: string): void { this.extractQuestionLastElementNotInContent(question, selector, 'infoHtml'); @@ -211,9 +211,9 @@ export class CoreQuestionHelperProvider { * Searches the last occurrence of a certain element and check it's not in the question contents. * If found, removes it from the question HTML and adds it to a new property inside question. * - * @param {any} question Question. - * @param {string} selector Selector to search the element. - * @param {string} attrName Name of the attribute to store the HTML in. + * @param question Question. + * @param selector Selector to search the element. + * @param attrName Name of the attribute to store the HTML in. */ protected extractQuestionLastElementNotInContent(question: any, selector: string, attrName: string): void { const element = this.domUtils.convertToElement(question.html); @@ -241,8 +241,8 @@ export class CoreQuestionHelperProvider { * Removes the scripts from a question's HTML and adds it in a new 'scriptsCode' property. * It will also search for init_question functions of the question type and add the object to an 'initObjects' property. * - * @param {any} question Question. - * @param {number} usageId Usage ID. + * @param question Question. + * @param usageId Usage ID. */ extractQuestionScripts(question: any, usageId: number): void { question.scriptsCode = ''; @@ -292,8 +292,8 @@ export class CoreQuestionHelperProvider { * This function will return an object where the keys are the input names. The values will always be true. * This is in order to make this function compatible with other functions like CoreQuestionProvider.getBasicAnswers. * - * @param {string} html HTML code. - * @return {any} Object where the keys are the names. + * @param html HTML code. + * @return Object where the keys are the names. */ getAllInputNamesFromHtml(html: string): any { const element = this.domUtils.convertToElement('
' + html + '
'), @@ -319,8 +319,8 @@ export class CoreQuestionHelperProvider { * Retrieve the answers entered in a form. * We don't use ngModel because it doesn't detect changes done by JavaScript and some questions might do that. * - * @param {HTMLFormElement} form Form. - * @return {any} Object with the answers. + * @param form Form. + * @return Object with the answers. */ getAnswersFromForm(form: HTMLFormElement): any { if (!form || !form.elements) { @@ -358,8 +358,8 @@ export class CoreQuestionHelperProvider { * Please take into account that this function will treat all the anchors in the HTML, you should provide * an HTML containing only the attachments anchors. * - * @param {String} html HTML code to search in. - * @return {Object[]} Attachments. + * @param html HTML code to search in. + * @return Attachments. */ getQuestionAttachmentsFromHtml(html: string): any[] { const element = this.domUtils.convertToElement(html); @@ -390,8 +390,8 @@ export class CoreQuestionHelperProvider { /** * Get the sequence check from a question HTML. * - * @param {string} html Question's HTML. - * @return {{name: string, value: string}} Object with the sequencecheck name and value. + * @param html Question's HTML. + * @return Object with the sequencecheck name and value. */ getQuestionSequenceCheckFromHtml(html: string): {name: string, value: string} { if (html) { @@ -411,8 +411,8 @@ export class CoreQuestionHelperProvider { /** * Get the CSS class for a question based on its state. * - * @param {string} name Question's state name. - * @return {string} State class. + * @param name Question's state name. + * @return State class. */ getQuestionStateClass(name: string): string { const state = this.questionProvider.getState(name); @@ -423,8 +423,8 @@ export class CoreQuestionHelperProvider { /** * Get the validation error message from a question HTML if it's there. * - * @param {string} html Question's HTML. - * @return {string} Validation error message if present. + * @param html Question's HTML. + * @return Validation error message if present. */ getValidationErrorFromHtml(html: string): string { const element = this.domUtils.convertToElement(html); @@ -435,8 +435,8 @@ export class CoreQuestionHelperProvider { /** * Check if some HTML contains draft file URLs for the current site. * - * @param {string} html Question's HTML. - * @return {boolean} Whether it contains draft files URLs. + * @param html Question's HTML. + * @return Whether it contains draft files URLs. */ hasDraftFileUrls(html: string): boolean { let url = this.sitesProvider.getCurrentSite().getURL(); @@ -452,7 +452,7 @@ export class CoreQuestionHelperProvider { * For each input element found in the HTML, search if there's a local answer stored and * override the HTML's value with the local one. * - * @param {any} question Question. + * @param question Question. */ loadLocalAnswersInHtml(question: any): void { const element = this.domUtils.convertToElement('
' + question.html + '
'), @@ -507,12 +507,12 @@ export class CoreQuestionHelperProvider { /** * Prefetch the files in a question HTML. * - * @param {any} question Question. - * @param {string} [component] The component to link the files to. If not defined, question component. - * @param {string|number} [componentId] An ID to use in conjunction with the component. If not defined, question ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {number} [usageId] Usage ID. Required in Moodle 3.7+. - * @return {Promise} Promise resolved when all the files have been downloaded. + * @param question Question. + * @param component The component to link the files to. If not defined, question component. + * @param componentId An ID to use in conjunction with the component. If not defined, question ID. + * @param siteId Site ID. If not defined, current site. + * @param usageId Usage ID. Required in Moodle 3.7+. + * @return Promise resolved when all the files have been downloaded. */ prefetchQuestionFiles(question: any, component?: string, componentId?: string | number, siteId?: string, usageId?: number) : Promise { @@ -548,11 +548,11 @@ export class CoreQuestionHelperProvider { /** * Prepare and return the answers. * - * @param {any[]} questions The list of questions. - * @param {any} answers The input data. - * @param {boolean} offline True if data should be saved in offline. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with answers to send to server. + * @param questions The list of questions. + * @param answers The input data. + * @param offline True if data should be saved in offline. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with answers to send to server. */ prepareAnswers(questions: any[], answers: any, offline?: boolean, siteId?: string): Promise { const promises = []; @@ -570,7 +570,7 @@ export class CoreQuestionHelperProvider { /** * Replace Moodle's correct/incorrect classes with the Mobile ones. * - * @param {HTMLElement} element DOM element. + * @param element DOM element. */ replaceCorrectnessClasses(element: HTMLElement): void { this.domUtils.replaceClassesInElement(element, { @@ -582,7 +582,7 @@ export class CoreQuestionHelperProvider { /** * Replace Moodle's feedback classes with the Mobile ones. * - * @param {HTMLElement} element DOM element. + * @param element DOM element. */ replaceFeedbackClasses(element: HTMLElement): void { this.domUtils.replaceClassesInElement(element, { @@ -594,10 +594,10 @@ export class CoreQuestionHelperProvider { /** * Search a behaviour button in a certain question property containing HTML. * - * @param {any} question Question. - * @param {string} htmlProperty The name of the property containing the HTML to search. - * @param {string} selector The selector to find the button. - * @return {boolean} Whether the button is found. + * @param question Question. + * @param htmlProperty The name of the property containing the HTML to search. + * @param selector The selector to find the button. + * @return Whether the button is found. */ protected searchBehaviourButton(question: any, htmlProperty: string, selector: string): boolean { const element = this.domUtils.convertToElement(question[htmlProperty]); @@ -622,8 +622,8 @@ export class CoreQuestionHelperProvider { /** * Convenience function to show a parsing error and abort. * - * @param {EventEmitter} [onAbort] If supplied, will emit an event. - * @param {string} [error] Error to show. + * @param onAbort If supplied, will emit an event. + * @param error Error to show. */ showComponentError(onAbort: EventEmitter, error?: string): void { // Prevent consecutive errors. @@ -639,7 +639,7 @@ export class CoreQuestionHelperProvider { /** * Treat correctness icons, replacing them with local icons and setting click events to show the feedback if needed. * - * @param {HTMLElement} element DOM element. + * @param element DOM element. */ treatCorrectnessIcons(element: HTMLElement): void { @@ -688,9 +688,9 @@ export class CoreQuestionHelperProvider { /** * Add click listeners to all tappable correctness icons. * - * @param {HTMLElement} element DOM element. - * @param {string} [component] The component to use when viewing the feedback. - * @param {string|number} [componentId] An ID to use in conjunction with the component. + * @param element DOM element. + * @param component The component to use when viewing the feedback. + * @param componentId An ID to use in conjunction with the component. */ treatCorrectnessIconsClicks(element: HTMLElement, component?: string, componentId?: number): void { const icons = Array.from(element.querySelectorAll('i.icon.questioncorrectnessicon[tappable]')), diff --git a/src/core/question/providers/question.ts b/src/core/question/providers/question.ts index 92d8bc55c..d8372a358 100644 --- a/src/core/question/providers/question.ts +++ b/src/core/question/providers/question.ts @@ -24,31 +24,26 @@ import { CoreUtilsProvider } from '@providers/utils/utils'; export interface CoreQuestionState { /** * Name of the state. - * @type {string} */ name: string; /** * Class of the state. - * @type {string} */ class: string; /** * The string key to translate the status. - * @type {string} */ status: string; /** * Whether the question with this state is active. - * @type {boolean} */ active: boolean; /** * Whether the question with this state is finished. - * @type {boolean} */ finished: boolean; } @@ -254,9 +249,9 @@ export class CoreQuestionProvider { /** * Compare that all the answers in two objects are equal, except some extra data like sequencecheck or certainty. * - * @param {any} prevAnswers Object with previous answers. - * @param {any} newAnswers Object with new answers. - * @return {boolean} Whether all answers are equal. + * @param prevAnswers Object with previous answers. + * @param newAnswers Object with new answers. + * @return Whether all answers are equal. */ compareAllAnswers(prevAnswers: any, newAnswers: any): boolean { // Get all the keys. @@ -280,9 +275,9 @@ export class CoreQuestionProvider { /** * Convert a list of answers retrieved from local DB to an object with name - value. * - * @param {any[]} answers List of answers. - * @param {boolean} [removePrefix] Whether to remove the prefix in the answer's name. - * @return {any} Object with name -> value. + * @param answers List of answers. + * @param removePrefix Whether to remove the prefix in the answer's name. + * @return Object with name -> value. */ convertAnswersArrayToObject(answers: any[], removePrefix?: boolean): any { const result = {}; @@ -302,11 +297,11 @@ export class CoreQuestionProvider { /** * Retrieve an answer from site DB. * - * @param {string} component Component the attempt belongs to. - * @param {number} attemptId Attempt ID. - * @param {string} name Answer's name. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the answer. + * @param component Component the attempt belongs to. + * @param attemptId Attempt ID. + * @param name Answer's name. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the answer. */ getAnswer(component: string, attemptId: number, name: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -317,10 +312,10 @@ export class CoreQuestionProvider { /** * Retrieve an attempt answers from site DB. * - * @param {string} component Component the attempt belongs to. - * @param {number} attemptId Attempt ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the answers. + * @param component Component the attempt belongs to. + * @param attemptId Attempt ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the answers. */ getAttemptAnswers(component: string, attemptId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -331,10 +326,10 @@ export class CoreQuestionProvider { /** * Retrieve an attempt questions from site DB. * - * @param {string} component Component the attempt belongs to. - * @param {number} attemptId Attempt ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the questions. + * @param component Component the attempt belongs to. + * @param attemptId Attempt ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the questions. */ getAttemptQuestions(component: string, attemptId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -345,8 +340,8 @@ export class CoreQuestionProvider { /** * Get all the answers that aren't "extra" (sequencecheck, certainty, ...). * - * @param {any} answers Object with all the answers. - * @return {any} Object with the basic answers. + * @param answers Object with all the answers. + * @return Object with the basic answers. */ getBasicAnswers(answers: any): any { const result = {}; @@ -363,8 +358,8 @@ export class CoreQuestionProvider { /** * Get all the answers that aren't "extra" (sequencecheck, certainty, ...). * - * @param {any[]} answers List of answers. - * @return {any[]} List with the basic answers. + * @param answers List of answers. + * @return List with the basic answers. */ getBasicAnswersFromArray(answers: any[]): any[] { const result = []; @@ -381,11 +376,11 @@ export class CoreQuestionProvider { /** * Retrieve a question from site DB. * - * @param {string} component Component the attempt belongs to. - * @param {number} attemptId Attempt ID. - * @param {string} slot Question slot. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the question. + * @param component Component the attempt belongs to. + * @param attemptId Attempt ID. + * @param slot Question slot. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the question. */ getQuestion(component: string, attemptId: number, slot: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -396,12 +391,12 @@ export class CoreQuestionProvider { /** * Retrieve a question answers from site DB. * - * @param {string} component Component the attempt belongs to. - * @param {number} attemptId Attempt ID. - * @param {string} slot Question slot. - * @param {boolean} [filter] Whether it should ignore "extra" answers like sequencecheck or certainty. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the answers. + * @param component Component the attempt belongs to. + * @param attemptId Attempt ID. + * @param slot Question slot. + * @param filter Whether it should ignore "extra" answers like sequencecheck or certainty. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the answers. */ getQuestionAnswers(component: string, attemptId: number, slot: number, filter?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -421,8 +416,8 @@ export class CoreQuestionProvider { /** * Extract the question slot from a question name. * - * @param {string} name Question name. - * @return {number} Question slot. + * @param name Question name. + * @return Question slot. */ getQuestionSlotFromName(name: string): number { if (name) { @@ -438,8 +433,8 @@ export class CoreQuestionProvider { /** * Get question state based on state name. * - * @param {string} name State name. - * @return {CoreQuestionState} State. + * @param name State name. + * @return State. */ getState(name: string): CoreQuestionState { return this.STATES[name || 'cannotdeterminestatus']; @@ -448,8 +443,8 @@ export class CoreQuestionProvider { /** * Check if an answer is extra data like sequencecheck or certainty. * - * @param {string} name Answer name. - * @return {boolean} Whether it's extra data. + * @param name Answer name. + * @return Whether it's extra data. */ isExtraAnswer(name: string): boolean { // Maybe the name still has the prefix. @@ -461,10 +456,10 @@ export class CoreQuestionProvider { /** * Remove an attempt answers from site DB. * - * @param {string} component Component the attempt belongs to. - * @param {number} attemptId Attempt ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param component Component the attempt belongs to. + * @param attemptId Attempt ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ removeAttemptAnswers(component: string, attemptId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -475,10 +470,10 @@ export class CoreQuestionProvider { /** * Remove an attempt questions from site DB. * - * @param {string} component Component the attempt belongs to. - * @param {number} attemptId Attempt ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param component Component the attempt belongs to. + * @param attemptId Attempt ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ removeAttemptQuestions(component: string, attemptId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -489,11 +484,11 @@ export class CoreQuestionProvider { /** * Remove an answer from site DB. * - * @param {string} component Component the attempt belongs to. - * @param {number} attemptId Attempt ID. - * @param {string} name Answer's name. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param component Component the attempt belongs to. + * @param attemptId Attempt ID. + * @param name Answer's name. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ removeAnswer(component: string, attemptId: number, name: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -505,11 +500,11 @@ export class CoreQuestionProvider { /** * Remove a question from site DB. * - * @param {string} component Component the attempt belongs to. - * @param {number} attemptId Attempt ID. - * @param {string} slot Question slot. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param component Component the attempt belongs to. + * @param attemptId Attempt ID. + * @param slot Question slot. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ removeQuestion(component: string, attemptId: number, slot: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -520,11 +515,11 @@ export class CoreQuestionProvider { /** * Remove a question answers from site DB. * - * @param {string} component Component the attempt belongs to. - * @param {number} attemptId Attempt ID. - * @param {string} slot Question slot. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param component Component the attempt belongs to. + * @param attemptId Attempt ID. + * @param slot Question slot. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ removeQuestionAnswers(component: string, attemptId: number, slot: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -536,8 +531,8 @@ export class CoreQuestionProvider { /** * Remove the prefix from a question answer name. * - * @param {string} name Question name. - * @return {string} Name without prefix. + * @param name Question name. + * @return Name without prefix. */ removeQuestionPrefix(name: string): string { if (name) { @@ -550,14 +545,14 @@ export class CoreQuestionProvider { /** * Save answers in local DB. * - * @param {string} component Component the answers belong to. E.g. 'mmaModQuiz'. - * @param {number} componentId ID of the component the answers belong to. - * @param {number} attemptId Attempt ID. - * @param {number} userId User ID. - * @param {any} answers Object with the answers to save. - * @param {number} [timemodified] 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 component Component the answers belong to. E.g. 'mmaModQuiz'. + * @param componentId ID of the component the answers belong to. + * @param attemptId Attempt ID. + * @param userId User ID. + * @param answers Object with the answers to save. + * @param timemodified 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(component: string, componentId: number, attemptId: number, userId: number, answers: any, timemodified?: number, siteId?: string): Promise { @@ -590,14 +585,14 @@ export class CoreQuestionProvider { /** * Save a question in local DB. * - * @param {string} component Component the question belongs to. E.g. 'mmaModQuiz'. - * @param {number} componentId ID of the component the question belongs to. - * @param {number} attemptId Attempt ID. - * @param {number} userId User ID. - * @param {any} question The question to save. - * @param {string} state Question's state. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param component Component the question belongs to. E.g. 'mmaModQuiz'. + * @param componentId ID of the component the question belongs to. + * @param attemptId Attempt ID. + * @param userId User ID. + * @param question The question to save. + * @param state Question's state. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ saveQuestion(component: string, componentId: number, attemptId: number, userId: number, question: any, state: string, siteId?: string): Promise { diff --git a/src/core/rating/pages/ratings/ratings.ts b/src/core/rating/pages/ratings/ratings.ts index 1114562ca..1b0980ae2 100644 --- a/src/core/rating/pages/ratings/ratings.ts +++ b/src/core/rating/pages/ratings/ratings.ts @@ -61,7 +61,7 @@ export class CoreRatingRatingsPage { /** * Fetch all the data required for the view. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ fetchData(): Promise { return this.ratingProvider.getItemRatings(this.contextLevel, this.instanceId, this.component, this.ratingArea, this.itemId, @@ -75,7 +75,7 @@ export class CoreRatingRatingsPage { /** * Refresh data. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshRatings(refresher: any): void { this.ratingProvider.invalidateRatingItems(this.contextLevel, this.instanceId, this.component, this.ratingArea, this.itemId, diff --git a/src/core/rating/providers/offline.ts b/src/core/rating/providers/offline.ts index 920130118..1de0b00b0 100644 --- a/src/core/rating/providers/offline.ts +++ b/src/core/rating/providers/offline.ts @@ -117,13 +117,13 @@ export class CoreRatingOfflineProvider { /** * Get an offline rating. * - * @param {string} contextLevel Context level: course, module, user, etc. - * @param {numnber} instanceId Context instance id. - * @param {string} component Component. Example: "mod_forum". - * @param {string} ratingArea Rating area. Example: "post". - * @param {number} itemId Item id. Example: forum post id. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the saved rating, rejected if not found. + * @param contextLevel Context level: course, module, user, etc. + * @param instanceId Context instance id. + * @param component Component. Example: "mod_forum". + * @param ratingArea Rating area. Example: "post". + * @param itemId Item id. Example: forum post id. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the saved rating, rejected if not found. */ getRating(contextLevel: string, instanceId: number, component: string, ratingArea: string, itemId: number, siteId?: string): Promise { @@ -143,19 +143,19 @@ export class CoreRatingOfflineProvider { /** * Add an offline rating. * - * @param {string} component Component. Example: "mod_forum". - * @param {string} ratingArea Rating area. Example: "post". - * @param {string} contextLevel Context level: course, module, user, etc. - * @param {numnber} instanceId Context instance id. - * @param {number} itemId Item id. Example: forum post id. - * @param {number} itemSetId Item set id. Example: forum discussion id. - * @param {number} courseId Course id. - * @param {number} scaleId Scale id. - * @param {number} rating Rating value. Use CoreRatingProvider.UNSET_RATING to delete rating. - * @param {number} ratedUserId Rated user id. - * @param {number} aggregateMethod Aggregate method. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the rating is saved. + * @param component Component. Example: "mod_forum". + * @param ratingArea Rating area. Example: "post". + * @param contextLevel Context level: course, module, user, etc. + * @param instanceId Context instance id. + * @param itemId Item id. Example: forum post id. + * @param itemSetId Item set id. Example: forum discussion id. + * @param courseId Course id. + * @param scaleId Scale id. + * @param rating Rating value. Use CoreRatingProvider.UNSET_RATING to delete rating. + * @param ratedUserId Rated user id. + * @param aggregateMethod Aggregate method. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the rating is saved. */ addRating(component: string, ratingArea: string, contextLevel: string, instanceId: number, itemId: number, itemSetId: number, courseId: number, scaleId: number, rating: number, ratedUserId: number, aggregateMethod: number, siteId?: string): @@ -182,12 +182,12 @@ export class CoreRatingOfflineProvider { /** * Delete offline rating. * - * @param {string} component Component. Example: "mod_forum". - * @param {string} ratingArea Rating area. Example: "post". - * @param {string} contextLevel Context level: course, module, user, etc. - * @param {number} itemId Item id. Example: forum post id. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the rating is saved. + * @param component Component. Example: "mod_forum". + * @param ratingArea Rating area. Example: "post". + * @param contextLevel Context level: course, module, user, etc. + * @param itemId Item id. Example: forum post id. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the rating is saved. */ deleteRating(component: string, ratingArea: string, contextLevel: string, instanceId: number, itemId: number, siteId?: string): Promise { @@ -207,13 +207,13 @@ export class CoreRatingOfflineProvider { /** * Get the list of item sets in a component or instance. * - * @param {string} component Component. Example: "mod_forum". - * @param {string} ratingArea Rating Area. Example: "post". - * @param {string} [contextLevel] Context level: course, module, user, etc. - * @param {numnber} [instanceId] Context instance id. - * @param {number} [itemSetId] Item set id. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the list of item set ids. + * @param component Component. Example: "mod_forum". + * @param ratingArea Rating Area. Example: "post". + * @param contextLevel Context level: course, module, user, etc. + * @param instanceId Context instance id. + * @param itemSetId Item set id. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the list of item set ids. */ getItemSets(component: string, ratingArea: string, contextLevel?: string, instanceId?: number, itemSetId?: number, siteId?: string): Promise { @@ -250,13 +250,13 @@ export class CoreRatingOfflineProvider { /** * Get offline ratings of an item set. * - * @param {string} component Component. Example: "mod_forum". - * @param {string} ratingArea Rating Area. Example: "post". - * @param {string} contextLevel Context level: course, module, user, etc. - * @param {number} itemId Item id. Example: forum post id. - * @param {number} itemSetId Item set id. Example: forum discussion id. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the list of ratings. + * @param component Component. Example: "mod_forum". + * @param ratingArea Rating Area. Example: "post". + * @param contextLevel Context level: course, module, user, etc. + * @param itemId Item id. Example: forum post id. + * @param itemSetId Item set id. Example: forum discussion id. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the list of ratings. */ getRatings(component: string, ratingArea: string, contextLevel: string, instanceId: number, itemSetId: number, siteId?: string): Promise { @@ -276,13 +276,13 @@ export class CoreRatingOfflineProvider { /** * Return whether a component, instance or item set has offline ratings. * - * @param {string} component Component. Example: "mod_forum". - * @param {string} ratingArea Rating Area. Example: "post". - * @param {string} [contextLevel] Context level: course, module, user, etc. - * @param {number} [instanceId] Context instance id. - * @param {number} [itemSetId] Item set id. Example: forum discussion id. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with a boolean. + * @param component Component. Example: "mod_forum". + * @param ratingArea Rating Area. Example: "post". + * @param contextLevel Context level: course, module, user, etc. + * @param instanceId Context instance id. + * @param itemSetId Item set id. Example: forum discussion id. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with a boolean. */ hasRatings(component: string, ratingArea: string, contextLevel?: string, instanceId?: number, itemSetId?: number, siteId?: string): Promise { diff --git a/src/core/rating/providers/rating.ts b/src/core/rating/providers/rating.ts index 1b482dd08..bbdd1d454 100644 --- a/src/core/rating/providers/rating.ts +++ b/src/core/rating/providers/rating.ts @@ -104,7 +104,7 @@ export class CoreRatingProvider { /** * Returns whether the web serivce to add ratings is available. * - * @return {boolean} If WS is abalaible. + * @return If WS is abalaible. * @since 3.2 */ isAddRatingWSAvailable(): boolean { @@ -114,19 +114,19 @@ export class CoreRatingProvider { /** * Add a rating to an item. * - * @param {string} component Component. Example: "mod_forum". - * @param {string} ratingArea Rating area. Example: "post". - * @param {string} contextLevel Context level: course, module, user, etc. - * @param {number} instanceId Context instance id. - * @param {number} itemId Item id. Example: forum post id. - * @param {number} itemSetId Item set id. Example: forum discussion id. - * @param {number} courseId Course id. - * @param {number} scaleId Scale id. - * @param {number} rating Rating value. Use CoreRatingProvider.UNSET_RATING to delete rating. - * @param {number} ratedUserId Rated user id. - * @param {number} aggregateMethod Aggregate method. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the aggregated rating or null if stored offline. + * @param component Component. Example: "mod_forum". + * @param ratingArea Rating area. Example: "post". + * @param contextLevel Context level: course, module, user, etc. + * @param instanceId Context instance id. + * @param itemId Item id. Example: forum post id. + * @param itemSetId Item set id. Example: forum discussion id. + * @param courseId Course id. + * @param scaleId Scale id. + * @param rating Rating value. Use CoreRatingProvider.UNSET_RATING to delete rating. + * @param ratedUserId Rated user id. + * @param aggregateMethod Aggregate method. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the aggregated rating or null if stored offline. * @since 3.2 */ addRating(component: string, ratingArea: string, contextLevel: string, instanceId: number, itemId: number, itemSetId: number, @@ -174,17 +174,17 @@ export class CoreRatingProvider { /** * Add a rating to an item. It will fail if offline or cannot connect. * - * @param {string} component Component. Example: "mod_forum". - * @param {string} ratingArea Rating area. Example: "post". - * @param {string} contextLevel Context level: course, module, user, etc. - * @param {number} instanceId Context instance id. - * @param {number} itemId Item id. Example: forum post id. - * @param {number} scaleId Scale id. - * @param {number} rating Rating value. Use CoreRatingProvider.UNSET_RATING to delete rating. - * @param {number} ratedUserId Rated user id. - * @param {number} aggregateMethod Aggregate method. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the aggregated rating. + * @param component Component. Example: "mod_forum". + * @param ratingArea Rating area. Example: "post". + * @param contextLevel Context level: course, module, user, etc. + * @param instanceId Context instance id. + * @param itemId Item id. Example: forum post id. + * @param scaleId Scale id. + * @param rating Rating value. Use CoreRatingProvider.UNSET_RATING to delete rating. + * @param ratedUserId Rated user id. + * @param aggregateMethod Aggregate method. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the aggregated rating. * @since 3.2 */ addRatingOnline(component: string, ratingArea: string, contextLevel: string, instanceId: number, itemId: number, @@ -225,17 +225,17 @@ export class CoreRatingProvider { /** * Get item ratings. * - * @param {string} contextLevel Context level: course, module, user, etc. - * @param {number} instanceId Context instance id. - * @param {string} component Component. Example: "mod_forum". - * @param {string} ratingArea Rating area. Example: "post". - * @param {number} itemId Item id. Example: forum post id. - * @param {number} scaleId Scale id. - * @param {string} [sort="timemodified"] Sort field. - * @param {number} [courseId] Course id. Used for fetching user profiles. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {boolean} [ignoreCache=false] True if it should ignore cached data (it will always fail in offline or server down). - * @return {Promise} Promise resolved with the list of ratings. + * @param contextLevel Context level: course, module, user, etc. + * @param instanceId Context instance id. + * @param component Component. Example: "mod_forum". + * @param ratingArea Rating area. Example: "post". + * @param itemId Item id. Example: forum post id. + * @param scaleId Scale id. + * @param sort Sort field. + * @param courseId Course id. Used for fetching user profiles. + * @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 list of ratings. */ getItemRatings(contextLevel: string, instanceId: number, component: string, ratingArea: string, itemId: number, scaleId: number, sort: string = 'timemodified', courseId?: number, siteId?: string, ignoreCache: boolean = false): @@ -284,15 +284,15 @@ export class CoreRatingProvider { /** * Invalidate item ratings. * - * @param {string} contextLevel Context level: course, module, user, etc. - * @param {number} instanceId Context instance id. - * @param {string} component Component. Example: "mod_forum". - * @param {string} ratingArea Rating area. Example: "post". - * @param {number} itemId Item id. Example: forum post id. - * @param {number} scaleId Scale id. - * @param {string} [sort="timemodified"] Sort field. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param contextLevel Context level: course, module, user, etc. + * @param instanceId Context instance id. + * @param component Component. Example: "mod_forum". + * @param ratingArea Rating area. Example: "post". + * @param itemId Item id. Example: forum post id. + * @param scaleId Scale id. + * @param sort Sort field. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateRatingItems(contextLevel: string, instanceId: number, component: string, ratingArea: string, itemId: number, scaleId: number, sort: string = 'timemodified', siteId?: string): Promise { @@ -306,8 +306,8 @@ export class CoreRatingProvider { /** * Check if rating 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. */ isRatingDisabledInSite(site?: CoreSite): boolean { site = site || this.sitesProvider.getCurrentSite(); @@ -318,8 +318,8 @@ export class CoreRatingProvider { /** * Check if rating 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. */ isRatingDisabled(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -330,8 +330,8 @@ export class CoreRatingProvider { /** * Convenience function to merge two or more rating infos of the same instance. * - * @param {CoreRatingInfo[]} ratingInfos Array of rating infos. - * @return {CoreRatingInfo} Merged rating info or null. + * @param ratingInfos Array of rating infos. + * @return Merged rating info or null. */ mergeRatingInfos(ratingInfos: CoreRatingInfo[]): CoreRatingInfo { let result: CoreRatingInfo = null; @@ -370,12 +370,12 @@ export class CoreRatingProvider { * * This function should be called from the prefetch handler of activities with ratings. * - * @param {string} contextLevel Context level: course, module, user, etc. - * @param {number} instanceId Instance id. - * @param {string} [siteId] Site id. If not defined, current site. - * @param {number} [courseId] Course id. Used for prefetching user profiles. - * @param {CoreRatingInfo} [ratingInfo] Rating info returned by web services. - * @return {Promise} Promise resolved when done. + * @param contextLevel Context level: course, module, user, etc. + * @param instanceId Instance id. + * @param siteId Site id. If not defined, current site. + * @param courseId Course id. Used for prefetching user profiles. + * @param ratingInfo Rating info returned by web services. + * @return Promise resolved when done. */ prefetchRatings(contextLevel: string, instanceId: number, scaleId: number, courseId?: number, ratingInfo?: CoreRatingInfo, siteId?: string): Promise { @@ -400,13 +400,13 @@ export class CoreRatingProvider { /** * Get cache key for rating items WS calls. * - * @param {string} contextLevel Context level: course, module, user, etc. - * @param {string} component Component. Example: "mod_forum". - * @param {string} ratingArea Rating area. Example: "post". - * @param {number} itemId Item id. Example: forum post id. - * @param {number} scaleId Scale id. - * @param {string} sort Sort field. - * @return {string} Cache key. + * @param contextLevel Context level: course, module, user, etc. + * @param component Component. Example: "mod_forum". + * @param ratingArea Rating area. Example: "post". + * @param itemId Item id. Example: forum post id. + * @param scaleId Scale id. + * @param sort Sort field. + * @return Cache key. */ protected getItemRatingsCacheKey(contextLevel: string, instanceId: number, component: string, ratingArea: string, itemId: number, scaleId: number, sort: string): string { diff --git a/src/core/rating/providers/sync.ts b/src/core/rating/providers/sync.ts index 341797fe2..e7f292d36 100644 --- a/src/core/rating/providers/sync.ts +++ b/src/core/rating/providers/sync.ts @@ -54,14 +54,14 @@ export class CoreRatingSyncProvider extends CoreSyncBaseProvider { * * This function should be called from the sync provider of activities with ratings. * - * @param {string} component Component. Example: "mod_forum". - * @param {string} ratingArea Rating Area. Example: "post". - * @param {string} [contextLevel] Context level: course, module, user, etc. - * @param {numnber} [instanceId] Context instance id. - * @param {number} [itemSetId] Item set id. - * @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 if sync fails. + * @param component Component. Example: "mod_forum". + * @param ratingArea Rating Area. Example: "post". + * @param contextLevel Context level: course, module, user, etc. + * @param instanceId Context instance id. + * @param itemSetId Item set id. + * @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 if sync fails. */ syncRatings(component: string, ratingArea: string, contextLevel?: string, instanceId?: number, itemSetId?: number, force?: boolean, siteId?: string): Promise<{itemSet: CoreRatingItemSet, updated: number[], warnings: string[]}[]> { @@ -97,13 +97,13 @@ export class CoreRatingSyncProvider extends CoreSyncBaseProvider { /** * Sync ratings of an item set only if a certain time has passed since the last time. * - * @param {string} component Component. Example: "mod_forum". - * @param {string} ratingArea Rating Area. Example: "post". - * @param {string} contextLevel Context level: course, module, user, etc. - * @param {number} instanceId Context instance id. - * @param {number} itemSetId Item set id. Example: forum discussion id. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when ratings are synced or if it doesn't need to be synced. + * @param component Component. Example: "mod_forum". + * @param ratingArea Rating Area. Example: "post". + * @param contextLevel Context level: course, module, user, etc. + * @param instanceId Context instance id. + * @param itemSetId Item set id. Example: forum discussion id. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when ratings are synced or if it doesn't need to be synced. */ protected syncItemSetIfNeeded(component: string, ratingArea: string, contextLevel: string, instanceId: number, itemSetId: number, siteId?: string): Promise<{updated: number[], warnings: string[]}> { @@ -121,13 +121,13 @@ export class CoreRatingSyncProvider extends CoreSyncBaseProvider { /** * Synchronize all offline ratings of an item set. * - * @param {string} component Component. Example: "mod_forum". - * @param {string} ratingArea Rating Area. Example: "post". - * @param {string} contextLevel Context level: course, module, user, etc. - * @param {number} instanceId Context instance id. - * @param {number} itemSetId Item set id. Example: forum discussion id. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved if sync is successful, rejected otherwise. + * @param component Component. Example: "mod_forum". + * @param ratingArea Rating Area. Example: "post". + * @param contextLevel Context level: course, module, user, etc. + * @param instanceId Context instance id. + * @param itemSetId Item set id. Example: forum discussion id. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved if sync is successful, rejected otherwise. */ protected syncItemSet(component: string, ratingArea: string, contextLevel: string, instanceId: number, itemSetId: number, siteId?: string): Promise<{updated: number[], warnings: string[]}> { @@ -186,12 +186,12 @@ export class CoreRatingSyncProvider extends CoreSyncBaseProvider { /** * Get the sync id of an item set. * - * @param {string} component Component. Example: "mod_forum". - * @param {string} ratingArea Rating Area. Example: "post". - * @param {string} contextLevel Context level: course, module, user, etc. - * @param {number} instanceId Context instance id. - * @param {number} itemSetId Item set id. Example: forum discussion id. - * @return {string} Sync id. + * @param component Component. Example: "mod_forum". + * @param ratingArea Rating Area. Example: "post". + * @param contextLevel Context level: course, module, user, etc. + * @param instanceId Context instance id. + * @param itemSetId Item set id. Example: forum discussion id. + * @return Sync id. */ protected getItemSetSyncId(component: string, ratingArea: string, contextLevel: string, instanceId: number, itemSetId: number): string { diff --git a/src/core/settings/pages/list/list.ts b/src/core/settings/pages/list/list.ts index 8e6c4b944..3c50f1350 100644 --- a/src/core/settings/pages/list/list.ts +++ b/src/core/settings/pages/list/list.ts @@ -54,8 +54,8 @@ export class CoreSettingsListPage { /** * Open a handler. * - * @param {string} page Page to open. - * @param {any} params Params of the page to open. + * @param page Page to open. + * @param params Params of the page to open. */ openHandler(page: string, params?: any): void { this.selectedPage = page; diff --git a/src/core/settings/pages/space-usage/space-usage.ts b/src/core/settings/pages/space-usage/space-usage.ts index afd45fcd2..0a782a9c6 100644 --- a/src/core/settings/pages/space-usage/space-usage.ts +++ b/src/core/settings/pages/space-usage/space-usage.ts @@ -57,7 +57,7 @@ export class CoreSettingsSpaceUsagePage { /** * Convenience function to calculate each site's usage, and the total usage. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected calculateSizeUsage(): Promise { return this.sitesProvider.getSortedSites().then((sites) => { @@ -101,7 +101,7 @@ export class CoreSettingsSpaceUsagePage { /** * Convenience function to calculate space usage. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ protected fetchData(): Promise { const promises = [ @@ -114,7 +114,7 @@ export class CoreSettingsSpaceUsagePage { /** * Refresh the data. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshData(refresher: any): void { this.fetchData().finally(() => { @@ -125,8 +125,8 @@ export class CoreSettingsSpaceUsagePage { /** * Convenience function to update site size, along with total usage. * - * @param {any} site Site object with space usage. - * @param {number} newUsage New space usage of the site in bytes. + * @param site Site object with space usage. + * @param newUsage New space usage of the site in bytes. */ protected updateSiteUsage(site: any, newUsage: number): void { const oldUsage = site.spaceUsage; @@ -137,8 +137,8 @@ export class CoreSettingsSpaceUsagePage { /** * Calculate the number of rows to be deleted on a site. * - * @param {any} site Site object. - * @return {Promise} If there are rows to delete or not. + * @param site Site object. + * @return If there are rows to delete or not. */ protected calcSiteClearRows(site: any): Promise { const clearTables = this.sitesProvider.getSiteTableSchemasToClear(); @@ -159,7 +159,7 @@ export class CoreSettingsSpaceUsagePage { /** * Deletes files of a site and the tables that can be cleared. * - * @param {any} siteData Site object with space usage. + * @param siteData Site object with space usage. */ deleteSiteStorage(siteData: any): void { this.textUtils.formatText(siteData.siteName).then((siteName) => { diff --git a/src/core/settings/pages/synchronization/synchronization.ts b/src/core/settings/pages/synchronization/synchronization.ts index e6c81b5e7..4238282d1 100644 --- a/src/core/settings/pages/synchronization/synchronization.ts +++ b/src/core/settings/pages/synchronization/synchronization.ts @@ -82,7 +82,7 @@ export class CoreSettingsSynchronizationPage implements OnDestroy { /** * Syncrhonizes a site. * - * @param {string} siteId Site ID. + * @param siteId Site ID. */ synchronize(siteId: string): void { this.settingsHelper.synchronizeSite(this.syncOnlyOnWifi, siteId).catch((error) => { @@ -96,8 +96,8 @@ export class CoreSettingsSynchronizationPage implements OnDestroy { /** * Returns true if site is beeing synchronized. * - * @param {string} siteId Site ID. - * @return {boolean} True if site is beeing synchronized, false otherwise. + * @param siteId Site ID. + * @return True if site is beeing synchronized, false otherwise. */ isSynchronizing(siteId: string): boolean { return !!this.settingsHelper.getSiteSyncPromise(siteId); diff --git a/src/core/settings/providers/delegate.ts b/src/core/settings/providers/delegate.ts index a7fca19cd..e3e5fb2b4 100644 --- a/src/core/settings/providers/delegate.ts +++ b/src/core/settings/providers/delegate.ts @@ -25,14 +25,13 @@ import { CoreUtilsProvider } from '@providers/utils/utils'; export interface CoreSettingsHandler extends CoreDelegateHandler { /** * The highest priority is displayed first. - * @type {number} */ priority: number; /** * Returns the data needed to render the handler. * - * @return {CoreSettingsHandlerData} Data. + * @return Data. */ getDisplayData(): CoreSettingsHandlerData; } @@ -43,31 +42,26 @@ export interface CoreSettingsHandler extends CoreDelegateHandler { export interface CoreSettingsHandlerData { /** * Name of the page to load for the handler. - * @type {string} */ page: string; /** * Params list of the page to load for the handler. - * @type {any} */ params?: any; /** * Title to display for the handler. - * @type {string} */ title: string; /** * Name of the icon to display for the handler. - * @type {string} */ icon?: string; // Name of the icon to display in the menu. /** * Class to add to the displayed handler. - * @type {string} */ class?: string; } @@ -97,8 +91,6 @@ export class CoreSettingsDelegate extends CoreDelegate { /** * Get the handlers for the current site. - * - * @return {CoreSettingsHandlerData[]} */ getHandlers(): CoreSettingsHandlerData[] { return this.siteHandlers; diff --git a/src/core/settings/providers/helper.ts b/src/core/settings/providers/helper.ts index 0a2d5c32f..26ffda777 100644 --- a/src/core/settings/providers/helper.ts +++ b/src/core/settings/providers/helper.ts @@ -39,10 +39,10 @@ export class CoreSettingsHelper { /** * Get a certain processor from a list of processors. * - * @param {any[]} processors List of processors. - * @param {string} name Name of the processor to get. - * @param {boolean} [fallback=true] True to return first processor if not found, false to not return any. Defaults to true. - * @return {any} Processor. + * @param processors List of processors. + * @param name Name of the processor to get. + * @param fallback True to return first processor if not found, false to not return any. Defaults to true. + * @return Processor. */ getProcessor(processors: any[], name: string, fallback: boolean = true): any { if (!processors || !processors.length) { @@ -63,9 +63,9 @@ export class CoreSettingsHelper { /** * Return the components and notifications that have a certain processor. * - * @param {string} processor Name of the processor to filter. - * @param {any[]} components Array of components. - * @return {any[]} Filtered components. + * @param processor Name of the processor to filter. + * @param components Array of components. + * @return Filtered components. */ getProcessorComponents(processor: string, components: any[]): any[] { const result = []; @@ -104,8 +104,8 @@ export class CoreSettingsHelper { /** * Get the synchronization promise of a site. * - * @param {string} siteId ID of the site. - * @return {Promise | null} Sync promise or null if site is not being syncrhonized. + * @param siteId ID of the site. + * @return Sync promise or null if site is not being syncrhonized. */ getSiteSyncPromise(siteId: string): Promise { if (this.syncPromises[siteId]) { @@ -118,9 +118,9 @@ export class CoreSettingsHelper { /** * Synchronize a site. * - * @param {boolean} syncOnlyOnWifi True to sync only on wifi, false otherwise. - * @param {string} siteId ID of the site to synchronize. - * @return {Promise} Promise resolved when synchronized, rejected if failure. + * @param syncOnlyOnWifi True to sync only on wifi, false otherwise. + * @param siteId ID of the site to synchronize. + * @return Promise resolved when synchronized, rejected if failure. */ synchronizeSite(syncOnlyOnWifi: boolean, siteId: string): Promise { if (this.syncPromises[siteId]) { diff --git a/src/core/sharedfiles/pages/choose-site/choose-site.ts b/src/core/sharedfiles/pages/choose-site/choose-site.ts index d680913a9..e1622c50c 100644 --- a/src/core/sharedfiles/pages/choose-site/choose-site.ts +++ b/src/core/sharedfiles/pages/choose-site/choose-site.ts @@ -78,7 +78,7 @@ export class CoreSharedFilesChooseSitePage implements OnInit { /** * Store the file in a certain site. * - * @param {string} siteId Site ID. + * @param siteId Site ID. */ storeInSite(siteId: string): void { this.loaded = false; diff --git a/src/core/sharedfiles/pages/list/list.ts b/src/core/sharedfiles/pages/list/list.ts index db1f706b5..72e1db6e9 100644 --- a/src/core/sharedfiles/pages/list/list.ts +++ b/src/core/sharedfiles/pages/list/list.ts @@ -75,7 +75,7 @@ export class CoreSharedFilesListPage implements OnInit, OnDestroy { /** * Load the files. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected loadFiles(): Promise { if (this.path) { @@ -100,7 +100,7 @@ export class CoreSharedFilesListPage implements OnInit, OnDestroy { /** * Refresh the list of files. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshFiles(refresher: any): void { this.loadFiles().finally(() => { @@ -111,7 +111,7 @@ export class CoreSharedFilesListPage implements OnInit, OnDestroy { /** * Called when a file is deleted. Remove the file from the list. * - * @param {number} index Position of the file. + * @param index Position of the file. */ fileDeleted(index: number): void { this.files.splice(index, 1); @@ -120,8 +120,8 @@ export class CoreSharedFilesListPage implements OnInit, OnDestroy { /** * Called when a file is renamed. Update the list. * - * @param {number} index Position of the file. - * @param {any} data Data containing the new FileEntry. + * @param index Position of the file. + * @param data Data containing the new FileEntry. */ fileRenamed(index: number, data: any): void { this.files[index] = data.file; @@ -130,7 +130,7 @@ export class CoreSharedFilesListPage implements OnInit, OnDestroy { /** * Open a subfolder. * - * @param {any} folder The folder to open. + * @param folder The folder to open. */ openFolder(folder: any): void { const path = this.textUtils.concatenatePaths(this.path, folder.name); @@ -154,7 +154,7 @@ export class CoreSharedFilesListPage implements OnInit, OnDestroy { /** * Change site loaded. * - * @param {string} id Site to load. + * @param id Site to load. */ changeSite(id: string): void { this.siteId = id; @@ -166,7 +166,7 @@ export class CoreSharedFilesListPage implements OnInit, OnDestroy { /** * A file was picked. * - * @param {any} file Picked file. + * @param file Picked file. */ filePicked(file: any): void { this.viewCtrl.dismiss(file); diff --git a/src/core/sharedfiles/providers/helper.ts b/src/core/sharedfiles/providers/helper.ts index a2ef83342..449905abf 100644 --- a/src/core/sharedfiles/providers/helper.ts +++ b/src/core/sharedfiles/providers/helper.ts @@ -43,9 +43,9 @@ export class CoreSharedFilesHelperProvider { /** * Ask a user if he wants to replace a file (using originalName) or rename it (using newName). * - * @param {string} originalName Original name. - * @param {string} newName New name. - * @return {Promise} Promise resolved with the name to use when the user chooses. Rejected if user cancels. + * @param originalName Original name. + * @param newName New name. + * @return Promise resolved with the name to use when the user chooses. Rejected if user cancels. */ askRenameReplace(originalName: string, newName: string): Promise { const deferred = this.utils.promiseDefer(), @@ -76,8 +76,8 @@ export class CoreSharedFilesHelperProvider { /** * Go to the choose site view. * - * @param {string} filePath File path to send to the view. - * @param {boolean} [isInbox] Whether the file is in the Inbox folder. + * @param filePath File path to send to the view. + * @param isInbox Whether the file is in the Inbox folder. */ goToChooseSite(filePath: string, isInbox?: boolean): void { const navCtrl = this.appProvider.getRootNavController(); @@ -87,7 +87,7 @@ export class CoreSharedFilesHelperProvider { /** * Whether the user is already choosing a site to store a shared file. * - * @return {boolean} Whether the user is already choosing a site to store a shared file. + * @return Whether the user is already choosing a site to store a shared file. */ protected isChoosingSite(): boolean { const navCtrl = this.appProvider.getRootNavController(); @@ -98,8 +98,8 @@ export class CoreSharedFilesHelperProvider { /** * Open the view to select a shared file. * - * @param {string[]} [mimetypes] List of supported mimetypes. If undefined, all mimetypes supported. - * @return {Promise} Promise resolved when a file is picked, rejected if file picker is closed without selecting a file. + * @param mimetypes List of supported mimetypes. If undefined, all mimetypes supported. + * @return Promise resolved when a file is picked, rejected if file picker is closed without selecting a file. */ pickSharedFile(mimetypes?: string[]): Promise { return new Promise((resolve, reject): void => { @@ -130,9 +130,9 @@ export class CoreSharedFilesHelperProvider { /** * Delete a shared file. * - * @param {any} fileEntry The file entry to delete. - * @param {boolean} [isInbox] Whether the file is in the Inbox folder. - * @return {Promise} Promise resolved when done. + * @param fileEntry The file entry to delete. + * @param isInbox Whether the file is in the Inbox folder. + * @return Promise resolved when done. */ protected removeSharedFile(fileEntry: any, isInbox?: boolean): Promise { if (isInbox) { @@ -147,8 +147,8 @@ export class CoreSharedFilesHelperProvider { * If more than one site is found, the user will have to choose the site where to store it in. * If more than one file is found, treat only the first one. * - * @param {string} [path] Path to a file received when launching the app. - * @return {Promise} Promise resolved when done. + * @param path Path to a file received when launching the app. + * @return Promise resolved when done. */ searchIOSNewSharedFiles(path?: string): Promise { return this.initDelegate.ready().then(() => { @@ -190,10 +190,10 @@ export class CoreSharedFilesHelperProvider { /** * Store a shared file in a site's shared files folder. * - * @param {any} fileEntry Shared file entry. - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {boolean} [isInbox] Whether the file is in the Inbox folder. - * @return {Promise} Promise resolved when done. + * @param fileEntry Shared file entry. + * @param siteId Site ID. If not defined, current site. + * @param isInbox Whether the file is in the Inbox folder. + * @return Promise resolved when done. */ storeSharedFileInSite(fileEntry: any, siteId?: string, isInbox?: boolean): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); diff --git a/src/core/sharedfiles/providers/sharedfiles.ts b/src/core/sharedfiles/providers/sharedfiles.ts index 1938c1ec4..0c31bf8a1 100644 --- a/src/core/sharedfiles/providers/sharedfiles.ts +++ b/src/core/sharedfiles/providers/sharedfiles.ts @@ -59,7 +59,7 @@ export class CoreSharedFilesProvider { * Checks if there is a new file received in iOS. If more than one file is found, treat only the first one. * The file returned is marked as "treated" and will be deleted in the next execution. * - * @return {Promise} Promise resolved with a new file to be treated. If no new files found, promise is rejected. + * @return Promise resolved with a new file to be treated. If no new files found, promise is rejected. */ checkIOSNewFiles(): Promise { this.logger.debug('Search for new files on iOS'); @@ -110,8 +110,8 @@ export class CoreSharedFilesProvider { /** * Deletes a file in the Inbox folder (shared with the app). * - * @param {any} entry FileEntry. - * @return {Promise} Promise resolved when done, rejected otherwise. + * @param entry FileEntry. + * @return Promise resolved when done, rejected otherwise. */ deleteInboxFile(entry: any): Promise { this.logger.debug('Delete inbox file: ' + entry.name); @@ -132,8 +132,8 @@ export class CoreSharedFilesProvider { /** * Get the ID of a file for managing "treated" files. * - * @param {any} entry FileEntry. - * @return {string} File ID. + * @param entry FileEntry. + * @return File ID. */ protected getFileId(entry: any): string { return Md5.hashAsciiStr(entry.name); @@ -142,10 +142,10 @@ export class CoreSharedFilesProvider { /** * Get the shared files stored in a site. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @param {string} [path] Path to search inside the site shared folder. - * @param {string[]} [mimetypes] List of supported mimetypes. If undefined, all mimetypes supported. - * @return {Promise} Promise resolved with the files. + * @param siteId Site ID. If not defined, current site. + * @param path Path to search inside the site shared folder. + * @param mimetypes List of supported mimetypes. If undefined, all mimetypes supported. + * @return Promise resolved with the files. */ getSiteSharedFiles(siteId?: string, path?: string, mimetypes?: string[]): Promise { let pathToGet = this.getSiteSharedFilesDirPath(siteId); @@ -174,8 +174,8 @@ export class CoreSharedFilesProvider { /** * Get the path to a site's shared files folder. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {string} Path. + * @param siteId Site ID. If not defined, current site. + * @return Path. */ getSiteSharedFilesDirPath(siteId?: string): string { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -186,8 +186,8 @@ export class CoreSharedFilesProvider { /** * Check if a file has been treated already. * - * @param {string} fileId File ID. - * @return {Promise} Resolved if treated, rejected otherwise. + * @param fileId File ID. + * @return Resolved if treated, rejected otherwise. */ protected isFileTreated(fileId: string): Promise { return this.appDB.getRecord(this.SHARED_FILES_TABLE, { id: fileId }); @@ -196,8 +196,8 @@ export class CoreSharedFilesProvider { /** * Mark a file as treated. * - * @param {string} fileId File ID. - * @return {Promise} Promise resolved when marked. + * @param fileId File ID. + * @return Promise resolved when marked. */ protected markAsTreated(fileId: string): Promise { // Check if it's already marked. @@ -210,10 +210,10 @@ export class CoreSharedFilesProvider { /** * Store a file in a site's shared folder. * - * @param {any} entry File entry. - * @param {string} [newName] Name of the new file. If not defined, use original file's name. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise}Promise resolved when done. + * @param entry File entry. + * @param newName Name of the new file. If not defined, use original file's name. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. */ storeFileInSite(entry: any, newName?: string, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -240,8 +240,8 @@ export class CoreSharedFilesProvider { /** * Unmark a file as treated. * - * @param {string} fileId File ID. - * @return {Promise} Resolved when unmarked. + * @param fileId File ID. + * @return Resolved when unmarked. */ protected unmarkAsTreated(fileId: string): Promise { return this.appDB.deleteRecords(this.SHARED_FILES_TABLE, { id: fileId }); diff --git a/src/core/sharedfiles/providers/upload-handler.ts b/src/core/sharedfiles/providers/upload-handler.ts index da602b2b3..6eab53615 100644 --- a/src/core/sharedfiles/providers/upload-handler.ts +++ b/src/core/sharedfiles/providers/upload-handler.ts @@ -29,7 +29,7 @@ export class CoreSharedFilesUploadHandler implements CoreFileUploaderHandler { /** * 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 this.platform.is('ios'); @@ -38,8 +38,8 @@ export class CoreSharedFilesUploadHandler implements CoreFileUploaderHandler { /** * Given a list of mimetypes, return the ones that are supported by the handler. * - * @param {string[]} [mimetypes] List of mimetypes. - * @return {string[]} Supported mimetypes. + * @param mimetypes List of mimetypes. + * @return Supported mimetypes. */ getSupportedMimetypes(mimetypes: string[]): string[] { return mimetypes; @@ -48,7 +48,7 @@ export class CoreSharedFilesUploadHandler implements CoreFileUploaderHandler { /** * Get the data to display the handler. * - * @return {CoreFileUploaderHandlerData} Data. + * @return Data. */ getData(): CoreFileUploaderHandlerData { return { diff --git a/src/core/sitehome/components/index/index.ts b/src/core/sitehome/components/index/index.ts index 8c8a511c1..efc92458d 100644 --- a/src/core/sitehome/components/index/index.ts +++ b/src/core/sitehome/components/index/index.ts @@ -58,7 +58,7 @@ export class CoreSiteHomeIndexComponent implements OnInit { /** * Refresh the data. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ doRefresh(refresher: any): void { const promises = []; @@ -93,7 +93,7 @@ export class CoreSiteHomeIndexComponent implements OnInit { /** * Convenience function to fetch the data. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected loadContent(): Promise { this.hasContent = false; diff --git a/src/core/sitehome/providers/index-link-handler.ts b/src/core/sitehome/providers/index-link-handler.ts index f0e15ad31..2f57e218b 100644 --- a/src/core/sitehome/providers/index-link-handler.ts +++ b/src/core/sitehome/providers/index-link-handler.ts @@ -36,11 +36,11 @@ export class CoreSiteHomeIndexLinkHandler 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 { @@ -55,11 +55,11 @@ export class CoreSiteHomeIndexLinkHandler 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); diff --git a/src/core/sitehome/providers/sitehome.ts b/src/core/sitehome/providers/sitehome.ts index 2d5fa3aaf..207916fe9 100644 --- a/src/core/sitehome/providers/sitehome.ts +++ b/src/core/sitehome/providers/sitehome.ts @@ -34,8 +34,8 @@ export class CoreSiteHomeProvider { /** * Get the news forum for the Site Home. * - * @param {number} siteHomeId Site Home ID. - * @return {Promise} Promise resolved with the forum if found, rejected otherwise. + * @param siteHomeId Site Home ID. + * @return Promise resolved with the forum if found, rejected otherwise. */ getNewsForum(siteHomeId: number): Promise { return this.forumProvider.getCourseForums(siteHomeId).then((forums) => { @@ -52,8 +52,8 @@ export class CoreSiteHomeProvider { /** * Invalidate the WS call to get the news forum for the Site Home. * - * @param {number} siteHomeId Site Home ID. - * @return {Promise} Promise resolved when invalidated. + * @param siteHomeId Site Home ID. + * @return Promise resolved when invalidated. */ invalidateNewsForum(siteHomeId: number): Promise { return this.forumProvider.invalidateForumData(siteHomeId); @@ -62,8 +62,8 @@ export class CoreSiteHomeProvider { /** * Returns whether or not the frontpage is available for the current site. * - * @param {string} [siteId] The site ID. If not defined, current site. - * @return {Promise} Promise resolved with boolean: whether it's available. + * @param siteId The site ID. If not defined, current site. + * @return Promise resolved with boolean: whether it's available. */ isAvailable(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -112,8 +112,8 @@ export class CoreSiteHomeProvider { /** * Check if Site Home 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) => { @@ -124,8 +124,8 @@ export class CoreSiteHomeProvider { /** * Check if Site Home 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(); diff --git a/src/core/siteplugins/classes/call-ws-click-directive.ts b/src/core/siteplugins/classes/call-ws-click-directive.ts index c6c598b53..eb88363fe 100644 --- a/src/core/siteplugins/classes/call-ws-click-directive.ts +++ b/src/core/siteplugins/classes/call-ws-click-directive.ts @@ -63,7 +63,7 @@ export class CoreSitePluginsCallWSOnClickBaseDirective extends CoreSitePluginsCa /** * Call a WS. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected callWS(): Promise { const modal = this.domUtils.showModalLoading(); diff --git a/src/core/siteplugins/classes/call-ws-directive.ts b/src/core/siteplugins/classes/call-ws-directive.ts index 08dac6c7f..fb4f7d1f8 100644 --- a/src/core/siteplugins/classes/call-ws-directive.ts +++ b/src/core/siteplugins/classes/call-ws-directive.ts @@ -57,7 +57,7 @@ export class CoreSitePluginsCallWSBaseDirective implements OnInit, OnDestroy { /** * Call a WS. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected callWS(): Promise { const params = this.getParamsForWS(); @@ -78,7 +78,7 @@ export class CoreSitePluginsCallWSBaseDirective implements OnInit, OnDestroy { /** * Get the params for the WS call. * - * @return {any} Params. + * @return Params. */ protected getParamsForWS(): any { let params = this.params || {}; @@ -97,7 +97,7 @@ export class CoreSitePluginsCallWSBaseDirective implements OnInit, OnDestroy { /** * Function called when the WS call is successful. * - * @param {any} result Result of the WS call. + * @param result Result of the WS call. */ protected wsCallSuccess(result: any): void { // Function to be overridden. @@ -106,7 +106,7 @@ export class CoreSitePluginsCallWSBaseDirective implements OnInit, OnDestroy { /** * Invalidate the WS call. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ invalidate(): Promise { const params = this.getParamsForWS(); diff --git a/src/core/siteplugins/classes/compile-init-component.ts b/src/core/siteplugins/classes/compile-init-component.ts index 96bf80965..c71fe3b6f 100644 --- a/src/core/siteplugins/classes/compile-init-component.ts +++ b/src/core/siteplugins/classes/compile-init-component.ts @@ -29,7 +29,7 @@ export class CoreSitePluginsCompileInitComponent { /** * Function called when the component is created. * - * @param {any} instance The component instance. + * @param instance The component instance. */ componentCreated(instance: any): void { // Check if the JS defined an init function. @@ -42,7 +42,7 @@ export class CoreSitePluginsCompileInitComponent { /** * Get the handler data. * - * @param {string} name The name of the handler. + * @param name The name of the handler. */ getHandlerData(name: string): void { // Retrieve the handler data. diff --git a/src/core/siteplugins/classes/handlers/assign-feedback-handler.ts b/src/core/siteplugins/classes/handlers/assign-feedback-handler.ts index 0948c0c8c..527c6bb3e 100644 --- a/src/core/siteplugins/classes/handlers/assign-feedback-handler.ts +++ b/src/core/siteplugins/classes/handlers/assign-feedback-handler.ts @@ -30,10 +30,10 @@ export class CoreSitePluginsAssignFeedbackHandler extends AddonModAssignBaseFeed * Return the Component to use to display the plugin data, either in read or in edit mode. * 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} plugin The plugin object. - * @param {boolean} [edit] Whether the user is editing. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param plugin The plugin object. + * @param edit Whether the user is editing. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector, plugin: any, edit?: boolean): any | Promise { return CoreSitePluginsAssignFeedbackComponent; @@ -42,8 +42,8 @@ export class CoreSitePluginsAssignFeedbackHandler extends AddonModAssignBaseFeed /** * Get a readable name to use for the plugin. * - * @param {any} plugin The plugin object. - * @return {string} The plugin name. + * @param plugin The plugin object. + * @return The plugin name. */ getPluginName(plugin: any): string { // Check if there's a translated string for the plugin. diff --git a/src/core/siteplugins/classes/handlers/assign-submission-handler.ts b/src/core/siteplugins/classes/handlers/assign-submission-handler.ts index 9658a370e..85de62cb0 100644 --- a/src/core/siteplugins/classes/handlers/assign-submission-handler.ts +++ b/src/core/siteplugins/classes/handlers/assign-submission-handler.ts @@ -30,10 +30,10 @@ export class CoreSitePluginsAssignSubmissionHandler extends AddonModAssignBaseSu * Return the Component to use to display the plugin data, either in read or in edit mode. * 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} plugin The plugin object. - * @param {boolean} [edit] Whether the user is editing. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param plugin The plugin object. + * @param edit Whether the user is editing. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector, plugin: any, edit?: boolean): any | Promise { return CoreSitePluginsAssignSubmissionComponent; @@ -42,8 +42,8 @@ export class CoreSitePluginsAssignSubmissionHandler extends AddonModAssignBaseSu /** * Get a readable name to use for the plugin. * - * @param {any} plugin The plugin object. - * @return {string} The plugin name. + * @param plugin The plugin object. + * @return The plugin name. */ getPluginName(plugin: any): string { // Check if there's a translated string for the plugin. @@ -64,7 +64,7 @@ export class CoreSitePluginsAssignSubmissionHandler extends AddonModAssignBaseSu /** * Whether or not the handler is enabled for edit on a site level. * - * @return {boolean|Promise} Whether or not the handler is enabled for edit on a site level. + * @return Whether or not the handler is enabled for edit on a site level. */ isEnabledForEdit(): boolean | Promise { return true; diff --git a/src/core/siteplugins/classes/handlers/base-handler.ts b/src/core/siteplugins/classes/handlers/base-handler.ts index 7226a0202..dc68d6b43 100644 --- a/src/core/siteplugins/classes/handlers/base-handler.ts +++ b/src/core/siteplugins/classes/handlers/base-handler.ts @@ -24,7 +24,7 @@ export class CoreSitePluginsBaseHandler implements CoreDelegateHandler { /** * 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; diff --git a/src/core/siteplugins/classes/handlers/block-handler.ts b/src/core/siteplugins/classes/handlers/block-handler.ts index b4734d36e..25602a22e 100644 --- a/src/core/siteplugins/classes/handlers/block-handler.ts +++ b/src/core/siteplugins/classes/handlers/block-handler.ts @@ -33,11 +33,11 @@ export class CoreSitePluginsBlockHandler extends CoreSitePluginsBaseHandler impl * Gets display data for this block. The class and title can be provided either by data from * the handler schema (mobile.php) or using default values. * - * @param {Injector} injector Injector - * @param {any} block Block data - * @param {string} contextLevel Context level (not used) - * @param {number} instanceId Instance id (not used) - * @return {CoreBlockHandlerData|Promise} Data or promise resolved with the data + * @param injector Injector + * @param block Block data + * @param contextLevel Context level (not used) + * @param instanceId Instance id (not used) + * @return Data or promise resolved with the data */ getDisplayData(injector: Injector, block: any, contextLevel: string, instanceId: number): CoreBlockHandlerData | Promise { diff --git a/src/core/siteplugins/classes/handlers/course-format-handler.ts b/src/core/siteplugins/classes/handlers/course-format-handler.ts index b8453ca2a..83a1d5149 100644 --- a/src/core/siteplugins/classes/handlers/course-format-handler.ts +++ b/src/core/siteplugins/classes/handlers/course-format-handler.ts @@ -29,8 +29,8 @@ export class CoreSitePluginsCourseFormatHandler extends CoreSitePluginsBaseHandl /** * 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 typeof this.handlerSchema.canviewallsections != 'undefined' ? this.handlerSchema.canviewallsections : true; @@ -39,8 +39,8 @@ export class CoreSitePluginsCourseFormatHandler extends CoreSitePluginsBaseHandl /** * 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 { return typeof this.handlerSchema.displayenabledownload != 'undefined' ? this.handlerSchema.displayenabledownload : true; @@ -49,8 +49,8 @@ export class CoreSitePluginsCourseFormatHandler extends CoreSitePluginsBaseHandl /** * 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 typeof this.handlerSchema.displaysectionselector != 'undefined' ? this.handlerSchema.displaysectionselector : true; @@ -62,9 +62,9 @@ export class CoreSitePluginsCourseFormatHandler extends CoreSitePluginsBaseHandl * 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 { if (this.handlerSchema.method) { diff --git a/src/core/siteplugins/classes/handlers/course-option-handler.ts b/src/core/siteplugins/classes/handlers/course-option-handler.ts index e3fde2bd2..4bc1f7ce2 100644 --- a/src/core/siteplugins/classes/handlers/course-option-handler.ts +++ b/src/core/siteplugins/classes/handlers/course-option-handler.ts @@ -42,11 +42,11 @@ export class CoreSitePluginsCourseOptionHandler extends CoreSitePluginsBaseHandl /** * 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 { // Wait for "init" result to be updated. @@ -61,9 +61,9 @@ export class CoreSitePluginsCourseOptionHandler extends CoreSitePluginsBaseHandl /** * Returns the data needed to render the handler (if it isn't a menu 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 { @@ -79,9 +79,9 @@ export class CoreSitePluginsCourseOptionHandler extends CoreSitePluginsBaseHandl /** * Returns the data needed to render the handler (if it's a menu handler). * - * @param {Injector} injector Injector. - * @param {any} 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 { @@ -106,8 +106,8 @@ export class CoreSitePluginsCourseOptionHandler extends CoreSitePluginsBaseHandl /** * Called when a course is downloaded. It should prefetch all the data to be able to see the plugin 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 { const args = { @@ -121,7 +121,7 @@ export class CoreSitePluginsCourseOptionHandler extends CoreSitePluginsBaseHandl /** * Set init result. * - * @param {any} result Result to set. + * @param result Result to set. */ setInitResult(result: any): void { this.initResult = result; diff --git a/src/core/siteplugins/classes/handlers/main-menu-handler.ts b/src/core/siteplugins/classes/handlers/main-menu-handler.ts index db47e3598..09be56f52 100644 --- a/src/core/siteplugins/classes/handlers/main-menu-handler.ts +++ b/src/core/siteplugins/classes/handlers/main-menu-handler.ts @@ -31,7 +31,7 @@ export class CoreSitePluginsMainMenuHandler extends CoreSitePluginsBaseHandler i /** * Returns the data needed to render the handler. * - * @return {CoreMainMenuHandlerData} Data. + * @return Data. */ getDisplayData(): CoreMainMenuHandlerData { return { diff --git a/src/core/siteplugins/classes/handlers/message-output-handler.ts b/src/core/siteplugins/classes/handlers/message-output-handler.ts index 264008d64..51e636e78 100644 --- a/src/core/siteplugins/classes/handlers/message-output-handler.ts +++ b/src/core/siteplugins/classes/handlers/message-output-handler.ts @@ -28,7 +28,7 @@ export class CoreSitePluginsMessageOutputHandler extends CoreSitePluginsBaseHand /** * Returns the data needed to render the handler. * - * @return {AddonMessageOutputHandlerData} Data. + * @return Data. */ getDisplayData(): AddonMessageOutputHandlerData { return { diff --git a/src/core/siteplugins/classes/handlers/module-handler.ts b/src/core/siteplugins/classes/handlers/module-handler.ts index 41cffe6c6..bb454ea11 100644 --- a/src/core/siteplugins/classes/handlers/module-handler.ts +++ b/src/core/siteplugins/classes/handlers/module-handler.ts @@ -40,10 +40,10 @@ export class CoreSitePluginsModuleHandler extends CoreSitePluginsBaseHandler imp /** * 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 hasOffline = !!(this.handlerSchema.offlinefunctions && Object.keys(this.handlerSchema.offlinefunctions).length), @@ -70,7 +70,7 @@ export class CoreSitePluginsModuleHandler extends CoreSitePluginsBaseHandler imp /** * Get the icon src for the module. * - * @return {string} The icon src. + * @return The icon src. */ getIconSrc(): string { return this.handlerSchema.displaydata.icon; @@ -81,10 +81,10 @@ export class CoreSitePluginsModuleHandler extends CoreSitePluginsBaseHandler imp * 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 { return CoreSitePluginsModuleIndexComponent; diff --git a/src/core/siteplugins/classes/handlers/module-prefetch-handler.ts b/src/core/siteplugins/classes/handlers/module-prefetch-handler.ts index 434838b75..91788299b 100644 --- a/src/core/siteplugins/classes/handlers/module-prefetch-handler.ts +++ b/src/core/siteplugins/classes/handlers/module-prefetch-handler.ts @@ -54,10 +54,10 @@ export class CoreSitePluginsModulePrefetchHandler extends CoreCourseActivityPref /** * 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.prefetchPackage(module, courseId, false, this.downloadPrefetchPlugin.bind(this), undefined, false, dirPath); @@ -66,13 +66,13 @@ export class CoreSitePluginsModulePrefetchHandler extends CoreCourseActivityPref /** * Download or prefetch the plugin, downloading the files and calling the needed WS. * - * @param {any} module The module object returned by WS. - * @param {number} courseId Course ID. - * @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. - * @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. - * @return {Promise} Promise resolved when done. + * @param module The module object returned by WS. + * @param courseId Course ID. + * @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. + * @param prefetch True to prefetch, false to download right away. + * @param dirPath Path of the directory where to store all the content files. + * @return Promise resolved when done. */ protected downloadPrefetchPlugin(module: any, courseId: number, single?: boolean, siteId?: string, prefetch?: boolean, dirPath?: string): Promise { @@ -99,11 +99,11 @@ export class CoreSitePluginsModulePrefetchHandler extends CoreCourseActivityPref /** * Download or prefetch the plugin files. * - * @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. - * @return {Promise} Promise resolved when done. + * @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. + * @return Promise resolved when done. */ protected downloadOrPrefetchFiles(siteId: string, module: any, courseId: number, prefetch?: boolean, dirPath?: string) : Promise { @@ -137,11 +137,11 @@ export class CoreSitePluginsModulePrefetchHandler extends CoreCourseActivityPref /** * 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 }> { // In most cases, to calculate the size we'll have to do all the WS calls. Just return unknown size. @@ -151,9 +151,9 @@ export class CoreSitePluginsModulePrefetchHandler extends CoreCourseActivityPref /** * 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 { const promises = [], @@ -186,7 +186,7 @@ export class CoreSitePluginsModulePrefetchHandler extends CoreCourseActivityPref /** * 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; @@ -195,10 +195,10 @@ export class CoreSitePluginsModulePrefetchHandler extends CoreCourseActivityPref /** * 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 { if (this.isResource) { @@ -211,11 +211,11 @@ export class CoreSitePluginsModulePrefetchHandler extends CoreCourseActivityPref /** * 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, false, this.downloadPrefetchPlugin.bind(this), undefined, true, dirPath); diff --git a/src/core/siteplugins/classes/handlers/question-behaviour-handler.ts b/src/core/siteplugins/classes/handlers/question-behaviour-handler.ts index 1b7a8372f..62ad964b9 100644 --- a/src/core/siteplugins/classes/handlers/question-behaviour-handler.ts +++ b/src/core/siteplugins/classes/handlers/question-behaviour-handler.ts @@ -31,10 +31,10 @@ export class CoreSitePluginsQuestionBehaviourHandler extends CoreQuestionBehavio * 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.hasTemplate) { diff --git a/src/core/siteplugins/classes/handlers/question-handler.ts b/src/core/siteplugins/classes/handlers/question-handler.ts index b0be9ed6a..51ce3885c 100644 --- a/src/core/siteplugins/classes/handlers/question-handler.ts +++ b/src/core/siteplugins/classes/handlers/question-handler.ts @@ -29,9 +29,9 @@ export class CoreSitePluginsQuestionHandler extends CoreQuestionBaseHandler { * 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): any | Promise { return CoreSitePluginsQuestionComponent; diff --git a/src/core/siteplugins/classes/handlers/quiz-access-rule-handler.ts b/src/core/siteplugins/classes/handlers/quiz-access-rule-handler.ts index 5ddf618f3..c72ba32ca 100644 --- a/src/core/siteplugins/classes/handlers/quiz-access-rule-handler.ts +++ b/src/core/siteplugins/classes/handlers/quiz-access-rule-handler.ts @@ -25,11 +25,11 @@ export class CoreSitePluginsQuizAccessRuleHandler { /** * 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 { return this.hasTemplate; @@ -38,12 +38,12 @@ export class CoreSitePluginsQuizAccessRuleHandler { /** * 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 { // Nothing to do. @@ -54,8 +54,8 @@ export class CoreSitePluginsQuizAccessRuleHandler { * 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 { if (this.hasTemplate) { @@ -66,12 +66,12 @@ export class CoreSitePluginsQuizAccessRuleHandler { /** * 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 { @@ -81,12 +81,12 @@ export class CoreSitePluginsQuizAccessRuleHandler { /** * 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 { @@ -96,10 +96,10 @@ export class CoreSitePluginsQuizAccessRuleHandler { /** * 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 { return false; diff --git a/src/core/siteplugins/classes/handlers/settings-handler.ts b/src/core/siteplugins/classes/handlers/settings-handler.ts index a90764c8b..ee788d317 100644 --- a/src/core/siteplugins/classes/handlers/settings-handler.ts +++ b/src/core/siteplugins/classes/handlers/settings-handler.ts @@ -31,7 +31,7 @@ export class CoreSitePluginsSettingsHandler extends CoreSitePluginsBaseHandler i /** * Returns the data needed to render the handler. * - * @return {CoreSettingsHandlerData} Data. + * @return Data. */ getDisplayData(): CoreSettingsHandlerData { return { diff --git a/src/core/siteplugins/classes/handlers/user-handler.ts b/src/core/siteplugins/classes/handlers/user-handler.ts index 5bd01b063..bb5b6bbf7 100644 --- a/src/core/siteplugins/classes/handlers/user-handler.ts +++ b/src/core/siteplugins/classes/handlers/user-handler.ts @@ -24,7 +24,6 @@ import { CoreUtilsProvider, PromiseDefer } from '@providers/utils/utils'; export class CoreSitePluginsUserProfileHandler extends CoreSitePluginsBaseHandler implements CoreUserProfileHandler { /** * The highest priority is displayed first. - * @type {number} */ priority: number; @@ -34,7 +33,6 @@ export class CoreSitePluginsUserProfileHandler extends CoreSitePluginsBaseHandle * - 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; @@ -54,11 +52,11 @@ export class CoreSitePluginsUserProfileHandler extends CoreSitePluginsBaseHandle /** * 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 { // First check if it's enabled for the user. @@ -75,9 +73,9 @@ export class CoreSitePluginsUserProfileHandler extends CoreSitePluginsBaseHandle /** * 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 { return { @@ -105,7 +103,7 @@ export class CoreSitePluginsUserProfileHandler extends CoreSitePluginsBaseHandle /** * Set init result. * - * @param {any} result Result to set. + * @param result Result to set. */ setInitResult(result: any): void { this.initResult = result; diff --git a/src/core/siteplugins/classes/handlers/user-profile-field-handler.ts b/src/core/siteplugins/classes/handlers/user-profile-field-handler.ts index bd7c1250a..0dfebb1c0 100644 --- a/src/core/siteplugins/classes/handlers/user-profile-field-handler.ts +++ b/src/core/siteplugins/classes/handlers/user-profile-field-handler.ts @@ -30,8 +30,8 @@ export class CoreSitePluginsUserProfileFieldHandler extends CoreSitePluginsBaseH * 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 CoreSitePluginsUserProfileFieldComponent; @@ -39,11 +39,11 @@ export class CoreSitePluginsUserProfileFieldHandler extends CoreSitePluginsBaseH /** * 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 { diff --git a/src/core/siteplugins/classes/handlers/workshop-assessment-strategy-handler.ts b/src/core/siteplugins/classes/handlers/workshop-assessment-strategy-handler.ts index c85da31db..e04c5b1b4 100644 --- a/src/core/siteplugins/classes/handlers/workshop-assessment-strategy-handler.ts +++ b/src/core/siteplugins/classes/handlers/workshop-assessment-strategy-handler.ts @@ -29,10 +29,10 @@ export class CoreSitePluginsWorkshopAssessmentStrategyHandler implements AddonWo * Return the Component to use to display the plugin data, either in read or in edit mode. * 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} plugin The plugin object. - * @param {boolean} [edit] Whether the user is editing. - * @return {any|Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param injector Injector. + * @param plugin The plugin object. + * @param edit Whether the user is editing. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(injector: Injector): any | Promise { return CoreSitePluginsWorkshopAssessmentStrategyComponent; @@ -41,9 +41,9 @@ export class CoreSitePluginsWorkshopAssessmentStrategyHandler implements AddonWo /** * 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 { return Promise.resolve([]); @@ -52,9 +52,9 @@ export class CoreSitePluginsWorkshopAssessmentStrategyHandler implements AddonWo /** * 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 { return false; @@ -62,7 +62,7 @@ export class CoreSitePluginsWorkshopAssessmentStrategyHandler implements AddonWo /** * 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; @@ -71,9 +71,9 @@ export class CoreSitePluginsWorkshopAssessmentStrategyHandler implements AddonWo /** * 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 { return Promise.resolve({}); diff --git a/src/core/siteplugins/components/assign-feedback/assign-feedback.ts b/src/core/siteplugins/components/assign-feedback/assign-feedback.ts index 4fc0fe2eb..e7e236261 100644 --- a/src/core/siteplugins/components/assign-feedback/assign-feedback.ts +++ b/src/core/siteplugins/components/assign-feedback/assign-feedback.ts @@ -60,7 +60,7 @@ export class CoreSitePluginsAssignFeedbackComponent extends CoreSitePluginsCompi /** * Invalidate the data. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ invalidate(): Promise { return Promise.resolve(); diff --git a/src/core/siteplugins/components/assign-submission/assign-submission.ts b/src/core/siteplugins/components/assign-submission/assign-submission.ts index f034caf41..572d76158 100644 --- a/src/core/siteplugins/components/assign-submission/assign-submission.ts +++ b/src/core/siteplugins/components/assign-submission/assign-submission.ts @@ -58,7 +58,7 @@ export class CoreSitePluginsAssignSubmissionComponent extends CoreSitePluginsCom /** * Invalidate the data. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ invalidate(): Promise { return Promise.resolve(); diff --git a/src/core/siteplugins/components/course-format/course-format.ts b/src/core/siteplugins/components/course-format/course-format.ts index 3f4407c6e..76c21493b 100644 --- a/src/core/siteplugins/components/course-format/course-format.ts +++ b/src/core/siteplugins/components/course-format/course-format.ts @@ -88,10 +88,10 @@ export class CoreSitePluginsCourseFormatComponent 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 { return Promise.resolve(this.content.refreshContent(afterCompletionChange)); diff --git a/src/core/siteplugins/components/course-option/course-option.ts b/src/core/siteplugins/components/course-option/course-option.ts index a92fb71a4..54d534c45 100644 --- a/src/core/siteplugins/components/course-option/course-option.ts +++ b/src/core/siteplugins/components/course-option/course-option.ts @@ -56,7 +56,7 @@ export class CoreSitePluginsCourseOptionComponent implements OnInit { /** * Refresh the data. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshData(refresher: any): void { this.content.refreshContent(false).finally(() => { diff --git a/src/core/siteplugins/components/module-index/module-index.ts b/src/core/siteplugins/components/module-index/module-index.ts index 79be3063c..c5947c0ca 100644 --- a/src/core/siteplugins/components/module-index/module-index.ts +++ b/src/core/siteplugins/components/module-index/module-index.ts @@ -102,9 +102,9 @@ export class CoreSitePluginsModuleIndexComponent implements OnInit, OnDestroy, C /** * 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 { if (this.content) { @@ -173,9 +173,9 @@ export class CoreSitePluginsModuleIndexComponent implements OnInit, OnDestroy, C /** * 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. - * @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 { return this.content.callComponentFunction(name, params); diff --git a/src/core/siteplugins/components/plugin-content/plugin-content.ts b/src/core/siteplugins/components/plugin-content/plugin-content.ts index 4b6356844..ede995524 100644 --- a/src/core/siteplugins/components/plugin-content/plugin-content.ts +++ b/src/core/siteplugins/components/plugin-content/plugin-content.ts @@ -83,8 +83,8 @@ export class CoreSitePluginsPluginContentComponent implements OnInit, DoCheck { /** * Fetches the content to render. * - * @param {boolean} [refresh] Whether the user is refreshing. - * @return {Promise} Promise resolved when done. + * @param refresh Whether the user is refreshing. + * @return Promise resolved when done. */ fetchContent(refresh?: boolean): Promise { this.onLoadingContent.emit(refresh); @@ -116,13 +116,13 @@ export class CoreSitePluginsPluginContentComponent implements OnInit, DoCheck { /** * Open a new page with a new content. * - * @param {string} title The title to display with the new content. - * @param {any} args New params. - * @param {string} [component] New component. If not provided, current component - * @param {string} [method] New method. If not provided, current method - * @param {any} [jsData] JS variables to pass to the new view so they can be used in the template or JS. - * If true is supplied instead of an object, all initial variables from current page will be copied. - * @param {any} [preSets] The preSets for the WS call of the new content. + * @param title The title to display with the new content. + * @param args New params. + * @param component New component. If not provided, current component + * @param method New method. If not provided, current method + * @param jsData JS variables to pass to the new view so they can be used in the template or JS. + * If true is supplied instead of an object, all initial variables from current page will be copied. + * @param preSets The preSets for the WS call of the new content. */ openContent(title: string, args: any, component?: string, method?: string, jsData?: any, preSets?: any): void { if (jsData === true) { @@ -143,7 +143,7 @@ export class CoreSitePluginsPluginContentComponent implements OnInit, DoCheck { /** * Refresh the data. * - * @param {boolean} [showSpinner=true] Whether to show spinner while refreshing. + * @param showSpinner Whether to show spinner while refreshing. */ refreshContent(showSpinner: boolean = true): Promise { if (showSpinner) { @@ -160,10 +160,10 @@ export class CoreSitePluginsPluginContentComponent implements OnInit, DoCheck { /** * Update the content, usually with a different method or params. * - * @param {any} args New params. - * @param {string} [component] New component. If not provided, current component - * @param {string} [method] New method. If not provided, current method - * @param {string} [jsData] JS variables to pass to the new view so they can be used in the template or JS. + * @param args New params. + * @param component New component. If not provided, current component + * @param method New method. If not provided, current method + * @param jsData JS variables to pass to the new view so they can be used in the template or JS. */ updateContent(args: any, component?: string, method?: string, jsData?: any): void { this.component = component || this.component; @@ -180,9 +180,9 @@ export class CoreSitePluginsPluginContentComponent implements OnInit, 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. - * @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.compileComponent) { diff --git a/src/core/siteplugins/directives/call-ws-new-content.ts b/src/core/siteplugins/directives/call-ws-new-content.ts index a2d80d939..159085d6d 100644 --- a/src/core/siteplugins/directives/call-ws-new-content.ts +++ b/src/core/siteplugins/directives/call-ws-new-content.ts @@ -72,7 +72,7 @@ export class CoreSitePluginsCallWSNewContentDirective extends CoreSitePluginsCal /** * Function called when the WS call is successful. * - * @param {any} result Result of the WS call. + * @param result Result of the WS call. */ protected wsCallSuccess(result: any): void { let args = this.args || {}; diff --git a/src/core/siteplugins/directives/call-ws.ts b/src/core/siteplugins/directives/call-ws.ts index 9a0742ed2..4f5b65e69 100644 --- a/src/core/siteplugins/directives/call-ws.ts +++ b/src/core/siteplugins/directives/call-ws.ts @@ -63,7 +63,7 @@ export class CoreSitePluginsCallWSDirective extends CoreSitePluginsCallWSOnClick /** * Function called when the WS call is successful. * - * @param {any} result Result of the WS call. + * @param result Result of the WS call. */ protected wsCallSuccess(result: any): void { if (typeof this.successMessage != 'undefined') { diff --git a/src/core/siteplugins/pages/module-index/module-index.ts b/src/core/siteplugins/pages/module-index/module-index.ts index f5829666c..7203d804a 100644 --- a/src/core/siteplugins/pages/module-index/module-index.ts +++ b/src/core/siteplugins/pages/module-index/module-index.ts @@ -41,7 +41,7 @@ export class CoreSitePluginsModuleIndexPage { /** * Refresh the data. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshData(refresher: any): void { this.content.doRefresh().finally(() => { @@ -87,7 +87,7 @@ export class CoreSitePluginsModuleIndexPage { /** * 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 { return this.content.callComponentFunction('ionViewCanLeave'); diff --git a/src/core/siteplugins/pages/plugin-page/plugin-page.ts b/src/core/siteplugins/pages/plugin-page/plugin-page.ts index 4d3128918..9156aa30c 100644 --- a/src/core/siteplugins/pages/plugin-page/plugin-page.ts +++ b/src/core/siteplugins/pages/plugin-page/plugin-page.ts @@ -49,7 +49,7 @@ export class CoreSitePluginsPluginPage { /** * Refresh the data. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshData(refresher: any): void { this.content.refreshContent(false).finally(() => { @@ -95,7 +95,7 @@ export class CoreSitePluginsPluginPage { /** * 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 { return this.content.callComponentFunction('ionViewCanLeave'); diff --git a/src/core/siteplugins/providers/helper.ts b/src/core/siteplugins/providers/helper.ts index b6c33d0fc..4a0230413 100644 --- a/src/core/siteplugins/providers/helper.ts +++ b/src/core/siteplugins/providers/helper.ts @@ -140,11 +140,11 @@ export class CoreSitePluginsHelperProvider { /** * Download the styles for a handler (if any). * - * @param {any} plugin Data of the plugin. - * @param {string} handlerName Name of the handler in the plugin. - * @param {any} handlerSchema Data about the handler. - * @param {string} [siteId] Site ID. If not provided, current site. - * @return {Promise} Promise resolved with the CSS code. + * @param plugin Data of the plugin. + * @param handlerName Name of the handler in the plugin. + * @param handlerSchema Data about the handler. + * @param siteId Site ID. If not provided, current site. + * @return Promise resolved with the CSS code. */ downloadStyles(plugin: any, handlerName: string, handlerSchema: any, siteId?: string): Promise { @@ -203,10 +203,10 @@ export class CoreSitePluginsHelperProvider { /** * Execute a handler's init method if it has any. * - * @param {any} plugin Data of the plugin. - * @param {any} handlerSchema Data about the handler. - * @return {Promise} Promise resolved when done. It returns the results of the getContent call and the data returned by - * the init JS (if any). + * @param plugin Data of the plugin. + * @param handlerSchema Data about the handler. + * @return Promise resolved when done. It returns the results of the getContent call and the data returned by + * the init JS (if any). */ protected executeHandlerInit(plugin: any, handlerSchema: any): Promise { if (!handlerSchema.init) { @@ -219,11 +219,11 @@ export class CoreSitePluginsHelperProvider { /** * Execute a get_content method and run its javascript (if any). * - * @param {any} plugin Data of the plugin. - * @param {string} method The method to call. - * @param {boolean} [isInit] Whether it's the init method. - * @return {Promise} Promise resolved when done. It returns the results of the getContent call and the data returned by - * the JS (if any). + * @param plugin Data of the plugin. + * @param method The method to call. + * @param isInit Whether it's the init method. + * @return Promise resolved when done. It returns the results of the getContent call and the data returned by + * the JS (if any). */ protected executeMethodAndJS(plugin: any, method: string, isInit?: boolean): Promise { const siteId = this.sitesProvider.getCurrentSiteId(), @@ -265,8 +265,8 @@ export class CoreSitePluginsHelperProvider { /** * Fetch site plugins. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. Returns the list of plugins to load. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. Returns the list of plugins to load. */ fetchSitePlugins(siteId?: string): Promise { const plugins = []; @@ -294,8 +294,8 @@ export class CoreSitePluginsHelperProvider { /** * Given an addon name, return the prefix to add to its string keys. * - * @param {string} addon Name of the addon (plugin.addon). - * @return {string} Prefix. + * @param addon Name of the addon (plugin.addon). + * @return Prefix. */ protected getPrefixForStrings(addon: string): string { if (addon) { @@ -308,9 +308,9 @@ export class CoreSitePluginsHelperProvider { /** * Given an addon name and the key of a string, return the full string key (prefixed). * - * @param {string} addon Name of the addon (plugin.addon). - * @param {string} key The key of the string. - * @return {string} Full string key. + * @param addon Name of the addon (plugin.addon). + * @param key The key of the string. + * @return Full string key. */ protected getPrefixedString(addon: string, key: string): string { return this.getPrefixForStrings(addon) + key; @@ -319,9 +319,9 @@ export class CoreSitePluginsHelperProvider { /** * Check if a certain plugin is a site plugin and it's enabled in a certain site. * - * @param {any} plugin Data of the plugin. - * @param {CoreSite} site Site affected. - * @return {boolean} Whether it's a site plugin and it's enabled. + * @param plugin Data of the plugin. + * @param site Site affected. + * @return Whether it's a site plugin and it's enabled. */ isSitePluginEnabled(plugin: any, site: CoreSite): boolean { if (!site.isFeatureDisabled('sitePlugin_' + plugin.component + '_' + plugin.addon) && plugin.handlers) { @@ -340,7 +340,7 @@ export class CoreSitePluginsHelperProvider { /** * Load the lang strings for a plugin. * - * @param {any} plugin Data of the plugin. + * @param plugin Data of the plugin. */ loadLangStrings(plugin: any): void { if (!plugin.parsedLang) { @@ -357,8 +357,8 @@ export class CoreSitePluginsHelperProvider { /** * Load a site plugin. * - * @param {any} plugin Data of the plugin. - * @return {Promise} Promise resolved when loaded. + * @param plugin Data of the plugin. + * @return Promise resolved when loaded. */ loadSitePlugin(plugin: any): Promise { const promises = []; @@ -390,8 +390,8 @@ export class CoreSitePluginsHelperProvider { /** * Load site plugins. * - * @param {any[]} plugins The plugins to load. - * @return {Promise} Promise resolved when loaded. + * @param plugins The plugins to load. + * @return Promise resolved when loaded. */ loadSitePlugins(plugins: any[]): Promise { const promises = []; @@ -409,12 +409,12 @@ export class CoreSitePluginsHelperProvider { /** * Load the styles for a handler. * - * @param {any} plugin Data of the plugin. - * @param {string} handlerName Name of the handler in the plugin. - * @param {string} fileUrl CSS file URL. - * @param {string} cssCode CSS code. - * @param {number} [version] Styles version. - * @param {string} [siteId] Site ID. If not provided, current site. + * @param plugin Data of the plugin. + * @param handlerName Name of the handler in the plugin. + * @param fileUrl CSS file URL. + * @param cssCode CSS code. + * @param version Styles version. + * @param siteId Site ID. If not provided, current site. */ loadStyles(plugin: any, handlerName: string, fileUrl: string, cssCode: string, version?: number, siteId?: string): void { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -438,10 +438,10 @@ export class CoreSitePluginsHelperProvider { /** * Register a site plugin handler in the right delegate. * - * @param {any} plugin Data of the plugin. - * @param {string} handlerName Name of the handler in the plugin. - * @param {any} handlerSchema Data about the handler. - * @return {Promise} Promise resolved when done. + * @param plugin Data of the plugin. + * @param handlerName Name of the handler in the plugin. + * @param handlerSchema Data about the handler. + * @return Promise resolved when done. */ registerHandler(plugin: any, handlerName: string, handlerSchema: any): Promise { @@ -563,10 +563,10 @@ export class CoreSitePluginsHelperProvider { * These type of handlers will return a generic template and its JS in the main method, so it will be called * before registering the handler. * - * @param {any} plugin Data of the plugin. - * @param {string} handlerName Name of the handler in the plugin. - * @param {any} handlerSchema Data about the handler. - * @return {string|Promise} A string (or a promise resolved with a string) to identify the handler. + * @param plugin Data of the plugin. + * @param handlerName Name of the handler in the plugin. + * @param handlerSchema Data about the handler. + * @return A string (or a promise resolved with a string) to identify the handler. */ protected registerComponentInitHandler(plugin: any, handlerName: string, handlerSchema: any, delegate: any, createHandlerFn: (uniqueName: string, result: any) => any): string | Promise { @@ -613,10 +613,10 @@ export class CoreSitePluginsHelperProvider { /** * Given a handler in a plugin, register it in the assign feedback delegate. * - * @param {any} plugin Data of the plugin. - * @param {string} handlerName Name of the handler in the plugin. - * @param {any} handlerSchema Data about the handler. - * @return {string|Promise} A string (or a promise resolved with a string) to identify the handler. + * @param plugin Data of the plugin. + * @param handlerName Name of the handler in the plugin. + * @param handlerSchema Data about the handler. + * @return A string (or a promise resolved with a string) to identify the handler. */ protected registerAssignFeedbackHandler(plugin: any, handlerName: string, handlerSchema: any): string | Promise { @@ -633,10 +633,10 @@ export class CoreSitePluginsHelperProvider { /** * Given a handler in a plugin, register it in the assign submission delegate. * - * @param {any} plugin Data of the plugin. - * @param {string} handlerName Name of the handler in the plugin. - * @param {any} handlerSchema Data about the handler. - * @return {string|Promise} A string (or a promise resolved with a string) to identify the handler. + * @param plugin Data of the plugin. + * @param handlerName Name of the handler in the plugin. + * @param handlerSchema Data about the handler. + * @return A string (or a promise resolved with a string) to identify the handler. */ protected registerAssignSubmissionHandler(plugin: any, handlerName: string, handlerSchema: any): string | Promise { @@ -653,11 +653,11 @@ export class CoreSitePluginsHelperProvider { /** * Given a handler in a plugin, register it in the block delegate. * - * @param {any} plugin Data of the plugin. - * @param {string} handlerName Name of the handler in the plugin. - * @param {any} handlerSchema Data about the handler. - * @param {any} initResult Result of init function. - * @return {string|Promise} A string (or a promise resolved with a string) to identify the handler. + * @param plugin Data of the plugin. + * @param handlerName Name of the handler in the plugin. + * @param handlerSchema Data about the handler. + * @param initResult Result of init function. + * @return A string (or a promise resolved with a string) to identify the handler. */ protected registerBlockHandler(plugin: any, handlerName: string, handlerSchema: any, initResult: any): string | Promise { @@ -675,10 +675,10 @@ export class CoreSitePluginsHelperProvider { /** * Given a handler in a plugin, register it in the course format delegate. * - * @param {any} plugin Data of the plugin. - * @param {string} handlerName Name of the handler in the plugin. - * @param {any} handlerSchema Data about the handler. - * @return {string} A string to identify the handler. + * @param plugin Data of the plugin. + * @param handlerName Name of the handler in the plugin. + * @param handlerSchema Data about the handler. + * @return A string to identify the handler. */ protected registerCourseFormatHandler(plugin: any, handlerName: string, handlerSchema: any): string { this.logger.debug('Register site plugin in course format delegate:', plugin, handlerSchema); @@ -694,11 +694,11 @@ export class CoreSitePluginsHelperProvider { /** * Given a handler in a plugin, register it in the course options delegate. * - * @param {any} plugin Data of the plugin. - * @param {string} handlerName Name of the handler in the plugin. - * @param {any} handlerSchema Data about the handler. - * @param {any} initResult Result of the init WS call. - * @return {string} A string to identify the handler. + * @param plugin Data of the plugin. + * @param handlerName Name of the handler in the plugin. + * @param handlerSchema Data about the handler. + * @param initResult Result of the init WS call. + * @return A string to identify the handler. */ protected registerCourseOptionHandler(plugin: any, handlerName: string, handlerSchema: any, initResult: any): string { if (!handlerSchema.displaydata) { @@ -734,11 +734,11 @@ export class CoreSitePluginsHelperProvider { /** * Given a handler in a plugin, register it in the main menu delegate. * - * @param {any} plugin Data of the plugin. - * @param {string} handlerName Name of the handler in the plugin. - * @param {any} handlerSchema Data about the handler. - * @param {any} initResult Result of the init WS call. - * @return {string} A string to identify the handler. + * @param plugin Data of the plugin. + * @param handlerName Name of the handler in the plugin. + * @param handlerSchema Data about the handler. + * @param initResult Result of the init WS call. + * @return A string to identify the handler. */ protected registerMainMenuHandler(plugin: any, handlerName: string, handlerSchema: any, initResult: any): string { if (!handlerSchema.displaydata) { @@ -763,11 +763,11 @@ export class CoreSitePluginsHelperProvider { /** * Given a handler in a plugin, register it in the message output delegate. * - * @param {any} plugin Data of the plugin. - * @param {string} handlerName Name of the handler in the plugin. - * @param {any} handlerSchema Data about the handler. - * @param {any} initResult Result of the init WS call. - * @return {string} A string to identify the handler. + * @param plugin Data of the plugin. + * @param handlerName Name of the handler in the plugin. + * @param handlerSchema Data about the handler. + * @param initResult Result of the init WS call. + * @return A string to identify the handler. */ protected registerMessageOutputHandler(plugin: any, handlerName: string, handlerSchema: any, initResult: any): string { if (!handlerSchema.displaydata) { @@ -793,11 +793,11 @@ export class CoreSitePluginsHelperProvider { /** * Given a handler in a plugin, register it in the module delegate. * - * @param {any} plugin Data of the plugin. - * @param {string} handlerName Name of the handler in the plugin. - * @param {any} handlerSchema Data about the handler. - * @param {any} initResult Result of the init WS call. - * @return {string} A string to identify the handler. + * @param plugin Data of the plugin. + * @param handlerName Name of the handler in the plugin. + * @param handlerSchema Data about the handler. + * @param initResult Result of the init WS call. + * @return A string to identify the handler. */ protected registerModuleHandler(plugin: any, handlerName: string, handlerSchema: any, initResult: any): string { if (!handlerSchema.displaydata) { @@ -828,10 +828,10 @@ export class CoreSitePluginsHelperProvider { /** * Given a handler in a plugin, register it in the question delegate. * - * @param {any} plugin Data of the plugin. - * @param {string} handlerName Name of the handler in the plugin. - * @param {any} handlerSchema Data about the handler. - * @return {string|Promise} A string (or a promise resolved with a string) to identify the handler. + * @param plugin Data of the plugin. + * @param handlerName Name of the handler in the plugin. + * @param handlerSchema Data about the handler. + * @return A string (or a promise resolved with a string) to identify the handler. */ protected registerQuestionHandler(plugin: any, handlerName: string, handlerSchema: any): string | Promise { @@ -845,10 +845,10 @@ export class CoreSitePluginsHelperProvider { /** * Given a handler in a plugin, register it in the question behaviour delegate. * - * @param {any} plugin Data of the plugin. - * @param {string} handlerName Name of the handler in the plugin. - * @param {any} handlerSchema Data about the handler. - * @return {string|Promise} A string (or a promise resolved with a string) to identify the handler. + * @param plugin Data of the plugin. + * @param handlerName Name of the handler in the plugin. + * @param handlerSchema Data about the handler. + * @return A string (or a promise resolved with a string) to identify the handler. */ protected registerQuestionBehaviourHandler(plugin: any, handlerName: string, handlerSchema: any): string | Promise { @@ -864,10 +864,10 @@ export class CoreSitePluginsHelperProvider { /** * Given a handler in a plugin, register it in the quiz access rule delegate. * - * @param {any} plugin Data of the plugin. - * @param {string} handlerName Name of the handler in the plugin. - * @param {any} handlerSchema Data about the handler. - * @return {string|Promise} A string (or a promise resolved with a string) to identify the handler. + * @param plugin Data of the plugin. + * @param handlerName Name of the handler in the plugin. + * @param handlerSchema Data about the handler. + * @return A string (or a promise resolved with a string) to identify the handler. */ protected registerQuizAccessRuleHandler(plugin: any, handlerName: string, handlerSchema: any): string | Promise { @@ -882,11 +882,11 @@ export class CoreSitePluginsHelperProvider { /** * Given a handler in a plugin, register it in the settings delegate. * - * @param {any} plugin Data of the plugin. - * @param {string} handlerName Name of the handler in the plugin. - * @param {any} handlerSchema Data about the handler. - * @param {any} initResult Result of the init WS call. - * @return {string} A string to identify the handler. + * @param plugin Data of the plugin. + * @param handlerName Name of the handler in the plugin. + * @param handlerSchema Data about the handler. + * @param initResult Result of the init WS call. + * @return A string to identify the handler. */ protected registerSettingsHandler(plugin: any, handlerName: string, handlerSchema: any, initResult: any): string { if (!handlerSchema.displaydata) { @@ -911,11 +911,11 @@ export class CoreSitePluginsHelperProvider { /** * Given a handler in a plugin, register it in the user profile delegate. * - * @param {any} plugin Data of the plugin. - * @param {string} handlerName Name of the handler in the plugin. - * @param {any} handlerSchema Data about the handler. - * @param {any} initResult Result of the init WS call. - * @return {string} A string to identify the handler. + * @param plugin Data of the plugin. + * @param handlerName Name of the handler in the plugin. + * @param handlerSchema Data about the handler. + * @param initResult Result of the init WS call. + * @return A string to identify the handler. */ protected registerUserProfileHandler(plugin: any, handlerName: string, handlerSchema: any, initResult: any): string { if (!handlerSchema.displaydata) { @@ -951,10 +951,10 @@ export class CoreSitePluginsHelperProvider { /** * Given a handler in a plugin, register it in the user profile field delegate. * - * @param {any} plugin Data of the plugin. - * @param {string} handlerName Name of the handler in the plugin. - * @param {any} handlerSchema Data about the handler. - * @return {string|Promise} A string (or a promise resolved with a string) to identify the handler. + * @param plugin Data of the plugin. + * @param handlerName Name of the handler in the plugin. + * @param handlerSchema Data about the handler. + * @return A string (or a promise resolved with a string) to identify the handler. */ protected registerUserProfileFieldHandler(plugin: any, handlerName: string, handlerSchema: any): string | Promise { @@ -970,10 +970,10 @@ export class CoreSitePluginsHelperProvider { /** * Given a handler in a plugin, register it in the workshop assessment strategy delegate. * - * @param {any} plugin Data of the plugin. - * @param {string} handlerName Name of the handler in the plugin. - * @param {any} handlerSchema Data about the handler. - * @return {string|Promise} A string (or a promise resolved with a string) to identify the handler. + * @param plugin Data of the plugin. + * @param handlerName Name of the handler in the plugin. + * @param handlerSchema Data about the handler. + * @return A string (or a promise resolved with a string) to identify the handler. */ protected registerWorkshopAssessmentStrategyHandler(plugin: any, handlerName: string, handlerSchema: any) : string | Promise { @@ -990,7 +990,7 @@ export class CoreSitePluginsHelperProvider { /** * Reload the handlers that are restricted to certain courses. * - * @return {Promise} Promise resolved when done. + * @return Promise resolved when done. */ protected reloadCourseRestrictHandlers(): Promise { if (!Object.keys(this.courseRestrictHandlers).length) { diff --git a/src/core/siteplugins/providers/siteplugins.ts b/src/core/siteplugins/providers/siteplugins.ts index fe20b3115..0ba03045d 100644 --- a/src/core/siteplugins/providers/siteplugins.ts +++ b/src/core/siteplugins/providers/siteplugins.ts @@ -32,25 +32,21 @@ import { CoreEventsProvider } from '@providers/events'; export interface CoreSitePluginsHandler { /** * The site plugin data. - * @type {any} */ plugin: any; /** * Name of the handler. - * @type {string} */ handlerName: string; /** * Data of the handler. - * @type {any} */ handlerSchema: any; /** * Result of the init WS call. - * @type {any} */ initResult?: any; } @@ -92,9 +88,9 @@ export class CoreSitePluginsProvider { /** * Add some params that will always be sent for get content. * - * @param {any} args Original params. - * @param {CoreSite} [site] Site. If not defined, current site. - * @return {Promise} Promise resolved with the new params. + * @param args Original params. + * @param site Site. If not defined, current site. + * @return Promise resolved with the new params. */ protected addDefaultArgs(args: any, site?: CoreSite): Promise { args = args || {}; @@ -140,11 +136,11 @@ export class CoreSitePluginsProvider { /** * Call a WS for a site plugin. * - * @param {string} method WS method to use. - * @param {any} data Data to send to the WS. - * @param {CoreSiteWSPreSets} [preSets] Extra options. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the response. + * @param method WS method to use. + * @param data Data to send to the WS. + * @param preSets Extra options. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the response. */ callWS(method: string, data: any, preSets?: CoreSiteWSPreSets, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -159,9 +155,9 @@ export class CoreSitePluginsProvider { * Given the result of a init get_content and, optionally, the result of another get_content, * build an object with the data to pass to the JS of the get_content. * - * @param {any} initResult Result of the init WS call. - * @param {any} [contentResult] Result of the content WS call (if any). - * @return {any} An object with the data to pass to the JS. + * @param initResult Result of the init WS call. + * @param contentResult Result of the content WS call (if any). + * @return An object with the data to pass to the JS. */ createDataForJS(initResult: any, contentResult?: any): any { let data; @@ -190,9 +186,9 @@ export class CoreSitePluginsProvider { /** * Get cache key for a WS call. * - * @param {string} method Name of the method. - * @param {any} data Data to identify the WS call. - * @return {string} Cache key. + * @param method Name of the method. + * @param data Data to identify the WS call. + * @return Cache key. */ getCallWSCacheKey(method: string, data: any): string { return this.getCallWSCommonCacheKey(method) + ':' + this.utils.sortAndStringify(data); @@ -201,8 +197,8 @@ export class CoreSitePluginsProvider { /** * Get common cache key for a WS call. * - * @param {string} method Name of the method. - * @return {string} Cache key. + * @param method Name of the method. + * @return Cache key. */ protected getCallWSCommonCacheKey(method: string): string { return this.ROOT_CACHE_KEY + 'ws:' + method; @@ -211,12 +207,12 @@ export class CoreSitePluginsProvider { /** * Get a certain content for a site plugin. * - * @param {string} component Component where the class is. E.g. mod_assign. - * @param {string} method Method to execute in the class. - * @param {any} args The params for the method. - * @param {CoreSiteWSPreSets} [preSets] Extra options. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the result. + * @param component Component where the class is. E.g. mod_assign. + * @param method Method to execute in the class. + * @param args The params for the method. + * @param preSets Extra options. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the result. */ getContent(component: string, method: string, args: any, preSets?: CoreSiteWSPreSets, siteId?: string): Promise { this.logger.debug(`Get content for component '${component}' and method '${method}'`); @@ -262,10 +258,10 @@ export class CoreSitePluginsProvider { /** * Get cache key for get content WS calls. * - * @param {string} component Component where the class is. E.g. mod_assign. - * @param {string} method Method to execute in the class. - * @param {any} args The params for the method. - * @return {string} Cache key. + * @param component Component where the class is. E.g. mod_assign. + * @param method Method to execute in the class. + * @param args The params for the method. + * @return Cache key. */ protected getContentCacheKey(component: string, method: string, args: any): string { return this.ROOT_CACHE_KEY + 'content:' + component + ':' + method + ':' + this.utils.sortAndStringify(args); @@ -274,11 +270,11 @@ export class CoreSitePluginsProvider { /** * Get the value of a WS param for prefetch. * - * @param {string} component The component of the handler. - * @param {string} paramName Name of the param as defined by the handler. - * @param {number} [courseId] Course ID (if prefetching a course). - * @param {any} [module] The module object returned by WS (if prefetching a module). - * @return {any} The value. + * @param component The component of the handler. + * @param paramName Name of the param as defined by the handler. + * @param courseId Course ID (if prefetching a course). + * @param module The module object returned by WS (if prefetching a module). + * @return The value. */ protected getDownloadParam(component: string, paramName: string, courseId?: number, module?: any): any { switch (paramName) { @@ -298,9 +294,9 @@ export class CoreSitePluginsProvider { /** * Get the unique name of a handler (plugin + handler). * - * @param {any} plugin Data of the plugin. - * @param {string} handlerName Name of the handler inside the plugin. - * @return {string} Unique name. + * @param plugin Data of the plugin. + * @param handlerName Name of the handler inside the plugin. + * @return Unique name. */ getHandlerUniqueName(plugin: any, handlerName: string): string { return plugin.addon + '_' + handlerName; @@ -309,8 +305,8 @@ export class CoreSitePluginsProvider { /** * Get a site plugin handler. * - * @param {string} name Unique name of the handler. - * @return {CoreSitePluginsHandler} Handler. + * @param name Unique name of the handler. + * @return Handler. */ getSitePluginHandler(name: string): CoreSitePluginsHandler { return this.sitePlugins[name]; @@ -319,9 +315,9 @@ export class CoreSitePluginsProvider { /** * Invalidate all WS call to a certain method. * - * @param {string} method WS method to use. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param method WS method to use. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateAllCallWSForMethod(method: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -332,11 +328,11 @@ export class CoreSitePluginsProvider { /** * Invalidate a WS call. * - * @param {string} method WS method to use. - * @param {any} data Data to send to the WS. - * @param {CoreSiteWSPreSets} [preSets] Extra options. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param method WS method to use. + * @param data Data to send to the WS. + * @param preSets Extra options. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateCallWS(method: string, data: any, preSets?: CoreSiteWSPreSets, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -347,11 +343,11 @@ export class CoreSitePluginsProvider { /** * Invalidate a page content. * - * @param {string} component Component where the class is. E.g. mod_assign. - * @param {string} method Method to execute in the class. - * @param {any} args The params for the method. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the data is invalidated. + * @param component Component where the class is. E.g. mod_assign. + * @param method Method to execute in the class. + * @param args The params for the method. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when the data is invalidated. */ invalidateContent(component: string, callback: string, args: any, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -362,7 +358,7 @@ export class CoreSitePluginsProvider { /** * Check if the get content WS is available. * - * @param {CoreSite} site The site to check. If not defined, current site. + * @param site The site to check. If not defined, current site. */ isGetContentAvailable(site?: CoreSite): boolean { site = site || this.sitesProvider.getCurrentSite(); @@ -373,10 +369,10 @@ export class CoreSitePluginsProvider { /** * Check if a handler is enabled for a certain course. * - * @param {number} courseId Course ID to check. - * @param {boolean} [restrictEnrolled] If true or undefined, handler is only enabled for courses the user is enrolled in. - * @param {any} [restrict] Users and courses the handler is restricted to. - * @return {boolean | Promise} Whether the handler is enabled. + * @param courseId Course ID to check. + * @param restrictEnrolled If true or undefined, handler is only enabled for courses the user is enrolled in. + * @param restrict Users and courses the handler is restricted to. + * @return Whether the handler is enabled. */ isHandlerEnabledForCourse(courseId: number, restrictEnrolled?: boolean, restrict?: any): boolean | Promise { if (restrict && restrict.courses && restrict.courses.indexOf(courseId) == -1) { @@ -399,10 +395,10 @@ export class CoreSitePluginsProvider { /** * Check if a handler is enabled for a certain user. * - * @param {number} userId User ID to check. - * @param {boolean} [restrictCurrent] Whether handler is only enabled for current user. - * @param {any} [restrict] Users and courses the handler is restricted to. - * @return {boolean} Whether the handler is enabled. + * @param userId User ID to check. + * @param restrictCurrent Whether handler is only enabled for current user. + * @param restrict Users and courses the handler is restricted to. + * @return Whether the handler is enabled. */ isHandlerEnabledForUser(userId: number, restrictCurrent?: boolean, restrict?: any): boolean { if (restrictCurrent && userId != this.sitesProvider.getCurrentSite().getUserId()) { @@ -424,10 +420,10 @@ export class CoreSitePluginsProvider { * If useOtherData is an array, it will only copy the properties whose names are in the array. * If useOtherData is any other value, it will copy all the data from otherData to args. * - * @param {any} args The current args. - * @param {any} otherData All the other data. - * @param {any} useOtherData Names of the attributes to include. - * @return {any} New args. + * @param args The current args. + * @param otherData All the other data. + * @param useOtherData Names of the attributes to include. + * @return New args. */ loadOtherDataInArgs(args: any, otherData: any, useOtherData: any): any { if (!args) { @@ -471,15 +467,15 @@ export class CoreSitePluginsProvider { /** * Prefetch offline functions for a site plugin handler. * - * @param {string} component The component of the handler. - * @param {any} args Params to send to the get_content calls. - * @param {any} handlerSchema The handler schema. - * @param {number} [courseId] Course ID (if prefetching a course). - * @param {any} [module] The module object returned by WS (if prefetching a module). - * @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. - * @param {CoreSite} [site] Site. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param component The component of the handler. + * @param args Params to send to the get_content calls. + * @param handlerSchema The handler schema. + * @param courseId Course ID (if prefetching a course). + * @param module The module object returned by WS (if prefetching a module). + * @param prefetch True to prefetch, false to download right away. + * @param dirPath Path of the directory where to store all the content files. + * @param site Site. If not defined, current site. + * @return Promise resolved when done. */ prefetchFunctions(component: string, args: any, handlerSchema: any, courseId?: number, module?: any, prefetch?: boolean, dirPath?: string, site?: CoreSite): Promise { @@ -536,8 +532,8 @@ export class CoreSitePluginsProvider { /** * Store a site plugin handler. * - * @param {string} name A unique name to identify the handler. - * @param {CoreSitePluginsHandler} handler Handler to set. + * @param name A unique name to identify the handler. + * @param handler Handler to set. */ setSitePluginHandler(name: string, handler: CoreSitePluginsHandler): void { this.sitePlugins[name] = handler; @@ -546,8 +542,8 @@ export class CoreSitePluginsProvider { /** * Store the promise for a plugin that is being initialised. * - * @param {String} component - * @param {Promise} promise + * @param component + * @param promise */ registerSitePluginPromise(component: string, promise: Promise): void { this.sitePluginPromises[component] = promise; @@ -563,8 +559,7 @@ export class CoreSitePluginsProvider { /** * Is a plugin being initialised for the specified component? * - * @param {String} component - * @return {boolean} + * @param component */ sitePluginPromiseExists(component: string): boolean { return this.sitePluginPromises.hasOwnProperty(component); @@ -573,8 +568,7 @@ export class CoreSitePluginsProvider { /** * Get the promise for a plugin that is being initialised. * - * @param {String} component - * @return {Promise} + * @param component */ sitePluginLoaded(component: string): Promise { return this.sitePluginPromises[component]; @@ -583,7 +577,7 @@ export class CoreSitePluginsProvider { /** * Wait for fetch plugins to be done. * - * @return {Promise} Promise resolved when site plugins have been fetched. + * @return Promise resolved when site plugins have been fetched. */ waitFetchPlugins(): Promise { return this.fetchPluginsDeferred.promise; diff --git a/src/core/tag/pages/index-area/index-area.ts b/src/core/tag/pages/index-area/index-area.ts index 9f71f0532..a5e76402f 100644 --- a/src/core/tag/pages/index-area/index-area.ts +++ b/src/core/tag/pages/index-area/index-area.ts @@ -88,8 +88,8 @@ export class CoreTagIndexAreaPage { /** * Fetch next page of the tag index area. * - * @param {boolean} [refresh=false] Whether to refresh the data or fetch a new page. - * @return {Promise} Resolved when done. + * @param refresh Whether to refresh the data or fetch a new page. + * @return Resolved when done. */ fetchData(refresh: boolean = false): Promise { this.loadMoreError = false; @@ -125,8 +125,8 @@ export class CoreTagIndexAreaPage { /** * Load more items. * - * @param {any} infiniteComplete Infinite scroll complete function. - * @return {Promise} Resolved when done. + * @param infiniteComplete Infinite scroll complete function. + * @return Resolved when done. */ loadMore(infiniteComplete: any): Promise { return this.fetchData().finally(() => { @@ -137,7 +137,7 @@ export class CoreTagIndexAreaPage { /** * Refresh data. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshData(refresher: any): void { this.tagProvider.invalidateTagIndexPerArea(this.tagId, this.tagName, this.collectionId, this.areaId, this.fromContextId, diff --git a/src/core/tag/pages/index/index.ts b/src/core/tag/pages/index/index.ts index 9185bae0f..ed9fbe805 100644 --- a/src/core/tag/pages/index/index.ts +++ b/src/core/tag/pages/index/index.ts @@ -78,7 +78,7 @@ export class CoreTagIndexPage { /** * Fetch first page of tag index per area. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ fetchData(): Promise { return this.tagProvider.getTagIndexPerArea(this.tagId, this.tagName, this.collectionId, this.areaId, this.fromContextId, @@ -116,7 +116,7 @@ export class CoreTagIndexPage { /** * Refresh data. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshData(refresher: any): void { this.tagProvider.invalidateTagIndexPerArea(this.tagId, this.tagName, this.collectionId, this.areaId, this.fromContextId, @@ -130,7 +130,7 @@ export class CoreTagIndexPage { /** * Navigate to an index area. * - * @param {any} area Area. + * @param area Area. */ openArea(area: any): void { this.selectedAreaId = area.id; diff --git a/src/core/tag/pages/search/search.ts b/src/core/tag/pages/search/search.ts index 13f09bb4c..c79edfa28 100644 --- a/src/core/tag/pages/search/search.ts +++ b/src/core/tag/pages/search/search.ts @@ -67,7 +67,7 @@ export class CoreTagSearchPage { /** * Fetch tag collections. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ fetchCollections(): Promise { return this.tagProvider.getTagCollections().then((collections) => { @@ -83,7 +83,7 @@ export class CoreTagSearchPage { /** * Fetch tags. * - * @return {Promise} Resolved when done. + * @return Resolved when done. */ fetchTags(): Promise { return this.tagProvider.getTagCloud(this.collectionId, undefined, undefined, this.query).then((cloud) => { @@ -102,7 +102,7 @@ export class CoreTagSearchPage { /** * Refresh data. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshData(refresher: any): void { this.utils.allPromises([ @@ -118,8 +118,8 @@ export class CoreTagSearchPage { /** * Search tags. * - * @param {string} query Search query. - * @return {Promise} Resolved when done. + * @param query Search query. + * @return Resolved when done. */ searchTags(query: string): Promise { this.searching = true; diff --git a/src/core/tag/providers/area-delegate.ts b/src/core/tag/providers/area-delegate.ts index 2b0e2ffb8..ac1693ec1 100644 --- a/src/core/tag/providers/area-delegate.ts +++ b/src/core/tag/providers/area-delegate.ts @@ -24,23 +24,22 @@ import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate'; export interface CoreTagAreaHandler extends CoreDelegateHandler { /** * Component and item type separated by a slash. E.g. 'core/course_modules'. - * @type {string} */ type: string; /** * 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; /** * 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; } @@ -60,9 +59,9 @@ export class CoreTagAreaDelegate extends CoreDelegate { /** * Returns the display name string for this area. * - * @param {string} component Component name. - * @param {string} itemType Item type. - * @return {string} String key. + * @param component Component name. + * @param itemType Item type. + * @return String key. */ getDisplayNameKey(component: string, itemType: string): string { return (component == 'core' ? 'core.tag' : 'addon.' + component) + '.tagarea_' + itemType; @@ -71,10 +70,10 @@ export class CoreTagAreaDelegate extends CoreDelegate { /** * Parses the rendered content of a tag index and returns the items. * - * @param {string} component Component name. - * @param {string} itemType Item type. - * @param {string} content Rendered content. - * @return {Promise} Promise resolved with the area items, or undefined if not found. + * @param component Component name. + * @param itemType Item type. + * @param content Rendered content. + * @return Promise resolved with the area items, or undefined if not found. */ parseContent(component: string, itemType: string, content: string): Promise { const type = component + '/' + itemType; @@ -85,10 +84,10 @@ export class CoreTagAreaDelegate extends CoreDelegate { /** * Get the component to use to display an area item. * - * @param {string} component Component name. - * @param {string} itemType Item type. - * @param {Injector} injector Injector. - * @return {Promise} The component (or promise resolved with component) to use, undefined if not found. + * @param component Component name. + * @param itemType Item type. + * @param injector Injector. + * @return The component (or promise resolved with component) to use, undefined if not found. */ getComponent(component: string, itemType: string, injector: Injector): Promise { const type = component + '/' + itemType; diff --git a/src/core/tag/providers/helper.ts b/src/core/tag/providers/helper.ts index 38c097b79..f62cac0ec 100644 --- a/src/core/tag/providers/helper.ts +++ b/src/core/tag/providers/helper.ts @@ -26,8 +26,8 @@ export class CoreTagHelperProvider { /** * Parses the rendered content of the "core_tag/tagfeed" web template and returns the items. * - * @param {string} content Rendered content. - * @return {any[]} Area items. + * @param content Rendered content. + * @return Area items. */ parseFeedContent(content: string): any[] { const items = []; diff --git a/src/core/tag/providers/index-link-handler.ts b/src/core/tag/providers/index-link-handler.ts index c8e1d7c49..e94bca812 100644 --- a/src/core/tag/providers/index-link-handler.ts +++ b/src/core/tag/providers/index-link-handler.ts @@ -33,12 +33,12 @@ export class CoreTagIndexLinkHandler 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 { @@ -69,11 +69,11 @@ export class CoreTagIndexLinkHandler 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.tagProvider.areTagsAvailable(siteId); diff --git a/src/core/tag/providers/mainmenu-handler.ts b/src/core/tag/providers/mainmenu-handler.ts index 8e676acc1..60f7b1e8c 100644 --- a/src/core/tag/providers/mainmenu-handler.ts +++ b/src/core/tag/providers/mainmenu-handler.ts @@ -30,7 +30,7 @@ export class CoreTagMainMenuHandler implements CoreMainMenuHandler { /** * 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.tagProvider.areTagsAvailable().then((available) => { @@ -46,7 +46,7 @@ export class CoreTagMainMenuHandler 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/core/tag/providers/search-link-handler.ts b/src/core/tag/providers/search-link-handler.ts index 68ea7cb96..e0b16f7df 100644 --- a/src/core/tag/providers/search-link-handler.ts +++ b/src/core/tag/providers/search-link-handler.ts @@ -33,12 +33,12 @@ export class CoreTagSearchLinkHandler 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 { @@ -58,11 +58,11 @@ export class CoreTagSearchLinkHandler 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.tagProvider.areTagsAvailable(siteId); diff --git a/src/core/tag/providers/tag.ts b/src/core/tag/providers/tag.ts index 3a377cba9..4b012d32f 100644 --- a/src/core/tag/providers/tag.ts +++ b/src/core/tag/providers/tag.ts @@ -100,8 +100,8 @@ export class CoreTagProvider { /** * Check whether tags are available in a certain site. * - * @param {string} [siteId] Site Id. If not defined, use current site. - * @return {Promise} Promise resolved with true if available, resolved with false otherwise. + * @param siteId Site Id. If not defined, use current site. + * @return Promise resolved with true if available, resolved with false otherwise. * @since 3.7 */ areTagsAvailable(siteId?: string): Promise { @@ -113,8 +113,8 @@ export class CoreTagProvider { /** * Check whether tags are available in a certain site. * - * @param {CoreSite} [site] Site. If not defined, use current site. - * @return {boolean} True if available. + * @param site Site. If not defined, use current site. + * @return True if available. */ areTagsAvailableInSite(site?: CoreSite): boolean { site = site || this.sitesProvider.getCurrentSite(); @@ -128,16 +128,16 @@ export class CoreTagProvider { /** * Fetch the tag cloud. * - * @param {number} [collectionId=0] Tag collection ID. - * @param {boolean} [isStandard=false] Whether to return only standard tags. - * @param {string} [sort='name'] Sort order for display (id, name, rawname, count, flag, isstandard, tagcollid). - * @param {string} [search=''] Search string. - * @param {number} [fromContextId=0] Context ID where this tag cloud is displayed. - * @param {number} [contextId=0] Only retrieve tag instances in this context. - * @param {boolean} [recursive=true] Retrieve tag instances in the context and its children. - * @param {number} [limit] Maximum number of tags to retrieve. Defaults to SEARCH_LIMIT. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the tag cloud. + * @param collectionId Tag collection ID. + * @param isStandard Whether to return only standard tags. + * @param sort Sort order for display (id, name, rawname, count, flag, isstandard, tagcollid). + * @param search Search string. + * @param fromContextId Context ID where this tag cloud is displayed. + * @param contextId Only retrieve tag instances in this context. + * @param recursive Retrieve tag instances in the context and its children. + * @param limit Maximum number of tags to retrieve. Defaults to SEARCH_LIMIT. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the tag cloud. * @since 3.7 */ getTagCloud(collectionId: number = 0, isStandard: boolean = false, sort: string = 'name', search: string = '', @@ -169,8 +169,8 @@ export class CoreTagProvider { /** * Fetch the tag collections. * - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the tag collections. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the tag collections. * @since 3.7 */ getTagCollections(siteId?: string): Promise { @@ -193,16 +193,16 @@ export class CoreTagProvider { /** * Fetch the tag index. * - * @param {number} [id=0] Tag ID. - * @param {string} [name=''] Tag name. - * @param {number} [collectionId=0] Tag collection ID. - * @param {number} [areaId=0] Tag area ID. - * @param {number} [fromContextId=0] Context ID where the link was displayed. - * @param {number} [contextId=0] Context ID where to search for items. - * @param {boolean} [recursive=true] Search in the context and its children. - * @param {number} [page=0] Page number. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved with the tag index per area. + * @param id Tag ID. + * @param name Tag name. + * @param collectionId Tag collection ID. + * @param areaId Tag area ID. + * @param fromContextId Context ID where the link was displayed. + * @param contextId Context ID where to search for items. + * @param recursive Search in the context and its children. + * @param page Page number. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the tag index per area. * @since 3.7 */ getTagIndexPerArea(id: number, name: string = '', collectionId: number = 0, areaId: number = 0, fromContextId: number = 0, @@ -246,14 +246,14 @@ export class CoreTagProvider { /** * Invalidate tag cloud. * - * @param {number} [collectionId=0] Tag collection ID. - * @param {boolean} [isStandard=false] Whether to return only standard tags. - * @param {string} [sort='name'] Sort order for display (id, name, rawname, count, flag, isstandard, tagcollid). - * @param {string} [search=''] Search string. - * @param {number} [fromContextId=0] Context ID where this tag cloud is displayed. - * @param {number} [contextId=0] Only retrieve tag instances in this context. - * @param {boolean} [recursive=true] Retrieve tag instances in the context and its children. - * @return {Promise} Promise resolved when the data is invalidated. + * @param collectionId Tag collection ID. + * @param isStandard Whether to return only standard tags. + * @param sort Sort order for display (id, name, rawname, count, flag, isstandard, tagcollid). + * @param search Search string. + * @param fromContextId Context ID where this tag cloud is displayed. + * @param contextId Only retrieve tag instances in this context. + * @param recursive Retrieve tag instances in the context and its children. + * @return Promise resolved when the data is invalidated. */ invalidateTagCloud(collectionId: number = 0, isStandard: boolean = false, sort: string = 'name', search: string = '', fromContextId: number = 0, contextId: number = 0, recursive: boolean = true, siteId?: string): Promise { @@ -267,7 +267,7 @@ export class CoreTagProvider { /** * Invalidate tag collections. * - * @return {Promise} Promise resolved when the data is invalidated. + * @return Promise resolved when the data is invalidated. */ invalidateTagCollections(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -280,14 +280,14 @@ export class CoreTagProvider { /** * Invalidate tag index. * - * @param {number} [id=0] Tag ID. - * @param {string} [name=''] Tag name. - * @param {number} [collectionId=0] Tag collection ID. - * @param {number} [areaId=0] Tag area ID. - * @param {number} [fromContextId=0] Context ID where the link was displayed. - * @param {number} [contextId=0] Context ID where to search for items. - * @param {boolean} [recursive=true] Search in the context and its children. - * @return {Promise} Promise resolved when the data is invalidated. + * @param id Tag ID. + * @param name Tag name. + * @param collectionId Tag collection ID. + * @param areaId Tag area ID. + * @param fromContextId Context ID where the link was displayed. + * @param contextId Context ID where to search for items. + * @param recursive Search in the context and its children. + * @return Promise resolved when the data is invalidated. */ invalidateTagIndexPerArea(id: number, name: string = '', collectionId: number = 0, areaId: number = 0, fromContextId: number = 0, contextId: number = 0, recursive: boolean = true, siteId?: string): Promise { @@ -301,14 +301,14 @@ export class CoreTagProvider { /** * Get cache key for tag cloud. * - * @param {number} collectionId Tag collection ID. - * @param {boolean} isStandard Whether to return only standard tags. - * @param {string} sort Sort order for display (id, name, rawname, count, flag, isstandard, tagcollid). - * @param {string} search Search string. - * @param {number} fromContextId Context ID where this tag cloud is displayed. - * @param {number} contextId Only retrieve tag instances in this context. - * @param {boolean} recursive Retrieve tag instances in the context and it's children. - * @return {string} Cache key. + * @param collectionId Tag collection ID. + * @param isStandard Whether to return only standard tags. + * @param sort Sort order for display (id, name, rawname, count, flag, isstandard, tagcollid). + * @param search Search string. + * @param fromContextId Context ID where this tag cloud is displayed. + * @param contextId Only retrieve tag instances in this context. + * @param recursive Retrieve tag instances in the context and it's children. + * @return Cache key. */ protected getTagCloudKey(collectionId: number, isStandard: boolean, sort: string, search: string, fromContextId: number, contextId: number, recursive: boolean): string { @@ -319,7 +319,7 @@ export class CoreTagProvider { /** * Get cache key for tag collections. * - * @return {string} Cache key. + * @return Cache key. */ protected getTagCollectionsKey(): string { return this.ROOT_CACHE_KEY + 'collections'; @@ -328,14 +328,14 @@ export class CoreTagProvider { /** * Get cache key for tag index. * - * @param {number} id Tag ID. - * @param {string} name Tag name. - * @param {number} collectionId Tag collection ID. - * @param {number} areaId Tag area ID. - * @param {number} fromContextId Context ID where the link was displayed. - * @param {number} contextId Context ID where to search for items. - * @param {boolean} [recursive=true] Search in the context and its children. - * @return {string} Cache key. + * @param id Tag ID. + * @param name Tag name. + * @param collectionId Tag collection ID. + * @param areaId Tag area ID. + * @param fromContextId Context ID where the link was displayed. + * @param contextId Context ID where to search for items. + * @param recursive Search in the context and its children. + * @return Cache key. */ protected getTagIndexPerAreaKey(id: number, name: string, collectionId: number, areaId: number, fromContextId: number, contextId: number, recursive: boolean): string { diff --git a/src/core/user/components/participants/participants.ts b/src/core/user/components/participants/participants.ts index 35475a2b4..236345e90 100644 --- a/src/core/user/components/participants/participants.ts +++ b/src/core/user/components/participants/participants.ts @@ -64,8 +64,8 @@ export class CoreUserParticipantsComponent implements OnInit { /** * Fetch all the data required for the view. * - * @param {boolean} [refresh] Empty events array first. - * @return {Promise} Resolved when done. + * @param refresh Empty events array first. + * @return Resolved when done. */ fetchData(refresh: boolean = false): Promise { const firstToGet = refresh ? 0 : this.participants.length; @@ -87,8 +87,8 @@ export class CoreUserParticipantsComponent implements OnInit { /** * Function to load more data. * - * @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. */ loadMoreData(infiniteComplete?: any): Promise { return this.fetchData().finally(() => { @@ -99,7 +99,7 @@ export class CoreUserParticipantsComponent implements OnInit { /** * Refresh data. * - * @param {any} refresher Refresher. + * @param refresher Refresher. */ refreshParticipants(refresher: any): void { this.userProvider.invalidateParticipantsList(this.courseId).finally(() => { @@ -111,7 +111,7 @@ export class CoreUserParticipantsComponent implements OnInit { /** * Navigate to a particular user profile. - * @param {number} userId User Id where to navigate. + * @param userId User Id where to navigate. */ gotoParticipant(userId: number): void { this.participantId = userId; diff --git a/src/core/user/pages/about/about.ts b/src/core/user/pages/about/about.ts index baed48158..96ab5b210 100644 --- a/src/core/user/pages/about/about.ts +++ b/src/core/user/pages/about/about.ts @@ -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/profile/profile.ts b/src/core/user/pages/profile/profile.ts index fd3875f0d..c60bb9a2f 100644 --- a/src/core/user/pages/profile/profile.ts +++ b/src/core/user/pages/profile/profile.ts @@ -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..5d1c61d33 100644 --- a/src/core/user/providers/course-option-handler.ts +++ b/src/core/user/providers/course-option-handler.ts @@ -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..876ea1aa0 100644 --- a/src/core/user/providers/helper.ts +++ b/src/core/user/providers/helper.ts @@ -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..470a52c9d 100644 --- a/src/core/user/providers/offline.ts +++ b/src/core/user/providers/offline.ts @@ -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..501ecc937 100644 --- a/src/core/user/providers/participants-link-handler.ts +++ b/src/core/user/providers/participants-link-handler.ts @@ -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..5ee16c66d 100644 --- a/src/core/user/providers/sync-cron-handler.ts +++ b/src/core/user/providers/sync-cron-handler.ts @@ -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..58cbf6ea7 100644 --- a/src/core/user/providers/sync.ts +++ b/src/core/user/providers/sync.ts @@ -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..3bd607f44 100644 --- a/src/core/user/providers/tag-area-handler.ts +++ b/src/core/user/providers/tag-area-handler.ts @@ -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..0e74b38f8 100644 --- a/src/core/user/providers/user-delegate.ts +++ b/src/core/user/providers/user-delegate.ts @@ -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..862dde6a5 100644 --- a/src/core/user/providers/user-handler.ts +++ b/src/core/user/providers/user-handler.ts @@ -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..6fd64e759 100644 --- a/src/core/user/providers/user-link-handler.ts +++ b/src/core/user/providers/user-link-handler.ts @@ -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..a059ad1a5 100644 --- a/src/core/user/providers/user-profile-field-delegate.ts +++ b/src/core/user/providers/user-profile-field-delegate.ts @@ -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..7529b0b90 100644 --- a/src/core/user/providers/user.ts +++ b/src/core/user/providers/user.ts @@ -72,9 +72,9 @@ export class CoreUserProvider { /** * 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 +95,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 +121,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 +172,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 +182,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 +205,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 +215,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 +228,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 +282,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 +313,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 +323,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 +344,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 +357,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 +370,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 +383,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 +395,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 +407,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 +423,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 +434,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 +456,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 +472,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(); @@ -508,11 +508,11 @@ 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} 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 +529,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 +548,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 +587,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 +607,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 { diff --git a/src/directives/auto-rows.ts b/src/directives/auto-rows.ts index 3160c00f1..6bd56aa22 100644 --- a/src/directives/auto-rows.ts +++ b/src/directives/auto-rows.ts @@ -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/external-content.ts b/src/directives/external-content.ts index 4026e7e0a..d86493a80 100644 --- a/src/directives/external-content.ts +++ b/src/directives/external-content.ts @@ -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/format-text.ts b/src/directives/format-text.ts index ede668a6b..bada2a967 100644 --- a/src/directives/format-text.ts +++ b/src/directives/format-text.ts @@ -37,7 +37,6 @@ import { CoreSplitViewComponent } from '@components/split-view/split-view'; * * Example usage: * - * */ @Directive({ selector: 'core-format-text' @@ -89,8 +88,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 +112,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 +121,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 +250,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) { @@ -352,7 +351,7 @@ 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 { @@ -497,8 +496,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 +525,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; @@ -553,8 +552,8 @@ export class CoreFormatTextDirective implements OnChanges { /** * Treat video filters. Currently only treating youtube video using video JS. * - * @param {HTMLElement} el Video element. - * @param {NavController} navCtrl NavController to use. + * @param el Video element. + * @param navCtrl NavController to use. */ protected treatVideoFilters(video: HTMLElement, navCtrl: NavController): void { // Treat Video JS Youtube video links and translate them to iframes. @@ -587,7 +586,7 @@ export class CoreFormatTextDirective implements OnChanges { /** * 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 +614,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, @@ -699,7 +698,7 @@ export class CoreFormatTextDirective implements OnChanges { * 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. + * @param url URL of the video. */ protected parseYoutubeUrl(url: string): {videoId: string, listId?: string, start?: number} { const result = { diff --git a/src/directives/link.ts b/src/directives/link.ts index a10f69fde..53ac98f78 100644 --- a/src/directives/link.ts +++ b/src/directives/link.ts @@ -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/supress-events.ts b/src/directives/supress-events.ts index 88bfc891b..5c35bb8f2 100644 --- a/src/directives/supress-events.ts +++ b/src/directives/supress-events.ts @@ -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/pipes/bytes-to-size.ts b/src/pipes/bytes-to-size.ts index 6e2d07127..620e1782f 100644 --- a/src/pipes/bytes-to-size.ts +++ b/src/pipes/bytes-to-size.ts @@ -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..b7a5453e4 100644 --- a/src/pipes/create-links.ts +++ b/src/pipes/create-links.ts @@ -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..ed90d1193 100644 --- a/src/pipes/date-day-or-time.ts +++ b/src/pipes/date-day-or-time.ts @@ -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..4be29d433 100644 --- a/src/pipes/duration.ts +++ b/src/pipes/duration.ts @@ -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..e01f62645 100644 --- a/src/pipes/format-date.ts +++ b/src/pipes/format-date.ts @@ -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..cc415e720 100644 --- a/src/pipes/no-tags.ts +++ b/src/pipes/no-tags.ts @@ -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/seconds-to-hms.ts b/src/pipes/seconds-to-hms.ts index dd71624d6..dd2913936 100644 --- a/src/pipes/seconds-to-hms.ts +++ b/src/pipes/seconds-to-hms.ts @@ -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..cbb67e3df 100644 --- a/src/pipes/time-ago.ts +++ b/src/pipes/time-ago.ts @@ -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..be74e7f64 100644 --- a/src/pipes/to-locale-string.ts +++ b/src/pipes/to-locale-string.ts @@ -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..5e9336eb7 100644 --- a/src/providers/app.ts +++ b/src/providers/app.ts @@ -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; } @@ -111,7 +107,7 @@ export class CoreAppProvider { /** * 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 +116,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 +134,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 +143,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,7 +152,7 @@ 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. @@ -166,7 +162,7 @@ export class CoreAppProvider { /** * 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; @@ -177,7 +173,7 @@ export class CoreAppProvider { /** * 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; @@ -188,7 +184,7 @@ export class CoreAppProvider { /** * Check if the keyboard is visible. * - * @return {boolean} Whether keyboard is visible. + * @return Whether keyboard is visible. */ isKeyboardVisible(): boolean { return this.isKeyboardShown; @@ -197,7 +193,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 +210,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 +227,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 +236,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 +245,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 +254,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 +273,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 +290,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 +299,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 +326,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 +378,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 +387,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 +396,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 +424,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 +507,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 +575,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..f5f42b573 100644 --- a/src/providers/config.ts +++ b/src/providers/config.ts @@ -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..0970f12e0 100644 --- a/src/providers/cron.ts +++ b/src/providers/cron.ts @@ -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..20c94218e 100644 --- a/src/providers/db.ts +++ b/src/providers/db.ts @@ -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..243d4a3d4 100644 --- a/src/providers/events.ts +++ b/src/providers/events.ts @@ -77,10 +77,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 +121,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 +141,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..91b91c8c8 100644 --- a/src/providers/file-helper.ts +++ b/src/providers/file-helper.ts @@ -34,13 +34,13 @@ export class CoreFileHelperProvider { /** * 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,15 +104,15 @@ 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 { @@ -185,14 +185,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 +222,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 +231,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 +240,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 +250,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) { diff --git a/src/providers/file-session.ts b/src/providers/file-session.ts index 8d35df55e..bd71659e3 100644 --- a/src/providers/file-session.ts +++ b/src/providers/file-session.ts @@ -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..916a2fb7c 100644 --- a/src/providers/file.ts +++ b/src/providers/file.ts @@ -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; } @@ -97,7 +94,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 +107,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 +135,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 +145,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 +154,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 +181,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 +190,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 +206,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 +220,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 +230,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 +274,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 +285,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 +296,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 +312,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 +337,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 +349,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 +365,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 +408,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 +425,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 +442,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 +456,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 +473,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 +485,13 @@ 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 + * @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. @@ -516,13 +513,13 @@ 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 + * @return Promise to be resolved when the file is read. */ readFileData(fileData: any, format: number = CoreFileProvider.FORMATTEXT): Promise { format = format || CoreFileProvider.FORMATTEXT; @@ -574,10 +571,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 +602,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 +639,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 +655,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 +667,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 +680,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 +697,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 +716,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) { @@ -734,9 +731,9 @@ export class CoreFileProvider { /** * 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. + * @return Promise resolved when the entry is moved. */ moveFile(originalPath: string, newPath: string): Promise { return this.init().then(() => { @@ -786,9 +783,9 @@ export class CoreFileProvider { /** * 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. + * @return Promise resolved when the entry is copied. */ copyFile(from: string, to: string): Promise { let fromFileAndDir, @@ -832,8 +829,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 +853,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 +868,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 +882,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,11 +894,11 @@ 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. + * @return Promise resolved when the file is unzipped. */ unzipFile(path: string, destFolder?: string, onProgress?: Function): Promise { // Get the source file. @@ -920,10 +917,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 +939,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 +955,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 +975,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 +989,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 +1016,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 +1027,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 +1038,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 +1091,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 +1102,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,8 +1140,8 @@ 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; diff --git a/src/providers/filepool.ts b/src/providers/filepool.ts index aded3de7e..8f45b4a0e 100644 --- a/src/providers/filepool.ts +++ b/src/providers/filepool.ts @@ -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,11 +463,11 @@ 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. @@ -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,17 +595,17 @@ 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. + * @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) @@ -748,17 +718,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. + * @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) @@ -829,8 +799,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 +822,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 +837,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 +868,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 +901,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 { @@ -997,15 +967,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 +1015,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 +1115,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 +1133,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. * @@ -1256,9 +1226,9 @@ export class CoreFilepoolProvider { * 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 +1269,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 +1290,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') { @@ -1345,9 +1315,9 @@ export class CoreFilepoolProvider { /** * 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. + * @return Resolved with fixed URL on success, rejected otherwise. */ protected fixPluginfileURL(siteId: string, fileUrl: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { @@ -1358,10 +1328,10 @@ export class CoreFilepoolProvider { /** * 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,9 +1345,9 @@ 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()) { @@ -1397,9 +1367,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 +1378,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,9 +1389,9 @@ 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) => { @@ -1437,8 +1407,8 @@ 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), @@ -1464,9 +1434,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 +1447,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,9 +1478,9 @@ 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) => { @@ -1523,8 +1493,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 +1503,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 +1542,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,12 +1569,12 @@ 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 { @@ -1647,18 +1617,18 @@ export class CoreFilepoolProvider { /** * 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. * @@ -1736,9 +1706,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 +1726,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 +1750,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,9 +1766,9 @@ 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()) { @@ -1815,10 +1785,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 +1803,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,9 +1831,9 @@ 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) => { @@ -1876,9 +1846,9 @@ 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()) { @@ -1898,10 +1868,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 +1882,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 +1896,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 +1907,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 +1923,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 +1939,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 +1962,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 +1992,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 +2006,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 +2020,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 +2041,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 +2067,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 +2092,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 +2110,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 +2135,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 +2194,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 +2213,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 +2232,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 +2253,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. @@ -2305,12 +2275,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,9 +2306,9 @@ 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) => { @@ -2351,10 +2321,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 +2333,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 +2343,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 +2353,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 +2363,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 +2373,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 +2383,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 +2393,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 +2452,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 +2472,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 +2589,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,9 +2600,9 @@ 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) => { @@ -2667,10 +2637,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,9 +2655,9 @@ 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) => { @@ -2700,8 +2670,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 +2688,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); @@ -2754,9 +2724,9 @@ export class CoreFilepoolProvider { /** * 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 +2757,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 +2829,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 +2880,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 +2908,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 +2927,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 +2946,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..f4bf56593 100644 --- a/src/providers/groups.ts +++ b/src/providers/groups.ts @@ -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..e0a8d331d 100644 --- a/src/providers/init.ts +++ b/src/providers/init.ts @@ -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..acf8ec8ca 100644 --- a/src/providers/lang.ts +++ b/src/providers/lang.ts @@ -53,9 +53,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 +90,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 +100,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 +191,7 @@ export class CoreLangProvider { /** * Get all current custom strings. * - * @return {any} Custom strings. + * @return Custom strings. */ getAllCustomStrings(): any { return this.customStrings; @@ -200,7 +200,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 +209,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 +263,7 @@ export class CoreLangProvider { /** * Get the default language. * - * @return {string} Default language. + * @return Default language. */ getDefaultLanguage(): string { return this.defaultLanguage; @@ -272,7 +272,7 @@ export class CoreLangProvider { /** * Get the fallback language. * - * @return {string} Fallback language. + * @return Fallback language. */ getFallbackLanguage(): string { return this.fallbackLanguage; @@ -281,8 +281,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 +300,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) { @@ -345,9 +345,9 @@ export class CoreLangProvider { /** * 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 +375,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 +407,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..712f3eac8 100644 --- a/src/providers/local-notifications.ts +++ b/src/providers/local-notifications.ts @@ -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..f3d989b32 100644 --- a/src/providers/logger.ts +++ b/src/providers/logger.ts @@ -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..8708e7810 100644 --- a/src/providers/plugin-file-delegate.ts +++ b/src/providers/plugin-file-delegate.ts @@ -21,29 +21,27 @@ import { CoreLoggerProvider } from './logger'; export interface CorePluginFileHandler { /** * A name to identify the handler. - * @type {string} */ name: string; /** * The "component" of the handler. It should match the "component" of pluginfile URLs. - * @type {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; } @@ -63,8 +61,8 @@ export class CorePluginFileDelegate { /** * Get the handler for a certain pluginfile url. * - * @param {string} component Component of the plugin. - * @return {CorePluginFileHandler} Handler. Undefined if no handler found for the plugin. + * @param component Component of the plugin. + * @return Handler. Undefined if no handler found for the plugin. */ protected getPluginHandler(component: string): CorePluginFileHandler { if (typeof this.handlers[component] != 'undefined') { @@ -75,8 +73,8 @@ export class CorePluginFileDelegate { /** * 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]). @@ -90,8 +88,8 @@ export class CorePluginFileDelegate { /** * Register a handler. * - * @param {CorePluginFileHandler} 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: CorePluginFileHandler): boolean { if (typeof this.handlers[handler.component] !== 'undefined') { @@ -109,9 +107,9 @@ export class CorePluginFileDelegate { /** * 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]). diff --git a/src/providers/sites-factory.ts b/src/providers/sites-factory.ts index dda512f8c..5bb68cd11 100644 --- a/src/providers/sites-factory.ts +++ b/src/providers/sites-factory.ts @@ -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 515d7117a..5eafef8d8 100644 --- a/src/providers/sites.ts +++ b/src/providers/sites.ts @@ -36,31 +36,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 +66,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,37 +86,31 @@ 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; } @@ -135,22 +121,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,10 +144,10 @@ 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; } @@ -338,8 +318,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; @@ -352,9 +332,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. @@ -395,9 +375,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; @@ -515,8 +495,8 @@ 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() @@ -537,12 +517,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 { @@ -603,11 +583,11 @@ export class CoreSitesProvider { /** * 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') { @@ -683,10 +663,10 @@ 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, @@ -742,9 +722,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); @@ -753,8 +733,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). @@ -778,8 +758,8 @@ export class CoreSitesProvider { /** * Check for the minimum required version. * - * @param {any} info Site info. - * @return {number} Either VALID_VERSION, LEGACY_APP_VERSION, WORKPLACE_APP, MOODLE_APP or INVALID_VERSION. + * @param info Site info. + * @return Either VALID_VERSION, LEGACY_APP_VERSION, WORKPLACE_APP, MOODLE_APP or INVALID_VERSION. */ protected isValidMoodleVersion(info: any): number { if (!info) { @@ -821,8 +801,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) => { @@ -848,8 +828,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+)?)?/); @@ -863,8 +843,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) { @@ -879,13 +859,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 = { @@ -904,10 +884,10 @@ export class CoreSitesProvider { /** * 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}`); @@ -948,7 +928,7 @@ export class CoreSitesProvider { /** * Get current site. * - * @return {CoreSite} Current site. + * @return Current site. */ getCurrentSite(): CoreSite { return this.currentSite; @@ -957,7 +937,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) { @@ -970,7 +950,7 @@ export class CoreSitesProvider { /** * Get current site ID. * - * @return {string} Current site ID. + * @return Current site ID. */ getCurrentSiteId(): string { if (this.currentSite) { @@ -983,7 +963,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) { @@ -996,7 +976,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' && @@ -1006,8 +986,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}`); @@ -1037,7 +1017,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) => { @@ -1048,8 +1028,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) { @@ -1069,8 +1049,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, @@ -1095,8 +1075,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) { @@ -1111,8 +1091,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) => { @@ -1123,8 +1103,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) => { @@ -1135,8 +1115,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) => { @@ -1163,8 +1143,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) => { @@ -1193,7 +1173,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) => { @@ -1206,7 +1186,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) => { @@ -1219,8 +1199,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 = { @@ -1236,7 +1216,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; @@ -1263,7 +1243,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) { @@ -1285,9 +1265,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) => { @@ -1312,11 +1292,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); @@ -1329,10 +1309,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) => { @@ -1353,8 +1333,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) => { @@ -1394,9 +1374,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); @@ -1408,11 +1388,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. @@ -1466,7 +1446,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) => { @@ -1477,8 +1457,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); @@ -1489,8 +1469,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')) { @@ -1504,9 +1484,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) => { @@ -1517,7 +1497,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]); @@ -1526,7 +1506,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. @@ -1541,9 +1521,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(); @@ -1554,8 +1534,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. */ isLegacyMoodleByInfo(info: any): boolean { return this.isValidMoodleVersion(info) == this.LEGACY_APP_VERSION; @@ -1571,8 +1551,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(); @@ -1630,10 +1610,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. @@ -1664,7 +1644,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 = []; diff --git a/src/providers/sync.ts b/src/providers/sync.ts index a248c2ad1..ec8f91653 100644 --- a/src/providers/sync.ts +++ b/src/providers/sync.ts @@ -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..d8a27d5bf 100644 --- a/src/providers/update-manager.ts +++ b/src/providers/update-manager.ts @@ -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..74e0da4ee 100644 --- a/src/providers/urlschemes.ts +++ b/src/providers/urlschemes.ts @@ -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..fb91e5532 100644 --- a/src/providers/utils/dom.ts +++ b/src/providers/utils/dom.ts @@ -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}>; } @@ -86,9 +84,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 +123,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 { @@ -207,8 +205,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 +218,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 +228,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,8 +253,8 @@ 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. */ extractDownloadableFilesFromHtml(html: string): string[] { const urls = []; @@ -288,8 +286,8 @@ 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. */ extractDownloadableFilesFromHtmlAsFakeFileObjects(html: string): any[] { const urls = this.extractDownloadableFilesFromHtml(html); @@ -305,8 +303,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 +329,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 +356,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,8 +373,8 @@ 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)) { @@ -395,9 +393,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 +409,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 +446,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 +457,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 +472,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 +529,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 +540,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 +552,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 +567,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 +615,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 +631,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 +691,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 +703,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 +742,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 +781,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 +806,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 +821,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 +830,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 +852,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 +867,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 +895,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 +905,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 +914,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 +932,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 +983,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 +997,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 +1009,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 +1021,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 +1032,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 +1043,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 +1053,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 +1072,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 +1091,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 +1106,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 +1115,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 +1185,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; @@ -1204,12 +1202,12 @@ export class CoreDomUtilsProvider { /** * 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 +1261,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 +1280,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 +1304,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 +1319,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 +1369,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 +1429,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 +1459,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 +1475,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 +1486,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 +1498,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 +1534,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 +1556,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 +1590,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..5ccbab10b 100644 --- a/src/providers/utils/iframe.ts +++ b/src/providers/utils/iframe.ts @@ -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) { diff --git a/src/providers/utils/mimetype.ts b/src/providers/utils/mimetype.ts index cac90bcb0..0e6e82d63 100644 --- a/src/providers/utils/mimetype.ts +++ b/src/providers/utils/mimetype.ts @@ -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) { @@ -87,7 +87,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 +111,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 || ''; @@ -141,8 +141,8 @@ export class CoreMimetypeUtilsProvider { /** * 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 +153,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 +172,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 +186,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 +199,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 +212,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 +221,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 +232,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 +268,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 +293,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 +312,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 +327,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 +408,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 +430,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 +454,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 +468,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 +490,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 a21ba8838..a33025210 100644 --- a/src/providers/utils/text.ts +++ b/src/providers/utils/text.ts @@ -78,8 +78,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=') + @@ -89,8 +89,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 = ''; @@ -107,9 +107,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 { @@ -140,9 +140,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') { @@ -167,9 +167,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) { @@ -194,8 +194,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. @@ -207,8 +207,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') { @@ -240,8 +240,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))) { @@ -262,8 +262,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) { @@ -277,8 +277,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 { @@ -293,8 +293,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 { @@ -309,8 +309,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') { @@ -323,8 +323,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))) { @@ -344,11 +344,11 @@ 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. */ expandText(title: string, text: string, component?: string, componentId?: string | number, files?: any[]): void { if (text.length > 0) { @@ -371,8 +371,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); @@ -392,12 +392,12 @@ 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. */ formatText(text: string, clean?: boolean, singleLine?: boolean, shortenLength?: number, highlight?: string): Promise { return this.treatMultilangTags(text).then((formatted) => { @@ -418,8 +418,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') { @@ -432,8 +432,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) { @@ -449,8 +449,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); @@ -459,9 +459,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') { @@ -478,8 +478,8 @@ 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) { @@ -496,8 +496,8 @@ export class CoreTextUtilsProvider { * 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++) { @@ -512,8 +512,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) { @@ -532,10 +532,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 { @@ -554,8 +554,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) { @@ -572,8 +572,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') { @@ -586,9 +586,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') { @@ -601,9 +601,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') { @@ -619,9 +619,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') { @@ -640,9 +640,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); @@ -656,8 +656,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) { @@ -670,9 +670,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) { @@ -693,8 +693,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 = ''; @@ -710,8 +710,8 @@ export class CoreTextUtilsProvider { /** * 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) { @@ -730,8 +730,8 @@ 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. */ treatMultilangTags(text: string): Promise { if (!text || typeof text != 'string') { @@ -766,8 +766,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) { @@ -780,8 +780,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); @@ -791,9 +791,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..a35317c34 100644 --- a/src/providers/utils/time.ts +++ b/src/providers/utils/time.ts @@ -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 a7b47de34..7edd7174f 100644 --- a/src/providers/utils/url.ts +++ b/src/providers/utils/url.ts @@ -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) { @@ -47,9 +47,9 @@ export class CoreUrlUtilsProvider { /** * 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 + ''; @@ -58,8 +58,8 @@ export class CoreUrlUtilsProvider { /** * 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,10 +104,10 @@ 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. + * @return Fixed URL. */ fixPluginfileURL(url: string, token: string, siteUrl: string): string { if (!url) { @@ -146,8 +146,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 +171,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; @@ -199,8 +199,8 @@ export class CoreUrlUtilsProvider { * 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 +215,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 +233,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 +250,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 +269,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 +279,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 +289,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 +299,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 +309,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 +319,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 +329,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 +344,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..5b9d3b70f 100644 --- a/src/providers/utils/utils.ts +++ b/src/providers/utils/utils.ts @@ -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) { @@ -138,10 +137,10 @@ export class CoreUtilsProvider { * Converts an array of objects to an object, using a property of each entry as the key. * 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. + * @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 { result = result || {}; @@ -157,12 +156,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 +214,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 +252,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 +266,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 +309,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 +326,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 +341,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 +352,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 +361,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 +374,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 +421,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 +455,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 +474,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 +513,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 +534,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 +591,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 +604,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. @@ -627,7 +626,7 @@ export class CoreUtilsProvider { /** * 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 +648,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 +677,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 +698,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 +712,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 +738,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 +762,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 +772,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 +782,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 +797,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 +825,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 +837,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 +870,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) { @@ -943,7 +942,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 +961,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 +999,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 +1013,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 +1074,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 +1098,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 +1129,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 +1147,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 +1162,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 +1176,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 +1192,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 +1215,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,8 +1225,8 @@ 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)) { @@ -1246,8 +1245,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 +1262,8 @@ 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. */ sumFileSizes(files: any[]): { size: number, total: boolean } { const result = { @@ -1288,9 +1287,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 +1308,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 +1347,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..1d891cfa3 100644 --- a/src/providers/ws.ts +++ b/src/providers/ws.ts @@ -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,25 +62,21 @@ 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; } @@ -96,19 +87,16 @@ export interface CoreWSAjaxPreSets { 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; } @@ -119,13 +107,11 @@ export interface CoreWSError { 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; } @@ -151,12 +137,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 +166,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 +204,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 +292,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,9 +348,9 @@ 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. + * @return Fake WS error. */ createFakeWSError(message: string, needsTranslate?: boolean): CoreWSError { if (needsTranslate) { @@ -379,11 +365,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 +441,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 +457,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 +484,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 +505,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 +514,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 +530,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 +547,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 +673,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 +702,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 +795,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 { From 31c5990593ba2aa0f4bf585669354c880372b2f4 Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Fri, 13 Sep 2019 12:06:09 +0200 Subject: [PATCH 010/257] MOBILE-3147 doc: Fix typos --- src/addon/messages/pages/conversation-info/conversation-info.ts | 2 +- .../messages/pages/group-conversations/group-conversations.ts | 2 +- src/components/infinite-loading/infinite-loading.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/addon/messages/pages/conversation-info/conversation-info.ts b/src/addon/messages/pages/conversation-info/conversation-info.ts index f28253463..eb8046fc1 100644 --- a/src/addon/messages/pages/conversation-info/conversation-info.ts +++ b/src/addon/messages/pages/conversation-info/conversation-info.ts @@ -69,7 +69,7 @@ export class AddonMessagesConversationInfoPage implements OnInit { /** * Get conversation members. * - * @param [loadingMore} Whether we are loading more data or just the first ones. + * @param loadingMore Whether we are loading more data or just the first ones. * @return Promise resolved when done. */ protected fetchMembers(loadingMore?: boolean): Promise { diff --git a/src/addon/messages/pages/group-conversations/group-conversations.ts b/src/addon/messages/pages/group-conversations/group-conversations.ts index de862bc39..e35485724 100644 --- a/src/addon/messages/pages/group-conversations/group-conversations.ts +++ b/src/addon/messages/pages/group-conversations/group-conversations.ts @@ -345,7 +345,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { * Fetch data for a certain option. * * @param option The option to fetch data for. - * @param [loadingMore} Whether we are loading more data or just the first ones. + * @param loadingMore Whether we are loading more data or just the first ones. * @param getCounts Whether to get counts data. * @return Promise resolved when done. */ diff --git a/src/components/infinite-loading/infinite-loading.ts b/src/components/infinite-loading/infinite-loading.ts index 5c5a3ff18..5d19ac215 100644 --- a/src/components/infinite-loading/infinite-loading.ts +++ b/src/components/infinite-loading/infinite-loading.ts @@ -48,7 +48,7 @@ export class CoreInfiniteLoadingComponent implements OnChanges { /** * Detect changes on input properties. * - * @param } changes Changes. + * @param changes Changes. */ ngOnChanges(changes: {[name: string]: SimpleChange}): void { if (changes.enabled && this.enabled && this.position == 'bottom') { From 6c56a83edeeacacd7803c05f207329a85eefcf10 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Mon, 23 Sep 2019 10:16:42 +0200 Subject: [PATCH 011/257] MOBILE-3173 quiz: Fix get amd data in ddmarker questions --- src/core/question/providers/helper.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/question/providers/helper.ts b/src/core/question/providers/helper.ts index e564a97d8..3f06e0929 100644 --- a/src/core/question/providers/helper.ts +++ b/src/core/question/providers/helper.ts @@ -275,9 +275,9 @@ export class CoreQuestionHelperProvider { question.initObjects = this.textUtils.parseJSON(initMatch, null); } - const amdRegExp = new RegExp('require\\(\\["qtype_' + question.type + '/question"\\], ' + - 'function\\(amd\\) \\{ amd\.init\\(("(q|question-' + usageId + '-)' + question.slot + - '".*?)\\); \\}\\);;', 'm'); + const amdRegExp = new RegExp('require\\(\\[["\']qtype_' + question.type + '/question["\']\\], ?' + + 'function\\(amd\\) ?\\{ ?amd\\.init\\((["\'](q|question-' + usageId + '-)' + question.slot + + '["\'].*?)\\);', 'm'); const amdMatch = match.match(amdRegExp); if (amdMatch) { // Try to convert the arguments to an array and add them to the question. From 0c6c1b6383605074c9fb6ffffb64bf0126b35b43 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 2 Aug 2019 11:53:48 +0200 Subject: [PATCH 012/257] MOBILE-3109 scripts: Create a script to convert PHP to TS --- scripts/functions.sh | 4 + scripts/get_ws_ts.php | 69 +++++++++++++++++ scripts/ws_to_ts_functions.php | 138 +++++++++++++++++++++++++++++++++ src/providers/ws.ts | 85 ++++++++++++++++++++ 4 files changed, 296 insertions(+) create mode 100644 scripts/get_ws_ts.php create mode 100644 scripts/ws_to_ts_functions.php diff --git a/scripts/functions.sh b/scripts/functions.sh index ed1472357..87e5e5c43 100644 --- a/scripts/functions.sh +++ b/scripts/functions.sh @@ -21,16 +21,19 @@ function print_success { function print_error { tput setaf 1; echo " ERROR: $1" + tput setaf 0 } function print_ok { tput setaf 2; echo " OK: $1" echo + tput setaf 0 } function print_message { tput setaf 3; echo "-------- $1" echo + tput setaf 0 } function print_title { @@ -38,4 +41,5 @@ function print_title { echo tput setaf 5; echo "$stepnumber $1" tput setaf 5; echo '==================' + tput setaf 0 } \ No newline at end of file diff --git a/scripts/get_ws_ts.php b/scripts/get_ws_ts.php new file mode 100644 index 000000000..4c93260ec --- /dev/null +++ b/scripts/get_ws_ts.php @@ -0,0 +1,69 @@ +. + +/** + * 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 boolean: true to convert the params structure, + * false to convert the returns structure. Defaults to false. + */ + +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'); + +// 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])) { + echo "ERROR: The WS wasn't found in this Moodle installation.\n"; + die(); +} + +if ($useparams) { + $structure = $functiondescs[$wsname]->parameters_desc; + $description = "Params of WS $wsname."; +} else { + $structure = $functiondescs[$wsname]->returns_desc; + $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/ws_to_ts_functions.php b/scripts/ws_to_ts_functions.php new file mode 100644 index 000000000..c6eb60332 --- /dev/null +++ b/scripts/ws_to_ts_functions.php @@ -0,0 +1,138 @@ +. + +/** + * Helper functions for converting a Moodle WS structure to a TS type. + */ + +/** + * 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 ""; + } +} diff --git a/src/providers/ws.ts b/src/providers/ws.ts index 1d891cfa3..cb8aa6eed 100644 --- a/src/providers/ws.ts +++ b/src/providers/ws.ts @@ -116,6 +116,91 @@ export interface CoreWSFileUploadOptions extends FileUploadOptions { itemId?: number; } + +/** + * Structure of warnings returned by WS. + */ +export type CoreWSExternalWarning = { + /** + * Item. + * @type {string} + */ + item?: string; + + /** + * Item id. + * @type {number} + */ + itemid?: number; + + /** + * The warning code can be used by the client app to implement specific behaviour. + * @type {string} + */ + warningcode: string; + + /** + * Untranslated english message to explain the warning. + * @type {string} + */ + message: string; + +}; + +/** + * Structure of files returned by WS. + */ +export type CoreWSExternalFile = { + /** + * File name. + * @type {string} + */ + filename?: string; + + /** + * File path. + * @type {string} + */ + filepath?: string; + + /** + * File size. + * @type {number} + */ + filesize?: number; + + /** + * Downloadable file url. + * @type {string} + */ + fileurl?: string; + + /** + * Time modified. + * @type {number} + */ + timemodified?: number; + + /** + * File mime type. + * @type {string} + */ + mimetype?: string; + + /** + * Whether is an external file. + * @type {number} + */ + isexternalfile?: number; + + /** + * The repository type for external files. + * @type {string} + */ + repositorytype?: string; + +}; + /** * This service allows performing WS calls and download/upload files. */ From b2497a1dd0ccbafda6dea4cebc7acedd2a55ba2c Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Mon, 2 Sep 2019 11:34:48 +0200 Subject: [PATCH 013/257] MOBILE-3109 addon: Add return types to all addons except mod --- .../pages/issued-badge/issued-badge.html | 16 +- .../badges/pages/issued-badge/issued-badge.ts | 4 +- .../badges/pages/user-badges/user-badges.ts | 4 +- src/addon/badges/providers/badges.ts | 76 ++- .../recentlyaccesseditems.ts | 6 +- .../providers/recentlyaccesseditems.ts | 30 +- .../timeline/components/timeline/timeline.ts | 5 +- .../block/timeline/providers/timeline.ts | 34 +- src/addon/blog/components/entries/entries.ts | 15 +- src/addon/blog/providers/blog.ts | 51 +- .../blog/providers/course-option-handler.ts | 3 +- .../calendar/components/calendar/calendar.ts | 4 +- .../addon-calendar-upcoming-events.html | 2 +- .../upcoming-events/upcoming-events.ts | 6 +- src/addon/calendar/pages/day/day.html | 2 +- src/addon/calendar/pages/day/day.ts | 6 +- .../calendar/pages/edit-event/edit-event.html | 2 +- .../calendar/pages/edit-event/edit-event.ts | 15 +- src/addon/calendar/pages/event/event.html | 4 +- src/addon/calendar/pages/list/list.html | 2 +- src/addon/calendar/pages/list/list.ts | 10 +- src/addon/calendar/providers/calendar.ts | 421 +++++++++++-- src/addon/calendar/providers/helper.ts | 14 +- .../competency/components/course/course.ts | 4 +- .../pages/competencies/competencies.ts | 24 +- .../pages/competency/competency.html | 10 +- .../competency/pages/competency/competency.ts | 32 +- .../competencysummary/competencysummary.ts | 7 +- src/addon/competency/pages/plan/plan.html | 3 +- src/addon/competency/pages/plan/plan.ts | 7 +- .../competency/pages/planlist/planlist.ts | 13 +- src/addon/competency/providers/competency.ts | 470 +++++++++++++- .../addon-course-completion-report.html | 2 +- .../components/report/report.ts | 7 +- .../providers/coursecompletion.ts | 58 +- src/addon/files/pages/list/list.ts | 8 +- src/addon/files/providers/files.ts | 77 ++- .../airnotifier/pages/devices/devices.ts | 17 +- .../airnotifier/providers/airnotifier.ts | 45 +- .../confirmed-contacts/confirmed-contacts.ts | 4 +- .../contact-requests/contact-requests.ts | 4 +- .../messages/components/contacts/contacts.ts | 18 +- .../conversation-info/conversation-info.ts | 8 +- .../messages/pages/discussion/discussion.ts | 127 ++-- .../group-conversations.ts | 62 +- src/addon/messages/pages/search/search.ts | 14 +- src/addon/messages/pages/settings/settings.ts | 40 +- .../messages/providers/mainmenu-handler.ts | 2 +- src/addon/messages/providers/messages.ts | 595 +++++++++++++++--- src/addon/messages/providers/sync.ts | 4 +- src/addon/notes/components/list/list.ts | 22 +- src/addon/notes/providers/notes.ts | 79 ++- src/addon/notifications/pages/list/list.ts | 11 +- .../notifications/pages/settings/settings.ts | 44 +- src/addon/notifications/providers/helper.ts | 8 +- .../notifications/providers/notifications.ts | 226 ++++++- src/core/comments/providers/comments.ts | 24 + src/core/course/providers/course.ts | 35 ++ src/core/tag/providers/tag.ts | 136 ++-- src/core/user/providers/user.ts | 18 + src/providers/ws.ts | 244 +++---- 61 files changed, 2607 insertions(+), 634 deletions(-) diff --git a/src/addon/badges/pages/issued-badge/issued-badge.html b/src/addon/badges/pages/issued-badge/issued-badge.html index 111cc8a8f..565993bec 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 @@ - + @@ -30,7 +30,7 @@ - +

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

@@ -48,7 +48,7 @@ - +

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

@@ -99,7 +99,7 @@
- +

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

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

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

@@ -159,7 +159,7 @@
- +

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

@@ -172,7 +172,7 @@
- +

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

diff --git a/src/addon/badges/pages/issued-badge/issued-badge.ts b/src/addon/badges/pages/issued-badge/issued-badge.ts index d33990ddb..ba99f1bc2 100644 --- a/src/addon/badges/pages/issued-badge/issued-badge.ts +++ b/src/addon/badges/pages/issued-badge/issued-badge.ts @@ -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; diff --git a/src/addon/badges/pages/user-badges/user-badges.ts b/src/addon/badges/pages/user-badges/user-badges.ts index fe5ea5c1c..d25b88627 100644 --- a/src/addon/badges/pages/user-badges/user-badges.ts +++ b/src/addon/badges/pages/user-badges/user-badges.ts @@ -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; diff --git a/src/addon/badges/providers/badges.ts b/src/addon/badges/providers/badges.ts index f2b16814b..6cf3a29bf 100644 --- a/src/addon/badges/providers/badges.ts +++ b/src/addon/badges/providers/badges.ts @@ -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'; /** @@ -70,7 +71,7 @@ export class AddonBadgesProvider { * @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); @@ -110,3 +111,76 @@ 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.6. 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. + }[]; + 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/block/recentlyaccesseditems/components/recentlyaccesseditems/recentlyaccesseditems.ts b/src/addon/block/recentlyaccesseditems/components/recentlyaccesseditems/recentlyaccesseditems.ts index 6aa7b3650..4c232f39f 100644 --- a/src/addon/block/recentlyaccesseditems/components/recentlyaccesseditems/recentlyaccesseditems.ts +++ b/src/addon/block/recentlyaccesseditems/components/recentlyaccesseditems/recentlyaccesseditems.ts @@ -16,7 +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 { + AddonBlockRecentlyAccessedItemsProvider, AddonBlockRecentlyAccessedItemsItem +} from '../../providers/recentlyaccesseditems'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; @@ -28,7 +30,7 @@ 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.'; diff --git a/src/addon/block/recentlyaccesseditems/providers/recentlyaccesseditems.ts b/src/addon/block/recentlyaccesseditems/providers/recentlyaccesseditems.ts index a24bc035c..414b2366f 100644 --- a/src/addon/block/recentlyaccesseditems/providers/recentlyaccesseditems.ts +++ b/src/addon/block/recentlyaccesseditems/providers/recentlyaccesseditems.ts @@ -42,14 +42,16 @@ export class AddonBlockRecentlyAccessedItemsProvider { * @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); @@ -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/timeline/components/timeline/timeline.ts b/src/addon/block/timeline/components/timeline/timeline.ts index 8325cbcf3..68e104bbe 100644 --- a/src/addon/block/timeline/components/timeline/timeline.ts +++ b/src/addon/block/timeline/components/timeline/timeline.ts @@ -21,6 +21,7 @@ import { CoreCoursesHelperProvider } from '@core/courses/providers/helper'; import { CoreCourseOptionsDelegate } from '@core/course/providers/options-delegate'; import { CoreBlockBaseComponent } from '@core/block/classes/base-block-component'; import { AddonBlockTimelineProvider } from '../../providers/timeline'; +import { AddonCalendarEvent } from '@addon/calendar/providers/calendar'; /** * Component to render a timeline block. @@ -34,9 +35,9 @@ export class AddonBlockTimelineComponent extends CoreBlockBaseComponent implemen filter = 'next30days'; currentSite: any; timeline = { - events: [], + events: [], loaded: false, - canLoadMore: undefined + canLoadMore: undefined }; timelineCourses = { courses: [], diff --git a/src/addon/block/timeline/providers/timeline.ts b/src/addon/block/timeline/providers/timeline.ts index 6f90184e8..66d4bcd43 100644 --- a/src/addon/block/timeline/providers/timeline.ts +++ b/src/addon/block/timeline/providers/timeline.ts @@ -15,6 +15,7 @@ import { Injectable } from '@angular/core'; import { CoreSitesProvider } from '@providers/sites'; import { CoreCoursesDashboardProvider } from '@core/courses/providers/dashboard'; +import { AddonCalendarEvents, AddonCalendarEventsGroupedByCourse, AddonCalendarEvent } from '@addon/calendar/providers/calendar'; import * as moment from 'moment'; /** @@ -38,7 +39,7 @@ export class AddonBlockTimelineProvider { * @return Promise resolved when the info is retrieved. */ getActionEventsByCourse(courseId: number, afterEventId?: number, siteId?: string): - Promise<{ events: any[], canLoadMore: number }> { + Promise<{ events: AddonCalendarEvent[], canLoadMore: number }> { return this.sitesProvider.getSite(siteId).then((site) => { const time = moment().subtract(14, 'days').unix(), // Check two weeks ago. @@ -55,7 +56,9 @@ export class AddonBlockTimelineProvider { data.aftereventid = afterEventId; } - return site.read('core_calendar_get_action_events_by_course', data, preSets).then((courseEvents): any => { + return site.read('core_calendar_get_action_events_by_course', data, preSets) + .then((courseEvents: AddonCalendarEvents): any => { + if (courseEvents && courseEvents.events) { return this.treatCourseEvents(courseEvents, time); } @@ -82,8 +85,9 @@ export class AddonBlockTimelineProvider { * @param siteId Site ID. If not defined, use current site. * @return Promise resolved when the info is retrieved. */ - getActionEventsByCourses(courseIds: number[], siteId?: string): Promise<{ [s: string]: - { events: any[], canLoadMore: number } }> { + getActionEventsByCourses(courseIds: number[], siteId?: string): Promise<{ [courseId: string]: + { events: AddonCalendarEvent[], canLoadMore: number } }> { + return this.sitesProvider.getSite(siteId).then((site) => { const time = moment().subtract(14, 'days').unix(), // Check two weeks ago. data = { @@ -95,7 +99,9 @@ export class AddonBlockTimelineProvider { cacheKey: this.getActionEventsByCoursesCacheKey() }; - return site.read('core_calendar_get_action_events_by_courses', data, preSets).then((events): any => { + return site.read('core_calendar_get_action_events_by_courses', data, preSets) + .then((events: AddonCalendarEventsGroupedByCourse): any => { + if (events && events.groupedbycourse) { const courseEvents = {}; @@ -127,7 +133,9 @@ export class AddonBlockTimelineProvider { * @param siteId Site ID. If not defined, use current site. * @return Promise resolved when the info is retrieved. */ - getActionEventsByTimesort(afterEventId: number, siteId?: string): Promise<{ events: any[], canLoadMore: number }> { + getActionEventsByTimesort(afterEventId: number, siteId?: string): + Promise<{ events: AddonCalendarEvent[], canLoadMore: number }> { + return this.sitesProvider.getSite(siteId).then((site) => { const time = moment().subtract(14, 'days').unix(), // Check two weeks ago. data: any = { @@ -144,12 +152,14 @@ export class AddonBlockTimelineProvider { data.aftereventid = afterEventId; } - return site.read('core_calendar_get_action_events_by_timesort', data, preSets).then((events): any => { - if (events && events.events) { - const canLoadMore = events.events.length >= data.limitnum ? events.lastid : undefined; + return site.read('core_calendar_get_action_events_by_timesort', data, preSets) + .then((result: AddonCalendarEvents): any => { + + if (result && result.events) { + const canLoadMore = result.events.length >= data.limitnum ? result.lastid : undefined; // Filter events by time in case it uses cache. - events = events.events.filter((element) => { + const events = result.events.filter((element) => { return element.timesort >= time; }); @@ -236,7 +246,9 @@ export class AddonBlockTimelineProvider { * @param timeFrom Current time to filter events from. * @return Object with course events and last loaded event id if more can be loaded. */ - protected treatCourseEvents(course: any, timeFrom: number): { events: any[], canLoadMore: number } { + protected treatCourseEvents(course: AddonCalendarEvents, timeFrom: number): + { events: AddonCalendarEvent[], canLoadMore: number } { + const canLoadMore: number = course.events.length >= AddonBlockTimelineProvider.EVENTS_LIMIT_PER_COURSE ? course.lastid : undefined; diff --git a/src/addon/blog/components/entries/entries.ts b/src/addon/blog/components/entries/entries.ts index 3cd10ebd6..e0e022245 100644 --- a/src/addon/blog/components/entries/entries.ts +++ b/src/addon/blog/components/entries/entries.ts @@ -18,7 +18,7 @@ import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreSitesProvider } from '@providers/sites'; import { CoreUserProvider } from '@core/user/providers/user'; -import { AddonBlogProvider } from '../../providers/blog'; +import { AddonBlogProvider, AddonBlogPost } from '../../providers/blog'; import { CoreCommentsProvider } from '@core/comments/providers/comments'; import { CoreTagProvider } from '@core/tag/providers/tag'; @@ -48,7 +48,7 @@ export class AddonBlogEntriesComponent implements OnInit { loaded = false; canLoadMore = false; loadMoreError = false; - entries = []; + entries: AddonBlogPostFormatted[] = []; currentUserId: number; showMyEntriesToggle = false; onlyMyEntries = false; @@ -118,7 +118,7 @@ export class AddonBlogEntriesComponent implements OnInit { const loadPage = this.onlyMyEntries ? this.userPageLoaded : this.pageLoaded; return this.blogProvider.getEntries(this.filter, loadPage).then((result) => { - const promises = result.entries.map((entry) => { + const promises = result.entries.map((entry: AddonBlogPostFormatted) => { switch (entry.publishstate) { case 'draft': entry.publishTranslated = 'publishtonoone'; @@ -237,5 +237,12 @@ export class AddonBlogEntriesComponent implements OnInit { }); }); } - } + +/** + * Blog post with some calculated data. + */ +type AddonBlogPostFormatted = AddonBlogPost & { + publishTranslated?: string; // Calculated in the app. Key of the string to translate the publish state of the post. + user?: any; // Calculated in the app. Data of the user that wrote the post. +}; diff --git a/src/addon/blog/providers/blog.ts b/src/addon/blog/providers/blog.ts index a816fd9d3..ab3cf1935 100644 --- a/src/addon/blog/providers/blog.ts +++ b/src/addon/blog/providers/blog.ts @@ -18,6 +18,8 @@ import { CoreSitesProvider } from '@providers/sites'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CorePushNotificationsProvider } from '@core/pushnotifications/providers/pushnotifications'; import { CoreSite } from '@classes/site'; +import { CoreWSExternalWarning, CoreWSExternalFile } from '@providers/ws'; +import { CoreTagItem } from '@core/tag/providers/tag'; /** * Service to handle blog entries. @@ -68,7 +70,7 @@ export class AddonBlogProvider { * @param siteId Site ID. If not defined, current site. * @return Promise to be resolved when the entries are retrieved. */ - getEntries(filter: any = {}, page: number = 0, siteId?: string): Promise { + getEntries(filter: any = {}, page: number = 0, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const data = { filters: this.utils.objectToArrayOfObjects(filter, 'name', 'value'), @@ -105,7 +107,7 @@ export class AddonBlogProvider { * @param siteId Site ID. If not defined, current site. * @return Promise to be resolved when done. */ - logView(filter: any = {}, siteId?: string): Promise { + logView(filter: any = {}, siteId?: string): Promise { this.pushNotificationsProvider.logViewListEvent('blog', 'core_blog_view_entries', filter, siteId); return this.sitesProvider.getSite(siteId).then((site) => { @@ -117,3 +119,48 @@ export class AddonBlogProvider { }); } } + +/** + * Data returned by blog's post_exporter. + */ +export type AddonBlogPost = { + id: number; // Post/entry id. + module: string; // Where it was published the post (blog, blog_external...). + userid: number; // Post author. + courseid: number; // Course where the post was created. + groupid: number; // Group post was created for. + moduleid: number; // Module id where the post was created (not used anymore). + coursemoduleid: number; // Course module id where the post was created. + subject: string; // Post subject. + summary: string; // Post summary. + summaryformat: number; // Summary format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). + content: string; // Post content. + uniquehash: string; // Post unique hash. + rating: number; // Post rating. + format: number; // Post content format. + attachment: string; // Post atachment. + publishstate: string; // Post publish state. + lastmodified: number; // When it was last modified. + created: number; // When it was created. + usermodified: number; // User that updated the post. + summaryfiles: CoreWSExternalFile[]; // Summaryfiles. + attachmentfiles?: CoreWSExternalFile[]; // Attachmentfiles. + tags?: CoreTagItem[]; // @since 3.7. Tags. +}; + +/** + * Result of WS core_blog_get_entries. + */ +export type AddonBlogGetEntriesResult = { + entries: AddonBlogPost[]; + totalentries: number; // The total number of entries found. + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Result of WS core_blog_view_entries. + */ +export type AddonBlogViewEntriesResult = { + status: boolean; // Status: true if success. + warnings?: CoreWSExternalWarning[]; +}; diff --git a/src/addon/blog/providers/course-option-handler.ts b/src/addon/blog/providers/course-option-handler.ts index 34140ca2a..769a75f5f 100644 --- a/src/addon/blog/providers/course-option-handler.ts +++ b/src/addon/blog/providers/course-option-handler.ts @@ -21,6 +21,7 @@ import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseHelperProvider } from '@core/course/providers/helper'; import { AddonBlogEntriesComponent } from '../components/entries/entries'; import { AddonBlogProvider } from './blog'; +import { CoreWSExternalFile } from '@providers/ws'; /** * Course nav handler. @@ -100,7 +101,7 @@ export class AddonBlogCourseOptionHandler implements CoreCourseOptionsHandler { return this.blogProvider.getEntries({courseid: course.id}).then((result) => { return result.entries.map((entry) => { - let files = []; + let files: CoreWSExternalFile[] = []; if (entry.attachmentfiles && entry.attachmentfiles.length) { files = entry.attachmentfiles; diff --git a/src/addon/calendar/components/calendar/calendar.ts b/src/addon/calendar/components/calendar/calendar.ts index 391116337..cd9e1deda 100644 --- a/src/addon/calendar/components/calendar/calendar.ts +++ b/src/addon/calendar/components/calendar/calendar.ts @@ -19,7 +19,7 @@ import { CoreSitesProvider } from '@providers/sites'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreUtilsProvider } from '@providers/utils/utils'; -import { AddonCalendarProvider } from '../../providers/calendar'; +import { AddonCalendarProvider, AddonCalendarWeek } from '../../providers/calendar'; import { AddonCalendarHelperProvider } from '../../providers/helper'; import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline'; import { CoreCoursesProvider } from '@core/courses/providers/courses'; @@ -44,7 +44,7 @@ export class AddonCalendarCalendarComponent implements OnInit, OnChanges, OnDest periodName: string; weekDays: any[]; - weeks: any[]; + weeks: AddonCalendarWeek[]; loaded = false; timeFormat: string; isCurrentMonth: boolean; diff --git a/src/addon/calendar/components/upcoming-events/addon-calendar-upcoming-events.html b/src/addon/calendar/components/upcoming-events/addon-calendar-upcoming-events.html index 68f174608..c9ff2ded7 100644 --- a/src/addon/calendar/components/upcoming-events/addon-calendar-upcoming-events.html +++ b/src/addon/calendar/components/upcoming-events/addon-calendar-upcoming-events.html @@ -6,7 +6,7 @@ - +

diff --git a/src/addon/calendar/components/upcoming-events/upcoming-events.ts b/src/addon/calendar/components/upcoming-events/upcoming-events.ts index 43489bd80..2dae78490 100644 --- a/src/addon/calendar/components/upcoming-events/upcoming-events.ts +++ b/src/addon/calendar/components/upcoming-events/upcoming-events.ts @@ -17,7 +17,7 @@ import { CoreEventsProvider } from '@providers/events'; import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; import { CoreSitesProvider } from '@providers/sites'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; -import { AddonCalendarProvider } from '../../providers/calendar'; +import { AddonCalendarProvider, AddonCalendarCalendarEvent } from '../../providers/calendar'; import { AddonCalendarHelperProvider } from '../../providers/helper'; import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline'; import { CoreCoursesProvider } from '@core/courses/providers/courses'; @@ -43,8 +43,8 @@ export class AddonCalendarUpcomingEventsComponent implements OnInit, OnChanges, protected categoriesRetrieved = false; protected categories = {}; protected currentSiteId: string; - protected events = []; // Events (both online and offline). - protected onlineEvents = []; + protected events: AddonCalendarCalendarEvent[] = []; // Events (both online and offline). + protected onlineEvents: AddonCalendarCalendarEvent[] = []; protected offlineEvents = []; // Offline events. protected deletedEvents = []; // Events deleted in offline. protected lookAhead: number; diff --git a/src/addon/calendar/pages/day/day.html b/src/addon/calendar/pages/day/day.html index 34335a7de..cb9102207 100644 --- a/src/addon/calendar/pages/day/day.html +++ b/src/addon/calendar/pages/day/day.html @@ -50,7 +50,7 @@ - +

diff --git a/src/addon/calendar/pages/day/day.ts b/src/addon/calendar/pages/day/day.ts index d87725e8f..87936754d 100644 --- a/src/addon/calendar/pages/day/day.ts +++ b/src/addon/calendar/pages/day/day.ts @@ -20,7 +20,7 @@ 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 { AddonCalendarSyncProvider } from '../../providers/calendar-sync'; @@ -45,7 +45,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. @@ -287,7 +287,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy { 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); } 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.ts b/src/addon/calendar/pages/edit-event/edit-event.ts index df1f0f8c7..a17e5cd5a 100644 --- a/src/addon/calendar/pages/edit-event/edit-event.ts +++ b/src/addon/calendar/pages/edit-event/edit-event.ts @@ -27,7 +27,7 @@ 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'; @@ -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; @@ -152,7 +153,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { * @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; @@ -489,7 +490,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 +498,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. diff --git a/src/addon/calendar/pages/event/event.html b/src/addon/calendar/pages/event/event.html index 366327f66..176250dec 100644 --- a/src/addon/calendar/pages/event/event.html +++ b/src/addon/calendar/pages/event/event.html @@ -2,7 +2,7 @@ - + @@ -32,7 +32,7 @@ - +

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

diff --git a/src/addon/calendar/pages/list/list.html b/src/addon/calendar/pages/list/list.html index 3ae4f4c0d..a400360b4 100644 --- a/src/addon/calendar/pages/list/list.html +++ b/src/addon/calendar/pages/list/list.html @@ -34,7 +34,7 @@
- +

{{ event.timestart * 1000 | coreFormatDate: "strftimetime" }} diff --git a/src/addon/calendar/pages/list/list.ts b/src/addon/calendar/pages/list/list.ts index 77b8922d1..2bb675eda 100644 --- a/src/addon/calendar/pages/list/list.ts +++ b/src/addon/calendar/pages/list/list.ts @@ -14,7 +14,7 @@ import { Component, ViewChild, OnDestroy, NgZone } from '@angular/core'; import { IonicPage, Content, NavParams, NavController } from 'ionic-angular'; -import { AddonCalendarProvider } from '../../providers/calendar'; +import { AddonCalendarProvider, AddonCalendarGetEventsEvent } from '../../providers/calendar'; import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline'; import { AddonCalendarHelperProvider } from '../../providers/helper'; import { AddonCalendarSyncProvider } from '../../providers/calendar-sync'; @@ -62,7 +62,7 @@ export class AddonCalendarListPage implements OnDestroy { protected manualSyncObserver: any; protected onlineObserver: any; protected currentSiteId: string; - protected onlineEvents = []; + protected onlineEvents: AddonCalendarGetEventsEvent[] = []; protected offlineEvents = []; protected deletedEvents = []; @@ -70,7 +70,7 @@ export class AddonCalendarListPage implements OnDestroy { eventsLoaded = false; events = []; // Events (both online and offline). notificationsEnabled = false; - filteredEvents = []; + filteredEvents: AddonCalendarGetEventsEvent[] = []; canLoadMore = false; loadMoreError = false; courseId: number; @@ -402,7 +402,7 @@ export class AddonCalendarListPage implements OnDestroy { * * @return Filtered events. */ - protected getFilteredEvents(): any[] { + protected getFilteredEvents(): AddonCalendarGetEventsEvent[] { if (!this.courseId) { // No filter, display everything. return this.events; @@ -581,7 +581,7 @@ export class AddonCalendarListPage implements OnDestroy { * @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; diff --git a/src/addon/calendar/providers/calendar.ts b/src/addon/calendar/providers/calendar.ts index c518a989d..eb45fe61a 100644 --- a/src/addon/calendar/providers/calendar.ts +++ b/src/addon/calendar/providers/calendar.ts @@ -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'; /** @@ -489,7 +490,7 @@ export class AddonCalendarProvider { * @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 = { @@ -535,22 +536,6 @@ export class AddonCalendarProvider { }); } - /** - * Check if event ends the same day or not. - * - * @param event Event info. - * @return 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. * @@ -562,8 +547,8 @@ export class AddonCalendarProvider { * @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; @@ -635,7 +620,7 @@ export class AddonCalendarProvider { * @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 = { @@ -680,7 +665,7 @@ export class AddonCalendarProvider { * @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 +676,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 = {}; @@ -812,11 +798,10 @@ 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 id Event ID. - * @param refresh True when we should update the event data. * @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 +819,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; }); @@ -849,12 +835,11 @@ 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 id Event ID. - * @param refresh True when we should update the event data. * @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 +849,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(() => { @@ -918,7 +904,7 @@ export class AddonCalendarProvider { * @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, @@ -976,7 +962,7 @@ export class AddonCalendarProvider { * @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 +989,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; @@ -1071,10 +1057,10 @@ export class AddonCalendarProvider { * @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 participants are retrieved. + * @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 +1108,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); @@ -1178,7 +1166,7 @@ export class AddonCalendarProvider { * @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 +1198,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); @@ -1270,7 +1260,8 @@ export class AddonCalendarProvider { * @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 +1284,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; @@ -1604,11 +1595,14 @@ export class AddonCalendarProvider { * If local notification plugin is not enabled, resolve the promise. * * @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(); @@ -1672,7 +1666,7 @@ export class AddonCalendarProvider { * @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(); @@ -1803,11 +1797,10 @@ export class AddonCalendarProvider { * @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. + * @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(); @@ -1847,7 +1840,7 @@ export class AddonCalendarProvider { * @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 +1858,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 +1874,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; // 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; // Visible. + fullnamedisplay: string; // Fullnamedisplay. + viewurl: string; // Viewurl. + courseimage: string; // Courseimage. + progress?: number; // Progress. + hasprogress: boolean; // Hasprogress. + isfavourite: boolean; // Isfavourite. + hidden: boolean; // Hidden. + timeaccess?: number; // Timeaccess. + showshortname: boolean; // Showshortname. + coursecategory: string; // 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; // Normalisedeventtype. + normalisedeventtypetext: string; // 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; // 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; // 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 6c2cd8a6b..515ad44fa 100644 --- a/src/addon/calendar/providers/helper.ts +++ b/src/addon/calendar/providers/helper.ts @@ -16,7 +16,7 @@ import { Injectable } from '@angular/core'; import { CoreLoggerProvider } from '@providers/logger'; import { CoreSitesProvider } from '@providers/sites'; import { CoreCourseProvider } from '@core/course/providers/course'; -import { AddonCalendarProvider } from './calendar'; +import { AddonCalendarProvider, AddonCalendarCalendarEvent } from './calendar'; import { CoreConstants } from '@core/constants'; import { CoreConfigProvider } from '@providers/config'; import { CoreUtilsProvider } from '@providers/utils/utils'; @@ -130,11 +130,11 @@ export class AddonCalendarHelperProvider { * * @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; + formatEventData(e: AddonCalendarCalendarEvent): void { + e.eventIcon = this.EVENTICONS[e.eventtype] || ''; + if (!e.eventIcon) { + e.eventIcon = this.courseProvider.getModuleIconSrc(e.modulename); + e.moduleIcon = e.eventIcon; } e.formattedType = this.calendarProvider.getEventType(e); @@ -160,7 +160,7 @@ export class AddonCalendarHelperProvider { * @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) { diff --git a/src/addon/competency/components/course/course.ts b/src/addon/competency/components/course/course.ts index c9c2a0c15..d6cfb77c9 100644 --- a/src/addon/competency/components/course/course.ts +++ b/src/addon/competency/components/course/course.ts @@ -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, diff --git a/src/addon/competency/pages/competencies/competencies.ts b/src/addon/competency/pages/competencies/competencies.ts index 40eed9b3d..c3f945bf3 100644 --- a/src/addon/competency/pages/competencies/competencies.ts +++ b/src/addon/competency/pages/competencies/competencies.ts @@ -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; @@ -72,7 +75,7 @@ export class AddonCompetencyCompetenciesPage { * @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'); } diff --git a/src/addon/competency/pages/competency/competency.html b/src/addon/competency/pages/competency/competency.html index 4f66d9dc1..20b6876a2 100644 --- a/src/addon/competency/pages/competency/competency.html +++ b/src/addon/competency/pages/competency/competency.html @@ -51,22 +51,22 @@ - + {{ '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 }} diff --git a/src/addon/competency/pages/competency/competency.ts b/src/addon/competency/pages/competency/competency.ts index a9999c740..c7265f4e2 100644 --- a/src/addon/competency/pages/competency/competency.ts +++ b/src/addon/competency/pages/competency/competency.ts @@ -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,10 @@ 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; constructor(private navCtrl: NavController, navParams: NavParams, private translate: TranslateService, private sitesProvider: CoreSitesProvider, private domUtils: CoreDomUtilsProvider, @@ -79,7 +86,8 @@ export class AddonCompetencyCompetencyPage { * @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 +98,21 @@ export class AddonCompetencyCompetencyPage { } return promise.then((competency) => { - competency.usercompetencysummary.usercompetency = competency.usercompetencysummary.usercompetencyplan || - competency.usercompetencysummary.usercompetency; + 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; } diff --git a/src/addon/competency/pages/competencysummary/competencysummary.ts b/src/addon/competency/pages/competencysummary/competencysummary.ts index fc81a80fc..27eccf836 100644 --- a/src/addon/competency/pages/competencysummary/competencysummary.ts +++ b/src/addon/competency/pages/competencysummary/competencysummary.ts @@ -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,7 +29,7 @@ import { AddonCompetencyProvider } from '../../providers/competency'; export class AddonCompetencyCompetencySummaryPage { competencyLoaded = false; competencyId: number; - competency: any; + competency: AddonCompetencySummary; constructor(private navCtrl: NavController, navParams: NavParams, private domUtils: CoreDomUtilsProvider, @Optional() private svComponent: CoreSplitViewComponent, private competencyProvider: AddonCompetencyProvider) { @@ -41,8 +41,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. diff --git a/src/addon/competency/pages/plan/plan.html b/src/addon/competency/pages/plan/plan.html index f8c5a91da..1d965ebf2 100644 --- a/src/addon/competency/pages/plan/plan.html +++ b/src/addon/competency/pages/plan/plan.html @@ -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.ts b/src/addon/competency/pages/plan/plan.ts index acbb1e419..a5ef30d5a 100644 --- a/src/addon/competency/pages/plan/plan.ts +++ b/src/addon/competency/pages/plan/plan.ts @@ -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, @@ -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.'); diff --git a/src/addon/competency/pages/planlist/planlist.ts b/src/addon/competency/pages/planlist/planlist.ts index 04994ac20..0774d66d2 100644 --- a/src/addon/competency/pages/planlist/planlist.ts +++ b/src/addon/competency/pages/planlist/planlist.ts @@ -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) { @@ -66,7 +66,7 @@ export class AddonCompetencyPlanListPage { */ 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: @@ -109,3 +109,10 @@ export class AddonCompetencyPlanListPage { 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.ts b/src/addon/competency/providers/competency.ts index ea0f6df89..5e4940dcc 100644 --- a/src/addon/competency/providers/competency.ts +++ b/src/addon/competency/providers/competency.ts @@ -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. @@ -147,7 +150,7 @@ export class AddonCompetencyProvider { * @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; } @@ -176,9 +181,9 @@ export class AddonCompetencyProvider { * * @param planId ID of the plan. * @param siteId Site ID. If not defined, current site. - * @return Promise to be resolved when the plans are retrieved. + * @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; } @@ -207,9 +214,11 @@ export class AddonCompetencyProvider { * @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 plans are retrieved. + * @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; } @@ -241,10 +252,10 @@ export class AddonCompetencyProvider { * @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 plans are retrieved. + * @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; } @@ -283,9 +296,11 @@ export class AddonCompetencyProvider { * @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 plans are retrieved. + * @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,7 +320,9 @@ 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; } @@ -324,7 +341,9 @@ export class AddonCompetencyProvider { * @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; }); @@ -486,7 +509,7 @@ export class AddonCompetencyProvider { * @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); + } + }); }); } @@ -527,7 +554,7 @@ export class AddonCompetencyProvider { * @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); + } + }); }); } @@ -563,7 +594,7 @@ export class AddonCompetencyProvider { * @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; // 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; // 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[]; // Plans. + pluginbaseurl: string; // 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; // 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[]; +}; 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..54683082d 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 }}

diff --git a/src/addon/coursecompletion/components/report/report.ts b/src/addon/coursecompletion/components/report/report.ts index 3b6371244..640def927 100644 --- a/src/addon/coursecompletion/components/report/report.ts +++ b/src/addon/coursecompletion/components/report/report.ts @@ -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, @@ -59,7 +60,7 @@ export class AddonCourseCompletionReportComponent implements OnInit { 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); diff --git a/src/addon/coursecompletion/providers/coursecompletion.ts b/src/addon/coursecompletion/providers/coursecompletion.ts index 64b4c6f01..f303acddb 100644 --- a/src/addon/coursecompletion/providers/coursecompletion.ts +++ b/src/addon/coursecompletion/providers/coursecompletion.ts @@ -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. @@ -43,7 +44,7 @@ export class AddonCourseCompletionProvider { * @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; @@ -68,7 +69,7 @@ export class AddonCourseCompletionProvider { * @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 { @@ -96,7 +97,9 @@ export class AddonCourseCompletionProvider { * @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; } @@ -243,17 +248,56 @@ export class AddonCourseCompletionProvider { * Mark a course as self completed. * * @param courseId Course ID. - * @return Resolved on success. + * @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/files/pages/list/list.ts b/src/addon/files/pages/list/list.ts index 24aeb0390..061025ed0 100644 --- a/src/addon/files/pages/list/list.ts +++ b/src/addon/files/pages/list/list.ts @@ -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. @@ -147,7 +147,7 @@ export class AddonFilesListPage implements OnDestroy { * @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. diff --git a/src/addon/files/providers/files.ts b/src/addon/files/providers/files.ts index f9051494f..05613b992 100644 --- a/src/addon/files/providers/files.ts +++ b/src/addon/files/providers/files.ts @@ -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. @@ -73,7 +74,7 @@ export class AddonFilesProvider { * @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, @@ -135,7 +136,7 @@ export class AddonFilesProvider { * * @return Promise resolved with the files. */ - getPrivateFiles(): Promise { + getPrivateFiles(): Promise { return this.getFiles(this.getPrivateFilesRootParams()); } @@ -164,7 +165,7 @@ export class AddonFilesProvider { * @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(); @@ -204,7 +205,7 @@ export class AddonFilesProvider { * * @return Promise resolved with the files. */ - getSiteFiles(): Promise { + getSiteFiles(): Promise { return this.getFiles(this.getSiteFilesRootParams()); } @@ -388,7 +389,7 @@ export class AddonFilesProvider { * @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 }, @@ -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/messageoutput/airnotifier/pages/devices/devices.ts b/src/addon/messageoutput/airnotifier/pages/devices/devices.ts index a9b581f37..dfe0e5403 100644 --- a/src/addon/messageoutput/airnotifier/pages/devices/devices.ts +++ b/src/addon/messageoutput/airnotifier/pages/devices/devices.ts @@ -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; @@ -54,7 +54,7 @@ export class AddonMessageOutputAirnotifierDevicesPage implements OnDestroy { 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; }); @@ -110,8 +110,9 @@ export class AddonMessageOutputAirnotifierDevicesPage implements OnDestroy { * @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 e67e134bb..ff810dfc0 100644 --- a/src/addon/messageoutput/airnotifier/providers/airnotifier.ts +++ b/src/addon/messageoutput/airnotifier/providers/airnotifier.ts @@ -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. @@ -39,14 +40,16 @@ export class AddonMessageOutputAirnotifierProvider { * @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) { @@ -74,7 +77,7 @@ export class AddonMessageOutputAirnotifierProvider { * @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; }); }); @@ -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/messages/components/confirmed-contacts/confirmed-contacts.ts b/src/addon/messages/components/confirmed-contacts/confirmed-contacts.ts index 53983afe9..b367bdee3 100644 --- a/src/addon/messages/components/confirmed-contacts/confirmed-contacts.ts +++ b/src/addon/messages/components/confirmed-contacts/confirmed-contacts.ts @@ -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; diff --git a/src/addon/messages/components/contact-requests/contact-requests.ts b/src/addon/messages/components/contact-requests/contact-requests.ts index 1eb5aba36..50f1afffe 100644 --- a/src/addon/messages/components/contact-requests/contact-requests.ts +++ b/src/addon/messages/components/contact-requests/contact-requests.ts @@ -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; diff --git a/src/addon/messages/components/contacts/contacts.ts b/src/addon/messages/components/contacts/contacts.ts index 311196532..16f5a62d3 100644 --- a/src/addon/messages/components/contacts/contacts.ts +++ b/src/addon/messages/components/contacts/contacts.ts @@ -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 = ''; @@ -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); }); @@ -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/pages/conversation-info/conversation-info.ts b/src/addon/messages/pages/conversation-info/conversation-info.ts index eb8046fc1..c3d030c6f 100644 --- a/src/addon/messages/pages/conversation-info/conversation-info.ts +++ b/src/addon/messages/pages/conversation-info/conversation-info.ts @@ -14,7 +14,9 @@ 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'; /** @@ -28,8 +30,8 @@ import { CoreDomUtilsProvider } from '@providers/utils/dom'; export class AddonMessagesConversationInfoPage implements OnInit { loaded = false; - conversation: any; - members = []; + conversation: AddonMessagesConversationFormatted; + members: AddonMessagesConversationMember[] = []; canLoadMore = false; loadMoreError = false; diff --git a/src/addon/messages/pages/discussion/discussion.ts b/src/addon/messages/pages/discussion/discussion.ts index ef908e1c9..e6fed3ca2 100644 --- a/src/addon/messages/pages/discussion/discussion.ts +++ b/src/addon/messages/pages/discussion/discussion.ts @@ -17,7 +17,10 @@ import { IonicPage, NavParams, NavController, Content, ModalController } from 'i import { TranslateService } from '@ngx-translate/core'; import { CoreEventsProvider } from '@providers/events'; import { CoreSitesProvider } from '@providers/sites'; -import { AddonMessagesProvider } from '../../providers/messages'; +import { + AddonMessagesProvider, AddonMessagesConversationFormatted, AddonMessagesConversationMember, AddonMessagesConversationMessage, + AddonMessagesGetMessagesMessage +} from '../../providers/messages'; import { AddonMessagesOfflineProvider } from '../../providers/messages-offline'; import { AddonMessagesSyncProvider } from '../../providers/sync'; import { CoreUserProvider } from '@core/user/providers/user'; @@ -54,7 +57,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { protected messagesBeingSent = 0; protected pagesLoaded = 1; protected lastMessage = {text: '', timecreated: 0}; - protected keepMessageMap = {}; + protected keepMessageMap: {[hash: string]: boolean} = {}; protected syncObserver: any; protected oldContentHeight = 0; protected keyboardObserver: any; @@ -64,7 +67,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { protected showLoadingModal = false; // Whether to show a loading modal while fetching data. conversationId: number; // Conversation ID. Undefined if it's a new individual conversation. - conversation: any; // The conversation object (if it exists). + conversation: AddonMessagesConversationFormatted; // The conversation object (if it exists). userId: number; // User ID you're talking to (only if group messaging not enabled or it's a new individual conversation). currentUserId: number; title: string; @@ -74,18 +77,18 @@ export class AddonMessagesDiscussionPage implements OnDestroy { showKeyboard = false; canLoadMore = false; loadMoreError = false; - messages = []; + messages: (AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted)[] = []; showDelete = false; canDelete = false; groupMessagingEnabled: boolean; isGroup = false; - members: any = {}; // Members that wrote a message, indexed by ID. + members: {[id: number]: AddonMessagesConversationMember} = {}; // Members that wrote a message, indexed by ID. favouriteIcon = 'fa-star'; favouriteIconSlash = false; deleteIcon = 'trash'; blockIcon = 'close-circle'; addRemoveIcon = 'person'; - otherMember: any; // Other member information (individual conversations only). + otherMember: AddonMessagesConversationMember; // Other member information (individual conversations only). footerType: 'message' | 'blocked' | 'requiresContact' | 'requestSent' | 'requestReceived' | 'unable'; requestContactSent = false; requestContactReceived = false; @@ -139,7 +142,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy { * @param message Message to be added. * @param keep If set the keep flag or not. */ - protected addMessage(message: any, keep: boolean = true): void { + protected addMessage(message: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted, + keep: boolean = true): void { + /* Create a hash to identify the message. The text of online messages isn't reliable because it can have random data like VideoJS ID. Try to use id and fallback to text for offline messages. */ message.hash = Md5.hashAsciiStr(String(message.id || message.text || '')) + '#' + message.timecreated + '#' + @@ -158,7 +163,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { * * @param hash Hash of the message to be removed. */ - protected removeMessage(hash: any): void { + protected removeMessage(hash: string): void { if (this.keepMessageMap[hash]) { // Selected to keep it, clear the flag. this.keepMessageMap[hash] = false; @@ -261,10 +266,11 @@ export class AddonMessagesDiscussionPage implements OnDestroy { if (!this.title && this.messages.length) { // Didn't receive the fullname via argument. Try to get it from messages. // It's possible that name cannot be resolved when no messages were yet exchanged. - if (this.messages[0].useridto != this.currentUserId) { - this.title = this.messages[0].usertofullname || ''; + const firstMessage = this.messages[0]; + if (firstMessage.useridto != this.currentUserId) { + this.title = firstMessage.usertofullname || ''; } else { - this.title = this.messages[0].userfromfullname || ''; + this.title = firstMessage.userfromfullname || ''; } } }); @@ -302,7 +308,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { * * @return Resolved when done. */ - protected fetchMessages(): Promise { + protected fetchMessages(): Promise { this.loadMoreError = false; if (this.messagesBeingSent > 0) { @@ -341,7 +347,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { return this.getDiscussionMessages(this.pagesLoaded); }); } - }).then((messages) => { + }).then((messages: (AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted)[]) => { this.loadMessages(messages); }).finally(() => { this.fetching = false; @@ -353,7 +359,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy { * * @param messages Messages to load. */ - protected loadMessages(messages: any[]): void { + protected loadMessages(messages: (AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted)[]) + : void { + if (this.viewDestroyed) { return; } @@ -382,7 +390,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { this.messagesProvider.sortMessages(this.messages); // Calculate which messages need to display the date or user data. - this.messages.forEach((message, index): any => { + this.messages.forEach((message, index) => { message.showDate = this.showDate(message, this.messages[index - 1]); message.showUserData = this.showUserData(message, this.messages[index - 1]); message.showTail = this.showTail(message, this.messages[index + 1]); @@ -411,20 +419,22 @@ export class AddonMessagesDiscussionPage implements OnDestroy { * @return Promise resolved with a boolean: whether the conversation exists or not. */ protected getConversation(conversationId: number, userId: number): Promise { - let promise, - fallbackConversation; + let promise: Promise, + fallbackConversation: AddonMessagesConversationFormatted; // Try to get the conversationId if we don't have it. if (conversationId) { promise = Promise.resolve(conversationId); } else { + let subPromise: Promise; + if (userId == this.currentUserId && this.messagesProvider.isSelfConversationEnabled()) { - promise = this.messagesProvider.getSelfConversation(); + subPromise = this.messagesProvider.getSelfConversation(); } else { - promise = this.messagesProvider.getConversationBetweenUsers(userId, undefined, true); + subPromise = this.messagesProvider.getConversationBetweenUsers(userId, undefined, true); } - promise = promise.then((conversation) => { + promise = subPromise.then((conversation) => { fallbackConversation = conversation; return conversation.id; @@ -437,14 +447,14 @@ export class AddonMessagesDiscussionPage implements OnDestroy { // Ignore errors. }).then(() => { return this.messagesProvider.getConversation(conversationId, undefined, true); - }).catch((error) => { + }).catch((error): any => { // Get conversation failed, use the fallback one if we have it. if (fallbackConversation) { return fallbackConversation; } return Promise.reject(error); - }).then((conversation) => { + }).then((conversation: AddonMessagesConversationFormatted) => { this.conversation = conversation; if (conversation) { @@ -495,7 +505,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy { * @param offset Offset for message list. * @return Promise resolved with the list of messages. */ - protected getConversationMessages(pagesToLoad: number, offset: number = 0): Promise { + protected getConversationMessages(pagesToLoad: number, offset: number = 0) + : Promise { + const excludePending = offset > 0; return this.messagesProvider.getConversationMessages(this.conversationId, excludePending, offset).then((result) => { @@ -535,7 +547,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { * @return Resolved when done. */ protected getDiscussionMessages(pagesToLoad: number, lfReceivedUnread: number = 0, lfReceivedRead: number = 0, - lfSentUnread: number = 0, lfSentRead: number = 0): Promise { + lfSentUnread: number = 0, lfSentRead: number = 0): Promise { // Only get offline messages if we're loading the first "page". const excludePending = lfReceivedUnread > 0 || lfReceivedRead > 0 || lfSentUnread > 0 || lfSentRead > 0; @@ -547,7 +559,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { pagesToLoad--; if (pagesToLoad > 0 && result.canLoadMore) { // More pages to load. Calculate new limit froms. - result.messages.forEach((message) => { + result.messages.forEach((message: AddonMessagesGetMessagesMessageFormatted) => { if (!message.pending) { if (message.useridfrom == this.userId) { if (message.read) { @@ -598,7 +610,8 @@ export class AddonMessagesDiscussionPage implements OnDestroy { for (const x in this.messages) { const message = this.messages[x]; // If an unread message is found, mark all messages as read. - if (message.useridfrom != this.currentUserId && message.read == 0) { + if (message.useridfrom != this.currentUserId && + ( message).read == 0) { messageUnreadFound = true; break; } @@ -616,7 +629,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { promise = this.messagesProvider.markAllMessagesRead(this.userId).then(() => { // Mark all messages as read. this.messages.forEach((message) => { - message.read = 1; + ( message).read = 1; }); }); } @@ -630,10 +643,10 @@ export class AddonMessagesDiscussionPage implements OnDestroy { // Mark each message as read one by one. this.messages.forEach((message) => { // If the message is unread, call this.messagesProvider.markMessageRead. - if (message.useridfrom != this.currentUserId && message.read == 0) { + if (message.useridfrom != this.currentUserId && ( message).read == 0) { promises.push(this.messagesProvider.markMessageRead(message.id).then(() => { readChanged = true; - message.read = 1; + ( message).read = 1; })); } }); @@ -703,7 +716,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { if (!message.pending && message.useridfrom != this.currentUserId) { found++; if (found == this.conversation.unreadcount) { - this.unreadMessageFrom = parseInt(message.id, 10); + this.unreadMessageFrom = Number(message.id); break; } } @@ -713,13 +726,13 @@ export class AddonMessagesDiscussionPage implements OnDestroy { let previousMessageRead = false; for (const x in this.messages) { - const message = this.messages[x]; + const message = this.messages[x]; if (message.useridfrom != this.currentUserId) { const unreadFrom = message.read == 0 && previousMessageRead; if (unreadFrom) { // Save where the label is placed. - this.unreadMessageFrom = parseInt(message.id, 10); + this.unreadMessageFrom = Number(message.id); break; } @@ -808,8 +821,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy { * * @param message Message to be copied. */ - copyMessage(message: any): void { - const text = this.textUtils.decodeHTMLEntities(message.smallmessage || message.text || ''); + copyMessage(message: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted): void { + const text = this.textUtils.decodeHTMLEntities( + ( message).smallmessage || message.text || ''); this.utils.copyToClipboard(text); } @@ -819,7 +833,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy { * @param message Message object to delete. * @param index Index where the message is to delete it from the view. */ - deleteMessage(message: any, index: number): void { + deleteMessage(message: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted, index: number) + : void { + const canDeleteAll = this.conversation && this.conversation.candeletemessagesforallusers, langKey = message.pending || canDeleteAll || this.isSelf ? 'core.areyousure' : 'addon.messages.deletemessageconfirmation', @@ -860,7 +876,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { * @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading. * @return Resolved when done. */ - loadPrevious(infiniteComplete?: any): Promise { + loadPrevious(infiniteComplete?: any): Promise { let infiniteHeight = this.infinite ? this.infinite.getHeight() : 0; const scrollHeight = this.domUtils.getScrollHeight(this.content); @@ -962,7 +978,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { * @param text Message text. */ sendMessage(text: string): void { - let message; + let message: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted; this.hideUnreadLabel(); @@ -970,6 +986,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { this.scrollBottom = true; message = { + id: null, pending: true, sending: true, useridfrom: this.currentUserId, @@ -985,7 +1002,7 @@ export class AddonMessagesDiscussionPage implements OnDestroy { // If there is an ongoing fetch, wait for it to finish. // Otherwise, if a message is sent while fetching it could disappear until the next fetch. this.waitForFetch().finally(() => { - let promise; + let promise: Promise<{sent: boolean, message: any}>; if (this.conversationId) { promise = this.messagesProvider.sendMessageToConversation(this.conversation, text); @@ -1050,7 +1067,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy { * @param prevMessage Previous message where to compare the date with. * @return If date has changed and should be shown. */ - showDate(message: any, prevMessage?: any): boolean { + showDate(message: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted, + prevMessage?: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted): boolean { + if (!prevMessage) { // First message, show it. return true; @@ -1068,7 +1087,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy { * @param prevMessage Previous message. * @return Whether user data should be shown. */ - showUserData(message: any, prevMessage?: any): boolean { + showUserData(message: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted, + prevMessage?: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted): boolean { + return this.isGroup && message.useridfrom != this.currentUserId && this.members[message.useridfrom] && (!prevMessage || prevMessage.useridfrom != message.useridfrom || message.showDate); } @@ -1080,7 +1101,8 @@ export class AddonMessagesDiscussionPage implements OnDestroy { * @param nextMessage Next message. * @return Whether user data should be shown. */ - showTail(message: any, nextMessage?: any): boolean { + showTail(message: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted, + nextMessage?: AddonMessagesConversationMessageFormatted | AddonMessagesGetMessagesMessageFormatted): boolean { return !nextMessage || nextMessage.useridfrom != message.useridfrom || nextMessage.showDate; } @@ -1422,3 +1444,26 @@ export class AddonMessagesDiscussionPage implements OnDestroy { this.viewDestroyed = true; } } + +/** + * Conversation message with some calculated data. + */ +type AddonMessagesConversationMessageFormatted = AddonMessagesConversationMessage & { + pending?: boolean; // Calculated in the app. Whether the message is pending to be sent. + sending?: boolean; // Calculated in the app. Whether the message is being sent right now. + hash?: string; // Calculated in the app. A hash to identify the message. + showDate?: boolean; // Calculated in the app. Whether to show the date before the message. + showUserData?: boolean; // Calculated in the app. Whether to show the user data in the message. + showTail?: boolean; // Calculated in the app. Whether to show a "tail" in the message. +}; + +/** + * Message with some calculated data. + */ +type AddonMessagesGetMessagesMessageFormatted = AddonMessagesGetMessagesMessage & { + sending?: boolean; // Calculated in the app. Whether the message is being sent right now. + hash?: string; // Calculated in the app. A hash to identify the message. + showDate?: boolean; // Calculated in the app. Whether to show the date before the message. + showUserData?: boolean; // Calculated in the app. Whether to show the user data in the message. + showTail?: boolean; // Calculated in the app. Whether to show a "tail" in the message. +}; diff --git a/src/addon/messages/pages/group-conversations/group-conversations.ts b/src/addon/messages/pages/group-conversations/group-conversations.ts index e35485724..007d55b8f 100644 --- a/src/addon/messages/pages/group-conversations/group-conversations.ts +++ b/src/addon/messages/pages/group-conversations/group-conversations.ts @@ -17,7 +17,9 @@ import { IonicPage, Platform, NavController, NavParams, Content } from 'ionic-an import { TranslateService } from '@ngx-translate/core'; import { CoreEventsProvider } from '@providers/events'; import { CoreSitesProvider } from '@providers/sites'; -import { AddonMessagesProvider } from '../../providers/messages'; +import { + AddonMessagesProvider, AddonMessagesConversationFormatted, AddonMessagesConversationMessage +} from '../../providers/messages'; import { AddonMessagesOfflineProvider } from '../../providers/messages-offline'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreUtilsProvider } from '@providers/utils/utils'; @@ -45,19 +47,19 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { selectedConversationId: number; selectedUserId: number; contactRequestsCount = 0; - favourites: any = { + favourites: AddonMessagesGroupConversationOption = { type: null, favourites: true, count: 0, - unread: 0 + unread: 0, }; - group: any = { + group: AddonMessagesGroupConversationOption = { type: AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_GROUP, favourites: false, count: 0, unread: 0 }; - individual: any = { + individual: AddonMessagesGroupConversationOption = { type: AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_INDIVIDUAL, favourites: false, count: 0, @@ -331,7 +333,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { * * @return Promise resolved when done. */ - protected fetchDataForExpandedOption(): Promise { + protected fetchDataForExpandedOption(): Promise { const expandedOption = this.getExpandedOption(); if (expandedOption) { @@ -349,12 +351,12 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { * @param getCounts Whether to get counts data. * @return Promise resolved when done. */ - fetchDataForOption(option: any, loadingMore?: boolean, getCounts?: boolean): Promise { + fetchDataForOption(option: AddonMessagesGroupConversationOption, loadingMore?: boolean, getCounts?: boolean): Promise { option.loadMoreError = false; const limitFrom = loadingMore ? option.conversations.length : 0, promises = []; - let data, + let data: {conversations: AddonMessagesConversationForList[], canLoadMore: boolean}, offlineMessages; // Get the conversations and, if needed, the offline messages. Always try to get the latest data. @@ -422,7 +424,9 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { * @param option The option to search in. If not defined, search in all options. * @return Conversation. */ - protected findConversation(conversationId: number, userId?: number, option?: any): any { + protected findConversation(conversationId: number, userId?: number, option?: AddonMessagesGroupConversationOption) + : AddonMessagesConversationForList { + if (conversationId) { const conversations = option ? (option.conversations || []) : ((this.favourites.conversations || []) .concat(this.group.conversations || []).concat(this.individual.conversations || [])); @@ -445,7 +449,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { * * @return Option currently expanded. */ - protected getExpandedOption(): any { + protected getExpandedOption(): AddonMessagesGroupConversationOption { if (this.favourites.expanded) { return this.favourites; } else if (this.group.expanded) { @@ -495,9 +499,9 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { * * @param option The option to fetch data for. * @param infiniteComplete Infinite scroll complete function. Only used from core-infinite-loading. - * @return Resolved when done. + * @return Promise resolved when done. */ - loadMoreConversations(option: any, infiniteComplete?: any): Promise { + loadMoreConversations(option: AddonMessagesGroupConversationOption, infiniteComplete?: any): Promise { return this.fetchDataForOption(option, true).catch((error) => { this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingdiscussions', true); option.loadMoreError = true; @@ -513,7 +517,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { * @param messages Offline messages. * @return Promise resolved when done. */ - protected loadOfflineMessages(option: any, messages: any[]): Promise { + protected loadOfflineMessages(option: AddonMessagesGroupConversationOption, messages: any[]): Promise { const promises = []; messages.forEach((message) => { @@ -588,7 +592,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { * @param conversation Conversation where to put the last message. * @param message Offline message to add. */ - protected addLastOfflineMessage(conversation: any, message: any): void { + protected addLastOfflineMessage(conversation: any, message: AddonMessagesConversationMessage): void { conversation.lastmessage = message.text; conversation.lastmessagedate = message.timecreated / 1000; conversation.lastmessagepending = true; @@ -601,7 +605,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { * @param conversation Conversation to check. * @return Option object. */ - protected getConversationOption(conversation: any): any { + protected getConversationOption(conversation: AddonMessagesConversationForList): AddonMessagesGroupConversationOption { if (conversation.isfavourite) { return this.favourites; } else if (conversation.type == AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_GROUP) { @@ -618,7 +622,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { * @param refreshUnreadCounts Whether to refresh unread counts. * @return Promise resolved when done. */ - refreshData(refresher?: any, refreshUnreadCounts: boolean = true): Promise { + refreshData(refresher?: any, refreshUnreadCounts: boolean = true): Promise { // Don't invalidate conversations and so, they always try to get latest data. const promises = [ this.messagesProvider.invalidateContactRequestsCountCache(this.siteId) @@ -638,7 +642,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { * * @param option The option to expand/collapse. */ - toggle(option: any): void { + toggle(option: AddonMessagesGroupConversationOption): void { if (option.expanded) { // Already expanded, close it. option.expanded = false; @@ -658,7 +662,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { * @param getCounts Whether to get counts data. * @return Promise resolved when done. */ - protected expandOption(option: any, getCounts?: boolean): Promise { + protected expandOption(option: AddonMessagesGroupConversationOption, getCounts?: boolean): Promise { // Collapse all and expand the right one. this.favourites.expanded = false; this.group.expanded = false; @@ -715,3 +719,25 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { this.memberInfoObserver && this.memberInfoObserver.off(); } } + +/** + * Conversation options. + */ +export type AddonMessagesGroupConversationOption = { + type: number; // Option type. + favourites: boolean; // Whether it contains favourites conversations. + count: number; // Number of conversations. + unread?: number; // Number of unread conversations. + expanded?: boolean; // Whether the option is currently expanded. + loading?: boolean; // Whether the option is being loaded. + canLoadMore?: boolean; // Whether it can load more data. + loadMoreError?: boolean; // Whether there was an error loading more conversations. + conversations?: AddonMessagesConversationForList[]; // List of conversations. +}; + +/** + * Formatted conversation with some calculated data for the list. + */ +export type AddonMessagesConversationForList = AddonMessagesConversationFormatted & { + lastmessagepending?: boolean; // Calculated in the app. Whether last message is pending to be sent. +}; diff --git a/src/addon/messages/pages/search/search.ts b/src/addon/messages/pages/search/search.ts index 54e5a74c7..9268f58f0 100644 --- a/src/addon/messages/pages/search/search.ts +++ b/src/addon/messages/pages/search/search.ts @@ -16,7 +16,7 @@ import { Component, OnDestroy, ViewChild } from '@angular/core'; import { IonicPage } from 'ionic-angular'; import { CoreEventsProvider } from '@providers/events'; import { CoreSitesProvider } from '@providers/sites'; -import { AddonMessagesProvider } from '../../providers/messages'; +import { AddonMessagesProvider, AddonMessagesConversationMember, AddonMessagesMessageAreaContact } from '../../providers/messages'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreAppProvider } from '@providers/app'; @@ -38,21 +38,21 @@ export class AddonMessagesSearchPage implements OnDestroy { contacts = { type: 'contacts', titleString: 'addon.messages.contacts', - results: [], + results: [], canLoadMore: false, loadingMore: false }; nonContacts = { type: 'noncontacts', titleString: 'addon.messages.noncontacts', - results: [], + results: [], canLoadMore: false, loadingMore: false }; messages = { type: 'messages', titleString: 'addon.messages.messages', - results: [], + results: [], canLoadMore: false, loadingMore: false, loadMoreError: false @@ -116,9 +116,9 @@ export class AddonMessagesSearchPage implements OnDestroy { this.displaySearching = !loadMore; const promises = []; - let newContacts = []; - let newNonContacts = []; - let newMessages = []; + let newContacts: AddonMessagesConversationMember[] = []; + let newNonContacts: AddonMessagesConversationMember[] = []; + let newMessages: AddonMessagesMessageAreaContact[] = []; let canLoadMoreContacts = false; let canLoadMoreNonContacts = false; let canLoadMoreMessages = false; diff --git a/src/addon/messages/pages/settings/settings.ts b/src/addon/messages/pages/settings/settings.ts index b2eed0099..1a8d9de5e 100644 --- a/src/addon/messages/pages/settings/settings.ts +++ b/src/addon/messages/pages/settings/settings.ts @@ -14,7 +14,10 @@ import { Component, OnDestroy } from '@angular/core'; import { IonicPage } from 'ionic-angular'; -import { AddonMessagesProvider } from '../../providers/messages'; +import { + AddonMessagesProvider, AddonMessagesMessagePreferences, AddonMessagesMessagePreferencesNotification, + AddonMessagesMessagePreferencesNotificationProcessor +} from '../../providers/messages'; import { CoreUserProvider } from '@core/user/providers/user'; import { CoreAppProvider } from '@providers/app'; import { CoreConfigProvider } from '@providers/config'; @@ -34,7 +37,7 @@ import { CoreConstants } from '@core/constants'; export class AddonMessagesSettingsPage implements OnDestroy { protected updateTimeout: any; - preferences: any; + preferences: AddonMessagesMessagePreferences; preferencesLoaded: boolean; contactablePrivacy: number | boolean; advancedContactable = false; // Whether the site supports "advanced" contactable privacy. @@ -78,9 +81,9 @@ export class AddonMessagesSettingsPage implements OnDestroy { /** * Fetches preference data. * - * @return Resolved when done. + * @return Promise resolved when done. */ - protected fetchPreferences(): Promise { + protected fetchPreferences(): Promise { return this.messagesProvider.getMessagePreferences().then((preferences) => { if (this.groupMessagingEnabled) { // Simplify the preferences. @@ -90,11 +93,12 @@ export class AddonMessagesSettingsPage implements OnDestroy { return notification.preferencekey == AddonMessagesProvider.NOTIFICATION_PREFERENCES_KEY; }); - for (const notification of component.notifications) { - for (const processor of notification.processors) { + component.notifications.forEach((notification) => { + notification.processors.forEach( + (processor: AddonMessagesMessagePreferencesNotificationProcessorFormatted) => { processor.checked = processor.loggedin.checked || processor.loggedoff.checked; - } - } + }); + }); } } @@ -168,14 +172,16 @@ export class AddonMessagesSettingsPage implements OnDestroy { * @param state State name, ['loggedin', 'loggedoff']. * @param processor Notification processor. */ - changePreference(notification: any, state: string, processor: any): void { + changePreference(notification: AddonMessagesMessagePreferencesNotificationFormatted, state: string, + processor: AddonMessagesMessagePreferencesNotificationProcessorFormatted): void { + if (this.groupMessagingEnabled) { // Update both states at the same time. const valueArray = [], promises = []; let value = 'none'; - notification.processors.forEach((processor) => { + notification.processors.forEach((processor: AddonMessagesMessagePreferencesNotificationProcessorFormatted) => { if (processor.checked) { valueArray.push(processor.name); } @@ -268,3 +274,17 @@ export class AddonMessagesSettingsPage implements OnDestroy { } } } + +/** + * Message preferences notification with some caclulated data. + */ +type AddonMessagesMessagePreferencesNotificationFormatted = AddonMessagesMessagePreferencesNotification & { + updating?: boolean | {[state: string]: boolean}; // Calculated in the app. Whether the notification is being updated. +}; + +/** + * Message preferences notification processor with some caclulated data. + */ +type AddonMessagesMessagePreferencesNotificationProcessorFormatted = AddonMessagesMessagePreferencesNotificationProcessor & { + checked?: boolean; // Calculated in the app. Whether the processor is checked either for loggedin or loggedoff. +}; diff --git a/src/addon/messages/providers/mainmenu-handler.ts b/src/addon/messages/providers/mainmenu-handler.ts index b39725450..f7e0547f2 100644 --- a/src/addon/messages/providers/mainmenu-handler.ts +++ b/src/addon/messages/providers/mainmenu-handler.ts @@ -248,7 +248,7 @@ export class AddonMessagesMainMenuHandler implements CoreMainMenuHandler, CoreCr } const currentUserId = site.getUserId(), - message = conv.messages[0]; // Treat only the last message, is the one we're interested. + message: any = conv.messages[0]; // Treat only the last message, is the one we're interested. if (!message || message.useridfrom == currentUserId) { // No last message or not from current user. Return empty list. diff --git a/src/addon/messages/providers/messages.ts b/src/addon/messages/providers/messages.ts index a16d9810f..12c482b94 100644 --- a/src/addon/messages/providers/messages.ts +++ b/src/addon/messages/providers/messages.ts @@ -23,6 +23,7 @@ import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreEmulatorHelperProvider } from '@core/emulator/providers/helper'; import { CoreEventsProvider } from '@providers/events'; import { CoreSite } from '@classes/site'; +import { CoreWSExternalWarning } from '@providers/ws'; /** * Service to handle messages. @@ -89,9 +90,9 @@ export class AddonMessagesProvider { * * @param userId User ID of the person to block. * @param siteId Site ID. If not defined, use current site. - * @return Resolved when done. + * @return Promise resolved when done. */ - blockContact(userId: number, siteId?: string): Promise { + blockContact(userId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { let promise; if (site.wsAvailable('core_message_block_user')) { @@ -313,7 +314,9 @@ export class AddonMessagesProvider { * @param userId User ID viewing the conversation. * @return Formatted conversation. */ - protected formatConversation(conversation: any, userId: number): any { + protected formatConversation(conversation: AddonMessagesConversationFormatted, userId: number) + : AddonMessagesConversationFormatted { + const numMessages = conversation.messages.length, lastMessage = numMessages ? conversation.messages[numMessages - 1] : null; @@ -536,10 +539,10 @@ export class AddonMessagesProvider { * Get all the contacts of the current user. * * @param siteId Site ID. If not defined, use current site. - * @return Resolved with the WS data. + * @return Promise resolved with the WS data. * @deprecated since Moodle 3.6 */ - getAllContacts(siteId?: string): Promise { + getAllContacts(siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); return this.getContacts(siteId).then((contacts) => { @@ -562,9 +565,9 @@ export class AddonMessagesProvider { * Get all the users blocked by the current user. * * @param siteId Site ID. If not defined, use current site. - * @return Resolved with the WS data. + * @return Promise resolved with the WS data. */ - getBlockedContacts(siteId?: string): Promise { + getBlockedContacts(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const userId = site.getUserId(), params = { @@ -585,19 +588,24 @@ export class AddonMessagesProvider { * This excludes the blocked users. * * @param siteId Site ID. If not defined, use current site. - * @return Resolved with the WS data. + * @return Promise resolved with the WS data. * @deprecated since Moodle 3.6 */ - getContacts(siteId?: string): Promise { + getContacts(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const preSets = { cacheKey: this.getCacheKeyForContacts(), updateFrequency: CoreSite.FREQUENCY_OFTEN }; - return site.read('core_message_get_contacts', undefined, preSets).then((contacts) => { + return site.read('core_message_get_contacts', undefined, preSets).then((contacts: AddonMessagesGetContactsResult) => { // Filter contacts with negative ID, they are notifications. - const validContacts = {}; + const validContacts: AddonMessagesGetContactsResult = { + online: [], + offline: [], + strangers: [] + }; + for (const typeName in contacts) { if (!validContacts[typeName]) { validContacts[typeName] = []; @@ -621,11 +629,11 @@ export class AddonMessagesProvider { * @param limitFrom Position of the first contact to fetch. * @param limitNum Number of contacts to fetch. Default is AddonMessagesProvider.LIMIT_CONTACTS. * @param siteId Site ID. If not defined, use current site. - * @return Resolved with the list of user contacts. + * @return Promise resolved with the list of user contacts. * @since 3.6 */ getUserContacts(limitFrom: number = 0, limitNum: number = AddonMessagesProvider.LIMIT_CONTACTS , siteId?: string): - Promise<{contacts: any[], canLoadMore: boolean}> { + Promise<{contacts: AddonMessagesConversationMember[], canLoadMore: boolean}> { return this.sitesProvider.getSite(siteId).then((site) => { const params = { @@ -638,7 +646,9 @@ export class AddonMessagesProvider { updateFrequency: CoreSite.FREQUENCY_OFTEN }; - return site.read('core_message_get_user_contacts', params, preSets).then((contacts) => { + return site.read('core_message_get_user_contacts', params, preSets) + .then((contacts: AddonMessagesConversationMember[]) => { + if (!contacts || !contacts.length) { return { contacts: [], canLoadMore: false }; } @@ -663,11 +673,11 @@ export class AddonMessagesProvider { * @param limitFrom Position of the first contact request to fetch. * @param limitNum Number of contact requests to fetch. Default is AddonMessagesProvider.LIMIT_CONTACTS. * @param siteId Site ID. If not defined, use current site. - * @return Resolved with the list of contact requests. + * @return Promise resolved with the list of contact requests. * @since 3.6 */ getContactRequests(limitFrom: number = 0, limitNum: number = AddonMessagesProvider.LIMIT_CONTACTS, siteId?: string): - Promise<{requests: any[], canLoadMore: boolean}> { + Promise<{requests: AddonMessagesConversationMember[], canLoadMore: boolean}> { return this.sitesProvider.getSite(siteId).then((site) => { const data = { @@ -680,7 +690,9 @@ export class AddonMessagesProvider { updateFrequency: CoreSite.FREQUENCY_OFTEN }; - return site.read('core_message_get_contact_requests', data, preSets).then((requests) => { + return site.read('core_message_get_contact_requests', data, preSets) + .then((requests: AddonMessagesConversationMember[]) => { + if (!requests || !requests.length) { return { requests: [], canLoadMore: false }; } @@ -716,7 +728,7 @@ export class AddonMessagesProvider { typeExpected: 'number' }; - return site.read('core_message_get_received_contact_requests_count', data, preSets).then((count) => { + return site.read('core_message_get_received_contact_requests_count', data, preSets).then((count: number) => { // Notify the new count so all badges are updated. this.eventsProvider.trigger(AddonMessagesProvider.CONTACT_REQUESTS_COUNT_EVENT, { count }, site.id); @@ -745,7 +757,7 @@ export class AddonMessagesProvider { */ getConversation(conversationId: number, includeContactRequests?: boolean, includePrivacyInfo?: boolean, messageOffset: number = 0, messageLimit: number = 1, memberOffset: number = 0, memberLimit: number = 2, - newestFirst: boolean = true, siteId?: string, userId?: number): Promise { + newestFirst: boolean = true, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { userId = userId || site.getUserId(); @@ -765,7 +777,7 @@ export class AddonMessagesProvider { newestmessagesfirst: newestFirst ? 1 : 0 }; - return site.read('core_message_get_conversation', params, preSets).then((conversation) => { + return site.read('core_message_get_conversation', params, preSets).then((conversation: AddonMessagesConversation) => { return this.formatConversation(conversation, userId); }); }); @@ -792,7 +804,8 @@ export class AddonMessagesProvider { */ getConversationBetweenUsers(otherUserId: number, includeContactRequests?: boolean, includePrivacyInfo?: boolean, messageOffset: number = 0, messageLimit: number = 1, memberOffset: number = 0, memberLimit: number = 2, - newestFirst: boolean = true, siteId?: string, userId?: number, preferCache?: boolean): Promise { + newestFirst: boolean = true, siteId?: string, userId?: number, preferCache?: boolean) + : Promise { return this.sitesProvider.getSite(siteId).then((site) => { userId = userId || site.getUserId(); @@ -813,7 +826,8 @@ export class AddonMessagesProvider { newestmessagesfirst: newestFirst ? 1 : 0 }; - return site.read('core_message_get_conversation_between_users', params, preSets).then((conversation) => { + return site.read('core_message_get_conversation_between_users', params, preSets) + .then((conversation: AddonMessagesConversation) => { return this.formatConversation(conversation, userId); }); }); @@ -826,12 +840,11 @@ export class AddonMessagesProvider { * @param limitFrom Offset for members list. * @param limitTo Limit of members. * @param siteId Site ID. If not defined, use current site. - * @param userId User ID. If not defined, current user in the site. - * @return Promise resolved with the response. + * @param userId User ID. If not defined, current user in * @since 3.6 */ getConversationMembers(conversationId: number, limitFrom: number = 0, limitTo?: number, includeContactRequests?: boolean, - siteId?: string, userId?: number): Promise { + siteId?: string, userId?: number): Promise<{members: AddonMessagesConversationMember[], canLoadMore: boolean}> { return this.sitesProvider.getSite(siteId).then((site) => { userId = userId || site.getUserId(); @@ -853,18 +866,21 @@ export class AddonMessagesProvider { includeprivacyinfo: 1, }; - return site.read('core_message_get_conversation_members', params, preSets).then((members) => { - const result: any = {}; + return site.read('core_message_get_conversation_members', params, preSets) + .then((members: AddonMessagesConversationMember[]) => { if (limitTo < 1) { - result.canLoadMore = false; - result.members = members; + return { + canLoadMore: false, + members: members + }; } else { - result.canLoadMore = members.length > limitTo; - result.members = members.slice(0, limitTo); + return { + canLoadMore: members.length > limitTo, + members: members.slice(0, limitTo) + }; } - return result; }); }); } @@ -884,7 +900,8 @@ export class AddonMessagesProvider { * @since 3.6 */ getConversationMessages(conversationId: number, excludePending: boolean, limitFrom: number = 0, limitTo?: number, - newestFirst: boolean = true, timeFrom: number = 0, siteId?: string, userId?: number): Promise { + newestFirst: boolean = true, timeFrom: number = 0, siteId?: string, userId?: number) + : Promise { return this.sitesProvider.getSite(siteId).then((site) => { userId = userId || site.getUserId(); @@ -913,7 +930,9 @@ export class AddonMessagesProvider { preSets['emergencyCache'] = false; } - return site.read('core_message_get_conversation_messages', params, preSets).then((result) => { + return site.read('core_message_get_conversation_messages', params, preSets) + .then((result: AddonMessagesGetConversationMessagesResult) => { + if (limitTo < 1) { result.canLoadMore = false; result.messages = result.messages; @@ -975,7 +994,8 @@ export class AddonMessagesProvider { * @since 3.6 */ getConversations(type?: number, favourites?: boolean, limitFrom: number = 0, siteId?: string, userId?: number, - forceCache?: boolean, ignoreCache?: boolean): Promise<{conversations: any[], canLoadMore: boolean}> { + forceCache?: boolean, ignoreCache?: boolean) + : Promise<{conversations: AddonMessagesConversationFormatted[], canLoadMore: boolean}> { return this.sitesProvider.getSite(siteId).then((site) => { userId = userId || site.getUserId(); @@ -1017,7 +1037,7 @@ export class AddonMessagesProvider { } return Promise.reject(error); - }).then((response) => { + }).then((response: AddonMessagesGetConversationsResult) => { // Format the conversations, adding some calculated fields. const conversations = response.conversations.slice(0, this.LIMIT_MESSAGES).map((conversation) => { return this.formatConversation(conversation, userId); @@ -1053,7 +1073,9 @@ export class AddonMessagesProvider { cacheKey: this.getCacheKeyForConversationCounts() }; - return site.read('core_message_get_conversation_counts', {}, preSets).then((result) => { + return site.read('core_message_get_conversation_counts', {}, preSets) + .then((result: AddonMessagesGetConversationCountsResult) => { + const counts = { favourites: result.favourites, individual: result.types[AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_INDIVIDUAL], @@ -1080,10 +1102,14 @@ export class AddonMessagesProvider { * @return Promise resolved with messages and a boolean telling if can load more messages. */ getDiscussion(userId: number, excludePending: boolean, lfReceivedUnread: number = 0, lfReceivedRead: number = 0, - lfSentUnread: number = 0, lfSentRead: number = 0, toDisplay: boolean = true, siteId?: string): Promise { + lfSentUnread: number = 0, lfSentRead: number = 0, toDisplay: boolean = true, siteId?: string) + : Promise<{messages: AddonMessagesGetMessagesMessage[], canLoadMore: boolean}> { return this.sitesProvider.getSite(siteId).then((site) => { - const result = {}, + const result = { + messages: [], + canLoadMore: false + }, preSets = { cacheKey: this.getCacheKeyForDiscussion(userId) }, @@ -1107,7 +1133,7 @@ export class AddonMessagesProvider { // Get message received by current user. return this.getRecentMessages(params, preSets, lfReceivedUnread, lfReceivedRead, toDisplay, site.getId()) .then((response) => { - result['messages'] = response; + result.messages = response; params.useridto = userId; params.useridfrom = site.getUserId(); hasReceived = response.length > 0; @@ -1115,16 +1141,16 @@ export class AddonMessagesProvider { // Get message sent by current user. return this.getRecentMessages(params, preSets, lfSentUnread, lfSentRead, toDisplay, siteId); }).then((response) => { - result['messages'] = result['messages'].concat(response); + result.messages = result.messages.concat(response); hasSent = response.length > 0; - if (result['messages'].length > this.LIMIT_MESSAGES) { + if (result.messages.length > this.LIMIT_MESSAGES) { // Sort messages and get the more recent ones. - result['canLoadMore'] = true; - result['messages'] = this.sortMessages(result['messages']); - result['messages'] = result['messages'].slice(-this.LIMIT_MESSAGES); + result.canLoadMore = true; + result.messages = this.sortMessages(result['messages']); + result.messages = result.messages.slice(-this.LIMIT_MESSAGES); } else { - result['canLoadMore'] = result['messages'].length == this.LIMIT_MESSAGES && (!hasReceived || !hasSent); + result.canLoadMore = result.messages.length == this.LIMIT_MESSAGES && (!hasReceived || !hasSent); } if (excludePending) { @@ -1140,7 +1166,7 @@ export class AddonMessagesProvider { message.text = message.smallmessage; }); - result['messages'] = result['messages'].concat(offlineMessages); + result.messages = result.messages.concat(offlineMessages); return result; }); @@ -1153,11 +1179,11 @@ export class AddonMessagesProvider { * If the site is 3.6 or higher, please use getConversations. * * @param siteId Site ID. If not defined, current site. - * @return Resolved with an object where the keys are the user ID of the other user. + * @return Promise resolved with an object where the keys are the user ID of the other user. */ - getDiscussions(siteId?: string): Promise { + getDiscussions(siteId?: string): Promise<{[userId: number]: AddonMessagesDiscussion}> { return this.sitesProvider.getSite(siteId).then((site) => { - const discussions = {}, + const discussions: {[userId: number]: AddonMessagesDiscussion} = {}, currentUserId = site.getUserId(), params = { useridto: currentUserId, @@ -1171,7 +1197,7 @@ export class AddonMessagesProvider { /** * Convenience function to treat a recent message, adding it to discussions list if needed. */ - const treatRecentMessage = (message: any, userId: number, userFullname: string): void => { + const treatRecentMessage = (message: AddonMessagesGetMessagesMessage, userId: number, userFullname: string): void => { if (typeof discussions[userId] === 'undefined') { discussions[userId] = { fullname: userFullname, @@ -1272,7 +1298,7 @@ export class AddonMessagesProvider { * @return Promise resolved with the member info. * @since 3.6 */ - getMemberInfo(otherUserId: number, siteId?: string, userId?: number): Promise { + getMemberInfo(otherUserId: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { userId = userId || site.getUserId(); @@ -1287,7 +1313,9 @@ export class AddonMessagesProvider { includeprivacyinfo: 1, }; - return site.read('core_message_get_member_info', params, preSets).then((members) => { + return site.read('core_message_get_member_info', params, preSets) + .then((members: AddonMessagesConversationMember[]): any => { + if (!members || members.length < 1) { // Should never happen. return Promise.reject(null); @@ -1313,7 +1341,7 @@ export class AddonMessagesProvider { * @param siteId Site ID. If not defined, use current site. * @return Promise resolved with the message preferences. */ - getMessagePreferences(siteId?: string): Promise { + getMessagePreferences(siteId?: string): Promise { this.logger.debug('Get message preferences'); return this.sitesProvider.getSite(siteId).then((site) => { @@ -1322,7 +1350,9 @@ export class AddonMessagesProvider { updateFrequency: CoreSite.FREQUENCY_SOMETIMES }; - return site.read('core_message_get_user_message_preferences', {}, preSets).then((data) => { + return site.read('core_message_get_user_message_preferences', {}, preSets) + .then((data: AddonMessagesGetUserMessagePreferencesResult): any => { + if (data.preferences) { data.preferences.blocknoncontacts = data.blocknoncontacts; @@ -1341,15 +1371,18 @@ export class AddonMessagesProvider { * @param preSets Set of presets for the WS. * @param toDisplay True if messages will be displayed to the user, either in view or in a notification. * @param siteId Site ID. If not defined, use current site. + * @return Promise resolved with the data. */ - protected getMessages(params: any, preSets: any, toDisplay: boolean = true, siteId?: string): Promise { + protected getMessages(params: any, preSets: any, toDisplay: boolean = true, siteId?: string) + : Promise { + params['type'] = 'conversations'; params['newestfirst'] = 1; return this.sitesProvider.getSite(siteId).then((site) => { const userId = site.getUserId(); - return site.read('core_message_get_messages', params, preSets).then((response) => { + return site.read('core_message_get_messages', params, preSets).then((response: AddonMessagesGetMessagesResult) => { response.messages.forEach((message) => { message.read = params.read == 0 ? 0 : 1; // Convert times to milliseconds. @@ -1377,9 +1410,10 @@ export class AddonMessagesProvider { * @param limitFromRead Number of unread messages already fetched, so fetch will be done from this number. * @param toDisplay True if messages will be displayed to the user, either in view or in a notification. * @param siteId Site ID. If not defined, use current site. + * @return Promise resolved with the data. */ protected getRecentMessages(params: any, preSets: any, limitFromUnread: number = 0, limitFromRead: number = 0, - toDisplay: boolean = true, siteId?: string): Promise { + toDisplay: boolean = true, siteId?: string): Promise { limitFromUnread = limitFromUnread || 0; limitFromRead = limitFromRead || 0; @@ -1427,7 +1461,7 @@ export class AddonMessagesProvider { * @since 3.7 */ getSelfConversation(messageOffset: number = 0, messageLimit: number = 1, newestFirst: boolean = true, siteId?: string, - userId?: number): Promise { + userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { userId = userId || site.getUserId(); @@ -1442,7 +1476,8 @@ export class AddonMessagesProvider { newestmessagesfirst: newestFirst ? 1 : 0 }; - return site.read('core_message_get_self_conversation', params, preSets).then((conversation) => { + return site.read('core_message_get_self_conversation', params, preSets) + .then((conversation: AddonMessagesConversation) => { return this.formatConversation(conversation, userId); }); }); @@ -1466,7 +1501,8 @@ export class AddonMessagesProvider { cacheKey: this.getCacheKeyForUnreadConversationCounts() }; - promise = site.read('core_message_get_unread_conversation_counts', {}, preSets).then((result) => { + promise = site.read('core_message_get_unread_conversation_counts', {}, preSets) + .then((result: AddonMessagesGetUnreadConversationCountsResult) => { return { favourites: result.favourites, individual: result.types[AddonMessagesProvider.MESSAGE_CONVERSATION_TYPE_INDIVIDUAL], @@ -1485,7 +1521,7 @@ export class AddonMessagesProvider { typeExpected: 'number' }; - promise = site.read('core_message_get_unread_conversations_count', params, preSets).then((count) => { + promise = site.read('core_message_get_unread_conversations_count', params, preSets).then((count: number) => { return { favourites: 0, individual: count, group: 0, self: 0 }; }); } else { @@ -1536,7 +1572,7 @@ export class AddonMessagesProvider { * @return Promise resolved with the message unread count. */ getUnreadReceivedMessages(toDisplay: boolean = true, forceCache: boolean = false, ignoreCache: boolean = false, - siteId?: string): Promise { + siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const params = { read: 0, @@ -2049,7 +2085,7 @@ export class AddonMessagesProvider { * @param siteId Site ID. If not defined, current site. * @return Promise resolved with boolean marking success or not. */ - markMessageRead(messageId: number, siteId?: string): Promise { + markMessageRead(messageId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const params = { messageid: messageId, @@ -2067,7 +2103,7 @@ export class AddonMessagesProvider { * @return Promise resolved if success. * @since 3.6 */ - markAllConversationMessagesRead(conversationId?: number): Promise { + markAllConversationMessagesRead(conversationId?: number): Promise { const params = { userid: this.sitesProvider.getCurrentSiteUserId(), conversationid: conversationId @@ -2085,7 +2121,7 @@ export class AddonMessagesProvider { * @param userIdFrom User Id for the sender. * @return Promise resolved with boolean marking success or not. */ - markAllMessagesRead(userIdFrom?: number): Promise { + markAllMessagesRead(userIdFrom?: number): Promise { const params = { useridto: this.sitesProvider.getCurrentSiteUserId(), useridfrom: userIdFrom @@ -2217,8 +2253,9 @@ export class AddonMessagesProvider { * @param query The query string. * @param limit The number of results to return, 0 for none. * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the contacts. */ - searchContacts(query: string, limit: number = 100, siteId?: string): Promise { + searchContacts(query: string, limit: number = 100, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const data = { searchtext: query, @@ -2228,7 +2265,9 @@ export class AddonMessagesProvider { getFromCache: false // Always try to get updated data. If it fails, it will get it from cache. }; - return site.read('core_message_search_contacts', data, preSets).then((contacts) => { + return site.read('core_message_search_contacts', data, preSets) + .then((contacts: AddonMessagesSearchContactsContact[]) => { + if (limit && contacts.length > limit) { contacts = contacts.splice(0, limit); } @@ -2250,7 +2289,7 @@ export class AddonMessagesProvider { * @return Promise resolved with the results. */ searchMessages(query: string, userId?: number, limitFrom: number = 0, limitNum: number = AddonMessagesProvider.LIMIT_SEARCH, - siteId?: string): Promise<{messages: any[], canLoadMore: boolean}> { + siteId?: string): Promise<{messages: AddonMessagesMessageAreaContact[], canLoadMore: boolean}> { return this.sitesProvider.getSite(siteId).then((site) => { const params = { @@ -2263,13 +2302,15 @@ export class AddonMessagesProvider { getFromCache: false // Always try to get updated data. If it fails, it will get it from cache. }; - return site.read('core_message_data_for_messagearea_search_messages', params, preSets).then((result) => { + return site.read('core_message_data_for_messagearea_search_messages', params, preSets) + .then((result: AddonMessagesDataForMessageAreaSearchMessagesResult) => { + if (!result.contacts || !result.contacts.length) { return { messages: [], canLoadMore: false }; } - result.contacts.forEach((result) => { - result.id = result.userid; + result.contacts.forEach((contact) => { + contact.id = contact.userid; }); this.userProvider.storeUsers(result.contacts, site.id); @@ -2297,7 +2338,8 @@ export class AddonMessagesProvider { * @since 3.6 */ searchUsers(query: string, limitFrom: number = 0, limitNum: number = AddonMessagesProvider.LIMIT_SEARCH, siteId?: string): - Promise<{contacts: any[], nonContacts: any[], canLoadMoreContacts: boolean, canLoadMoreNonContacts: boolean}> { + Promise<{contacts: AddonMessagesConversationMember[], nonContacts: AddonMessagesConversationMember[], + canLoadMoreContacts: boolean, canLoadMoreNonContacts: boolean}> { return this.sitesProvider.getSite(siteId).then((site) => { const data = { @@ -2310,7 +2352,7 @@ export class AddonMessagesProvider { getFromCache: false // Always try to get updated data. If it fails, it will get it from cache. }; - return site.read('core_message_message_search_users', data, preSets).then((result) => { + return site.read('core_message_message_search_users', data, preSets).then((result: AddonMessagesSearchUsersResult) => { const contacts = result.contacts || []; const nonContacts = result.noncontacts || []; @@ -2341,7 +2383,9 @@ export class AddonMessagesProvider { * - sent (Boolean) True if message was sent to server, false if stored in device. * - message (Object) If sent=false, contains the stored message. */ - sendMessage(toUserId: number, message: string, siteId?: string): Promise { + sendMessage(toUserId: number, message: string, siteId?: string) + : Promise<{sent: boolean, message: AddonMessagesSendInstantMessagesMessage}> { + // Convenience function to store a message to be synchronized later. const storeOffline = (): Promise => { return this.messagesOffline.saveMessage(toUserId, message, siteId).then((entry) => { @@ -2395,7 +2439,7 @@ export class AddonMessagesProvider { * @param siteId Site ID. If not defined, current site. * @return Promise resolved if success, rejected if failure. */ - sendMessageOnline(toUserId: number, message: string, siteId?: string): Promise { + sendMessageOnline(toUserId: number, message: string, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); const messages = [ @@ -2430,7 +2474,7 @@ export class AddonMessagesProvider { * @return Promise resolved if success, rejected if failure. Promise resolved doesn't mean that messages * have been sent, the resolve param can contain errors for messages not sent. */ - sendMessagesOnline(messages: any, siteId?: string): Promise { + sendMessagesOnline(messages: any[], siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const data = { messages: messages @@ -2451,7 +2495,9 @@ export class AddonMessagesProvider { * - message (any) If sent=false, contains the stored message. * @since 3.6 */ - sendMessageToConversation(conversation: any, message: string, siteId?: string): Promise { + sendMessageToConversation(conversation: any, message: string, siteId?: string) + : Promise<{sent: boolean, message: AddonMessagesSendMessagesToConversationMessage}> { + // Convenience function to store a message to be synchronized later. const storeOffline = (): Promise => { return this.messagesOffline.saveConversationMessage(conversation, message, siteId).then((entry) => { @@ -2506,7 +2552,8 @@ export class AddonMessagesProvider { * @return Promise resolved if success, rejected if failure. * @since 3.6 */ - sendMessageToConversationOnline(conversationId: number, message: string, siteId?: string): Promise { + sendMessageToConversationOnline(conversationId: number, message: string, siteId?: string) + : Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); const messages = [ @@ -2534,7 +2581,9 @@ export class AddonMessagesProvider { * @return Promise resolved if success, rejected if failure. * @since 3.6 */ - sendMessagesToConversationOnline(conversationId: number, messages: any, siteId?: string): Promise { + sendMessagesToConversationOnline(conversationId: number, messages: any[], siteId?: string) + : Promise { + return this.sitesProvider.getSite(siteId).then((site) => { const params = { conversationid: conversationId, @@ -2603,10 +2652,10 @@ export class AddonMessagesProvider { * @param conversations Array of conversations. * @return Conversations sorted with most recent last. */ - sortConversations(conversations: any[]): any[] { + sortConversations(conversations: AddonMessagesConversationFormatted[]): AddonMessagesConversationFormatted[] { return conversations.sort((a, b) => { - const timeA = parseInt(a.lastmessagedate, 10), - timeB = parseInt(b.lastmessagedate, 10); + const timeA = Number(a.lastmessagedate), + timeB = Number(b.lastmessagedate); if (timeA == timeB && a.id) { // Same time, sort by ID. @@ -2651,7 +2700,9 @@ export class AddonMessagesProvider { * @param siteId Site ID. If not defined, current site. * @return Promise resolved when done. */ - protected storeLastReceivedMessageIfNeeded(convIdOrUserIdFrom: number, message: any, siteId?: string): Promise { + protected storeLastReceivedMessageIfNeeded(convIdOrUserIdFrom: number, + message: AddonMessagesGetMessagesMessage | AddonMessagesConversationMessage, siteId?: string): Promise { + const component = AddonMessagesProvider.PUSH_SIMULATION_COMPONENT; // Get the last received message. @@ -2675,7 +2726,7 @@ export class AddonMessagesProvider { * * @param contactTypes List of contacts grouped in types. */ - protected storeUsersFromAllContacts(contactTypes: any): void { + protected storeUsersFromAllContacts(contactTypes: AddonMessagesGetContactsResult): void { for (const x in contactTypes) { this.userProvider.storeUsers(contactTypes[x]); } @@ -2735,3 +2786,377 @@ export class AddonMessagesProvider { }); } } + +/** + * Conversation. + */ +export type AddonMessagesConversation = { + id: number; // The conversation id. + name: string; // The conversation name, if set. + subname: string; // A subtitle for the conversation name, if set. + imageurl: string; // A link to the conversation picture, if set. + type: number; // The type of the conversation (1=individual,2=group,3=self). + membercount: number; // Total number of conversation members. + ismuted: boolean; // If the user muted this conversation. + isfavourite: boolean; // If the user marked this conversation as a favourite. + isread: boolean; // If the user has read all messages in the conversation. + unreadcount: number; // The number of unread messages in this conversation. + members: AddonMessagesConversationMember[]; + messages: AddonMessagesConversationMessage[]; + candeletemessagesforallusers: boolean; // @since 3.7. If the user can delete messages in the conversation for all users. +}; + +/** + * Conversation with some calculated data. + */ +export type AddonMessagesConversationFormatted = AddonMessagesConversation & { + lastmessage?: string; // Calculated in the app. Last message. + lastmessagedate?: number; // Calculated in the app. Date the last message was sent. + sentfromcurrentuser?: boolean; // Calculated in the app. Whether last message was sent by the current user. + name?: string; // Calculated in the app. If private conversation, name of the other user. + userid?: number; // Calculated in the app. URL. If private conversation, ID of the other user. + showonlinestatus?: boolean; // Calculated in the app. If private conversation, whether to show online status of the other user. + isonline?: boolean; // Calculated in the app. If private conversation, whether the other user is online. + isblocked?: boolean; // Calculated in the app. If private conversation, whether the other user is blocked. + otherUser?: AddonMessagesConversationMember; // Calculated in the app. Other user in the conversation. +}; + +/** + * Conversation member. + */ +export type AddonMessagesConversationMember = { + id: number; // The user id. + fullname: string; // The user's name. + profileurl: string; // The link to the user's profile page. + profileimageurl: string; // User picture URL. + profileimageurlsmall: string; // Small user picture URL. + isonline: boolean; // The user's online status. + showonlinestatus: boolean; // Show the user's online status?. + isblocked: boolean; // If the user has been blocked. + iscontact: boolean; // Is the user a contact?. + isdeleted: boolean; // Is the user deleted?. + canmessageevenifblocked: boolean; // If the user can still message even if they get blocked. + canmessage: boolean; // If the user can be messaged. + requirescontact: boolean; // If the user requires to be contacts. + contactrequests?: { // The contact requests. + id: number; // The id of the contact request. + userid: number; // The id of the user who created the contact request. + requesteduserid: number; // The id of the user confirming the request. + timecreated: number; // The timecreated timestamp for the contact request. + }[]; + conversations?: { // Conversations between users. + id: number; // Conversations id. + type: number; // Conversation type: private or public. + name: string; // Multilang compatible conversation name2. + timecreated: number; // The timecreated timestamp for the conversation. + }[]; +}; + +/** + * Conversation message. + */ +export type AddonMessagesConversationMessage = { + id: number; // The id of the message. + useridfrom: number; // The id of the user who sent the message. + text: string; // The text of the message. + timecreated: number; // The timecreated timestamp for the message. +}; + +/** + * Message preferences. + */ +export type AddonMessagesMessagePreferences = { + userid: number; // User id. + disableall: number; // Whether all the preferences are disabled. + processors: { // Config form values. + 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. + }[]; + components: { // Available components. + displayname: string; // Display name. + notifications: AddonMessagesMessagePreferencesNotification[]; // List of notificaitons for the component. + }[]; +} & AddonMessagesMessagePreferencesCalculatedData; + +/** + * Notification processor in message preferences. + */ +export type AddonMessagesMessagePreferencesNotification = { + displayname: string; // Display name. + preferencekey: string; // Preference key. + processors: AddonMessagesMessagePreferencesNotificationProcessor[]; // Processors values for this notification. +}; + +/** + * Notification processor in message preferences. + */ +export type AddonMessagesMessagePreferencesNotificationProcessor = { + displayname: string; // Display name. + name: string; // Processor name. + locked: boolean; // Is locked by admin?. + lockedmessage?: string; // Text to display if locked. + userconfigured: number; // Is configured?. + loggedin: { + name: string; // Name. + displayname: string; // Display name. + checked: boolean; // Is checked?. + }; + loggedoff: { + name: string; // Name. + displayname: string; // Display name. + checked: boolean; // Is checked?. + }; +}; + +/** + * Message discussion (before 3.6). + */ +export type AddonMessagesDiscussion = { + fullname: string; // Full name of the other user in the discussion. + profileimageurl: string; // Profile image of the other user in the discussion. + message?: { // Last message. + id: number; // Message ID. + user: number; // User ID that sent the message. + message: string; // Text of the message. + timecreated: number; // Time the message was sent. + pending?: boolean; // Whether the message is pending to be sent. + }; + unread?: boolean; // Whether the discussion has unread messages. +}; + +/** + * Contact for message area. + */ +export type AddonMessagesMessageAreaContact = { + userid: number; // The user's id. + fullname: string; // The user's name. + profileimageurl: string; // User picture URL. + profileimageurlsmall: string; // Small user picture URL. + ismessaging: boolean; // If we are messaging the user. + sentfromcurrentuser: boolean; // Was the last message sent from the current user?. + lastmessage: string; // The user's last message. + lastmessagedate: number; // Timestamp for last message. + messageid: number; // The unique search message id. + showonlinestatus: boolean; // Show the user's online status?. + isonline: boolean; // The user's online status. + isread: boolean; // If the user has read the message. + isblocked: boolean; // If the user has been blocked. + unreadcount: number; // The number of unread messages in this conversation. + conversationid: number; // The id of the conversation. +} & AddonMessagesMessageAreaContactCalculatedData; + +/** + * Result of WS core_message_get_blocked_users. + */ +export type AddonMessagesGetBlockedUsersResult = { + users: AddonMessagesBlockedUser[]; // List of blocked users. + warnings?: CoreWSExternalWarning[]; +}; + +/** + * User data returned by core_message_get_blocked_users. + */ +export type AddonMessagesBlockedUser = { + id: number; // User ID. + fullname: string; // User full name. + profileimageurl?: string; // User picture URL. +}; + +/** + * Result of WS core_message_get_contacts. + */ +export type AddonMessagesGetContactsResult = { + online: AddonMessagesGetContactsContact[]; // List of online contacts. + offline: AddonMessagesGetContactsContact[]; // List of offline contacts. + strangers: AddonMessagesGetContactsContact[]; // List of users that are not in the user's contact list but have sent a message. +} & AddonMessagesGetContactsCalculatedData; + +/** + * User data returned by core_message_get_contacts. + */ +export type AddonMessagesGetContactsContact = { + id: number; // User ID. + fullname: string; // User full name. + profileimageurl?: string; // User picture URL. + profileimageurlsmall?: string; // Small user picture URL. + unread: number; // Unread message count. +}; + +/** + * User data returned by core_message_search_contacts. + */ +export type AddonMessagesSearchContactsContact = { + id: number; // User ID. + fullname: string; // User full name. + profileimageurl?: string; // User picture URL. + profileimageurlsmall?: string; // Small user picture URL. +}; + +/** + * Result of WS core_message_get_conversation_messages. + */ +export type AddonMessagesGetConversationMessagesResult = { + id: number; // The conversation id. + members: AddonMessagesConversationMember[]; + messages: AddonMessagesConversationMessage[]; +} & AddonMessagesGetConversationMessagesCalculatedData; + +/** + * Result of WS core_message_get_conversations. + */ +export type AddonMessagesGetConversationsResult = { + conversations: AddonMessagesConversation[]; +}; + +/** + * Result of WS core_message_get_conversation_counts. + */ +export type AddonMessagesGetConversationCountsResult = { + favourites: number; // Total number of favourite conversations. + types: { + 1: number; // Total number of individual conversations. + 2: number; // Total number of group conversations. + 3: number; // Total number of self conversations. + }; +}; + +/** + * Result of WS core_message_get_unread_conversation_counts. + */ +export type AddonMessagesGetUnreadConversationCountsResult = { + favourites: number; // Total number of unread favourite conversations. + types: { + 1: number; // Total number of unread individual conversations. + 2: number; // Total number of unread group conversations. + 3: number; // Total number of unread self conversations. + }; +}; + +/** + * Result of WS core_message_get_user_message_preferences. + */ +export type AddonMessagesGetUserMessagePreferencesResult = { + preferences: AddonMessagesMessagePreferences; + blocknoncontacts: number; // Privacy messaging setting to define who can message you. + entertosend: boolean; // User preference for using enter to send messages. + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Result of WS core_message_get_messages. + */ +export type AddonMessagesGetMessagesResult = { + messages: AddonMessagesGetMessagesMessage[]; + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Message data returned by core_message_get_messages. + */ +export type AddonMessagesGetMessagesMessage = { + 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; // The component that generated the notification. + eventtype?: string; // The type of notification. + customdata?: string; // Custom data to be passed to the message processor. +} & AddonMessagesGetMessagesMessageCalculatedData; + +/** + * Result of WS core_message_data_for_messagearea_search_messages. + */ +export type AddonMessagesDataForMessageAreaSearchMessagesResult = { + contacts: AddonMessagesMessageAreaContact[]; +}; + +/** + * Result of WS core_message_message_search_users. + */ +export type AddonMessagesSearchUsersResult = { + contacts: AddonMessagesConversationMember[]; + noncontacts: AddonMessagesConversationMember[]; +}; + +/** + * Result of WS core_message_mark_message_read. + */ +export type AddonMessagesMarkMessageReadResult = { + messageid: number; // The id of the message in the messages table. + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Result of WS core_message_send_instant_messages. + */ +export type AddonMessagesSendInstantMessagesMessage = { + msgid: number; // Test this to know if it succeeds: id of the created message if it succeeded, -1 when failed. + clientmsgid?: string; // Your own id for the message. + errormessage?: string; // Error message - if it failed. + text?: string; // The text of the message. + timecreated?: number; // The timecreated timestamp for the message. + conversationid?: number; // The conversation id for this message. + useridfrom?: number; // The user id who sent the message. + candeletemessagesforallusers: boolean; // If the user can delete messages in the conversation for all users. +}; + +/** + * Result of WS core_message_send_messages_to_conversation. + */ +export type AddonMessagesSendMessagesToConversationMessage = { + id: number; // The id of the message. + useridfrom: number; // The id of the user who sent the message. + text: string; // The text of the message. + timecreated: number; // The timecreated timestamp for the message. +}; + +/** + * Calculated data for core_message_get_contacts. + */ +export type AddonMessagesGetContactsCalculatedData = { + blocked?: AddonMessagesBlockedUser[]; // Calculated in the app. List of blocked users. +}; + +/** + * Calculated data for core_message_get_conversation_messages. + */ +export type AddonMessagesGetConversationMessagesCalculatedData = { + canLoadMore?: boolean; // Calculated in the app. Whether more messages can be loaded. +}; + +/** + * Calculated data for message preferences. + */ +export type AddonMessagesMessagePreferencesCalculatedData = { + blocknoncontacts?: number; // Calculated in the app. Based on the result of core_message_get_user_message_preferences. +}; + +/** + * Calculated data for messages returned by core_message_get_messages. + */ +export type AddonMessagesGetMessagesMessageCalculatedData = { + pending?: boolean; // Calculated in the app. Whether the message is pending to be sent. + read?: number; // Calculated in the app. Whether the message has been read. +}; + +/** + * Calculated data for contact for message area. + */ +export type AddonMessagesMessageAreaContactCalculatedData = { + id?: number; // Calculated in the app. User ID. +}; diff --git a/src/addon/messages/providers/sync.ts b/src/addon/messages/providers/sync.ts index 6c7d33886..20a3a27c1 100644 --- a/src/addon/messages/providers/sync.ts +++ b/src/addon/messages/providers/sync.ts @@ -18,7 +18,7 @@ import { CoreSitesProvider } from '@providers/sites'; import { CoreSyncBaseProvider } from '@classes/base-sync'; import { CoreAppProvider } from '@providers/app'; import { AddonMessagesOfflineProvider } from './messages-offline'; -import { AddonMessagesProvider } from './messages'; +import { AddonMessagesProvider, AddonMessagesConversationFormatted } from './messages'; import { CoreUserProvider } from '@core/user/providers/user'; import { CoreEventsProvider } from '@providers/events'; import { CoreTextUtilsProvider } from '@providers/utils/text'; @@ -258,7 +258,7 @@ export class AddonMessagesSyncProvider extends CoreSyncBaseProvider { // Get conversation name and add errors to warnings array. return this.messagesProvider.getConversation(conversationId, false, false).catch(() => { // Ignore errors. - return {}; + return {}; }).then((conversation) => { errors.forEach((error) => { warnings.push(this.translate.instant('addon.messages.warningconversationmessagenotsent', { diff --git a/src/addon/notes/components/list/list.ts b/src/addon/notes/components/list/list.ts index 2011957ed..c05cbbc05 100644 --- a/src/addon/notes/components/list/list.ts +++ b/src/addon/notes/components/list/list.ts @@ -21,7 +21,7 @@ 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 +44,7 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { type = 'course'; refreshIcon = 'spinner'; syncIcon = 'spinner'; - notes: any[]; + notes: AddonNotesNoteFormatted[]; hasOffline = false; notesLoaded = false; user: any; @@ -101,21 +101,21 @@ 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) => { + return this.notesProvider.setOfflineDeletedNotes(notesList, this.courseId).then((notesList) => { - this.hasOffline = notes.some((note) => note.offline || note.deleted); + 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 +126,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; }); } @@ -178,6 +178,7 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { 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,6 +193,7 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { this.typeChanged(); } }); + modal.present(); } @@ -201,7 +203,7 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { * @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(); @@ -226,7 +228,7 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { * @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(); diff --git a/src/addon/notes/providers/notes.ts b/src/addon/notes/providers/notes.ts index 3753f9404..ff89faaad 100644 --- a/src/addon/notes/providers/notes.ts +++ b/src/addon/notes/providers/notes.ts @@ -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. @@ -119,9 +120,9 @@ export class AddonNotesProvider { * @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) => { @@ -142,7 +143,7 @@ export class AddonNotesProvider { * @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) { @@ -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. @@ -288,7 +289,9 @@ export class AddonNotesProvider { * @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; } @@ -339,9 +342,11 @@ export class AddonNotesProvider { * @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 [description] + * @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); @@ -358,7 +363,7 @@ export class AddonNotesProvider { * @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) => { @@ -400,7 +405,7 @@ export class AddonNotesProvider { * @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; // Whether the user can manage notes at system level. + canmanagecoursenotes?: boolean; // 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/notifications/pages/list/list.ts b/src/addon/notifications/pages/list/list.ts index db9153bd8..6a38c3a11 100644 --- a/src/addon/notifications/pages/list/list.ts +++ b/src/addon/notifications/pages/list/list.ts @@ -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; @@ -130,11 +130,12 @@ export class AddonNotificationsListPage { * * @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(); @@ -202,7 +203,7 @@ export class AddonNotificationsListPage { * * @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.ts b/src/addon/notifications/pages/settings/settings.ts index 5754f18a2..e4ca15b6f 100644 --- a/src/addon/notifications/pages/settings/settings.ts +++ b/src/addon/notifications/pages/settings/settings.ts @@ -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; @@ -99,7 +103,7 @@ export class AddonNotificationsSettingsPage implements OnDestroy { // 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)); @@ -118,7 +122,7 @@ export class AddonNotificationsSettingsPage implements OnDestroy { * * @param processor Processor object. */ - protected loadProcessor(processor: any): void { + protected loadProcessor(processor: AddonNotificationsNotificationPreferencesProcessorFormatted): void { if (!processor) { return; } @@ -191,8 +195,9 @@ export class AddonNotificationsSettingsPage implements OnDestroy { * @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(); @@ -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/helper.ts b/src/addon/notifications/providers/helper.ts index 442a0b1e1..fce881a1e 100644 --- a/src/addon/notifications/providers/helper.ts +++ b/src/addon/notifications/providers/helper.ts @@ -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. @@ -37,7 +39,7 @@ export class AddonNotificationsHelperProvider { * @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/notifications.ts b/src/addon/notifications/providers/notifications.ts index 6e0af0129..02093823b 100644 --- a/src/addon/notifications/providers/notifications.ts +++ b/src/addon/notifications/providers/notifications.ts @@ -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. @@ -51,14 +52,13 @@ export class AddonNotificationsProvider { * @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; } @@ -117,7 +117,7 @@ export class AddonNotificationsProvider { * @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; }); }); @@ -154,7 +156,7 @@ export class AddonNotificationsProvider { * @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; @@ -209,7 +211,7 @@ export class AddonNotificationsProvider { * @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. @@ -269,7 +271,7 @@ export class AddonNotificationsProvider { * @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); } @@ -285,7 +287,7 @@ export class AddonNotificationsProvider { * @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); } @@ -349,7 +351,7 @@ export class AddonNotificationsProvider { * @return Resolved when done. * @since 3.2 */ - markAllNotificationsAsRead(): Promise { + markAllNotificationsAsRead(): Promise { const params = { useridto: this.sitesProvider.getCurrentSiteUserId() }; @@ -362,10 +364,12 @@ export class AddonNotificationsProvider { * * @param notificationId ID of notification to mark as read * @param siteId Site ID. If not defined, current site. - * @return Resolved when done. + * @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')) { @@ -436,3 +440,179 @@ export class AddonNotificationsProvider { 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. +}; + +/** + * 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; // 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/core/comments/providers/comments.ts b/src/core/comments/providers/comments.ts index 24475b69a..3e2e8c075 100644 --- a/src/core/comments/providers/comments.ts +++ b/src/core/comments/providers/comments.ts @@ -407,3 +407,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; // 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/course/providers/course.ts b/src/core/course/providers/course.ts index 1b1af7c17..7bbc92983 100644 --- a/src/core/course/providers/course.ts +++ b/src/core/course/providers/course.ts @@ -1135,3 +1135,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; // Summary. + summaryformat: number; // Summary format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). + startdate: number; // Startdate. + enddate: number; // Enddate. + visible: boolean; // Visible. + fullnamedisplay: string; // Fullnamedisplay. + viewurl: string; // Viewurl. + courseimage: string; // Courseimage. + progress?: number; // Progress. + hasprogress: boolean; // Hasprogress. + isfavourite: boolean; // Isfavourite. + hidden: boolean; // Hidden. + timeaccess?: number; // Timeaccess. + showshortname: boolean; // Showshortname. + coursecategory: string; // 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/tag/providers/tag.ts b/src/core/tag/providers/tag.ts index 4b012d32f..0f993914a 100644 --- a/src/core/tag/providers/tag.ts +++ b/src/core/tag/providers/tag.ts @@ -17,74 +17,6 @@ import { TranslateService } from '@ngx-translate/core'; import { CoreSitesProvider } from '@providers/sites'; import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; -/** - * Structure of a tag cloud returned by WS. - */ -export interface CoreTagCloud { - tags: CoreTagCloudTag[]; - tagscount: number; - totalcount: number; -} - -/** - * Structure of a tag cloud tag returned by WS. - */ -export interface CoreTagCloudTag { - name: string; - viewurl: string; - flag: boolean; - isstandard: boolean; - count: number; - size: number; -} - -/** - * Structure of a tag collection returned by WS. - */ -export interface CoreTagCollection { - id: number; - name: string; - isdefault: boolean; - component: string; - sortoder: number; - searchable: boolean; - customurl: string; -} - -/** - * Structure of a tag index returned by WS. - */ -export interface CoreTagIndex { - tagid: number; - ta: number; - component: string; - itemtype: string; - nextpageurl: string; - prevpageurl: string; - exclusiveurl: string; - exclusivetext: string; - title: string; - content: string; - hascontent: number; - anchor: string; -} - -/** - * Structure of a tag item returned by WS. - */ -export interface CoreTagItem { - id: number; - name: string; - rawname: string; - isstandard: boolean; - tagcollid: number; - taginstanceid: number; - taginstancecontextid: number; - itemid: number; - ordering: number; - flag: number; -} - /** * Service to handle tags. */ @@ -343,3 +275,71 @@ export class CoreTagProvider { + contextId + ':' + (recursive ? 1 : 0); } } + +/** + * Structure of a tag cloud returned by WS. + */ +export type CoreTagCloud = { + tags: CoreTagCloudTag[]; + tagscount: number; + totalcount: number; +}; + +/** + * Structure of a tag cloud tag returned by WS. + */ +export type CoreTagCloudTag = { + name: string; + viewurl: string; + flag: boolean; + isstandard: boolean; + count: number; + size: number; +}; + +/** + * Structure of a tag collection returned by WS. + */ +export type CoreTagCollection = { + id: number; + name: string; + isdefault: boolean; + component: string; + sortoder: number; + searchable: boolean; + customurl: string; +}; + +/** + * Structure of a tag index returned by WS. + */ +export type CoreTagIndex = { + tagid: number; + ta: number; + component: string; + itemtype: string; + nextpageurl: string; + prevpageurl: string; + exclusiveurl: string; + exclusivetext: string; + title: string; + content: string; + hascontent: number; + anchor: string; +}; + +/** + * Structure of a tag item returned by WS. + */ +export type CoreTagItem = { + id: number; + name: string; + rawname: string; + isstandard: boolean; + tagcollid: number; + taginstanceid: number; + taginstancecontextid: number; + itemid: number; + ordering: number; + flag: number; +}; diff --git a/src/core/user/providers/user.ts b/src/core/user/providers/user.ts index 7529b0b90..1541cecf5 100644 --- a/src/core/user/providers/user.ts +++ b/src/core/user/providers/user.ts @@ -634,3 +634,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/providers/ws.ts b/src/providers/ws.ts index cb8aa6eed..4bd4ac412 100644 --- a/src/providers/ws.ts +++ b/src/providers/ws.ts @@ -81,126 +81,6 @@ export interface CoreWSAjaxPreSets { useGet?: boolean; } -/** - * 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. - * @type {string} - */ - item?: string; - - /** - * Item id. - * @type {number} - */ - itemid?: number; - - /** - * The warning code can be used by the client app to implement specific behaviour. - * @type {string} - */ - warningcode: string; - - /** - * Untranslated english message to explain the warning. - * @type {string} - */ - message: string; - -}; - -/** - * Structure of files returned by WS. - */ -export type CoreWSExternalFile = { - /** - * File name. - * @type {string} - */ - filename?: string; - - /** - * File path. - * @type {string} - */ - filepath?: string; - - /** - * File size. - * @type {number} - */ - filesize?: number; - - /** - * Downloadable file url. - * @type {string} - */ - fileurl?: string; - - /** - * Time modified. - * @type {number} - */ - timemodified?: number; - - /** - * File mime type. - * @type {string} - */ - mimetype?: string; - - /** - * Whether is an external file. - * @type {number} - */ - isexternalfile?: number; - - /** - * The repository type for external files. - * @type {string} - */ - repositorytype?: string; - -}; - /** * This service allows performing WS calls and download/upload files. */ @@ -948,3 +828,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. +}; From 5699c9b73896667ab4ee8b37cc8d1f5517531faa Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 3 Sep 2019 16:50:53 +0200 Subject: [PATCH 014/257] MOBILE-3109 badges: Fix badge alignment renamed in WS --- .../pages/issued-badge/issued-badge.html | 8 ++++---- src/addon/badges/providers/badges.ts | 20 ++++++++++++++++--- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/src/addon/badges/pages/issued-badge/issued-badge.html b/src/addon/badges/pages/issued-badge/issued-badge.html index 565993bec..0dcc1e810 100644 --- a/src/addon/badges/pages/issued-badge/issued-badge.html +++ b/src/addon/badges/pages/issued-badge/issued-badge.html @@ -172,14 +172,14 @@ - +

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

- -

+
+

- +

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

diff --git a/src/addon/badges/providers/badges.ts b/src/addon/badges/providers/badges.ts index 6cf3a29bf..1bfe00eb6 100644 --- a/src/addon/badges/providers/badges.ts +++ b/src/addon/badges/providers/badges.ts @@ -86,8 +86,13 @@ 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) => { 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; + }); + return response.badges; } else { return Promise.reject(null); @@ -167,7 +172,7 @@ export type AddonBadgesUserBadge = { claimcomment: string; // Claim comment. dateissued: number; // Date issued. }; - alignment: { // @since 3.6. Badge alignments. + 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. @@ -176,7 +181,16 @@ export type AddonBadgesUserBadge = { targetFramework?: string; // Target framework. targetCode?: string; // Target code. }[]; - relatedbadges: { // @since 3.6. Related badges. + 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. From f80dd0c2417a4e259e7294472a1613e0f548bd46 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 4 Sep 2019 07:38:48 +0200 Subject: [PATCH 015/257] MOBILE-3109 scripts: Add script to detect changes in WS --- scripts/get_ws_changes.php | 99 ++++++++++++++++++++++++++++++ scripts/get_ws_structure.php | 55 +++++++++++++++++ scripts/get_ws_ts.php | 15 ++--- scripts/ws_to_ts_functions.php | 106 +++++++++++++++++++++++++++++++++ 4 files changed, 264 insertions(+), 11 deletions(-) create mode 100644 scripts/get_ws_changes.php create mode 100644 scripts/get_ws_structure.php 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 index 4c93260ec..900a153b8 100644 --- a/scripts/get_ws_ts.php +++ b/scripts/get_ws_ts.php @@ -20,8 +20,8 @@ * 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 boolean: true to convert the params structure, - * false to convert the returns structure. Defaults to false. + * 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])) { @@ -46,23 +46,16 @@ require($moodlepath . '/config.php'); require($CFG->dirroot . '/webservice/lib.php'); require_once('ws_to_ts_functions.php'); -// 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); -} +$structure = get_ws_structure($wsname, $useparams); -if (!isset($functiondescs[$wsname])) { +if ($structure === false) { echo "ERROR: The WS wasn't found in this Moodle installation.\n"; die(); } if ($useparams) { - $structure = $functiondescs[$wsname]->parameters_desc; $description = "Params of WS $wsname."; } else { - $structure = $functiondescs[$wsname]->returns_desc; $description = "Result of WS $wsname."; } diff --git a/scripts/ws_to_ts_functions.php b/scripts/ws_to_ts_functions.php index c6eb60332..ca05d2082 100644 --- a/scripts/ws_to_ts_functions.php +++ b/scripts/ws_to_ts_functions.php @@ -18,6 +18,28 @@ * 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. */ @@ -136,3 +158,87 @@ function convert_to_ts($key, $value, $boolisnumber = false, $indentation = '', $ 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); + } +} From 346c8a42ff8649a56da69321a543a073ad9f373f Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 4 Sep 2019 17:19:57 +0200 Subject: [PATCH 016/257] MOBILE-3109 addon: Add 'since' tags in WS return types --- src/addon/badges/providers/badges.ts | 2 +- src/addon/calendar/providers/calendar.ts | 28 ++++++++-------- src/addon/competency/providers/competency.ts | 12 +++---- src/addon/messages/providers/messages.ts | 32 +++++++++---------- src/addon/notes/providers/notes.ts | 4 +-- .../notifications/providers/notifications.ts | 2 +- src/core/comments/providers/comments.ts | 2 +- src/core/course/providers/course.ts | 28 ++++++++-------- 8 files changed, 55 insertions(+), 55 deletions(-) diff --git a/src/addon/badges/providers/badges.ts b/src/addon/badges/providers/badges.ts index 1bfe00eb6..79f0598ad 100644 --- a/src/addon/badges/providers/badges.ts +++ b/src/addon/badges/providers/badges.ts @@ -86,7 +86,7 @@ export class AddonBadgesProvider { updateFrequency: CoreSite.FREQUENCY_RARELY }; - return site.read('core_badges_get_user_badges', data, preSets).then((response: AddonBadgesGetUserBadgesResult) => { + 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) => { diff --git a/src/addon/calendar/providers/calendar.ts b/src/addon/calendar/providers/calendar.ts index eb45fe61a..794e86796 100644 --- a/src/addon/calendar/providers/calendar.ts +++ b/src/addon/calendar/providers/calendar.ts @@ -1906,7 +1906,7 @@ export type AddonCalendarEventBase = { name: string; // Name. description?: string; // Description. descriptionformat: number; // Description format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). - location?: string; // Location. + location?: string; // @since 3.6. Location. categoryid?: number; // Categoryid. groupid?: number; // Groupid. userid?: number; // Userid. @@ -1947,17 +1947,17 @@ export type AddonCalendarEventBase = { summaryformat: number; // Summary format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). startdate: number; // Startdate. enddate: number; // Enddate. - visible: boolean; // Visible. + visible: boolean; // @since 3.8. Visible. fullnamedisplay: string; // Fullnamedisplay. viewurl: string; // Viewurl. - courseimage: string; // Courseimage. - progress?: number; // Progress. - hasprogress: boolean; // Hasprogress. - isfavourite: boolean; // Isfavourite. - hidden: boolean; // Hidden. - timeaccess?: number; // Timeaccess. - showshortname: boolean; // Showshortname. - coursecategory: string; // Coursecategory. + 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. @@ -1974,8 +1974,8 @@ export type AddonCalendarEventBase = { iscourseevent: boolean; // Iscourseevent. iscategoryevent: boolean; // Iscategoryevent. groupname?: string; // Groupname. - normalisedeventtype: string; // Normalisedeventtype. - normalisedeventtypetext: string; // Normalisedeventtypetext. + normalisedeventtype: string; // @since 3.7. Normalisedeventtype. + normalisedeventtypetext: string; // @since 3.7. Normalisedeventtypetext. }; /** @@ -2047,7 +2047,7 @@ export type AddonCalendarMonth = { date: CoreWSDate; periodname: string; // Periodname. includenavigation: boolean; // Includenavigation. - initialeventsloaded: boolean; // Initialeventsloaded. + initialeventsloaded: boolean; // @since 3.5. Initialeventsloaded. previousperiod: CoreWSDate; previousperiodlink: string; // Previousperiodlink. previousperiodname: string; // Previousperiodname. @@ -2121,7 +2121,7 @@ export type AddonCalendarUpcoming = { courseid: number; // Courseid. categoryid?: number; // Categoryid. isloggedin: boolean; // Isloggedin. - date: CoreWSDate; // Date. + date: CoreWSDate; // @since 3.8. Date. }; /** diff --git a/src/addon/competency/providers/competency.ts b/src/addon/competency/providers/competency.ts index 5e4940dcc..9be1e7068 100644 --- a/src/addon/competency/providers/competency.ts +++ b/src/addon/competency/providers/competency.ts @@ -717,7 +717,7 @@ export type AddonCompetencyPath = { framework: AddonCompetencyPathNode; pluginbaseurl: string; // Pluginbaseurl. pagecontextid: number; // Pagecontextid. - showlinks: boolean; // Showlinks. + showlinks: boolean; // @since 3.7. Showlinks. }; /** @@ -816,7 +816,7 @@ export type AddonCompetencySummary = { scaleconfiguration: string; // Scaleconfiguration. taxonomyterm: string; // Taxonomyterm. comppath: AddonCompetencyPath; - pluginbaseurl: string; // Pluginbaseurl. + pluginbaseurl: string; // @since 3.7. Pluginbaseurl. }; /** @@ -891,8 +891,8 @@ export type AddonCompetencyUserCompetencySummaryInCourse = { usercompetencysummary: AddonCompetencyUserCompetencySummary; course: CoreCourseSummary; coursemodules: CoreCourseModuleSummary[]; // Coursemodules. - plans: AddonCompetencyPlan[]; // Plans. - pluginbaseurl: string; // Pluginbaseurl. + plans: AddonCompetencyPlan[]; // @since 3.7. Plans. + pluginbaseurl: string; // @since 3.7. Pluginbaseurl. }; /** @@ -986,7 +986,7 @@ export type AddonCompetencyDataForCourseCompetenciesPageResult = { statistics: AddonCompetencyCourseCompetencyStatistics; competencies: AddonCompetencyDataForCourseCompetenciesPageCompetency[]; manageurl: string; // Url to the manage competencies page. - pluginbaseurl: string; // Url to the course competencies page. + pluginbaseurl: string; // @since 3.6. Url to the course competencies page. }; /** @@ -1003,5 +1003,5 @@ export type AddonCompetencyDataForCourseCompetenciesPageCompetency = { selected: boolean; // If this is the currently selected option. }[]; comppath: AddonCompetencyPath; - plans: AddonCompetencyPlan[]; + plans: AddonCompetencyPlan[]; // @since 3.7. }; diff --git a/src/addon/messages/providers/messages.ts b/src/addon/messages/providers/messages.ts index 12c482b94..7b7d19447 100644 --- a/src/addon/messages/providers/messages.ts +++ b/src/addon/messages/providers/messages.ts @@ -2797,7 +2797,7 @@ export type AddonMessagesConversation = { imageurl: string; // A link to the conversation picture, if set. type: number; // The type of the conversation (1=individual,2=group,3=self). membercount: number; // Total number of conversation members. - ismuted: boolean; // If the user muted this conversation. + ismuted: boolean; // @since 3.7. If the user muted this conversation. isfavourite: boolean; // If the user marked this conversation as a favourite. isread: boolean; // If the user has read all messages in the conversation. unreadcount: number; // The number of unread messages in this conversation. @@ -2835,7 +2835,7 @@ export type AddonMessagesConversationMember = { isblocked: boolean; // If the user has been blocked. iscontact: boolean; // Is the user a contact?. isdeleted: boolean; // Is the user deleted?. - canmessageevenifblocked: boolean; // If the user can still message even if they get blocked. + canmessageevenifblocked: boolean; // @since 3.8. If the user can still message even if they get blocked. canmessage: boolean; // If the user can be messaged. requirescontact: boolean; // If the user requires to be contacts. contactrequests?: { // The contact requests. @@ -2897,7 +2897,7 @@ export type AddonMessagesMessagePreferencesNotificationProcessor = { displayname: string; // Display name. name: string; // Processor name. locked: boolean; // Is locked by admin?. - lockedmessage?: string; // Text to display if locked. + lockedmessage?: string; // @since 3.6. Text to display if locked. userconfigured: number; // Is configured?. loggedin: { name: string; // Name. @@ -2938,14 +2938,14 @@ export type AddonMessagesMessageAreaContact = { ismessaging: boolean; // If we are messaging the user. sentfromcurrentuser: boolean; // Was the last message sent from the current user?. lastmessage: string; // The user's last message. - lastmessagedate: number; // Timestamp for last message. + lastmessagedate: number; // @since 3.6. Timestamp for last message. messageid: number; // The unique search message id. showonlinestatus: boolean; // Show the user's online status?. isonline: boolean; // The user's online status. isread: boolean; // If the user has read the message. isblocked: boolean; // If the user has been blocked. unreadcount: number; // The number of unread messages in this conversation. - conversationid: number; // The id of the conversation. + conversationid: number; // @since 3.6. The id of the conversation. } & AddonMessagesMessageAreaContactCalculatedData; /** @@ -3019,7 +3019,7 @@ export type AddonMessagesGetConversationCountsResult = { types: { 1: number; // Total number of individual conversations. 2: number; // Total number of group conversations. - 3: number; // Total number of self conversations. + 3: number; // @since 3.7. Total number of self conversations. }; }; @@ -3031,7 +3031,7 @@ export type AddonMessagesGetUnreadConversationCountsResult = { types: { 1: number; // Total number of unread individual conversations. 2: number; // Total number of unread group conversations. - 3: number; // Total number of unread self conversations. + 3: number; // @since 3.7. Total number of unread self conversations. }; }; @@ -3041,7 +3041,7 @@ export type AddonMessagesGetUnreadConversationCountsResult = { export type AddonMessagesGetUserMessagePreferencesResult = { preferences: AddonMessagesMessagePreferences; blocknoncontacts: number; // Privacy messaging setting to define who can message you. - entertosend: boolean; // User preference for using enter to send messages. + entertosend: boolean; // @since 3.6. User preference for using enter to send messages. warnings?: CoreWSExternalWarning[]; }; @@ -3073,9 +3073,9 @@ export type AddonMessagesGetMessagesMessage = { timeread: number; // Time read. usertofullname: string; // User to full name. userfromfullname: string; // User from full name. - component?: string; // The component that generated the notification. - eventtype?: string; // The type of notification. - customdata?: string; // Custom data to be passed to the message processor. + component?: string; // @since 3.7. The component that generated the notification. + eventtype?: string; // @since 3.7. The type of notification. + customdata?: string; // @since 3.7. Custom data to be passed to the message processor. } & AddonMessagesGetMessagesMessageCalculatedData; /** @@ -3108,11 +3108,11 @@ export type AddonMessagesSendInstantMessagesMessage = { msgid: number; // Test this to know if it succeeds: id of the created message if it succeeded, -1 when failed. clientmsgid?: string; // Your own id for the message. errormessage?: string; // Error message - if it failed. - text?: string; // The text of the message. - timecreated?: number; // The timecreated timestamp for the message. - conversationid?: number; // The conversation id for this message. - useridfrom?: number; // The user id who sent the message. - candeletemessagesforallusers: boolean; // If the user can delete messages in the conversation for all users. + text?: string; // @since 3.6. The text of the message. + timecreated?: number; // @since 3.6. The timecreated timestamp for the message. + conversationid?: number; // @since 3.6. The conversation id for this message. + useridfrom?: number; // @since 3.6. The user id who sent the message. + candeletemessagesforallusers: boolean; // @since 3.7. If the user can delete messages in the conversation for all users. }; /** diff --git a/src/addon/notes/providers/notes.ts b/src/addon/notes/providers/notes.ts index ff89faaad..12e476b8c 100644 --- a/src/addon/notes/providers/notes.ts +++ b/src/addon/notes/providers/notes.ts @@ -441,8 +441,8 @@ export type AddonNotesGetCourseNotesResult = { sitenotes?: AddonNotesNote[]; // Site notes. coursenotes?: AddonNotesNote[]; // Couse notes. personalnotes?: AddonNotesNote[]; // Personal notes. - canmanagesystemnotes?: boolean; // Whether the user can manage notes at system level. - canmanagecoursenotes?: boolean; // Whether the user can manage notes at the given course. + 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[]; }; diff --git a/src/addon/notifications/providers/notifications.ts b/src/addon/notifications/providers/notifications.ts index 02093823b..cd7f37bfc 100644 --- a/src/addon/notifications/providers/notifications.ts +++ b/src/addon/notifications/providers/notifications.ts @@ -486,7 +486,7 @@ export type AddonNotificationsNotificationPreferencesNotificationProcessor = { displayname: string; // Display name. name: string; // Processor name. locked: boolean; // Is locked by admin?. - lockedmessage?: string; // Text to display if locked. + lockedmessage?: string; // @since 3.6. Text to display if locked. userconfigured: number; // Is configured?. loggedin: AddonNotificationsNotificationPreferencesNotificationProcessorState; loggedoff: AddonNotificationsNotificationPreferencesNotificationProcessorState; diff --git a/src/core/comments/providers/comments.ts b/src/core/comments/providers/comments.ts index 3e2e8c075..b7a132cfe 100644 --- a/src/core/comments/providers/comments.ts +++ b/src/core/comments/providers/comments.ts @@ -422,7 +422,7 @@ export type CoreCommentsArea = { canpost: boolean; // Canpost. canview: boolean; // Canview. count: number; // Count. - collapsediconkey: string; // Collapsediconkey. + collapsediconkey: string; // @since 3.3. Collapsediconkey. displaytotalcount: boolean; // Displaytotalcount. displaycancel: boolean; // Displaycancel. fullwidth: boolean; // Fullwidth. diff --git a/src/core/course/providers/course.ts b/src/core/course/providers/course.ts index 7bbc92983..ed2d9fc7e 100644 --- a/src/core/course/providers/course.ts +++ b/src/core/course/providers/course.ts @@ -1144,21 +1144,21 @@ export type CoreCourseSummary = { 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; // Visible. - fullnamedisplay: string; // Fullnamedisplay. + 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; // Courseimage. - progress?: number; // Progress. - hasprogress: boolean; // Hasprogress. - isfavourite: boolean; // Isfavourite. - hidden: boolean; // Hidden. - timeaccess?: number; // Timeaccess. - showshortname: boolean; // Showshortname. - coursecategory: string; // Coursecategory. + 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. }; /** From 7fc1c96b0345f62eda6ef567ef80cd27776526ec Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 6 Sep 2019 09:25:30 +0200 Subject: [PATCH 017/257] MOBILE-3109 assign: Add return types to assign --- .../assign/classes/base-feedback-handler.ts | 20 +- .../assign/classes/base-submission-handler.ts | 39 +- .../classes/feedback-plugin-component.ts | 7 +- .../classes/submission-plugin-component.ts | 9 +- .../feedback-plugin/feedback-plugin.ts | 10 +- .../mod/assign/components/index/index.ts | 8 +- .../submission-plugin/submission-plugin.ts | 10 +- .../components/submission/submission.ts | 44 ++- .../feedback/comments/providers/handler.ts | 23 +- .../feedback/editpdf/providers/handler.ts | 11 +- .../assign/feedback/file/providers/handler.ts | 11 +- .../edit-feedback-modal.ts | 13 +- src/addon/mod/assign/pages/edit/edit.ts | 10 +- .../pages/submission-list/submission-list.ts | 28 +- .../submission-review/submission-review.ts | 4 +- src/addon/mod/assign/providers/assign-sync.ts | 16 +- src/addon/mod/assign/providers/assign.ts | 338 ++++++++++++++++-- .../mod/assign/providers/feedback-delegate.ts | 48 ++- src/addon/mod/assign/providers/helper.ts | 116 ++++-- .../mod/assign/providers/prefetch-handler.ts | 29 +- .../assign/providers/submission-delegate.ts | 79 ++-- .../submission/comments/providers/handler.ts | 12 +- .../submission/file/providers/handler.ts | 39 +- ...ddon-mod-assign-submission-onlinetext.html | 2 +- .../onlinetext/component/onlinetext.ts | 9 +- .../onlinetext/providers/handler.ts | 32 +- 26 files changed, 728 insertions(+), 239 deletions(-) diff --git a/src/addon/mod/assign/classes/base-feedback-handler.ts b/src/addon/mod/assign/classes/base-feedback-handler.ts index 33e08c2d4..a63a11a82 100644 --- a/src/addon/mod/assign/classes/base-feedback-handler.ts +++ b/src/addon/mod/assign/classes/base-feedback-handler.ts @@ -15,6 +15,7 @@ import { Injector } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { AddonModAssignFeedbackHandler } from '../providers/feedback-delegate'; +import { AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin } from '../providers/assign'; /** * Base handler for feedback plugins. @@ -48,7 +49,7 @@ export class AddonModAssignBaseFeedbackHandler implements AddonModAssignFeedback * @param plugin The plugin object. * @return The component (or promise resolved with component) to use, undefined if not found. */ - getComponent(injector: Injector, plugin: any): any | Promise { + getComponent(injector: Injector, plugin: AddonModAssignPlugin): any | Promise { // Nothing to do. } @@ -74,7 +75,8 @@ export class AddonModAssignBaseFeedbackHandler implements AddonModAssignFeedback * @param siteId Site ID. If not defined, current site. * @return The files (or promise resolved with the files). */ - getPluginFiles(assign: any, submission: any, plugin: any, siteId?: string): any[] | Promise { + getPluginFiles(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, siteId?: string): any[] | Promise { return []; } @@ -84,7 +86,7 @@ export class AddonModAssignBaseFeedbackHandler implements AddonModAssignFeedback * @param plugin The plugin object. * @return The plugin name. */ - getPluginName(plugin: any): string { + getPluginName(plugin: AddonModAssignPlugin): string { // Check if there's a translated string for the plugin. const translationId = 'addon.mod_assign_feedback_' + plugin.type + '.pluginname', translation = this.translate.instant(translationId); @@ -109,7 +111,8 @@ export class AddonModAssignBaseFeedbackHandler implements AddonModAssignFeedback * @param inputData Data entered by the user for the feedback. * @return Boolean (or promise resolved with boolean): whether the data has changed. */ - hasDataChanged(assign: any, submission: any, plugin: any, inputData: any): boolean | Promise { + hasDataChanged(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, inputData: any, userId: number): boolean | Promise { return false; } @@ -144,7 +147,8 @@ export class AddonModAssignBaseFeedbackHandler implements AddonModAssignFeedback * @param siteId Site ID. If not defined, current site. * @return Promise resolved when done. */ - prefetch(assign: any, submission: any, plugin: any, siteId?: string): Promise { + prefetch(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, siteId?: string): Promise { return Promise.resolve(); } @@ -158,7 +162,8 @@ export class AddonModAssignBaseFeedbackHandler implements AddonModAssignFeedback * @param siteId Site ID. If not defined, current site. * @return If the function is async, it should return a Promise resolved when done. */ - prepareFeedbackData(assignId: number, userId: number, plugin: any, pluginData: any, siteId?: string): void | Promise { + prepareFeedbackData(assignId: number, userId: number, plugin: AddonModAssignPlugin, pluginData: any, + siteId?: string): void | Promise { // Nothing to do. } @@ -172,7 +177,8 @@ export class AddonModAssignBaseFeedbackHandler implements AddonModAssignFeedback * @param siteId Site ID. If not defined, current site. * @return If the function is async, it should return a Promise resolved when done. */ - saveDraft(assignId: number, userId: number, plugin: any, data: any, siteId?: string): void | Promise { + saveDraft(assignId: number, userId: number, plugin: AddonModAssignPlugin, data: any, siteId?: string) + : void | Promise { // Nothing to do. } } diff --git a/src/addon/mod/assign/classes/base-submission-handler.ts b/src/addon/mod/assign/classes/base-submission-handler.ts index e0e6bb34e..1c34ce157 100644 --- a/src/addon/mod/assign/classes/base-submission-handler.ts +++ b/src/addon/mod/assign/classes/base-submission-handler.ts @@ -15,6 +15,7 @@ import { Injector } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { AddonModAssignSubmissionHandler } from '../providers/submission-delegate'; +import { AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin } from '../providers/assign'; /** * Base handler for submission plugins. @@ -38,7 +39,8 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis * @param plugin The plugin object. * @return Boolean or promise resolved with boolean: whether it can be edited in offline. */ - canEditOffline(assign: any, submission: any, plugin: any): boolean | Promise { + canEditOffline(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin): boolean | Promise { return false; } @@ -50,7 +52,8 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis * @param plugin The plugin object. * @param inputData Data entered by the user for the submission. */ - clearTmpData(assign: any, submission: any, plugin: any, inputData: any): void { + clearTmpData(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, inputData: any): void { // Nothing to do. } @@ -65,7 +68,8 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis * @param siteId Site ID. If not defined, current site. * @return If the function is async, it should return a Promise resolved when done. */ - copySubmissionData(assign: any, plugin: any, pluginData: any, userId?: number, siteId?: string): void | Promise { + copySubmissionData(assign: AddonModAssignAssign, plugin: AddonModAssignPlugin, pluginData: any, + userId?: number, siteId?: string): void | Promise { // Nothing to do. } @@ -79,7 +83,8 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis * @param siteId Site ID. If not defined, current site. * @return If the function is async, it should return a Promise resolved when done. */ - deleteOfflineData(assign: any, submission: any, plugin: any, offlineData: any, siteId?: string): void | Promise { + deleteOfflineData(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, offlineData: any, siteId?: string): void | Promise { // Nothing to do. } @@ -92,7 +97,7 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis * @param edit Whether the user is editing. * @return The component (or promise resolved with component) to use, undefined if not found. */ - getComponent(injector: Injector, plugin: any, edit?: boolean): any | Promise { + getComponent(injector: Injector, plugin: AddonModAssignPlugin, edit?: boolean): any | Promise { // Nothing to do. } @@ -106,7 +111,8 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis * @param siteId Site ID. If not defined, current site. * @return The files (or promise resolved with the files). */ - getPluginFiles(assign: any, submission: any, plugin: any, siteId?: string): any[] | Promise { + getPluginFiles(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, siteId?: string): any[] | Promise { return []; } @@ -116,7 +122,7 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis * @param plugin The plugin object. * @return The plugin name. */ - getPluginName(plugin: any): string { + getPluginName(plugin: AddonModAssignPlugin): string { // Check if there's a translated string for the plugin. const translationId = 'addon.mod_assign_submission_' + plugin.type + '.pluginname', translation = this.translate.instant(translationId); @@ -139,7 +145,7 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis * @param plugin The plugin object. * @return The size (or promise resolved with size). */ - getSizeForCopy(assign: any, plugin: any): number | Promise { + getSizeForCopy(assign: AddonModAssignAssign, plugin: AddonModAssignPlugin): number | Promise { return 0; } @@ -147,10 +153,12 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis * Get the size of data (in bytes) this plugin will send to add or edit a submission. * * @param assign The assignment. + * @param submission The submission. * @param plugin The plugin object. * @return The size (or promise resolved with size). */ - getSizeForEdit(assign: any, plugin: any): number | Promise { + getSizeForEdit(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, inputData: any): number | Promise { return 0; } @@ -163,7 +171,8 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis * @param inputData Data entered by the user for the submission. * @return Boolean (or promise resolved with boolean): whether the data has changed. */ - hasDataChanged(assign: any, submission: any, plugin: any, inputData: any): boolean | Promise { + hasDataChanged(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, inputData: any): boolean | Promise { return false; } @@ -194,7 +203,8 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis * @param siteId Site ID. If not defined, current site. * @return Promise resolved when done. */ - prefetch(assign: any, submission: any, plugin: any, siteId?: string): Promise { + prefetch?(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, siteId?: string): Promise { return Promise.resolve(); } @@ -211,7 +221,8 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis * @param siteId Site ID. If not defined, current site. * @return If the function is async, it should return a Promise resolved when done. */ - prepareSubmissionData?(assign: any, submission: any, plugin: any, inputData: any, pluginData: any, offline?: boolean, + prepareSubmissionData(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, inputData: any, pluginData: any, offline?: boolean, userId?: number, siteId?: string): void | Promise { // Nothing to do. } @@ -228,8 +239,8 @@ export class AddonModAssignBaseSubmissionHandler implements AddonModAssignSubmis * @param siteId Site ID. If not defined, current site. * @return If the function is async, it should return a Promise resolved when done. */ - prepareSyncData?(assign: any, submission: any, plugin: any, offlineData: any, pluginData: any, siteId?: string) - : void | Promise { + prepareSyncData(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, offlineData: any, pluginData: any, siteId?: string): void | Promise { // Nothing to do. } } diff --git a/src/addon/mod/assign/classes/feedback-plugin-component.ts b/src/addon/mod/assign/classes/feedback-plugin-component.ts index d315d1b7f..0076ba466 100644 --- a/src/addon/mod/assign/classes/feedback-plugin-component.ts +++ b/src/addon/mod/assign/classes/feedback-plugin-component.ts @@ -14,14 +14,15 @@ import { Input } from '@angular/core'; import { ModalController } from 'ionic-angular'; +import { AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin } from '../providers/assign'; /** * Base class for component to render a feedback plugin. */ export class AddonModAssignFeedbackPluginComponentBase { - @Input() assign: any; // The assignment. - @Input() submission: any; // The submission. - @Input() plugin: any; // The plugin object. + @Input() assign: AddonModAssignAssign; // The assignment. + @Input() submission: AddonModAssignSubmission; // The submission. + @Input() plugin: AddonModAssignPlugin; // The plugin object. @Input() userId: number; // The user ID of the submission. @Input() configs: any; // The configs for the plugin. @Input() canEdit: boolean; // Whether the user can edit. diff --git a/src/addon/mod/assign/classes/submission-plugin-component.ts b/src/addon/mod/assign/classes/submission-plugin-component.ts index 39e8cdd2b..552657b9d 100644 --- a/src/addon/mod/assign/classes/submission-plugin-component.ts +++ b/src/addon/mod/assign/classes/submission-plugin-component.ts @@ -13,15 +13,16 @@ // limitations under the License. import { Input } from '@angular/core'; +import { AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin } from '../providers/assign'; /** * Base class for component to render a submission plugin. */ export class AddonModAssignSubmissionPluginComponent { - @Input() assign: any; // The assignment. - @Input() submission: any; // The submission. - @Input() plugin: any; // The plugin object. - @Input() configs: any; // The configs for the plugin. + @Input() assign: AddonModAssignAssign; // The assignment. + @Input() submission: AddonModAssignSubmission; // The submission. + @Input() plugin: AddonModAssignPlugin; // The plugin object. + @Input() configs: {[name: string]: string}; // The configs for the plugin. @Input() edit: boolean; // Whether the user is editing. @Input() allowOffline: boolean; // Whether to allow offline. diff --git a/src/addon/mod/assign/components/feedback-plugin/feedback-plugin.ts b/src/addon/mod/assign/components/feedback-plugin/feedback-plugin.ts index 94b847fda..d28973ae8 100644 --- a/src/addon/mod/assign/components/feedback-plugin/feedback-plugin.ts +++ b/src/addon/mod/assign/components/feedback-plugin/feedback-plugin.ts @@ -13,7 +13,9 @@ // limitations under the License. import { Component, Input, OnInit, Injector, ViewChild } from '@angular/core'; -import { AddonModAssignProvider } from '../../providers/assign'; +import { + AddonModAssignProvider, AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin +} from '../../providers/assign'; import { AddonModAssignHelperProvider } from '../../providers/helper'; import { AddonModAssignFeedbackDelegate } from '../../providers/feedback-delegate'; import { CoreDynamicComponent } from '@components/dynamic-component/dynamic-component'; @@ -28,9 +30,9 @@ import { CoreDynamicComponent } from '@components/dynamic-component/dynamic-comp export class AddonModAssignFeedbackPluginComponent implements OnInit { @ViewChild(CoreDynamicComponent) dynamicComponent: CoreDynamicComponent; - @Input() assign: any; // The assignment. - @Input() submission: any; // The submission. - @Input() plugin: any; // The plugin object. + @Input() assign: AddonModAssignAssign; // The assignment. + @Input() submission: AddonModAssignSubmission; // The submission. + @Input() plugin: AddonModAssignPlugin; // The plugin object. @Input() userId: number; // The user ID of the submission. @Input() canEdit: boolean | string; // Whether the user can edit. @Input() edit: boolean | string; // Whether the user is editing. diff --git a/src/addon/mod/assign/components/index/index.ts b/src/addon/mod/assign/components/index/index.ts index 2697f6c36..0361570eb 100644 --- a/src/addon/mod/assign/components/index/index.ts +++ b/src/addon/mod/assign/components/index/index.ts @@ -17,7 +17,7 @@ import { Content, NavController } from 'ionic-angular'; import { CoreGroupsProvider, CoreGroupInfo } from '@providers/groups'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main-activity-component'; -import { AddonModAssignProvider } from '../../providers/assign'; +import { AddonModAssignProvider, AddonModAssignAssign, AddonModAssignSubmissionGradingSummary } from '../../providers/assign'; import { AddonModAssignHelperProvider } from '../../providers/helper'; import { AddonModAssignOfflineProvider } from '../../providers/assign-offline'; import { AddonModAssignSyncProvider } from '../../providers/assign-sync'; @@ -36,13 +36,13 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo component = AddonModAssignProvider.COMPONENT; moduleName = 'assign'; - assign: any; // The assign object. + assign: AddonModAssignAssign; // The assign object. canViewAllSubmissions: boolean; // Whether the user can view all submissions. canViewOwnSubmission: boolean; // Whether the user can view their own submission. timeRemaining: string; // Message about time remaining to submit. lateSubmissions: string; // Message about late submissions. showNumbers = true; // Whether to show number of submissions with each status. - summary: any; // The summary. + summary: AddonModAssignSubmissionGradingSummary; // The grading summary. needsGradingAvalaible: boolean; // Whether we can see the submissions that need grading. groupInfo: CoreGroupInfo = { @@ -153,7 +153,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo this.assign = assignData; this.dataRetrieved.emit(this.assign); - this.description = this.assign.intro || this.description; + this.description = this.assign.intro; if (sync) { // Try to synchronize the assign. diff --git a/src/addon/mod/assign/components/submission-plugin/submission-plugin.ts b/src/addon/mod/assign/components/submission-plugin/submission-plugin.ts index f8ba6d005..de2d664aa 100644 --- a/src/addon/mod/assign/components/submission-plugin/submission-plugin.ts +++ b/src/addon/mod/assign/components/submission-plugin/submission-plugin.ts @@ -13,7 +13,9 @@ // limitations under the License. import { Component, Input, OnInit, Injector, ViewChild } from '@angular/core'; -import { AddonModAssignProvider } from '../../providers/assign'; +import { + AddonModAssignProvider, AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin +} from '../../providers/assign'; import { AddonModAssignHelperProvider } from '../../providers/helper'; import { AddonModAssignSubmissionDelegate } from '../../providers/submission-delegate'; import { CoreDynamicComponent } from '@components/dynamic-component/dynamic-component'; @@ -28,9 +30,9 @@ import { CoreDynamicComponent } from '@components/dynamic-component/dynamic-comp export class AddonModAssignSubmissionPluginComponent implements OnInit { @ViewChild(CoreDynamicComponent) dynamicComponent: CoreDynamicComponent; - @Input() assign: any; // The assignment. - @Input() submission: any; // The submission. - @Input() plugin: any; // The plugin object. + @Input() assign: AddonModAssignAssign; // The assignment. + @Input() submission: AddonModAssignSubmission; // The submission. + @Input() plugin: AddonModAssignPlugin; // The plugin object. @Input() edit: boolean | string; // Whether the user is editing. @Input() allowOffline: boolean | string; // Whether to allow offline. diff --git a/src/addon/mod/assign/components/submission/submission.ts b/src/addon/mod/assign/components/submission/submission.ts index 762270fb6..b5fa53729 100644 --- a/src/addon/mod/assign/components/submission/submission.ts +++ b/src/addon/mod/assign/components/submission/submission.ts @@ -29,7 +29,10 @@ import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreFileUploaderHelperProvider } from '@core/fileuploader/providers/helper'; import { CoreGradesHelperProvider } from '@core/grades/providers/helper'; import { CoreUserProvider } from '@core/user/providers/user'; -import { AddonModAssignProvider } from '../../providers/assign'; +import { + AddonModAssignProvider, AddonModAssignAssign, AddonModAssignSubmissionFeedback, AddonModAssignSubmission, + AddonModAssignSubmissionAttempt, AddonModAssignSubmissionPreviousAttempt, AddonModAssignPlugin +} from '../../providers/assign'; import { AddonModAssignHelperProvider } from '../../providers/helper'; import { AddonModAssignOfflineProvider } from '../../providers/assign-offline'; import { CoreTabsComponent } from '@components/tabs/tabs'; @@ -55,11 +58,11 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy { loaded: boolean; // Whether data has been loaded. selectedTab: number; // Tab selected on start. - assign: any; // The assignment the submission belongs to. - userSubmission: any; // The submission object. + assign: AddonModAssignAssign; // The assignment the submission belongs to. + userSubmission: AddonModAssignSubmission; // The submission object. isSubmittedForGrading: boolean; // Whether the submission has been submitted for grading. submitModel: any = {}; // Model where to store the data to submit (for grading). - feedback: any; // The feedback. + feedback: AddonModAssignSubmissionFeedbackFormatted; // The feedback. hasOffline: boolean; // Whether there is offline data. submittedOffline: boolean; // Whether it was submitted in offline. fromDate: string; // Readable date when the assign started accepting submissions. @@ -67,7 +70,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy { maxAttemptsText: string; // The text for maximum attempts. blindMarking: boolean; // Whether blind marking is enabled. user: any; // The user. - lastAttempt: any; // The last attempt. + lastAttempt: AddonModAssignSubmissionAttemptFormatted; // The last attempt. membersToSubmit: any[]; // Team members that need to submit the assignment. canSubmit: boolean; // Whether the user can submit for grading. canEdit: boolean; // Whether the user can edit the submission. @@ -77,7 +80,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy { gradingStatusTranslationId: string; // Key of the text to display for the grading status. gradingColor: string; // Color to apply to the grading status. workflowStatusTranslationId: string; // Key of the text to display for the workflow status. - submissionPlugins: string[]; // List of submission plugins names. + submissionPlugins: AddonModAssignPlugin[]; // List of submission plugins. timeRemaining: string; // Message about time remaining. timeRemainingClass: string; // Class to apply to time remaining message. statusTranslated: string; // Status. @@ -99,7 +102,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy { protected siteId: string; // Current site ID. protected currentUserId: number; // Current user ID. - protected previousAttempt: any; // The previous attempt. + protected previousAttempt: AddonModAssignSubmissionPreviousAttempt; // The previous attempt. protected submissionStatusAvailable: boolean; // Whether we were able to retrieve the submission status. protected originalGrades: any = {}; // Object with the original grade data, to check for changes. protected isDestroyed: boolean; // Whether the component has been destroyed. @@ -209,7 +212,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy { return this.goToEdit(); } - const previousSubmission = this.assignProvider.getSubmissionObjectFromAttempt(this.assign, this.previousAttempt); + const previousSubmission = this.previousAttempt.submission; let modal = this.domUtils.showModalLoading(); this.assignHelper.getSubmissionSizeForCopy(this.assign, previousSubmission).catch(() => { @@ -303,7 +306,8 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy { } if (this.feedback && this.feedback.plugins) { - return this.assignHelper.hasFeedbackDataChanged(this.assign, this.submitId, this.feedback).catch(() => { + return this.assignHelper.hasFeedbackDataChanged(this.assign, this.userSubmission, this.feedback, this.submitId) + .catch(() => { // Error ocurred, consider there are no changes. return false; }); @@ -438,7 +442,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy { // Check if there's any unsupported plugin for editing. if (!this.userSubmission || !this.userSubmission.plugins) { // Submission not created yet, we have to use assign configs to detect the plugins used. - this.userSubmission = {}; + this.userSubmission = this.assignHelper.createEmptySubmission(); this.userSubmission.plugins = this.assignHelper.getPluginsEnabled(this.assign, 'assignsubmission'); } @@ -461,7 +465,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy { * @param feedback The feedback data from the submission status. * @return Promise resolved when done. */ - protected loadFeedback(feedback: any): Promise { + protected loadFeedback(feedback: AddonModAssignSubmissionFeedback): Promise { this.grade = { method: false, grade: false, @@ -571,7 +575,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy { if (!this.feedback || !this.feedback.plugins) { // Feedback plugins not present, we have to use assign configs to detect the plugins used. - this.feedback = {}; + this.feedback = this.assignHelper.createEmptyFeedback(); this.feedback.plugins = this.assignHelper.getPluginsEnabled(this.assign, 'assignfeedback'); } @@ -885,7 +889,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy { // Show error if submission statement should be shown but it couldn't be retrieved. this.showErrorStatementEdit = submissionStatementMissing && !this.assign.submissiondrafts && this.submitId == this.currentUserId; - this.showErrorStatementSubmit = submissionStatementMissing && this.assign.submissiondrafts; + this.showErrorStatementSubmit = submissionStatementMissing && !!this.assign.submissiondrafts; this.userSubmission = this.assignProvider.getSubmissionObjectFromAttempt(this.assign, response.lastattempt); @@ -954,3 +958,17 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy { } } } + +/** + * Submission attempt with some calculated data. + */ +type AddonModAssignSubmissionAttemptFormatted = AddonModAssignSubmissionAttempt & { + submissiongroupname?: string; // Calculated in the app. Group name the attempt belongs to. +}; + +/** + * Feedback of an assign submission with some calculated data. + */ +type AddonModAssignSubmissionFeedbackFormatted = AddonModAssignSubmissionFeedback & { + advancedgrade?: boolean; // Calculated in the app. Whether it uses advanced grading. +}; diff --git a/src/addon/mod/assign/feedback/comments/providers/handler.ts b/src/addon/mod/assign/feedback/comments/providers/handler.ts index ef4e1655b..1533fd67d 100644 --- a/src/addon/mod/assign/feedback/comments/providers/handler.ts +++ b/src/addon/mod/assign/feedback/comments/providers/handler.ts @@ -16,7 +16,9 @@ import { Injectable, Injector } from '@angular/core'; import { CoreSitesProvider } from '@providers/sites'; import { CoreTextUtilsProvider } from '@providers/utils/text'; -import { AddonModAssignProvider } from '../../../providers/assign'; +import { + AddonModAssignProvider, AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin +} from '../../../providers/assign'; import { AddonModAssignOfflineProvider } from '../../../providers/assign-offline'; import { AddonModAssignFeedbackHandler } from '../../../providers/feedback-delegate'; import { AddonModAssignFeedbackCommentsComponent } from '../component/comments'; @@ -50,14 +52,14 @@ export class AddonModAssignFeedbackCommentsHandler implements AddonModAssignFeed } /** - * Return the Component to use to display the plugin data, either in read or in edit mode. + * 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. * @param plugin The plugin object. * @return The component (or promise resolved with component) to use, undefined if not found. */ - getComponent(injector: Injector, plugin: any): any | Promise { + getComponent(injector: Injector, plugin: AddonModAssignPlugin): any | Promise { return AddonModAssignFeedbackCommentsComponent; } @@ -101,7 +103,8 @@ export class AddonModAssignFeedbackCommentsHandler implements AddonModAssignFeed * @param siteId Site ID. If not defined, current site. * @return The files (or promise resolved with the files). */ - getPluginFiles(assign: any, submission: any, plugin: any, siteId?: string): any[] | Promise { + getPluginFiles(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, siteId?: string): any[] | Promise { return this.assignProvider.getSubmissionPluginAttachments(plugin); } @@ -135,7 +138,9 @@ export class AddonModAssignFeedbackCommentsHandler implements AddonModAssignFeed * @param userId User ID of the submission. * @return Boolean (or promise resolved with boolean): whether the data has changed. */ - hasDataChanged(assign: any, submission: any, plugin: any, inputData: any, userId: number): boolean | Promise { + hasDataChanged(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, inputData: any, userId: number): boolean | Promise { + // Get it from plugin or offline. return this.assignOfflineProvider.getSubmissionGrade(assign.id, userId).catch(() => { // No offline data found. @@ -191,7 +196,9 @@ export class AddonModAssignFeedbackCommentsHandler implements AddonModAssignFeed * @param siteId Site ID. If not defined, current site. * @return If the function is async, it should return a Promise resolved when done. */ - prepareFeedbackData(assignId: number, userId: number, plugin: any, pluginData: any, siteId?: string): void | Promise { + prepareFeedbackData(assignId: number, userId: number, plugin: AddonModAssignPlugin, pluginData: any, + siteId?: string): void | Promise { + const draft = this.getDraft(assignId, userId, siteId); if (draft) { @@ -212,7 +219,9 @@ export class AddonModAssignFeedbackCommentsHandler implements AddonModAssignFeed * @param siteId Site ID. If not defined, current site. * @return If the function is async, it should return a Promise resolved when done. */ - saveDraft(assignId: number, userId: number, plugin: any, data: any, siteId?: string): void | Promise { + saveDraft(assignId: number, userId: number, plugin: AddonModAssignPlugin, data: any, siteId?: string) + : void | Promise { + if (data) { this.drafts[this.getDraftId(assignId, userId, siteId)] = data; } diff --git a/src/addon/mod/assign/feedback/editpdf/providers/handler.ts b/src/addon/mod/assign/feedback/editpdf/providers/handler.ts index 45276e932..5b2f8e0ef 100644 --- a/src/addon/mod/assign/feedback/editpdf/providers/handler.ts +++ b/src/addon/mod/assign/feedback/editpdf/providers/handler.ts @@ -14,7 +14,9 @@ // limitations under the License. import { Injectable, Injector } from '@angular/core'; -import { AddonModAssignProvider } from '../../../providers/assign'; +import { + AddonModAssignProvider, AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin +} from '../../../providers/assign'; import { AddonModAssignFeedbackHandler } from '../../../providers/feedback-delegate'; import { AddonModAssignFeedbackEditPdfComponent } from '../component/editpdf'; @@ -29,14 +31,14 @@ export class AddonModAssignFeedbackEditPdfHandler implements AddonModAssignFeedb constructor(private assignProvider: AddonModAssignProvider) { } /** - * Return the Component to use to display the plugin data, either in read or in edit mode. + * 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. * @param plugin The plugin object. * @return The component (or promise resolved with component) to use, undefined if not found. */ - getComponent(injector: Injector, plugin: any): any | Promise { + getComponent(injector: Injector, plugin: AddonModAssignPlugin): any | Promise { return AddonModAssignFeedbackEditPdfComponent; } @@ -50,7 +52,8 @@ export class AddonModAssignFeedbackEditPdfHandler implements AddonModAssignFeedb * @param siteId Site ID. If not defined, current site. * @return The files (or promise resolved with the files). */ - getPluginFiles(assign: any, submission: any, plugin: any, siteId?: string): any[] | Promise { + getPluginFiles(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, siteId?: string): any[] | Promise { return this.assignProvider.getSubmissionPluginAttachments(plugin); } diff --git a/src/addon/mod/assign/feedback/file/providers/handler.ts b/src/addon/mod/assign/feedback/file/providers/handler.ts index 2d55cc35d..adc6da09d 100644 --- a/src/addon/mod/assign/feedback/file/providers/handler.ts +++ b/src/addon/mod/assign/feedback/file/providers/handler.ts @@ -14,7 +14,9 @@ // limitations under the License. import { Injectable, Injector } from '@angular/core'; -import { AddonModAssignProvider } from '../../../providers/assign'; +import { + AddonModAssignProvider, AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin +} from '../../../providers/assign'; import { AddonModAssignFeedbackHandler } from '../../../providers/feedback-delegate'; import { AddonModAssignFeedbackFileComponent } from '../component/file'; @@ -29,14 +31,14 @@ export class AddonModAssignFeedbackFileHandler implements AddonModAssignFeedback constructor(private assignProvider: AddonModAssignProvider) { } /** - * Return the Component to use to display the plugin data, either in read or in edit mode. + * 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. * @param plugin The plugin object. * @return The component (or promise resolved with component) to use, undefined if not found. */ - getComponent(injector: Injector, plugin: any): any | Promise { + getComponent(injector: Injector, plugin: AddonModAssignPlugin): any | Promise { return AddonModAssignFeedbackFileComponent; } @@ -50,7 +52,8 @@ export class AddonModAssignFeedbackFileHandler implements AddonModAssignFeedback * @param siteId Site ID. If not defined, current site. * @return The files (or promise resolved with the files). */ - getPluginFiles(assign: any, submission: any, plugin: any, siteId?: string): any[] | Promise { + getPluginFiles(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, siteId?: string): any[] | Promise { return this.assignProvider.getSubmissionPluginAttachments(plugin); } diff --git a/src/addon/mod/assign/pages/edit-feedback-modal/edit-feedback-modal.ts b/src/addon/mod/assign/pages/edit-feedback-modal/edit-feedback-modal.ts index 3c60de76e..ace3043c0 100644 --- a/src/addon/mod/assign/pages/edit-feedback-modal/edit-feedback-modal.ts +++ b/src/addon/mod/assign/pages/edit-feedback-modal/edit-feedback-modal.ts @@ -17,6 +17,9 @@ import { IonicPage, ViewController, NavParams } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { AddonModAssignFeedbackDelegate } from '../../providers/feedback-delegate'; +import { + AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin +} from '../../providers/assign'; /** * Modal that allows editing a feedback plugin. @@ -28,9 +31,9 @@ import { AddonModAssignFeedbackDelegate } from '../../providers/feedback-delegat }) export class AddonModAssignEditFeedbackModalPage { - @Input() assign: any; // The assignment. - @Input() submission: any; // The submission. - @Input() plugin: any; // The plugin object. + @Input() assign: AddonModAssignAssign; // The assignment. + @Input() submission: AddonModAssignSubmission; // The submission. + @Input() plugin: AddonModAssignPlugin; // The plugin object. @Input() userId: number; // The user ID of the submission. protected forceLeave = false; // To allow leaving the page without checking for changes. @@ -99,8 +102,8 @@ export class AddonModAssignEditFeedbackModalPage { * @return Promise resolved with boolean: whether the data has changed. */ protected hasDataChanged(): Promise { - return this.feedbackDelegate.hasPluginDataChanged(this.assign, this.userId, this.plugin, this.getInputData(), this.userId) - .catch(() => { + return this.feedbackDelegate.hasPluginDataChanged(this.assign, this.submission, this.plugin, this.getInputData(), + this.userId).catch(() => { // Ignore errors. return true; }); diff --git a/src/addon/mod/assign/pages/edit/edit.ts b/src/addon/mod/assign/pages/edit/edit.ts index 51f72b0a6..f8569eb0f 100644 --- a/src/addon/mod/assign/pages/edit/edit.ts +++ b/src/addon/mod/assign/pages/edit/edit.ts @@ -20,7 +20,7 @@ import { CoreSitesProvider } from '@providers/sites'; import { CoreSyncProvider } from '@providers/sync'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreFileUploaderHelperProvider } from '@core/fileuploader/providers/helper'; -import { AddonModAssignProvider } from '../../providers/assign'; +import { AddonModAssignProvider, AddonModAssignAssign, AddonModAssignSubmission } from '../../providers/assign'; import { AddonModAssignOfflineProvider } from '../../providers/assign-offline'; import { AddonModAssignSyncProvider } from '../../providers/assign-sync'; import { AddonModAssignHelperProvider } from '../../providers/helper'; @@ -35,9 +35,9 @@ import { AddonModAssignHelperProvider } from '../../providers/helper'; }) export class AddonModAssignEditPage implements OnInit, OnDestroy { title: string; // Title to display. - assign: any; // Assignment. + assign: AddonModAssignAssign; // Assignment. courseId: number; // Course ID the assignment belongs to. - userSubmission: any; // The user submission. + userSubmission: AddonModAssignSubmission; // The user submission. allowOffline: boolean; // Whether offline is allowed. submissionStatement: string; // The submission statement. submissionStatementAccepted: boolean; // Whether submission statement is accepted. @@ -129,7 +129,7 @@ export class AddonModAssignEditPage implements OnInit, OnDestroy { const userSubmission = this.assignProvider.getSubmissionObjectFromAttempt(this.assign, response.lastattempt); // Check if the user can edit it in offline. - return this.assignHelper.canEditSubmissionOffline(this.assign, userSubmission).then((canEditOffline) => { + return this.assignHelper.canEditSubmissionOffline(this.assign, userSubmission).then((canEditOffline): any => { if (canEditOffline) { return response; } @@ -301,7 +301,7 @@ export class AddonModAssignEditPage implements OnInit, OnDestroy { } else { // Try to send it to server. promise = this.assignProvider.saveSubmission(this.assign.id, this.courseId, pluginData, this.allowOffline, - this.userSubmission.timemodified, this.assign.submissiondrafts, this.userId); + this.userSubmission.timemodified, !!this.assign.submissiondrafts, this.userId); } return promise.then(() => { diff --git a/src/addon/mod/assign/pages/submission-list/submission-list.ts b/src/addon/mod/assign/pages/submission-list/submission-list.ts index 9cc87c045..c3a364b4d 100644 --- a/src/addon/mod/assign/pages/submission-list/submission-list.ts +++ b/src/addon/mod/assign/pages/submission-list/submission-list.ts @@ -19,9 +19,11 @@ import { CoreEventsProvider } from '@providers/events'; import { CoreSitesProvider } from '@providers/sites'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreGroupsProvider, CoreGroupInfo } from '@providers/groups'; -import { AddonModAssignProvider } from '../../providers/assign'; +import { + AddonModAssignProvider, AddonModAssignAssign, AddonModAssignGrade, AddonModAssignSubmission +} from '../../providers/assign'; import { AddonModAssignOfflineProvider } from '../../providers/assign-offline'; -import { AddonModAssignHelperProvider } from '../../providers/helper'; +import { AddonModAssignHelperProvider, AddonModAssignSubmissionFormatted } from '../../providers/helper'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; /** @@ -36,7 +38,7 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy { @ViewChild(CoreSplitViewComponent) splitviewCtrl: CoreSplitViewComponent; title: string; // Title to display. - assign: any; // Assignment. + assign: AddonModAssignAssign; // Assignment. submissions: any[]; // List of submissions loaded: boolean; // Whether data has been loaded. haveAllParticipants: boolean; // Whether all participants have been loaded. @@ -53,7 +55,7 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy { protected courseId: number; // Course ID the assignment belongs to. protected selectedStatus: string; // The status to see. protected gradedObserver; // Observer to refresh data when a grade changes. - protected submissionsData: any; + protected submissionsData: {canviewsubmissions: boolean, submissions?: AddonModAssignSubmission[]}; constructor(navParams: NavParams, protected sitesProvider: CoreSitesProvider, eventsProvider: CoreEventsProvider, protected domUtils: CoreDomUtilsProvider, protected translate: TranslateService, @@ -161,14 +163,14 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy { !this.assign.markingworkflow ? this.assignProvider.getAssignmentGrades(this.assign.id) : Promise.resolve(null), ]; - return Promise.all(promises).then(([submissions, grades]) => { + return Promise.all(promises).then(([submissions, grades]: [AddonModAssignSubmissionFormatted[], AddonModAssignGrade[]]) => { // Filter the submissions to get only the ones with the right status and add some extra data. const getNeedGrading = this.selectedStatus == AddonModAssignProvider.NEED_GRADING, searchStatus = getNeedGrading ? AddonModAssignProvider.SUBMISSION_STATUS_SUBMITTED : this.selectedStatus, promises = [], showSubmissions = []; - submissions.forEach((submission) => { + submissions.forEach((submission: AddonModAssignSubmissionForList) => { if (!searchStatus || searchStatus == submission.status) { promises.push(this.assignOfflineProvider.getSubmissionGrade(this.assign.id, submission.userid).catch(() => { // Ignore errors. @@ -213,7 +215,7 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy { submission.statusTranslated = this.translate.instant('addon.mod_assign.submissionstatus_' + submission.status); } else { - submission.statusTranslated = false; + submission.statusTranslated = ''; } if (notSynced) { @@ -224,7 +226,7 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy { submission.gradingStatusTranslationId = this.assignProvider.getSubmissionGradingStatusTranslationId(submission.gradingstatus); } else { - submission.gradingStatusTranslationId = false; + submission.gradingStatusTranslationId = ''; } showSubmissions.push(submission); @@ -299,3 +301,13 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy { this.gradedObserver && this.gradedObserver.off(); } } + +/** + * Calculated data for an assign submission. + */ +type AddonModAssignSubmissionForList = AddonModAssignSubmissionFormatted & { + statusColor?: string; // Calculated in the app. Color of the submission status. + gradingColor?: string; // Calculated in the app. Color of the submission grading status. + statusTranslated?: string; // Calculated in the app. Translated text of the submission status. + gradingStatusTranslationId?: string; // Calculated in the app. Key of the text of the submission grading status. +}; diff --git a/src/addon/mod/assign/pages/submission-review/submission-review.ts b/src/addon/mod/assign/pages/submission-review/submission-review.ts index a3652667d..9c96c1c07 100644 --- a/src/addon/mod/assign/pages/submission-review/submission-review.ts +++ b/src/addon/mod/assign/pages/submission-review/submission-review.ts @@ -17,7 +17,7 @@ import { IonicPage, NavController, NavParams } from 'ionic-angular'; import { CoreAppProvider } from '@providers/app'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreCourseProvider } from '@core/course/providers/course'; -import { AddonModAssignProvider } from '../../providers/assign'; +import { AddonModAssignProvider, AddonModAssignAssign } from '../../providers/assign'; import { AddonModAssignSubmissionComponent } from '../../components/submission/submission'; /** @@ -40,7 +40,7 @@ export class AddonModAssignSubmissionReviewPage implements OnInit { loaded: boolean; // Whether data has been loaded. canSaveGrades: boolean; // Whether the user can save grades. - protected assign: any; // The assignment the submission belongs to. + protected assign: AddonModAssignAssign; // The assignment the submission belongs to. protected blindMarking: boolean; // Whether it uses blind marking. protected forceLeave = false; // To allow leaving the page without checking for changes. diff --git a/src/addon/mod/assign/providers/assign-sync.ts b/src/addon/mod/assign/providers/assign-sync.ts index 60967dc07..c61e09cfa 100644 --- a/src/addon/mod/assign/providers/assign-sync.ts +++ b/src/addon/mod/assign/providers/assign-sync.ts @@ -26,7 +26,7 @@ import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreGradesHelperProvider } from '@core/grades/providers/helper'; import { CoreSyncBaseProvider } from '@classes/base-sync'; -import { AddonModAssignProvider } from './assign'; +import { AddonModAssignProvider, AddonModAssignAssign } from './assign'; import { AddonModAssignOfflineProvider } from './assign-offline'; import { AddonModAssignSubmissionDelegate } from './submission-delegate'; @@ -169,14 +169,14 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider { syncAssign(assignId: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); - const promises = [], + const promises: Promise[] = [], result: AddonModAssignSyncResult = { warnings: [], updated: false }; - let assign, - courseId, - syncPromise; + let assign: AddonModAssignAssign, + courseId: number, + syncPromise: Promise; if (this.isSyncing(assignId, siteId)) { // There's already a sync ongoing for this assign, return the promise. @@ -269,7 +269,7 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider { * @param siteId Site ID. If not defined, current site. * @return Promise resolved if success, rejected otherwise. */ - protected syncSubmission(assign: any, offlineData: any, warnings: string[], siteId?: string): Promise { + protected syncSubmission(assign: AddonModAssignAssign, offlineData: any, warnings: string[], siteId?: string): Promise { const userId = offlineData.userid, pluginData = {}; let discardError, @@ -358,8 +358,8 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider { * @param siteId Site ID. If not defined, current site. * @return Promise resolved if success, rejected otherwise. */ - protected syncSubmissionGrade(assign: any, offlineData: any, warnings: string[], courseId: number, siteId?: string) - : Promise { + protected syncSubmissionGrade(assign: AddonModAssignAssign, offlineData: any, warnings: string[], courseId: number, + siteId?: string): Promise { const userId = offlineData.userid; let discardError; diff --git a/src/addon/mod/assign/providers/assign.ts b/src/addon/mod/assign/providers/assign.ts index e819751a6..07a656c1a 100644 --- a/src/addon/mod/assign/providers/assign.ts +++ b/src/addon/mod/assign/providers/assign.ts @@ -27,6 +27,7 @@ import { AddonModAssignSubmissionDelegate } from './submission-delegate'; import { AddonModAssignOfflineProvider } from './assign-offline'; import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; import { CoreInterceptor } from '@classes/interceptor'; +import { CoreWSExternalWarning, CoreWSExternalFile } from '@providers/ws'; /** * Service that provides some functions for assign. @@ -123,7 +124,7 @@ export class AddonModAssignProvider { * @param siteId Site ID. If not defined, current site. * @return Promise resolved with the assignment. */ - getAssignment(courseId: number, cmId: number, ignoreCache?: boolean, siteId?: string): Promise { + getAssignment(courseId: number, cmId: number, ignoreCache?: boolean, siteId?: string): Promise { return this.getAssignmentByField(courseId, 'cmid', cmId, ignoreCache, siteId); } @@ -138,7 +139,7 @@ export class AddonModAssignProvider { * @return Promise resolved when the assignment is retrieved. */ protected getAssignmentByField(courseId: number, key: string, value: any, ignoreCache?: boolean, siteId?: string) - : Promise { + : Promise { return this.sitesProvider.getSite(siteId).then((site) => { const params = { @@ -161,7 +162,7 @@ export class AddonModAssignProvider { delete params.includenotenrolledcourses; return site.read('mod_assign_get_assignments', params, preSets); - }).then((response) => { + }).then((response: AddonModAssignGetAssignmentsResult): any => { // Search the assignment to return. if (response.courses && response.courses.length) { const assignments = response.courses[0].assignments; @@ -187,7 +188,7 @@ export class AddonModAssignProvider { * @param siteId Site ID. If not defined, current site. * @return Promise resolved with the assignment. */ - getAssignmentById(courseId: number, id: number, ignoreCache?: boolean, siteId?: string): Promise { + getAssignmentById(courseId: number, id: number, ignoreCache?: boolean, siteId?: string): Promise { return this.getAssignmentByField(courseId, 'id', id, ignoreCache, siteId); } @@ -225,7 +226,9 @@ export class AddonModAssignProvider { preSets.emergencyCache = false; } - return site.read('mod_assign_get_user_mappings', params, preSets).then((response) => { + return site.read('mod_assign_get_user_mappings', params, preSets) + .then((response: AddonModAssignGetUserMappingsResult): any => { + // Search the user. if (response.assignments && response.assignments.length) { if (!userId || userId < 0) { @@ -271,7 +274,7 @@ export class AddonModAssignProvider { * @param siteId Site ID. If not defined, current site. * @return Resolved with requested info when done. */ - getAssignmentGrades(assignId: number, ignoreCache?: boolean, siteId?: string): Promise { + getAssignmentGrades(assignId: number, ignoreCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const params = { assignmentids: [assignId] @@ -285,7 +288,7 @@ export class AddonModAssignProvider { preSets.emergencyCache = false; } - return site.read('mod_assign_get_grades', params, preSets).then((response) => { + return site.read('mod_assign_get_grades', params, preSets).then((response: AddonModAssignGetGradesResult): any => { // Search the assignment. if (response.assignments && response.assignments.length) { const assignment = response.assignments[0]; @@ -294,7 +297,7 @@ export class AddonModAssignProvider { return assignment.grades; } } else if (response.warnings && response.warnings.length) { - if (response.warnings[0].warningcode == 3) { + if (response.warnings[0].warningcode == '3') { // No grades found. return []; } @@ -362,7 +365,9 @@ export class AddonModAssignProvider { * @param attempt Attempt. * @return Submission object or null. */ - getSubmissionObjectFromAttempt(assign: any, attempt: any): any { + getSubmissionObjectFromAttempt(assign: AddonModAssignAssign, attempt: AddonModAssignSubmissionAttempt) + : AddonModAssignSubmission { + if (!attempt) { return null; } @@ -432,7 +437,7 @@ export class AddonModAssignProvider { * @return Promise resolved when done. */ getSubmissions(assignId: number, ignoreCache?: boolean, siteId?: string) - : Promise<{canviewsubmissions: boolean, submissions?: any[]}> { + : Promise<{canviewsubmissions: boolean, submissions?: AddonModAssignSubmission[]}> { return this.sitesProvider.getSite(siteId).then((site) => { const params = { @@ -448,9 +453,11 @@ export class AddonModAssignProvider { preSets.emergencyCache = false; } - return site.read('mod_assign_get_submissions', params, preSets).then((response): any => { + return site.read('mod_assign_get_submissions', params, preSets) + .then((response: AddonModAssignGetSubmissionsResult): any => { + // Check if we can view submissions, with enough permissions. - if (response.warnings.length > 0 && response.warnings[0].warningcode == 1) { + if (response.warnings.length > 0 && response.warnings[0].warningcode == '1') { return {canviewsubmissions: false}; } @@ -489,7 +496,7 @@ export class AddonModAssignProvider { * @return Promise always resolved with the user submission status. */ getSubmissionStatus(assignId: number, userId?: number, groupId?: number, isBlind?: boolean, filter: boolean = true, - ignoreCache?: boolean, siteId?: string): Promise { + ignoreCache?: boolean, siteId?: string): Promise { userId = userId || 0; @@ -540,7 +547,7 @@ export class AddonModAssignProvider { * @return Promise always resolved with the user submission status. */ getSubmissionStatusWithRetry(assign: any, userId?: number, groupId?: number, isBlind?: boolean, filter: boolean = true, - ignoreCache?: boolean, siteId?: string): Promise { + ignoreCache?: boolean, siteId?: string): Promise { return this.getSubmissionStatus(assign.id, userId, groupId, isBlind, filter, ignoreCache, siteId).then((response) => { const userSubmission = this.getSubmissionObjectFromAttempt(assign, response.lastattempt); @@ -630,7 +637,9 @@ export class AddonModAssignProvider { * @param siteId Site ID. If not defined, current site. * @return Promise resolved with the list of participants and summary of submissions. */ - listParticipants(assignId: number, groupId?: number, ignoreCache?: boolean, siteId?: string): Promise { + listParticipants(assignId: number, groupId?: number, ignoreCache?: boolean, siteId?: string) + : Promise { + groupId = groupId || 0; return this.sitesProvider.getSite(siteId).then((site) => { @@ -1051,14 +1060,14 @@ export class AddonModAssignProvider { * @param siteId Site ID. If not defined, current site. * @return Promise resolved when saved, rejected otherwise. */ - saveSubmissionOnline(assignId: number, pluginData: any, siteId?: string): Promise { + saveSubmissionOnline(assignId: number, pluginData: any, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const params = { assignmentid: assignId, plugindata: pluginData }; - return site.write('mod_assign_save_submission', params).then((warnings) => { + return site.write('mod_assign_save_submission', params).then((warnings: CoreWSExternalWarning[]) => { if (warnings && warnings.length) { // The WebService returned warnings, reject. return Promise.reject(warnings[0]); @@ -1120,14 +1129,14 @@ export class AddonModAssignProvider { * @param siteId Site ID. If not defined, current site. * @return Promise resolved when submitted, rejected otherwise. */ - submitForGradingOnline(assignId: number, acceptStatement: boolean, siteId?: string): Promise { + submitForGradingOnline(assignId: number, acceptStatement: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const params = { assignmentid: assignId, acceptsubmissionstatement: acceptStatement ? 1 : 0 }; - return site.write('mod_assign_submit_for_grading', params).then((warnings) => { + return site.write('mod_assign_submit_for_grading', params).then((warnings: CoreWSExternalWarning[]) => { if (warnings && warnings.length) { // The WebService returned warnings, reject. return Promise.reject(warnings[0]); @@ -1169,7 +1178,10 @@ export class AddonModAssignProvider { return this.isGradingOfflineEnabled(siteId).then((enabled) => { if (!enabled) { return this.submitGradingFormOnline(assignId, userId, grade, attemptNumber, addAttempt, workflowState, - applyToAll, outcomes, pluginData, siteId); + applyToAll, outcomes, pluginData, siteId).then(() => { + + return true; + }); } if (!this.appProvider.isOnline()) { @@ -1212,7 +1224,7 @@ export class AddonModAssignProvider { * @return Promise resolved when submitted, rejected otherwise. */ submitGradingFormOnline(assignId: number, userId: number, grade: number, attemptNumber: number, addAttempt: boolean, - workflowState: string, applyToAll: boolean, outcomes: any, pluginData: any, siteId?: string): Promise { + workflowState: string, applyToAll: boolean, outcomes: any, pluginData: any, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { userId = userId || site.getUserId(); @@ -1243,7 +1255,7 @@ export class AddonModAssignProvider { jsonformdata: JSON.stringify(serialized) }; - return site.write('mod_assign_submit_grading_form', params).then((warnings) => { + return site.write('mod_assign_submit_grading_form', params).then((warnings: CoreWSExternalWarning[]) => { if (warnings && warnings.length) { // The WebService returned warnings, reject. return Promise.reject(warnings[0]); @@ -1271,3 +1283,285 @@ export class AddonModAssignProvider { }); } } + +/** + * Assign data returned by mod_assign_get_assignments. + */ +export type AddonModAssignAssign = { + id: number; // Assignment id. + cmid: number; // Course module id. + course: number; // Course id. + name: string; // Assignment name. + nosubmissions: number; // No submissions. + submissiondrafts: number; // Submissions drafts. + sendnotifications: number; // Send notifications. + sendlatenotifications: number; // Send notifications. + sendstudentnotifications: number; // Send student notifications (default). + duedate: number; // Assignment due date. + allowsubmissionsfromdate: number; // Allow submissions from date. + grade: number; // Grade type. + timemodified: number; // Last time assignment was modified. + completionsubmit: number; // If enabled, set activity as complete following submission. + cutoffdate: number; // Date after which submission is not accepted without an extension. + gradingduedate?: number; // @since 3.3. The expected date for marking the submissions. + teamsubmission: number; // If enabled, students submit as a team. + requireallteammemberssubmit: number; // If enabled, all team members must submit. + teamsubmissiongroupingid: number; // The grouping id for the team submission groups. + blindmarking: number; // If enabled, hide identities until reveal identities actioned. + hidegrader?: number; // @since 3.7. If enabled, hide grader to student. + revealidentities: number; // Show identities for a blind marking assignment. + attemptreopenmethod: string; // Method used to control opening new attempts. + maxattempts: number; // Maximum number of attempts allowed. + markingworkflow: number; // Enable marking workflow. + markingallocation: number; // Enable marking allocation. + requiresubmissionstatement: number; // Student must accept submission statement. + preventsubmissionnotingroup?: number; // @since 3.2. Prevent submission not in group. + submissionstatement?: string; // @since 3.2. Submission statement formatted. + submissionstatementformat?: number; // @since 3.2. Submissionstatement format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). + configs: AddonModAssignConfig[]; // Configuration settings. + intro?: string; // Assignment intro, not allways returned because it deppends on the activity configuration. + introformat?: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). + introfiles?: CoreWSExternalFile[]; // @since 3.2. + introattachments?: CoreWSExternalFile[]; +}; + +/** + * Config setting in an assign. + */ +export type AddonModAssignConfig = { + id?: number; // Assign_plugin_config id. + assignment?: number; // Assignment id. + plugin: string; // Plugin. + subtype: string; // Subtype. + name: string; // Name. + value: string; // Value. +}; + +/** + * Grade of an assign, returned by mod_assign_get_grades. + */ +export type AddonModAssignGrade = { + id: number; // Grade id. + assignment?: number; // Assignment id. + userid: number; // Student id. + attemptnumber: number; // Attempt number. + timecreated: number; // Grade creation time. + timemodified: number; // Grade last modified time. + grader: number; // Grader, -1 if grader is hidden. + grade: string; // Grade. + gradefordisplay?: string; // Grade rendered into a format suitable for display. +}; + +/** + * Assign submission returned by mod_assign_get_submissions. + */ +export type AddonModAssignSubmission = { + id: number; // Submission id. + userid: number; // Student id. + attemptnumber: number; // Attempt number. + timecreated: number; // Submission creation time. + timemodified: number; // Submission last modified time. + status: string; // Submission status. + groupid: number; // Group id. + assignment?: number; // Assignment id. + latest?: number; // Latest attempt. + plugins?: AddonModAssignPlugin[]; // Plugins. + gradingstatus?: string; // @since 3.2. Grading status. +}; + +/** + * Assign plugin. + */ +export type AddonModAssignPlugin = { + type: string; // Submission plugin type. + name: string; // Submission plugin name. + fileareas?: { // Fileareas. + area: string; // File area. + files?: CoreWSExternalFile[]; + }[]; + editorfields?: { // Editorfields. + name: string; // Field name. + description: string; // Field description. + text: string; // Field value. + format: number; // Text format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). + }[]; +}; + +/** + * Grading summary of an assign submission. + */ +export type AddonModAssignSubmissionGradingSummary = { + participantcount: number; // Number of users who can submit. + submissiondraftscount: number; // Number of submissions in draft status. + submissionsenabled: boolean; // Whether submissions are enabled or not. + submissionssubmittedcount: number; // Number of submissions in submitted status. + submissionsneedgradingcount: number; // Number of submissions that need grading. + warnofungroupedusers: string; // Whether we need to warn people about groups. +}; + +/** + * Attempt of an assign submission. + */ +export type AddonModAssignSubmissionAttempt = { + submission?: AddonModAssignSubmission; // Submission info. + teamsubmission?: AddonModAssignSubmission; // Submission info. + submissiongroup?: number; // The submission group id (for group submissions only). + submissiongroupmemberswhoneedtosubmit?: number[]; // List of users who still need to submit (for group submissions only). + submissionsenabled: boolean; // Whether submissions are enabled or not. + locked: boolean; // Whether new submissions are locked. + graded: boolean; // Whether the submission is graded. + canedit: boolean; // Whether the user can edit the current submission. + caneditowner?: boolean; // @since 3.2. Whether the owner of the submission can edit it. + cansubmit: boolean; // Whether the user can submit. + extensionduedate: number; // Extension due date. + blindmarking: boolean; // Whether blind marking is enabled. + gradingstatus: string; // Grading status. + usergroups: number[]; // User groups in the course. +}; + +/** + * Previous attempt of an assign submission. + */ +export type AddonModAssignSubmissionPreviousAttempt = { + attemptnumber: number; // Attempt number. + submission?: AddonModAssignSubmission; // Submission info. + grade?: AddonModAssignGrade; // Grade information. + feedbackplugins?: AddonModAssignPlugin[]; // Feedback info. +}; + +/** + * Feedback of an assign submission. + */ +export type AddonModAssignSubmissionFeedback = { + grade: AddonModAssignGrade; // Grade information. + gradefordisplay: string; // Grade rendered into a format suitable for display. + gradeddate: number; // The date the user was graded. + plugins?: AddonModAssignPlugin[]; // Plugins info. +}; + +/** + * Participant returned by mod_assign_list_participants. + */ +export type AddonModAssignParticipant = { + id: number; // ID of the user. + username?: string; // The username. + firstname?: string; // The first name(s) of the user. + lastname?: string; // The family name of the user. + fullname: string; // The fullname of the user. + email?: string; // Email address. + address?: string; // Postal address. + phone1?: string; // Phone 1. + phone2?: string; // Phone 2. + icq?: string; // Icq number. + skype?: string; // Skype id. + yahoo?: string; // Yahoo id. + aim?: string; // Aim id. + msn?: string; // Msn number. + department?: string; // Department. + institution?: string; // Institution. + idnumber?: string; // The idnumber of the user. + interests?: string; // User interests (separated by commas). + firstaccess?: number; // First access to the site (0 if never). + lastaccess?: number; // Last access to the site (0 if never). + suspended?: boolean; // @since 3.2. Suspend user account, either false to enable user login or true to disable it. + description?: string; // User profile description. + descriptionformat?: number; // Int format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). + city?: string; // Home city of the user. + url?: string; // URL of the user. + country?: string; // Home country code of the user, such as AU or CZ. + profileimageurlsmall?: string; // User image profile URL - small version. + profileimageurl?: string; // User image profile URL - big version. + customfields?: { // User custom fields (also known as user profile fields). + type: string; // The type of the custom field - text field, checkbox... + value: string; // The value of the custom field. + name: string; // The name of the custom field. + shortname: string; // The shortname of the custom field - to be able to build the field class in the code. + }[]; + preferences?: { // Users preferences. + name: string; // The name of the preferences. + value: string; // The value of the preference. + }[]; + recordid?: number; // @since 3.7. Record id. + groups?: { // User groups. + id: number; // Group id. + name: string; // Group name. + description: string; // Group description. + }[]; + roles?: { // User roles. + roleid: number; // Role id. + name: string; // Role name. + shortname: string; // Role shortname. + sortorder: number; // Role sortorder. + }[]; + enrolledcourses?: { // Courses where the user is enrolled - limited by which courses the user is able to see. + id: number; // Id of the course. + fullname: string; // Fullname of the course. + shortname: string; // Shortname of the course. + }[]; + submitted: boolean; // Have they submitted their assignment. + requiregrading: boolean; // Is their submission waiting for grading. + grantedextension?: boolean; // @since 3.3. Have they been granted an extension. + groupid?: number; // For group assignments this is the group id. + groupname?: string; // For group assignments this is the group name. +}; + +/** + * Result of WS mod_assign_get_assignments. + */ +export type AddonModAssignGetAssignmentsResult = { + courses: { // List of courses. + id: number; // Course id. + fullname: string; // Course full name. + shortname: string; // Course short name. + timemodified: number; // Last time modified. + assignments: AddonModAssignAssign[]; // Assignment info. + }[]; + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Result of WS mod_assign_get_user_mappings. + */ +export type AddonModAssignGetUserMappingsResult = { + assignments: { // List of assign user mapping data. + assignmentid: number; // Assignment id. + mappings: { + id: number; // User mapping id. + userid: number; // Student id. + }[]; + }[]; + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Result of WS mod_assign_get_grades. + */ +export type AddonModAssignGetGradesResult = { + assignments: { // List of assignment grade information. + assignmentid: number; // Assignment id. + grades: AddonModAssignGrade[]; + }[]; + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Result of WS mod_assign_get_submissions. + */ +export type AddonModAssignGetSubmissionsResult = { + assignments: { // Assignment submissions. + assignmentid: number; // Assignment id. + submissions: AddonModAssignSubmission[]; + }[]; + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Result of WS mod_assign_get_submission_status. + */ +export type AddonModAssignGetSubmissionStatusResult = { + gradingsummary?: AddonModAssignSubmissionGradingSummary; // Grading information. + lastattempt?: AddonModAssignSubmissionAttempt; // Last attempt information. + feedback?: AddonModAssignSubmissionFeedback; // Feedback for the last attempt. + previousattempts?: AddonModAssignSubmissionPreviousAttempt[]; // List all the previous attempts did by the user. + warnings?: CoreWSExternalWarning[]; +}; diff --git a/src/addon/mod/assign/providers/feedback-delegate.ts b/src/addon/mod/assign/providers/feedback-delegate.ts index c72dda969..4565f64be 100644 --- a/src/addon/mod/assign/providers/feedback-delegate.ts +++ b/src/addon/mod/assign/providers/feedback-delegate.ts @@ -18,6 +18,7 @@ import { CoreEventsProvider } from '@providers/events'; import { CoreSitesProvider } from '@providers/sites'; import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate'; import { AddonModAssignDefaultFeedbackHandler } from './default-feedback-handler'; +import { AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin } from './assign'; /** * Interface that all feedback handlers must implement. @@ -47,7 +48,7 @@ export interface AddonModAssignFeedbackHandler extends CoreDelegateHandler { * @param plugin The plugin object. * @return The component (or promise resolved with component) to use, undefined if not found. */ - getComponent?(injector: Injector, plugin: any): any | Promise; + getComponent?(injector: Injector, plugin: AddonModAssignPlugin): any | Promise; /** * Return the draft saved data of the feedback plugin. @@ -69,7 +70,8 @@ export interface AddonModAssignFeedbackHandler extends CoreDelegateHandler { * @param siteId Site ID. If not defined, current site. * @return The files (or promise resolved with the files). */ - getPluginFiles?(assign: any, submission: any, plugin: any, siteId?: string): any[] | Promise; + getPluginFiles?(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, siteId?: string): any[] | Promise; /** * Get a readable name to use for the plugin. @@ -77,7 +79,7 @@ export interface AddonModAssignFeedbackHandler extends CoreDelegateHandler { * @param plugin The plugin object. * @return The plugin name. */ - getPluginName?(plugin: any): string; + getPluginName?(plugin: AddonModAssignPlugin): string; /** * Check if the feedback data has changed for this plugin. @@ -89,7 +91,8 @@ export interface AddonModAssignFeedbackHandler extends CoreDelegateHandler { * @param userId User ID of the submission. * @return Boolean (or promise resolved with boolean): whether the data has changed. */ - hasDataChanged?(assign: any, submission: any, plugin: any, inputData: any, userId: number): boolean | Promise; + hasDataChanged?(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, inputData: any, userId: number): boolean | Promise; /** * Check whether the plugin has draft data stored. @@ -111,7 +114,8 @@ export interface AddonModAssignFeedbackHandler extends CoreDelegateHandler { * @param siteId Site ID. If not defined, current site. * @return Promise resolved when done. */ - prefetch?(assign: any, submission: any, plugin: any, siteId?: string): Promise; + prefetch?(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, siteId?: string): Promise; /** * Prepare and add to pluginData the data to send to the server based on the draft data saved. @@ -123,7 +127,8 @@ export interface AddonModAssignFeedbackHandler extends CoreDelegateHandler { * @param siteId Site ID. If not defined, current site. * @return If the function is async, it should return a Promise resolved when done. */ - prepareFeedbackData?(assignId: number, userId: number, plugin: any, pluginData: any, siteId?: string): void | Promise; + prepareFeedbackData?(assignId: number, userId: number, plugin: AddonModAssignPlugin, pluginData: any, + siteId?: string): void | Promise; /** * Save draft data of the feedback plugin. @@ -135,7 +140,8 @@ export interface AddonModAssignFeedbackHandler extends CoreDelegateHandler { * @param siteId Site ID. If not defined, current site. * @return If the function is async, it should return a Promise resolved when done. */ - saveDraft?(assignId: number, userId: number, plugin: any, data: any, siteId?: string): void | Promise; + saveDraft?(assignId: number, userId: number, plugin: AddonModAssignPlugin, data: any, siteId?: string) + : void | Promise; } /** @@ -160,7 +166,8 @@ export class AddonModAssignFeedbackDelegate extends CoreDelegate { * @param siteId Site ID. If not defined, current site. * @return Promise resolved when done. */ - discardPluginFeedbackData(assignId: number, userId: number, plugin: any, siteId?: string): Promise { + discardPluginFeedbackData(assignId: number, userId: number, plugin: AddonModAssignPlugin, siteId?: string) + : Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'discardDraft', [assignId, userId, siteId])); } @@ -171,7 +178,7 @@ export class AddonModAssignFeedbackDelegate extends CoreDelegate { * @param plugin The plugin object. * @return Promise resolved with the component to use, undefined if not found. */ - getComponentForPlugin(injector: Injector, plugin: any): Promise { + getComponentForPlugin(injector: Injector, plugin: AddonModAssignPlugin): Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'getComponent', [injector, plugin])); } @@ -184,7 +191,8 @@ export class AddonModAssignFeedbackDelegate extends CoreDelegate { * @param siteId Site ID. If not defined, current site. * @return Promise resolved with the draft data. */ - getPluginDraftData(assignId: number, userId: number, plugin: any, siteId?: string): Promise { + getPluginDraftData(assignId: number, userId: number, plugin: AddonModAssignPlugin, siteId?: string) + : Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'getDraft', [assignId, userId, siteId])); } @@ -198,7 +206,8 @@ export class AddonModAssignFeedbackDelegate extends CoreDelegate { * @param siteId Site ID. If not defined, current site. * @return Promise resolved with the files. */ - getPluginFiles(assign: any, submission: any, plugin: any, siteId?: string): Promise { + getPluginFiles(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, siteId?: string): Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'getPluginFiles', [assign, submission, plugin, siteId])); } @@ -208,7 +217,7 @@ export class AddonModAssignFeedbackDelegate extends CoreDelegate { * @param plugin Plugin to get the name for. * @return Human readable name. */ - getPluginName(plugin: any): string { + getPluginName(plugin: AddonModAssignPlugin): string { return this.executeFunctionOnEnabled(plugin.type, 'getPluginName', [plugin]); } @@ -222,7 +231,8 @@ export class AddonModAssignFeedbackDelegate extends CoreDelegate { * @param userId User ID of the submission. * @return Promise resolved with true if data has changed, resolved with false otherwise. */ - hasPluginDataChanged(assign: any, submission: any, plugin: any, inputData: any, userId: number): Promise { + hasPluginDataChanged(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, inputData: any, userId: number): Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'hasDataChanged', [assign, submission, plugin, inputData, userId])); } @@ -236,7 +246,8 @@ export class AddonModAssignFeedbackDelegate extends CoreDelegate { * @param siteId Site ID. If not defined, current site. * @return Promise resolved with true if it has draft data. */ - hasPluginDraftData(assignId: number, userId: number, plugin: any, siteId?: string): Promise { + hasPluginDraftData(assignId: number, userId: number, plugin: AddonModAssignPlugin, siteId?: string) + : Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'hasDraftData', [assignId, userId, siteId])); } @@ -259,7 +270,8 @@ export class AddonModAssignFeedbackDelegate extends CoreDelegate { * @param siteId Site ID. If not defined, current site. * @return Promise resolved when done. */ - prefetch(assign: any, submission: any, plugin: any, siteId?: string): Promise { + prefetch(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, plugin: AddonModAssignPlugin, + siteId?: string): Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'prefetch', [assign, submission, plugin, siteId])); } @@ -273,7 +285,8 @@ export class AddonModAssignFeedbackDelegate extends CoreDelegate { * @param siteId Site ID. If not defined, current site. * @return Promise resolved when data has been gathered. */ - preparePluginFeedbackData(assignId: number, userId: number, plugin: any, pluginData: any, siteId?: string): Promise { + preparePluginFeedbackData(assignId: number, userId: number, plugin: AddonModAssignPlugin, pluginData: any, + siteId?: string): Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'prepareFeedbackData', [assignId, userId, plugin, pluginData, siteId])); @@ -289,7 +302,8 @@ export class AddonModAssignFeedbackDelegate extends CoreDelegate { * @param siteId Site ID. If not defined, current site. * @return Promise resolved when data has been saved. */ - saveFeedbackDraft(assignId: number, userId: number, plugin: any, inputData: any, siteId?: string): Promise { + saveFeedbackDraft(assignId: number, userId: number, plugin: AddonModAssignPlugin, inputData: any, + siteId?: string): Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'saveDraft', [assignId, userId, plugin, inputData, siteId])); } diff --git a/src/addon/mod/assign/providers/helper.ts b/src/addon/mod/assign/providers/helper.ts index a634e520c..d89d14c97 100644 --- a/src/addon/mod/assign/providers/helper.ts +++ b/src/addon/mod/assign/providers/helper.ts @@ -21,7 +21,10 @@ import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader'; import { AddonModAssignFeedbackDelegate } from './feedback-delegate'; import { AddonModAssignSubmissionDelegate } from './submission-delegate'; -import { AddonModAssignProvider } from './assign'; +import { + AddonModAssignProvider, AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignParticipant, + AddonModAssignSubmissionFeedback +} from './assign'; import { AddonModAssignOfflineProvider } from './assign-offline'; /** @@ -46,7 +49,7 @@ export class AddonModAssignHelperProvider { * @param submission Submission. * @return Whether it can be edited offline. */ - canEditSubmissionOffline(assign: any, submission: any): Promise { + canEditSubmissionOffline(assign: AddonModAssignAssign, submission: AddonModAssignSubmission): Promise { if (!submission) { return Promise.resolve(false); } @@ -81,7 +84,7 @@ export class AddonModAssignHelperProvider { * @param submission Submission to clear the data for. * @param inputData Data entered in the submission form. */ - clearSubmissionPluginTmpData(assign: any, submission: any, inputData: any): void { + clearSubmissionPluginTmpData(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, inputData: any): void { submission.plugins.forEach((plugin) => { this.submissionDelegate.clearTmpData(assign, submission, plugin, inputData); }); @@ -95,7 +98,7 @@ export class AddonModAssignHelperProvider { * @param previousSubmission Submission to copy. * @return Promise resolved when done. */ - copyPreviousAttempt(assign: any, previousSubmission: any): Promise { + copyPreviousAttempt(assign: AddonModAssignAssign, previousSubmission: AddonModAssignSubmission): Promise { const pluginData = {}, promises = []; @@ -112,6 +115,36 @@ export class AddonModAssignHelperProvider { }); } + /** + * Create an empty feedback object. + * + * @return Feedback. + */ + createEmptyFeedback(): AddonModAssignSubmissionFeedback { + return { + grade: undefined, + gradefordisplay: undefined, + gradeddate: undefined + }; + } + + /** + * Create an empty submission object. + * + * @return Submission. + */ + createEmptySubmission(): AddonModAssignSubmissionFormatted { + return { + id: undefined, + userid: undefined, + attemptnumber: undefined, + timecreated: undefined, + timemodified: undefined, + status: undefined, + groupid: undefined + }; + } + /** * Delete stored submission files for a plugin. See storeSubmissionFiles. * @@ -136,7 +169,9 @@ export class AddonModAssignHelperProvider { * @param siteId Site ID. If not defined, current site. * @return Promise resolved when done. */ - discardFeedbackPluginData(assignId: number, userId: number, feedback: any, siteId?: string): Promise { + discardFeedbackPluginData(assignId: number, userId: number, feedback: AddonModAssignSubmissionFeedback, + siteId?: string): Promise { + const promises = []; feedback.plugins.forEach((plugin) => { @@ -155,7 +190,9 @@ export class AddonModAssignHelperProvider { * @param siteId Site ID. If not defined, current site. * @return Promise resolved with the list of participants and summary of submissions. */ - getParticipants(assign: any, groupId?: number, ignoreCache?: boolean, siteId?: string): Promise { + getParticipants(assign: AddonModAssignAssign, groupId?: number, ignoreCache?: boolean, siteId?: string) + : Promise { + groupId = groupId || 0; siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -167,7 +204,7 @@ export class AddonModAssignHelperProvider { // If no participants returned and all groups specified, get participants by groups. return this.groupsProvider.getActivityGroupInfo(assign.cmid, false, undefined, siteId).then((info) => { const promises = [], - participants = {}; + participants: {[id: number]: AddonModAssignParticipant} = {}; info.groups.forEach((userGroup) => { promises.push(this.assignProvider.listParticipants(assign.id, userGroup.id, ignoreCache, siteId) @@ -194,8 +231,8 @@ export class AddonModAssignHelperProvider { * @param type Name of the subplugin. * @return Object containing all configurations of the subplugin selected. */ - getPluginConfig(assign: any, subtype: string, type: string): any { - const configs = {}; + getPluginConfig(assign: AddonModAssignAssign, subtype: string, type: string): {[name: string]: string} { + const configs: {[name: string]: string} = {}; assign.configs.forEach((config) => { if (config.subtype == subtype && config.plugin == type) { @@ -213,7 +250,7 @@ export class AddonModAssignHelperProvider { * @param subtype Subtype name (assignsubmission or assignfeedback) * @return List of enabled plugins for the assign. */ - getPluginsEnabled(assign: any, subtype: string): any[] { + getPluginsEnabled(assign: AddonModAssignAssign, subtype: string): any[] { const enabled = []; assign.configs.forEach((config) => { @@ -250,7 +287,7 @@ export class AddonModAssignHelperProvider { * @param previousSubmission Submission to copy. * @return Promise resolved with the size. */ - getSubmissionSizeForCopy(assign: any, previousSubmission: any): Promise { + getSubmissionSizeForCopy(assign: AddonModAssignAssign, previousSubmission: AddonModAssignSubmission): Promise { const promises = []; let totalSize = 0; @@ -273,7 +310,8 @@ export class AddonModAssignHelperProvider { * @param inputData Data entered in the submission form. * @return Promise resolved with the size. */ - getSubmissionSizeForEdit(assign: any, submission: any, inputData: any): Promise { + getSubmissionSizeForEdit(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, inputData: any): Promise { + const promises = []; let totalSize = 0; @@ -298,14 +336,14 @@ export class AddonModAssignHelperProvider { * @param siteId Site id (empty for current site). * @return Promise always resolved. Resolve param is the formatted submissions. */ - getSubmissionsUserData(assign: any, submissions: any[], groupId?: number, ignoreCache?: boolean, siteId?: string): - Promise { - return this.getParticipants(assign, groupId).then((participants) => { + getSubmissionsUserData(assign: AddonModAssignAssign, submissions: AddonModAssignSubmissionFormatted[], groupId?: number, + ignoreCache?: boolean, siteId?: string): Promise { + + return this.getParticipants(assign, groupId).then((parts) => { const blind = assign.blindmarking && !assign.revealidentities; const promises = []; - const result = []; - - participants = this.utils.arrayToObject(participants, 'id'); + const result: AddonModAssignSubmissionFormatted[] = []; + const participants: {[id: number]: AddonModAssignParticipant} = this.utils.arrayToObject(parts, 'id'); submissions.forEach((submission) => { submission.submitid = submission.userid > 0 ? submission.userid : submission.blindid; @@ -356,10 +394,10 @@ export class AddonModAssignHelperProvider { return Promise.all(promises).then(() => { // Create a submission for each participant left in the list (the participants already treated were removed). - this.utils.objectToArray(participants).forEach((participant) => { - const submission: any = { - submitid: participant.id - }; + this.utils.objectToArray(participants).forEach((participant: AddonModAssignParticipant) => { + const submission = this.createEmptySubmission(); + + submission.submitid = participant.id; if (!blind) { submission.userid = participant.id; @@ -390,17 +428,20 @@ export class AddonModAssignHelperProvider { * Check if the feedback data has changed for a certain submission and assign. * * @param assign Assignment. - * @param userId User Id. + * @param submission The submission. * @param feedback Feedback data. + * @param userId The user ID. * @return Promise resolved with true if data has changed, resolved with false otherwise. */ - hasFeedbackDataChanged(assign: any, userId: number, feedback: any): Promise { + hasFeedbackDataChanged(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + feedback: AddonModAssignSubmissionFeedback, userId: number): Promise { + const promises = []; let hasChanged = false; feedback.plugins.forEach((plugin) => { promises.push(this.prepareFeedbackPluginData(assign.id, userId, feedback).then((inputData) => { - return this.feedbackDelegate.hasPluginDataChanged(assign, userId, plugin, inputData, userId).then((changed) => { + return this.feedbackDelegate.hasPluginDataChanged(assign, submission, plugin, inputData, userId).then((changed) => { if (changed) { hasChanged = true; } @@ -423,7 +464,9 @@ export class AddonModAssignHelperProvider { * @param inputData Data entered in the submission form. * @return Promise resolved with true if data has changed, resolved with false otherwise. */ - hasSubmissionDataChanged(assign: any, submission: any, inputData: any): Promise { + hasSubmissionDataChanged(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, inputData: any) + : Promise { + const promises = []; let hasChanged = false; @@ -451,7 +494,9 @@ export class AddonModAssignHelperProvider { * @param siteId Site ID. If not defined, current site. * @return Promise resolved with plugin data to send to server. */ - prepareFeedbackPluginData(assignId: number, userId: number, feedback: any, siteId?: string): Promise { + prepareFeedbackPluginData(assignId: number, userId: number, feedback: AddonModAssignSubmissionFeedback, siteId?: string) + : Promise { + const pluginData = {}, promises = []; @@ -473,7 +518,9 @@ export class AddonModAssignHelperProvider { * @param offline True to prepare the data for an offline submission, false otherwise. * @return Promise resolved with plugin data to send to server. */ - prepareSubmissionPluginData(assign: any, submission: any, inputData: any, offline?: boolean): Promise { + prepareSubmissionPluginData(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, inputData: any, + offline?: boolean): Promise { + const pluginData = {}, promises = []; @@ -553,3 +600,16 @@ export class AddonModAssignHelperProvider { } } } + +/** + * Assign submission with some calculated data. + */ +export type AddonModAssignSubmissionFormatted = AddonModAssignSubmission & { + blindid?: number; // Calculated in the app. Blindid of the user that did the submission. + submitid?: number; // Calculated in the app. Userid or blindid of the user that did the submission. + userfullname?: string; // Calculated in the app. Full name of the user that did the submission. + userprofileimageurl?: string; // Calculated in the app. Avatar of the user that did the submission. + manyGroups?: boolean; // Calculated in the app. Whether the user belongs to more than 1 group. + noGroups?: boolean; // Calculated in the app. Whether the user doesn't belong to any group. + groupname?: string; // Calculated in the app. Name of the group the submission belongs to. +}; diff --git a/src/addon/mod/assign/providers/prefetch-handler.ts b/src/addon/mod/assign/providers/prefetch-handler.ts index 284d558ab..228e8e5ad 100644 --- a/src/addon/mod/assign/providers/prefetch-handler.ts +++ b/src/addon/mod/assign/providers/prefetch-handler.ts @@ -26,8 +26,8 @@ import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseHelperProvider } from '@core/course/providers/helper'; import { CoreGradesHelperProvider } from '@core/grades/providers/helper'; import { CoreUserProvider } from '@core/user/providers/user'; -import { AddonModAssignProvider } from './assign'; -import { AddonModAssignHelperProvider } from './helper'; +import { AddonModAssignProvider, AddonModAssignGetSubmissionStatusResult, AddonModAssignSubmission } from './assign'; +import { AddonModAssignHelperProvider, AddonModAssignSubmissionFormatted } from './helper'; import { AddonModAssignSyncProvider } from './assign-sync'; import { AddonModAssignFeedbackDelegate } from './feedback-delegate'; import { AddonModAssignSubmissionDelegate } from './submission-delegate'; @@ -106,7 +106,7 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan if (data.canviewsubmissions) { // Teacher, get all submissions. return this.assignHelper.getSubmissionsUserData(assign, data.submissions, 0, false, siteId) - .then((submissions) => { + .then((submissions: AddonModAssignSubmissionFormatted[]) => { const promises = []; @@ -161,9 +161,10 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan return this.assignProvider.getSubmissionStatusWithRetry(assign, submitId, undefined, blindMarking, true, false, siteId) .then((response) => { const promises = []; + let userSubmission: AddonModAssignSubmission; if (response.lastattempt) { - const userSubmission = this.assignProvider.getSubmissionObjectFromAttempt(assign, response.lastattempt); + userSubmission = this.assignProvider.getSubmissionObjectFromAttempt(assign, response.lastattempt); if (userSubmission && userSubmission.plugins) { // Add submission plugin files. userSubmission.plugins.forEach((plugin) => { @@ -175,7 +176,7 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan if (response.feedback && response.feedback.plugins) { // Add feedback plugin files. response.feedback.plugins.forEach((plugin) => { - promises.push(this.feedbackDelegate.getPluginFiles(assign, response, plugin, siteId)); + promises.push(this.feedbackDelegate.getPluginFiles(assign, userSubmission, plugin, siteId)); }); } @@ -303,7 +304,7 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan groupInfo.groups.forEach((group) => { groupProms.push(this.assignHelper.getSubmissionsUserData(assign, data.submissions, group.id, true, siteId) - .then((submissions) => { + .then((submissions: AddonModAssignSubmissionFormatted[]) => { const subPromises = []; @@ -327,7 +328,8 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan } // Prefetch the submission of the current user even if it does not exist, this will be create it. - if (!data.submissions || !data.submissions.find((subm) => subm.submitid == userId)) { + if (!data.submissions || + !data.submissions.find((subm: AddonModAssignSubmissionFormatted) => subm.submitid == userId)) { subPromises.push(this.assignProvider.getSubmissionStatusWithRetry(assign, userId, group.id, false, true, true, siteId).then((subm) => { return this.prefetchSubmission(assign, courseId, moduleId, subm, userId, siteId); @@ -385,15 +387,16 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan * @param siteId Site ID. If not defined, current site. * @return Promise resolved when prefetched, rejected otherwise. */ - protected prefetchSubmission(assign: any, courseId: number, moduleId: number, submission: any, userId?: number, - siteId?: string): Promise { + protected prefetchSubmission(assign: any, courseId: number, moduleId: number, + submission: AddonModAssignGetSubmissionStatusResult, userId?: number, siteId?: string): Promise { const promises = [], blindMarking = assign.blindmarking && !assign.revealidentities; - let userIds = []; + let userIds = [], + userSubmission: AddonModAssignSubmission; if (submission.lastattempt) { - const userSubmission = this.assignProvider.getSubmissionObjectFromAttempt(assign, submission.lastattempt); + userSubmission = this.assignProvider.getSubmissionObjectFromAttempt(assign, submission.lastattempt); // Get IDs of the members who need to submit. if (!blindMarking && submission.lastattempt.submissiongroupmemberswhoneedtosubmit) { @@ -440,10 +443,10 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan if (submission.feedback.plugins) { submission.feedback.plugins.forEach((plugin) => { // Prefetch the plugin WS data. - promises.push(this.feedbackDelegate.prefetch(assign, submission, plugin, siteId)); + promises.push(this.feedbackDelegate.prefetch(assign, userSubmission, plugin, siteId)); // Prefetch the plugin files. - promises.push(this.feedbackDelegate.getPluginFiles(assign, submission, plugin, siteId).then((files) => { + promises.push(this.feedbackDelegate.getPluginFiles(assign, userSubmission, plugin, siteId).then((files) => { return this.filepoolProvider.addFilesToQueue(siteId, files, this.component, module.id); }).catch(() => { // Ignore errors. diff --git a/src/addon/mod/assign/providers/submission-delegate.ts b/src/addon/mod/assign/providers/submission-delegate.ts index 70c8e9752..7b65d55ce 100644 --- a/src/addon/mod/assign/providers/submission-delegate.ts +++ b/src/addon/mod/assign/providers/submission-delegate.ts @@ -18,6 +18,7 @@ import { CoreEventsProvider } from '@providers/events'; import { CoreSitesProvider } from '@providers/sites'; import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate'; import { AddonModAssignDefaultSubmissionHandler } from './default-submission-handler'; +import { AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin } from './assign'; /** * Interface that all submission handlers must implement. @@ -39,7 +40,8 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler { * @param plugin The plugin object. * @return Boolean or promise resolved with boolean: whether it can be edited in offline. */ - canEditOffline?(assign: any, submission: any, plugin: any): boolean | Promise; + canEditOffline?(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin): boolean | Promise; /** * Should clear temporary data for a cancelled submission. @@ -49,7 +51,8 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler { * @param plugin The plugin object. * @param inputData Data entered by the user for the submission. */ - clearTmpData?(assign: any, submission: any, plugin: any, inputData: any): void; + clearTmpData?(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, inputData: any): void; /** * This function will be called when the user wants to create a new submission based on the previous one. @@ -62,7 +65,8 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler { * @param siteId Site ID. If not defined, current site. * @return If the function is async, it should return a Promise resolved when done. */ - copySubmissionData?(assign: any, plugin: any, pluginData: any, userId?: number, siteId?: string): void | Promise; + copySubmissionData?(assign: AddonModAssignAssign, plugin: AddonModAssignPlugin, pluginData: any, + userId?: number, siteId?: string): void | Promise; /** * Delete any stored data for the plugin and submission. @@ -74,7 +78,8 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler { * @param siteId Site ID. If not defined, current site. * @return If the function is async, it should return a Promise resolved when done. */ - deleteOfflineData?(assign: any, submission: any, plugin: any, offlineData: any, siteId?: string): void | Promise; + deleteOfflineData?(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, offlineData: any, siteId?: string): void | Promise; /** * Return the Component to use to display the plugin data, either in read or in edit mode. @@ -85,7 +90,7 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler { * @param edit Whether the user is editing. * @return The component (or promise resolved with component) to use, undefined if not found. */ - getComponent?(injector: Injector, plugin: any, edit?: boolean): any | Promise; + getComponent?(injector: Injector, plugin: AddonModAssignPlugin, edit?: boolean): any | Promise; /** * Get files used by this plugin. @@ -97,7 +102,8 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler { * @param siteId Site ID. If not defined, current site. * @return The files (or promise resolved with the files). */ - getPluginFiles?(assign: any, submission: any, plugin: any, siteId?: string): any[] | Promise; + getPluginFiles?(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, siteId?: string): any[] | Promise; /** * Get a readable name to use for the plugin. @@ -105,7 +111,7 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler { * @param plugin The plugin object. * @return The plugin name. */ - getPluginName?(plugin: any): string; + getPluginName?(plugin: AddonModAssignPlugin): string; /** * Get the size of data (in bytes) this plugin will send to copy a previous submission. @@ -114,7 +120,7 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler { * @param plugin The plugin object. * @return The size (or promise resolved with size). */ - getSizeForCopy?(assign: any, plugin: any): number | Promise; + getSizeForCopy?(assign: AddonModAssignAssign, plugin: AddonModAssignPlugin): number | Promise; /** * Get the size of data (in bytes) this plugin will send to add or edit a submission. @@ -125,7 +131,8 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler { * @param inputData Data entered by the user for the submission. * @return The size (or promise resolved with size). */ - getSizeForEdit?(assign: any, submission: any, plugin: any, inputData: any): number | Promise; + getSizeForEdit?(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, inputData: any): number | Promise; /** * Check if the submission data has changed for this plugin. @@ -136,7 +143,8 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler { * @param inputData Data entered by the user for the submission. * @return Boolean (or promise resolved with boolean): whether the data has changed. */ - hasDataChanged?(assign: any, submission: any, plugin: any, inputData: any): boolean | Promise; + hasDataChanged?(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, inputData: any): boolean | Promise; /** * Whether or not the handler is enabled for edit on a site level. @@ -155,7 +163,8 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler { * @param siteId Site ID. If not defined, current site. * @return Promise resolved when done. */ - prefetch?(assign: any, submission: any, plugin: any, siteId?: string): Promise; + prefetch?(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, siteId?: string): Promise; /** * Prepare and add to pluginData the data to send to the server based on the input data. @@ -170,8 +179,9 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler { * @param siteId Site ID. If not defined, current site. * @return If the function is async, it should return a Promise resolved when done. */ - prepareSubmissionData?(assign: any, submission: any, plugin: any, inputData: any, pluginData: any, offline?: boolean, - userId?: number, siteId?: string): void | Promise; + prepareSubmissionData?(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, inputData: any, pluginData: any, offline?: boolean, + userId?: number, siteId?: string): void | Promise; /** * Prepare and add to pluginData the data to send to the server based on the offline data stored. @@ -185,8 +195,8 @@ export interface AddonModAssignSubmissionHandler extends CoreDelegateHandler { * @param siteId Site ID. If not defined, current site. * @return If the function is async, it should return a Promise resolved when done. */ - prepareSyncData?(assign: any, submission: any, plugin: any, offlineData: any, pluginData: any, siteId?: string) - : void | Promise; + prepareSyncData?(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, offlineData: any, pluginData: any, siteId?: string): void | Promise; } /** @@ -210,7 +220,8 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate { * @param plugin The plugin object. * @return Promise resolved with boolean: whether it can be edited in offline. */ - canPluginEditOffline(assign: any, submission: any, plugin: any): Promise { + canPluginEditOffline(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin): Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'canEditOffline', [assign, submission, plugin])); } @@ -222,7 +233,8 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate { * @param plugin The plugin object. * @param inputData Data entered by the user for the submission. */ - clearTmpData(assign: any, submission: any, plugin: any, inputData: any): void { + clearTmpData(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, inputData: any): void { return this.executeFunctionOnEnabled(plugin.type, 'clearTmpData', [assign, submission, plugin, inputData]); } @@ -236,7 +248,8 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate { * @param siteId Site ID. If not defined, current site. * @return Promise resolved when the data has been copied. */ - copyPluginSubmissionData(assign: any, plugin: any, pluginData: any, userId?: number, siteId?: string): void | Promise { + copyPluginSubmissionData(assign: AddonModAssignAssign, plugin: AddonModAssignPlugin, pluginData: any, + userId?: number, siteId?: string): void | Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'copySubmissionData', [assign, plugin, pluginData, userId, siteId])); } @@ -251,7 +264,8 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate { * @param siteId Site ID. If not defined, current site. * @return Promise resolved when done. */ - deletePluginOfflineData(assign: any, submission: any, plugin: any, offlineData: any, siteId?: string): Promise { + deletePluginOfflineData(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, offlineData: any, siteId?: string): Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'deleteOfflineData', [assign, submission, plugin, offlineData, siteId])); } @@ -264,7 +278,7 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate { * @param edit Whether the user is editing. * @return Promise resolved with the component to use, undefined if not found. */ - getComponentForPlugin(injector: Injector, plugin: any, edit?: boolean): Promise { + getComponentForPlugin(injector: Injector, plugin: AddonModAssignPlugin, edit?: boolean): Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'getComponent', [injector, plugin, edit])); } @@ -278,7 +292,8 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate { * @param siteId Site ID. If not defined, current site. * @return Promise resolved with the files. */ - getPluginFiles(assign: any, submission: any, plugin: any, siteId?: string): Promise { + getPluginFiles(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, siteId?: string): Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'getPluginFiles', [assign, submission, plugin, siteId])); } @@ -288,7 +303,7 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate { * @param plugin Plugin to get the name for. * @return Human readable name. */ - getPluginName(plugin: any): string { + getPluginName(plugin: AddonModAssignPlugin): string { return this.executeFunctionOnEnabled(plugin.type, 'getPluginName', [plugin]); } @@ -299,7 +314,7 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate { * @param plugin The plugin object. * @return Promise resolved with size. */ - getPluginSizeForCopy(assign: any, plugin: any): Promise { + getPluginSizeForCopy(assign: AddonModAssignAssign, plugin: AddonModAssignPlugin): Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'getSizeForCopy', [assign, plugin])); } @@ -312,7 +327,8 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate { * @param inputData Data entered by the user for the submission. * @return Promise resolved with size. */ - getPluginSizeForEdit(assign: any, submission: any, plugin: any, inputData: any): Promise { + getPluginSizeForEdit(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, inputData: any): Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'getSizeForEdit', [assign, submission, plugin, inputData])); } @@ -326,7 +342,8 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate { * @param inputData Data entered by the user for the submission. * @return Promise resolved with true if data has changed, resolved with false otherwise. */ - hasPluginDataChanged(assign: any, submission: any, plugin: any, inputData: any): Promise { + hasPluginDataChanged(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, inputData: any): Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'hasDataChanged', [assign, submission, plugin, inputData])); } @@ -360,7 +377,8 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate { * @param siteId Site ID. If not defined, current site. * @return Promise resolved when done. */ - prefetch(assign: any, submission: any, plugin: any, siteId?: string): Promise { + prefetch(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, plugin: AddonModAssignPlugin, + siteId?: string): Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'prefetch', [assign, submission, plugin, siteId])); } @@ -377,8 +395,9 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate { * @param siteId Site ID. If not defined, current site. * @return Promise resolved when data has been gathered. */ - preparePluginSubmissionData(assign: any, submission: any, plugin: any, inputData: any, pluginData: any, offline?: boolean, - userId?: number, siteId?: string): Promise { + preparePluginSubmissionData(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, inputData: any, pluginData: any, offline?: boolean, userId?: number, + siteId?: string): Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'prepareSubmissionData', [assign, submission, plugin, inputData, pluginData, offline, userId, siteId])); @@ -395,8 +414,8 @@ export class AddonModAssignSubmissionDelegate extends CoreDelegate { * @param siteId Site ID. If not defined, current site. * @return Promise resolved when data has been gathered. */ - preparePluginSyncData(assign: any, submission: any, plugin: any, offlineData: any, pluginData: any, siteId?: string) - : Promise { + preparePluginSyncData(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, offlineData: any, pluginData: any, siteId?: string): Promise { return Promise.resolve(this.executeFunctionOnEnabled(plugin.type, 'prepareSyncData', [assign, submission, plugin, offlineData, pluginData, siteId])); diff --git a/src/addon/mod/assign/submission/comments/providers/handler.ts b/src/addon/mod/assign/submission/comments/providers/handler.ts index 54f450df5..a608d2132 100644 --- a/src/addon/mod/assign/submission/comments/providers/handler.ts +++ b/src/addon/mod/assign/submission/comments/providers/handler.ts @@ -17,6 +17,9 @@ import { Injectable, Injector } from '@angular/core'; import { CoreCommentsProvider } from '@core/comments/providers/comments'; import { AddonModAssignSubmissionHandler } from '../../../providers/submission-delegate'; import { AddonModAssignSubmissionCommentsComponent } from '../component/comments'; +import { + AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin +} from '../../../providers/assign'; /** * Handler for comments submission plugin. @@ -38,7 +41,8 @@ export class AddonModAssignSubmissionCommentsHandler implements AddonModAssignSu * @param plugin The plugin object. * @return Boolean or promise resolved with boolean: whether it can be edited in offline. */ - canEditOffline(assign: any, submission: any, plugin: any): boolean | Promise { + canEditOffline(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin): boolean | Promise { // This plugin is read only, but return true to prevent blocking the edition. return true; } @@ -52,7 +56,7 @@ export class AddonModAssignSubmissionCommentsHandler implements AddonModAssignSu * @param edit Whether the user is editing. * @return The component (or promise resolved with component) to use, undefined if not found. */ - getComponent(injector: Injector, plugin: any, edit?: boolean): any | Promise { + getComponent(injector: Injector, plugin: AddonModAssignPlugin, edit?: boolean): any | Promise { return edit ? undefined : AddonModAssignSubmissionCommentsComponent; } @@ -84,7 +88,9 @@ export class AddonModAssignSubmissionCommentsHandler implements AddonModAssignSu * @param siteId Site ID. If not defined, current site. * @return Promise resolved when done. */ - prefetch(assign: any, submission: any, plugin: any, siteId?: string): Promise { + prefetch(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, siteId?: string): Promise { + return this.commentsProvider.getComments('module', assign.cmid, 'assignsubmission_comments', submission.id, 'submission_comments', 0, siteId).catch(() => { // Fail silently (Moodle < 3.1.1, 3.2) diff --git a/src/addon/mod/assign/submission/file/providers/handler.ts b/src/addon/mod/assign/submission/file/providers/handler.ts index 57a4a70bf..7fe7388dc 100644 --- a/src/addon/mod/assign/submission/file/providers/handler.ts +++ b/src/addon/mod/assign/submission/file/providers/handler.ts @@ -21,7 +21,9 @@ import { CoreSitesProvider } from '@providers/sites'; import { CoreWSProvider } from '@providers/ws'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader'; -import { AddonModAssignProvider } from '../../../providers/assign'; +import { + AddonModAssignProvider, AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin +} from '../../../providers/assign'; import { AddonModAssignOfflineProvider } from '../../../providers/assign-offline'; import { AddonModAssignHelperProvider } from '../../../providers/helper'; import { AddonModAssignSubmissionHandler } from '../../../providers/submission-delegate'; @@ -53,7 +55,8 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis * @param plugin The plugin object. * @return Boolean or promise resolved with boolean: whether it can be edited in offline. */ - canEditOffline(assign: any, submission: any, plugin: any): boolean | Promise { + canEditOffline(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin): boolean | Promise { // This plugin doesn't use Moodle filters, it can be edited in offline. return true; } @@ -66,7 +69,8 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis * @param plugin The plugin object. * @param inputData Data entered by the user for the submission. */ - clearTmpData(assign: any, submission: any, plugin: any, inputData: any): void { + clearTmpData(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, inputData: any): void { const files = this.fileSessionProvider.getFiles(AddonModAssignProvider.COMPONENT, assign.id); // Clear the files in session for this assign. @@ -87,7 +91,9 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis * @param siteId Site ID. If not defined, current site. * @return If the function is async, it should return a Promise resolved when done. */ - copySubmissionData(assign: any, plugin: any, pluginData: any, userId?: number, siteId?: string): void | Promise { + copySubmissionData(assign: AddonModAssignAssign, plugin: AddonModAssignPlugin, pluginData: any, + userId?: number, siteId?: string): void | Promise { + // We need to re-upload all the existing files. const files = this.assignProvider.getSubmissionPluginAttachments(plugin); @@ -105,7 +111,7 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis * @param edit Whether the user is editing. * @return The component (or promise resolved with component) to use, undefined if not found. */ - getComponent(injector: Injector, plugin: any, edit?: boolean): any | Promise { + getComponent(injector: Injector, plugin: AddonModAssignPlugin, edit?: boolean): any | Promise { return AddonModAssignSubmissionFileComponent; } @@ -119,7 +125,9 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis * @param siteId Site ID. If not defined, current site. * @return If the function is async, it should return a Promise resolved when done. */ - deleteOfflineData(assign: any, submission: any, plugin: any, offlineData: any, siteId?: string): void | Promise { + deleteOfflineData(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, offlineData: any, siteId?: string): void | Promise { + return this.assignHelper.deleteStoredSubmissionFiles(assign.id, AddonModAssignSubmissionFileHandler.FOLDER_NAME, submission.userid, siteId).catch(() => { // Ignore errors, maybe the folder doesn't exist. @@ -136,7 +144,8 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis * @param siteId Site ID. If not defined, current site. * @return The files (or promise resolved with the files). */ - getPluginFiles(assign: any, submission: any, plugin: any, siteId?: string): any[] | Promise { + getPluginFiles(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, siteId?: string): any[] | Promise { return this.assignProvider.getSubmissionPluginAttachments(plugin); } @@ -147,7 +156,7 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis * @param plugin The plugin object. * @return The size (or promise resolved with size). */ - getSizeForCopy(assign: any, plugin: any): number | Promise { + getSizeForCopy(assign: AddonModAssignAssign, plugin: AddonModAssignPlugin): number | Promise { const files = this.assignProvider.getSubmissionPluginAttachments(plugin), promises = []; let totalSize = 0; @@ -177,7 +186,8 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis * @param inputData Data entered by the user for the submission. * @return The size (or promise resolved with size). */ - getSizeForEdit(assign: any, submission: any, plugin: any, inputData: any): number | Promise { + getSizeForEdit(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, inputData: any): number | Promise { const siteId = this.sitesProvider.getCurrentSiteId(); // Check if there's any change. @@ -232,7 +242,9 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis * @param inputData Data entered by the user for the submission. * @return Boolean (or promise resolved with boolean): whether the data has changed. */ - hasDataChanged(assign: any, submission: any, plugin: any, inputData: any): boolean | Promise { + hasDataChanged(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, inputData: any): boolean | Promise { + // Check if there's any offline data. return this.assignOfflineProvider.getSubmission(assign.id, submission.userid).catch(() => { // No offline data found. @@ -299,7 +311,8 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis * @param siteId Site ID. If not defined, current site. * @return If the function is async, it should return a Promise resolved when done. */ - prepareSubmissionData(assign: any, submission: any, plugin: any, inputData: any, pluginData: any, offline?: boolean, + prepareSubmissionData(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, inputData: any, pluginData: any, offline?: boolean, userId?: number, siteId?: string): void | Promise { if (this.hasDataChanged(assign, submission, plugin, inputData)) { @@ -330,8 +343,8 @@ export class AddonModAssignSubmissionFileHandler implements AddonModAssignSubmis * @param siteId Site ID. If not defined, current site. * @return If the function is async, it should return a Promise resolved when done. */ - prepareSyncData(assign: any, submission: any, plugin: any, offlineData: any, pluginData: any, siteId?: string) - : void | Promise { + prepareSyncData(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, offlineData: any, pluginData: any, siteId?: string): void | Promise { const filesData = offlineData && offlineData.plugindata && offlineData.plugindata.files_filemanager; if (filesData) { diff --git a/src/addon/mod/assign/submission/onlinetext/component/addon-mod-assign-submission-onlinetext.html b/src/addon/mod/assign/submission/onlinetext/component/addon-mod-assign-submission-onlinetext.html index a231c0cfa..5d2565e06 100644 --- a/src/addon/mod/assign/submission/onlinetext/component/addon-mod-assign-submission-onlinetext.html +++ b/src/addon/mod/assign/submission/onlinetext/component/addon-mod-assign-submission-onlinetext.html @@ -10,7 +10,7 @@
{{ plugin.name }} - +

{{ 'addon.mod_assign.wordlimit' | translate }}

{{ 'core.numwords' | translate: {'$a': words + ' / ' + configs.wordlimit} }}

diff --git a/src/addon/mod/assign/submission/onlinetext/component/onlinetext.ts b/src/addon/mod/assign/submission/onlinetext/component/onlinetext.ts index 25b0cd5e6..b5c6d4045 100644 --- a/src/addon/mod/assign/submission/onlinetext/component/onlinetext.ts +++ b/src/addon/mod/assign/submission/onlinetext/component/onlinetext.ts @@ -34,6 +34,7 @@ export class AddonModAssignSubmissionOnlineTextComponent extends AddonModAssignS component = AddonModAssignProvider.COMPONENT; text: string; loaded: boolean; + wordLimitEnabled: boolean; protected wordCountTimeout: any; protected element: HTMLElement; @@ -61,9 +62,7 @@ export class AddonModAssignSubmissionOnlineTextComponent extends AddonModAssignS // No offline data found, return online text. return this.assignProvider.getSubmissionPluginText(this.plugin); }).then((text) => { - // We receive them as strings, convert to int. - this.configs.wordlimit = parseInt(this.configs.wordlimit, 10); - this.configs.wordlimitenabled = parseInt(this.configs.wordlimitenabled, 10); + this.wordLimitEnabled = !!parseInt(this.configs.wordlimitenabled, 10); // Set the text. this.text = text; @@ -85,7 +84,7 @@ export class AddonModAssignSubmissionOnlineTextComponent extends AddonModAssignS } // Calculate initial words. - if (this.configs.wordlimitenabled) { + if (this.wordLimitEnabled) { this.words = this.textUtils.countWords(text); } }).finally(() => { @@ -100,7 +99,7 @@ export class AddonModAssignSubmissionOnlineTextComponent extends AddonModAssignS */ onChange(text: string): void { // Count words if needed. - if (this.configs.wordlimitenabled) { + if (this.wordLimitEnabled) { // Cancel previous wait. clearTimeout(this.wordCountTimeout); diff --git a/src/addon/mod/assign/submission/onlinetext/providers/handler.ts b/src/addon/mod/assign/submission/onlinetext/providers/handler.ts index dd4b847a4..69a84b85e 100644 --- a/src/addon/mod/assign/submission/onlinetext/providers/handler.ts +++ b/src/addon/mod/assign/submission/onlinetext/providers/handler.ts @@ -18,7 +18,9 @@ import { TranslateService } from '@ngx-translate/core'; import { CoreSitesProvider } from '@providers/sites'; import { CoreWSProvider } from '@providers/ws'; import { CoreTextUtilsProvider } from '@providers/utils/text'; -import { AddonModAssignProvider } from '../../../providers/assign'; +import { + AddonModAssignProvider, AddonModAssignAssign, AddonModAssignSubmission, AddonModAssignPlugin +} from '../../../providers/assign'; import { AddonModAssignOfflineProvider } from '../../../providers/assign-offline'; import { AddonModAssignHelperProvider } from '../../../providers/helper'; import { AddonModAssignSubmissionHandler } from '../../../providers/submission-delegate'; @@ -46,7 +48,8 @@ export class AddonModAssignSubmissionOnlineTextHandler implements AddonModAssign * @param plugin The plugin object. * @return Boolean or promise resolved with boolean: whether it can be edited in offline. */ - canEditOffline(assign: any, submission: any, plugin: any): boolean | Promise { + canEditOffline(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin): boolean | Promise { // This plugin uses Moodle filters, it cannot be edited in offline. return false; } @@ -62,7 +65,9 @@ export class AddonModAssignSubmissionOnlineTextHandler implements AddonModAssign * @param siteId Site ID. If not defined, current site. * @return If the function is async, it should return a Promise resolved when done. */ - copySubmissionData(assign: any, plugin: any, pluginData: any, userId?: number, siteId?: string): void | Promise { + copySubmissionData(assign: AddonModAssignAssign, plugin: AddonModAssignPlugin, pluginData: any, + userId?: number, siteId?: string): void | Promise { + const text = this.assignProvider.getSubmissionPluginText(plugin, true), files = this.assignProvider.getSubmissionPluginAttachments(plugin); let promise; @@ -93,7 +98,7 @@ export class AddonModAssignSubmissionOnlineTextHandler implements AddonModAssign * @param edit Whether the user is editing. * @return The component (or promise resolved with component) to use, undefined if not found. */ - getComponent(injector: Injector, plugin: any, edit?: boolean): any | Promise { + getComponent(injector: Injector, plugin: AddonModAssignPlugin, edit?: boolean): any | Promise { return AddonModAssignSubmissionOnlineTextComponent; } @@ -107,7 +112,8 @@ export class AddonModAssignSubmissionOnlineTextHandler implements AddonModAssign * @param siteId Site ID. If not defined, current site. * @return The files (or promise resolved with the files). */ - getPluginFiles(assign: any, submission: any, plugin: any, siteId?: string): any[] | Promise { + getPluginFiles(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, siteId?: string): any[] | Promise { return this.assignProvider.getSubmissionPluginAttachments(plugin); } @@ -118,7 +124,7 @@ export class AddonModAssignSubmissionOnlineTextHandler implements AddonModAssign * @param plugin The plugin object. * @return The size (or promise resolved with size). */ - getSizeForCopy(assign: any, plugin: any): number | Promise { + getSizeForCopy(assign: AddonModAssignAssign, plugin: AddonModAssignPlugin): number | Promise { const text = this.assignProvider.getSubmissionPluginText(plugin, true), files = this.assignProvider.getSubmissionPluginAttachments(plugin), promises = []; @@ -153,7 +159,8 @@ export class AddonModAssignSubmissionOnlineTextHandler implements AddonModAssign * @param inputData Data entered by the user for the submission. * @return The size (or promise resolved with size). */ - getSizeForEdit(assign: any, submission: any, plugin: any, inputData: any): number | Promise { + getSizeForEdit(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, inputData: any): number | Promise { const text = this.assignProvider.getSubmissionPluginText(plugin, true); return text.length; @@ -182,7 +189,9 @@ export class AddonModAssignSubmissionOnlineTextHandler implements AddonModAssign * @param inputData Data entered by the user for the submission. * @return Boolean (or promise resolved with boolean): whether the data has changed. */ - hasDataChanged(assign: any, submission: any, plugin: any, inputData: any): boolean | Promise { + hasDataChanged(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, inputData: any): boolean | Promise { + // Get the original text from plugin or offline. return this.assignOfflineProvider.getSubmission(assign.id, submission.userid).catch(() => { // No offline data found. @@ -234,7 +243,8 @@ export class AddonModAssignSubmissionOnlineTextHandler implements AddonModAssign * @param siteId Site ID. If not defined, current site. * @return If the function is async, it should return a Promise resolved when done. */ - prepareSubmissionData(assign: any, submission: any, plugin: any, inputData: any, pluginData: any, offline?: boolean, + prepareSubmissionData(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, inputData: any, pluginData: any, offline?: boolean, userId?: number, siteId?: string): void | Promise { let text = this.getTextToSubmit(plugin, inputData); @@ -274,8 +284,8 @@ export class AddonModAssignSubmissionOnlineTextHandler implements AddonModAssign * @param siteId Site ID. If not defined, current site. * @return If the function is async, it should return a Promise resolved when done. */ - prepareSyncData(assign: any, submission: any, plugin: any, offlineData: any, pluginData: any, siteId?: string) - : void | Promise { + prepareSyncData(assign: AddonModAssignAssign, submission: AddonModAssignSubmission, + plugin: AddonModAssignPlugin, offlineData: any, pluginData: any, siteId?: string): void | Promise { const textData = offlineData && offlineData.plugindata && offlineData.plugindata.onlinetext_editor; if (textData) { From 334b28bda219e92402c0a8ba14c7371ed4e259f3 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 6 Sep 2019 13:08:36 +0200 Subject: [PATCH 018/257] MOBILE-3109 mod: Add return types to resources --- src/addon/mod/book/components/index/index.ts | 2 +- src/addon/mod/book/providers/book.ts | 104 ++++++++++++------ .../mod/folder/components/index/index.ts | 13 +-- src/addon/mod/folder/providers/folder.ts | 39 ++++++- src/addon/mod/imscp/components/index/index.ts | 2 +- src/addon/mod/imscp/providers/imscp.ts | 38 ++++++- src/addon/mod/label/providers/label.ts | 39 ++++++- src/addon/mod/page/components/index/index.ts | 8 +- src/addon/mod/page/providers/page.ts | 47 +++++++- .../mod/resource/providers/module-handler.ts | 3 +- src/addon/mod/resource/providers/resource.ts | 43 +++++++- src/addon/mod/url/providers/module-handler.ts | 5 +- src/addon/mod/url/providers/url.ts | 41 ++++++- 13 files changed, 310 insertions(+), 74 deletions(-) diff --git a/src/addon/mod/book/components/index/index.ts b/src/addon/mod/book/components/index/index.ts index 794d4970c..267c0343a 100644 --- a/src/addon/mod/book/components/index/index.ts +++ b/src/addon/mod/book/components/index/index.ts @@ -121,7 +121,7 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp // Try to get the book data. promises.push(this.bookProvider.getBook(this.courseId, this.module.id).then((book) => { this.dataRetrieved.emit(book); - this.description = book.intro || this.description; + this.description = book.intro; }).catch(() => { // Ignore errors since this WS isn't available in some Moodle versions. })); diff --git a/src/addon/mod/book/providers/book.ts b/src/addon/mod/book/providers/book.ts index 1e97a6749..d1061ef65 100644 --- a/src/addon/mod/book/providers/book.ts +++ b/src/addon/mod/book/providers/book.ts @@ -25,38 +25,7 @@ import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreSite } from '@classes/site'; import { CoreTagItem } from '@core/tag/providers/tag'; - -/** - * A book chapter inside the toc list. - */ -export interface AddonModBookTocChapter { - /** - * ID to identify the chapter. - */ - id: string; - - /** - * Chapter's title. - */ - title: string; - - /** - * The chapter's level. - */ - level: number; -} - -/** - * Map of book contents. For each chapter it has its index URL and the list of paths of the files the chapter has. Each path - * is identified by the relative path in the book, and the value is the URL of the file. - */ -export type AddonModBookContentsMap = { - [chapter: string]: { - indexUrl?: string, - paths: {[path: string]: string}, - tags?: CoreTagItem[] - } -}; +import { CoreWSExternalWarning, CoreWSExternalFile } from '@providers/ws'; /** * Service that provides some features for books. @@ -83,7 +52,7 @@ export class AddonModBookProvider { * @param siteId Site ID. If not defined, current site. * @return Promise resolved when the book is retrieved. */ - getBook(courseId: number, cmId: number, siteId?: string): Promise { + getBook(courseId: number, cmId: number, siteId?: string): Promise { return this.getBookByField(courseId, 'coursemodule', cmId, siteId); } @@ -96,7 +65,7 @@ export class AddonModBookProvider { * @param siteId Site ID. If not defined, current site. * @return Promise resolved when the book is retrieved. */ - protected getBookByField(courseId: number, key: string, value: any, siteId?: string): Promise { + protected getBookByField(courseId: number, key: string, value: any, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const params = { courseids: [courseId] @@ -106,7 +75,9 @@ export class AddonModBookProvider { updateFrequency: CoreSite.FREQUENCY_RARELY }; - return site.read('mod_book_get_books_by_courses', params, preSets).then((response) => { + return site.read('mod_book_get_books_by_courses', params, preSets) + .then((response: AddonModBookGetBooksByCoursesResult): any => { + // Search the book. if (response && response.books) { for (const i in response.books) { @@ -401,3 +372,66 @@ export class AddonModBookProvider { {chapterid: chapterId}, siteId); } } + +/** + * A book chapter inside the toc list. + */ +export type AddonModBookTocChapter = { + /** + * ID to identify the chapter. + */ + id: string; + + /** + * Chapter's title. + */ + title: string; + + /** + * The chapter's level. + */ + level: number; +}; + +/** + * Map of book contents. For each chapter it has its index URL and the list of paths of the files the chapter has. Each path + * is identified by the relative path in the book, and the value is the URL of the file. + */ +export type AddonModBookContentsMap = { + [chapter: string]: { + indexUrl?: string, + paths: {[path: string]: string}, + tags?: CoreTagItem[] + } +}; + +/** + * Book returned by mod_book_get_books_by_courses. + */ +export type AddonModBookBook = { + id: number; // Book id. + coursemodule: number; // Course module id. + course: number; // Course id. + name: string; // Book name. + intro: string; // The Book intro. + introformat: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). + introfiles?: CoreWSExternalFile[]; // @since 3.2. + numbering: number; // Book numbering configuration. + navstyle: number; // Book navigation style configuration. + customtitles: number; // Book custom titles type. + revision?: number; // Book revision. + timecreated?: number; // Time of creation. + timemodified?: number; // Time of last modification. + section?: number; // Course section id. + visible?: boolean; // Visible. + groupmode?: number; // Group mode. + groupingid?: number; // Group id. +}; + +/** + * Result of WS mod_book_get_books_by_courses. + */ +export type AddonModBookGetBooksByCoursesResult = { + books: AddonModBookBook[]; + warnings?: CoreWSExternalWarning[]; +}; diff --git a/src/addon/mod/folder/components/index/index.ts b/src/addon/mod/folder/components/index/index.ts index d23b70e38..dc9b246dd 100644 --- a/src/addon/mod/folder/components/index/index.ts +++ b/src/addon/mod/folder/components/index/index.ts @@ -34,6 +34,7 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo component = AddonModFolderProvider.COMPONENT; canGetFolder: boolean; contents: any; + moduleContents: any; constructor(injector: Injector, private folderProvider: AddonModFolderProvider, private courseProvider: CoreCourseProvider, private appProvider: CoreAppProvider, private folderHelper: AddonModFolderHelperProvider) { @@ -87,9 +88,9 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo if (this.path) { // Subfolder. - this.contents = module.contents; + this.contents = this.moduleContents; } else { - this.contents = this.folderHelper.formatContents(module.contents); + this.contents = this.folderHelper.formatContents(this.moduleContents); } } @@ -105,7 +106,7 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo if (this.canGetFolder) { promise = this.folderProvider.getFolder(this.courseId, this.module.id).then((folder) => { return this.courseProvider.loadModuleContents(this.module, this.courseId).then(() => { - folder.contents = this.module.contents; + this.moduleContents = this.module.contents; return folder; }); @@ -117,17 +118,13 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo folder.contents = this.module.contents; } this.module = folder; + this.moduleContents = folder.contents; return folder; }); } return promise.then((folder) => { - if (folder) { - this.description = folder.intro || folder.description; - this.dataRetrieved.emit(folder); - } - this.showModuleData(folder); // All data obtained, now fill the context menu. diff --git a/src/addon/mod/folder/providers/folder.ts b/src/addon/mod/folder/providers/folder.ts index 989a0c69a..c472ce0f6 100644 --- a/src/addon/mod/folder/providers/folder.ts +++ b/src/addon/mod/folder/providers/folder.ts @@ -19,6 +19,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 folder. @@ -43,7 +44,7 @@ export class AddonModFolderProvider { * @param siteId Site ID. If not defined, current site. * @return Promise resolved when the book is retrieved. */ - getFolder(courseId: number, cmId: number, siteId?: string): Promise { + getFolder(courseId: number, cmId: number, siteId?: string): Promise { return this.getFolderByKey(courseId, 'coursemodule', cmId, siteId); } @@ -56,7 +57,7 @@ export class AddonModFolderProvider { * @param siteId Site ID. If not defined, current site. * @return Promise resolved when the book is retrieved. */ - protected getFolderByKey(courseId: number, key: string, value: any, siteId?: string): Promise { + protected getFolderByKey(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 AddonModFolderProvider { updateFrequency: CoreSite.FREQUENCY_RARELY }; - return site.read('mod_folder_get_folders_by_courses', params, preSets).then((response) => { + return site.read('mod_folder_get_folders_by_courses', params, preSets) + .then((response: AddonModFolderGetFoldersByCoursesResult): any => { + if (response && response.folders) { const currentFolder = response.folders.find((folder) => { return folder[key] == value; @@ -147,3 +150,33 @@ export class AddonModFolderProvider { {}, siteId); } } + +/** + * Folder returned by mod_folder_get_folders_by_courses. + */ +export type AddonModFolderFolder = { + 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[]; + revision: number; // Incremented when after each file changes, to avoid cache. + timemodified: number; // Last time the folder was modified. + display: number; // Display type of folder contents on a separate page or inline. + showexpanded: number; // 1 = expanded, 0 = collapsed for sub-folders. + showdownloadfolder: number; // Whether to show the download folder button. + section: number; // Course section id. + visible: number; // Module visibility. + groupmode: number; // Group mode. + groupingid: number; // Grouping id. +}; + +/** + * Result of WS mod_folder_get_folders_by_courses. + */ +export type AddonModFolderGetFoldersByCoursesResult = { + folders: AddonModFolderFolder[]; + warnings?: CoreWSExternalWarning[]; +}; diff --git a/src/addon/mod/imscp/components/index/index.ts b/src/addon/mod/imscp/components/index/index.ts index 37edb9b87..6940db0d8 100644 --- a/src/addon/mod/imscp/components/index/index.ts +++ b/src/addon/mod/imscp/components/index/index.ts @@ -79,7 +79,7 @@ export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceCom 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); })); diff --git a/src/addon/mod/imscp/providers/imscp.ts b/src/addon/mod/imscp/providers/imscp.ts index 366cb7bf6..45adb57f1 100644 --- a/src/addon/mod/imscp/providers/imscp.ts +++ b/src/addon/mod/imscp/providers/imscp.ts @@ -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. @@ -157,7 +158,7 @@ export class AddonModImscpProvider { * @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) { @@ -188,7 +191,7 @@ export class AddonModImscpProvider { * @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); } @@ -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/label/providers/label.ts b/src/addon/mod/label/providers/label.ts index ec75856b6..64e3d5c31 100644 --- a/src/addon/mod/label/providers/label.ts +++ b/src/addon/mod/label/providers/label.ts @@ -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. @@ -52,7 +53,7 @@ export class AddonModLabelProvider { * @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) { @@ -93,7 +96,8 @@ export class AddonModLabelProvider { * @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); } @@ -107,7 +111,8 @@ export class AddonModLabelProvider { * @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); } @@ -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/page/components/index/index.ts b/src/addon/mod/page/components/index/index.ts index bc6b76671..a4297216c 100644 --- a/src/addon/mod/page/components/index/index.ts +++ b/src/addon/mod/page/components/index/index.ts @@ -17,7 +17,7 @@ import { CoreAppProvider } from '@providers/app'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseModuleMainResourceComponent } from '@core/course/classes/main-resource-component'; -import { AddonModPageProvider } from '../../providers/page'; +import { AddonModPageProvider, AddonModPagePage } from '../../providers/page'; import { AddonModPageHelperProvider } from '../../providers/helper'; import { AddonModPagePrefetchHandler } from '../../providers/prefetch-handler'; @@ -34,7 +34,7 @@ export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComp contents: any; displayDescription = true; displayTimemodified = true; - page: any; + page: AddonModPagePage; protected fetchContentDefaultError = 'addon.mod_page.errorwhileloadingthepage'; @@ -109,8 +109,8 @@ export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComp this.page = page; // Check if description and timemodified should be displayed. - if (page.displayoptions) { - const options = this.textUtils.unserialize(page.displayoptions) || {}; + if (this.page.displayoptions) { + const options = this.textUtils.unserialize(this.page.displayoptions) || {}; this.displayDescription = typeof options.printintro == 'undefined' || this.utils.isTrueOrOne(options.printintro); this.displayTimemodified = typeof options.printlastmodified == 'undefined' || diff --git a/src/addon/mod/page/providers/page.ts b/src/addon/mod/page/providers/page.ts index ba42a2412..f41739e9a 100644 --- a/src/addon/mod/page/providers/page.ts +++ b/src/addon/mod/page/providers/page.ts @@ -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 page. @@ -43,9 +44,9 @@ export class AddonModPageProvider { * @param courseId Course ID. * @param cmId Course module ID. * @param siteId Site ID. If not defined, current site. - * @return Promise resolved when the book is retrieved. + * @return Promise resolved when the page is retrieved. */ - getPageData(courseId: number, cmId: number, siteId?: string): Promise { + getPageData(courseId: number, cmId: number, siteId?: string): Promise { return this.getPageByKey(courseId, 'coursemodule', cmId, siteId); } @@ -56,9 +57,9 @@ export class AddonModPageProvider { * @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 book is retrieved. + * @return Promise resolved when the page is retrieved. */ - protected getPageByKey(courseId: number, key: string, value: any, siteId?: string): Promise { + protected getPageByKey(courseId: number, key: string, value: any, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const params = { courseids: [courseId] @@ -68,7 +69,9 @@ export class AddonModPageProvider { updateFrequency: CoreSite.FREQUENCY_RARELY }; - return site.read('mod_page_get_pages_by_courses', params, preSets).then((response) => { + return site.read('mod_page_get_pages_by_courses', params, preSets) + .then((response: AddonModPageGetPagesByCoursesResult): any => { + if (response && response.pages) { const currentPage = response.pages.find((page) => { return page[key] == value; @@ -161,3 +164,37 @@ export class AddonModPageProvider { return this.logHelper.logSingle('mod_page_view_page', params, AddonModPageProvider.COMPONENT, id, name, 'page', {}, siteId); } } + +/** + * Page returned by mod_page_get_pages_by_courses. + */ +export type AddonModPagePage = { + 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[]; + content: string; // Page content. + contentformat: number; // Content format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). + contentfiles: CoreWSExternalFile[]; + legacyfiles: number; // Legacy files flag. + legacyfileslast: number; // Legacy files last control flag. + display: number; // How to display the page. + displayoptions: string; // Display options (width, height). + revision: number; // Incremented when after each file changes, to avoid cache. + timemodified: number; // Last time the page was modified. + section: number; // Course section id. + visible: number; // Module visibility. + groupmode: number; // Group mode. + groupingid: number; // Grouping id. +}; + +/** + * Result of WS mod_page_get_pages_by_courses. + */ +export type AddonModPageGetPagesByCoursesResult = { + pages: AddonModPagePage[]; + warnings?: CoreWSExternalWarning[]; +}; diff --git a/src/addon/mod/resource/providers/module-handler.ts b/src/addon/mod/resource/providers/module-handler.ts index f4a9888bf..b4c6b1b32 100644 --- a/src/addon/mod/resource/providers/module-handler.ts +++ b/src/addon/mod/resource/providers/module-handler.ts @@ -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. @@ -146,7 +147,7 @@ export class AddonModResourceModuleHandler implements CoreCourseModuleHandler { */ 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. diff --git a/src/addon/mod/resource/providers/resource.ts b/src/addon/mod/resource/providers/resource.ts index 05c8e4620..19305d71d 100644 --- a/src/addon/mod/resource/providers/resource.ts +++ b/src/addon/mod/resource/providers/resource.ts @@ -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. @@ -56,7 +57,7 @@ export class AddonModResourceProvider { * @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; @@ -89,7 +92,7 @@ export class AddonModResourceProvider { * @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); } @@ -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/url/providers/module-handler.ts b/src/addon/mod/url/providers/module-handler.ts index 05f475d59..d1ef86acd 100644 --- a/src/addon/mod/url/providers/module-handler.ts +++ b/src/addon/mod/url/providers/module-handler.ts @@ -19,7 +19,7 @@ import { AddonModUrlIndexComponent } from '../components/index/index'; import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@core/course/providers/module-delegate'; import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; -import { AddonModUrlProvider } from './url'; +import { AddonModUrlProvider, AddonModUrlUrl } from './url'; import { AddonModUrlHelperProvider } from './helper'; import { CoreConstants } from '@core/constants'; @@ -90,7 +90,8 @@ export class AddonModUrlModuleHandler implements CoreCourseModuleHandler { if (handler.urlProvider.isGetUrlWSAvailable()) { return handler.urlProvider.getUrl(courseId, module.id).catch(() => { // Ignore errors. - }).then((url) => { + return undefined; + }).then((url: AddonModUrlUrl) => { const displayType = handler.urlProvider.getFinalDisplayType(url); return displayType == CoreConstants.RESOURCELIB_DISPLAY_OPEN || diff --git a/src/addon/mod/url/providers/url.ts b/src/addon/mod/url/providers/url.ts index 28ffe8da8..cd8f9e1fe 100644 --- a/src/addon/mod/url/providers/url.ts +++ b/src/addon/mod/url/providers/url.ts @@ -21,6 +21,7 @@ import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreConstants } from '@core/constants'; import { CoreSite } from '@classes/site'; +import { CoreWSExternalWarning, CoreWSExternalFile } from '@providers/ws'; /** * Service that provides some features for urls. @@ -44,7 +45,7 @@ export class AddonModUrlProvider { * @param url URL data. * @return Final display type. */ - getFinalDisplayType(url: any): number { + getFinalDisplayType(url: AddonModUrlUrl): number { if (!url) { return -1; } @@ -109,7 +110,7 @@ export class AddonModUrlProvider { * @param siteId Site ID. If not defined, current site. * @return Promise resolved when the url is retrieved. */ - protected getUrlDataByKey(courseId: number, key: string, value: any, siteId?: string): Promise { + protected getUrlDataByKey(courseId: number, key: string, value: any, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const params = { courseids: [courseId] @@ -119,7 +120,9 @@ export class AddonModUrlProvider { updateFrequency: CoreSite.FREQUENCY_RARELY }; - return site.read('mod_url_get_urls_by_courses', params, preSets).then((response) => { + return site.read('mod_url_get_urls_by_courses', params, preSets) + .then((response: AddonModUrlGetUrlsByCoursesResult): any => { + if (response && response.urls) { const currentUrl = response.urls.find((url) => { return url[key] == value; @@ -142,7 +145,7 @@ export class AddonModUrlProvider { * @param siteId Site ID. If not defined, current site. * @return Promise resolved when the url is retrieved. */ - getUrl(courseId: number, cmId: number, siteId?: string): Promise { + getUrl(courseId: number, cmId: number, siteId?: string): Promise { return this.getUrlDataByKey(courseId, 'coursemodule', cmId, siteId); } @@ -231,3 +234,33 @@ export class AddonModUrlProvider { return this.logHelper.logSingle('mod_url_view_url', params, AddonModUrlProvider.COMPONENT, id, name, 'url', {}, siteId); } } + +/** + * URL returnd by mod_url_get_urls_by_courses. + */ +export type AddonModUrlUrl = { + id: number; // Module id. + coursemodule: number; // Course module id. + course: number; // Course id. + name: string; // URL name. + intro: string; // Summary. + introformat: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). + introfiles: CoreWSExternalFile[]; + externalurl: string; // External URL. + display: number; // How to display the url. + displayoptions: string; // Display options (width, height). + parameters: string; // Parameters to append to the URL. + timemodified: number; // Last time the url was modified. + section: number; // Course section id. + visible: number; // Module visibility. + groupmode: number; // Group mode. + groupingid: number; // Grouping id. +}; + +/** + * Result of WS mod_url_get_urls_by_courses. + */ +export type AddonModUrlGetUrlsByCoursesResult = { + urls: AddonModUrlUrl[]; + warnings?: CoreWSExternalWarning[]; +}; From 49deffa9dd9406b787e544eb5491d4dbd3491b5f Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 6 Sep 2019 15:04:28 +0200 Subject: [PATCH 019/257] MOBILE-3109 chat: Add return types to chat --- src/addon/mod/chat/components/index/index.ts | 6 +- src/addon/mod/chat/pages/chat/chat.ts | 20 +- .../session-messages/session-messages.ts | 8 +- src/addon/mod/chat/pages/sessions/sessions.ts | 21 +- src/addon/mod/chat/pages/users/users.ts | 10 +- src/addon/mod/chat/providers/chat.ts | 190 ++++++++++++++++-- .../mod/chat/providers/prefetch-handler.ts | 6 +- 7 files changed, 219 insertions(+), 42 deletions(-) diff --git a/src/addon/mod/chat/components/index/index.ts b/src/addon/mod/chat/components/index/index.ts index 71e646d53..4945a5ebe 100644 --- a/src/addon/mod/chat/components/index/index.ts +++ b/src/addon/mod/chat/components/index/index.ts @@ -16,7 +16,7 @@ import { Component, Injector } from '@angular/core'; import { NavController } from 'ionic-angular'; import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main-activity-component'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; -import { AddonModChatProvider } from '../../providers/chat'; +import { AddonModChatProvider, AddonModChatChat } from '../../providers/chat'; /** * Component that displays a chat. @@ -29,7 +29,7 @@ export class AddonModChatIndexComponent extends CoreCourseModuleMainActivityComp component = AddonModChatProvider.COMPONENT; moduleName = 'chat'; - chat: any; + chat: AddonModChatChat; chatInfo: any; protected title: string; @@ -66,7 +66,7 @@ export class AddonModChatIndexComponent extends CoreCourseModuleMainActivityComp protected fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise { return this.chatProvider.getChat(this.courseId, this.module.id).then((chat) => { this.chat = chat; - this.description = chat.intro || chat.description; + this.description = chat.intro; const now = this.timeUtils.timestamp(); const span = chat.chattime - now; diff --git a/src/addon/mod/chat/pages/chat/chat.ts b/src/addon/mod/chat/pages/chat/chat.ts index e4951d6e3..47be73f24 100644 --- a/src/addon/mod/chat/pages/chat/chat.ts +++ b/src/addon/mod/chat/pages/chat/chat.ts @@ -20,7 +20,7 @@ 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 { AddonModChatProvider, AddonModChatMessageWithUserData } from '../../providers/chat'; import { Network } from '@ionic-native/network'; import * as moment from 'moment'; @@ -37,7 +37,7 @@ export class AddonModChatChatPage { loaded = false; title: string; - messages = []; + messages: AddonModChatMessageWithUserData[] = []; newMessage: string; polling: any; isOnline: boolean; @@ -46,7 +46,7 @@ export class AddonModChatChatPage { protected logger; protected courseId: number; protected chatId: number; - protected sessionId: number; + protected sessionId: string; protected lastTime = 0; protected oldContentHeight = 0; protected onlineObserver: any; @@ -131,9 +131,9 @@ export class AddonModChatChatPage { /** * Convenience function to login the user. * - * @return 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; }); @@ -144,12 +144,12 @@ export class AddonModChatChatPage { * * @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); + this.messages = this.messages.concat( messages); if (messages.length) { // New messages or beeps, scroll to bottom. setTimeout(() => this.scrollToBottom()); @@ -190,7 +190,7 @@ export class AddonModChatChatPage { * * @return Promised 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. @@ -225,7 +225,7 @@ export class AddonModChatChatPage { * @param prevMessage Previous message object. * @return True if messages are from diferent days, false othetwise. */ - showDate(message: any, prevMessage: any): boolean { + showDate(message: AddonModChatMessageWithUserData, prevMessage: AddonModChatMessageWithUserData): boolean { if (!prevMessage) { return true; } @@ -267,7 +267,7 @@ export class AddonModChatChatPage { }); } - 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/session-messages/session-messages.ts b/src/addon/mod/chat/pages/session-messages/session-messages.ts index b96ed597a..1a29a8365 100644 --- a/src/addon/mod/chat/pages/session-messages/session-messages.ts +++ b/src/addon/mod/chat/pages/session-messages/session-messages.ts @@ -15,7 +15,7 @@ import { Component } from '@angular/core'; import { IonicPage, NavParams } from 'ionic-angular'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; -import { AddonModChatProvider } from '../../providers/chat'; +import { AddonModChatProvider, AddonModChatSessionMessageWithUserData } from '../../providers/chat'; import * as moment from 'moment'; /** @@ -34,7 +34,7 @@ export class AddonModChatSessionMessagesPage { protected sessionEnd: number; protected groupId: number; protected loaded = false; - protected messages = []; + protected messages: AddonModChatSessionMessageWithUserData[] = []; constructor(navParams: NavParams, private domUtils: CoreDomUtilsProvider, private chatProvider: AddonModChatProvider) { this.courseId = navParams.get('courseId'); @@ -55,7 +55,7 @@ export class AddonModChatSessionMessagesPage { 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; }); }).catch((error) => { this.domUtils.showErrorModalDefault(error, 'core.errorloadingcontent', true); @@ -84,7 +84,7 @@ export class AddonModChatSessionMessagesPage { * @param prevMessage Previous message object. * @return True if messages are from diferent days, false othetwise. */ - showDate(message: any, prevMessage: any): boolean { + showDate(message: AddonModChatSessionMessageWithUserData, prevMessage: AddonModChatSessionMessageWithUserData): boolean { if (!prevMessage) { return true; } diff --git a/src/addon/mod/chat/pages/sessions/sessions.ts b/src/addon/mod/chat/pages/sessions/sessions.ts index 6043f3f3b..1f6ddcf0d 100644 --- a/src/addon/mod/chat/pages/sessions/sessions.ts +++ b/src/addon/mod/chat/pages/sessions/sessions.ts @@ -20,7 +20,7 @@ import { CoreUserProvider } from '@core/user/providers/user'; import { CoreGroupsProvider, CoreGroupInfo } from '@providers/groups'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreUtilsProvider } from '@providers/utils/utils'; -import { AddonModChatProvider } from '../../providers/chat'; +import { AddonModChatProvider, AddonModChatSession, AddonModChatSessionUser } from '../../providers/chat'; /** * Page that displays list of chat sessions. @@ -73,13 +73,13 @@ export class AddonModChatSessionsPage { this.groupId = this.groupsProvider.validateGroupId(this.groupId, groupInfo); return this.chatProvider.getSessions(this.chatId, this.groupId, this.showAll); - }).then((sessions) => { + }).then((sessions: AddonModChatSessionFormatted[]) => { // Fetch user profiles. const promises = []; sessions.forEach((session) => { session.duration = session.sessionend - session.sessionstart; - session.sessionusers.forEach((sessionUser) => { + session.sessionusers.forEach((sessionUser: AddonModChatUserSessionFormatted) => { if (!sessionUser.userfullname) { // The WS does not return the user name, fetch user profile. promises.push(this.userProvider.getProfile(sessionUser.userid, this.courseId, true).then((user) => { @@ -156,3 +156,18 @@ export class AddonModChatSessionsPage { $event.stopPropagation(); } } + +/** + * Fields added to chat session in this view. + */ +type AddonModChatSessionFormatted = AddonModChatSession & { + duration?: number; // Session duration. + allsessionusers?: AddonModChatUserSessionFormatted[]; // All session users. +}; + +/** + * Fields added to user session in this view. + */ +type AddonModChatUserSessionFormatted = AddonModChatSessionUser & { + userfullname?: string; // User full name. +}; diff --git a/src/addon/mod/chat/pages/users/users.ts b/src/addon/mod/chat/pages/users/users.ts index 4cd59049d..f54560ba3 100644 --- a/src/addon/mod/chat/pages/users/users.ts +++ b/src/addon/mod/chat/pages/users/users.ts @@ -17,7 +17,7 @@ import { IonicPage, NavParams, ViewController } from 'ionic-angular'; import { CoreAppProvider } from '@providers/app'; import { CoreSitesProvider } from '@providers/sites'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; -import { AddonModChatProvider } from '../../providers/chat'; +import { AddonModChatProvider, AddonModChatUser } from '../../providers/chat'; import { Network } from '@ionic-native/network'; /** @@ -30,12 +30,12 @@ import { Network } from '@ionic-native/network'; }) export class AddonModChatUsersPage { - users = []; + users: AddonModChatUser[] = []; usersLoaded = false; currentUserId: number; isOnline: boolean; - protected sessionId: number; + protected sessionId: string; protected onlineObserver: any; constructor(navParams: NavParams, network: Network, zone: NgZone, private appProvider: CoreAppProvider, @@ -77,7 +77,7 @@ export class AddonModChatUsersPage { * * @param user User object. */ - talkTo(user: any): void { + talkTo(user: AddonModChatUser): void { this.viewCtrl.dismiss({talkTo: user.fullname}); } @@ -86,7 +86,7 @@ export class AddonModChatUsersPage { * * @param user User object. */ - beepTo(user: any): void { + beepTo(user: AddonModChatUser): void { this.viewCtrl.dismiss({beepTo: user.id}); } diff --git a/src/addon/mod/chat/providers/chat.ts b/src/addon/mod/chat/providers/chat.ts index 9b9a02e39..f656375a7 100644 --- a/src/addon/mod/chat/providers/chat.ts +++ b/src/addon/mod/chat/providers/chat.ts @@ -19,6 +19,7 @@ import { CoreUserProvider } from '@core/user/providers/user'; import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; +import { CoreWSExternalWarning, CoreWSExternalFile } from '@providers/ws'; /** * Service that provides some features for chats. @@ -41,7 +42,7 @@ export class AddonModChatProvider { * @param siteId Site ID. If not defined, current site. * @return Promise resolved when the chat is retrieved. */ - getChat(courseId: number, cmId: number, siteId?: string): Promise { + getChat(courseId: number, cmId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const params = { courseids: [courseId] @@ -51,7 +52,9 @@ export class AddonModChatProvider { updateFrequency: CoreSite.FREQUENCY_RARELY }; - return site.read('mod_chat_get_chats_by_courses', params, preSets).then((response) => { + return site.read('mod_chat_get_chats_by_courses', params, preSets) + .then((response: AddonModChatGetChatsByCoursesResult): any => { + if (response.chats) { const chat = response.chats.find((chat) => chat.coursemodule == cmId); if (chat) { @@ -70,12 +73,14 @@ export class AddonModChatProvider { * @param chatId Chat instance ID. * @return Promise resolved when the WS is executed. */ - loginUser(chatId: number): Promise { + loginUser(chatId: number): Promise { const params = { chatid: chatId }; - return this.sitesProvider.getCurrentSite().write('mod_chat_login_user', params).then((response) => { + return this.sitesProvider.getCurrentSite().write('mod_chat_login_user', params) + .then((response: AddonModChatLoginUserResult): any => { + if (response.chatsid) { return response.chatsid; } @@ -108,14 +113,16 @@ export class AddonModChatProvider { * @param beepUserId Beep user ID. * @return Promise resolved when the WS is executed. */ - sendMessage(sessionId: number, message: string, beepUserId: number): Promise { + sendMessage(sessionId: string, message: string, beepUserId: number): Promise { const params = { chatsid: sessionId, messagetext: message, beepid: beepUserId }; - return this.sitesProvider.getCurrentSite().write('mod_chat_send_chat_message', params).then((response) => { + return this.sitesProvider.getCurrentSite().write('mod_chat_send_chat_message', params) + .then((response: AddonModChatSendChatMessageResult): any => { + if (response.messageid) { return response.messageid; } @@ -131,7 +138,7 @@ export class AddonModChatProvider { * @param lastTime Last time when messages were retrieved. * @return Promise resolved when the WS is executed. */ - getLatestMessages(sessionId: number, lastTime: number): Promise { + getLatestMessages(sessionId: string, lastTime: number): Promise { const params = { chatsid: sessionId, chatlasttime: lastTime @@ -149,8 +156,10 @@ export class AddonModChatProvider { * @param courseId ID of the course the messages belong to. * @return Promise always resolved with the formatted messages. */ - getMessagesUserData(messages: any[], courseId: number): Promise { - const promises = messages.map((message) => { + getMessagesUserData(messages: (AddonModChatMessage | AddonModChatSessionMessage)[], courseId: number) + : Promise<(AddonModChatMessageWithUserData | AddonModChatSessionMessageWithUserData)[]> { + + const promises = messages.map((message: AddonModChatMessageWithUserData | AddonModChatSessionMessageWithUserData) => { return this.userProvider.getProfile(message.userid, courseId, true).then((user) => { message.userfullname = user.fullname; message.userprofileimageurl = user.profileimageurl; @@ -171,7 +180,7 @@ export class AddonModChatProvider { * @param sessionId Chat sessiond ID. * @return Promise resolved when the WS is executed. */ - getChatUsers(sessionId: number): Promise { + getChatUsers(sessionId: string): Promise { const params = { chatsid: sessionId }; @@ -206,7 +215,8 @@ export class AddonModChatProvider { * @since 3.5 */ getSessions(chatId: number, groupId: number = 0, showAll: boolean = false, ignoreCache: boolean = false, siteId?: string): - Promise { + Promise { + return this.sitesProvider.getSite(siteId).then((site) => { const params = { chatid: chatId, @@ -222,7 +232,7 @@ export class AddonModChatProvider { preSets.emergencyCache = false; } - return site.read('mod_chat_get_sessions', params, preSets).then((response) => { + return site.read('mod_chat_get_sessions', params, preSets).then((response: AddonModChatGetSessionsResult): any => { if (!response || !response.sessions) { return Promise.reject(null); } @@ -245,7 +255,8 @@ export class AddonModChatProvider { * @since 3.5 */ getSessionMessages(chatId: number, sessionStart: number, sessionEnd: number, groupId: number = 0, ignoreCache: boolean = false, - siteId?: string): Promise { + siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { const params = { chatid: chatId, @@ -262,7 +273,9 @@ export class AddonModChatProvider { preSets.emergencyCache = false; } - return site.read('mod_chat_get_session_messages', params, preSets).then((response) => { + return site.read('mod_chat_get_session_messages', params, preSets) + .then((response: AddonModChatGetSessionMessagesResult): any => { + if (!response || !response.messages) { return Promise.reject(null); } @@ -390,3 +403,152 @@ export class AddonModChatProvider { return this.ROOT_CACHE_KEY + 'sessionsMessages:' + chatId + ':'; } } + +/** + * Chat returned by mod_chat_get_chats_by_courses. + */ +export type AddonModChatChat = { + id: number; // Chat id. + coursemodule: number; // Course module id. + course: number; // Course id. + name: string; // Chat name. + intro: string; // The Chat intro. + introformat: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). + introfiles?: CoreWSExternalFile[]; // @since 3.2. + chatmethod?: string; // Chat method (sockets, ajax, header_js). + keepdays?: number; // Keep days. + studentlogs?: number; // Student logs visible to everyone. + chattime?: number; // Chat time. + schedule?: number; // Schedule type. + timemodified?: number; // Time of last modification. + section?: number; // Course section id. + visible?: boolean; // Visible. + groupmode?: number; // Group mode. + groupingid?: number; // Group id. +}; + +/** + * Chat user returned by mod_chat_get_chat_users. + */ +export type AddonModChatUser = { + id: number; // User id. + fullname: string; // User full name. + profileimageurl: string; // User picture URL. +}; + +/** + * Meessage returned by mod_chat_get_chat_latest_messages. + */ +export type AddonModChatMessage = { + id: number; // Message id. + userid: number; // User id. + system: boolean; // True if is a system message (like user joined). + message: string; // Message text. + timestamp: number; // Timestamp for the message. +}; + +/** + * Message with user data + */ +export type AddonModChatMessageWithUserData = AddonModChatMessage & AddonModChatMessageUserData; + +/** + * Chat session. + */ +export type AddonModChatSession = { + sessionstart: number; // Session start time. + sessionend: number; // Session end time. + sessionusers: AddonModChatSessionUser[]; // Session users. + iscomplete: boolean; // Whether the session is completed or not. +}; + +/** + * Chat user returned by mod_chat_get_sessions. + */ +export type AddonModChatSessionUser = { + userid: number; // User id. + messagecount: number; // Number of messages in the session. +}; + +/** + * Message returned by mod_chat_get_session_messages. + */ +export type AddonModChatSessionMessage = { + id: number; // The message record id. + chatid: number; // The chat id. + userid: number; // The user who wrote the message. + groupid: number; // The group this message belongs to. + issystem: boolean; // Whether is a system message or not. + message: string; // The message text. + timestamp: number; // The message timestamp (indicates when the message was sent). +}; + +/** + * Message with user data + */ +export type AddonModChatSessionMessageWithUserData = AddonModChatSessionMessage & AddonModChatMessageUserData; + +/** + * Result of WS mod_chat_get_chats_by_courses. + */ +export type AddonModChatGetChatsByCoursesResult = { + chats: AddonModChatChat[]; + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Result of WS mod_chat_get_chat_users. + */ +export type AddonModChatGetChatUsersResult = { + users: AddonModChatUser[]; // List of users. + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Result of WS mod_chat_get_sessions. + */ +export type AddonModChatGetSessionsResult = { + sessions: AddonModChatSession[]; // List of sessions. + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Result of WS mod_chat_get_session_messages. + */ +export type AddonModChatGetSessionMessagesResult = { + messages: AddonModChatSessionMessage[]; + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Result of WS mod_chat_send_chat_message. + */ +export type AddonModChatSendChatMessageResult = { + messageid: number; // Message sent id. + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Result of WS mod_chat_get_chat_latest_messages. + */ +export type AddonModChatGetChatLatestMessagesResult = { + messages: AddonModChatMessage[]; // List of messages. + chatnewlasttime: number; // New last time. + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Result of WS mod_chat_login_user. + */ +export type AddonModChatLoginUserResult = { + chatsid: string; // Unique chat session id. + warnings?: CoreWSExternalWarning[]; +}; + +/** + * User data added to messages. + */ +type AddonModChatMessageUserData = { + userfullname?: string; // Calculated in the app. Full name of the user who wrote the message. + userprofileimageurl?: string; // Calculated in the app. Full name of the user who wrote the message. +}; diff --git a/src/addon/mod/chat/providers/prefetch-handler.ts b/src/addon/mod/chat/providers/prefetch-handler.ts index ca146b06d..11b3d9183 100644 --- a/src/addon/mod/chat/providers/prefetch-handler.ts +++ b/src/addon/mod/chat/providers/prefetch-handler.ts @@ -23,7 +23,7 @@ import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseActivityPrefetchHandlerBase } from '@core/course/classes/activity-prefetch-handler'; import { CoreUserProvider } from '@core/user/providers/user'; -import { AddonModChatProvider } from './chat'; +import { AddonModChatProvider, AddonModChatChat } from './chat'; /** * Handler to prefetch chats. @@ -116,12 +116,12 @@ export class AddonModChatPrefetchHandler extends CoreCourseActivityPrefetchHandl */ protected prefetchChat(module: any, courseId: number, single: boolean, siteId: string): Promise { // Prefetch chat and group info. - const promises = [ + const promises: Promise[] = [ this.chatProvider.getChat(courseId, module.id, siteId), this.groupsProvider.getActivityGroupInfo(module.id, false, undefined, siteId) ]; - return Promise.all(promises).then(([chat, groupInfo]: [any, CoreGroupInfo]) => { + return Promise.all(promises).then(([chat, groupInfo]: [AddonModChatChat, CoreGroupInfo]) => { const promises = []; let groupIds = [0]; From 3647b7bfa0885934c57f4d65cb0ee4f9fadf0401 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 6 Sep 2019 17:31:47 +0200 Subject: [PATCH 020/257] MOBILE-3109 choice: Add return types to choice --- .../index/addon-mod-choice-index.html | 8 +- .../mod/choice/components/index/index.ts | 35 +++-- src/addon/mod/choice/providers/choice.ts | 122 ++++++++++++++++-- 3 files changed, 139 insertions(+), 26 deletions(-) diff --git a/src/addon/mod/choice/components/index/addon-mod-choice-index.html b/src/addon/mod/choice/components/index/addon-mod-choice-index.html index a9e81b5f6..cfeaddc5f 100644 --- a/src/addon/mod/choice/components/index/addon-mod-choice-index.html +++ b/src/addon/mod/choice/components/index/addon-mod-choice-index.html @@ -19,13 +19,13 @@ -

{{ 'addon.mod_choice.previewonly' | translate:{$a: choice.openTimeReadable} }}

-

{{ 'addon.mod_choice.notopenyet' | translate:{$a: choice.openTimeReadable} }}

+

{{ 'addon.mod_choice.previewonly' | translate:{$a: openTimeReadable} }}

+

{{ 'addon.mod_choice.notopenyet' | translate:{$a: openTimeReadable} }}

{{ 'addon.mod_choice.yourselection' | translate }}

-

{{ 'addon.mod_choice.expired' | translate:{$a: choice.closeTimeReadable} }}

+

{{ 'addon.mod_choice.expired' | translate:{$a: closeTimeReadable} }}

@@ -80,7 +80,7 @@

-

{{ 'addon.mod_choice.numberofuser' | translate }}: {{ result.numberofuser }} ({{ 'core.percentagenumber' | translate: {$a: result.percentageamount} }})

+

{{ 'addon.mod_choice.numberofuser' | translate }}: {{ result.numberofuser }} ({{ 'core.percentagenumber' | translate: {$a: result.percentageamountfixed} }})

diff --git a/src/addon/mod/choice/components/index/index.ts b/src/addon/mod/choice/components/index/index.ts index c5bcdc852..6e3b5799e 100644 --- a/src/addon/mod/choice/components/index/index.ts +++ b/src/addon/mod/choice/components/index/index.ts @@ -16,7 +16,7 @@ import { Component, Optional, Injector } from '@angular/core'; import { Content } from 'ionic-angular'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main-activity-component'; -import { AddonModChoiceProvider } from '../../providers/choice'; +import { AddonModChoiceProvider, AddonModChoiceChoice, AddonModChoiceOption, AddonModChoiceResult } from '../../providers/choice'; import { AddonModChoiceOfflineProvider } from '../../providers/offline'; import { AddonModChoiceSyncProvider } from '../../providers/sync'; @@ -31,9 +31,9 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo component = AddonModChoiceProvider.COMPONENT; moduleName = 'choice'; - choice: any; - options = []; - selectedOption: any; + choice: AddonModChoiceChoice; + options: AddonModChoiceOption[] = []; + selectedOption: {id: number}; choiceNotOpenYet = false; choiceClosed = false; canEdit = false; @@ -43,6 +43,8 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo labels = []; results = []; publishInfo: string; // Message explaining the user what will happen with his choices. + openTimeReadable: string; + closeTimeReadable: string; protected userId: number; protected syncEventName = AddonModChoiceSyncProvider.AUTO_SYNCED; @@ -122,12 +124,12 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo return this.choiceProvider.getChoice(this.courseId, this.module.id).then((choice) => { this.choice = choice; - this.choice.timeopen = parseInt(choice.timeopen) * 1000; - this.choice.openTimeReadable = this.timeUtils.userDate(choice.timeopen); - this.choice.timeclose = parseInt(choice.timeclose) * 1000; - this.choice.closeTimeReadable = this.timeUtils.userDate(choice.timeclose); + this.choice.timeopen = choice.timeopen * 1000; + this.choice.timeclose = choice.timeclose * 1000; + this.openTimeReadable = this.timeUtils.userDate(choice.timeopen); + this.closeTimeReadable = this.timeUtils.userDate(choice.timeclose); - this.description = choice.intro || choice.description; + this.description = choice.intro; this.choiceNotOpenYet = choice.timeopen && choice.timeopen > this.now; this.choiceClosed = choice.timeclose && choice.timeclose <= this.now; @@ -175,7 +177,7 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo if (hasOffline) { promise = this.choiceOffline.getResponse(this.choice.id).then((response) => { - const optionsKeys = {}; + const optionsKeys: {[id: number]: AddonModChoiceOption} = {}; options.forEach((option) => { optionsKeys[option.id] = option; }); @@ -223,7 +225,7 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo promise = Promise.resolve(options); } - promise.then((options) => { + promise.then((options: AddonModChoiceOption[]) => { const isOpen = this.isChoiceOpen(); let hasAnswered = false; @@ -291,11 +293,11 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo let hasVotes = false; this.data = []; this.labels = []; - results.forEach((result) => { + results.forEach((result: AddonModChoiceResultFormatted) => { if (result.numberofuser > 0) { hasVotes = true; } - result.percentageamount = parseFloat(result.percentageamount).toFixed(1); + result.percentageamountfixed = result.percentageamount.toFixed(1); this.data.push(result.numberofuser); this.labels.push(result.text); }); @@ -429,3 +431,10 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo return result.updated; } } + +/** + * Choice result with some calculated data. + */ +export type AddonModChoiceResultFormatted = AddonModChoiceResult & { + percentageamountfixed: string; // Percentage of users answers with fixed decimals. +}; diff --git a/src/addon/mod/choice/providers/choice.ts b/src/addon/mod/choice/providers/choice.ts index b16119516..246903592 100644 --- a/src/addon/mod/choice/providers/choice.ts +++ b/src/addon/mod/choice/providers/choice.ts @@ -20,6 +20,7 @@ import { CoreFilepoolProvider } from '@providers/filepool'; import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { AddonModChoiceOfflineProvider } from './offline'; import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; +import { CoreWSExternalWarning, CoreWSExternalFile } from '@providers/ws'; /** * Service that provides some features for choices. @@ -118,7 +119,9 @@ export class AddonModChoiceProvider { responses: responses }; - return site.write('mod_choice_delete_choice_responses', params).then((response) => { + return site.write('mod_choice_delete_choice_responses', params) + .then((response: AddonModChoiceDeleteChoiceResponsesResult) => { + // Other errors ocurring. if (!response || response.status === false) { return Promise.reject(this.utils.createFakeWSError('')); @@ -179,7 +182,7 @@ export class AddonModChoiceProvider { * @return Promise resolved when the choice is retrieved. */ protected getChoiceByDataKey(siteId: string, courseId: number, key: string, value: any, forceCache?: boolean, - ignoreCache?: boolean): Promise { + ignoreCache?: boolean): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const params = { @@ -198,7 +201,9 @@ export class AddonModChoiceProvider { preSets.emergencyCache = false; } - return site.read('mod_choice_get_choices_by_courses', params, preSets).then((response) => { + return site.read('mod_choice_get_choices_by_courses', params, preSets) + .then((response: AddonModChoiceGetChoicesByCoursesResult): any => { + if (response && response.choices) { const currentChoice = response.choices.find((choice) => choice[key] == value); if (currentChoice) { @@ -221,7 +226,8 @@ export class AddonModChoiceProvider { * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). * @return Promise resolved when the choice is retrieved. */ - getChoice(courseId: number, cmId: number, siteId?: string, forceCache?: boolean, ignoreCache?: boolean): Promise { + getChoice(courseId: number, cmId: number, siteId?: string, forceCache?: boolean, ignoreCache?: boolean) + : Promise { return this.getChoiceByDataKey(siteId, courseId, 'coursemodule', cmId, forceCache, ignoreCache); } @@ -235,7 +241,8 @@ export class AddonModChoiceProvider { * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). * @return Promise resolved when the choice is retrieved. */ - getChoiceById(courseId: number, choiceId: number, siteId?: string, forceCache?: boolean, ignoreCache?: boolean): Promise { + getChoiceById(courseId: number, choiceId: number, siteId?: string, forceCache?: boolean, ignoreCache?: boolean) + : Promise { return this.getChoiceByDataKey(siteId, courseId, 'id', choiceId, forceCache, ignoreCache); } @@ -247,7 +254,7 @@ export class AddonModChoiceProvider { * @param siteId Site ID. If not defined, current site. * @return Promise resolved with choice options. */ - getOptions(choiceId: number, ignoreCache?: boolean, siteId?: string): Promise { + getOptions(choiceId: number, ignoreCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const params = { choiceid: choiceId @@ -262,7 +269,9 @@ export class AddonModChoiceProvider { preSets.emergencyCache = false; } - return site.read('mod_choice_get_choice_options', params, preSets).then((response) => { + return site.read('mod_choice_get_choice_options', params, preSets) + .then((response: AddonModChoiceGetChoiceOptionsResult): any => { + if (response.options) { return response.options; } @@ -280,7 +289,7 @@ export class AddonModChoiceProvider { * @param siteId Site ID. If not defined, current site. * @return Promise resolved with choice results. */ - getResults(choiceId: number, ignoreCache?: boolean, siteId?: string): Promise { + getResults(choiceId: number, ignoreCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const params = { choiceid: choiceId @@ -294,7 +303,9 @@ export class AddonModChoiceProvider { preSets.emergencyCache = false; } - return site.read('mod_choice_get_choice_results', params, preSets).then((response) => { + return site.read('mod_choice_get_choice_results', params, preSets) + .then((response: AddonModChoiceGetChoiceResults): any => { + if (response.options) { return response.options; } @@ -456,3 +467,96 @@ export class AddonModChoiceProvider { }); } } + +/** + * Choice returned by mod_choice_get_choices_by_courses. + */ +export type AddonModChoiceChoice = { + id: number; // Choice instance id. + coursemodule: number; // Course module id. + course: number; // Course id. + name: string; // Choice name. + intro: string; // The choice intro. + introformat: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). + introfiles?: CoreWSExternalFile[]; // @since 3.2. + publish?: boolean; // If choice is published. + showresults?: number; // 0 never, 1 after answer, 2 after close, 3 always. + display?: number; // Display mode (vertical, horizontal). + allowupdate?: boolean; // Allow update. + allowmultiple?: boolean; // Allow multiple choices. + showunanswered?: boolean; // Show users who not answered yet. + includeinactive?: boolean; // Include inactive users. + limitanswers?: boolean; // Limit unswers. + timeopen?: number; // Date of opening validity. + timeclose?: number; // Date of closing validity. + showpreview?: boolean; // Show preview before timeopen. + timemodified?: number; // Time of last modification. + completionsubmit?: boolean; // Completion on user submission. + section?: number; // Course section id. + visible?: boolean; // Visible. + groupmode?: number; // Group mode. + groupingid?: number; // Group id. +}; + +/** + * Option returned by mod_choice_get_choice_options. + */ +export type AddonModChoiceOption = { + id: number; // Option id. + text: string; // Text of the choice. + maxanswers: number; // Maximum number of answers. + displaylayout: boolean; // True for orizontal, otherwise vertical. + countanswers: number; // Number of answers. + checked: boolean; // We already answered. + disabled: boolean; // Option disabled. +}; + +/** + * Result returned by mod_choice_get_choice_results. + */ +export type AddonModChoiceResult = { + id: number; // Choice instance id. + text: string; // Text of the choice. + maxanswer: number; // Maximum number of answers. + userresponses: { + userid: number; // User id. + fullname: string; // User full name. + profileimageurl: string; // Profile user image url. + answerid?: number; // Answer id. + timemodified?: number; // Time of modification. + }[]; + numberofuser: number; // Number of users answers. + percentageamount: number; // Percentage of users answers. +}; + +/** + * Result of WS mod_choice_get_choices_by_courses. + */ +export type AddonModChoiceGetChoicesByCoursesResult = { + choices: AddonModChoiceChoice[]; + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Result of WS mod_choice_get_choice_options. + */ +export type AddonModChoiceGetChoiceOptionsResult = { + options: AddonModChoiceOption[]; // Options. + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Result of WS mod_choice_get_choice_results. + */ +export type AddonModChoiceGetChoiceResults = { + options: AddonModChoiceResult[]; + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Result of WS mod_choice_delete_choice_responses. + */ +export type AddonModChoiceDeleteChoiceResponsesResult = { + status: boolean; // Status, true if everything went right. + warnings?: CoreWSExternalWarning[]; +}; From a2fbe1c808a01d7f9d2a953c55a5132ced37f295 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 10 Sep 2019 08:38:09 +0200 Subject: [PATCH 021/257] MOBILE-3109 lti: Add return types to lti --- src/addon/mod/lti/components/index/index.ts | 6 +- src/addon/mod/lti/providers/lti.ts | 83 ++++++++++++++++++--- 2 files changed, 76 insertions(+), 13 deletions(-) diff --git a/src/addon/mod/lti/components/index/index.ts b/src/addon/mod/lti/components/index/index.ts index 53819e792..f454dec08 100644 --- a/src/addon/mod/lti/components/index/index.ts +++ b/src/addon/mod/lti/components/index/index.ts @@ -15,7 +15,7 @@ import { Component, Optional, Injector } from '@angular/core'; import { Content } from 'ionic-angular'; import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main-activity-component'; -import { AddonModLtiProvider } from '../../providers/lti'; +import { AddonModLtiProvider, AddonModLtiLti } from '../../providers/lti'; /** * Component that displays an LTI entry page. @@ -28,7 +28,7 @@ export class AddonModLtiIndexComponent extends CoreCourseModuleMainActivityCompo component = AddonModLtiProvider.COMPONENT; moduleName = 'lti'; - lti: any; // The LTI object. + lti: AddonModLtiLti; // The LTI object. protected fetchContentDefaultError = 'addon.mod_lti.errorgetlti'; @@ -65,7 +65,7 @@ export class AddonModLtiIndexComponent extends CoreCourseModuleMainActivityCompo protected fetchContent(refresh: boolean = false, sync: boolean = false, showErrors: boolean = false): Promise { return this.ltiProvider.getLti(this.courseId, this.module.id).then((ltiData) => { this.lti = ltiData; - this.description = this.lti.intro || this.description; + this.description = this.lti.intro; this.dataRetrieved.emit(this.lti); }).then(() => { // All data obtained, now fill the context menu. diff --git a/src/addon/mod/lti/providers/lti.ts b/src/addon/mod/lti/providers/lti.ts index 73eef5121..8ea9b09e9 100644 --- a/src/addon/mod/lti/providers/lti.ts +++ b/src/addon/mod/lti/providers/lti.ts @@ -22,11 +22,7 @@ import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreUrlUtilsProvider } from '@providers/utils/url'; import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreSite } from '@classes/site'; - -export interface AddonModLtiParam { - name: string; - value: string; -} +import { CoreWSExternalWarning, CoreWSExternalFile } from '@providers/ws'; /** * Service that provides some features for LTI. @@ -104,7 +100,7 @@ export class AddonModLtiProvider { * @param cmId Course module ID. * @return Promise resolved when the LTI is retrieved. */ - getLti(courseId: number, cmId: number): Promise { + getLti(courseId: number, cmId: number): Promise { const params: any = { courseids: [courseId] }; @@ -113,7 +109,9 @@ export class AddonModLtiProvider { updateFrequency: CoreSite.FREQUENCY_RARELY }; - return this.sitesProvider.getCurrentSite().read('mod_lti_get_ltis_by_courses', params, preSets).then((response) => { + return this.sitesProvider.getCurrentSite().read('mod_lti_get_ltis_by_courses', params, preSets) + .then((response: AddonModLtiGetLtisByCoursesResult): any => { + if (response.ltis) { const currentLti = response.ltis.find((lti) => lti.coursemodule == cmId); if (currentLti) { @@ -141,8 +139,8 @@ export class AddonModLtiProvider { * @param id LTI id. * @return Promise resolved when the launch data is retrieved. */ - getLtiLaunchData(id: number): Promise { - const params: any = { + getLtiLaunchData(id: number): Promise { + const params = { toolid: id }; @@ -154,7 +152,9 @@ export class AddonModLtiProvider { cacheKey: this.getLtiLaunchDataCacheKey(id) }; - return this.sitesProvider.getCurrentSite().read('mod_lti_get_tool_launch_data', params, preSets).then((response) => { + return this.sitesProvider.getCurrentSite().read('mod_lti_get_tool_launch_data', params, preSets) + .then((response: AddonModLtiGetToolLaunchDataResult): any => { + if (response.endpoint) { return response; } @@ -227,3 +227,66 @@ export class AddonModLtiProvider { return this.logHelper.logSingle('mod_lti_view_lti', params, AddonModLtiProvider.COMPONENT, id, name, 'lti', {}, siteId); } } + +/** + * LTI returned by mod_lti_get_ltis_by_courses. + */ +export type AddonModLtiLti = { + id: number; // External tool id. + coursemodule: number; // Course module id. + course: number; // Course id. + name: string; // LTI name. + intro?: string; // The LTI intro. + introformat?: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). + introfiles?: CoreWSExternalFile[]; // @since 3.2. + timecreated?: number; // Time of creation. + timemodified?: number; // Time of last modification. + typeid?: number; // Type id. + toolurl?: string; // Tool url. + securetoolurl?: string; // Secure tool url. + instructorchoicesendname?: string; // Instructor choice send name. + instructorchoicesendemailaddr?: number; // Instructor choice send mail address. + instructorchoiceallowroster?: number; // Instructor choice allow roster. + instructorchoiceallowsetting?: number; // Instructor choice allow setting. + instructorcustomparameters?: string; // Instructor custom parameters. + instructorchoiceacceptgrades?: number; // Instructor choice accept grades. + grade?: number; // Enable grades. + launchcontainer?: number; // Launch container mode. + resourcekey?: string; // Resource key. + password?: string; // Shared secret. + debuglaunch?: number; // Debug launch. + showtitlelaunch?: number; // Show title launch. + showdescriptionlaunch?: number; // Show description launch. + servicesalt?: string; // Service salt. + icon?: string; // Alternative icon URL. + secureicon?: string; // Secure icon URL. + section?: number; // Course section id. + visible?: number; // Visible. + groupmode?: number; // Group mode. + groupingid?: number; // Group id. +}; + +/** + * Param to send to the LTI. + */ +export type AddonModLtiParam = { + name: string; // Parameter name. + value: string; // Parameter value. +}; + +/** + * Result of WS mod_lti_get_ltis_by_courses. + */ +export type AddonModLtiGetLtisByCoursesResult = { + ltis: AddonModLtiLti[]; + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Result of WS mod_lti_get_tool_launch_data. + */ +export type AddonModLtiGetToolLaunchDataResult = { + endpoint: string; // Endpoint URL. + parameters: AddonModLtiParam[]; + warnings?: CoreWSExternalWarning[]; +}; From a779e69b4987628deecc483b8b87e988947e16c0 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 10 Sep 2019 09:39:15 +0200 Subject: [PATCH 022/257] MOBILE-3109 survey: Add return types to survey --- .../index/addon-mod-survey-index.html | 12 +-- .../mod/survey/components/index/index.ts | 16 ++-- src/addon/mod/survey/providers/helper.ts | 29 +++++-- src/addon/mod/survey/providers/survey.ts | 87 ++++++++++++++++--- 4 files changed, 111 insertions(+), 33 deletions(-) diff --git a/src/addon/mod/survey/components/index/addon-mod-survey-index.html b/src/addon/mod/survey/components/index/addon-mod-survey-index.html index da07713d4..3f5eec9f1 100644 --- a/src/addon/mod/survey/components/index/addon-mod-survey-index.html +++ b/src/addon/mod/survey/components/index/addon-mod-survey-index.html @@ -36,14 +36,14 @@ -
+

{{ question.text }}

{{ 'addon.mod_survey.responses' | translate }}
- +
{{ option }}
@@ -61,20 +61,20 @@ - + {{ 'core.choose' | translate }} - {{option}} + {{option}}
- + @@ -82,7 +82,7 @@ - {{option}} + {{option}} diff --git a/src/addon/mod/survey/components/index/index.ts b/src/addon/mod/survey/components/index/index.ts index 23f479553..e1715f02a 100644 --- a/src/addon/mod/survey/components/index/index.ts +++ b/src/addon/mod/survey/components/index/index.ts @@ -15,8 +15,8 @@ import { Component, Optional, Injector } from '@angular/core'; import { Content } from 'ionic-angular'; import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main-activity-component'; -import { AddonModSurveyProvider } from '../../providers/survey'; -import { AddonModSurveyHelperProvider } from '../../providers/helper'; +import { AddonModSurveyProvider, AddonModSurveySurvey } from '../../providers/survey'; +import { AddonModSurveyHelperProvider, AddonModSurveyQuestionFormatted } from '../../providers/helper'; import { AddonModSurveyOfflineProvider } from '../../providers/offline'; import { AddonModSurveySyncProvider } from '../../providers/sync'; @@ -31,8 +31,8 @@ export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityCo component = AddonModSurveyProvider.COMPONENT; moduleName = 'survey'; - survey: any; - questions: any; + survey: AddonModSurveySurvey; + questions: AddonModSurveyQuestionFormatted[]; answers = {}; protected userId: number; @@ -103,7 +103,7 @@ export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityCo return this.surveyProvider.getSurvey(this.courseId, this.module.id).then((survey) => { this.survey = survey; - this.description = survey.intro || survey.description; + this.description = survey.intro; this.dataRetrieved.emit(survey); if (sync) { @@ -144,13 +144,13 @@ export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityCo // Init answers object. this.questions.forEach((q) => { if (q.name) { - const isTextArea = q.multi && q.multi.length === 0 && q.type === 0; + const isTextArea = q.multiArray && q.multiArray.length === 0 && q.type === 0; this.answers[q.name] = q.required ? -1 : (isTextArea ? '' : '0'); } - if (q.multi && !q.multi.length && q.parent === 0 && q.type > 0) { + if (q.multiArray && !q.multiArray.length && q.parent === 0 && q.type > 0) { // Options shown in a select. Remove all HTML. - q.options = q.options.map((option) => { + q.optionsArray = q.optionsArray.map((option) => { return this.textUtils.cleanTags(option); }); } diff --git a/src/addon/mod/survey/providers/helper.ts b/src/addon/mod/survey/providers/helper.ts index 23d7dc9b4..1553bd9d4 100644 --- a/src/addon/mod/survey/providers/helper.ts +++ b/src/addon/mod/survey/providers/helper.ts @@ -14,6 +14,7 @@ import { Injectable } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; +import { AddonModSurveyQuestion } from './survey'; /** * Service that provides helper functions for surveys. @@ -29,7 +30,7 @@ export class AddonModSurveyHelperProvider { * @param value Value to convert. * @return Array. */ - protected commaStringToArray(value: any): string[] { + protected commaStringToArray(value: string | string[]): string[] { if (typeof value == 'string') { if (value.length > 0) { return value.split(','); @@ -47,7 +48,7 @@ export class AddonModSurveyHelperProvider { * @param questions Questions. * @return Object with parent questions. */ - protected getParentQuestions(questions: any[]): any { + protected getParentQuestions(questions: AddonModSurveyQuestion[]): {[id: number]: AddonModSurveyQuestion} { const parents = {}; questions.forEach((question) => { @@ -66,24 +67,24 @@ export class AddonModSurveyHelperProvider { * @param questions Questions. * @return Promise resolved with the formatted questions. */ - formatQuestions(questions: any[]): any[] { + formatQuestions(questions: AddonModSurveyQuestion[]): AddonModSurveyQuestionFormatted[] { const strIPreferThat = this.translate.instant('addon.mod_survey.ipreferthat'), strIFoundThat = this.translate.instant('addon.mod_survey.ifoundthat'), strChoose = this.translate.instant('core.choose'), - formatted = [], + formatted: AddonModSurveyQuestionFormatted[] = [], parents = this.getParentQuestions(questions); let num = 1; questions.forEach((question) => { // Copy the object to prevent modifying the original. - const q1 = Object.assign({}, question), + const q1: AddonModSurveyQuestionFormatted = Object.assign({}, question), parent = parents[q1.parent]; // Turn multi and options into arrays. - q1.multi = this.commaStringToArray(q1.multi); - q1.options = this.commaStringToArray(q1.options); + q1.multiArray = this.commaStringToArray(q1.multi); + q1.optionsArray = this.commaStringToArray(q1.options); if (parent) { // It's a sub-question. @@ -114,7 +115,7 @@ export class AddonModSurveyHelperProvider { q1.name = 'q' + q1.id; q1.num = num++; if (q1.type > 0) { // Add "choose" option since this question is not required. - q1.options.unshift(strChoose); + q1.optionsArray.unshift(strChoose); } } @@ -123,5 +124,15 @@ export class AddonModSurveyHelperProvider { return formatted; } - } + +/** + * Survey question with some calculated data. + */ +export type AddonModSurveyQuestionFormatted = AddonModSurveyQuestion & { + required?: boolean; // Calculated in the app. Whether the question is required. + name?: string; // Calculated in the app. The name of the question. + num?: number; // Calculated in the app. Number of the question. + multiArray?: string[]; // Subquestions ids, converted to an array. + optionsArray?: string[]; // Question options, converted to an array. +}; diff --git a/src/addon/mod/survey/providers/survey.ts b/src/addon/mod/survey/providers/survey.ts index 078fae02c..428a3db39 100644 --- a/src/addon/mod/survey/providers/survey.ts +++ b/src/addon/mod/survey/providers/survey.ts @@ -21,6 +21,7 @@ import { CoreFilepoolProvider } from '@providers/filepool'; import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { AddonModSurveyOfflineProvider } from './offline'; import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; +import { CoreWSExternalWarning, CoreWSExternalFile } from '@providers/ws'; /** * Service that provides some features for surveys. @@ -46,7 +47,7 @@ export class AddonModSurveyProvider { * @param siteId Site ID. If not defined, current site. * @return Promise resolved when the questions are retrieved. */ - getQuestions(surveyId: number, ignoreCache?: boolean, siteId?: string): Promise { + getQuestions(surveyId: number, ignoreCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const params = { surveyid: surveyId @@ -61,7 +62,9 @@ export class AddonModSurveyProvider { preSets.emergencyCache = false; } - return site.read('mod_survey_get_questions', params, preSets).then((response) => { + return site.read('mod_survey_get_questions', params, preSets) + .then((response: AddonModSurveyGetQuestionsResult): any => { + if (response.questions) { return response.questions; } @@ -101,7 +104,9 @@ export class AddonModSurveyProvider { * @param siteId Site ID. If not defined, current site. * @return Promise resolved when the survey is retrieved. */ - protected getSurveyDataByKey(courseId: number, key: string, value: any, ignoreCache?: boolean, siteId?: string): Promise { + protected getSurveyDataByKey(courseId: number, key: string, value: any, ignoreCache?: boolean, siteId?: string) + : Promise { + return this.sitesProvider.getSite(siteId).then((site) => { const params = { courseids: [courseId] @@ -116,7 +121,9 @@ export class AddonModSurveyProvider { preSets.emergencyCache = false; } - return site.read('mod_survey_get_surveys_by_courses', params, preSets).then((response) => { + return site.read('mod_survey_get_surveys_by_courses', params, preSets) + .then((response: AddonModSurveyGetSurveysByCoursesResult): any => { + if (response && response.surveys) { const currentSurvey = response.surveys.find((survey) => { return survey[key] == value; @@ -140,7 +147,7 @@ export class AddonModSurveyProvider { * @param siteId Site ID. If not defined, current site. * @return Promise resolved when the survey is retrieved. */ - getSurvey(courseId: number, cmId: number, ignoreCache?: boolean, siteId?: string): Promise { + getSurvey(courseId: number, cmId: number, ignoreCache?: boolean, siteId?: string): Promise { return this.getSurveyDataByKey(courseId, 'coursemodule', cmId, ignoreCache, siteId); } @@ -153,7 +160,7 @@ export class AddonModSurveyProvider { * @param siteId Site ID. If not defined, current site. * @return Promise resolved when the survey is retrieved. */ - getSurveyById(courseId: number, id: number, ignoreCache?: boolean, siteId?: string): Promise { + getSurveyById(courseId: number, id: number, ignoreCache?: boolean, siteId?: string): Promise { return this.getSurveyDataByKey(courseId, 'id', id, ignoreCache, siteId); } @@ -277,17 +284,16 @@ export class AddonModSurveyProvider { * @param surveyId Survey ID. * @param answers Answers. * @param siteId Site ID. If not defined, current site. - * @return Promise resolved when answers are successfully submitted. Rejected with object containing - * the error message (if any) and a boolean indicating if the error was returned by WS. + * @return Promise resolved when answers are successfully submitted. */ - submitAnswersOnline(surveyId: number, answers: any[], siteId?: string): Promise { + submitAnswersOnline(surveyId: number, answers: any[], siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const params = { surveyid: surveyId, answers: answers }; - return site.write('mod_survey_submit_answers', params).then((response) => { + return site.write('mod_survey_submit_answers', params).then((response: AddonModSurveySubmitAnswersResult) => { if (!response.status) { return Promise.reject(this.utils.createFakeWSError('')); } @@ -295,3 +301,64 @@ export class AddonModSurveyProvider { }); } } + +/** + * Survey returned by WS mod_survey_get_surveys_by_courses. + */ +export type AddonModSurveySurvey = { + id: number; // Survey id. + coursemodule: number; // Course module id. + course: number; // Course id. + name: string; // Survey name. + intro?: string; // The Survey intro. + introformat?: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). + introfiles?: CoreWSExternalFile[]; // @since 3.2. + template?: number; // Survey type. + days?: number; // Days. + questions?: string; // Question ids. + surveydone?: number; // Did I finish the survey?. + timecreated?: number; // Time of creation. + timemodified?: number; // Time of last modification. + section?: number; // Course section id. + visible?: number; // Visible. + groupmode?: number; // Group mode. + groupingid?: number; // Group id. +}; + +/** + * Survey question. + */ +export type AddonModSurveyQuestion = { + id: number; // Question id. + text: string; // Question text. + shorttext: string; // Question short text. + multi: string; // Subquestions ids. + intro: string; // The question intro. + type: number; // Question type. + options: string; // Question options. + parent: number; // Parent question (for subquestions). +}; + +/** + * Result of WS mod_survey_get_questions. + */ +export type AddonModSurveyGetQuestionsResult = { + questions: AddonModSurveyQuestion[]; + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Result of WS mod_survey_get_surveys_by_courses. + */ +export type AddonModSurveyGetSurveysByCoursesResult = { + surveys: AddonModSurveySurvey[]; + warnings?: CoreWSExternalWarning[]; +}; + +/** + * Result of WS mod_survey_submit_answers. + */ +export type AddonModSurveySubmitAnswersResult = { + status: boolean; // Status: true if success. + warnings?: CoreWSExternalWarning[]; +}; From 3b15b1a0fb647c0f6fb48ae0407556bfa9f461e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Mon, 2 Sep 2019 16:04:44 +0200 Subject: [PATCH 023/257] MOBILE-3130 comments: Use count field to get real comment count --- src/core/comments/comments.module.ts | 2 +- src/core/comments/pages/viewer/viewer.ts | 7 +++++- src/core/comments/providers/comments.ts | 28 +++++++++++++++--------- 3 files changed, 25 insertions(+), 12 deletions(-) diff --git a/src/core/comments/comments.module.ts b/src/core/comments/comments.module.ts index d19d2ccf3..a216cde81 100644 --- a/src/core/comments/comments.module.ts +++ b/src/core/comments/comments.module.ts @@ -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/pages/viewer/viewer.ts b/src/core/comments/pages/viewer/viewer.ts index b1925c26d..db0973235 100644 --- a/src/core/comments/pages/viewer/viewer.ts +++ b/src/core/comments/pages/viewer/viewer.ts @@ -156,7 +156,12 @@ export class CoreCommentsViewerPage implements OnDestroy { 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. diff --git a/src/core/comments/providers/comments.ts b/src/core/comments/providers/comments.ts index b7a132cfe..04438b4fe 100644 --- a/src/core/comments/providers/comments.ts +++ b/src/core/comments/providers/comments.ts @@ -28,7 +28,7 @@ export class CoreCommentsProvider { static REFRESH_COMMENTS_EVENT = 'core_comments_refresh_comments'; 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, @@ -305,6 +305,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; } @@ -328,17 +333,19 @@ export class CoreCommentsProvider { 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 +355,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) + '+'; } From 716f76f9693c9a5cc19186ef80ac9b334242c303 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 17 Sep 2019 13:03:18 +0200 Subject: [PATCH 024/257] MOBILE-3144 settings: Add color scheme setting --- scripts/langindex.json | 4 ++ src/assets/lang/en.json | 4 ++ src/config.json | 3 +- src/core/constants.ts | 1 + src/core/settings/lang/en.json | 4 ++ src/core/settings/pages/general/general.html | 6 +++ src/core/settings/pages/general/general.ts | 32 +++++++++++- src/core/settings/providers/helper.ts | 52 +++++++++++++++++++- src/core/settings/settings.module.ts | 6 ++- src/providers/utils/dom.ts | 5 -- 10 files changed, 107 insertions(+), 10 deletions(-) diff --git a/scripts/langindex.json b/scripts/langindex.json index fe54142bd..741c90a6b 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -1781,6 +1781,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", diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index d837ace11..f8d0edfe3 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -1779,6 +1779,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", diff --git a/src/config.json b/src/config.json index 30d79ac4b..4fd352e58 100644 --- a/src/config.json +++ b/src/config.json @@ -89,5 +89,6 @@ "statusbarlighttextandroid": true, "statusbarbgremotetheme": "#000000", "statusbarlighttextremotetheme": true, - "enableanalytics": false + "enableanalytics": false, + "forceColorScheme": "" } diff --git a/src/core/constants.ts b/src/core/constants.ts index a6f31c3a5..ccd5f9c71 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -36,6 +36,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/settings/lang/en.json b/src/core/settings/lang/en.json index f0e09d02d..32a82c6cc 100644 --- a/src/core/settings/lang/en.json +++ b/src/core/settings/lang/en.json @@ -3,6 +3,10 @@ "appready": "App ready", "cannotsyncoffline": "Cannot synchronise offline.", "cannotsyncwithoutwifi": "Cannot synchronise because the current settings only allow to synchronise when connected to Wi-Fi. Please connect to a Wi-Fi network.", + "colorscheme": "Color Scheme", + "colorscheme-auto": "Auto (based on system settings)", + "colorscheme-dark": "Dark", + "colorscheme-light": "Light", "compilationinfo": "Compilation info", "cordovadevicemodel": "Cordova device model", "cordovadeviceosversion": "Cordova device OS version", diff --git a/src/core/settings/pages/general/general.html b/src/core/settings/pages/general/general.html index 0e1418813..34f0d2376 100644 --- a/src/core/settings/pages/general/general.html +++ b/src/core/settings/pages/general/general.html @@ -20,6 +20,12 @@ + +

{{ 'core.settings.colorscheme' | translate }}

+ + {{ 'core.settings.colorscheme-' + scheme | translate }} + +

{{ 'core.settings.enablerichtexteditor' | translate }}

diff --git a/src/core/settings/pages/general/general.ts b/src/core/settings/pages/general/general.ts index 9befe638a..720e8fbdc 100644 --- a/src/core/settings/pages/general/general.ts +++ b/src/core/settings/pages/general/general.ts @@ -24,6 +24,7 @@ import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; import { CorePushNotificationsProvider } from '@core/pushnotifications/providers/pushnotifications'; import { CoreConfigConstants } from '../../../../configconstants'; +import { CoreSettingsHelper } from '../../providers/helper'; /** * Page that displays the general settings. @@ -44,11 +45,13 @@ export class CoreSettingsGeneralPage { debugDisplay: boolean; analyticsSupported: boolean; analyticsEnabled: boolean; + colorSchemes = []; + selectedScheme: string; constructor(appProvider: CoreAppProvider, private configProvider: CoreConfigProvider, fileProvider: CoreFileProvider, private eventsProvider: CoreEventsProvider, private langProvider: CoreLangProvider, private domUtils: CoreDomUtilsProvider, private pushNotificationsProvider: CorePushNotificationsProvider, - localNotificationsProvider: CoreLocalNotificationsProvider) { + localNotificationsProvider: CoreLocalNotificationsProvider, private settingsHelper: CoreSettingsHelper) { // Get the supported languages. const languages = CoreConfigConstants.languages; @@ -59,6 +62,22 @@ export class CoreSettingsGeneralPage { }); } + if (!CoreConfigConstants.forceColorScheme) { + let defaultColorScheme = 'light'; + + if (window.matchMedia('(prefers-color-scheme: dark)').matches || + window.matchMedia('(prefers-color-scheme: light)').matches) { + this.colorSchemes.push('auto'); + defaultColorScheme = 'auto'; + } + this.colorSchemes.push('light'); + this.colorSchemes.push('dark'); + + this.configProvider.get(CoreConstants.SETTINGS_COLOR_SCHEME, defaultColorScheme).then((scheme) => { + this.selectedScheme = scheme; + }); + } + // Sort them by name. this.languages.sort((a, b) => { return a.name.localeCompare(b.name); @@ -126,10 +145,19 @@ export class CoreSettingsGeneralPage { return fontSize; }); - document.documentElement.style.fontSize = this.selectedFontSize + '%'; + + this.settingsHelper.setFontSize(this.selectedFontSize); this.configProvider.set(CoreConstants.SETTINGS_FONT_SIZE, this.selectedFontSize); } + /** + * Called when a new color scheme is selected. + */ + colorSchemeChanged(): void { + this.settingsHelper.setColorScheme(this.selectedScheme); + this.configProvider.set(CoreConstants.SETTINGS_COLOR_SCHEME, this.selectedScheme); + } + /** * Called when the rich text editor is enabled or disabled. */ diff --git a/src/core/settings/providers/helper.ts b/src/core/settings/providers/helper.ts index 26ffda777..8579f96e0 100644 --- a/src/core/settings/providers/helper.ts +++ b/src/core/settings/providers/helper.ts @@ -20,6 +20,9 @@ import { CoreFilepoolProvider } from '@providers/filepool'; import { CoreLoggerProvider } from '@providers/logger'; import { CoreSitesProvider } from '@providers/sites'; import { CoreUtilsProvider } from '@providers/utils/utils'; +import { CoreConstants } from '@core/constants'; +import { CoreConfigProvider } from '@providers/config'; +import { CoreConfigConstants } from '../../../configconstants'; import { TranslateService } from '@ngx-translate/core'; /** @@ -32,7 +35,8 @@ export class CoreSettingsHelper { constructor(loggerProvider: CoreLoggerProvider, private appProvider: CoreAppProvider, private cronDelegate: CoreCronDelegate, private eventsProvider: CoreEventsProvider, private filePoolProvider: CoreFilepoolProvider, - private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider, private translate: TranslateService) { + private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider, private translate: TranslateService, + private configProvider: CoreConfigProvider) { this.logger = loggerProvider.getInstance('CoreSettingsHelper'); } @@ -175,4 +179,50 @@ export class CoreSettingsHelper { return syncPromise; } + + /** + * Init Settings related to DOM. + */ + initDomSettings(): void { + // Set the font size based on user preference. + this.configProvider.get(CoreConstants.SETTINGS_FONT_SIZE, CoreConfigConstants.font_sizes[0].toString()).then((fontSize) => { + this.setFontSize(fontSize); + }); + + if (!!CoreConfigConstants.forceColorScheme) { + this.setColorScheme(CoreConfigConstants.forceColorScheme); + } else { + let defaultColorScheme = 'light'; + + if (window.matchMedia('(prefers-color-scheme: dark)').matches || + window.matchMedia('(prefers-color-scheme: light)').matches) { + defaultColorScheme = 'auto'; + } + + this.configProvider.get(CoreConstants.SETTINGS_COLOR_SCHEME, defaultColorScheme).then((scheme) => { + this.setColorScheme(scheme); + }); + } + } + + /** + * Set document default font size. + * + * @param fontSize Font size in percentage. + */ + setFontSize(fontSize: string): void { + document.documentElement.style.fontSize = fontSize + '%'; + } + + /** + * Set body color scheme. + * + * @param colorScheme Name of the color scheme. + */ + setColorScheme(colorScheme: string): void { + document.body.classList.remove('scheme-light'); + document.body.classList.remove('scheme-dark'); + document.body.classList.remove('scheme-auto'); + document.body.classList.add('scheme-' + colorScheme); + } } diff --git a/src/core/settings/settings.module.ts b/src/core/settings/settings.module.ts index 6098d4c91..25693befa 100644 --- a/src/core/settings/settings.module.ts +++ b/src/core/settings/settings.module.ts @@ -28,4 +28,8 @@ import { CoreSettingsHelper } from './providers/helper'; CoreSettingsHelper ] }) -export class CoreSettingsModule {} +export class CoreSettingsModule { + constructor(settingsHelper: CoreSettingsHelper) { + settingsHelper.initDomSettings(); + } +} diff --git a/src/providers/utils/dom.ts b/src/providers/utils/dom.ts index fb91e5532..9de6e424d 100644 --- a/src/providers/utils/dom.ts +++ b/src/providers/utils/dom.ts @@ -22,7 +22,6 @@ import { TranslateService } from '@ngx-translate/core'; import { CoreTextUtilsProvider } from './text'; import { CoreAppProvider } from '../app'; import { CoreConfigProvider } from '../config'; -import { CoreConfigConstants } from '../../configconstants'; import { CoreUrlUtilsProvider } from './url'; import { CoreFileProvider } from '@providers/file'; import { CoreConstants } from '@core/constants'; @@ -73,10 +72,6 @@ export class CoreDomUtilsProvider { 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 + '%'; - }); } /** From 7f882a67aef3e7c2a46a313d1959c51b3e80e0de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 12 Sep 2019 16:40:42 +0200 Subject: [PATCH 025/257] MOBILE-3144 theme: Dark mode --- .../events/addon-block-timeline-events.html | 2 +- .../components/calendar/calendar.scss | 6 + .../messages/pages/discussion/discussion.scss | 9 +- .../group-conversations.scss | 3 + .../components/submission/submission.scss | 4 + src/addon/mod/data/data.scss | 11 +- .../forum/pages/discussion/discussion.scss | 3 + src/addon/mod/lesson/pages/player/player.scss | 15 +++ .../mod/quiz/components/index/index.scss | 13 ++ .../navigation-modal/navigation-modal.scss | 8 ++ .../mod/survey/components/index/index.scss | 6 + .../mod/wiki/components/index/index.scss | 14 +- .../qtype/ddmarker/component/ddmarker.scss | 1 + src/addon/qtype/ddwtos/component/ddwtos.scss | 7 + .../qtype/gapselect/component/gapselect.scss | 4 + src/app/app.ios.scss | 4 + src/app/app.md.scss | 4 + src/app/app.scss | 41 ++++++ src/app/app.wp.scss | 4 + src/components/icon/icon.scss | 6 + src/components/iframe/iframe.scss | 1 + src/components/loading/loading.scss | 3 + .../mark-required/mark-required.scss | 4 + src/components/progress-bar/progress-bar.scss | 7 + .../rich-text-editor/rich-text-editor.scss | 26 ++++ .../send-message-form/send-message-form.scss | 5 + src/components/split-view/split-view.scss | 4 + src/components/timer/core-timer.html | 2 +- src/components/timer/timer.scss | 4 +- src/core/block/components/block/block.scss | 6 + .../components/module/core-course-module.html | 2 +- src/core/course/components/module/module.scss | 43 +++++- src/core/grades/components/course/course.scss | 14 ++ src/core/login/login.scss | 74 +++++++++++ .../login/pages/credentials/credentials.html | 6 +- .../login/pages/credentials/credentials.scss | 41 ------ src/core/login/pages/init/init.scss | 2 + src/core/login/pages/reconnect/reconnect.html | 6 +- src/core/login/pages/reconnect/reconnect.scss | 46 +------ src/core/login/pages/site/site.scss | 36 +---- src/core/mainmenu/pages/more/more.scss | 43 +++++- src/core/user/pages/profile/profile.scss | 13 +- src/theme/dark.scss | 123 ++++++++++++++++++ src/theme/variables.scss | 46 +++++++ 44 files changed, 586 insertions(+), 136 deletions(-) create mode 100644 src/core/login/login.scss create mode 100644 src/theme/dark.scss 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..46af070cc 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,7 +4,7 @@
- +

diff --git a/src/addon/calendar/components/calendar/calendar.scss b/src/addon/calendar/components/calendar/calendar.scss index b04fe3d64..17c3fd478 100644 --- a/src/addon/calendar/components/calendar/calendar.scss +++ b/src/addon/calendar/components/calendar/calendar.scss @@ -58,6 +58,9 @@ ion-app.app-root addon-calendar-calendar { .addon-calendar-months { background-color: white; padding: 0; + @include darkmode() { + background-color: $gray-darker; + } } .addon-calendar-day { @@ -108,6 +111,9 @@ ion-app.app-root addon-calendar-calendar { } &.dayblank { background-color: $gray-lighter; + @include darkmode() { + background-color: $black; + } } .addon-calendar-event { diff --git a/src/addon/messages/pages/discussion/discussion.scss b/src/addon/messages/pages/discussion/discussion.scss index f17702e24..5b8ee2a35 100644 --- a/src/addon/messages/pages/discussion/discussion.scss +++ b/src/addon/messages/pages/discussion/discussion.scss @@ -4,10 +4,13 @@ $item-message-note-text: $gray-dark !default; $item-message-note-font-size: 75% !default; $item-message-mine-bg: $gray-light !default; -ion-app.app-root page-addon-messages-discussion { +ion-app.app-root page-addon-messages-discussion.ion-page { ion-content { background-color: $gray-lighter !important; + @include darkmode() { + background-color: $gray-darker !important; + } } .addon-messages-discussion-container { @@ -59,6 +62,7 @@ ion-app.app-root page-addon-messages-discussion { align-items: center; margin-bottom: .5rem!important; margin-top: 0; + color: $text-color; ion-avatar { display: block; @@ -108,6 +112,9 @@ ion-app.app-root page-addon-messages-discussion { .addon-message-text { display: inline-flex; + * { + color: $text-color; + } } .addon-messages-delete-button { diff --git a/src/addon/messages/pages/group-conversations/group-conversations.scss b/src/addon/messages/pages/group-conversations/group-conversations.scss index d4dd9a821..517363205 100644 --- a/src/addon/messages/pages/group-conversations/group-conversations.scss +++ b/src/addon/messages/pages/group-conversations/group-conversations.scss @@ -28,6 +28,9 @@ ion-app.app-root .addon-message-discussion { white-space: nowrap; color: $black; @include margin(null, 2px, null, null); + @include darkmode() { + color: $white; + } } .addon-message-last-message-text { diff --git a/src/addon/mod/assign/components/submission/submission.scss b/src/addon/mod/assign/components/submission/submission.scss index d8d1d5aeb..8d3f9a458 100644 --- a/src/addon/mod/assign/components/submission/submission.scss +++ b/src/addon/mod/assign/components/submission/submission.scss @@ -10,6 +10,9 @@ ion-app.app-root addon-mod-assign-submission { div.submissioneditable p { color: $red; + @include darkmode() { + color: $red-light; + } } .core-grading-summary .advancedgrade { @@ -45,6 +48,7 @@ core-format-text { margin: 0 auto; tbody { background: $white; + color: $text-color; } } diff --git a/src/addon/mod/data/data.scss b/src/addon/mod/data/data.scss index d18a66a08..1bce48a43 100644 --- a/src/addon/mod/data/data.scss +++ b/src/addon/mod/data/data.scss @@ -3,7 +3,7 @@ white-space: normal; word-break: break-word; padding: $content-padding; - background-color: white; + background-color: $white; border-top-width: 1px; border-bottom-width: 1px; border-right-width: 0; @@ -11,6 +11,10 @@ border-style: solid; border-color: $list-border-color; + @include darkmode { + background-color: $core-dark-item-bg-color; + } + table, tbody { display: block; } @@ -46,6 +50,11 @@ page-addon-mod-data-edit { form .addon-data-advanced-search { background-color: $list-background-color; + @include darkmode() { + background-color: $core-dark-item-bg-color; + color: $core-dark-text-color; + } + .item.item-input .item-block .item-inner ion-input, .item.item-input.item-input-has-focus .item-inner ion-input, .item.item-input.input-has-focus .item-inner ion-input { diff --git a/src/addon/mod/forum/pages/discussion/discussion.scss b/src/addon/mod/forum/pages/discussion/discussion.scss index b4151f311..f9013b8f7 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 { background-color: $gray-lighter; + @include darkmode() { + background-color: $black; + } } .addon-forum-reply-button .label { diff --git a/src/addon/mod/lesson/pages/player/player.scss b/src/addon/mod/lesson/pages/player/player.scss index cb4a4caf9..750d00287 100644 --- a/src/addon/mod/lesson/pages/player/player.scss +++ b/src/addon/mod/lesson/pages/player/player.scss @@ -26,6 +26,9 @@ ion-app.app-root page-addon-mod-lesson-player { tr:nth-child(odd) { background-color: $gray-lighter; + @include darkmode() { + background-color: $core-dark-item-divider-bg-color; + } } tr:last-child td { @@ -52,13 +55,25 @@ ion-app.app-root page-addon-mod-lesson-player { .item-ios table { @extend .card-ios; + @include darkmode() { + color: $white; + background-color: $core-dark-item-bg-color; + } } .item-md table { @extend .card-md; + @include darkmode() { + color: $white; + background-color: $core-dark-item-bg-color; + } } .item-wp table { @extend .card-wp; + @include darkmode() { + color: $white; + background-color: $core-dark-item-bg-color; + } } } diff --git a/src/addon/mod/quiz/components/index/index.scss b/src/addon/mod/quiz/components/index/index.scss index 69ad61736..e359e8a0b 100644 --- a/src/addon/mod/quiz/components/index/index.scss +++ b/src/addon/mod/quiz/components/index/index.scss @@ -25,6 +25,9 @@ ion-app.app-root addon-mod-quiz-index { .item:nth-child(even) { background-color: $gray-lighter; + @include darkmode() { + background-color: $core-dark-item-divider-bg-color; + } } .addon-mod_quiz-highlighted, @@ -34,5 +37,15 @@ ion-app.app-root addon-mod-quiz-index { background-color: $blue-light; color: $blue-dark; } + + @include darkmode() { + .addon-mod_quiz-highlighted, + .item.addon-mod_quiz-highlighted, + .addon-mod_quiz-highlighted p, + .item.addon-mod_quiz-highlighted p { + background-color: $blue-dark; + color: $blue-light; + } + } } } diff --git a/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.scss b/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.scss index 6576971e0..2896cb9d3 100644 --- a/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.scss +++ b/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.scss @@ -10,5 +10,13 @@ ion-app.app-root page-addon-mod-quiz-navigation-modal { } color: $text-color; background-color: $white; + + @include darkmode() { + ion-label.label { + color: $core-dark-text-color; + } + color: $core-dark-text-color; + background-color: $core-dark-item-bg-color; + } } } diff --git a/src/addon/mod/survey/components/index/index.scss b/src/addon/mod/survey/components/index/index.scss index e8bc5f985..e7af8521d 100644 --- a/src/addon/mod/survey/components/index/index.scss +++ b/src/addon/mod/survey/components/index/index.scss @@ -11,10 +11,16 @@ ion-app.app-root addon-mod-survey-index { ion-grid { background-color: $white; + @include darkmode() { + background-color: $black; + } } .even { background-color: $gray-light; + @include darkmode() { + background-color: $gray-darker; + } } .addon-mod_survey-textarea textarea { diff --git a/src/addon/mod/wiki/components/index/index.scss b/src/addon/mod/wiki/components/index/index.scss index 49867d614..e320264d2 100644 --- a/src/addon/mod/wiki/components/index/index.scss +++ b/src/addon/mod/wiki/components/index/index.scss @@ -6,20 +6,32 @@ $addon-mod-wiki-toc-background-color: $gray-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; + @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; + } 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; 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/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/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/app/app.ios.scss b/src/app/app.ios.scss index 8eeaf67a3..d90e3da7b 100644 --- a/src/app/app.ios.scss +++ b/src/app/app.ios.scss @@ -43,6 +43,10 @@ ion-app.app-root.ios { @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); diff --git a/src/app/app.md.scss b/src/app/app.md.scss index a97ce6e95..db1666048 100644 --- a/src/app/app.md.scss +++ b/src/app/app.md.scss @@ -38,6 +38,10 @@ 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); diff --git a/src/app/app.scss b/src/app/app.scss index 594dccfe3..474ace86f 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -255,6 +255,10 @@ ion-app.app-root { &.core-shortened { color: $gray-darker; + @include darkmode() { + color: $white; + } + overflow: hidden; min-height: 50px; @@ -268,6 +272,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 +290,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), white)); + 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)); + } } } } @@ -469,6 +486,23 @@ ion-app.app-root { } } + @include darkmode() { + ion-select.core-button-select, + .core-button-select { + background-color: $core-dark-item-divider-bg-color; + + + &.select-md, + &.button-md, + &.select-ios, + &.button-ios, + &.select-wp, + &.button-wp { + background: $core-dark-item-divider-bg-color; + } + } + } + // File uploader. // ------------------------- .core-fileuploader-file-handler { @@ -692,6 +726,10 @@ 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); position: relative; @@ -713,6 +751,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 { diff --git a/src/app/app.wp.scss b/src/app/app.wp.scss index b91fb4f10..127e273b0 100644 --- a/src/app/app.wp.scss +++ b/src/app/app.wp.scss @@ -35,6 +35,10 @@ 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); 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/iframe/iframe.scss b/src/components/iframe/iframe.scss index 82b13e078..0d0e5ca92 100644 --- a/src/components/iframe/iframe.scss +++ b/src/components/iframe/iframe.scss @@ -6,6 +6,7 @@ ion-app.app-root core-iframe { border: 0; display: block; max-width: 100%; + background-color: $gray-light; } .core-loading-container { diff --git a/src/components/loading/loading.scss b/src/components/loading/loading.scss index bc1faec9f..8b4136eea 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 { 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/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/rich-text-editor/rich-text-editor.scss b/src/components/rich-text-editor/rich-text-editor.scss index 9460297fd..d4ba94b06 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: $black; + } .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: $black; + 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/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/split-view/split-view.scss b/src/components/split-view/split-view.scss index 5c399b5e5..4d1fe88aa 100644 --- a/src/components/split-view/split-view.scss +++ b/src/components/split-view/split-view.scss @@ -22,6 +22,10 @@ ion-app.app-root core-split-view { .split-pane-side .core-split-item-selected { background-color: $gray-lighter; @include core-selected-item($core-splitview-selected); + + @include darkmode() { + background-color: $black; + } } .item-ios[detail-push] .item-inner, 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/core/block/components/block/block.scss b/src/core/block/components/block/block.scss index 4029d7ffa..2ab0c8f20 100644 --- a/src/core/block/components/block/block.scss +++ b/src/core/block/components/block/block.scss @@ -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/course/components/module/core-course-module.html b/src/core/course/components/module/core-course-module.html index eae6da52c..0b0239685 100644 --- a/src/core/course/components/module/core-course-module.html +++ b/src/core/course/components/module/core-course-module.html @@ -1,4 +1,4 @@ - +

diff --git a/src/core/course/components/module/module.scss b/src/core/course/components/module/module.scss index c08daa491..739d79166 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,32 @@ 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; + } + } + + @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/grades/components/course/course.scss b/src/core/grades/components/course/course.scss index 8c0ee2091..5a3a9eb4e 100644 --- a/src/core/grades/components/course/course.scss +++ b/src/core/grades/components/course/course.scss @@ -6,6 +6,10 @@ ion-app.app-root core-grades-course { font-size: 16px; color: $text-color; + @include darkmode() { + color: $core-dark-text-color; + } + tr { border-bottom: 1px solid $list-border-color; } @@ -19,6 +23,10 @@ ion-app.app-root core-grades-course { vertical-align: bottom; font-weight: bold; background-color: $white; + + @include darkmode() { + background-color: $black; + } } tbody th { font-weight: normal; @@ -58,11 +66,17 @@ ion-app.app-root core-grades-course { .odd { td, th, th.core-split-item-selected { background-color: $gray-lighter; + @include darkmode() { + background-color: $gray-darker; + } } } .even { td, th, th.core-split-item-selected { background-color: $white; + @include darkmode() { + background-color: $black; + } } } 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/credentials/credentials.html b/src/core/login/pages/credentials/credentials.html index dcadd2e23..ee0a1c62c 100644 --- a/src/core/login/pages/credentials/credentials.html +++ b/src/core/login/pages/credentials/credentials.html @@ -36,11 +36,11 @@ -
+ - + - +
+ @@ -50,12 +50,12 @@ -
+ - +
- - + +

-

+

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

+

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

-
-
- - + + + +

-

+

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

+

+

{{discussion.created | coreDateDayOrTime}}

- - - - - - - {{ discussion.groupname }} - - - - - {{ 'addon.mod_forum.numreplies' | translate:{numreplies: discussion.numreplies} }} - - - - - {{discussion.timemodified | coreTimeAgo}} - - - -
+ + + + + {{ discussion.groupname }} + + + + + {{ 'addon.mod_forum.numreplies' | translate:{numreplies: discussion.numreplies} }} + + + + + {{ 'addon.mod_forum.lastpost' | translate }} {{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..5b0fd16cc 100644 --- a/src/addon/mod/forum/components/index/index.scss +++ b/src/addon/mod/forum/components/index/index.scss @@ -1,16 +1,5 @@ ion-app.app-root addon-mod-forum-index { - .addon-forum-discussion-selected { - border-top: 5px solid $core-splitview-selected; - } - .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; - } } diff --git a/src/addon/mod/forum/lang/en.json b/src/addon/mod/forum/lang/en.json index 65a9af9df..29215eb9d 100644 --- a/src/addon/mod/forum/lang/en.json +++ b/src/addon/mod/forum/lang/en.json @@ -28,6 +28,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", diff --git a/src/app/app.scss b/src/app/app.scss index 474ace86f..a43d1efee 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -503,6 +503,13 @@ ion-app.app-root { } } + 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 { diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index f8d0edfe3..d4514cc94 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -579,6 +579,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", diff --git a/src/core/course/components/format/core-course-format.html b/src/core/course/components/format/core-course-format.html index c7c3bf6ed..40bcd52fe 100644 --- a/src/core/course/components/format/core-course-format.html +++ b/src/core/course/components/format/core-course-format.html @@ -14,7 +14,7 @@
diff --git a/src/core/course/components/format/format.scss b/src/core/course/components/format/format.scss index 75426f095..34684e29c 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; From 041db81a33cc6dc84bd501b9db6f184882d4cc2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 31 May 2019 13:59:29 +0200 Subject: [PATCH 027/257] MOBILE-2828 android: Add adaptative icons for Android --- config.xml | 37 +++---- resources/android/icon-foreground.png | Bin 0 -> 14862 bytes resources/android/icon-foreground.svg | 98 ++++++++++++++++++ resources/android/icon/hdpi-foreground.png | Bin 0 -> 1517 bytes resources/android/icon/ldpi-foreground.png | Bin 0 -> 3552 bytes resources/android/icon/mdpi-foreground.png | Bin 0 -> 984 bytes resources/android/icon/xhdpi-foreground.png | Bin 0 -> 2065 bytes resources/android/icon/xxhdpi-foreground.png | Bin 0 -> 3251 bytes resources/android/icon/xxxhdpi-foreground.png | Bin 0 -> 4640 bytes .../splash/drawable-land-hdpi-screen.png | Bin 14301 -> 10631 bytes .../splash/drawable-land-ldpi-screen.png | Bin 5224 -> 4003 bytes .../splash/drawable-land-mdpi-screen.png | Bin 8303 -> 6132 bytes .../splash/drawable-land-xhdpi-screen.png | Bin 24188 -> 18494 bytes .../splash/drawable-land-xxhdpi-screen.png | Bin 31537 -> 25854 bytes .../splash/drawable-land-xxxhdpi-screen.png | Bin 41246 -> 33932 bytes .../splash/drawable-port-hdpi-screen.png | Bin 14867 -> 10835 bytes .../splash/drawable-port-ldpi-screen.png | Bin 5368 -> 4078 bytes .../splash/drawable-port-mdpi-screen.png | Bin 8612 -> 6232 bytes .../splash/drawable-port-xhdpi-screen.png | Bin 25291 -> 19067 bytes .../splash/drawable-port-xxhdpi-screen.png | Bin 32738 -> 25073 bytes .../splash/drawable-port-xxxhdpi-screen.png | Bin 42758 -> 32436 bytes resources/ios/icon/icon-1024.png | Bin 23610 -> 20032 bytes resources/ios/icon/icon-40.png | Bin 1518 -> 1193 bytes resources/ios/icon/icon-40@2x.png | Bin 2746 -> 2140 bytes resources/ios/icon/icon-40@3x.png | Bin 3947 -> 3100 bytes resources/ios/icon/icon-50.png | Bin 1672 -> 1386 bytes resources/ios/icon/icon-50@2x.png | Bin 3340 -> 2575 bytes resources/ios/icon/icon-60.png | Bin 2136 -> 1644 bytes resources/ios/icon/icon-60@2x.png | Bin 3947 -> 3100 bytes resources/ios/icon/icon-60@3x.png | Bin 5762 -> 4683 bytes resources/ios/icon/icon-72.png | Bin 2550 -> 1970 bytes resources/ios/icon/icon-72@2x.png | Bin 4719 -> 3763 bytes resources/ios/icon/icon-76.png | Bin 2667 -> 2076 bytes resources/ios/icon/icon-76@2x.png | Bin 4898 -> 3943 bytes resources/ios/icon/icon-83.5@2x.png | Bin 5388 -> 4199 bytes resources/ios/icon/icon-small.png | Bin 1195 -> 851 bytes resources/ios/icon/icon-small@2x.png | Bin 2077 -> 1612 bytes resources/ios/icon/icon-small@3x.png | Bin 2976 -> 2340 bytes resources/ios/icon/icon.png | Bin 2062 -> 1563 bytes resources/ios/icon/icon@2x.png | Bin 3784 -> 2980 bytes .../ios/splash/Default-568h@2x~iphone.png | Bin 22258 -> 16374 bytes resources/ios/splash/Default-667h.png | Bin 26694 -> 19833 bytes resources/ios/splash/Default-736h.png | Bin 49479 -> 37292 bytes .../ios/splash/Default-Landscape-736h.png | Bin 47416 -> 38609 bytes .../ios/splash/Default-Landscape@2x~ipad.png | Bin 47077 -> 37335 bytes .../ios/splash/Default-Landscape@~ipadpro.png | Bin 55504 -> 44093 bytes .../ios/splash/Default-Landscape~ipad.png | Bin 20256 -> 15329 bytes .../ios/splash/Default-Portrait@2x~ipad.png | Bin 48050 -> 37447 bytes .../ios/splash/Default-Portrait@~ipadpro.png | Bin 56701 -> 45081 bytes .../ios/splash/Default-Portrait~ipad.png | Bin 20741 -> 15514 bytes resources/ios/splash/Default@2x~iphone.png | Bin 18946 -> 14001 bytes .../splash/Default@2x~universal~anyany.png | Bin 65795 -> 50719 bytes resources/ios/splash/Default~iphone.png | Bin 8612 -> 6232 bytes resources/values/colors.xml | 4 + 54 files changed, 121 insertions(+), 18 deletions(-) create mode 100644 resources/android/icon-foreground.png create mode 100644 resources/android/icon-foreground.svg create mode 100644 resources/android/icon/hdpi-foreground.png create mode 100644 resources/android/icon/ldpi-foreground.png create mode 100644 resources/android/icon/mdpi-foreground.png create mode 100644 resources/android/icon/xhdpi-foreground.png create mode 100644 resources/android/icon/xxhdpi-foreground.png create mode 100644 resources/android/icon/xxxhdpi-foreground.png create mode 100644 resources/values/colors.xml diff --git a/config.xml b/config.xml index 1ab1ad371..154531127 100644 --- a/config.xml +++ b/config.xml @@ -43,24 +43,18 @@ - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + @@ -77,6 +71,13 @@ + + + + + + + diff --git a/resources/android/icon-foreground.png b/resources/android/icon-foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..cdadeb07f6d589e7bda617328e4380ae8fbd3db5 GIT binary patch literal 14862 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 n_z!{q5cm&)|6d^RMqB{heswfnanIu`LWRwYt + + + + + image/svg+xml + + MoodleApp_Icon_White_RGB + + + + + + + + MoodleApp_Icon_White_RGB + + + + + + + + diff --git a/resources/android/icon/hdpi-foreground.png b/resources/android/icon/hdpi-foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..9b02860331c058dc2279a7cf1e762bc685f36d63 GIT binary patch literal 1517 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 aUFB~`D%8oqc5Y_?0000wZ7*uz}6rh2O(}=D%aA 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 literal 0 HcmV?d00001 diff --git a/resources/android/icon/xxxhdpi-foreground.png b/resources/android/icon/xxxhdpi-foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..c80626392b008abf465019aa67049d992aa3645b GIT binary patch literal 4640 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 O-M9+5Lb&Ym@P7b_okkr1 literal 0 HcmV?d00001 diff --git a/resources/android/splash/drawable-land-hdpi-screen.png b/resources/android/splash/drawable-land-hdpi-screen.png index 2bbe24c29d72c725fd34640b71dddb70d4aac6c9..bf0f39e5a3de94c892fe447ba01adf969090daa2 100644 GIT binary patch literal 10631 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 KM&Y&lfBrAETl?Ao literal 14301 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 E19$(AcmMzZ literal 5224 zcmeHLS5yM%-m^r=AM~5_s;y1?^+mgafomL001syBYi6X zfJvKC-efz&$o+40crpmHn~u2-0D#KiJc6+>>P&%FhPr_20kLI9<)V|3l{oW_%Q4GTFXJiuy0C4jE zS7HL>7YYLa{C39rIuF7o)~Ddvb`N2MEz0x~sU>tePs<2qX&U+fxsA9~oBR{gqG-I!$bGgd*HC*e%(nDCnA{H6)u|1MEqf+WeD#ND;EuqTfH8>UQmP(tJzQiYWXyO~4)P4O9)A`J2a?zLg zZm`_de_sYjG5UI5n>YE*sjHWMS^@4{e!}rCN#FF}LI0;MxE@)irakX6XBXxPJY#lB zf%Pc9Ms^m?2b$}kVUGKglFUFNO#l2^5*L0!-eKE!YJ%lYY&J-(70#h*Yz?ca)LW2SREbFYPMJI<@kx1H19dP+) z7&o4Pw|%SRuB-pPoELhzo#y1rW&Emru;9veiWPu%Z=eM=X5*>U1r4z|nC&31dtOqy z2cSu%{ju>xg=|l!q0!(*uW?;VOKwqBFX&LYxx7tX{9FdaUMicRP=|me>~FHcPCLzu z)SNk7@wnc#Uu2}lfwx}(Mes;ej<~Zog5;%BL-_rl;-ySx|m$l2Bc(07z9jd-kl~M$6K6nPMb6C16`i+L*dI=d1s{ zZL{ZlZaDXv92;KB&1tkBx76kxV<*x;oy+{#YI<%gRQuy@TuLW zCo#0lUk2Tyq;zsKA7oQ34EZ?->MEWFa^+p@9JRPzJz~a@5ar6X??mo`&^U#cSqdCA zs06V(9yk`C+n9&{uFmS-0EsUX`lRJM>yB;Y7)HbTmVV_R6iSo4xtm#t^yt`Y;Q+cq>d(1$)A4I=C7>tU4f_HY%OH|zez_HrmM$t^Lv>3`OX zpQKEo(<9btz_RnN_@F7l0l`KEjI|1O-dB{6G$cxYO4Thy#lh##bSL6s9c@o(&6mQ` z?3T0!|3czG`>(b#p5M-XADOg1b=~667XclPVy|^k85T zMX+O@o7oSL*%`Rq;Fo`(&3i8NLz$2rf+8V&4QhgD9vwGw}@`EhJd*F(RZ zQotN-qiWJpQ~i1do9aKe6_f{DpkNihGziAI5umT*H11d1SeLpF3w>&p@mgBjvY z2fEA&;fu}B_Om1zQu~`?i0`~HTZjEm+5A>3c|HV_%MiyJCX^0V6(@-qsfzoUeLhka z4Lac#vmE~uU8{&V6QtkO52|JN0943yrA14f2`~6+%Jr+Z1~#Pn4Y$is`TKRfPr^HiH7sQ%W&K?P^qt* zV#jH{!eA^fsn(AZ*~oSOMt|p{iFe>r#w_p=g0VuoNIf|jIKe&bq~SIvfnjOdKmh6% ziqCGX=8CF1eh^ZjonIR-{{Hp=b~W@{w}nxGQk6nnOP#L5f z$p#$DI?0_cw1lNM^L&Ye%@3=xhEz(o9iP6_eRe-3iZNv&|LW9&jBybNT9bVHqTKyMuTNPJ{#t%@SZ>g`_uag6;an=-=k_x& zM(7n$DTIPyIXDeKV@hMsY!X}CI~loly3y>-uzRsmk(0&k9LUokrlq43QGaqr?26u~ zqiun%QJu&#eiUliE(~a!0XdHP>8Mfm9f_$ct7cE0+O75yvZMHe%(WhPu|ju3_h3L1g?xKXK^i5uZ;3WQ z&;+Cus_Hi_r!wG<>eSpj^A+e6_YF3kN-8YF!QlCF+D+Hu_1PMNcyshxdhfd(KG)3N z^*uKUJbeZJqtzj7e*?7qss@(v95;rx)QsYF4-^_tdx7m;GZEd0sW7&9%3&^GKX_sg z{&0w)=E_|!U2L}e_r!AkmXx-(?2_DC7Sak@cKc-asE{M}#}3Eg)3uxOfFoWZs5dI& za*fWUSl=usE^WjRcw-32T71Dk5C3N{5ne%0wElAuVV49q;+M<>EQe){= zA#IsQE=TYF^^=z2y(P7o;uGHNd?xPFPnY2|15-pSEf#4+wSin37F*uLVHFyI9d-l!&XMaItwV%d{ny)7 zl&b7t5PK_;w)VWxGeKg^I3hAKaGY%;3K1_kea=_}o7)IY2SZ4W<|!(wZm{6K%%x0- zzuQ&^AA)dyuRlZZp2<6KwEE1=zRFUp(=)I7>l^f$p_$B8hlp2L>u!UsNrb=#dEu@{ zm#>}aaTgxX+&wmt1LUo%*Gx@HW1?(MRm_ZW{xB9O2Qyr&yI8}$n$hqz6hT--t@1NLE=;H7nzX?0NbBBgcK3 zh)m|u)xv_Yd2L?$D8+hj>K@j^3CG;)M~P49QNKyN+UUt$k8;IgSA?7|*320qyJ8Kj zu)elPa=R;S2_I)X#IxC+SmU<$wgGRxyg+&I5nv&eW}GoH@+iNc;DWI5JBDD}E}L^Y z6T40OYR8eg@t?Av5Lh+45*wo6tzq865DgxKN9gW=)jnW~hrDEuLCFLLsd)AW!t}hBz5KyB*%!bwbt0~qQRowzDCCmM?*%*65dEaj zT%BLT=>*va_TG%gK$kkQ;mv(Z&})x|gKhG+YRu4xqpO5Cg5H6&Vj&;%e|m`1*S1$Q z2yuy^;eIrIw${wmd-6?U8{S-ONuc@0T5Zq!+wxp;ExM1N0!qIs@sxL7s&O9qrSezc=(PgskQChpw5^fJGC~j z7Fl?z=r48+G>3R#35g-{wFu%|t~4<&6HF>43p<>e3+xVjl_1G_QGG71c~}h@`Mk_kc%uY? z+rL}`S2WWUCUlhz4S|h9hyA+dzIB)fCXNrBc}= z(nMRgWb2u3Y|;jv$|S;BdOS9#fbD~WPV$ggtK6U974lP)gL2PW!y-|^$24ylhb^1! zhodkP^O=AM*Q-UlKOY|y$F!TB?~F_~*9D09wyn#mWTAa!I(~Vs-8RE^d#nY_D)+o6 z&Nfx!AjjIbJ>Cz-*uq)05Z7mL@(gqO%WxB?X-sV6(4_171@~1-85<#(+bYO*&yCJTL=sh>5<_D~#edgE9wDR>h5l&k_Khft(6II%JD?wd*7qivD1UQD(!b`dvv!lOhl!AxPGsbrxn^s2=( zO~64@{G_)B*QjML%+bZ~`r=HzTnFVacib?exU%z!NlrwZ^EVgZAa@)HFC8Lf#_+R; zm)B6f1R9uIdJ^Z5P(c+S_J$lmCC~cexYN%^2{HGP_Olh*xC1v=abCgRY=g|?L(iiT z2VFr4&C|B;T+S`mO&STdJ7JA^oxd>L)IXl7B>5gW$xd#tWUQ4ab}iIv&t8d7+3v5= zz=`SA*SJisg@Iao?v%j42@PRmUxmxKpxj{T2r}F(P&u-VVN&@!o{c)jdA0wL-DkIIkBc^U*sAxTU;?O;@qNG{wFq-HK6teL zTehuHj(vvaf->U>1>NF43%HepgL;fjw}@23N9Ti=Y0Y2#Vjn2B9}Uqn%9#$D#9O1z{WQ ziIo!I;yum)Q5*<(1@*X<-Uj!V`Rl|5C<@7OqL^WH(*Ni?LR zP|?DnSFZ>kjOEjHRx)8!m8k?J#%&T^}W(8Piy4h&5Pv39F z;0P-2_4ekIGKy;d4Jmw<{})d97g72D1C@3$%8QsH=@%NO8R*-UpgYz<&afaC4W|GX z1_3AomDFzlZ{1KXy=E`~jPF?J JSL;G!{{vAIn_ng^t%gi|+?%cT_?uWIWnfL#!_gVk9JTvPT@z_L<<1GJK zCMG5h1O12QOiU+!91l&llgC#!i|x9y zciq1wvvx;)#!Gf*&;1C~YWYqvfLH#r7c6U$;13`Ma>zmX0^sbp- zuM|tNB_BNv6MAx84KY6j4X8ZyCE7|Bgnh{E-Su^?I6IpDA$DGU78Q7P_-h^)*>*HX z`DXETOYS<0j-rvd@KqF!8Zzjf|6#UoSBHwLFD?uPrD%p~GAGLoo9cr2i{t&~ftKmQ z1D+m(k+DdU4gS?}Vv$$QXnI@o8AUclO{5m`#7X17%^c@l?Cy8@c<_dZ4__VLSGV=( zP&PTToi#jGw(9Z%d(X)<%qckbUKaAJ7>wwjyg$7fW;KgRCIC@ca{Ke0%{km2 z?e(&YG9^h8JM%0m+vj5-jbEO!pW@f}p(r7)c-M1A(PQeC{erB06W*+bccsM{kHf%q zbvyUBmv+%cZNJuvhG2^h^K~F_S0BEy4TkkKAohc{bPwAH)?f*R{-Qw=GQ0DqYyjtJ z_lX=#A>~6L{)i!_zTVz&cE0%3)af$YhGT1EGb{XL&()zUVv>YbK&ABYi zqfFS_GsO-53x%%eSGHf#>ryuK6V!NbvXV@pOqN0z`&6gzgG{HI zgjsAD%`h4zTl-y54d0QS%J5oQO~3yf6x6WASCg48`=VGqPbx>8Vt(4w5@3-yJB3z^ zvuNh7x40C9h!pm3p|%gXL}!0ayT2A$w)&RwIH%k~6wwDGA-V&|m>|)EvhhEJSF2AJ zR9{m^mHGDzfQn-fQhEXjL4EeiBbNk51U$-h0q*p5V7jSN=j_^8Q`}UMcd5VW6$>xR zIL>NDLNl2>{`6=Q-(-$x{sh=2iZ<$C zNPjxOaK3Y2M`uPq&98p>`v^k|XfdU`a@Sn!>1-NZP()5*qm;EHMp&V*D8Zjql<)vd z#Czx7GpcF0e-Ti>wBy5b16lKLWObr zLwEUN_Y$Ko*T%PogLO6H4tXRu&w^ZA8odxv$VjhpbVK|W)}8+ z10p~PJJ!6f#HU+zS#slaM}=kam>%PxLhn5lsK&YLzS3#(c6@{@sadcUN-!&`$yL5H z)%xDa7dWlY%x4(hfP_y(Q*;-xi>ybOW~;&rWplN8tPRp0V&G(g-9nOV%Y}810v8JR zDv7HTSGsJis<&4%lCc616AF#SA5G@0FG`K4i1aDt9(R%8%NZUOlm6_rN^d7H^DWPU zH_^;J73M24&JWKytj@14N`tIJN2UWS7u*TgK_jAW z&~Vk%^KP{R&H@owwmiJ zPpG(j{T<%J-y})5ghI_wgVaI+@<-f(TLtKItMV6Z=P{vzcfuu{A@x$%D~>yvo5xc) zwF7?6wG8BuEJx;z3M84ZYAEJoWpiBR78PQ=Uf$m=+5dde>c<6_F`}l(;Y)_8H>S>T zsKp+_fuAYC*adOnzaQmCMQw$$aPZ#_B4@V&yNWrjVmF0lTrxfl92861J?d53oeEx>!peuXgp61Oi^G!3YFdn%F--dj*~VGWS}A0%S#;!f~!=Z8o-O zz|)%?_VaK8ru3sJrL|1M8;O|{^JX&iogd;>Wr>Z)&0wp>7!KEbMq)_>pQVTs3OItA z;}J{J=oGg4V+7}UJe3^}b>}%+(7Qw=B1k1sdW>v6tXf8vG+)a? zMs#B0*7RKu3*3y&9dX3y)hmo7yvdzy9_4Pm*D7Mz_=WMM2TF#CwTS>BReMJ`HIqFn z^k=>vh(9$_1O#q7%Zd94^N(_-n|0REqb-iK$PpEO_-F(4oa@K^N<~bbd?N-UZ5r<^ z7Gqv@*CPk#If3Z*iPkIQKo{~XeT|;7z!oqm+h^_^#SAGEH5^lR*Bz$9vSpM zcC*`i-l`|ulpVa}11xbdM-I5{dHygy0&v5h8pifsrDpE4A+!`jbkS#O!N~ec^imIo zcul3T$1B=YcRx0i>hna^k-)q!;?d2%PbSl<$t81-713ATlyE*U?9IKS_1?lU zL)Q#p;Xh`hcK2D)s?wf{+9D-DcFL?Ff`a9c)Noa?EtsIYB$8HSt<93Z03>l!vXDV^ z&e^+*GB?WA9gVhl$Lus}IX4Zt)^UA;`Y)x4#$)$M?i$-Ks5;e-aI$x(QWS`_sS*u3 zMApD93fHmjiAiJ|F}3hRhkmLKG6CqUI~ z#llI|(9fyzOD1dW@^@OAG3A3L&;Vw5ex5FJY4TY~*W|8dVr#&l6408cAu!N%%~eCs z-d$GUMVWM!)#XVPim%ElRz9DLT@83ZYMnVeStEFgfA_{Pm&W0*pVKu%?6rd4@%uka z!XZL#eEgeco`Fwpgls)AMDAr?aDmmdWnBT$)m!YooqNnoivB#qyzDiQu6l0+?zf6n zm+G)XNU%nAU;7yI?fU-M?9qw9=%vRf>zL@F?qYV0(xNS>zR1zo7=H7~i`jOvpJGKS z+QERJy5zZ5#>dNpw{&=WM3&w0acQdopibY?S-`OKZy+zZ9Ct;3|OnPfIXqwTeyZdvd>LnhoL;yTVvso0+;jhlC_X1K--3k@G_|thYOorfB9tr*< z1|!~Nq1X^{u&7;k7bk>O{GebyqooPQQ%66&bKV%o>kjwr zl!2Y>r`V`y`FTe>n@U3w8a4vv!FzMsO6iHkBIm6+i7xw1b~}g? zJ@dn%GpV8C!-WzdnX2F|Z7#J>R~`fNHF@(?yEs$N(bb@}#=^?5m+s?Olmo31SJt+E>(_awE{LEfBq& z&MR{V_2$O?Mu5$UZo7LA8NGoN^~5Wg&0PS2+r$`aD!@5?=H|WPJfDogj^9e|aZc0j z_F8|uXlPA#fsr|=LAvc%hwn+X-Aa-J3PUgTI`V53C?|4L)>d|sA>Z8=bypV`=vQZe zygy^|6XVZp9*GakjM zVoXf4r^kx~O~1{!_wgYQCr^DW=AOSTYgzI5l&tSp6EhbfatZtsA#HrCdb?f4MJ(2Z zk5ZmUc~{ZrNW0ef2~!B{vB~$+L~d*6jS5EV%7KxQswzN2V#7OSj9+ZBp{Yvz>+z`+ zrQf!MsCNGS=?vQQz)jfal-Y-a`fJgZN=Av+GruB|b`bogj2R83BKUcvgJ+7YRDN66 zS)TeN2mYJ_2dvMOgO?xfzIo^c*!mrc1TmbYaqB{a$bF))!PIJN?DhVHwpXoPt;=r)2U4nVi_%XGHakKuay88IWHHvH#_M$kuX(To2u) zj{Wl`Z8Kv0i_OjG4ASe^JFEo<3AH z->(zft7{c=*NKtQ|M7YZiQ<(oKa53v^+fG-O)d;=qd<_83!W3{XPSE ze?@;bQkl1FWN5r_Q+sXuH9QU1X(Q}Y9JBAOAA_ah zzAsMwdA61FN6ufUedY7!I`!G3!@$P<{!o)jOlx+Pv?*uu(=Pb}pVw9{-l&^gBQ`Tn z7IZzya(Ey=64ln3*uWk{^^k2@i{uz*x% zLRsmv^p(t?7|-unjBnNyi%^o*no_cBNcD0J9THpZP$Y@JDr1<=HRFzkmmdwUlgXoe znJ;AnyBRh`&+yl;cX1XQzz!|sxZmX$8!o;JPX??Dv6X*NvDZW%k+hmONm$sUd}*@k z<3;uo-<|IP^vM={2nMV&+HO#FOa|g6XKB6hnF?%*8~#X=t%3Z?cav4ybF(U}_2bgS zOb&$uWLGG5VEL2(dfjPi8A54vVCSTe2>Ma|BgXNOB?`{>Xh$g$nMkW_ZHSK`e_obh2@n-j_sHy zI^K_Q_RfmUaR)n(L2rtp>UKGnKV=h^3saqmBwXz>D&KQC2u5qQ+WBq?{ppRP48Ge7 z@1N~XuCFCyv^9Z;p%yAsB1gPOD(vnmXF$wPho9^xqXXAlZX|oN^UaEViCSM2oUyJ! zWwjJMs<2pUYHC=QET|O$CxBit_>^W-pfv*HU!ssp+hOj5aYLnIjn(Y3nCNyXpWzze zr@H3RuFPc(`CTTkB~G1a8_Ha>LQroI(bb{T-ggEf)cfOytG%akW8uL} zJq#r+o{M+ZbJ-i!_#&!S!CK4ST$jq7+sqB9IY`#UnZpNtt?hG!ActF+wB)Kw!t&g(Ki(eNXzQtZV9)L} z)SR^j*V06W^bOU=n>>ftTV-nO^$0Y+pEA(B1QdGh8eT<7rMXV5m$Ch;sPLbWynn#X z^Dh5SLH{$y{a2see*wk+^DpOb82K*}{l8w|E7u@{xtZ*eLrK2Hag6bq40KE$mOps@ G_TK;~oqjn0 literal 8303 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 diff --git a/resources/android/splash/drawable-land-xhdpi-screen.png b/resources/android/splash/drawable-land-xhdpi-screen.png index 0398df2bdae7435fe15e38b8701cd7ea6c0ecc5d..34d11f804d0e0f9c7110e24fb98b5c5ce2ac7e4c 100644 GIT binary patch literal 18494 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 literal 31537 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 diff --git a/resources/android/splash/drawable-land-xxxhdpi-screen.png b/resources/android/splash/drawable-land-xxxhdpi-screen.png index 686f1313291294d7e486e32cce8f852e9d647516..3688f573275eeded7a7c9a0ce881db3669e40520 100644 GIT binary patch literal 33932 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 literal 41246 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­PJ*?!&h}(4fbJC)avJbj+YX!yvVLpT$cFd>v&&JOT4EMPSm}&rQhB*n0 zH!>VI@QPqU!l01)$uaM;)+@${>td8hh)zv&bhDt;OyP%d| znw?p#ufs^yb61b?=su0zG*AIMJ)OgfKaF9yt@CbT2s6r4J-dA@2jw}N?QY078c(2Q z#Ca0Zz~s6hf_RKavKLUwq0Z4@C1jy~zYAfdScfMOk#4qyC#h4MLc4rC*WqCG`Sa?F z?)c4iMhN))o7KXL30OW3o8RwQ#d=&A|Ld$}5plNC&5MAHAU_IM4XpVv?6`pMpJH9i z%3ST9D1G3vc)o5kE8BF~ICT-ql6=F8u7PMv(l<%F+n>8mwqNwj_^_2Ox@t0Bf_G;^ z$oE6U(;-rRIkf5MV7@stVJOa%C@E|g?v|`(P-2YBo81mqkac8?-;1lZIqphINyo4|Xj#ghc+|>UV`fbg7=aY` z18cXi;>|!oX2V;r|Flw>Z?~$`<@L7iOH9%+mr(iLY~{9GZWg9&EDqAmZpLo9C_i^0 zTpJ8FPJfm*eoIe*((Q2(FgNVPvoB^9h>;o*+p= zIiofHlZE)WB(?C0eFoW8Y&i40nK>rRw`xRNg-WVuW76~jO|tAN-MFbExd2?tz0Y$b zOiHfuuaMkM_tqX^t=;I(()L>1EU+4XL=(Gt25mHX=OMjjg*EO8OtA0R$K`l;5WCPk zqC6l`02WU8cr8+l`jAAAL{84pn_#gJoRXeg;Q&Th2{3$CK?hw^MZ-8wqh$UDR=EZ& zABtf*+4;tuBFELZB58x=<}HT@`Qd6r@{YI^M~B;{!h0yZ%OaSydd~`hJ{&qXjQw6@ zEJdcr1?80V2dp^H14tyKk7?}Gosl)a&q@5$ZLglYUaNWNT%PBmSU;U(YN=UjFXK#TUc>RF))pHrmjZa%y>Zm( zz>o;pL?*lTTSefF1W8>ED~kwfs#dPXl`y@*Z>N#uY|5rpW14-R52s+|Q#cMOG9a*@ z%|s8{wfq^iT8BZt>l$q?URc7`s8wXT3}#F3mGBLs04Le0;uWyf$9EJwDyKUNJ|$j? zdmDFo#?P|>4iVO4N-0z6CK&&`UZfchQ-w6C@Wp*z!wazni$V;kQHSa&1_&nEeu58r_*&P@^W=*DR1Os?B*{(l11)dD(^O#zOozvX zTVLI`CM2GO6mhU<>ayR8!~on)Ux(lh8Plkn;BY8Ple1LX>%g<1U~6igaA_Led@HuQ zlq|9Fo4`md4$@S)QC~t0RQi{|L;4$BuTnGZ%jHWH!#(pe6aM%bq+{PBbkWMEE*yLD z)JGe;oRC>9T}BWhTz#ScFZ#2u36b_D&7OLjnXCG;vnx6BBWrPO4r^}gT=7`A28Mq; zGrlYGp<%l8C^$~!!yMa?Ss~=nwW8{hS+%TJExO`H`P~Nx7ZWGB1hj?hAs**FkP^>o%orE6rXYxmp+J?*n>VkwP;r_gg)%Tl8igI$weF!4^(Vts6*3yW(TW+qW&y;&5~>jvR>kIu6RI9KDtF(rT+^o}Di% zlkEqV=Gcw85rVtXu#UKBM!)^W{&e}USI5^cMl3`Weoh{Z_merU3ERxqj-d9h`SSEn z6z!=mS%_dY+w`po+~gS`Xd){4LbCnwoRF|3Ba6+^!eJT3lX-I*R^m`lk!og>So&evpc(1 z`y#5Ft@}*TPv|P>V{0&u<%I?Da=Cj_G zG!oT4?uzkvetU3S0ds6g&_A|mQy#Ql9Ur*dT0T_e6t5F^)6_|?!7-bgAu5&E%b(1* zneemQA1V2&3ED$m{S1OJ^Y_K$&6GX$#09JR;w~$jHlwv3S=oRO%8Oi1)5(Kv0*sQX zA}dN7IcHip*8~OFH6BR5xy2{SfSX=y0=Oy#W_l@@GC6DrfrT!-KTD^qX<(^u4)$rI zO$aJx4o8ngEm99kHKYaE$}+S3^A@kd_WX`>$qJ>l4S1znXYzoU@gc|~ZaA|)`9pI* zWi~qVx9*%>y}90AHjQb{QV%(_`;5LDIi2?29lLnzXvK{foT1QR*QXQ{4fHW7wb`t% zkMo2+2znxZcSK!(D)QB9W{ObCM{_aEu@$+25TkmA150w$NK6AOf$V;Gu0B>M3rCyu zMWge?FaelHg6nzzS<~>-8&$e)s9a_>EK3#-%dge)Drjd3?(Q1ARR5eokK53S`ysb2 z?2`QO9SSXpLQbeew5?b1{iQ_%n$)Y{vLCwu4#Ie+!pJ@RtXm$Z2>Wb!=CZHmE6W(P zBf#JE4GegPg@x@sf~qjo^uO2c5ePF>X`OC1b9`z8lAPoUQsFD0MK|`AHs4Wh4JKP5 z*x53Ul?_peO`NIL9rr8BrRb19Nmq*F+HryT{ivk zY=J|}c{!c~-~dEAR2k<5HoEkAdFl6bJMUkeU!9=IvQuRStuAek#biX8a0F3`&kuCs zIV&k?S^mAaZYF!9@>P!dp5lmv>fpGRIRNOHjzT`)ZoRRd`p?dyD`Z3*x-0K01isneq8dNKtf4apY2 zdPj@Z(n4Z0KXG((m~|Lj1BMa~B@f3TxGigS`3tb}im*>?#3FlDE-S;{SEIER?8N9| zuCdNXSp0A}97`a2$fcm%`OW(Hz|DqJZ!PIRC4qZ*GEKR&6umVVBCkqsD61g+-L5`2(%lqq6<2(T$}NQ);f*&L{K(*)wy}D^5<}qrU}}Id0r7@iQEBPvttUP1ufX zZmx3VpfX3XIg=vJ^n}Fcg6{krEtt5OqAwY@3KB1rj99+Nb}TwP>R<`3Gn8Va{;Y~n#z$FfY%eJ>_I@^JnQ&qZ1Ps_ZL0p1R2? zLA6KK9X`CWP3h{xk>n4mN_SrEg9)mGsLr6}-JmD>vPpe9aL55>Qnuf(F{aN$Hti7x z6eFZ4bZqp*MeAE8hC-q6!L}vlEn6_Y)b6N`o^;;4a8t?w8dVy_K2yw{;l z_bKgr*M+UC&TCx}&LROeZ0^~2F~1Ihbc;y`Bw36S(J-ipZX=DfuSuGFez-eS<#a}r z59fAtioWUau9l$G6RddsX>0rtSICG2Adej$WQQu__NupyU*HiRmGa;j8nR8ou znOz+?rYhYOXGvR}V@eA2-NG3)Wx`p(mAi33_35boKj%AOSW$x6D^FXY8Hb038P%lC z9LWonI3N~#6NUpCzua!JCWo~7%t*`~9}C)&!n3X%4ZxTDECZ?v!TOJ?id^x!eDsPY zKn2;?NQ#2>b03+B8GU7%mInJ1tMImFU!yVzPVqT0btRk zxnQ+V=dQUCWNP{z@(vKLm+f@34(aIq+WmQ(hv;4&%D1U;3xpicNE9pYCzwT8sT49*=;JjKIws(4r#7X{vo}jSI$XK%@@_z6d)z5Kcl3k-GLj z(}Bs%N<`1)h1^aK)Tn!ZVN}A7u_yh9cb;b0N@t3&>6k=(qMTF#Wx(Ke;76Q?!`?ioqZ8#NIz()4RHaFutQ4+nA$ z2qlZJ!`-L6VSMDed4JlxCbl;Ei>qY2J@3)cBFTEmW^YaDFu_%{Cb!BuprkYswr{V_ zcR&#qsFrZKTnlS!>-FeIzsSGgRP_5c=eQQ=Uoxo&NuU=OMv=i{ zeTjyNvbve?oRIIxxdj;t1hr!1D7?8GYVp~CME`M*%#7}W^WF{Sodi~Q4v6#NQ@)vq z|H)7bLco9Uu=kYx*pZSsXy>u*MXnTyhjI=@SzYxVCz#duOtqn3GEXd{OoEJe4=&ll z{XP!X);D!Mw*mvk{6T}Xxj}XBF77z|WhPAyOFt|k>*>t7NVQN%yTcTh1GnlO`pV=m zanMF{d!paK#qB_gg~F(VfeIiBg*vXJ7w*UX9EUl42P4{_1WYn%>NINe1BQ!zq-;4{ zvs2duAjJRC(hVo5yZRkC~IFZrSE)rn`~x#NEUp8? zGEOZ$HTN;CwxL=}F>+75AJw+yKT~I0j_**qbAtKZ&q{Z~D>;Jj#Kc73!*^VO(l2Ii z4ejE~Xb;TsA)X)xC4DZY+~}xW5*>HN+Lx=B%1Im#X(8M_VZE*>rDyE3xXvc107!Zg zJQrNaGu6NOk{CBe!S8ow11ht;$aPCL`zV zZh}?N=J={d>oEd+<8H7GZ?v&g{wm0gyXf1V5SYiVTu$hGoumgsF?bcsZ|CkP3&4Vd za*O=tsLiZB2Z{ChC<()< zK9Y9vP{Z2fDo9*|-IU-KT^skKTDDbQ_~8=1q|S&H2b_n^HE)hQwH!LKq59k7Q#ZW! ziuuTSYKi0O-H^zzTN5=eiXVlN*2@(Y2lWy;pL0H;^FujN(*1$lS0;<^hz0O4Xp;(H z3Vz7-9ROGIC9-K1heE%UG944~6$a(FCaC?m0bx?~=F~*KcICE!bs(=iNPTjXdLrLH zYYCwx7V&T>Ff`>FKnofT-m3v4=|UWi;c9K26K~(qz}VL^)_O0SLIET`WsXU~c!_ht z%3FYQYE(;~piRCyo*|mOQGR!U=q3-An+$?BMdhVs8KYPBP#z`D{_-qROd8jl$sy3yoC6lIM)EeJZb=egD3n96O8%pA)oMd zdhpPp&+9ddWt25^Ny~gQK-sqARJzYeK-9r^ zJ12+60AR3X<4M@eEQi`j`N{%V4mXPEE*u0wn+BhCC)sg|VOUvvNs!^V8f`>~wGJ<_ z-^6-r(;=lps`LXYbtcXFP!=k+7U5MZzgxn}b9YxA3JbVD{1?O(>&*`ma=aE(;T9Ct zadoo2Noz=3USWKwy?I(VS+m7%EnhQD8beRO((ZfXc9#zY<@(?9wXfM&w8D%LxTRD# zVLixgoo#QM={>&uusA#9`Y%9=X5_vOu}U$b>dBQ@#T5Eb{)4+t1O!@ zyD`l|8YMe4-njMd*s;`Id;TasEOG*pmob4+Uqfi+ZGG5ny0Ho&q(kT#v}Iaev_|&k zIT;U{glmwUF%6?a6%#*!2St7)VsYz2cd3Vg3dUfZwtNp6ZF5B=S~n+ACea@Dt*UgJ z;KCj<6i+cQCZ)7H^drYHW%j(^&IG~5nTQ*V3x zRdNJ#{^#lgsLC!+Y&=$c6>$D}R+<4h;&(hpX=EDUWOn=$Sc)%w>WN56vERLWcLhp! z0x0|$<`)n>2!{cWTcD4Y6}3#PZXR8A6Ni#I02)x;h~VW(5H@*hwi>Nb7#;~Lo3vhV zhmtLrn%BDsVF}N7A8aI1L?hXm&QE%sAIBSG<1N^aZ@<}l%ZYfK*X0$smgOBaJTsRO z;^qfJuQ%f6!4|lgh~o~b2MQ=9HaD-eP8ya&kul^)yfXVuU)pNZsZTK1+GBaQuZGtK zdvz}=do>^p?QRJsc*gcUm-nz#8wOLY`pVG8Yxm60*H2Cx2R&(et+rf-2C>#=X3CxY z3)iph`TFkN{*#52{Q%4ETEE;fovq?kMXC-6aBb}E~=iZB}I&!no2T#cuX zQbE!vlyQD5d}}VoCxJh}x)`)arJ0>HbO>-Da>M`PgSNWxztv!dq zAm8)kI!>W@5R@;j^(nnl$DP~i?eq>O5dGIz;(7k#k<>IF>X8=RW+07{8?a)fD?6UR2-){So)dfhvuBw$B`CI^7ZLF*S*^?v z=dr|inGX1|h7yXP2SA9;K*0{o)rT6DT3j|x=nXyuVG>+fTNr+jB#)00Vw^rK`$WV@ z{JX2r&Og-T&9vI7;!`9z`aXaQ51r+qT*wcCie{(lh!bz1wVym#b*6nCh!VpHWrQ zFB8xjbs-?*=5lk2#bBK*7#|f>B(tXCSM~I~FFZ26bqG8N<^tz?uCClX)mP7En_m5? zR#C2%jXZ*>i{!jH^D6SEKfJZQ0+yZlEnAsh0UIse1d`;kUKu%7E2Y=H3{Tlls53t= zWx@gCGmn`ziSb;Np7hk$LVqd>@s1uJTjlh(O!#>crIp72nN>~4rYke4>l!%rLE8I~ z&?~9&2Yg>(>)6@!BkUY8rN*s3G2)wKdz|xUaKdapzc3}t-aIJ)uZ%M2Q$y!-E93|Z z`P&umzjCqe?xZmQ3c|N8*w>&WUgBl$5p>@C)=JWEuZsJUEeqo5VCe|JM9;}0$mRFX z)Fi;1HkDr5UsvPOV(fid?bhl7pRz$)a1+E92i*AizvBB3$>>J7TU>{}k=^50iH=$@ z8Qsws2nf*GBZ03JkU-{6Dr#M5xx6mNvpHL7-3Rsc&X6Q8_r%v(_9e(&W_tpy zH^w*Oho=Fp-yVffW@4Wx+Z1GVYAV-3shcgEg2ustGP*h5QA@8>I&h3=UI1A*Y)j zLC7?yy$?Fbf^q^#zw%3x<1f%`8=l?#=}S%~{Cs|ahqZO`>H{GxXVndF4%BAkiy^o7 z4V|f)EwL%6Og&}K*)Z=p>kRz_(s` z8%H}{rwg;WjxcL~^r~h*bybazo*%}!fO6d3A5#9CpyeDI1mtNEvpnkY!3^UchO8a2}vW{Uf(|$LjlmiSH0)-L;4tmE^+- z0cF+7Xj0(i%O3$X*^);3)=lsWAsyc4>kHHSuXd&v2|~@e(|pE$*r&y z3ql1IG%gT#giQ6|*msxjnLZF+Hrx#Z>O%H?T*$ud$1s+djuYjfi6{E<R z_@#Qb{bw4%(v!N}*e_it0^&?ezR=iC@Bmg(yyoU+@6+C` z4gB`li$u{pK*kN@_z#qMcY>Q1CR2oiH}N%F$=!A|TvV>bb%tw%1}ZX1vuJo=4O z>f6>`SsxWv8`~GpmC)J(bJ)kwFEXz!+5UnjJ%^hZ0&-`CWEIdH{;C;}tuOto-v^ft z!Doii(qSRlr}Z!Zq8{w&E`GLgVLyzSye2BMm#op=wa+vsv3m`p%z==Bvd_;?=4vi# z9=YOXU$a?OPM2kr*mN#G{H-+|Q#zlt5xX9-L{(sI^)c%kZcdB)YY_ly;XI}zWV``J z+x6oo)4JOQpScL|PyQfYC?CZWl#Xn4FVlrrcPhsXc;W>@reE9V@Cnjz;*Z-~k^ zM^e4ibuKf%6Wx&b8pg!|aad9^&q$Kr2i?@=0OgPoxre>Ed}wm>;8X<0;b%am(n-lMS~mPocbBw%m6{SEsfHs_QvE1+qUQ zuvy#2*B0ploKYj2t>KDL0?|qxNK!{x={^DoBAHeYR}+_-KlLu{hRqF;+GYvh`U72C z->qNFDe)n-Z7q}&6E=^v*zFwEc@!?q7%-Pi8U*WkjDz>Cm(8av9Xg}4^T}vq2}PI= z4)=%MDt@JLX3d%b6#wM`vp|xBmpxZnei|LFwcSQKpc(Ll?k9_@<=UlN+jjl@oey}> zL*77z0$P8J-wuBm*o!%~J{cl=a;k#;s$rYJxG&zmM*IN)Te=;09J`$VsNA{mxngaO ziJUVpYg$^My?a${>v-A(D6MuM94@7ZGCrrfY_^?xJA6}orJEatr9btf`Rt3=c=I)r ziR^RO%ZUnhg2M)v5={Al(yW1+O;QQSI!T(>D(Hx`zWKc~h{&xFcyzZvT=4XOX$;wd z`1gQ42B>N0@Et>4x?^Wy)UVB+PLgKqy?e*!J6`VED5XN}Ze7>|)bzS^qyA312NMWD zu_^S&J1+JT#EhY~Uf%;E)igC#IZj>_6T`M6j3OXBHgf!?{U=({J$oT!Z^y)`r+#~D zcIziolHwy8t-xUG`I{ppe%Aa%dpke_tUxndteY84C2;9CM-he5XU8jy`^L-j*9olL zK>DXw4d=BONiC~g8)U)E086g5wkDQu=|!C|1v{zsX8HbMp~DCT3o~;TaZT%mp+FbZ`2s}(d*AHb#*nTiQ}tfdFAHF&o2py;r`k>j)(})Ao08S zKzR*Q{;3&PYtykZ987KX+}ZxMp{9f~r^0WUE)YKblX^|U36pe0EXp#M8^@|GUYtx@ z#FPltK=C)mrglA_z2@*$O}jr?SOZl3M1~I3W1R_4d#>s(Y-W1J$dgvahSi68&Ld|H z`gDn|+(ZLhu32MVY7POIf1lh1>jWoahhaEs76 zp4Xzpm3$;HKeQ$hz$4)&|7Ma;kYfVwE4|VAl0(^Kc|!>Ki>HgwBWb|T-BmFxu{WVE zF6I{v0r4XtwJAACezu_x_)A-NTOeUGkPzra3L-!JT5uR#=Gv4%3n`=FV`!a=r|FI} zuN`@#D1$uz<(9&P&V6=kulsAm`tTj=qzJq>Y;9oZWcAKH?t@_CGH1b_2)#{jrf&F+ zONLSuxgS9DV{UQ1=GQTub<5cUJnAofTo1$Dq3+xIUVaxZZvX;v#~e=}!En;%*xsGO@>WQfru6bljdp|quD(0n!z_9~l=ueach{ewDw+5a z_cCEEg5=EmZKmQh#3B+sPFYa{G*d;{esU`9{TuRn&$ARH$0=fw;!N;rGjvfKZ)D?Y z7q~!zPZpA)fagvbD(FxCTu^ttJ;>T0mpnz|x5$WPKr0E5b)-LzA$5Grv9QIyj4u z7PuliS#E#VsG6!)IYU7I%3UC)0SaoM*T|6ORq;ErJ3~{(|22#(^ zwtd@M>RM0Pj+NsJPqaQ(8Dem2aUve2Y@z1?{YyaweFT?BtT#5di-T9&MhBUJLeXX; z#|kXNxfXzuP*(DQnT=(xYWg_>x~`^bV#=W4Ne0M|fK0ZCb7ks#ks`4rU~+vqy(!K1 z(bS8dwCNvM3~(3}if3<_We+D@-OT8TrOXqSDBkh#p=9~f5{BYOuYVy+Y;vr z747!i7ct4#F>xwH1(oSQYjjrQ?h+a_MxQa$8{tyz>$y z03bTeShv9T=9`OWa$fet{B#x(xVk{2F+9Y=;1pjsER;D9iQ{N$^tFE-#@23Tg)mA> zEUPaKz;*>|ru2HZOzNAaZ`_eP z0FINZft@;sBtUMr7uz3%7q9q)&x~^PuDoqUeb6wNGt-CvfG#|wfway~jej`!$&N96RA$~)V27<(I>V1kyCjO_SB>R@xr)^;|~a? zI?rueYEM_rgBL?UG@OQ(%fj=y5186%VVCnIQVH3}6YdyCv(yPLJ{lZpG}!bYW0L;H zj0}|N?FXB1U8`GeiOAdbgJQP~JK*phtSIq4Z+A4L8sGsu8^%s>9bXtgJcCvmyu*VY ze>m~6Ct54Xr;Ly%eX%AZGLPK%JuI2X9v?C}P)-p)2c52yC_ zbIFs8xktl1M~6W}&I#^MsGN$~Q$DN8)Ix7%%c6q74A_KMIH_&5-uGrBHBz&zP1f;fPST)#Y;eWwTR(w?Kyf@Kr!#YKhi!E!{)?=?p#wpbw6!Hi?*&;^XM6E zEpM;BO8u!584?cPD2m?u#q6-p;QnJoyMy^xY}S{(35(BmtbQ@WY7B!+`>=o@HC zeKEnZlX3`5g(7J-=Dz7kq|?;)2Y~_cp@7C6fAr|X@E}+4EG-1+sy1!Z!)JbI7{!$6 zd@1yvx-!@F^Xph*@+Goc5DpgmO#g%p44^TH@lg6z>FRFN?RxAVUZUaMS&*qzkT;Ae zAdMvbN{%xJU6)i$9t4{J9TK^-4>b+@v}7?C5V%$Z$uX(t>pBp->US$**Xu_^?~xmm znQ;n1n4*2NI-pryGV)Y(wZ$U{R&G?<1$x*7H5z`1&?XVxt32#?_gEI)&tHb~UM{Q2 zsSC1@n7OO^o!16ro7ucFXL)i*S1#g3rhqYlUSs8z*h>vNW*d($To?zd$GEaKKTR+~ zz3(?!vSJa;eZfbj!w%%zS*{s!tV*LDbH?|1gM9k?%gw~4CHP)1{<)>;990?3IdA0~ zp(9$KymZt-56ad1dHdTba#IY~iy&hTg!^Zoa+@9eZ>@TTGXr^wj5CL(#8uqjgm?uQmb)^myn>dnB|39v+tUkzn4>uE-I~hP z8$bmM{C_KRnmt!Tbynz*6pfC`dP!GFh&e5?m;LxMytI4btMszXvC7`6PL|mTUsdJY zV0PA63?ORJrM=s_c8YZz1_QK6(s>`_;$tDC-4jd$OJHhm4)YP6ZyaDuqV9H#TXyTH zr~2Y$|GY$<0!TklTG}k9Q$lFzM2&g1kHp26&v5u{uWSx+jQxSKX@{ygg9VKvY#_dFB1%d zy{f7)C_?t(!F-0Bz4lH?nmY%Re&S2$Tp#T;I;q+(S^)f40JdYdgL1YhJ^S@sRS~>- zphZDjb&uVPp3D~Jsq0<&eCfT*{J5Qu(fA`UD{uO8=BbWEMk_0O0ih!!Ir(8J(diAN zQ?7U1aI>$Ecvks=#`0hmyfR{5q%_pT;e^JKV_lY8^W%lLufoY{#-`5xiB7gR5e`|& zH5M--i@*tNVWzgf6MzwwZ3!jz{FUs;+o}T5V9b|Y=&mzhDyVJOmM1pGrcX_7Iztz! zjr>-#p{C_>5#gym548`D{58lLXnp~+8XX2{W&aq!kAF*1ubhjLE^??K#CU>_s|g^> zZ{(mPu7aP9{{kW_r2Fo|lwV#W87oVxUF-$yaHW?h(5mpD9+j$v8#lwDO;K6Mdskq_ zq0XU<@ytEWd94_O)+*Zc$nkr(AlPzm9`(i-RPrA0RQ1N7Xj?;8(zV)!-5pAcT8w1c z)rFZ&C)!;n8dE7rqzm>vZ;>$H=%)rxyc~y`^&&cT-cKuy!?mYOO+}_7bqJ;WR z^#{Lj)rG9IXG=g&nX5i2Z-RmXjCV&_+(<+`1b%Kw7JuvfoZT7Pqs9(Pir|+ZIF;N`>@Az@n@ek z;mehjnDFj)r}QU2Fo311sP5WxT5T9Pek-h2_V5g*>fCsF4)wrQ$|LpzaJQ^)U*Xfv z1fCmqfHs{0OZ`FTgn6jyfdmtwUGRAoCUxEt%B40<~hgN37I{oYq^S&uiogGQ!8KV_1S7@>wc4|$mGDCRrNfZ zvff4iLf|7;d!quvUsf;U)oA#&;GeY_?$(zvi75HX_6ZXDyYQk8?c;bPjV{T+RIOX- z0A2gz7UsP>lunK+TJ#+f=g*f}UJgnet`1-|B}8FQomcG%yJp5;(4I=zwD`C!4Epoy z|JUA^z9pIdZ?`YsGHaU2{AxOtOJ=$63r;m-;YMRwnJJm5L#CFyA~KDo(_~WMiVD-H zn0u+IsYvCdks%Yf%2an^E%J-mIrFo z{*E_Xczgkz+geVn<}WxEIzN1`ulxQC6bx#JZUBy)e|_AM)<3`VP<6bH^`OO*DqM_3 z;V90J zfev9pHiJTK_(g0uFh}nT_<_;a7Y3}Nrj&r<-{&~st(M6L{i+J_TS;|f9bNZ!tH$7t z(aI`BT6BYj^I8ZJ_hMX|U3?MB+SzUENLl-*3Y5WCw>*5ilX-r$G2F|}C{Z`${|GG4x?dtBma+gCnbiqLxp55` zwaR5}GX2ZP`PBmxynA1E?a>y4c;iC7EibDd%M!VrhP1!qy4&~tV*A8pk)hdq`Q4uz zZjFU3_*Be$WhFbqLdG(+aE~98#$uK%`>so>JZvqk>ojg26Qw|- zFR0qDR5=xn=UO?taA?34e(BA>XSv5q|7k%I3^E5d@mEtoyhN{uK+pIuCW3cyraEPR zL!FP`_yIOPf9FX7S^L7}@Uxr8Nho)i{O{4@&7#fj;ih|{seok8i( zF8(=WQbKz}Z~RZLQMrgH6)ydq5s{WZp_+LKbSpZ*gnaVLS$}pV=w|-c%U&0;u&y!H z!=KpvK0dtsbV|-MO?At$1>d~G8(xL+0&1#Ka{aED|9mk$^>ZRbHx%?fZdc zKzA^G<#?s!7!OwX9T?iDvi}9WSN#6|{$Q-1Uv6M~Lg>GTT#xp8k85MD`gt(nNppZ{6#y7DCtR?WoGezKc`Q8$`*yp~_*ls*((6YJ~yAUl5j z^!$0h>BY6asVXP#BJ|7n>2CdZ6b}RYxFe4Nyz>(N_=c-_aM%C5=`P5~F)DPkP^6Y` zP0bl!z`i(e18^oWA6FbIeGSJg>-PruK^rZ$UA~w*wf-OY{^nP8-L0aS+_=0=FH7#7 z2;kkSTfO4p5&eFVk{mYgRC!@^IWe`=cY)8&`o z6M*my$E2mnFBo5N{aVcX#lq?`(YakwaK&_$Wfi1kG=J(EWqR1h1SrrE*Un1e0Qn{y z=AI4E#%?2m(^@sB$H&guwU`#7<1RgR?k)*v6N_ffSMf#rqZ6Lsn4X2>&k$#-BR@ea zoE>&uGV;TvHCVLIfINh!PX7bOeJ;ZHH^y2mDM98SucE37*dAs89-C4kihnU8&O(q`I-^4)hK{NnAHtEZW| zZzoAZAqAPfTNtjk^UY_stXDW}oUHP&BI$n25#gU*uosHrje&lqw^8i4vD{=-q_e>I|>@nZ+-;Hle%MeEM_=SX-T*0(D*)@r)Q!VAbyI=cC8U zAkL*4(CTVKj%}*JiEw;wB^h?P7=IlooUb@{PSzuCUeO;bv_gG9&}Pe6GjYt@vh|9s zrDLmh2u~Va{xSa6x;B5 zM$hA&BPk3Bu0j52re;S^Q%QxRKJ;DQ=ohNVYPxbA-Tez>US#^|4*BDfSPR`HgU*^x(&(Fhz zo6!DZ2Isl*0h?wMnzZNgW{ciBn@X|~uJFN^LU#V>u;k`D{$izFan8V=KQzRTc}W(- z?Y3O6tZ3(Q{B^n`)EIhhOCWZrgxJ4e-MPEfqv2tLls~g_@c~i?K%TGRR!uUmH8C~w z2R}n^FvjSr!qrSnYR>fq-ve;PS_q(WQKrE+TbP#SoXBTt zaLUW@&uZU{6rKTg-DFYrj3T^V^hA?Q0qg@0V^CPAd#AA{bt6z4Kz)+?f;BJd^!bqc zeD>KH4*Zy{922x+?anYyezxRTi0Fymvf(+S}_Jh-rydYl$RlWc=uOo zvgSp-c)qCM=)k-0CwY7ZYUh+w06h14&E7QTI^nO^m(IDzb25;HLc z_s67I#rs;ZVe>T0O^*#L=W`ZY%6P+tK(Jj6O1~2u#$1v-W3ICP^j>YX@vAy#?m^xa zZ5ddSngA@U$ONO17u?4lRd;hTJ8=#Fc-lVmFxP+2KalYX-KEo*NJXw*(Sn^c$CnmQ45zYzcNC~DhC4{p162tJVrsu6O?hQ$bIxNXfF%KMD2TDxN6d>Lm~c5Vn-7sz(V z`0>Q@)B5*!wSo$y(qzAvHNOTQX%?5wh)lQm zU7v>jx^;j^Su|-$z4`EYUH>xVio^jdZ|OYTFw|~ZZancP^M#Xg1#nh~Xa8y5d6GR} zqh(YQKV5zD7_`lh9;gp(T%9r;cxoDK(W$D{_ZCmsUc$ud%6$P(25|&QAK^hMADi(w zFK-~M`U#5h`tBuk%fdPd@VlvICV5M?a1DFOPVwUxlKUp30^Me`+lRaZ`%8p_KuY%7 zl452EH8w#p#}pEp;`N`iAb0-hytK#}_$<$SiKm&F!NdBh;mY|pJx(QiBVFAuTrq*R z_{0M(>U+eo+^2_2oan9?QW&eYE3*E_HJc|XJoXAVhfadEO<%$!(wg7>Shb`6zp7hJ zB`3D|kzzv|_%&rz3*rx{&%Rj}=LA3q%Xj3k7`Ljj;Z9pRUEu8`>_Tg^I_%@;SwY3~ zW#{zT^`TY5AY8u=KFc)89NJQ}^7c8BFJpdwwVbs|6ZB8+2hzM)mK@&zT;<>KhCIA} z!UGmw7*w3Y&Fb6Uld7{f!dFjmo`C9fN#$)SjsX30-sI4EvcZ>%KEA8(`?a z#2ufyW4+CAen$|&s~GajM%TKtHAIhHH$2q$F$B@+$Guh=heG%Nwm3=xo<5(HQQUQ} zgO~h_=~o)Wf1P;4oqW}$#>gBieUhiv2G{#QJ#zcEveYXW*l=_F=IWoTf$uZ4VQ~jE zJS=MpgYUPl>_bccq{kui?ksdoDr-1~I_6MW>~zG&KyQN`MJ(zvW6O7c_M&Ut!tyih zA*ri-fi~-Vu21pOZAAP;F0Ef1s!f6P_-Ypi03n##6GJ$*)3gs;?xP-Y z)6Ssx?(g2Y@#9=4aDdEW@qCAt07GxLLMQ8iH|EZwkB`_ujRdIfTTK0spW;kFK*ubf z>ZzWK$f%d@&s4=r%Ar+Gp}k#PjLaS%##xI>buimG`UB~?sMx;FW9eQ_yITB@<;}78 z$*XJnFqFpC-Ya0P{#y_`7`Z**^xcb`U(&%xI+c8>gjt+b8u@bw0c z^)fY0UEB~Z`Tj;w_U@$pzu8L0^Hf@C_Lbpy-*A`PUVw->9|2t18nphu_vAjw?9Zo| z6*`2Z<_?uAxi-ko6$~)q%R-BVC*H(6-n0Ogn1lxP;EC_nTATmntOp!HW`Wo+Q!hZa z$~4$J-mleZR!O$H@-Rg=UH?uE9hmzh?IwVm@mh^HNnc7--xfhubZFuT*gwxP`IkD@ zha4S{vJ1|a#!fu5tJx%QBg{2YiyBZvD-;Qlr&^T*wZOZvEAL*VXF`Bje7GfXP3>xe zN;n|1jUg+YHHRSe<&Mwr9JoCCyCaxH?}*0Tunyoi-dN$4jgUm2~L2Vq!DbtW7pX5+lFo@w05ebv^vVCEAq9l)wx!FnYY&36ou$K)K^ z-vuKpc}A7S_!n}f`Xkjlene2=w3ubdkUj~#QKGWN6vr^&hI>PHV$Ox`o~!djON(&# z_|sX@mzL9pBY^VIkk%QnHP$-M@fRHb{K{Knrn+CBEYBmss;pAmkDs!gIr1wh;EcM9 zS{TD28STaQ!1l~v(&$TGEwA^CRr=FjAD*uKLr-sGsIZ3xE-jNUy4gEy!PWfo6L0C` z=Rl#lELFRayT0r5JRa%{jLMgf1RCe!1Xo`{Yh7gK9I7%zhZd(br7>%hC{Z>@6O`S+=*j zKOC4*1wPCy!+E{-lpou?k>!_yyv18I%2*tW09oKx_~edWuu8KBze$d(3nosh&_ZkEa&Kp`h9! zqf;Q z55-)yQ);Ti2s&zTZmuzSCvTm3r~X1XkZJqZ0|yPGJ87f4tqWTz;F6TT#ns93dKR}m zJ?5cZoF?XbzmV4ZHlu9{FUddZT&wC)cI~}%IUa6IQ36Fz^LY*=F|-@xeZg+;`v-KE zvE~?QZ|q!Qc%3Nf=3hslZF?*06?J70K9|8XcVa1P`$f<^8=ilQ6H4Enf<6A9_`h2y`PhMD(0OKKcPrE^%#fv3m-G8Um=mG zA&w6A+c4NrLy^T555EfaEXE6%A`Wq*4_wH>S|=TYf`vQWQtmX(tA~eP0%y1&Ey9pX zv`wOPr_{g<=*{4B)HBDVv%xx(x8hOzFGLK+zpE?ZQq&ijsnQl))M16IFA$k56<>^e zn-suK#Ls;Ysh9IvW67)n>}}&`Zk5bbF4`o3THr-0jw5fWeU+aUm!ZEZ`ZHdRR~6`{ z#*smFMU(ZLuNecoe!%b~;1P(MzLXa|UbY?kW$I`_Un#F+!I^;}awd+}PCV$C8t>kb zv_0&Z<^6*hM!enM)8bCTtnNS_l8sTuu~uEhg>g=_xX(UJe0dAE7|&+`&!;Wa=j{gk z?&e=ZUjF^7M$#*t=H){X)oqgGIV@+w7y9VlGe1La_0U4`$&*LNQ6v(vEl-rzN#)cu z_PwJSlR6mKS1A$Z9Zqy@b5A4Gpo6>kltIA+o=Z}kF;cTJP^t^KYOvy)-{fY(?fq%; z(n5uVXixNMf@K$=-RAGDmqRvakJ`*hxoxfMQwkWI1aB26_1!N|}2y@~v+8rn8H)9T3#l6D(+ z>|2yFd}8Nz;AdU87W@PGT^4mI{Q#+VMuKq|&xf?zuhZl0&|~hDv~?hC`M>* zyn{yY&eQq_GCg85S{F1b>FD%yFTx$L4`12pmctjD>k_uPP{AQIgLN&< zOz-nynD{Vlg+DV>%?y(LF}!0b9Ney5(Hp?++8W5Rd2OEAcdWw>@U@hc(ad|TX*gTu zR=JddG)}CL3-$X%5{%WjQc_3Q!Y3vfEXdKXqnTy$PvKV5dVx>kG%EA>@7C`|+@Bh8 z^KoxjOb90uY4^T`pmK_EKslC1r=VjLxwJUgpt!zRxNWSA%FVY1S9(dLI#!yQrc`tG zTLPminnhNRY%J^$gj=K4664;M`cn$}(ik2&65e65sLj#nSzDd(NuUm(0se)#;Ts-Ogf5+Zz}Ayj6PtC|m`t z)2yK7NGR2$3tcv3u>osusHyTZqDqw9DEU~`wvKM9TXo+felHl&Og9{_ExzDmU?Yc* zOS4>T> z5wf|2D1ImKXuDhHzkvE%eCpdPSI%Gd!>+%*xgqE`J##;o-rdctvc>D5LKj!%c}co#d4(ksoqjM|5i zIO$QXZ2m^fBU-d_+RIJ;Ay-msE4@4{(B($Selem7>t?2?dVO!6%Val3rmp2x1eSb= z4WcG2YzMdFX*a+oDSyKoc3 zy<6TB;oF`nGrTZ@16-=COFK}wh_o}?#Tb?(zYD-I*ut<|FW4_kSy)9_8&-9rA&xWB z&9;FWL9=^5&nK2Ustj0Q=yfeO0mAoBvj1+R`3om7u}#M{+=`B64+Z~zbDzO!9sCCy zP)c(lihFS+2b1BLC_-&Txg+;Cgzz46UnY&2hD==fOyTM)uE|OyM$Vl%Ud_g(5dSOn z+X{C+s)OZ?HVrAJn>Y}m&JI1{w)nB_Nca-p3#0BX!yJjwo3MtH?jkH{MDq%@$Q&VS zQJb)ibIOKOuM_q%JhNS0ho72wTYFGDoQI`LPv@}XCA?Nwz^mbA4=TcrcQMxwMka@Y zc??;MWs4rv58N0(oqwfB^(h)ADTEi7EG#^oqo1^zC{Yc-I+Xl@{xzAHBIPn4?O{_zgL4AS~cr zb7}CnRyTBC_^a!bL5g>qE3G@$s*Rt?)Q#ogi>-M0iUb?6h1g)G{np zPArJGGldQ=qmY!@bD07W7E;X5!&=!WASBd<)xLkXf9uQ zvd!5ocWjvhR%o0R`bO*9tHHgz7lSLhNL*U;s_=fA4H)C!zW~9le}SM`DsIgW%vbUC z&q$^u20PG9#>v?>{{sn{_KUk+WFb~5Q&=)(a;0LQ3n4h>g=2C+_mJAWP$AC8&yHrA zyoxX3xfBn%Kt~Nw2^B9sqgb6w&W$m9XN@E5(BlxvDwn^OB%>;G5w~guuR2CtHeqqg zdBYL&;he$4G-ekHd&|Y6dxy4|mwk_&j_mwSG!PBN3_akg#rZ>Amjb1#<$l*D6|ugI zD$Tc6j@26%^__`jS~H7NqZ(${5MA4mgf}AA0osZfA;}gw>v`y{U6U;)8un@$urf0x zxDO|-2o%+7t9~LLQ3*p0NXjYm1C5f`wrQ6ExAOMO7)*Zt$4)y?A{J?m5mZ8Tu-&cJ zi9KsKZHi6?&NG8n`B`=7g;(448t?Usan-EO&&C0%LSFPe64{UP@nZMwcK+GzmnT3>%#)l44ioshR256U_M<_qC~? zGOdz7dvXQJ>EMN#_)!%E+HUdp`Z|v zo?Ex-syVl}9gVlS4|MG`@-&`qY*1upsMAB6tEJ`PGxgyvQ3blu`>c2#Pi3k8SlbBv zYzeKh{_Wa;I)G3c5UB)B>z;)p>HvE{%k~$Xo&-X+&(GC|9HoXdL`nMF;BsvveqKU_ zBPYN*ZBb1OTd>^fmL!69N4lG1E)seta*x5n_pBXxaMauRPOG<8xT1>WjpCO#%2j%Q zul_PpCSV4wbYumI@lc=ro4fuV`dOO_H|4mVS z;1;*2QVE`&_Wpdl*MGMBI=tyRSgUpwl=&X3G!~5Bj1KCNU#YvXl~S-=t@d{xQhG6q z8?OKN*`?n{jIAh^r7fz_Cau8deUFfrgju6ma8+Mcot$f6T;Ha}d(=E9+%BGN(!wN; z^iJ50d5WKY^jWj8M=^8)RiCn`s~1_tvOf@vy3$|1anqjIaT?mTCQ&}V zA4Z>$v$E?_ks}o)cp6E0MHZkAVDh}&b+9~JhJk;OPxcE?-KlQ6Gf=0jqBJ8B*9iv7 zr*n+tPy)X1od^c9^_6vouMz(P69wRypE@r%&tA z(g@6v(&`}BE5*-Z_MBQqCDz}S^0(^jvrglVGD)jEFZGHR6U~+2UGlQ*jS2&sZk7=+ zlz30tij5iR^d z)Cb2on8U-uTtx6e018 z=9(o1ll5h@q88-|tK^#w3{C%SQDXOTZXE)L=zW1SSBtZPmI9GJt`TcC0b)qJ>9Py5 z4|BmiTyc${(`h(vtU}A`RensLu!F6RuTL(eO47$^WiUGuey*Vf5l0_o64UrgCYjTy zg8U9wuiFBt5f*p zvCvcTU=paI7&*zv6ApFX0d?-P$W1D!NoE`34?wAwj@-N2fyu*Dd@rymnX55Od8-FrK4$OMO^x9;nq~N5UyC<8)BZRX4q5r}Wr;cn#3j0)tC^s^`d#dT1lEYhp-r=g{E6!S`t)atN}h zNy`mPHil2)f-!S^Er1(^!ZZlQ2JIQgUliS~f&U6WkTrL^0orX`<~aKXX^!z6@q`oS z$|Ace-oKs?*1=rd6*15+|8&xwEuw1&#zm6hic4)UWddjoU53#YnTz}R!DCTxC-%Fj zNyd%h>O(2ADaZgG%Dmxacf+%B@sFl_z`5K7w1l#F_=Pj|_u%*}qT21+FWXn~Fm0SqWwdt=VH7GtWU9cyVY(?XA z-_a(A_f2s#OMBI&-_vJ|Q6!b`M5MEs4dBMm3+?UqC`%77z?ZrZZmX_c(bT#6g zQJ33w^KwD^DmM%d_F*S4^VNvmyYnh&D=~wS*CT4N;Y>r%g_|zb#^`ot*=3H-pJ(D^|-28KBTz67!Csz?V_5TvYIda`;pYPaI z1QO?X3oOWV8vrKAoi>CWI%Sg&kYE~r(`ey=q6t|+B1IfL8P!(g_E6OJNWRQLHGg|J3MZ_q6LiPdh-$8X;yvu+!#xG0^r5gBX z)p^-SXmby=1I4&3Gc>fVKGjSK&aD%Bwldj?AuAupp$UfcxPqNgS`GHE(Ub$)VG9uK z>NxYMU$VWAOoKKU%PdY#9L^nVV(EKg$lG) zNvaZdnOMfg+t5ExswGd5ESNhoPZs|%BRS#mTR=}DE5by$mU6UMhl!pF;YKB6WX_O> zE-5ImON|&IwZea1{Xvkpby}G$Zk1yK#evL4kG80fDd>gGNFR*K!ZPi{bEFR>exQP3 zw2W=4_XO&`{}4LeKC1rHJUlTl7C9!U%l<4Jz$XXYtfV8nZ~5XhUOh5% zF@4ks*x6}GMVJMj) z(#O#mD`DSOWfw7BlQ>IhiT{`LXAE@Rmh_Xvk(JtA+6|@3^3WHaD zaK`uW&*UWxr`kAeeu%?HvVoks+R3V4ZAKeY(%Zlcd2QoXE33T`-9(g#`i5QYaz z*2pTpg#_iGPwZpHA)qWh_dTfGZf**|y1lF8xE*pPcFB`=-AonA|I|CO z!3+kdWCvvkpwslARN)kYQA#5&a`W{L{5B(cwi8b&!Y`8%%dIx|1MuEG<->7T8dmSv z^b{9_E!?VpJw4AMJ!n1P{Vc@L{lHbaybQzpfRLZ!CZ--&f{?>NP0ff-&Id$WY;z

PkUefmUJ4vYc-QIqggXf3u!t{E}3g;?#eXHWF|;cnVAcgq>&5mXb99; zI*q0UuDCGeHfl+!xqwP$NvOyOxr_S}BDjNs!1?q&=a=)t`3p{5UKf{K7x40ap3nV0 z@BQ4*{RZSM0x~i+HMP_X6?RWg5Vs6k)#9U_F*koD;7{p^zHYZ9vJUhfrq0+(z@QaY zo}ZKGsnsSaO`(>r=G!6F_FGX=r$}V8B1C+w@zUTC`EigOg|f zB3#z22V2`(A z6Fxmnxq@l1)5uc1{prA?u*?3S=lxF3>UUk+KhXiQQBRW#TJjC(3z+f7<6ib%+yNvBx#y3x$u$|QF3#4@lUd7_NR8-v@pBvyMm2BuGudDU6qFl{)yb()N2phO-_21 z@ym-#4#d>_`Qw*KnHYZa$bVbQdpq3tM(f7=7-~Dgk~wP)q=Nx+?_3PIsRd zT)Z;(YLr?#?0V;ZpswQO&ubU6+W)*@B{sd{iOqb}==-dC_rSYBN{hZOzSQG=@0}jd zEmGSJQx~XliN^tPY4>+P!RZH}_ZoEgK}$tN#hEeNYu_N^;^JPTpG-q}ApFbY`h0Vz zwT#-)!(&-PyhLrr~byw;9iO~tJm?n7yP}|4uTNBQszHd?y zP(K@Ed?puzH=j&aR2UdAw&TOIGI5>v!cvu(kB1()1k* zb!hEXA9GZJxQ=VwJIx!tWYb`khYhv+6_w`L5F=tTD=zc+Cp$zZj9M~>gzg&rTaGIw z+#|lK&mW&~!$5*HfL0i$X2(I?Sz{O7!6%k9aNqDL_U4(Ig53vw zPo1>?Yb9_ApVbzgs`=uI8(U)gKQSAS_j&H-$#`5iT`ty3vKX|69ypNx_Q4Og8Y=P- ztcvLtQ1$svkqi)tLQh3b-PT=sDi*p8Vc-`ZAw9jos)NN zh-Nxed&*n(JZ+;lswkNNi6K_RPV4TtdWYFKf6(~=kaFicIvo6S=ft;wrjLEW)6=LF z@I;uKN;U8K+{ynw>)s<3#QQ9RzK_3I)?VLN(NS7WXvrv99L~Nie`xRU>_dQVr#OY+ z>~rAYr$b$F=MDQvwL}j>nR?M3Kg_j@l&;O61As!|O|CQZCgm{Krl_OoT$Z-o;~IJE zir-GpETAS;5nB<|e?%IhWv1QZXa`hUt}Wh_oH23~!D9PsJ7VrSnR*^@)%6PTa0ktY zTtE`?)QGPS?zypENgsRhtoIYWx4E3LQlVm({dr69PbQ(Va`n%H%|8J~7yuTXRjF|} z4{|E4SBz$+A!9#U9@?LU%?G#zEBmFPD(uG;5zsr~CUz!uLo}vFYf%4yo^N7SqcsxH zuNBV`3(pbPrz3vn%#OtsX3ClEa5s{!gF{f1j&od#9N_HGdwv}q)M1!1yNxRPwH>DF z99FtdKi26AN&yz-7NkzA{>Cf?E_daTBfJ=gwljA08Rh5|R;1~F<>d(o5@OrXB{Cts zpmKe8&PeYa)93duUWBGAp3K_0>{Vgx`%J~9&Fx4OMU(T_dK~{JuLE@b=Z#XUcDA3d z^q_?{+P$?qTbX%!Vj&Eg@X4L&-YWdm_-eFis^r?agiT{3wU*18uKWgeEhhQGrPe^dq~p*Us)(o*}||jNx0I@3Zb+f4_%9TUb{kJk(4+ z9iTpN1Jq6H8OS3l|%GmNWVM9U1X9q}Zd1U|k5LcTvl)*u4p0{vtv; zTEfrZTex%hK=bX%n}Rc{5qjsqh`!kAv&#^o- zTQBIhmhkF^Q)vG-%?~5kFOm{QI#!Pd5X#86ge4%x8Pl74?gj!~BrhRnk147fr~dYhX!{y~R@?6$ds?LZY*>(65VsYO*%|-c zL_nPv%BS-M-s)CZRcy=NdFSqXyO6Dbd^nG_+4>%o7W3SLW&*HF-r8S#?e3*GtUYiG z7H*zBnx)4h%!x9}hgLrjdLyc?M-oDOp_xZ-Ez)&MBW z)&MwjwIt#{cRgHA#c=s=9hzwJ4DPa9ZJ0aB$K`R%T#C%HY;Ejg1q>TmsR_ev z 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 diff --git a/resources/ios/icon/icon-40.png b/resources/ios/icon/icon-40.png index d257a53035e66fb907617cd1bb2389164a5bc6df..125cfc7259f92b700f55b46494d1007e2b32f7a1 100644 GIT binary patch delta 1184 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 Qc>n+a07*qoM6N<$f^r2FumAu6 literal 2746 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 diff --git a/resources/ios/icon/icon-50@2x.png b/resources/ios/icon/icon-50@2x.png index 503c6de51f7c07cff66c1173727c2a939c9729a9..9b85f2603e8ac5182d84ccccae873248c2de0664 100644 GIT binary patch literal 2575 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 HaftdK^G+^V diff --git a/resources/ios/icon/icon-60.png b/resources/ios/icon/icon-60.png index f3ae32330986d2d29993a5ebfe39b70e6b2e8fd6..bfa0ecdba7165538ede6b5de241689e893d167b3 100644 GIT binary patch delta 1638 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 diff --git a/resources/ios/icon/icon-60@2x.png b/resources/ios/icon/icon-60@2x.png index 83acef98503f9e790e124ed29c77e31c385d91a9..9ec2a7f12ce03e407a0a5f8cc6d5b4e29478b2d7 100644 GIT binary patch literal 3100 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 b@b-@#eg1bsN6H!!Ab_+r^bsZM4+H-LY2AmK diff --git a/resources/ios/icon/icon-60@3x.png b/resources/ios/icon/icon-60@3x.png index 2be3dc5ca0c93e4983ca971307f15f479d2ece99..781bfc7eb1c41e27c5620c64adf1408658cc814b 100644 GIT binary patch literal 4683 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 VOBWO(i9-N@)_py-4=Prn{{@(Izi|Kn literal 5762 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 F|1Wuf7@+_F diff --git a/resources/ios/icon/icon-72.png b/resources/ios/icon/icon-72.png index 8ebd26a51d441afdb752d0202f0e5d68a3ce5150..948cb58bfd283f7c6161d91666ab6309791c33be 100644 GIT binary patch delta 1967 zcmV;g2T=I-6S5DG8Gix*007#LBoF`q010qNS#tmY4c7nw4c7reD4Tcy00(4AL_t(& z-tC(GPg7SI$NdBL3KN~mD5Fq;mQq^YnJhs-AO?RRMzRdaMrI6T3!8$ph4Lb>QD6)4 zh2f6^l5MDCgqSS|4yTE6nUWaC58w-m4$A9&>FwL?o_h~m+JAECC=&yCpPV$Om!96| zbH2}c?sHDrV&9~;MF11P1TXA!Jxk(!5kT04#rx6 zISRsyfnnI=$B*ybyLa>E&Gz>8*49>w#j<|EfCs@q$5F2F@$tdI!7Ep;G&eV^)#}{b z+ye&=Bqb$9M}J3)M55&6UAmN?pP!hRh<`KzHIzza)Pez|k~Qc6p;#Sl#q}R7#IjV5MUe(04SXet^hE6tgf!kXf&d4 zSnCMxARslv!#(1p%3smIi5%djL4!z-G5unOCOKY_lz1 zw9_vh%zqKdU@$;tOG!xq9-u-i#|@7oSXfwCW@hHYhYyif4v#sjeI>df&KCv7Aq}4Hr_0E zI?0y(cvT>#-U4Maplc*+(XsS1#)%J+-y*XpbkZtVCBD}Ec(Y{$#}@X7$Z;(fOHvyJV; z#eZ@UUb+)I;f?G3sBSGy&7oOe4H!y&w{G1!eE9Iuqeu06eN|NzJfI*478Vv36coTk zC?G%qkJzGA%Fu9CW};$3<7X_}fz?N1APd3(@7*_mEEi!#Z;@xyY%@>hrCWt!C=T!5 zy*n~8;!+tPY)AtTmH@Uiv-7Vpl`>4BjDL+({#7Wpo%Ea2`GXF4IWLm`GS@|Tr5HQ$ zHgR^Jox(iQZHtTdP{nb1j7ucwPXHVf6SG3Fs7RGe0V_<|pC4$i_M6o$FN^LyT)Ts5 zWxhxy9=4)4sP0maL~{k}@9%&5^eL}mqobpH_UwTo&^;JiZ772zyZ5RjH@{%0=6`g{ z#v7A7D#gTi9}rE6_{rVOTb&<|o&aAdwi!lN&gKf%*Vi{THs%60|CpYh-j|jx4-12Q z0Ac0`r($`)K+oXdV3|}}tkFGf|I()OnOiQ#%9+SCb*pVtkL(r-8a?;ibhUv=Dok@8 zRT+St@G}2?igncT!W2V&vx&;d&VQ1G$RzTp5IIDeO0H1KBb4wel}AWeg9iiDkT-7p zfd-PTxj2YW5wIk>0wAqZUBynzhLys6tqD`V<2r-^^ES49vU$U>*jkC}6 zi9LCu`a{x!G+EY%;VJQoq_~LmxUi4X6SNu)>>yF%VMbV)d9@A?;AOk`Lx1a}#5O*J zKI#ZY6O%+kG+w&HDRaWw)4ofbPNvPq6{Zl^ta$tO?FSDYECFM0IXgz_6?ElJd$owF z5J5heEcG|&#FNLp$e%u+ua&sE8SQQ;jHruX*_^zDq)(nnMPKb)_%PpKY|X_=oJFf( z;+ANLqRAPL!BCOf*4EbB+keZwbpo4yLVPWsEA>UohNuvc6(VC*08!#){loOtw?H12FYg&x*)Bq_q=_ky!B8k3IdY__smTo( zo0R=sqTkf3cU{)=U{nUdnD}JzP&%n6n6@To7j~zS4alQz! zbbhdQH(-#887z2OLr{} zDfPvUzqzm<;{)r~V8>r)LW3Kk-HEGxh}y8#?8n^yn#As)pMR03_7Zv((HH}}_xaWs zhu4If2hMPAIfL1Z=RXFWiN;umA+dO^jJos{kBcm)>1sV*D|H&u5Cd^Yp4qpW9rQ|c zqsb|nm~?Hpfi{uL4l>Ttgn?%WI=JRF6Ty67>5U)`S8-taYX1Yv{O@#28}0cJo4d&O zbcmps049J5U?>8Z049J5U;>x`CV&ZG0@ymSe*qLs!S~`@A)o*N002ovPDHLkV1nk| Brw;%C literal 2550 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 SUs^y!Xa>5*I#mzBG5-M^oC5~{ literal 4719 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 Ps02V?$4I*hiH-Ru9wgtT diff --git a/resources/ios/icon/icon-76@2x.png b/resources/ios/icon/icon-76@2x.png index d50c660f45547c756fe21df07763d63ca52129c1..b52383bf94b7986d1ffc6590fa673255a64fd37b 100644 GIT binary patch literal 3943 zcmZvfXHXMdw}nIRRf_b4E}cN6gF!kHkSbE7_a?m?LQy10klsN`KoCNt2ndLj(2R;m zkuH%cMVeBS8{fHi?!5DTKh8RH=IrxlpP9ASlWGRjXQ1V#1pojHh6Xz3#6AD-q9!G3 zUU_>saia+`uni&JPybyc+Tul#000x0p$^nC^2<(1l%<6YKkj!g+H=VIe!n&;IUB2> zFy+StACer`l8NT86P+z3;Lm*=w`~V8wRf0~fx~z5ZtJtD30ZqvH7NL0-|^iBThZYt z#vZlFcrA;_%AQ{=c3rKk7L^qR^)3CJKctuMQ0gfAIk#&!fkSMuZzWp5fw!qylm80z z8M83$UqR@A4A}lxP~zmN+JEIg%V?p3|6kT^>+y2d4a}9U7YPT((d55a=4vqr#Chl* zyIE9vpPlT?y?gf#BNC{C(zw>O+gw>$SzZ0@X!FzY@v&WdfV8yqyW!z{jhGXjz}NEO z-)ArwOlPNRBv&W`s4clFp?d3I>}C zvnHpckan!}KkP4qLZOxV8I+yfL%iMGRqTtdw;BGtj@VmFrsI55IyyGSDSelG{Ojtf zJJ>cdGBPMg&WX&4g@xsfl!OFrsldoJQBl1LGXTD&rG-Z+0M#g`*C~(J;2ajSHmm;s6-iz$k5v*%yP`pb2 z0qxj?bHhnr_^ptP`A#<`{5;>N%vi&h&=KH$80UUCZ~np~79n`Ly$qE)Mx?eB7|D>o z`Rm83X{oxQ>YhomK2u_gXhGZime$strQY7EDq#o&a{c;s@78b62 z+&1C%sMjb0;uWNp;fa7&kIUfW>3a|OMEQOt93~lNtPD&a@LxH)Ilq~G%_9ES3e{B@ z`qQz?V}4R^o~-h#muK5eAkP9hXVQE+WsPP1XC)yX)BZY1M^q>DK_9u(YdE_sOyMnS zW!AY)T&NCVF|nI7iGn1>V%O;H2Q#h;hEo5=4GzYge#@J(QXNwOw!>R=sUi3L{iWSX zQK9grmtU4XMdFvhIsh(KR?_CS)>ifuQTLQ@vt|N$LM?cQkdP2>Z*LbD!R+Wa8EjE9 z>z>u(YnHcfldLf7Jp})lt=Isg1`;z<6S>r*k2Wq&4?#Og%;Gsl4%=E?$F;;?Hxnf` z@?;A9aR=yc(+GMAkW~%anlyt|#Ro}hyQJN?KZN9B)79|ZorD1W^IXewYa1I&)uX_K z$ID9wKR9<-`ks)+jOWX{w)xF!`+Y3pmU$Hd!Ol{V<5m=m%5Q22?T*iSamVl`cvu)JFY8P zw+a>O>gq-`#~xY_#&f=e`FDQydwr0za)drx&FQ=;nJ;@#&?)7y#JYCqx6r4(kO}@h zBAt>(g}hYrH($Wyr)QYZZsL?~a4Tm%>#3=sl!(q(HisQaY0u?Y4Ws%b6%`eO1nw-Q z(?Pbn#0%+bw$Wj@(bhQ+uThst-9Ag0mc)6on9}hCXe-C1Gnn_V>GUkHy)eG#S5O(e z)P%B$(h$u37Tee;BO*fK0#Y_-B7k8SawWRkhi?+dq?9XHDt-##ng3X|Z}VtXTwaI1 zpa(>5T_kLgK4=A-Ik36)veSMZb2MLPsyVor`N=`?aXaf6H);~mq=9VJSP48=Hn&0H zmSPXz6$SPfa(te*Vd+c~@e>lNX24}M$AG%%3TH>$iwrLp!r%FDH|52yTfF@pG;KW+WMRQDUp>who7pV@~I&^z1%%N=4&KvYe!UATHjlK zu4d^s6|T;m62n#nsjRE5g~#!VBiM(Ah9uZ%gW)dO=`Qgi6lnhDFPYd4=S(bC*?Cn&Z7`hLt20opiFUNv0`*EKN}fyTv4ioZSCU+H&> zjgOB_r4wcz=&VrKhKFPd6>K2MQz&`OmfSt&r1z z{I<8ZLEE$4F%e;5$7}D3reD~#d%P>Os<&q6oz*jdp5ZFhJPloERr1GOo~Z=x@KUf^OIemAfmd`2RX=Rf!C zeif@5VCQ${+7lh3{ZawNKA1b!2k4-&132D2gZr`ha(q$-CCnYDWAZySz0`RY7qG+R z9K85C3e(E(qWH&1lqZud)w0;Bj-QN6D1$LAyITqR>>B;#8{ufyxj-y^`2iJ|EZ+}! zMr&5<+qUlRxIP30fdK8{=m_7rOP$LPs9R<&-dq(7T&HPCPRJ;9Wx}!{jHj~)^VI^w ztO_)!56+Ia3knL_&~wU+SS)sejDvKa?lYh#)@$ll43W{j#yJU4{$n0x+sa1~$oAlu9TWO_VIA(Q~#rY|9~`{&;0l*?UtM@^5{Uz{#}l>Zr^+(CP9vG>>6YvC0s zUZ-Q7YCA1mb}t6wGAFu$rp3xE{QSFbkNJX2i**EPSZO`+g&}`79LLTgCC&$7;UhcV z^WMxlV3Ti>HyXQIubyyaZK=LozbVl!ur5JyalXf-_V64z0YbrESsyHp<;e~Xi4Tk1 zmVpv28N>j)b!)yO1pUN$Cc%rn`&#noVTdNO4~Ck%cb-w;k%Co0I7iP6lQ8Ud@xc5D zxjqyZ-P?4x{Pj!`p)z_;e>w*GX5of(jjH8?XT_tkwef4BU`@jCGZ_^_Mx>gGN>qOe zgIgFv`fj>ktB>`96O%Z{FSVDC?uwV^GS)~A6?o^A*m|U5`!^Xmuq@emOyX?jr(~O9 zsT`Gflf};tw)F+Z?=5>gJ+cEPR92g+gWXgkX$oFa<}IF>SJ- zw6y=;Vs}YNNk)mxfb=apdURW2AOUN#Sk0fK^d=}?KxNYJ@`!Q9Yqu}{rZnGM&t4Xc zzI>W)&6A>lQHPyGKYxFR*OBs3 zq*7yK$U%}FV+u}i;*)W@S$QQtldXM3_M>q_mdz0tFRrYCs3zTpQtqCe7bw@(qNa<` zl8+M${9kv&rFM2HP{;d+k<6Xgv#qVPs3e&HDoAUImF)3)-a%-=pY;IRtP^N>XlNB0 ztzhrb$3sY#9WJE{Q_ZHz2BWMgwPyny@xhS1P{@_(SGa_=x1d~bYI2XOMvMPtit_v& z;y}VK<^*nx9I2@%Nnx(=8bXf0dm@_88hO6|skBEyeV`>6pQLdQZz2hho| zJbWoz+iLg5b8uf~SHn!KSwMp%u7g)+IPhJynOA32>AiWuPgwcnr6BvS&$XKCa5oUX z$#-<0v1NojNX;o;9F)z)h)&$=@)zx$eDbQRXgz9r69tzN80vzFmBy~CT}(*g(}^Q* zlLS>&s}=hT)7z`nQ9zSH$528(awgz09iwiNL>9G98zah&xvv;{pH4<{(4*!P4QjlU zzKq21ds9nG=!bGB(P&EnwgZhAxy#~p`r#Abt#AEN@T7g~2KHom$d4qLY z)ptrN;l}=M*PbeiP#g*2kt5?%xh`VtB?id~iHCBAG7-VqB8Vea)IgJCcQk~UawT#k z*d80_$^00OV6L)BP=D}8y+aXzd}^<^<(034a-=x;KKqiGk({0gX4bZ~aX`-`hC|lz zse>*6E^h9-R#wTtICi&?5Cs)M0S*ppOUoo+9K_e(*EcOKjVu=cR#3P?d}5H|ENWVM zD?m6WH#fz$-c*K~*<-KwGF}<260+vRx0wY@l~w;>}+y975^`ah$+zCV_-K`739_$fj~}4Nov3^77H|JA~n3V z|Dochc7+@Sl7oZTQcu+1xVtCJEFe(!^eLk8vkmlaIx&;Mij^~QhMCk8b2pyKMQu&i zU9+eu*FBRn5KM8lZnXE-_{al26E@IptgA~c7KVpaG1*lm@0FyoR81oY5uPsOo2|f8 z8X^D@!H9(jOei8g{p)aceORFk`}0DAMdc`q`+K0=quSRbTZtA1aO?2S=auKk1$xu2P*6rstUBa=`=6dBx-gwaZ70P40J?or A1^@s6 literal 4898 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 WJn~EmS-#sG;OS}^Y9iDfBL4@M%QvY2 literal 5388 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 RMNR+!002ovPDHLkV1k>noX!9M delta 1166 zcmZ{iT~HHe6oxUz*qr* zhbb8+0XTvH@KOPQqz-_P!f&nH-vMADU!I;NqiLF=D3T<-Ua!aFnVOpF?d@%CZFM*t zr%#{OXf$bQX;D#zD5+H1*49RmUYa6ZE>}xSi&m>mOiYZ7jEsng;PH6H#roNq*Gw~- z^kNux@#4kQ)Km_K!{u`Me7;yLj*E*E2n3s@(r06%US=koA}9LEzxv4O(T0WwnM}rF zv7)1+g+d{UqH%Fz?z-4*DVxZVwmFl|08enBaWyoog5_&n=cGhmz0>KmTCF1^BV%J@ zn>KC`3J|df6^n#Xyx42Xwcg{wm_7&|55tR=;zyUj(zjsAN?f^T8wR!6{i$0kyUx}BoVHs=wR=KD1Yv-AGXk<|{M2=;Ke~i-;b~&5-$!W2F#Qj!w>mTv(I5^7+&b(>G>WW3&!<9c%d)xuz@BC?~s)k zY$o~}$*DhS!c9DB2`2`x6a7u}ypwo*gZZg+OWu7(7VJ$+Nw!`xrwrGyLJSM`r6wn3 zaUT{aR`?ogYEA{5T#Ta2)@LjU4bBmN8l0XGe&~(9R zyVkB}b=%%tUi0OaNo94-`ExtuS(!Q8vkT-yj|T0vYU2Toz5nsRj!Xd}6bi(A5qi^f z{aQdqY6X>Sh>O7k~H*0t1t}dH=PvFj-oi_Qw?DzBD zIIsFy!`1H7e)!RTp7y|5_)?*mbo^95UDe})F1`@vrPtlFApu-NVyLXPCch`kI*{zFFVy0Wrx3_M?RnDRHKJCBEf3PTcp(;eh+q3YTpGRA784?)$t0rQwoW{M1 zM|)b;(-QyW+@`0dj|bHLRlK~L2VW}{znKsJ;fgQHUFvJM+cQ@TD*OPQru>H9bt#q! zAwnn1)hY9J1u99wC#rm%3J4H>0uK@K1ZWl?m54+VG(HAFBna~M%@FH<2$kgEA?1Fx W{{I2(%O-yY0I9O~k{hLp@_ztyFilSY diff --git a/resources/ios/icon/icon-small@2x.png b/resources/ios/icon/icon-small@2x.png index 1ab363c66daacc5793f546267e2fdec3e46e31d5..a0b6b043824953ec76175e3cf69b11105999a6f0 100644 GIT binary patch delta 1606 zcmV-M2D$m25X=ma8Gix*003^;-G2Z8010qNS#tmY4c7nw4c7reD4Tcy00sU@L_t(& z-tCxwOj}hH$NAI5Xf$rX28tdi3kiql8t2Q#0;hx8^p~qi8>f8jMDdBOZ$G*^MBqguRmzEk~Iq~@8;&_ zwmp6KbHDeTbMJit_Dxj+1Y7}Ez!h)>Tme_W^Ju&k<%hIdt&@|J9UUEIWo21eS&4~>GMP*)7DF2y9i5Pn zpin5}a(P`{9YGNOWxWqB%@x!qNpfOhqPe*_CnqO9K3*b`NTt%axH#YgVvRKI^! z2b$)sXB*5d>9|3HYs2wl+wfz-c=dK%w-c|EnLo<1o;^X|o#Do&eUv$X2Q`^YEiEme zVn|2`L=1#E1Uqz~1?7Ap)~Ho-rDp$b5z+N3J%9ZZ{$2`p2pi z(A?Y{%CZo4&_ds6G(NxYIb~dI;u>6(aoKb$lkbib4;4viVAZZsu_g?w3L+Ym#Jjuk z<5AerKo?wYGBu$@QzC;e-dAHc+=~}4x_@r*sD+g2ZHzhTTqyUjuyZ&?Smte?0#i9QnMKwj=y&2B#9!lvN5 zbE3|N6$i33k|a%{T%nes8mCeyl$n{C<>loUE?i)^F)V!*SmhSHHquSGqg%=GQ-97Z z%RO-A>(^LIeWVlj?PpfbRIU$qe0<#P0EINBtjxz5OO?S_%f%zFOTIfKJzXgM;jm=l zmtiAjcFc;Vz?AxX)Lk|ayUGnp23v6YCCxe*dG!ol6~s=)1=sTB%j?61v(?R;H$8Ce zRswrJ4f2uAAOhE9j_+jjp-f|poqu3mjpDo1A(vf%HXbW&%bT|YK4-Y$cQKYVwAU;ULs%~O{5>{JV8_EoxxRC7~YsW2dqlyh34}aX{S)$G> zF1-f#njdiCKn>?q@9I7fuD{hq&XAj%3z5#d^2WtQa@N+?78e(98r;Fb!IF{^&qTIi zodJ?ATwh(UX&V%mFL1Mk%j2fOb-N4-xPHd#q3p35&QNeF(1jDt8t=IE zF^+2jSQ^Aw^>*`TFL=jgAAhx44HB4`r8D0zxH!?AWP0lf>}U{HxfKqk*b(-Q#h8~o zagG0hv~aekaJc~(as^JJmM;!izB*{> zDztPRgp=M%Pbo~p1(&lV7FSLeSx&venH943o%z-u4esi4md0BS8$XBTA#+Eb_1puATDBKP{xEQOc-0+c1poj507*qoM6N<$ Ef{Qr{Y5)KL delta 2054 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 KMNUMnLSTY#$zqrQ literal 2976 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 aXg5#yu>UTgS1FgH3IHPmgnk3eDehl_+kqqi diff --git a/resources/ios/icon/icon.png b/resources/ios/icon/icon.png index 9c018d1042405a7eadc25c69e40ac296bc894a03..23aec95340a23ceba7add60fdae144517f25fffa 100644 GIT binary patch delta 1557 zcmV+w2I~2a5St8;8Gix*000Ae6w&|y010qNS#tmY4c7nw4c7reD4Tcy00qrSL_t(& z-tC$HPZMVx$N2;HRTQa;7J*W}_1j&6k?4RBGXpGvpTLpDM7EG^$>PG7mTv{>G_V>B zia|(NBE(o&#^MjKfs+^(VPu0}7L%$hGZ_n31lzmo*XwnkyMO2UjWQ|2xw_paPnsup zPw!sO``(}DK2ICteR(2AU=>&eR)JMu6-P7uVu#)|X<%5d)W_4WJr?@v!p4-XG_cXyvYeY&!;a$;fvoqeIK9xj5RS5{W; z+_^I_Fi=}tTYpheQBY7Im&-FUGSbu2MIupdZtm376dL)0bOk;zpWk^TNzTsB_Vo0e zJb5xdKOe}XQfX>xDs&VIMGhC4nVEHUb*rnZ&r#7WSdkr(D$rS2Sm^BREGa3;%F0Sf zNdfVcN@cb$)Lf*crC}I0G&JOQ%Rio5=r)KFTxc{JtADDh;Q2}<5nKOg1`9LE72 z47#|uh~~yC3K#Lvo44(IBPLu=+<|iU1T0w;_O4CXvj)qy2(&$JTC3t9xKw)X0r_r4jwsjL@X9VioorG6_N&vC~!57 zB_>O*^%+=ot!Bs9pHX@V(eNgr*+pn$3GHsK#S#r~kd1MaLGAqR3`3Z?+j}BqMW7EK zKCG^;202ShOUug2ii?ZE07XSbkna(ZVweiY^M7*ipK9Wmkq^kr$BDXEN!=l`BWaV? zewb|C57VH!FnVc*!=<<30WiSi7(V~qbnnh%L64@r^TgPFZvMn*_U^3Ah4;}iqxk%KF@q@xAtesDvKJ_b&4&J?c z_kY=}a8{vINo81nRT^{VkQE?6wg3}!4iTDIqW*RE={N7=&IWMFroFa{gRpDTv}Sn%X#2dfq0R;y2)eiQS7Me5(CDU4Q*c?9u$fq;y4wLY=9=WlFYX$W^fN!y5~x z(bC-X`-<7S@TIjeo(P5jP}Z->^n=@e4#w@U54}$`#szRYk|5{o#+of{5W+e)Hy6x` zo`}c#=*I@^a)sp664{ADd3nCF{HUzzom{O}Gcq!=yu8eREa-y({oISAfrpD8m&-<7EFjtB<7NfiE&C`#HgDMo zSq%n*$zLsM^AovebhiFf-YGduAABUs@ zVx8_LG-0gwZiTR-mrj4_PFW$Hp;CSZD|cx~g(zSxEG)cm;X<^tB7;H&-GA2B7O0V< zlNAXG`y`D<6YZ=BZhn4Vuh&OAEAjwrr^d&}gLVi}oLdl3UIliZQO+8&MHX0}8*3=| zOUL@igi0Z@_`=LHp91_^7u^# zANH&7QJ+iUQv9%UnYP{{#%aM;dA3jY@ftOBdRDzFNy0;|9ph(7^=v8-A0qYj+R00000NkvXX Hu0mjfd`Rw2 delta 2040 zcmV0Dy!50Qvv`0D$NK0Cg|`0P0`>06Lfe02gqax=}m;000JJOGiWi z000000Qp0^e~}>&E5?7Zyjd(L5Tef@}?HcsP4SpG}4WZ!$v{eI{B&Ue0Zf37gb7&=^XyA9|zO1R*ymmqk3RQh!CRT-cqSVp->>1Otu|T zyi9>^j6rJ+fMr=tLFVS>n4O*F@#Dv&(`ho94AaxoWV2aLpFYjV$Ou~NwtBW@$o0Uq z*38VzaR2^&f9~D8$K>QBj~+cDlgY5Ow1n^b04yvlFf=s8`Sa&Fbm$Pqn5LfI7RV4z zN?9>#adDA}i3x7ryvfAG1P>lOAfL~pl)|=c9LK@7Z6c8fzVEZVyv&IcCpdHF4AE$` z?H=4PWH`@4uX&!wojZ59cI_IsZ{H@JPNS5{*T- zJ64H=SCxF$g$&_B&pmtgjPdbtu3Wjo)YKGOYhtk&uInO%s0-st=R)B7K1YupWo&GW z!NEb4Qq^cMI`|-K zW9>bPi1$^!D_Rfn3S>Bcve_&bE?nU9<;yHAe=N}3+ly`6Xsr=KG+ioQK#C<`b}q|L z$3Eh{Q)lq!AK?G)Q`F=iG2T+(Iw`9z48vGj$ex|pL*K+1`52LS1B?REnhnAjW5V#9 znwsL=xpU0T&C%cAkJg&i)m2KR5=tpbr4oyai!D7s3h=Zb6%kx``@ih_%GW78ox-I5 ze~w6euI!m+ks*TreGgN}BX{g2^8U}U_6-LE(1J(n!c{57jT<*Oa^wi{cpPI4N+~?g zBN~lz_wHRzo;*n;64_A5N-0px=Xmp)XUo&z-VA~c&&!^xEe)7%|@4o4g zFKDb-57=%MP)INev-}xmc@DFjfzoOXfB{tk_cic~c;i0`ey302qCL$X?9_Wy}Lipr(c?3bZ3FQXJZKyWXfs-@QV;h;2iz{`T7?yf2%pv zy{qWie?lZt^{DnC+Jl<;8~(j(*hAj{9n9O7A=i2&2)h8fuP=oijWeHL#kMUh46+}-je*scT*|?~eJAd8r_{H#N{N;&eGVk(qRkGqsgz-rx zlk7WkoWq9?F+4m>DwV}Ge19H9=J8L&KOoof_zbu_avSUI@j7ziloqh@>L~?h2bt@sj`u^#u$WU zLo8Jd-`ePL?I-PC~NwDr&!e9wg_h=y`66)Zp z7n_AuO>J6#a}D&?Y{BZDP%IY7<#No<&aMl&uG+gL+&W0tb#WYrOeVA4c5OXmSV&uz z#lXM-xm<3$?b>=spt5WnJa~|mm6h$bt38lmvKSp5EthYdqslrNe^xaI4jia>@>QOV zwo?rhi9~{tkr9BlpQ3FWq!0qvb+_BD_CkiBout-Z69`ei*Q*>bJ#S#jr*Yw@?)M9x zg=}cksuNm+G$z>pNkyf2lqxuY(*FAGv=60X(6(l?Y)ZwN?N?)UWt4~U3kWGI+c4u7 zF}~MENZUd5?Ex7!e={I0L?nrb?`Y#mwL!hq zTdn^WZQ}jpdSmjUu-Qzz=Z*iylg}m~H@m!Srd=EMlbxzAe`Keq3)v~^LUxL}ke#9~ zWT)tV<>f#*-Cp;>0000bbVXQnWMOn=I%9HWVRU5xGB7bYEig4LGBZ>$GdeXjIx{gV zFfckWFo-WwX#fBKC3HntbYx+4WjbwdWNBu305UK!IV~_XEiyAyF*7eT=M3IG5}MNUMnLSTa1J;|N` diff --git a/resources/ios/icon/icon@2x.png b/resources/ios/icon/icon@2x.png index e3bc95e6abfbb039f5dd8173f98f3bb0fb627d67..e18bedc519133f71d74a87bb38d5eeb2b90b6136 100644 GIT binary patch literal 2980 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 Ypt6Xm_-WMF*}wK}VqmUcuj>-~A3x%}zyJUM literal 3784 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 GUi}}ZsP{Sm literal 22258 zcmeFY`CF3d8#eA#lQo%snr3p$t;MuWE%%+8W>j)v#w9Da7ShOY2^9r_X)K$@)RNM~ zg=XAFOf45s5y&i!+{Tpz!OUDh0T%?>->2`N@IBt&-tjnmkVkyHx9hyG>pai<{x5+( zJHOfcjgF4aPO$G!K{`5{e$&y}lDKUva0it)3tV-!#GVc~t)ufYZ-?sg*TDO)5J5ga z>eLUI@PRk`uKETA03Wr~(fRF;j*bGj_1l7uPRa=#9l<3X9k-`CI);fQ?Z0{ezu0=k z&*vxL75I{$q2hp-?TNk@5IQ-^0J#HFP>T*~BE`-irJCzSai=s^rlmo$tT?`ukl! zOziqvZ~wMOr?yRmHOhD`T=alA50Mah=zqhMb`;oaaDL|3JM|(``}|~hO4PdR{F-Yh zhYZ)zIqZ#THV0bfQ=qd&=kSC7F8__ef9v4CF!&D&{zJn5knsN*B=px_2kGeK?uQpQ z_p`moBs{5oQAwC)oBKst)r@OaUU`z(WO$x5Q%C3HyOW!?opKh0jlt*}D-MMn2aC#t z-kS}^Ux3BNQAL8sO@*K^cZzM7fwln(g9NQxrX7a|1ysr&gjKV#!wZAnn=X59Kc(aS z=!t`~CN7J(Q5Rxdg|7nNO4xP&P-t=Lzg1r1yU6XYjt^9~Eg0Z8vkuW}GCf)%4hc{2 z;b8ThHL8xkarN()F~CC#LW7%XoZ|gsD5?8X#paJH0vvBr--TnN+qX@=@MM-%l!5I| z>x>usuyi)v%hcI&>1R&rZfADq^)1W1v-u_+lSa)DRwwbekuFd0Zyd)dG}M~$)hkS?PK7U=!#gAunWf3T zL+#kHgTIdA%P@;NiBq$OyVYzLN%zaJ&`?1;y=SPS3f71e^OkrtcghqN9ZwaKCzD44 z+Jt1zE%|I=?2Q{owX+OWp$Sg4(mTCPN9MTcp)zOerL5)01~_MFqREOfdCTw1pJm8< zqXz<WrXRo-F zWlYNlp!sN-sNb40nO|Gqev}o3hR3(_t0K_CmNxlZaT8*Gpl@(6HYw?3z{qi*tBff~ z1FLs*G=xya1g=qPLD>A0hKy@5F)?q!r!SBN&9{JhQ99WUa z$oSUjtl3?(&Pxw1dRfcsO0|DLz%>*KC0~2)71{xeMM+Y(k^FA-#$AsYqe0&h2N=OJ z1-|N`BZF^xc$F$lZaRl{Og zQ_@T~HDgJzeP3;?YCNTMI`QddUH#7!gVlY0xSYe)i{B$h)z4a<#c zCh@?#X;D#;KLiq+nku!azGq-5J!kAQSLTUc{r8@i14H7+cx>e{y&Xn~ zNw?UgBZ6$%=QGA_;QO`5$!$88U#q%6sxx1Hn9Nw~H`2dXR8}?sOb!{&NiF~ zqezL2J@Etd^5y-u*p=+HCXlkpRn$6DvikK$Y=F+KkOw~e?nf=n&5yHf=`Lc77`?WV zhx1@X<1krWmw~x0XqYn2duMufxo&)My%DF?+~l@9f@BC#VIf^o*_+pYBw9x_2sCR{ zFnccl^LThdzmg}TcWBk(!FiBYJ{z~}6hHaYBF?Q6{UTM&EA0VkR^+qMnpqFkw1M`| zo~=5vuuTuUM51ha3cT1hV|qvWy!%v<0cPRMmr)v7T(@&~FNSl-*0z*eE<3komf$pU zIiINE-{f_qyJ~=*Jlg_1U`{mcY@(0o{Mzs{JOLf~z!RN;w7zM7o#c zFN0Yo1bb`1ZSi@_&#@d!;X+avn1oW{=iMSel zi?LtlLRf7>dHBVP?{z^NogK5*3CvipwW4NtagMQb3=nCNRtW#U;T(xlow0P)yf;tv zF7Go7D`c%!Sa}}lnqKBvVVeVx|Nh_;ltRHxSs3Z#W1dzBG?83CRIUDZV_dIiFdtpw z!Hj)IK2xim31j5rHkF<)tS)>gJT8K`NB9m~uKSbtIn2Jf5M#2Xy*;Pfk zY+DofIi74R3RxT7({pju%&8W;y%trjrU)AD!P#%yph^3UZ0b95(Q4=o%P~n7l!T

=X4^0p1NwX9WjgsNycrJkxPqhZg@t`6W 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�SaX@ZcT`KkaX;uFQt)FU1~`jMa+(U=i1ee&e4 z9ip>3x01)~nWHsajDtZc>~4FZLlYR+`xi#YD-bDKYwC1y&ua6Vcko3!sotT*5~M|N zwTY_Kyj%Azs`qpGwwcUGASRM-DQs1Dn<1EMgh84WaN@-u-5U!W2*MvveL1?=@YBzZ zdndrdZxxBxoS^m#R}RpiidNtx(*E@`QBM`W+MZ2$pV@9;H08FKSSsC{_l_Ju+G#ut z7|R3Icf^5fZw-JtYN~>!A>!4}mi-Zw$Q0Z)i+?V9ZgS7XTN*iW$&Q)vKQ4`up~~`L z;#vow^(P5*JuQ#Pz8!cpLGBM9L66r4f#kX)SbjM`11TMxBOWfbd0k+;qXO#baX%Ma z{f8R)F$+4^3a|?{W1ER(iplv3FJMmKssdv5P^y1IAld795!Ykl+aJ5Cb_|O;k=mNY zkmA9!9HKBPT=)Bnq3v-SPE~~DZ~I7?f~Y5omq0@EZoaB*^|b?0Sqpfh4o@Hg>?Vm4 z6nE7og`vdKDdJkPsrr+X|JF=s>*ESyU)f_pUo7Cxpj2T$?+7-<-8dHoxwq0{aPOZR z;mBum3jn(vAQhIV?m;;g_Qn7+mn4iv&)0GPcvo?Hj9fz&`NlZc33nLWgCTj1b6?-9 zoe30pXU=D5bca@E0Zv^b#3xssiBws8hH(3BQI8X-OvNPH^TC$RJ$q=D-&CzaJOVfLX= zT5t@oss79^Ewn->K&Vc)HI@-I-J@dmn%bpBP#D}wKK%%ZJa$sc~2aat8QWr_0 z^%u=?4violtJeHY+GqpO9}bBjSF=&xB)s_}fyAo9YYkiz=t?c#>BXKCP3-{X!Z-cz ztb>X?jYkEA5Mh;ML>l#Qq!|^#Bz#v#A5vM*3r;qawo2l5L zx|*@eBbLHR4Rop$HZQfamKOZ5FhI;TDEfT6U)wC_-;Z7A*xKx%da6J<)g znSP2?X9e^lV>sHhs+>CWoaI=bxMka^+(sw7?Q^%(KXhNL{PUhbm+TMRdSQ*_rKc-g zfxT`yUfHO6Irf(r-*wsbshzyy{un3qc+@LK#?El6p#AOhcC+Pp79w!fq;4I(S}QC+ zxY=Pa=GHfwn_Ie6Hl1I7m>Jj6xx4GP|1STH!GG)EzcBa@3jRaF|B&!MB>WEv|3kw6 zknlew{Qr%F|7Sn{Gu!*-8s$WGPY0g1s#@Y2QIe8al4_M)lnSI6j0_Adbq!2)jf_JK vjjc>gt&A+S4GgRd49c@)CZK4@%}>cptHiB=&3uUtPy>UftDnm{r-UW|5YOTC diff --git a/resources/ios/splash/Default-667h.png b/resources/ios/splash/Default-667h.png index 9b6ee14a54bb72699a673809151c34394c72c068..6c2e7b17e878ea0cb9b987ae7dfafcf1c2c3cbbd 100644 GIT binary patch literal 19833 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 Uz#hR+Bp6_V4#4iO-y3`B-|9mj*Z=?k diff --git a/resources/ios/splash/Default-736h.png b/resources/ios/splash/Default-736h.png index 1773d666e694cdbaab365c0ca8fd3fd02ac2fcbd..51e8cffc69db729f5f839ccc44b7d0da3c455442 100644 GIT binary patch literal 37292 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 Vigmd$@P<9k7ZB%b&tCui{{WePwDAA{ diff --git a/resources/ios/splash/Default-Landscape-736h.png b/resources/ios/splash/Default-Landscape-736h.png index 7cfc62946cb6b82ad420de9e988802040da04908..8123091af9218564db0d8b21581f3d896980c84e 100644 GIT binary patch literal 38609 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 literal 47416 zcmeGEiCdCs`v#6XGfg(5>CD?ZX=7so31Z%)7u zwqJ2Sa|(C_J~mEvAc2SPuDSXps;H>#QvUl&1zV~Oyj%rx>iD_T@h`HpM`7GRJqd!{ zoN``7NYgbXHyF6Lg!#X1-1$*kXX)?3?mKTzy% zmHwc+Uw!*G+o_E)1EZJRN2^DNQR!tJZ@b1a6dyaDSNWUz(Jly{*UB@?i01-3l1I|V z(#J0PGX*IMMEU5&Z?;wm{73a_r2YS%O#ThJ@%2AHdF8t8?En3e_FWaF1-Cv9{&?)G zf4;n=|Mj7NzEt`1l#0@J%8wfll{Wt0$C3Yh_22*d`qy9nz4*Uo*|NaD-tun{{SylR z=9Yi6;=ft(->e8&;9qF*FXH+q75s}8AOB+sm46}hzn#TDzVQF$hKpICyLA_(_2USy zh4mI6HyXT~jETP_It*P8F5O(DPCIxaScRSVTv=fRzgcL#yWG6PiC2(lHteix&Mx{un5FL-l*6r+WR#u^7H1HfDNv_{?9)*Ri!JsUfOzof&S@iO$ZsM?uIjoi#E~O zrlNAGAnHQ;mc6ch{@G{lO7k7Zw+}l3vrL}a|1VzrCmjCY$&BsB&dT>Mdp0V$)#qkp z>};ZZdLv|OQ+>b6{R%yM9PQ;6Rby1XqeS^56~q53Nst@Up=mEe(|Z?i|wgGl$|^=<|0(zY!wXuu@C0PXYwMgEZ9 z6$IQ?;o(QhyP9Z?r`85`G-OHFoL1kTWV{GDvwQb$3uk9sadEMUiHXzY3@DutiG@4h zO4B&SmCfM@go{>C>SdOcCFbA+nGvIP!6aJF){RU)QZiFF&=+b%f6Rk&kE8!OX$UMV zF3wCfaJW9xf6C0vAJH~ z3fLBk28E%&D8Z~2uCGNK6;!;>N;Vqc*eQu1nDeIjty0G=V9~^!oBt6~1F=cTu zZ=d6q`PL`1(dlj|C_?0e|d4{1+=4i}L_GB}GPEv5A`-4 zZobRkbfq{62SYBzZ@HrH(3XGfdb{FgI8ar(DwTa1Z#MRCjT_<&l{BUaIiHfo0=M3C zX|iXp1N>0)s1+1l5m|VVw|C#tvu3mN2kpyt`!51Z;LcaoQKW2zymMnH-jhiGO$6j- z6&1S!-#rE%s>WB2y1NY+Olvx4qKkfN-(^gYhk~bROK|=+TFCPsiBG+mw|H4#tt!!5l6(kz&uC z=|gVz3A@Z9iQ*xd?26(qxT|jm3C8 zY-_jQ4G$0hNQ&MY3opA{5lIt>=`FEA@|7Yk5h)=Gv16T}a!gZfNk!y^^gom)`lp$@ z@b{JTsEg6q3cv5SE(w??zI*w8{7jzX;yZpTyR62)6-1^9u@z*c7a@EmT8z6KVb3G6 zN(YchL|eHAzkV10?zC1Q{O|59uDWz?YlmMrw9_C)20*qO$`Uh+bmBXY+A`tZi(O)t zGHb~Y;9a|R6_=IqiVZ5a&_9m+ji%EBV67EwK}1wklu>HK$1?6IFExOe+fK0O4IHi0c5 zZn*~FgDtVJkCVV1357yNQ=~UyQWdcuEcpwJRqo{Lg`Fc-5E;pVNmZD^y8byOEys zV_$-B$TmW{#s^YGF&5ij0WB%~+R!k4F7!GH9SY=qA|8=0r?591H>ULDQ>6$A4|Wy2 z6?gT8TX9GIxMJf7p|!-e#4u3Qti{BQ^*(T!R6qg)t^lQ5ojK_E05kpBkvEUKE4{(wH8(gDqPuA3bq#QMoP^Lt`aq zn1t-x_xxjPeDIbd{-r0o+ASB=DXph8oib#;HjK80H$20jg6fKlYhV5%NFVEHh@^u| zQ)t)*?T6S_Y-DX~KL(rxpZc*R*X4-O5L1I6#W6R^w1&NDw*0=bH`IY1f&s>wD|Yo^sQr9?)uqko9@^gFp8q;AE}Pm%-OD=~><$h_ja)<2*!S9}J0x0Xj{gYG zSS@l-EO=R#_ZRW`%h`{X_x=*F$#gI-FST1)tgj_(tu5~v>o+&DGO@ujKkwT3*ymDM zhm3N-;~p&WO6HFj;5Ul#WPjF5q5hu49bt^mA3y8$ ze>iYE!lDp6yml9CxVp4z65M2x8(Z9Oi~l`hMRu_flTcs3@nzpppWLxQ`lG6uN_^tq z;@x$`sqEUnB6!;3w)mglrZ;3Tehcp1MX8(Rj?y~~O~kjjdRcmQ95k@r(QV4Dw!3S( z-Jq~_e9M_Nk?Xx}3$-1z1ySJ%!`u;PyXsLaP!?z9QUO)fW$C68Rk6ic>>{ALOlzO8 zm(+Y5(CX8OucHhZstL)j18DxPlCI&FBAN!G+Xm`JhdAH}JbmWf(1fi1nRB$rGLClM zCk~nSY{R$ZsvkFl{G_W=RSe2mEB6$@!q*HERlT@k!$c`M8Ppu;TB%k!bO#guOR=3n z*!*y4f_KAW6J{sTK*tlxfT#DrrK9faBz~Eed$22z4QVTFZY9GfrCyp}pc;Jl3UN8* z3oKj=T+*AXNm$bW@iV=+XbbyCRf`p&D;UgdVzYcWbed;W;_1$SkD~)%oO$xT1$n|3 zX~1p?1bLu2R`upnFE1=-$+>1Dyl>2@w6uF;;VP~e8m<9qq*pfPpf-v_FWV$twu#Ju zkXqrCa-RCvIm}AT-6Zl%duY_jehHK;xlfirjbM`Y`Mp3k8Qr~Q^zTgTQc?O!d}`>j zd^dBwSC68svOuAz_HQ)<65exM=C0;ZAmGEqi_DGt?DhL!m>srY`82p}r;d$N$^lG! zbpv_j0eLgiBJqgmRoSq!KRQs8xR`dtzR5D_vV)%;X1oN#qk{M+q8!1#cJ8R~U&@O; zu%1zt_Upo#iqI<|{eMeBr`wDog3A&(21XI3CDo1wVa&IoQ5I(krhQuw$rdJ$_jQqa zvld$`bJl=MJBgO1UV0?wB*Do_%MKK;54C}VIQp8vI*vTbNp^vND`TT15MD(E8grLm z`4F=>nMf>w+mu&>!HjE&{Z<$z2hYWi0RO!TI^i{Ps1ezgbKGP zcCZcW{g!;E(VLQq=)Z05X|K44p;J8lko^yP{wO~RGUWfVir$xv9cJ))vf>n!VP?Kz zMvHoF4I>K@pPUL6p~m%Ysko(RAM-1s1s26P+1d-!jEFdV0%yahgrdFe8&H0G(Z1`o z^({{1EJp~3>rE6B7bl9_r>BNvQ;3UMcEwEeAm1G~fgX&9Nrc%8q%~D;|1Jsx1#k=w z*|BgKbQD+Au=r=_$}q;;+|8bQ+lU(o*!_0)*(r(0o@4*g$`i<&yYiKJv-H&Nsz+8s zlem{1$^3qzi>Pw*P4MRF#ZDmw=vqR7JOcDzVEK;xIU{arqOzH3vv&)(qA;{!<*TOe=oePeB zn^|XSB1Xd4&)n7EVe6IpI1wbmf*0fA*=93T?4utMRG1(ii8M<^^ha_H7eGa`x_7wl zX@$}btf`ru%>XAP?m*JmUy-e!$|Uc`XmPUi1~G?_a)<}jrP%}u4Kk46?PgsO{4eWnL3&eYr79^}yc~eNB+0R9UfXrB|rJ1%|9$E?Q z`=B-x&A8%NIV=9NZ3FO>imWeX!+in|W}AJ|7@zzlJ6r0H4A&+&wm%)3%J}$Mygk?k z(ioR&?q5`z5ZmwPQ(l@F26&-cVvm}ar!#lSX>YVg>7%?P_M$KmU*QJ2nd+3D#XA^LkwHXY zQNm(Dq!|LzzhUlSKfM`xg<(Ht){Q9D7X#?$(vY&%xq<$BCD~Dlu6NpgdZ39|%5LLT zlmIE6#!oI9=kF+UN{pSTpz{C-$jz6}70}RD68oPLyH`P&7Vscv@5^%sILm_HYYS@hMr#MTfqIW5s=6<@z=Ja8{s&(0~P9cNmU`SU=);J~2h}&s5dpq7U zkrz!5Sp7Y;S2C4fs(&2wR~;@Z!@Kp}n=bg*9 zz~0Aq3tu&$fxsq{f?|rM(;*t4z;a$ z{*MP|ARG=oF2j!B8w|}_Qmgcj^99}nEKtqt2tjqQJY_&x0_B<;S5)Cyc3)d*&^ zSts?K@e*9b!>JGj#+Vi$AIEUJKttu`?)D3%J%Ye!@e4G0rOmi^x4)yVyL2wLQZwtQ zsK>lqH`R_8>u!-Tvv4(n(YAJrE&cC=(_wtVpk8ekp*1CyEsvV#cmif1zs>bF%Q}x8 zU_an|UI4P{`n84Hc9T~-05J0PmMd7tr7eX59kjB)a#?+%Ir~;8ey}@B+JKNrfbP)b z{Fy0+KcZT}iI;=a)YLX|6ia%Xr@3b7+YexR)u;f_TaA}a3`@aH>z zEMciJDY_xveY`I7aGU7&RcclkmP;}iYL0hLTn;;vmo_xidl4Rb^_eG?5Z7(Z&%#5p zW`va;-46%Bg&yku;Yp5=vM))iek>AxrLU0LniE{H=&IpTCTTV&Af*DL^8yx=8(fqy z;!k%{{A9E+W-5I{ISxd+$asWwsyS_1Mn-z^%)<*Wi@_dVt4`xP8u6S?fuyVW9^;e z|GkspSLleY9KVb2P5F;SusVoC3Cgn8GjXXz`0_*Ig))?pbIr1b6>!j*z z9Dxvr)K^o?s-hW=IW^sR;uqDB35c+T?I})T+p-eZo(ZcK8O3Y0<`rE+s<)mr#-hV9 zGF!MDw})x(wr)sBv{Pvc>ULm9Nrxg;ZES@K|(l4pxmDhxNQ!;gjpxj|~I zQ$AfkBmY8`o8UJFBN{^b@&J}bi3FQ3OrfVkuWNt|i7Ula1PD?RypEJ#G3#DMg|sI5 z20R0@I*H%NV=tAE*QlDisfM72RSkX%(0I4lO`3eG9RAKX+a&aYdW76 zAnJ+?+K@$>bzXV-a@4H(SHFV7)^B;uk$Rn`GIIrDK3h-s9Ns92kvXhi73NZyeCcKV zMN~NRlIMK)aGwiG-U8gQ%%9fz2*sH1yr*4;^Nec)(JgW#z1g|Od>E; z6}ZmF&~UC6)w=ajwYc&gP3~a`C`aKyhpi!!projm`O~FUv%(`sogyS;mOmXaOKB2k9vK=y z{DO;5qaIPI|MhqOX!1(N$1)rbIZfxWC933F9W<-^_?dPxSzqT)Vi#8MEtJN11Rp1~ zS|;lqi$U5GQ@pAdD6i|ig?(!HAlYcgs z{{nDfN)B>nW`-uT>yE04bc04*v0$&$h>aq#nEP3vXdBk*PFU)mee<5Yl*lBR%cz@U zO|5cn=+qNJql-~)Y~Tf9=1A`oEc^~1Ivy&~i6o_GjAtkuh6a$o;C#9rXPYb=qO`6R zv1)?1*W|}-_BORD#Gz~50PbBg9=aCnBh1u4LxH5ck9$fXEN-5X=qcRB9Z8lr9~skX ztn)zem$Fo~V)p~JprmvM{+J6^eqZ6!AXJ0U^#;ejyUsU>$_X*e{(gTmG2k|lneiq>LTv0%$R6hFiKD$2R%u@Tjm{gB1@=%=K?UD-X*FhX$ z`gBEWf3Iz_ITo8=E`EqCPG>7z9U37i(~}v7pE6C=TrhV-e<9zjBgh7sm71A{akiTQ z*s9v#EVY-pA;rP2iqZUX87;m%PLXNxK|||l;@75^TrP7pa&)kj`j$X(((kGXc54%y zp=HTn6#>`CQ{39M7L5T)$(lT#fb5MYvJ_7Ui>{zi!)ElsZ|n?Ff7RmSyfhKkqDHoB zmjcb}lp`UD{DtkYX&30C#KFKy_t?|pXY@Q2LPf+p6Ir^dtJhM8?VraH-&`*s6npe% z&S#VGEJ__QjZX!rP%**7vsP~JJ5b10U2L%}^5fA!h`P<$Mxqf|1Sa4kdiNqwGPdzbeN7&Blz9Gt`rP(3@4# zkxx$MG{i9<%l^J{Y}WUdS6;psih&?6P_a~TP>U_I^6qMdz;$EYoRD%U!+|Z?1r)6E ztj&k3ig|pw%cf!P+5VaOop0{OX=l#GxLOoRmhcr~ZVDl7gjyjS*YofrA!Y%GH$^G8 zNx@K9@^K1N01AP|cD`1Ro|inc4`5Ec7_O83nx4-tPhu|-`5VI!o!5Wc2Y!$JHa zxrwZ9^FqR8boK&B$LDhN{U626;lAHqJ|Oy_SuQSOy9$@2^t9=Gy*O2L=!mJ4dGmi4 zh)CfXrRWOk=(bzedbU{qdf>ZV-#>%IZl9{)P0$hZS}3Mwxo{s697Z=Xe2=L9(fvHk zZNA%?hg?PyWa9$$@i?f5`o(8e#+8?W0O7|w}z&7y-nxTn4=2Ut$S zomwI^HE(%!xpU|P!A;DqCAb3eElh4H4_;0>+oMv&d( zGfzdaEa~ek`}pYqP6iZ9Ip>v9(KY3Rmd7}lXQcpog(qd$349RfFA89k>$F2GFl(CZXp1oYK3Km6I`h$z%6NHY-_sy<=a{OO?OVGZ{w5DxL19Y7P@^1YUYW^3&82|ag#H>n_9~Mux8)Bh9Ew>u zN{Z|+j3ve8xR|#MeoFM$1WmDu^vIesM};S^ONy)8#;6B1UrlACv_hF{A6CI|*!*9L zXkb+@F!yzQ#<~Cs%PvyM%)a_A>cw;NU->RrfF@zTQ=)%S)W-+5c`hnD8QAD0*KgA(TY(s*HM%@Z5&g@yFoGp zH9X{(F|SsH6|bpR7-?#5XYRPYS!2A%N;aG$Khy>80^{Nph3w1xFtQcA7jtuyYX79`vD-r5$U{AxGBO7wb3%;Ttu0{6$zjv*oMy2zs90z z(1iV>_t5NbhcKl}xs9+hv1m9d4$531@{rf!S16i_c=q;8xGN5_Gago1K0(VxCS33f{yF}B#`e4qb9KbA(C-Ni$$;0;yd zAnm()*IW(h0bzdDPe2UCED!)(Z?Wu3d!6D*Xu>bX4#CZ!a)|U@77w`|OmLBKv#*aX zj$?`C_pD;)vlON>%{rK{0>7seZP@FaX`oK6Nnbi9!AqD;wu)bAae6I@&!ZPM($1&_`!6+q+ZqpLXG_-2XRoyE&q1ru2E2}t6h$Wc5Y_mR6Qs1i!!6BgI6rt6It zQ&QwYcx?v#dyU9cvnHUJ$kQZvIgz}1viCH%d0dwlx$jX=ZOW*jnr4M;(41)7XwHmR zWRN8yKnszcqi_1Beo0R`Ill%}nlXh*lJu|a+rsJQ8y(s%j4sPAlH}NYEU5q|j^B8`*Q0B8jINWiBP=zr z8K@nKI%JZzi(krrZovvQUOmQo6G6P1)k~v=;~JEjK0PqTK6@* z|KoOB$n#iQu^VU6T@gYmmDk|4%|i1cr7{4%;VWdH!0r-9)dCljr@H>JXb=4%`23hW z1gO0H(746*)+xWvT8+J~ioXDB81IfTRCeum!=60#bM+76cHy#tkxbr33TctN zy*V2ftGz5q59CCop{NWT59hyCDUpVGilTpwa} z%F;6EqXoggI35^DK0AA5t$Q_oT>&sL(!C~+Oj{Bvd0A7MbOhfPO^HEy4$|mNAM?2H z$LNZ8bro52Wz($>{Gw4nl$6jEGhla)9xa)>r?NNMNtK4!2(D1fk}jsNuO9;X!4FNX zn_L1tcj4ZLor_-qy-`=byl+@lzQ1^``r0}{{qP?c6rpI9i(&!n!#?R>w%~O{pmwJm z6TWnGaoSG($#5UI6bOW@d3jbJv1=ayK#o=ql3alqfxaPND8m6qxp)59|3*~;>87bz z)S$@SjQO4uQOCf=lTwl6Q;W|LD<&gYxS_Y6+!yF^9dNcX+y{;D8VGhZUY0s;E-xW` zfjli3u)F}^GqWVB7EQ5Ef^4uf38iVCyR~xhC9&#Nx-nV$U9ioCnMppxXBHh%-ID!- zTgO2eRWX3EG;>WHs>;a+`{Yh$w?*l-JIbiQMHO@V2~E#AQK>;@b*EnVHeXpzij3@x z7AMXBZSg2)d;#zYwtJV5q>r@9_Dc_DZo#yb4Z)IIu;DMoh~0 zd)<}V_tq#u^dNwGz1<*0OW&*7Dek3ltsVb8=9kM)b%TUU4a{Ad@e0v6GfkAHO4D*l z(-_vr5x&4O7WB&7I1}EKwj)%BxwF_NO(t4SRGg<}g#KiP!XJx*wljxS6?QUMU>QXT zvF_eAx%1<%a;8Bg-IAB}84U7Q*c3l{pqARQyXw1r?Upi_d;w#rIN85ZG}HE-`cC6> zdmS+H2!tgAx$tl+}$1_U7f_e<^P*?_9c1^ z9^h7jVF^c!b{W%cDy$&s7VgD?p_A|oxNd;e`8VrlvX3;O~ZPvf;=xjN+q z!aND#Qta{|dE%Q&P4*+nI<(TUyKvA61z>VN@LOT5_KC>dG?rw-h}(4t=aV%yH3|o^ zf8W-r$m*k8({W*ME-sl|IOZ4awtWDrCPUelS>o|+;VHE)!m|L94HGm5hZW{Oce&4X z8WFyI+yaR>oW#`vD5DTw_lAJ8N00F)>St9=f`X@rZxFu?y<%;O;_g|bANJF(GvF0A zvDfR2w7RTtW1Z7&{(Ixl^83ibbgCX`7u2hyC%fwPT{|ECW9<9U{0apR;Ki7v(&}LG z-c44|)K$_-QKeXMoo4UJe^mDH%|;|TIsDq<@JklPPvxS1dXY+HHP{2bEG39PK5lsTQ2>zf0xD>>6l2Uraj*htBE#5= z;;bmyj4>>J6sxrVhYoSH2q>`Cwi?$4fk%qI1g7Zo9ILg%Nb<$JnIIxI6&Z*%Gj3B^$9ea(vQ z&-;vs_<{)Ut|zh;Rc~7bgvPJUJH1lAd%sM-*WNGFFw=Rn>0nuK|0Nf%3&#uYU@>tt zq?62iQ;JcmLul-_$l(XrunX4)6h=l;v`6@CuVJomM1!;7tDH%AnAq(5+d*TJrZxGL zEuX1ymic!Z;KsTf51v21r)4=(uMI3KQPim)KIc(8*?j&^{2o{awb_f(lT>K{SCrx7!qO6!1_k3)67D_u3=TU2%KED&A>|b!78tY4Sw} z^R&POQ8dkERTfEFQH>PH8VQQ^BcX}wZGFNYh?d1)M_IJH0BjP)z+74*)j30T1mwozOg=1=5 zEIQe&82o5Zu=gB%ZU_{1`Te{;vtXy~u!D9VtAxf{>WHMsh~)OlKmQZ+Oc`$*ii7s6 z_X&<|uM48E$9)Qu5BNQTHN+hOGw+jU805d-9^XNPZ|}jh*ghI##6lGBC$Z0SwqMQ( zPq^lLAqIKJv}T;iL-N4oX)Jedv-VAfse`$E@C2BDpM?L?mZtq5-zS0oF%N0c=t?zj zN=6{IC^H^A04V*EHu8*jmpsNJ;a3N4Ft&5jpH?ir+2p9}*|pFJ5O3&VXIsN!-82uqHCY>aM@;X;y(~`M;ms}?5vP?ub!Fo-{{a#GGIdbTn7OD+laXq;|%?HHo1`V~1 zX>jIOh2A!$7LTetZFyqU?TukZc4bD3fwZM!@ug+bM3Hxja@xSP-8%Au#k;erBjB}6 z1DTH8znv`|fDnZy#(J10ah5e8iO=1l&b9&7S zu<6tyfI6*cTkoIwYz(L~d>(scs;O1R2Do)JInJxYxDju7yT|!)T!t0U1#$_@Lby$6 z=M)+R;G-mS4=$8cN~vNet&1u+CAU1wID+8kmkKbImGGdA7K||Xf!t7ja-O4jXq5=r z9Xy>^WRtBjO<9fK1OtN3X{^wE$o2d~AO)A*+VX(k!fv}&rF1ZrQ?`aND&-gy5a}(k ziTvgs&MRHtPVDH)bw> zA@})@#=Ev{o+i%%V!TXD=#qK!*y|JIT62A7___xF`eHmf)QmrkhcMerXE)VI8xD;6 zeJ3)wTp&@0S^S1-`7`ZPZirF#>6IWudwzit73uCUmYLFj+DG%EgVP&?dUsMJsz^ik zk>W#BM@uJ_bbm^!IyT!G70+YEt;C`QZo)_mq3sc-o$r4f_2G^dQ8m+XU49mDqk@=2ARDW-yO1!~4X%tsgfk7cehntS2< zRhU6ly_0wDWgM{Iz-J@X#ZiC|cL-}!x{Qkgll@SHuFZD1OI8v9mc3D3_HuzQqCGo) zyXndHqKY)WIRT)L-5B+VBQ0-kbKay}23Mx9?;9tUqA1bqf&{X=5j$M7yt3e z=<$NPS(@cpbAc35e@l13T?J-BCWKQTZQ*2QDrJ`-}>CEqXZo&qETLx8&&RXxN%ttyzhF=K3p84{D zUn|mqU!w;R^1%`hL@Li)Is()ldH-X#zI&mTcaA?vck2&h7jZ_A=0kvg_>mUhWd-(+ z?v3uApAw{CF_u(Ud*Ot4ZCc2DHqdkoDGmbiy4(1^>^JP|KL355RL^9n(iQW9@_;oI z23k^f{3py*vuJY6%T^((Ki&WR?e=sbc~2dTJE6GlU@mJp?pf2)TmR}%A7-e z*~!BTv&)~>9y2f6&tl-D1wDf8ivxV9!p2B7Ve{l6gGdh0YGy>%rMUxo_`2VPQ$SOj znxLG_t>0dAO7zb`6_anz?j{96zbc;H-S-htGFxIP4hz+tz@YY^aw;_-g_)F7^>Wx| zD3Vy7CZ$%46?;fWkOdfCb?T0CD+mtez%3Wnkg${mFtxc{ob6C!+*SQ?H5)03;X1e%#pR zvHlA0J0SrV4iK!^g#Wz$kXWE^9oA5?^128p>()T4dS?}^h(~AHYK~_NANAoxqCMbV zdh*cmj5rVOPw{(7t6+j-25Mca@p5|T;*-!TvhPcy4R}N4rNU8oH#Zm0 zJS}#p#(a9Pn6eovi3mi=4HapCNYX2HYdHLSj$y-_1wu_^ynH%T!6g6vw>AK^ll^yT z5rnY{IFReh4_IZ#)&}E&S5tYWd)=hXY*&Y_=}hD^h~>k*?dE(a62+lass4n$r92_S zeScS2cnawA_W0fXq5sPJnI>--w3SY%eto;$%MF-BHZlNs4L@Jscfi1ZNlLd6x8b9h z51uVT@QPOM`E*_}$^<6dBOS8eJgp?dS)1J$te1;Xt>9JLn@Wk1)YM#6q4TBu_&w}){l6}nDGKCy}GceAB*gbapC z_VMc5r+yeBfz%NKmRWG*B$yyXjcL9%Y&N@X)}3a64Ux@HWn#jO{`B$$TQXY}=DbeX z82bL$&fot_ojtKh{|yZXd8IG`iU^r;>MW;Z5#~=P@#|2yVE1i?fYc}Hpv&ZJx3(E6DhRcr?mziwH8444;$-pi*9sz@ zwU#5Kxg_|^_u@Sqa0SIxtjvB!yeAi@XSde{lE?mibWJ}^?CxJFyG>_(QEWG~dkc?eAvhX853uc5EcsczuJAzbUGNB=DN!Kx(0 zuFH)!+@!ggG_ESp&g}YL5v(MluWxB8KR5s9{F&XYZI`DjObFXG zBL$Y0mVl(vw$g7bRGuW#TYsgxVoX{wP8+ETDgb8I%Vl=I{B0S4G10Lh0MxjrO&5I0 zY`$Q;5k&yw6F#I=0N-?Vm|gn3k+>pNl}?nVTz9Bb6cg;hy(7Y9{v|q$DLytY$MyE* zUH3FML$Ncw=SN|hET;3~5+o9hKqt)R6Q#8ZAFP_0bLUxbLdJvHlAnVhxw+GIn-n;E zS>WxN7DmJG5q=%Dujy12(OV#`8g|Yoyd7Hh<;)#^Kyn;3L!6^(dh2cA>1P-MxTth( z9tasWA(5%0I&y5cv5HesV)JT86n3E2P3)`(;D|G*+kb*@c})-;3ncIR=*Y>!^6Snb z7j>8B6od`XV#eIGrnE(EPfY3ukgd#z7LY8W*~35OGaK%m9U2(}mW#xdZd4F#TUX4( z4com?Hjx7PsRlvqWwqMt%`78el(%1qJOoJ3uPw>hH}zZ~^9lDU{QHaR_LU0z z5}mBG$H?CRP=-%M%a?+h+>A$*x}vVKN%H-sP}QKF88_Ynoycp0p3tZp2t-8L#ygUR z-Ykp{wi+bHLh3;L1~apAC-J}^207WF+&O1URPD@2lR-ncIM)XY{N6UbL!)CC-BgK+>>T(+^X#-g@`{{jgf3;hv|8+?_?-_ zYg{zz!OHI`25Q3I3w9xl9s&=PsEU$J6lq>%c+XEh*|^=*iag}^(edrFsg742K7pJ; zla&R#*$Yd1t|*oRjs=V!CbV(R^D@UsZZ_Q}JZePelRNrM&-qauI!jR=rs?wLg&QqS z?$sb1y(~f>Wf9%bO^swp6+LT0QAQaV%dZr@eSGmZd+sLS2{7+6aTfW6BPZ%-YltB) z9+ok|)~KB&j;~$uy(%ahRs}m<6q=VhU+#)8>SXh8^iZppH zT~?-WwwE<$FL-pIA|jSM$D2+0&PcRXNK9C(_Em@J5nd-xK#cj+Wb(f0_uai^TES(U zBybVjT)VSd&(=D7TL_q+g+wP0WVtKi0rayJFju<@;NBbDQQ$O1Lp-~CRoa*M)$~W~ zlmkV$z}ozrt4BR(Df`r#64Koe@=MAhuAj1j;ssgm_)FiYl85Ojd4SIqb+&-IZr=uu zngKmAz*i_I_5ZY_yesetDjw@vOln&jj}~@voy`HdrGE+^J#p}aG5Z_@t3|TrkJSP) z;3KPrzK#cF7S9j~%R2M`8FnuzEfuEt^Sv2%Zp5rjC>ql8g=$V8seHp|YdQP4zw72SRvMpMjD z4f$*8^5@_@KnWcRwPhn)~tS@>9BQV#*w1CVvXzIw)@*1m- zu-KmU5fPEu>k&nZr**E%fbJ`!VYo2b;PuzNhkqJUt92JUA`QAon~c!K?j9NWsm!ub z`$Wd1J2}rEb$4e-I)85qWWWe`889T(71cSWu-(U63PLbT0RNL!rpPbQob%2@qDtUn z2C9qh8Db(WVCD64j;(T}%Y93nc_dmGGbG70;C!e85u zN>>!2vNiM59(L*jLu~Jt@@Y*62jM?JvN_(cg-3BNOr`93p3ro z7;Uxv7G`}5j29XB-BjuS{PksjI^~NeFh$S`bu%?8=lp&@v27j0Xlf2ke}Zm9*iKY* zq|IHj^^a(0*iQ^m7@!QUqy2ES1zSKEGv|#Ff;G)^;r*AK8|Y2N^Gyu951iFX8!lYZ zHq*>5((2~tBu;H`fn@4A03?RIql59Za4wG@WAOnh>|CHLBs|DkL`>{9i}h$t3QBBpY@r0_yW?23&57pT;S0TL{eC}CXjIJfBoN3Ut=7z9mt!&gp>ZB zZDzl!`8!!Qj{&EJDnbtou=la547ymC_M&k%-psbr7jZ!B7WDj_?w@e=2^n5T&LlzV zVrlX2oC(zJ7WL6eHw(tFhqX1-+wwzG@fXlSMkX+)id1Oj_ar8m z+)-UQ6D{`I_!jNIcogkeBqKw2lQ<#^vY|40KIro9{Mf@z=S=&M00-ogigJ$8tw z5Uz;J+a^CxrCx|yPD@?1fpD9_rE51~2998qRmk&g5A~rCrtJQ(X$mo50~NCE?47aE z7O8g%;8Sl?tKHBh9>jpF}gd(uqAn5ANh$R zI{Z%_t_5l@-WeIRikcD%rdNT+I0_HE>Gy3rQmTXsS<@D-!OR9i{Udw}32tUW` zZ2tzpI=?oKf0hH1+aES@pB@3wB`E$+!SmqSSoDT0`!qW&*U6wa*&(d{%ZE|a27(+@ z7aIN~v=;A?G+x>qcR(u+J0SEdp$MsVm@2BnqoUHpBj(QBV?pVZo`9^;o`CCbpEwDc zcmeg1W)Zdi@6Hudzx=(27u&fXI5Dv%b?*VrYv-L*iO>$Wm%YX`)@t86__pQod35qG zv!z;u7`rg1k^prX(0X` zEAx#fR$4(1&(m8T1AjK8%WSqGML!n(;@h92pbp-Z`3M(=Z0>V5EOUJAjF=fy}XOxj*YH_cu!!2W)SXA0%$)E@-|HYez?<^z?3q2 zobvgPnjytXX2YFPf3%ADl!C6zK76RC%gFCppZLg#`shdiyT)Is<>>!S3Pz zItS%2!okCj|DX2WGpxz;4Ij3(KWHnXt*sbYwlZZJ_Kc&B5FiT5kSz(wUSW?^sn}K! zO@I(qq$E%TL4$&XB_hy-B?Li67=~e3VFv<9-aGdH;s1VkzrLS*4iDkbBv0=9xv%TK z#(AE1y2qH>p*@_oCFd6;B*hTD+7C$y#cz(D(MMMeqiTmy;O{DD=&*8%=k|`+q;j4M zaC3u*0kf^8GgD+u-+%ZHj>e%jX0!WbtKNIRc4fMk!_^#m6DFRj_L#nwZJWf7x@s== zDY;eoJmuiJ`(&!_>?cX#mYKKlR2#dNI0fNEaOl zEhGp|Q&;{bD?0x3bK9f0`)trF@#PesXeN=ZV`8BjaRccaq+j@OUbms1h~!{gNPRv< zyKXq#Y*zoVC-uk~-*4sy$r!wFV(^KH`XNDUjGSEIT}tUfckpOuDAK%X`h}5%D!iHy z*;?TUDxFT9ceYdx>KOHOO%z4a>(mb&T34U!UfqA37+boslh6_S%4VRqtYC|8=-iw@ z$$ZT`y7H7V`m6fv|H2TZy>_E)$WVEz(No0* zpDqU~HaF}NB~KiwAR8k0N@N~C9?Xkm)YBc#KVUb$eDU(qAUu*mfxD8pr>-A-^20ryYU2=_jla1_wO;#$m#;n5AGd!unoQq3!9G-+tr88L{VNP+%)b>4mN@Bs?ah0ZX0m}UKKO^Emuzd~rDK`7VT@AE zs^dWpLxp)zqlXbKLCwK0k8?tg>7G{NJ&_)TVN6PcO{3B+h$=>u00-kDldr!^kGLN+ zW-+(DFmVx|oR9a5wA49k1Nz~SjBJ2?UL1&UwbF`dyWBQ|0>QRcfPL2vM6k7Y zAw2bshK?V#Po_HkRv6`=+hWZzbZxp8eP1EQ!OJPD{dBCA@zrJMvy_)Y>$%vTY6~NdZF;~%sHyAs1nW5-PgVW+`juiogUfphKi=1{?e7(TVG2qNKDSVN5alLa|IlkCd1k1 zi?|q{BwuaSt*N=erm?%S3Pp9aD8z#BVyDaQBlu{&xUtiRWlblxJ%*ar2M%EH^~o~2WR5{{~jZiQ2yCb~TFQy5h_9MFqRW|+w-oXWWt@P7TV*e}22#+i2ditEV9G!T$G5sp}A9)!DxZYOIV- z6K_{MCU5YdEG4vlakT*KJO5`=BnGcD4eewUOdq&~)~kw@Ux1uM^FZ6N>XkPi`j2kJ zsCT}1P-z>2;Zxssnd+=eyqWp-dAkO3pJunPy@!W(>{f|EM)gb5^ufWY#cRXApj?R4 z-5ZX&N$pQrjH4!2JeP$P^|Z-_6&?E0{WAy9+V}ERIa`jp@zD&kK6&OBai!-JoQ~bJ z$?Iy?agDVf=$+p|x+oUOH4Q4}{b(!gK!h z?BSt)Uwcr5n{P1=DD8+Uu=kD*)g2MMAQ#eIgl1Gj`s*adc|f2QEVWq)ZJb0G4H`4O z66~PSE5{#1T5=EBsS{b*v=Bg)oh1Nat*5mAJpR-7DL_DFg?=K_5~m_Yh8lpb9nhcQ z4Oiy1aR-%oRmQC8v+JhD#_lRB7{|Po+*r(ruqy3c`NY|U?S{_xGXJQAz=-DRpspId z^Xyu=8q1zBTWveK1#!!NG@v55GuV`=lNh`cFRCTOOqz$}IjTLJ0NsT148D)4_fL|? z0KP&!Ul-0_&b6<=88!_Tg2i8xdN+-5#UmHVYDrG-40?NA+i||E{^P=_kd1ms3@$55 z4?!!-ER%l!O+efY#rOH$me!?jr=AMzqb@`a-NPHwlk&A^-t6S+fk3$+(uX?zH#;fx zUF%wTO+f5;Hch|sa?Oya<$JL4g*tuo(gw-jyaPn{$IyvJGoX~v_2$j5Vhx+SN(may z9?{nVpCI~NYZ%|oF6R4{E4ICld11&7La+axQGK6xYLIxaTvADi8yA=#*#Te;me@FL zCytR(cYfA}XKrPSJ}1PUF%#i=S)q0$mjsVtCC>PUIPhS&%`$xN(d)FV=?@!kIDm6! zq`(N2ek9c~SO8jzAl(2|0Cqh=>i$Km=O?21ry2aMyDdgA{1P@P{aqmLxsjNcz3VI` z+Z9+);j1Ozd&#)@_{d(I1MSK;v+oy#isc4(alN@hP3jQpZ+H`1mYKLzle!(P{x&{a zDoULxE1OE{+-MBMC1{L9`A2>{9m(ftH7$xU*|uU|Yaf1ZTHM3y$v5!_xM6@~8)-_6 z1u{R4Kc4oH>c~20R_`B`TV?vxf}?nB1`^W`*iR-w{7lV?(uE1fjG0; z1-z-1Z$$wZ@BhgcFNpPhGA-9H0l6tan4p2;8hUZ)2^{A~d5nG);UK$g$2WjXe(7q* zNSor{RT@}IBns+B+j8%xe5!_sNIjx~3WFZUtroX&K62BV)ar-;;pMu1# z>9+%al9_h2#jlV>-QhNi#Ub?9t>iHXJK&wl;WDWetU5XGdC2{}FDih4vX!ySq0P6> zQ01yN?}HsdUH?;o@q}1U1!yvrjV+gE;efzN@Ca2E=-m|B&(GX!Ya2D`{$cQ71AgMx zsTZ)5@B}!oG0Q;Dm2+a%uoHBX)y$UvBAe-?;#7Q1o$TOqW%!YUbp79OyXWXj#bi#p z|HI}X6})DdM~-cb#J!3Na`fOc>6Nb8rFm7B9sPCjsado+eSdq9*wj{iS8jOo*7PZb z`Ni}3k#qmMc%S|ImB{gf=hUgwqS!&A0PcG0f&DUR3Efr%AT@uYQWB}L8#b~)d&RVM zR~`x;)c0a)J<#D{F3UvDfr_?Ra!N+g{Q1DS)cJ|-X0lc8Go7o$c^Ko;f`nch?yiA@ zu}2mL3DJIiADyZiE%aVkn%=W7B-s_h3`@Ve1X)^qQ<}xBR_L>aIon&P2he=K#SWgi zS14RT#Y{e&Bw8328N)B8qt8*-(ocnMm@XYeUJb&jafVSb@dw)1u4m`rXt877&5Roq z0CoM%|C6ALeR#CX6>mW29C;Ds3`)CzXO^dyxV=O#vPuMsN2}wm?gXz~h}S>Az#Z_t zC`Ov26aQ<=o@p65nPF0%AwKowarW1XKxQyk{^}Xm=lT{q3EJ-hBVQK2DXqyA)fOGP zr-L5~Q>dMS%>Nx`H|L+3bTc#|JS2m1W9TXE?M%C-W7++P7D(ABx2zXA|E3}|>W6Y8 zP8MLrtJD~Pus;FxdtUwd362abJtSJZFQ#Ch9`_2es~htO?5f#0TKQA7PiT_7mTGwj zV(pZ)fl|rr?r|I(mKcncmys6z;zvwM}h5VeX6U)yv@q@vlAHEDwfQ z{F8o_jC6W2Am)$yZ&z<#0uS$S?x)`N_aS4tg7|0u>)YJibT~jsrxbpfP9NB{t}_1F z=tYC>i-P0W3{Us@5OV}udF(=v#XoUR|5>Kg9`aSVC~>G<{raQgEV4oNGxf{&Wum~> z56SlN{>Q@7*%cp{KX~e{M7&?11n-eB7-}W;XlL>QXt=qWo)OMU9XPP#cR%!*V-Qdr zq61wz{MV13I!X1hNAGV36_w{@*_%*Y_k%XSiL^2*Z2kMgOKm^foXN~UJ9z#x<(k8~ zZ!hCk-t3sTjb#VjqsG}}`CWdI_M&`<>~Z-E^tB3j9@(U@Tgipf3MO1#AKM@aN}e}v zd=T|b6y6<)SKLk(74jw^OUF+?m}>sbLi4X5+wJ5vgGyAoatMP}i|-?7?J?{(p$pp) zhll?>Jp2%EST$x2ck4usTiPwD;FdI~L0ap7_l(B=IjVvC(0owk5A@swZ!On#idTC z`}b(VJqp|_D(xs1U-+veJaf(a0v#La-DeX9o@Dr!haZVI)6f7XJkS9X>l3~hI`^~9 zaI|bnkOeckE7mD-HG1=LZ)B z?(#o$ROy{H!#UGigVts{@4NT#>se?lV?WJx1)~-*d}}Z}S8!)ULzOesp82zLgKqgA z?v8qyDCM@c5fDMFJlVW)5G?O;hhVWNpjZs3r2pZ;89>D7X-!pse_GoT&v|nK9hy8g z9N%2+N(1O92RUFA-^ZoMSU%jmM;uv~Gopi%1goR1uP5ZvoQd6+_gpnpA(;+5l1A4h zTV(g^*1_ahzf#!aa-(>gaEIX#TSj;c!X#VQY$rT2oV&E0KXOb;ZFuA)$@DMt-G3kxS1^+E!Kn(B8Gt zZ+D}8?E^+ar4wkq#R!!$04r6Pesq$kkyWAc)1G{M5RRDy-LDw+0Y_o%D4_0V^U z6-2Gwd1SB@tgxfRf7PxeJ2@)xR6*@^G+d*c>vAQ)ReDP+JmAI!StF9tY!!p+{2;rdDz3KtXX2$0*D4-G z_-!o;nY)MaAb_)I&~0hbV$^}RzU$dDzr7xXDt4 z>5e4$41^SR(@x!c?DLjlo!|ySYl_0Rmz{hJpBuXMOsG4=VhuEFt{0)82ImVWp?}ur zxm3Z2L6$O+r7cY%TW_w-{CN?-%W}Tz9#Y+KzX$_)s@6?Vh0RsuWR7=9{ za5$$()0?QLl^r_urbB^x>T6w`mPx@36xpp+yH)o0NKM3?^2M^)vcR5i^->Wz(kOB%1 z#8F%?-!C)&=JF9GMAQ5|X3*TPe;`ZM1J_Pdxzlv(orY}uX%WNy@cUl;-P zbNmdVyg`|+@^V2Pn~)?AcD&!@?Efn*Is`|uGfc!Gl%JK4biNFZ3xrUT{fxbbqUj&- zoUpxD)H8$cj9pAdiiE{slnD`?Zrm4mxlv7=Rcx}l zB6*#Tv9Oo>Z!47$1|JpNFEz1|_;wcl0QyK5m#juanr+>tBh!k0kjTdO!RLU`ZQ6WX zm;+%jwH)X?L~|$T5oTv@+~>b(D@Ug2Xx5mTEj7$;Tb+6&-^YtqHf%WxA|$)j?Wk~>+Bx|c>GCZ^|eM9*fkvJ9#UrV}fW5{oziuPkMjhriJMign~I5p@-* zc@jnlzMjx^p@d{7(FSP_wWDQdHdH0f=~&FCp+ehGP8qIJLm-#iDCec9Z1w6XPV`>$ zpBS6g(1{l{t)J>9dH=s?qGt4gZK{8>j*hra%r% z?q!TL&K;V3PtMx(5?x(LCFN>Iw}awrqoFHy8}m&KyK zwG6>7#5%~y$--Jb*bMMEu-&SeXGq(eLuM0QI0bX<5>#j+12U|V8u@Y5Y=4curdiga zJ(3g(wbn0jy{y)HSxMx;*ArcVR#g}r&Fx)rNlr4~Nn#K+%iK-a5MP^t?7^`~ybyscy7o+}b8~BN3=lWFTJTbQNKu8YAL)IP zJ;O{dJ_l*T=-qIzZ#IPVExl6VsCAVl3BAf%c#FiIN`1Xw;UYRUp35V4GU(AXV*IqIcu{3_Bw_@1kh>@jkxfs-yJFlotJE5n|kABOvx z?!hm>>kL@&!>Sv7hKGN(Ba)D2+%$3}slV3F(}bn(`VuDS6mbEb)O7@nm+2{5Lv<+g zVL%hDu$zCX>(D<)M!+!HLa?u`qZ;)))GQP|wmp+BkE`sn^*_2Wq?!osby0L};q`|F zz(Vdl!o&(TAlUt3ys)2~E122+sQc2I2-Bi#)DRz%5epOPur0lTN!}CsR?695HPDgO zCt3g3xO=P5d;YiGjB5+gbNPWvotGAZYV`Y0A6X~^Vu5gBWV4~z2G zf9Zm7kSP^)iwcE}ivvwFKi&Osdn8Xn8o5N@NfjB!f4D~iV%kkh^YxcoCGfB7pIZoa{#c}L zCo=|yHB?_HEAJWw6((hXfG3c)`kGjrtg7@KhHC9oGx5pRy|_iOxb{6~wOtac7|KNkYe#qP5<8 z1}|4~0tWRP&KW^gSD?ZVcbV7^Sj74pmDfbqSC?@q1ysRfjGX(10?EA4=i_Yx1-8W> zY!ID;5=NTp82YY_>Vmsi9pIUmg%Q1s3qX63f=K=-OczaYRe6H`;B(bRf( z;=>p-ZsQP{lS3-0yJCtcn0whugqRsVP5FN_NNPgcw6BVvsZm@6Mf}!u6 zrml@+2#u-`8JVzQ1BIthlkO?puxgaQYV;`s`SD5tN+Pr03L+Y@7?|9?6VR<+fzB`O zQemyV+vJL7QKDfr1h*UNtpN`NonReJb#8$*PpT7}5X>me0K`foNF`~xb|_D37|7!k zr%#H4%8}OfXAxzCZCz?s;b9lJyJO|C`wamfjo5ashf4dMW9fGE(nC1mRPgPl>t$Tg z9#r_Yy0k?w^sfHLzJV9D{>X(8E;8~WZV#6b=D@Ys|5pHypIR+RT-D6Hi(7eEW~o$Y ztpu0IR83`Fv`$OJ^Di@c5zJ*;D9uVM`Lwur_3)GPegu|DTANSk%jq<5+`#hNji+-M z4_d`UPNhW@t@2f6)rPt2U;ghYDQr>CN1o^kSoV7NF5hQ<^jMa?bC8|cWWtu+zcDZg z^Y{f_x>&Em#zhYGaC%z;`R?&UNF0r-^yxrr_MV%j8+@OS!yOKwqyiD~CbVnwoGBJ()|=Y_f<{e|&x29S@uK%8Et zk$%700LxiZ7Dg_l@)SK8A=fm)ZYBvr1ykZ!)}W6XQ`Tzs%H6)Tqn4SS;gP>|qIoiBZO7+MPi+?1_>| z?8SvPS%tj9*<6{N;e@z9N&Tj5*%mD}Ot_1h&d2Yd=FYU4LP8O-yJL>VVdlT2oAI$w z$dZnF2qK<7h7WbaL3(dfF;aJ<=xx}%e(a9&0Ct&)Up7}d*#CmGavhExlYW zyCdrm3o~zUt!OR`(NYx*!w74gDOU6nI4xl3o$~CJOjd~sWEp8+Z=TvUX;`QeK#>LT zy70e$_!RS*n3xc%HbnfIoOfAUiw1EcJhiqO=$KapsWAnb`>Tc*-yhPn*n$*g=-Emb z%Sz{%+@G@}_+8>S#BzH-$_nd%=|q9K8$#5p;M*)bZOt|;20V60(a01Fkq`9chYU^s z#*?lJJ;J)BAiJ~;dYf|h$5_$z4rL*0X6ggxyZxiTo*_om<7q~(WPy`4+ zM(U2kA_$w3IB7Zm)d8;k90_^A5nn}am|F(oo9bTvr7)WXycC{)i7zYag9zwh6kTnl z&aY=z>nrxQ(VN1_?j4}pW63Y(R2p?oHSPax;xWPb8!!q}mSLHZ>*wu2u!#IjT&CJHp zOc^^-U=k*UTbrb~iw?@Uy-Co;FV-j{NP8r2T^T;|Tyt_eQkoh5D0s4HMO7349WNSm zl{{x=d6<-rENGKvr-gzpy_JR)UpmAvTRGzZUT&$YFcK8E)z%Ivr zZ#2wrGZ6?@+)9*@ylIf(lcTeNnIAR~%3=-TS7EyM3jH3bKy=i`s=B9U!;lh)&NH{f zB|828N^}QqIt5WfJtT$&;TK|4j+^bg`zaTKqAB!+dr*0YIIztPkO@h0bg_vN&dt`d zGX`piII?Bidrz|undiLL=~kRD(OG_9v({D>%vL5T=$JvbKlI$2^lX)qD~$7#*{wfS z_a0KLT&k*96*x~kNEo&t=!8Jey--Vyh9|3Rb%_j18bpS(x4Dd|g*rfFjbNln$9n?B zM62-Q@vCkS>`D{(0$jWm2_eDeELOg$KS}@SOIwRF#!);i5E1so^6r@?*{KS<67;oMzgpeM+sK(vgM7ImJC5&(n_%iK>KcX zkP^gO;06i+XzP7&Kbgk{jT)Y<-%QK$n8fp6h=SG+Gl;B%rM!hRq=d1S?j2SG-YrF! z^*=pg%F8~c?{nYX^}B8OP5RC~XtFFg?}|f<)y$IbY_N-WbEMW5$)ph1Qu{FdNYnfcGvL0m)yr*-ApG z1sj+3q)ctGjx05Y3)NTrL!oS&Ju}rjL%-;#T(2eY@VeBr=U6VAq5*0}92zIAQ(fyY z2?K9=5d6|I)mh*|^b*KL>C~11 zsqZ5Q4kL$R$pr!`#^2*1Rj`R-Z=*~}(4$J8Rlb(?iaIKLF=Z?Du&p9DM=sx%p9p)f zwAV2+ATOitg{6F%HmW}IV1HJZ+r?sUEFb7m z5YqOhPY6aZ1kq($&_vNnWf!h~<=?(12!lLr(J`iosOum7kt0syfrb}$ZjwA9Y9_BB z=H{GNl52;6ISf_zO?4>KKUTr)Wl~OPZV*{U*YM_r-^@--c72>=JzU9#6}CoC7E*m! zn{2@qzo&umO_Wts!$wx*5fZd|3Ip2KD=K$Q?nmdgk zn8h?y6-h|zq25HAA4nd_;54tD;J`LyarKplKrWxF33n3DZa9;U~Ugcveab9hv}SGZHuPAUIzEo;zr zWB0S7H!dn)^4fM!j>Ims#Zp?>CFOO(GYd`1_KX#>yuCk?I@7ymPJBIoN8pf#iEiQV3yKLIOo8-Y_KPzo?HjM^H<+M;Yw7w$&-=a}xG z7FHS@fr9;3Yc;42-$C^`yH#cYQESz0 z>tc=JKP)>X*7)>fmEK+0>}pwc+8;i|0aZaOY6Ag{TkazlG?w^mw7hhYjbRNF7y8BghkE?6Q|#?5=d@l)KJy zqFroHK67%7M48|(;y?Pe>-JN=Lnl+4RRpQ!vaEc|o#Me!RTdwW!c+-77Mz0g%%;0S| zzv(Ina(3%clX6V#0Ks1hl7u{=!e{M(492@ntS}Z= zIcVj543MpGRmg2tPQoILzOyJgIRef_*hV-4MB!{!6bRiBb0!o_RC#m(=nF}TPm91? z3d8w(EqVO+A#~v$PB3ZHfmDOoRrR;>vl~`Gp-V>1&8w(&QFwu|jaTPRa@J3Sd3o}m zgj_3AaNaMPmEy>!c5>HX`3j35=Y<~Z2{vznxXH84 zX>(oUIkhb1X~HGo5L@%K*&KdWo9LBIo-Q}53bmT2Y@hs5tK3WUB)hQw@iyEB>T(^& z0ezajQfp!PpoK76rUzwYs>v}{6Wl`WyQrOko9x!uPR1fKr;pPI94xf9Jc+$1qu3aU zYC%0l&Lg|6s|8q+f_!ru7_Ij*Q6?}(&Yjr02(*zVvo{Y(j201~kWMdm+mWqQ2#_4X zZ!Jc3U%XQ`|2TLguQfkKZFbWQ*WT`Xw7&6D7S#2+AM+ZdnS-^7or`A@1MjaHdEo~0 zTCY_jVp&L4b~OBgtH)yOU|S|X>+ULQi>O=ZWE@6cwG(+T0>W%BXeGto*Vu zYTiU0a1ht0)Wg7lD(zI^%OIfvNlnkBEscpwp-37be?+X&&oUrj`_X|BsDdoc0bzt` z3ZnHbWZrKz$Lmt<CU$RdQ zk(k_#WfsN@BczVyc-E@CK*mmILnhV{^c(`xZ}Yoi%LZuf8}-yEX4AGD^yRjl*-BTS zcF*;ifT*;({W?v5>@cR|uQ!j3_1+IyP5oT~>UUh#{3@FcN#`{y@1M@A^^q~~3-p^F zjBBv$>HHKZn#B)L0Y$O!rQJW}!50h~9yq-lo(oK512ZfN9tg8r`1|YGe{%e+%^obh z)NHF02r%z3&D$dlzYmbX4p5M_b9(Y%3a) zGF(k=SY_DyF>|LQPU3I>oye(B`<^irgzxv9UnsXBc7JD3r^Q+nhNe~T-a@^#^DTxb zR#13Du5zfILae&ku7#Sx{2!NZIvK?6E`oPiV3zMx;My!w8@V9@9H(I&-NOdwh<^)2PiTsL^REe&bjQdU!EX@v4?vpBX6lK2AL zN}A|Y8+V_W)#K(o2lzY(8b5y82I3-eFK7_u{ZY7Tcv)5&1E4b^jroWy|Oi`Wfu2a8v5C2bqPEo~xDnvCpil&tUoy`IWYVY5_?|i>m z#PW!COq&!`XqGjpLf(-Z|KNVnH}_UB?M)T_g$dia2-ZDYQ{MOwBq*5?*I38E5cHG~ z-`hAtUkaZ3_R3^;Z3Ov&yn;s$9Dk0C2@sM>RU||#n70&*R8q?v*?vd ztayUoPBIAV+L3C}Ow`jBO#Bt;9fEH8A^OeSNTDoxWltDEBaG*g?P1NYJ8$gP^MzjU z`j4Hs{_r16o=|w#Ahz29jn=F#tXk@@07XsNV@SFMT?2|Bai(N=M*?wZQ+>REjP%ci1(aMz4o>YpbKl$ZIhB&JxW0-FIsfz+2+ zs%((;Sxvl$g7PJr0n#GYbRU{4WYFxR93oF$^u|2d3) zALA^*q`_8O${Id&ph@dDtgV4jR=Sdph#T7FoOPM9!iVckExE$O!>IjdSe&Jhh%zk@ zUWkvpnf9kqv2D)Hqsq2aquCG2|4z-3F2xGbc-iWq$tee(L$ZtOBhGeWh!CJAIy1hGnA^=dk zt2XA5rvASEy1A?X^#IF!2Wea=GkCeShuf_Oy|bBi#navYI{Z$JN2G=Es|~E#)Jvu;7smnSV;p^gI!%dGbU4 zz`u*Z)1bfw@}mi2x-x>+YWQopSurqrlM!LcDF`78UxkbnWR@lFjyV{I6-ia;3Dg9E zFMb55LokKnmZIpHv;Thmq-{~75rEF+=NFZxQ0i*Y7t8i&x#@vWtUZk&YzG4`0W#r* z6-l50-uAJjtGg-Fjpx->jsVyxKr9#Fx z@f=+m|LuT&1>dsL=x)wsgoHDz03^Qy*v(j|a6e3SucZj1%1MLkm^}jq7$cRlGFyVO zv>zgoR{mI4+-R4~c*Oba5S1CUWQ?C3Z_x=#NAT@eps_ZL-7tGFnSSBzTKHDX z5dK`JPqy23^GSb?2h%*!*$%Ir`xS>oR-Zn+(m0V3JG;5&HS4CJvtyh?I2x)-m&4>+n|~ngf(Tg58Fy=8I0UL9Qs`{ zEe!u~2qWhSPS2V|3f5!&_cA6p;|+73p3Ox8D(W2p`UR?+!|Ct*E1H7)oA9!V7t zH?m|yE9^di$Wr0W>w?g2nA`g z%^$R^J~$Ln`#J^hoMOBPwU4scuu_x7LJwnaPy307)AFK7zSd-xPz#IWx5olvI-R^y zq|I|kLz+ow8NG6q5nU;3S)0tnrRa{t1<*jtXjXNyu=S1}a;Qgfw8ho4rZc)F2tfFB zXY)X{HYu53onj~;Ok^i?lGN_Ss(Tc6r-i?1uA1@QbgV5=vb~@RS+X4g+frAHI&0RX zEO;#CrpSIrJcTc|$Wo!gR*ZAfMSgX%hJcV_vNZuiUf`*FsC8*5UQfHZD`)03zf{g8 z*ydOq%f!=8jfBG)?IvXnK=4Y=lSE}_odX{ilU1@jIbdd9g;mbxh?4WGpFQ_5*tLjM z{jwZA{Ei>3IoxPHi*p90h>HW0{KWD)EO=32y68a-n9V$Eb9y2}y+)H7E$mElbbIsk8UlkX<-1w|HH zkav+uNuk4b1goQ{fIo9NUsw#u$HeVXpDXmlMS6BH(Y6v}4)#r_(8G3|syJU-jV@wN zlG(#4Lzq<-w=!lCk--y|L^PY~OqtkfPjA`32o4g`kKeC>w_w+IJxtX^~=HG!rfG-UQNsL$)u93IRglF2)bzshAOWjjhTqJM6 z&4Igb%dZv&EU-wISON}x-4a2X%vM1RFKwE8nj@n}U@+#QozRCL&*a%G2iBC8f1o+Q#e=|PRtk8a! zppPIF-jS7U366QsW{2OEWz#(LPf2kCpaz5x}?<17X4s^um0`UN~j9zQ*V>KXy~lfo68 zN$FBTJk7D!W-h1JM>9R{R6zx3Z5Kq&+JV&(z2GZL1_+b#$$giFWMyRX2RHhyJ^b1& z_>L3L?tYL-ZrW7@)_ThYJUYF^O^|KZ{S2u@3@u+WE+SId-~_`2pgBn>v)D~XG3U@; zAI2o~mH^T;CM5zY)k_JUPQ8cWuhfXZ| zAzxeP#E_oltY@E>hB>3zK#?T~@Cl{9_;GqTRwi>bK`IIB5&~ld{qROaQ>dlodn-(4usa zX1jy)6LWH@B}=i(LF;cpD`l&Fa4!rT=69H06jo}z=1pCFkIMN2nkTHs6y^9$<`z|- zn_tkJ*VxjW57bY_(8uBQ9HxW4x!&yHA^>ez&MtK^3sXFy3fhw&>23?{#wD!=9wk6} z7`y7mQKlKhZ&nSm>eMr_s%EP$3$KN!X*$Zsi@JjfYY@aY^6aC^R7G_4{YlA35bX$v>s|Fq`esA|F zqjj?y^AY1^E#OeohUS8VNjXq4-Wars1xQh3Wca#}%jYyD=zGXKPUK5zUM!3+15Gzq zYqx8#KP3o$6^}gzV&RHvi%7V=xz#Kg;Sl4QAGr;n8;R!`@G(-NIIwj3`BPwN8Ful{ z|8*Eea}NXKDdex3I4C!Oa`HhicdC0zHXh?7?G+?|uJ<>i8e^dr7g}KDhv0Sb(0je! zj?nkHQYI>G8FT=L-qoQGMLF7=8=%J^z>Er(Oku2AMLMniM4i^3XBe~hBdY=(c*>WQ z67{#H#~hUFb)x%{%Nw}%tVA#B%dqvM%ZLl$E_kMD(h)U-vaAfe>tWg!e#2;7KfW6< z3XIEw&2>g%Vi<6LazWA=h9)4&C4r-YrIBVvy~9kawo{O;a!EOyAkmR$RkSo&r~q6+ zc;Ae>jvK$UJ0!RJuNNt9jM5M~_|ws&8P!%+sZfgvQJCFwId?zOZVR5Agj_f`X_GvK z017t-;5*{fS8S|Se~n!2pP90P%P4r>SibzAHpv*z8LmcnVayX5#Ydlz#`Wfqr0&L= zP!QdLz-A(wDk#V*1XbZD{9-`nJzYiK(hEb<`B0M6eiQDlM zKX@*u-s69Jdj&a#q7xD-Wk)W6qm$Jox9ud1z>&vCw$<&rng|f)j>S=6bm5jC7 zH+PS0+vVIUFCoKBGmxOk1`2V8(n`jPMIpX|QqZhuE3>P9woQ0%=5+t%utG&pqNdEh z-|VNDUF}{yt9{1?*(A9MXNgeM*jPZ1cjQ?YEH#;I(E(9D(Sf%Nm$58Tc#l^|`h_Fs za1m)AsvecE#!IcwpwoKjG#&g~6nhB8h6C(>S+KRv2*!pQ@Wc6R>3p>=k;1c(m093! z{zQtSw*#H8*U`rU3-^ci*N$pb2_70G9Q`NhiyS{~7wf4+h1(A(uHWTIH1s@Qd#$*} zu1Lt5zyINFVu|)%+nT!B2c-v=dwbZc_%#hqC-Y`_0l9Q&4Kq@F25@>bSW~OM`nE+O zM!)bP#kf>4M&ZD#waDjT^)0MZ9VN+ip$7z}kNYCKfmpTG$%BmW+i{G&7RzO2&z)f_ z2F$+s`KP^e`%^^&@X`oq#IGPLd7uKR+62)NxC&f;2VH~%$Y_NlrvLqmIBEIy)40U{ zfBNtzqZ9vn!0uB8_|vb3Vu*T!c{IdI^!D>(4k6?}FDpIyOcR{&z8 z&#vIJEBNdRKD&a?u0Wh9eoh`gr_!GjgwIjIXIB7b;Ik|E>$me_0N1C>`}0db&%oyy z_&fujXW;V;e4c^-A7>!vfL55J-K`DbE3gr(V$Qo@P(B#6xo;#I{5SyBKV_z)Z=?e? zxO&RK+{nn>z)(wH-&|jRR4%#q|MiBDFjRnF{QvhGDs0*T!s5UI_<8$ZtIm4g{(k`0 CvP`D{ diff --git a/resources/ios/splash/Default-Landscape@2x~ipad.png b/resources/ios/splash/Default-Landscape@2x~ipad.png index c7a2b2494bdb60128c2aa1708eea23040edfc7f4..63398a5cdc084cafcca266f46722d1908171a12d 100644 GIT binary patch literal 37335 zcmeGEWmuH!_XZ3Pp@?oo36WN5h7cqq2Suc$yA=s(0cn^)5fDjX0O{^-$wBEHrMpAA z2Zmv0-ka^-|KIz3eLgS4z8V@}^n#?F33UJ>P zkhp1@Nz1Pw^gM*(UHC0(3hEV{_NY;f&Zx1WW2U<9(ejblo(+9LX`Sb$-#jFBZ(0p2 zM+yiA2)oYz7s20OwX^*F0LjQ)N)CJwv;KMh_l-X|{D;TiIQ(Z0|Cs}Tz<=KGpErR1 zOBVm-%1bx+FBtq64E_rS{}s-Ed%=If;J;w-UoiOpZZH_p6abeP=omJ;#5-(_!9RDD z?Bwgu{`zcEUp6D|bPv?Jt9Gb<sak`iT9IInEG3xiGNOP4 z{mc{Rhb^c,htr^!GZxe?F;yVk+o{K_(dg4bri?|50qetF)JPrxS`slRCz;RnFi z>S~w8p-lKCA2bpJd;+PnTz==|Z(ilUfByd-nSE0*>ZQ;<)P^YqfM-54poH|nN}fm8 zZoz@S|IvX&&bL4ZsY-RuVuLCx^x|n1OXT?W47C%MtX_ zzGq+LnI`Avp!?rRn4xEj5X>a-3R&ZkA%{JwMPoAfrJC-&_i{klA8eV??AkXag zZmifq&)B&G5JbX#dxBE+@uF)QW@dk}E%+v*_?w^UxrJ+mV`F27<3T||TB>4}b4^}B zw+YQymHsxKOT#Qatp%gA;kw286>olJ*1N9bj`JYRXG@`t4Gk@r*}Vul;msRnXFopO zW|^9rx_Xnrv-^H9gZJ+Y6%`d;UfzWqa|k-#;_fQ#|IP*_0bj z?e_0)y5(Hjs5%7rjpg#wgYRH*mQGCvogc2;VDjDnp76-p)TM`wk&!X$c{rVbd3*V* zHbX68Oy|Lg%NU8?_O1!DJjl6tU2uJGKBogXAQT|G5eBZ{#W}~G5;TPaAaORYIp|zUE z_{ge%SPfz_^^++~vc0AHe^`H*9xIeyg_Igpt&QrLrAT<}4v5arjE)L>AOBvCkl4-7 zim-KX!2Xtph`(KE?dDt(Y0Kx z@*@)-<@ro9zeLvGjQu~W_|Uh0q#VbSAnG*NpYRANjoFS-72g2lqZ72~j@v@pCm%C6 zg~HPcYc`LE)y%%VebrM5Det<(ET02_Z(_%x{)ZYGLk-rIJnDZWI_J6k&3PHHq>q3V zi3$n%Zog*UG99(%h(DhEQh+}kKWvdcNQa5S3~E z|Gmo`pv*Mr`3CJHt7PX++BE#d`tIGr z=83G!fcbZL+-9mZUbpV}OdTnG{`+`iSWW6={NL+m+oo#l(!4hdw#pE=5iK<=;8(LlLqj7YLV#nQ+FRmx zkhplW{+}7L{7w5m#!Y=nfHS<5y0mC-AQt(X=BbXUz)80z4o+GDYGv7;>?w*K!#9B;e`^ zfXmxGbtoI)xq$z^?hNw+;+T@-1xf9Q#O$BsH+mv>dxo#Kk&|fr=ITN8f-@k&2WeDKy+Yv z9`+`G%Cir~pNw^KbaZrZkXjCVWa_yXIAj#hOE|HusrP^JJ3sFdGxuqSOU-adAA|tz z2?RmFG3Tm!@o|8gMu@F_HFB)C97q&q>%)PZWC4lOQvsJ+kgA+CMgxa(!Jdq#y&F;l z&fDx>lU6c^!|yo3FyW$eoOnte98uFwY}x+wov$wam zv)dY|yKoXF3S*Q&bYw~@zH{k;G=RE2BP|SSfq)JG#RS;npTIgB zv(fxE17BxyB|jtM)kwaE>-r$C4X73Mev)v27mzwj6M^uf`x^J$>y-J z`1>sK5y?_M?lyXU6f$VqhB^~hJNJq%%H28nA(1XtaI8z<(3D$_PLUE=kE(C_^Xh8Z z>K+(xy)&iR8(-=0mnv8L!uEnp>}r)}Z|qnxZ;7D+W4`Ek-yOg;on1ur69kZTI8P0- z_VqOo;1ZLPJQ^|BI?L{;`>U`)AnrSUTMY7*M#Kcvpg(G()wZs80EWe9klcLMxII}P zn77L>3H4a~B5?z8ZnOA3Aut%K>3J4mzq|dT4A1MciL?LSWdG~K(ojc-XngG0s{0$7 zW*lhh;>vo}(xK&~GEz5a>AlfmRo*I8kjrCuVrDciTN#naN0*T6CvNhPRYgJ&`_`gi zrKlc;*4+)-i(`(#j~AdZj{T`kqL{XerOI*T`hmpOT}Vyys(#4;HU8KIg)yUHN=iYj z7hW)~uUdumnVjy-#B$_>BH+2<3N$yBX_!*^=u&f;V@l7S?}^rZ&3NiaWks@G!}z6LG9zE;Yd zk{kYFy*j6gPHVhtI6E?15zpoZeXMe^HR7zEc$UUJ?}1wv+He}m#wRvh&8M z+CV*CI)E!L-=8o+8(6QFN^_;0_Vj9KNIzxYuIi9Enf6U*N6kLe^#hYktVG5}=FC`j zt;5`{aC(m_1J(2*G9My9<$#K?D7{h2k}Q`@lVkL|!G(9au8A)Ci7sze-+`T0_mH*< z)^*vg)5FKFT}3a-4g+qG8yiWxeC9`Jqnl$G=Uyp-JZW44m(t>`kb`~r!?lZ)l=>pP zyf|LVM(K5;@7vY<-3gC2>U3moTO;P~?p!ZSBExL;!KB;U)jYntsg1ivXaf^;uoL(K zSFl81w%4gA2lDq&0K$oTcxvAQk3r>1VNvP%P<#uO)%Ua=QUAEOxWU1}aWh!6Ha0(i zk+xW6zSJa13P1gWpATu`VVme}k$89@j$(z1 z??QLb-j(JDH93LuyS>P}di5!YB1v|?;euJPEqnVog&HHfis&1MOI);?q!*khoM8v*(SAo~o1Gs``I#j-=w#f>rc6Q~l$(Y;2sqQeAzX=Vhz#ch?D3%_us ztv4$Lk7f(Jq6XJ3gEkV}2F-n+)`nsym2VuK&L=l|+3yC!(gvl4QtDwR4pS0tD8-b~ zVT6k|bNOp)Km3W4rPoT=UTh0Oj}D*lPF1#HF`8>jQK=9<3$ z?gAt?ZmzsTeUCL7kth^3SDWH+(^)^`rVJ#iov2M-nOYKl3u=u`)*V(apQ@yg zipLPFNJu|PIYV1{Y9?LSE01#i_;cgeamT}{&#GlvKo@i;4>}EQ&<{I?%kYo>T z!acg4Y>))6J8p4a>>dm?YzneBzpgDhTT2S;+8Z?SbFmDGjMyX;jyc^Ekn>q6ofEN$ zKJ12{j{f3z!#Ol8wO`2A#hJn!u$U1E%MSZrN9akK+#Awb2Bp^K^CR_UZs}L;zp~Db zzOJ#^ZG^49-T9q1XfrT-j{R}bRf~I1*(~e4TDaZg<0=|nz=IvBaB@pOKfRkD;Ew7x zfZX#N{zx?JmDWA$tcqH4HIlF;_TMqA67gAEyy(Zr3^GQu-^^D%-_sKVi!ve`QBk{I zIx`4wooy`xtvBZtXfZt+{22)XK<)2MkiDVKMx2(?>5MD#_pdCV^1CUqU4A(J-KQ`p zs7+O3r@DimzaUI4uGy`(Se*Qul?v`X^LlPX{!A>S0vW+;QDIky1%qwEd|-)!4?Wtx z_A)p^+e-TOoJl9Xts$q}0_CmO`5+mUFHg^!i@+BQ`oSel^Q6oQl+tiM+*7m^cd4;P z+N;Sm8J%5~!MLVm#4^bSNt%MSZoH3om-79c=pFxF!Y9_ydn#hA9>X40*6Xp?CynNQ zij!*>R6Ck1R;1}kA9xPQ4<+oC%~Y9f5pbnxS)&x!YghL!)|b*uXWzEah^Cwo%0w0| zoPRNBtbORD+Helj9b4u`sH_SUBM*$p#2EL~mN9uZBKnbuiOmgXyt3CpLQBW>s>!p-PSW0&%7gQxA&X3pNP9Elkz-=i)7tOwfhca9?L!7Ha%A)(W^9BL(XL7#U%9XHO8YYZm-Yz4A#>?kgkKS#TxLmvVNv#JBj<6lByb%1r+q3)w1qb!nXxR zD&qv%jgYe%J!Cl;`4Ok>F7j?a;WTBYrrT^*@Fc7`N_9PNl=_jcmoO2jf0c1|=GGQX z1#~ZZ{VMLx((2b}u$QFIvl?)VtEXg*Q(-?{`a0v0$-^PQ2tl{zqAnl)%GG`t6u^u~ zo-G8NY>rkv#p{TjJM}6!X^RSD1G?xKF4z@qeCjIg4LADV1az?#497HmBP+9yG4{$R zt#mVKmnJ~Hv2`U+J2KT~D6U|6nkj#ii*j1So%i;++CKG4!M0Te_ealXoIm|2=~2A= zdyd+h#xHgB#`#}0o35@V8EeEcow;ilmwkS$r17v;>wohFQ6#v$h=gw{gV2k(eJM-ZB|T}oLASb>KHju{*CEu`td@cy?*d<@@V&$A6NY{7$;u4xh`Cd zIuojP3oY1kh0{`=9r93f{YBBY)ldynHxV?@Q$F=1n`-NX93IB@q;O(xR+^dGT+!sS zy{5y_6>Go1F_#1Kej6WsH>egWuyrU|R@@;EFNt36UhD1&v-HE4>`kvW_j0F#_Ng(N zYh-z~r-$bnR+NF;FKd-@CX2qT-?4n^Bc--Y-cKo}a1}VBjQ-P077NsSpP%39$4An# zmUG(@!EemZx=wGH^b|8fqtOSg z8)LwApKfRANe^+Qe3hzNA*}S438TMy{4V%H$`w?T-v+MMwqnc<<$JfB_S5e|f5U`0 zaW@4#u*D+Qes%D^kjA;Ooc7tROcZ$m*qS!+cD1gcl0w3xkA`D3k@}x_B5xTHPZw2{ zBDW+&55Q8;;!So?vGy2FYCEs2$Bnu^Q`UyM&ued%QW-_OdD2k3Lq2bGTkP^!*C3Y_ z9jH~VVhvYjTz?4gDV`SxU1?rx37Fl+GUw$^a;anW;artHn*Mo!0QA|t(+?Z@$ssIU z^NN;iFc(U@mPXpRfj^z>M5RPwG3PdnEeHhDdkJL zaqIir~EeuF>;(=R@Hk{1iktMVbaeD6A1m9`psN2&Svgq! zFU#4&7kee~4`B$B!q|Q@G}`{f4VSSMbnSK&okpfG!)U?s@>J=7_mlfu2QINMK>ILd zlSY3x->~&y$&mXRpb`e*lxWY{T9MO3KgL@ZB!KzufBa**i|mc#8Cbg zeFPNy)_|l{887+*R%6pnTxYP-_rlJ82!LB^RI?l zYl*FstNfVdkQ*_d+$UMSY-@}xYMdZfTjEIdAFDqa3C!x)y*36%?TgzteQc02pxrpU z^PPOcmI6C*TjaNEW!I6f=9>u4qxY3&-y5Wha220VpeYQag_?w|Q+s+kYd0yu(89pQ zmnZ~vQOx=sH&j&Rbi<{xKi*)yd@NA-9Sw-`#rN&=-pqfz<)fse1kid_;%=K378W0$ z93wQcybHpa8aW)$UE^DmA$jt6<)SRsz~O8?42GMCK_Jq@d5$B9b%> zy=_ev^!(T9X@1dKuT4UgF#Nl=r4{VZ7}@sw)s(vLp%0&BH4& z3=!a%o=^Tbru^_3Rrk`;k{SeC?J!-thAb?!rOw@II2rKr@=EhPRMck$4bcnRMveV$ z2|MP(Hy9vMI=B#j7I2`k#z|TsJm7m6o@xO+>jOO!0MH<ho$1Pm2Ch5t0KBp^y0`5^>l1U(S=`l%c42N0^ z9*1f6ITf)e19c;j0E>aN8!u&CA_W~S(d8zjZOG1l)9F#VKE8Qpom5b~NGn*r#;@_& z{G0f(thHdkrJ1mGdXezB+tY;BT+=PU==6fIeImb>R z=U-%T2lt+kR%jUb?jq3GS1D?ryNUf{Gq`vxSW=qAwe-g@;>f57wl1Asq|`v3&fGFN zAn7grzk`JgTL0Dga#tkcXix&6 zejqsbm3AP=w}2*QRBClc^+mGRpAKA19=y@n@Ahlf@3jCv)itoihV?Y$0!Hb=I)UYnGpPyLaCHX(&^%(((jI z!V>oYo(uGp=^t2izv8;TumHU1IWVdS2eE>1n0b7V3*o(4(Nn}YxQ?dYfd@>Hg~uuF zmL_igI1FwmHfWWAro{xiC8Ix&m-cDND5Dp zLFx`Yo`hgD(Yz$f{RIqx#k|eUj_cvpfY|;o|FP$;>%+{C-!A0Do6fZvZ8P%6x zxaNvPhU>nbH60<};a6Kumi~>VthN+h6gsx# zVE`(Uu96ZICeS!*TBKWY>jmg5jW)kwFf~oZ6*bY?FDx1f7XhPkz~R>T zfpEeU6uK?W+vJ=%p=P$x!M19XHgp@woYqF7Ux!~N7%0BR_lCv&`#@<%? zucP@R2&xkCwN(_ZVsD<@?y9$Y*F~2Fvuc3|gC#xpNFqZ=5PMf5-4WHWIBrYdyB*!cyXXK3{HZVTTJ2&-$F5<<7 zR*h_}b=R?+b$y=v&#I)IHod&!g|aV?{ek4~F)JrC_yq#sSc93No?|?wL{D@HTg{3!BW_dnQ*u5^sNBm&ooeRgj+|(0Ufil_3^f*2w|XE}c+~p{K!dTrE25IA z?6Vfhz4N`0_4>3pH@L_q`mKioRIHU8Uf9;FBp> z5RkEixN)iU?0=Qc2C8+KMzs==_oqmVN2P!suCA=iICn8-#DEmk)YKf{re&|rVN9h6 z)RA5Iosb9DX^UT`=^Xyp0DC#FEmrp$vtG$v5{Yz;wdmv?Zuq=AJ_vby)>XmctJK(R zcrObta+v+;mi31>$#>_jMdS~)QMbY78Tr?oIY?>)jDL+(rZ(7&22QWTYbmIA;#Kdj z*GPV&{gz%^=dSV~tzq}`D%t9BO*A?H4MY1n*y;MX_)qsfPZ*t4Go#Nwlkbxgfwy>Y z)cE+DIrs~*U35}Dn}Niw3Rz)sB`@}JOTQL680GCDN6DGTB2#`wGWO-f^*$1{qEEz|KRgo6s3(+p1SfUidZY{O;kR2tY|)9VC_8MOD~Hs^WR_q?x3yAb zT;%$UuD^sT@_0Gw=ne38d0rNx*TnzM+UdR@qCjtelI2q|NI?{!Z1PN-8;7)v5b6@A zjYT8H?dBfkYq+CsJt51^{b(A-#7b!=u4NZ^f*;&V7VZ^*CVy>IIfn<-9WjQsr&#x>j!J$$D%ijyB1tq=j`tfCX^uQFCE87^oGIBv@tL6dGC zKQ6<(!l{mtt-Q!x8}(Yf!<4CVb7cl~cMR{>T>ks%j@CKV0m-@ypPP4d*F2G%gD!vj z;h}JX^GZXs;mmV~y_^X01KrJw$8Nq~r)K4O?Y>}?q!*jftLE3(uJF*`7aw$OZm=-- zqElM`zUs`|b36TUV~vWl8x!O1@kha|$QGX%4gI(8y;a+*o2TLR0r|sPzPA#|6@GFP z1{xB!()DlAXcaY7DnmmE?B|q|d`3E!!yjy+hyGG&{>>Ot0^xB{xPtC&%7Dl8Vy0MZ zqK>Q~aX^2=Ea6fiOLu9uzyEY{hZPhcpd56Vs@`bwI{K>s$US{NC!0EDrp@XTL_n)^ z^@Lu?^JJqduSu(@oi@vgq-tA@kl*Xeung_am@aZWl1Z?-N-kbo1yXHhB#=ag4>BBC z`!0wsqI`xWI}M-O6w8bm{57k4kfu_UZ|NtEkybzW`K;pZkIt$2fgot%AX8>ZYTnLF zH`qVzaqjy15t4sGWo@=TVR$nX~ijS@lks z8tR+BrBeG?W7BqzO7VJ?{Zl)K4GV|m>kjjL*ojwCcw$ID^UdvFB4uOHq*B$LALq@_ zdF_t0-%GshaESIgb@P28QH}Io;6HnQE5MeGdHQ`JCmT~Wiag&Hb{OQ`chd&?JC7;x zw9;?OWF&zof5)tciFzWIAtM1)PIn)Y=H0@Ms)(9>5~06|B1}7TO4*98o2nI5G><17 z!^*j|EB5K^Z);Fg-BFP(`j`f2=Bcm^?%hClmmR^86E9`t(RbJL1aLGdDPuT z?y)t8ZH*}DER&r)-S|FLAe<2F$kMp)t^YuJ_JiS!oj^lE%p^bt@tE=>Wf0 zr`KNsv8ZCHE{MXb00^7Uy;_?S2ZGIVjXD~*7|}724}am^PgcL>Bl49w%3JoLwhRT( zqPOH$V$d};J_^%_>3Ox$Se?kQAiRV~e{6-E&Ffakkw=tNG3I!6fu9`0vgc1m7&jF-NnQ z4qh>mYi&XPbideex&>IQf7FLREsfeOT@cWo{7S?zuK*RDi-L+uiB{g=tj9bsYcwk} ztWnbO`f4ytc7mMO@9L7HK9t!5)2o#h#fC73eIWeBSy=bEquy%4+s=G{YebDf>$$ks z6{en=$DUE4*nN1aN59X|Fu!a&fgUoi*og2Ex%hiEE2mlggwc6Xm1&PQnw z?lbG^Sod>ZcNo#<Ls;w7MYYlqDy^+y+l_A(H>g7nf@cc?f9H@KYs^ zH{}%Vi<(4C=Bd&NPO-UMopzDx%CKb~o8iciNXdTcR<%|Vvs{OLD+vK7(Sx-r35tT# zq$w0x%kT!S8z(0esyp>q{-!|cy#g-Srj%LwEm;S_<7SC=FQgHtO`A(^i^_|NIz>nD z5nZK|My~ttjT^oWIq0o4V|yc7NwRdDbdtDazx69+(?|F3xK1ojoawe)VY6-)IY>(U z9%Owu6M6ikgQS&gv;TciVM-Z-@4V)XNqK`W-D)<82+6IdU{E_Ra#sz-T3{sqsab7G z^klFgzPT&xB=CHZsCaxUnofy_&Bj1)y6KRC?6S2{PxvQ*l0GF|IGu;#xBO1?a&tun zuf4o_PfAKk_Mw_K5JZ991>k2`GryD3WHHyzp`ln0!?nQ<*3%rV9>0KX^a()5TqYH% ztn{Dat8DMc@sHi%4G30qBh9wl!Dp6pYPhOocTG3!Qp@Ol* z;Oo-s7c3qmTj<364LB=a({%_4w@AU{UP;IE(vlJO0ArVUrBC*8Rk?y$X&H)8vCky0 zxN2-DS{YOKG1McA7m={FThf zF`e+zYdN9lJGP8$64g#1!;zjubyMIZx_=9w~Iq^tE;Plu6(4_M1qB7 zdD1##s>!f{N8VGx4xNmTgnDR@QLX&Lc28YKB*tR-w6rbTXw|tmA~e zO82d)wk|$Gf2YG*lf^z(@4$YZZkS(f7*2&&vwk5|3Tp$VoFTeXBgtAJx0J8-2# zt{{142NO`tq~Hb4rt{wznWS1%L^3kNDp*GO)vAcLoitF%2e* z4M#PS_@5PK^4i5g$MUz7>n_$Sg2?A$brG+grvz=dY0(S6u!HL^63YNx>gGs+R-NN4 z03|?T4KBWYktoC^Dq6Qqwz>Y9)R15%ypIzz2C}cVTmHVQans2Jt{kE4z3hLT{;VZG z#XQE{Yw)tTxh=v=Nme~&G0xtrxO)F#Pow|yDklD|U~>Hc*|BSD14Wy?)w?S%?u3eE z2l4NSSpLe_O3d%CA291hnhZq5N##hrHx?c>Ao3;4>GQtlwZ1#s19ZX?$-KQy7yH@n zD?bgUlujL|TfMQ&VxZGb^Ll_4`ewB?m^iDPcZTQ zCBM)ma#g8ip79vs>bQs#rkC9GHXOlW=EfJ3k=B$`XHz7B{+AeP-}|Tky3+Me{{>K4 z^qQ2Qxd!H<^}Cazo4iX$DSD;Yi^SVbv(YEUDKM##{l&&%&0)GxyRW8t3@2K{^k3!a z)c*P!jE>Z#_lgW^nHR$Qqr1)!nusT}4Q{~f5RclfV)i@M$Wu+R8ceSD*fVYR6<|LI z4h=cM`v>2yS{Yo3$hsqHLofFQdtIb}`h&4_isZO*k#0LB=-0?WnK!gW_~r%Cj|+9H zV@e5f4y6(b%C@@Cv#2hvyqL$)p~@3Kojk*RLYy6#zBhe1@lxi`7$VTJ+JD0lF+?62 z16Eo)hNX&s)_5-7jCtM-T|nzkRY$j|#WrK)9@N|B_rau<@rEJ6khrLIk%S`s;=JM< zr!OiCOMSXoSaDsdj%X7;9x4jZoRo&HR-eR54)&Q%ZgJ8`&5et^YsY3*7knst8i_`2 zPpw;3X2}}IZL;UzvF{m8F0FL+pL7vPEihl_G=GD?>7nKIGSLUZ1otv~Lc6HNN_p=X zMQ%=p80u<%>8m}R2J88`&xYY=W_DJm5kRqLL@w;vqSz+CtJqxToCPG>JzMzPH;w1s zh7E2)j&P`lw|7kE2Kjsns*c?T-&E#Ubvk$VaQG+sXx(Y934M03jI*mP#KPC_v{Mh_ zzHIjhf@40RPe!m;q|`b`^y~;vGK_8U{vTU)C&BhcW)k1(j;}XQ-=wK(!Lv5D@4z~) zNW_dLVA~A{1yrVKFm@gg@@d5;7Z1rArBU}{NHcRuaeuD?+%0A0MTi%cG11n?L+P%V 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 diff --git a/resources/ios/splash/Default-Landscape@~ipadpro.png b/resources/ios/splash/Default-Landscape@~ipadpro.png index ab00ad82087e8c3b92e3bf90a16cabe011f7e936..add5c8016221327366459e3045b341fdab678b7c 100644 GIT binary patch literal 44093 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 diff --git a/resources/ios/splash/Default-Portrait@2x~ipad.png b/resources/ios/splash/Default-Portrait@2x~ipad.png index 5a9b090904e0247696e836ccbfe64d25a1cf412f..1ebad1ba77c63810635cbdfadc682246e0a81cd5 100644 GIT binary patch literal 37447 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 z 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 diff --git a/resources/ios/splash/Default-Portrait@~ipadpro.png b/resources/ios/splash/Default-Portrait@~ipadpro.png index dd8141a79963bafb02c5e404714fdc7626380896..c158dafffd66722b67d973a1cf355ad67359e7e7 100644 GIT binary patch literal 45081 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 diff --git a/resources/ios/splash/Default-Portrait~ipad.png b/resources/ios/splash/Default-Portrait~ipad.png index a430930f263463573ee4d124d4ad42936d6b71bf..f7a7b564bb0c3e77e5585d111d9dbc11dd470120 100644 GIT binary patch literal 15514 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 literal 20741 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 M-%rh_ul)Ld07|k7fdBvi diff --git a/resources/ios/splash/Default@2x~iphone.png b/resources/ios/splash/Default@2x~iphone.png index dfd140f53dfb9f073b26cda809065ae71a2d1b86..7149bb0d42aa9fcfac7202cb1b103ee303ea92c1 100644 GIT binary patch literal 14001 zcmeHucT`i`)-Q;Pihv%ZNQ)kia0CI7-jAZ7A|N8Y1f)twN(h7m4+07TDq`q4g7gv~ zK&S~4AyOlq5FqprT7W7w z@7;M8Hoi8=oh1?SWhwCM*Y_M(Gdbdm9-P(r$~o12|5-(Dk^NBI0;Uv@Lnv)76t23S zX-xX<)6b7r-v7Mw;@3y;sk@ZZU)72^xqfqcaQ9=jhKD=((4&udNtKdi%qh7C)uRZw zjOl1;H6iwTVO_8hZ*GpMpcR`_99LTZ9#I@z?7?y3{NLlut-r@VC;pS?{>ha8H(&lU z=KduR|CdbsllK3VIUm9Q{&f6jNWs6KfL3ncc+RI~>_Bij;lX%Hy*J{-KV)P>moE}` zMkSK6w&gq6g?=ng)~uzE`bxw7HLqr~9y33E6+a4FjonJb$H0KZ!U@fM$cq2qDgRK2 z;033sBo67B8rSuj)E33wSH{4kk!S4;^1`M0fhgrxn09V? zeuQ#h=|3JdSB9>*GEfrf+?=PwtfHhLnxaiFlq+tCj-penysn1YS}D_Bu2a#g>on6AgL3c9 z&WtVKpVo`XipFp2DliX`}?7vOAkQA03nz{>LY*5-eQWW zE*QtFmIYo+FE1~T13;Nq(3|Of2Rld@%m0|+Z(|7} zy1^wt6M?N!oK0(Wq@0L#jo_u9EiZ8|iDMv7`GYhZpVsOE9LPtSdTA4R$~x+zleHOM zWAfJ&S~J~5Xi}+9eGad8v?+|)h+$LIl2;#nnzw7#wnp`!`#q@hQ8QjL!WE$EJ6`j& z_}RP2a_4`n>9dTy)B4gn$sZd@?^QF+QdbnCy5E=tQ?gOpe3IJgVms?7f-Vsuo8b3F zDK{&Pzn`r|`>m|b$ypF$iTQfzL6#$ObcH4&L1f{i3a z#-tnEflrq?&EDj(R;zz}B%iwWX7%vZM3bXaCr2Xz$p*K$YNGSe{F5H z(j3t8=*t>3)nm1C@lQC%NrX;;DM_fh(i;i8t*^NFHRj(Z&G?t+-w+k3D^aP7?i>)R zNbO4bT3aW*;e&!Mub7{WVOjXxt6^C$lQ_gyt&gI9xzle>+8J#ZoUNndu!JD zxaize-^j)=Gi$NYnvO!iC5a~Q&sm;B?)^WeoE58ME;j^PS1Ly0ywaONkQx4NP!5|| z`|AR)7-y?1u+wXUqJrv5ZC^b>1z!h3&L0!8J;-<%5%5^a1=Xsv<1~WWvz#0|V zd*;d*;L;sN*HI43s7~8iu`%Yp#IUsP9?Awah^ADmyEiuSf9L}@Y{T>B1a{B05HRTphUll>a9!d%twhP6obnxc{WX8W_NFYA7zlV!+( z?vQ18Zk65Z;ejlL$BY&ay*E{vuk}!;x}i^r%Uz#?T#;p7`Q4c*L|8ZZ>$Wa;{r;MV zR_Frv(fjiBO2gramVJlqK3J_PVbp6Ul|^0!XhG|Vj(v(+c=)06s(b373Mx5<1C`Xo zVvw*0leG25m5Te}qXR`~`djr_UJ|rb9+nP$91)Q1mn3P@9xUgia&Ih<;Y=AMeJ26-q+?s6E-}`uT#Z@YO4qK zU%#yJ`Ni}&e5*%i_-aU_jL|Rng{D6KrhCMwh-b-93X(QJhgW3ulAyyD{R+Qn*lleh zWG0pKb@bFfG%LzxFrg>wnevbFw^(okb4D+}NN{ooqxkU8VcY!f!=xCR=ir7!B31Z! zy>dQPx>UinI=}vE&;`O;@;1 zTqGS8O*wiwx1~Wg5t5T9>$a3PSdIPRkzbsWp6Y|aN&89GeUv_LoRiV-c^uEXP4e#$ zmXM5{B>q+Su@O^RbDo7m|D3p{%UbCyB|JO*!9n!E9kGc%$h&d;3!j3=E1TNdi$YQg zhN?0WljvZuQR7m!{Q9T)Qa47>CY3vG+x<@Ye96qI-=X5yRO1PUmKg{+KJF0mAV*=l zo)jwQJo$bdP%v~gKD(p>Nm%U`G}NtzqM>EMgG0&)ZHwrOPL1!URtXdY?C8z4B<2J~ zcb7$j9UJQH_wz$HqE{8j2UEKkdNvJmKqBjI|I|M=T(=|dkEmfb6aAs|Og3-Gf8B{5 z8QTE;cz-7vr5C5m1lpkilQpic$xF;H{q>NEIB@E&?_C>7Hr2TTX3Q+5abBILnMIYZ&~td$rzZx0 zUxo+yqa{hgaG)S@StryHet`Pve^kmebNzP0KX z9I4u}0>O6~;@EUc(K7qMZh{1rE3IWH&ZN1pu1`0DD6z7!)mUM|UF$ol_VOpR-Fwnh zewoh2a4|$w*R-^1OvfObV{Qh5-X$0#Lnte^pY>6iwgfq_9@>3N$_oT^CvW?u(>S*s z^19yxPD&C>A>J+QRa!MR)|NL!C_8tn*pnYcqt9Czv9Tu`g5M!u*4BC;)gbX>1mMPRz?GHU=~_!`ID8THs1dpSb&F$~!5a3&Pj`m=CX z1$0FdmoJ+V65;PBUnDWMaA zcniLbpI`L#P=u)nzJs3Wt2p_?qe4`bzwol%JvGv$x?p}QhC8zCSDRre9}BF-9QsGy z^g?E#yfAjwgd9W1#hBzZss+q9+<=sy-4l^74XNKP0I5I9ihOPz3*A9UBsKYEH!~kd z6J;wJ!?%(=hZcDyUK6I|4svY@^*6$;{7!bomJrny&d;tO-(Pq~1rnrtA84Ho7?(us zvI1ZxETx~UHsM~&_8*Bj)=%m4@vtVK_5z|UzfZ2E?az>q-?0WK>{olWgG1^~8*8V& z&HJ1+^c6RrsM{_CN}Eo6?2EkaP=AHw&}~rD8MF(p$h_po2_T~So~YZI;YJl4GzyO1 zp1$A|KOt{hvy|qNbfdtnLTtJ-MH=}s3boHCsq7gi0n!Zk!BFBm4m&P=cB;p-LC#U3oAS2 zfTxwFP@E@iY0P1k4858IeFEviF1md0#c91h+9}n0>_e4AFj5&$L%Ke=fldjv2pAtx z0){OnFM6mWC$x@pSt!--g^@RM7^$Ai9`vTv@YtBLn*lBKZ`V|BPAY@joi0%`z~G_g zK(3jdU5<<Z(dhd`KL<_9!WI|2V-PDO^x`p1UO8-s%f5^N4r-ea z4Z#L~rY!nx63HgO2u#X1Z#RXtwE-0Y91>WLcnLLR7WK+XYuUe#;TU`nm8`=UJIw{@ z)1a&l?dbn($J1&CP4rLmYcAHOEIKL{M@|$Nb*MtBEF8&9G{Q8eMZqDX0@BS)R(oWX z#G4`%V;o?fy|((pk?l`gikgcXa(vKl6=wQul!Hi?h*e*(LbBllQ!UoZR*YODY|)_*Ll;76cz z_KiH=PuA$n8^u#t1jwT@<7LFwd3wB$dHVId+jg!POVW7mjrNK^u=}I%#K{r}rQrp5 z&mlfv!(Tx!z$Oh__CX?nt;N3HPJKi*#gUfRZ4(I?OZ0#n_604?!i6Y1EK8UwdQ3Kh zZg+T2;Bk?yvUYOL+@*$)Ey|!OckV?eQNDmILXNdsE1b2rwZ^^Lyz7W#h>Dsl@Hyj5;uhO*Zg-n%+?SVc+D~w$U>%B+oMPu`ymk zN~w>RYjC6>e1Y^=@NimJsOs@|X7llzj1?86hzG0}hd9cpZB=;JD#GJ4;Os zUC?DHJE4kqE0VzAuqp*B6#Ru^qSY4D*ZEwfXoL?jf{$DY8Qtq%b9%)w_##SJ!Exfp z8TC%C!;N=hSZ$xy)^8EKBI-VrSNb4;axmeW{%NZ`I}v5qACeJ#UAbCLtx;^pXzj0P zKF!56mz=QKw_OLnJ!LRF$@njnhx&tc*V}JZ1+RV2$lRGlZS`n}y5K*z zL@vag8K=LkX&Xl6dFSZJb!-pvbpd8S@ndqIDu-YLB%Z7hx~3T4TL$*%%kL_x0naf- z3x*X5wyLE;y7gmX^$YV!{4!>OLEz)ck{;;}N5PR8`Y6wMJMMdRa4p+#}SO0 zZ#Y;!r4BJsMmk~-khBWH>$B}ZRswUcOSI!-`6V<03+XV#Ky2>6n zNwt*=eUda;ep@d;@8_9YDm67V&HKxxyr-BMK7(~f)mESD5_zsW?u(Are03X~Z-_|v zKt;LVxT#q3>Lh)G;rLdc55S?h<gD;LK?QLuW`oB7OXOI7miAmARY~BvoABd(J@rSJKw808dh_k|A735)nQDD+x zqYE3MEC{;qhuVg2r69Cd%1|SoQGn&Ux4nI=^}}>6^t4k!2gukHhaCws(S&lHr82+4 zggL@Pc+T%x{B0&$SFm#R!16m|{aRlWgueP?FB%`ZX5;C#!O4XZ);b##0rdE}e1*}b zu_louph%yM6oD-4SK_y3=f8TCk~BOu%u(pyV{4autp{e!Ha;a?jwmR8QFK@{Q~&(5ljZwS(}^zqHc_7dzed95Wd zc)+o{xB2vmo^zbKN6a}i(LIw5UzdHDO9#Yq5VBJfuddjiaG2=Ta3eG7S3p5Rvdi(G zi$<=lA3k0#!ce^l`AF^lk*7gFZS1$%pikpObC<7@QLeyl7ozwla^%dx5FrNmHm<_Q zd)-W-b+SvT&nBP4%KheBDMNKt>`2a>Q+K&2ua0eC zk&p%1cHSo07QPjnz_itP_uAfz(F{&LH43Ck=LN8x1E?flqIB^dxyWsBd${l;{Ahf{ zo}QC_VS1-qExR(FGpT#C6H$sr_g_0&^S~j$N5e`&yn<2{xFZy}4u4ws^7cM&`OGCQ zZ$Zl4n&ze7Wrtp((=N$6p?@R$uw&MW{TY_CD5vyV|2(XO{MDS#QvvA}g_I=y5v6A2 zp_hU=d|($u3%@qKW27d5E#Z_O&Q#AmvvCA&sqp;06$Jdb5Cf*2$l{%iz>qoR*GW^q zftTwnY|PHwT6qvf(Ak}shk+@ZHC$~Zy{R7>)4WFQ zJvM-!`ajj`5`CO9HaDE8N8Xz%&fRsN6TA6=Ei$s_INc@AZm@FzRqSL6ucUU^Vo&>HTB0U!-yz{?y5M}4{z@~9Iii~`A0`_~7vTO=o`XZk z>hD?rn%N58YicirfCqnbcCi1LRGI|r3e{R}hSksQkxBH@zgFzOJ=4a{5U{rS9MZ!a z$lip?qfmb?VP}>5dExhaC)+-adQ*o7P;2m&R2ug0l!>O1*VMR`BiM}`x!WrWVM9_C z0-SvS?F(#wuKew#_|YXyCy!E79j9DJSHjfWB)~IDW8o?`=hj!9q(RW4A$|nwuadK` z@WKjt#!k~HbZt);)$0Fbe{f^t8xm_x<6B(*Eh)7n(L$8d^l;m@nfC<))nA`H+*qP* zCzuz3_kiRD_kJG`%rmk|vXMoWc67jbO&!B3yPJ&JoA+nZV2j@23_qHknrfn9A|}rS z=w8Gp;?-_l#K$g>OuDhVPxDr4Jg0*A@8zi?gVcQM=@c=r%lMo*_E}J9$FH~Sq>;LV zwdG(p#?QAQiw!q1gQUWkU8u*M;Mq#sCcbw5g(8Uov?Fc*+aBbk_JKcf_o>qRQnict zHgw+H?ioU*mv4!NQO9pC<|os3kAL5e5q7L+%WQ=?r->xCxUatQHmm*4UWoOJynLH* z{->-(3zL;tfex(a(wu+Jc#QicTyJ|qZVID#H@E=OwuYUwf;d8sV9Lr0i;)i43yo>% z-ivM_6?EI);@<}u%(7#%6(%f1s!Un?Pn0;LqRm{&YA&_xaDmV1WZJml7mwbI+EK@d z-ZVv*>@yo;GvWdJi>pgT8H47}5ckuxRr2}=E?sMGKpGhG{{VcqL#_9UNhdp65qZO> zll$L{2N?9(`Rx=2^CgtyPVt`ZBDArEq?(K83%7pJ9=cva%<7M7=f?pqmMHehqGuXEwT1zf5V6 z{k$u7KmA#z%nnCX)z8RRz>WryE850IFJr8BoH>Xk*-El@m9`#^^gs{QfScrSFa5ah z2{W`m!JBU*MlT=58p``UY247$no&Ta#FSZ>k0D z>d#WK>G|}g6>YD^tp|(9g20Au?g6nwrZSd=wy%!46ICoQ&6b*jSKN};g(vKx(*t!I zpZ*LFI+wI$*?9E&(Ot{Bh2Eo#lVZ#}iEVp0kmH4s%^Z^WnX0pu@>A8Ed#8{JES>wd=P(Pptut-l~5-t?saG z5`1@!CrQR!v^H_o0W-JiWfUJ2@o+)$P3mzbfEGC)yCD+glfE+XIzz9Iwzn)JQ$Y&f zVzBl2zCO-JJ)%dx(M3lre;`K#>crM#>(;MO7kO!|eH9}w(5XsN;^syh&Rg;nEuZ!J6d5M|(N z30+!FRXA>1&O{=Z9})MlwhF6x?^Z3rwU?0VsiQ@SdE_!-#MWN0pw!DdQ8sEy_Ca3{ zXU+vEI<>6T)Lq=^ctezSzbrOf7z_@QHhnM0$B?(NZ+zi35_Ms8{MBUydFY_&`Qn;u z`jc9G;jvwB;g}@~4t#BoIp$x>f4O5#+2WyhbUSNL4wO#qT8P8eKja5eH_%~mG4#B- zxFM2CC+(~s^)iB$y}hO;ixB(8q6@_HXpc~OTi^jT-YhllrpoO*!(OM=j|~REL8WS- zVP-sQ3o$yntol-JGVE?xjXndoGJ^gX zpL`wlS?_i_pv^c3qm>2mU9+Q|@jc#1tl5q?)v63W*q$dg;RGZWo6~y2%MM3Njo_@? z8TAe#S;OW2frhkgWPZMDLreVg3y@K;SGuqQ{2z}TbX-Q>D&;zJVQ(bzsn;V^!RwdSQ?EFbnDdOlY16NOJ#io2N}U7FV}8Y zK>Jf^@OP_12-ezj36Y~;Tg&SBZK7}ns)VT&rCdeUmeM!l9Tz3{l#S~Ke&T&@CSy|P z!G;Vz(1jSD?EO(#+*!Li6_COt{;IjLh$!-qsSl?OMK?* zbST|8bZm*DKUJwJ%AA1wf@iMM3i4}QI=TdtB^4YmA-38*=VN&VOVfJmYl|CiMarTs zj85|Z;NJGd{T*1L=`LUQx*odg2-fCmy?Uk?&#-^CDs$N*^}iDUs;lJ*1IUqC9Kw4HI;HF^8lWn$W@U*+Se8 zBCtU}4dy#`?ME$_eT&9U8-T()mFr#Xc_?TC_q$|FRERuq@c3Z$W6E3MK7L)&0PXGV zeAj23jjc5{=a1ORCC%yv*j$oK9Yen=5_X^=5GQ+&?WtE^73}`nI!un=0wNP_Re1nE zf3yRg)p9{hxh`mDoc{&r_3HJcadlUfL-ma1@-ZmU&v+pN>;gO~!dW3Wo%F@S&JHrg zU&4REevt4IfOQiQ&Dl!9+BRx z@2N)^Yfmd=1x+f8k|-@_0(NK%5ei8Lexiw_vRBvW5{68=%h~ewjh5mn(--GJWe!2x zk3f}wL|vQ)`4-T7-7dk8Ht3$r*u7T^N8VCl<$J`1i(=mkC7xw#H%zq_iEtC3@|sk{ z$Ohjx=Ha>J#hiTaq%I`ZWW|*7?8SyZj<^uO5t=aUeJnI{n>H7FPawg@a0D8qC8|yD z^6W9B)4$nO#6)JuwSm_^>n_+8T48e8>^~Er&*_IWas1#~rJ*4@tTr*IfgyXj)vK#z z`N9pi-zex*zl;g^sEjFLr*=CiEW@#@B7;q@<`6prnTdo9wKpBU=9t~wE>Uk!&BB_O zS9Yh2a;pHnrY6WXuTFtfm95d0_Ci?sVUK=lvF7L@P9$gTaQExn*~`-B6JlY%K{mW9 z2!a^o%!*+O*LFII^87X)ZX-A8vTdQ9$LmhLsJ{N*^Bki8!QT;FB4}? zB;5ds%h}qH-T{u|%{O&EOWOY`-x_HvPazU2ws*g#83)ghh__#NJ~ap}%^Gn?g*hiH}|vg+zTLIknyLKB!r7F{HtcDLTVSk<3$7`|5m9%7)5fg z$8wmI8!s-j;=JlUQ|jTez9>oj=y71s*_~`LwYme(RkSncU7_$-&_+6CBS^psxOUjy z00(dz;fu9aIK1$W@-;DoYic)gxSI!X=Z7Qzn$I_!x?7N4%^* z+hljNUE({NaLg{R8*Wjva=6-BHq1d*&R<({XiK~-y*|0O^0kE|#cS*cZvJpFfLb!L zl;(iz?CT@@B9>l%>HkFVIOVlE*)bY{cJ7~N4XUjq7N#tFhIGzu;b3nSn9jk$s;Ae8 zjLebS^nySub$JfZvh6uS@u zf0^w;8I%L~@%cFT!2{;N{VLmVOCk5~`$U*mcj!F>zh8_+t-HE*hu+=H8pidNUVNK0 z{0qKw5R_UWa@kGm_j1x}{ZqJ60_OR`PG<7!cCkqXO}{6QZ^!av(8K-YQJKqxLi;|< z2j;f__d>}Fi;s2JjIx^xA~kaMiSxx;TO4twHymIPWjFE$EgK36i&V^VL(reLkW*@I zf3aX-k=lYepR!m|$>Z9<#pKuVaXLR7`^Sv$y5YPBA+&(GO216H;6dh2o}%6N!b5Lr zLCVL`93&KY8gu3r?Uj%Dln~lb1?X#*C#^LX?l0(QmS=}nIq(}U=BDez=_wxTGUQM% z>cF*G6eiUdhGM;2dacuTPe2-Wm2D>n+T-AU+Bd+5i@{S_`M3_1T89e0m;{h91(Tr- zF{U=UR5!DAh)zZ$EO>Uh@|KN}iOwxu3Ms!EYJs7b&BvruXWNZHPez7xiaT%P2aFbi z*U2d!G!u76OH5=qwLqZyW-3in0ld0@OT?ktwL$+9AnTq5#px_8(WtXbx(k%!u#Y3m zzTm!(r%4(gJ`)j7_BsbY@tSI>dr_iEUwHvBhr*gSzI+B=boMJ zo?p{aULM(*%c#3Pqj;Ly6yEST$-vUX%5f1mjyq8!*C#CHrBKK#cPvVj4%=-D#xOqe zFQo*LSuT&-YKEzHs6lpFmAlU8o^WN>ff6;#hI*)mtFUW43~lTn$4D z-hG;c+xxMa>Pnh@3Uz8IGP-eS#GvK>4tZQ%XL@omZ)QQLK)o^xu(EEQwjoo#bN7-y zeU(R$48_Zq#T}c*#?EMbTv+b9S?&t|6|0G??sMVJy66;!Ku@IF)aYI@+PY(t#{cbT z;kN1XrLgs{AqoGf#r6NG<EzcpX|7f$?t=#VnEV}8-coSWAgT94U1XdDmko7}6o>-6XU0;~~ATmS$7 literal 18946 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 W3)Y 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 ZlJI~2LZnE(s#=@dQSTqhzCZiZ{{n|?$HD*r diff --git a/resources/ios/splash/Default~iphone.png b/resources/ios/splash/Default~iphone.png index ff0484e500fb5fed5c3701a4756a18fd3ca5de11..a3f133a165f9245acb5e398ee6cfd4216997f6e0 100644 GIT binary patch literal 6232 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 + + #FFFFFF + From 0e9b3dcbe1657feaf458c7d860e6d33cd63165c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 12 Sep 2019 09:22:03 +0200 Subject: [PATCH 028/257] MOBILE-3119 settings: Show site URL on about page --- src/core/settings/pages/about/about.html | 8 ++++++++ src/core/settings/pages/about/about.ts | 6 ++++++ 2 files changed, 14 insertions(+) diff --git a/src/core/settings/pages/about/about.html b/src/core/settings/pages/about/about.html index 65dd36145..f948746d8 100644 --- a/src/core/settings/pages/about/about.html +++ b/src/core/settings/pages/about/about.html @@ -23,6 +23,14 @@

{{ privacyPolicy }}

+ + + + {{ 'core.login.siteurl' | translate }} * + + +

{{ siteUrl }}

+
diff --git a/src/core/settings/pages/about/about.ts b/src/core/settings/pages/about/about.ts index 1c339f5c1..373f0d90e 100644 --- a/src/core/settings/pages/about/about.ts +++ b/src/core/settings/pages/about/about.ts @@ -55,6 +55,8 @@ export class CoreSettingsAboutPage { storageType: string; localNotifAvailable: string; pushId: string; + siteUrl: string; + isPrefixedUrl: boolean; constructor(platform: Platform, device: Device, appProvider: CoreAppProvider, fileProvider: CoreFileProvider, initDelegate: CoreInitDelegate, langProvider: CoreLangProvider, sitesProvider: CoreSitesProvider, @@ -114,5 +116,9 @@ export class CoreSettingsAboutPage { this.localNotifAvailable = localNotificationsProvider.isAvailable() ? 'core.yes' : 'core.no'; this.pushId = pushNotificationsProvider.getPushId(); + + this.siteUrl = (currentSite && currentSite.getURL()) || + (typeof CoreConfigConstants.siteurl == 'string' && CoreConfigConstants.siteurl); + this.isPrefixedUrl = !!CoreConfigConstants.siteurl; } } From 932f00cf1b9d3d1549e6f9ffb6ee92f0648f45fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 12 Sep 2019 10:28:58 +0200 Subject: [PATCH 029/257] MOBILE-3119 mainmenu: Show site name and URL on the menu --- src/core/mainmenu/pages/more/more.html | 6 ++++-- src/core/mainmenu/pages/more/more.ts | 2 ++ src/core/settings/pages/about/about.html | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/core/mainmenu/pages/more/more.html b/src/core/mainmenu/pages/more/more.html index 60ccc28a4..4630fb1e4 100644 --- a/src/core/mainmenu/pages/more/more.html +++ b/src/core/mainmenu/pages/more/more.html @@ -5,9 +5,11 @@ - + -

{{siteInfo.fullname}}

+

{{siteInfo.fullname}}

+ {{ siteName }} + {{ siteUrl }}
diff --git a/src/core/mainmenu/pages/more/more.ts b/src/core/mainmenu/pages/more/more.ts index 09dd27b06..84d1b86c8 100644 --- a/src/core/mainmenu/pages/more/more.ts +++ b/src/core/mainmenu/pages/more/more.ts @@ -38,6 +38,7 @@ export class CoreMainMenuMorePage implements OnDestroy { showHelp: boolean; docsUrl: string; customItems: CoreMainMenuCustomItem[]; + siteUrl: string; protected subscription; protected langObserver; @@ -108,6 +109,7 @@ export class CoreMainMenuMorePage implements OnDestroy { this.siteInfo = currentSite.getInfo(); this.siteName = currentSite.getSiteName(); + this.siteUrl = currentSite.getURL(); this.logoutLabel = 'core.mainmenu.' + (config && config.tool_mobile_forcelogout == '1' ? 'logout' : 'changesite'); this.showWeb = !currentSite.isFeatureDisabled('CoreMainMenuDelegate_website'); this.showHelp = !currentSite.isFeatureDisabled('CoreMainMenuDelegate_help'); diff --git a/src/core/settings/pages/about/about.html b/src/core/settings/pages/about/about.html index f948746d8..01ec8e930 100644 --- a/src/core/settings/pages/about/about.html +++ b/src/core/settings/pages/about/about.html @@ -24,7 +24,7 @@

{{ privacyPolicy }}

- + {{ 'core.login.siteurl' | translate }} * From c0feef896820afbb36352fc787ca14a445b04084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 30 Aug 2019 11:38:27 +0200 Subject: [PATCH 030/257] MOBILE-2872 book: Show hidden chapters --- src/addon/mod/book/pages/toc/toc.html | 4 +-- src/addon/mod/book/providers/book.ts | 50 +++++++++++++++++++++------ 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/src/addon/mod/book/pages/toc/toc.html b/src/addon/mod/book/pages/toc/toc.html index 30e22bea0..c4132681b 100644 --- a/src/addon/mod/book/pages/toc/toc.html +++ b/src/addon/mod/book/pages/toc/toc.html @@ -11,8 +11,8 @@ diff --git a/src/addon/mod/book/providers/book.ts b/src/addon/mod/book/providers/book.ts index d1061ef65..f5815f3c3 100644 --- a/src/addon/mod/book/providers/book.ts +++ b/src/addon/mod/book/providers/book.ts @@ -80,11 +80,9 @@ export class AddonModBookProvider { // Search the book. if (response && response.books) { - for (const i in response.books) { - const book = response.books[i]; - if (book[key] == value) { - return book; - } + const book = response.books.find((book) => book[key] == value); + if (book) { + return book; } } @@ -278,21 +276,41 @@ export class AddonModBookProvider { * @return The toc as a list. */ getTocList(contents: any[]): AddonModBookTocChapter[] { + // Convenience function to get chapter info. + const getChapterInfo = (chapter: any, chapterNumber: number, previousNumber: string = ''): AddonModBookTocChapter => { + chapter.hidden = !!parseInt(chapter.hidden, 10); + + const fullChapterNumber = previousNumber + (chapter.hidden ? 'x.' : chapterNumber + '.'); + + return { + id: chapter.href.replace('/index.html', ''), + title: chapter.title, + level: chapter.level, + number: fullChapterNumber, + hidden: chapter.hidden + }; + }; + const chapters = [], toc = this.getToc(contents); + let chapterNumber = 1; toc.forEach((chapter) => { + const tocChapter = getChapterInfo(chapter, chapterNumber); + // Add the chapter to the list. - let chapterId = chapter.href.replace('/index.html', ''); - chapters.push({id: chapterId, title: chapter.title, level: chapter.level}); + chapters.push(tocChapter); if (chapter.subitems) { + let subChapterNumber = 1; // Add all the subchapters to the list. chapter.subitems.forEach((subChapter) => { - chapterId = subChapter.href.replace('/index.html', ''); - chapters.push({id: chapterId, title: subChapter.title, level: subChapter.level}); + chapters.push(getChapterInfo(subChapter, subChapterNumber, tocChapter.number)); + subChapterNumber++; }); } + + chapterNumber++; }); return chapters; @@ -376,7 +394,7 @@ export class AddonModBookProvider { /** * A book chapter inside the toc list. */ -export type AddonModBookTocChapter = { +export interface AddonModBookTocChapter { /** * ID to identify the chapter. */ @@ -391,7 +409,17 @@ export type AddonModBookTocChapter = { * The chapter's level. */ level: number; -}; + + /** + * The chapter is hidden. + */ + hidden: boolean; + + /** + * The chapter's number'. + */ + number: string; +} /** * Map of book contents. For each chapter it has its index URL and the list of paths of the files the chapter has. Each path From fe4b760283f517fada03e27d584d43d37cfd0edb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 3 Sep 2019 12:27:48 +0200 Subject: [PATCH 031/257] MOBILE-3091 signup: Sort countries by name --- .../pages/email-signup/email-signup.html | 4 ++-- .../login/pages/email-signup/email-signup.ts | 4 +--- src/providers/utils/utils.ts | 19 +++++++++++++++++++ 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/core/login/pages/email-signup/email-signup.html b/src/core/login/pages/email-signup/email-signup.html index e00adbfca..30aa9cd58 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}} @@ -103,7 +103,7 @@ {{ 'core.user.country' | translate }} {{ 'core.login.selectacountry' | translate }} - {{countries[key]}} + {{country.name}} diff --git a/src/core/login/pages/email-signup/email-signup.ts b/src/core/login/pages/email-signup/email-signup.ts index 25ecd0e2e..cf25a4ab1 100644 --- a/src/core/login/pages/email-signup/email-signup.ts +++ b/src/core/login/pages/email-signup/email-signup.ts @@ -43,7 +43,6 @@ export class CoreLoginEmailSignupPage { authInstructions: string; settings: any; countries: any; - countriesKeys: any[]; categories: any[]; settingsLoaded = false; captcha = { @@ -177,9 +176,8 @@ export class CoreLoginEmailSignupPage { }); } - return this.utils.getCountryList().then((countries) => { + return this.utils.getCountryListSorted().then((countries) => { this.countries = countries; - this.countriesKeys = Object.keys(countries); }); }); } diff --git a/src/providers/utils/utils.ts b/src/providers/utils/utils.ts index 5b9d3b70f..736cf84cd 100644 --- a/src/providers/utils/utils.ts +++ b/src/providers/utils/utils.ts @@ -623,6 +623,25 @@ 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. * From 10328b7582743ec25c29b764de1bd2ec7ababbc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 29 Aug 2019 17:09:16 +0200 Subject: [PATCH 032/257] MOBILE-3105 wiki: Move Map to a lateral modal --- .../index/addon-mod-wiki-index.html | 75 ++++-------- src/addon/mod/wiki/components/index/index.ts | 112 +++++++----------- src/addon/mod/wiki/pages/map/map.html | 29 +++++ src/addon/mod/wiki/pages/map/map.module.ts | 33 ++++++ src/addon/mod/wiki/pages/map/map.ts | 93 +++++++++++++++ 5 files changed, 220 insertions(+), 122 deletions(-) create mode 100644 src/addon/mod/wiki/pages/map/map.html create mode 100644 src/addon/mod/wiki/pages/map/map.module.ts create mode 100644 src/addon/mod/wiki/pages/map/map.ts diff --git a/src/addon/mod/wiki/components/index/addon-mod-wiki-index.html b/src/addon/mod/wiki/components/index/addon-mod-wiki-index.html index 2ee9b5e8d..749a85458 100644 --- a/src/addon/mod/wiki/components/index/addon-mod-wiki-index.html +++ b/src/addon/mod/wiki/components/index/addon-mod-wiki-index.html @@ -5,9 +5,8 @@ - - @@ -25,58 +24,32 @@ +
+ - - - - -
- + +
+ + {{ 'core.hasdatatosync' | translate:{$a: pageStr} }} + {{ 'core.hasdatatosync' | translate:{$a: moduleName} }} +
- -
- - {{ 'core.hasdatatosync' | translate:{$a: pageStr} }} - {{ 'core.hasdatatosync' | translate:{$a: moduleName} }} -
+ +
+ + {{ pageWarning }} +
- -
- - {{ pageWarning }} -
- -
- - -
- -
- {{ 'core.tag.tags' | translate }}: - -
-
-
-
- - - - - - - - {{ letter.label }} - - - {{ page.title }} - {{ 'core.offline' | translate }} - - - - - -
+
+ + +
+
+ {{ 'core.tag.tags' | translate }}: + +
+
diff --git a/src/addon/mod/wiki/components/index/index.ts b/src/addon/mod/wiki/components/index/index.ts index 7296709d2..a7eab587f 100644 --- a/src/addon/mod/wiki/components/index/index.ts +++ b/src/addon/mod/wiki/components/index/index.ts @@ -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,7 +93,6 @@ 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) { @@ -118,6 +110,10 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp // Ignore errors. }); } + }).finally(() => { + if (this.action == 'map') { + this.openMap(); + } }); // Listen for manual sync events. @@ -179,40 +175,6 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp } } - /** - * Construct the map of pages. - * - * @param 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. * @@ -248,11 +210,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) { @@ -397,7 +354,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) { @@ -492,17 +448,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. */ @@ -613,9 +558,39 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp return; } + } - // No changes done. - this.tabs.selectTab(0); + /** + * Show the map. + * + * @param {MouseEvent} event Event. + */ + openMap(event: MouseEvent): void { + const modal = this.modalCtrl.create('AddonModWikiMapPage', { + pages: this.subwikiPages, + selected: this.currentPageObj && this.currentPageObj.id, + homeView: this.getWikiHomeView() + }, { cssClass: 'core-modal-lateral', + showBackdrop: true, + enableBackdropDismiss: true, + enterAnimation: 'core-modal-lateral-transition', + leaveAnimation: 'core-modal-lateral-transition' }); + + // If the modal sends back a SCO, 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 + }); } /** @@ -638,8 +613,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 }); } } @@ -731,8 +705,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 +717,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; } diff --git a/src/addon/mod/wiki/pages/map/map.html b/src/addon/mod/wiki/pages/map/map.html new file mode 100644 index 000000000..4d1545519 --- /dev/null +++ b/src/addon/mod/wiki/pages/map/map.html @@ -0,0 +1,29 @@ + + + {{ 'addon.mod_wiki.map' | translate }} + + + + + + + + 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..cf9fa5738 --- /dev/null +++ b/src/addon/mod/wiki/pages/map/map.module.ts @@ -0,0 +1,33 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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..e1fcb25ad --- /dev/null +++ b/src/addon/mod/wiki/pages/map/map.ts @@ -0,0 +1,93 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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; + homeView: ViewController; + + constructor(navParams: NavParams, protected viewCtrl: ViewController) { + this.constructMap(navParams.get('pages') || []); + + this.selected = navParams.get('selected'); + this.homeView = navParams.get('homeView'); + } + + /** + * Function called when a page is clicked. + * + * @param {any} 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 {any[]} 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(); + } +} From 3f1114f34c26ab5d32424714c0599518edc8c298 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 30 Aug 2019 09:44:36 +0200 Subject: [PATCH 033/257] MOBILE-3105 wiki: Code restyling --- .../index/addon-mod-wiki-index.html | 5 +- .../mod/wiki/components/index/index.scss | 1 + src/addon/mod/wiki/components/index/index.ts | 66 +++++++------------ src/addon/mod/wiki/pages/map/map.html | 5 +- src/addon/mod/wiki/pages/map/map.ts | 4 +- 5 files changed, 33 insertions(+), 48 deletions(-) diff --git a/src/addon/mod/wiki/components/index/addon-mod-wiki-index.html b/src/addon/mod/wiki/components/index/addon-mod-wiki-index.html index 749a85458..c16f7462c 100644 --- a/src/addon/mod/wiki/components/index/addon-mod-wiki-index.html +++ b/src/addon/mod/wiki/components/index/addon-mod-wiki-index.html @@ -24,7 +24,7 @@ -
+
@@ -39,7 +39,8 @@ {{ pageWarning }}
- +
+
diff --git a/src/addon/mod/wiki/components/index/index.scss b/src/addon/mod/wiki/components/index/index.scss index e320264d2..3a0249f11 100644 --- a/src/addon/mod/wiki/components/index/index.scss +++ b/src/addon/mod/wiki/components/index/index.scss @@ -12,6 +12,7 @@ ion-app.app-root addon-mod-wiki-index { .addon-mod_wiki-page-content { background-color: $white; + border-top: 1px solid $gray; @include darkmode() { background-color: $black; } diff --git a/src/addon/mod/wiki/components/index/index.ts b/src/addon/mod/wiki/components/index/index.ts index a7eab587f..f4780f4c8 100644 --- a/src/addon/mod/wiki/components/index/index.ts +++ b/src/addon/mod/wiki/components/index/index.ts @@ -145,31 +145,20 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp 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; } } } @@ -328,12 +317,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; } } @@ -527,7 +513,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp * * @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) { @@ -536,8 +522,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; @@ -551,8 +536,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp pageTitle: page.title, pageId: page.id, wikiId: page.wikiid, - subwikiId: page.subwikiid, - action: 'page' + subwikiId: page.subwikiid }); }); @@ -563,9 +547,9 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp /** * Show the map. * - * @param {MouseEvent} event Event. + * @param event Event. */ - openMap(event: MouseEvent): void { + openMap(event?: MouseEvent): void { const modal = this.modalCtrl.create('AddonModWikiMapPage', { pages: this.subwikiPages, selected: this.currentPageObj && this.currentPageObj.id, @@ -576,7 +560,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp enterAnimation: 'core-modal-lateral-transition', leaveAnimation: 'core-modal-lateral-transition' }); - // If the modal sends back a SCO, load it. + // If the modal sends back a page, load it. modal.onDidDismiss((page) => { if (page) { if (page.type == 'home') { @@ -984,9 +968,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: []}; @@ -997,16 +979,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); @@ -1015,7 +995,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/pages/map/map.html b/src/addon/mod/wiki/pages/map/map.html index 4d1545519..93429cb91 100644 --- a/src/addon/mod/wiki/pages/map/map.html +++ b/src/addon/mod/wiki/pages/map/map.html @@ -21,7 +21,10 @@ {{ page.title }} - {{ 'core.offline' | translate }} + + + {{ 'core.notsent' | translate }} + diff --git a/src/addon/mod/wiki/pages/map/map.ts b/src/addon/mod/wiki/pages/map/map.ts index e1fcb25ad..30fb14133 100644 --- a/src/addon/mod/wiki/pages/map/map.ts +++ b/src/addon/mod/wiki/pages/map/map.ts @@ -38,7 +38,7 @@ export class AddonModWikiMapPage { /** * Function called when a page is clicked. * - * @param {any} page Clicked page. + * @param page Clicked page. */ goToPage(page: any): void { this.viewCtrl.dismiss({type: 'page', goto: page}); @@ -54,7 +54,7 @@ export class AddonModWikiMapPage { /** * Construct the map of pages. * - * @param {any[]} pages List of pages. + * @param pages List of pages. */ protected constructMap(pages: any[]): void { let letter, From be6824d58c05ded076482e2adc43739399f254d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Mon, 23 Sep 2019 13:35:25 +0200 Subject: [PATCH 034/257] MOBILE-3167 blocks: Check block visibility --- src/core/block/components/block/block.ts | 6 ++++-- src/core/block/components/block/core-block.html | 2 +- .../components/course-blocks/core-block-course-blocks.html | 2 +- src/core/courses/pages/dashboard/dashboard.html | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/core/block/components/block/block.ts b/src/core/block/components/block/block.ts index 3632c972f..5a7546e52 100644 --- a/src/core/block/components/block/block.ts +++ b/src/core/block/components/block/block.ts @@ -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(); + } } /** 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/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/courses/pages/dashboard/dashboard.html b/src/core/courses/pages/dashboard/dashboard.html index d94fcdd0a..4f030b26f 100644 --- a/src/core/courses/pages/dashboard/dashboard.html +++ b/src/core/courses/pages/dashboard/dashboard.html @@ -42,7 +42,7 @@ - + From f985755fbfec2b6abb8fdeb7aae58cc3865bbe32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 24 Sep 2019 15:39:36 +0200 Subject: [PATCH 035/257] MOBILE-3168 tags: Reduce tag font size on blocks --- src/addon/block/blogtags/blogtags.scss | 1 + src/addon/block/tags/tags.scss | 1 + 2 files changed, 2 insertions(+) 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/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; From 697ef74d465f46c5bfb5fb4f898ba03529404504 Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Fri, 6 Sep 2019 16:22:25 +0200 Subject: [PATCH 036/257] MOBILE-3120 forum: Don't display description in single discussion forums --- src/addon/mod/forum/components/index/addon-mod-forum-index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/addon/mod/forum/components/index/addon-mod-forum-index.html b/src/addon/mod/forum/components/index/addon-mod-forum-index.html index 4c8be80bf..859e70d59 100644 --- a/src/addon/mod/forum/components/index/addon-mod-forum-index.html +++ b/src/addon/mod/forum/components/index/addon-mod-forum-index.html @@ -19,7 +19,7 @@ - + From 62a6913f30b238ec22c2c1b023407ff97e8e99da Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Fri, 6 Sep 2019 16:33:20 +0200 Subject: [PATCH 037/257] MOBILE-3120 imscp: Display description --- src/addon/mod/imscp/components/index/addon-mod-imscp-index.html | 1 + 1 file changed, 1 insertion(+) 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..775db0670 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 @@ -16,6 +16,7 @@
+
From f441ce79f7eabc4f882cf6fb767e2a76c3532126 Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Fri, 6 Sep 2019 16:34:03 +0200 Subject: [PATCH 038/257] MOBILE-3120 lti: Display description if setting enabled --- src/addon/mod/lti/components/index/addon-mod-lti-index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/addon/mod/lti/components/index/addon-mod-lti-index.html b/src/addon/mod/lti/components/index/addon-mod-lti-index.html index 492303fa3..e46b4fdae 100644 --- a/src/addon/mod/lti/components/index/addon-mod-lti-index.html +++ b/src/addon/mod/lti/components/index/addon-mod-lti-index.html @@ -11,9 +11,9 @@ - + -
+
-
+
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..fe3930374 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 @@ -14,7 +14,7 @@ - +
diff --git a/src/addon/mod/lti/components/index/addon-mod-lti-index.html b/src/addon/mod/lti/components/index/addon-mod-lti-index.html index 492303fa3..a4e3f2e1e 100644 --- a/src/addon/mod/lti/components/index/addon-mod-lti-index.html +++ b/src/addon/mod/lti/components/index/addon-mod-lti-index.html @@ -9,7 +9,7 @@ - + diff --git a/src/addon/mod/page/components/index/addon-mod-page-index.html b/src/addon/mod/page/components/index/addon-mod-page-index.html index 819cef352..d231a3874 100644 --- a/src/addon/mod/page/components/index/addon-mod-page-index.html +++ b/src/addon/mod/page/components/index/addon-mod-page-index.html @@ -11,7 +11,7 @@ - + diff --git a/src/addon/mod/quiz/components/index/addon-mod-quiz-index.html b/src/addon/mod/quiz/components/index/addon-mod-quiz-index.html index 7f7cb5b74..b6eba13f7 100644 --- a/src/addon/mod/quiz/components/index/addon-mod-quiz-index.html +++ b/src/addon/mod/quiz/components/index/addon-mod-quiz-index.html @@ -36,7 +36,9 @@ -

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

+
+

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

+
diff --git a/src/addon/mod/quiz/pages/review/review.html b/src/addon/mod/quiz/pages/review/review.html index 3dba297ac..4d398ed27 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} }}

+
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..8f57c0428 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 @@ -11,7 +11,7 @@ - + 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..959fd819f 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 @@ -26,7 +26,9 @@ -

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

+
+

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

+
diff --git a/src/addon/mod/survey/components/index/addon-mod-survey-index.html b/src/addon/mod/survey/components/index/addon-mod-survey-index.html index 3f5eec9f1..d0d7c33d3 100644 --- a/src/addon/mod/survey/components/index/addon-mod-survey-index.html +++ b/src/addon/mod/survey/components/index/addon-mod-survey-index.html @@ -12,7 +12,7 @@ - + diff --git a/src/addon/mod/url/components/index/addon-mod-url-index.html b/src/addon/mod/url/components/index/addon-mod-url-index.html index bf2d57265..2e3ae8b8b 100644 --- a/src/addon/mod/url/components/index/addon-mod-url-index.html +++ b/src/addon/mod/url/components/index/addon-mod-url-index.html @@ -31,11 +31,11 @@

{{ 'addon.mod_url.pointingtourl' | translate }}

{{ url }}

-
+ diff --git a/src/addon/mod/wiki/components/index/index.scss b/src/addon/mod/wiki/components/index/index.scss index 3a0249f11..e7edc2e28 100644 --- a/src/addon/mod/wiki/components/index/index.scss +++ b/src/addon/mod/wiki/components/index/index.scss @@ -13,6 +13,7 @@ ion-app.app-root addon-mod-wiki-index { .addon-mod_wiki-page-content { background-color: $white; border-top: 1px solid $gray; + @include safe-area-padding-horizontal(0px, 0px); @include darkmode() { background-color: $black; } diff --git a/src/app/app.ios.scss b/src/app/app.ios.scss index d90e3da7b..76b7d379d 100644 --- a/src/app/app.ios.scss +++ b/src/app/app.ios.scss @@ -37,7 +37,6 @@ 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 ; @@ -48,10 +47,10 @@ ion-app.app-root.ios { } &[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); } } } @@ -121,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 db1666048..a1efde285 100644 --- a/src/app/app.md.scss +++ b/src/app/app.md.scss @@ -43,10 +43,10 @@ ion-app.app-root.md { } &[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.scss b/src/app/app.scss index a43d1efee..8f49c0974 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -738,13 +738,13 @@ ion-app.app-root { } &[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; @@ -786,17 +786,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 { @@ -852,6 +842,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); @@ -1031,6 +1023,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 { @@ -1095,11 +1095,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 { @@ -1112,31 +1107,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; } @@ -1239,3 +1209,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 127e273b0..97a8317ae 100644 --- a/src/app/app.wp.scss +++ b/src/app/app.wp.scss @@ -40,10 +40,10 @@ ion-app.app-root.wp { } &[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/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/loading/loading.scss b/src/components/loading/loading.scss index 8b4136eea..d4c5916ba 100644 --- a/src/components/loading/loading.scss +++ b/src/components/loading/loading.scss @@ -20,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 > *, + > .core-loading-content-loading > * { + @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..ad4c61032 100644 --- a/src/components/loading/loading.ts +++ b/src/components/loading/loading.ts @@ -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/split-view/split-view.scss b/src/components/split-view/split-view.scss index 4d1fe88aa..355e1ef4e 100644 --- a/src/components/split-view/split-view.scss +++ b/src/components/split-view/split-view.scss @@ -17,6 +17,11 @@ 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 { @@ -27,12 +32,6 @@ ion-app.app-root core-split-view { background-color: $black; } } - - .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); - } } ion-header { display: none; @@ -41,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/core/block/components/block/block.scss b/src/core/block/components/block/block.scss index 2ab0c8f20..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 { 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/course/components/format/core-course-format.html b/src/core/course/components/format/core-course-format.html index 40bcd52fe..7013b69f5 100644 --- a/src/core/course/components/format/core-course-format.html +++ b/src/core/course/components/format/core-course-format.html @@ -55,7 +55,7 @@
- + ').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 all the H5P data from the DB of a certain site. * @@ -263,11 +563,56 @@ export class CoreH5PProvider { return Promise.all([ db.deleteRecords(this.CONTENT_TABLE), db.deleteRecords(this.LIBRARIES_TABLE), - db.deleteRecords(this.LIBRARY_DEPENDENCIES_TABLE) + db.deleteRecords(this.LIBRARY_DEPENDENCIES_TABLE), + db.deleteRecords(this.CONTENTS_LIBRARIES_TABLE) ]); }); } + /** + * Delete cached assets from DB and filesystem. + * + * @param libraryId Library identifier. + * @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 deleteCachedAssets(libraryId: number, folderName: string, siteId?: string): Promise { + + return this.sitesProvider.getSite(siteId).then((site) => { + const db = site.getDb(), + cachedAssetsFolder = this.getCachedAssetsFolderPath(folderName, site.getId()); + + // 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); + + ['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. + })); + }); + }); + + // Also, delete the index.html file. + promises.push(this.fileProvider.removeFile(this.getContentIndexPath(folderName, site.getId())).catch(() => { + // Ignore errors. + })); + + return Promise.all(promises).then(() => { + return db.deleteRecordsList(this.LIBRARIES_CACHEDASSETS_TABLE, 'hash', hashes); + }); + }); + }); + } + /** * Delete content data from DB. * @@ -276,9 +621,17 @@ export class CoreH5PProvider { * @return Promise resolved when done. */ deleteContentData(id: number, siteId?: string): Promise { - return this.sitesProvider.getSiteDb(siteId).then((db) => { + 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); } /** @@ -319,6 +672,19 @@ export class CoreH5PProvider { 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. @@ -350,7 +716,7 @@ export class CoreH5PProvider { const content: any = {}; // Save the libraries that were processed. - return this.saveLibraries(data.librariesJsonData, siteId).then(() => { + return this.saveLibraries(data.librariesJsonData, folderName, siteId).then(() => { // Now treat contents. // Find main library version @@ -368,7 +734,7 @@ export class CoreH5PProvider { // Save the content data in DB. content.params = JSON.stringify(data.contentJsonData); - return this.saveContentData(content, folderName, siteId); + return this.saveContentData(content, folderName, fileUrl, siteId); }).then(() => { // Save the content files in their right place. const contentPath = this.textUtils.concatenatePaths(destFolder, 'content'); @@ -386,9 +752,374 @@ export class CoreH5PProvider { return this.fileProvider.removeDir(destFolder).catch(() => { // Ignore errors, it will be deleted eventually. }); + }).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); + }); + }); + }); + }); + } + + /** + * 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 dependencies = {}, // In web, dependencies are built by the validator. + params = { + library: this.libraryToString(content.library), + params: this.textUtils.parseJSON(content.params, false) + }; + + if (!params.params) { + return null; + } + + // Get the main library data. + return this.loadLibrary(content.library.name, content.library.majorVersion, content.library.minorVersion, siteId) + .then((library) => { + + library.semantics = this.textUtils.parseJSON(library.semantics, ''); + + const depKey = 'preloaded-' + library.machineName; + let nextWeight; + + if (!dependencies[depKey]) { + dependencies[depKey] = { + library: library, + type: 'preloaded' + }; + } + + // Get the whole library dependency tree. + return this.findLibraryDependencies(dependencies, library, 1, false, siteId).then((weight) => { + nextWeight = weight; + dependencies[depKey].weight = nextWeight++; + + // Handle addons. + return this.loadAddons(siteId); + }).then((addons) => { + // Get the dependencies of all the addons. Use a chain of promises to calculate the weight properly. + let promise = Promise.resolve(); + + addons.forEach((addon) => { + const addTo = this.textUtils.parseJSON(addon.addTo, null); + + 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)) { + + const addonDepKey = 'preloaded-' + addon.machineName; + dependencies[addonDepKey] = { + library: addon, + type: 'preloaded' + }; + + promise = promise.then(() => { + return this.findLibraryDependencies(dependencies, addon, nextWeight).then((weight) => { + nextWeight = weight; + dependencies[addonDepKey].weight = nextWeight++; + }); + }); + + break; + } + } + } }); - // @todo: Load content? It's done in the player construct. + return promise; + }).then(() => { + // Update content dependencies. + content.dependencies = dependencies; + + 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) => { + const dependencyKey = type + '-' + dependency.machineName; + if (dependencies[dependencyKey]) { + return; // Skip, already have this. + } + + promise = promise.then(() => { + // 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; + }); + } + + /** + * 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(); + + return this.getContentFolderNameByUrl(fileUrl, site.getId()).then((folderName) => { + + return db.getRecord(this.CONTENT_TABLE, {foldername: folderName}); }); }); } @@ -401,7 +1132,339 @@ export class CoreH5PProvider { * @return Folder path. */ getContentFolderPath(folderName: string, siteId: string): string { - return this.textUtils.concatenatePaths(this.fileProvider.getSiteFolder(siteId), 'h5p/packages/' + folderName + '/content'); + return this.textUtils.concatenatePaths(this.getExternalH5PFolderPath(siteId), 'packages/' + folderName + '/content'); + } + + /** + * Get the content index file. + * + * @param fileUrl URL of the H5P package. + * @param siteId The site ID. If not defined, current site. + * @return Promise resolved with the file URL if exists, rejected otherwise. + */ + getContentIndexFileUrl(fileUrl: 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(); + }); + } + + /** + * 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, 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 { + const displayOptions = this.getDisplayOptionsAsObject(disable); + + if (this.getOption(CoreH5PProvider.DISPLAY_OPTION_FRAME, true) == false) { + displayOptions[CoreH5PProvider.DISPLAY_OPTION_FRAME] = false; + } else { + displayOptions[CoreH5PProvider.DISPLAY_OPTION_DOWNLOAD] = this.setDisplayOptionOverrides( + CoreH5PProvider.DISPLAY_OPTION_DOWNLOAD, CoreH5PPermission.DOWNLOAD_H5P, id, + displayOptions[CoreH5PProvider.DISPLAY_OPTION_DOWNLOAD]); + + 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; + } + + /** + * 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'); } /** @@ -514,6 +1577,19 @@ export class CoreH5PProvider { 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}); + }); + } + /** * Get a library ID. If not found, return null. * @@ -551,7 +1627,7 @@ export class CoreH5PProvider { * @return Folder path. */ getLibrariesFolderPath(siteId: string): string { - return this.textUtils.concatenatePaths(this.fileProvider.getSiteFolder(siteId), 'h5p/lib'); + return this.textUtils.concatenatePaths(this.getExternalH5PFolderPath(siteId), 'libraries'); } /** @@ -570,6 +1646,44 @@ export class CoreH5PProvider { 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 displayoptions DB field. + return 2; // CONTROLLED_BY_AUTHOR_DEFAULT_OFF. + } + + /** + * Resizing script for settings. + * + * @return The HTML code with the resize script. + */ + protected getResizeCode(): string { + // @todo return ''; + return ''; + } + + /** + * 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. * @@ -636,6 +1750,17 @@ export class CoreH5PProvider { 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 { + return true; + } + /** * Invalidates all trusted H5P file WS calls. * @@ -673,6 +1798,196 @@ export class CoreH5PProvider { 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(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 { + 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) => { + + // 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, + // The embedtype will be always set to 'iframe' to prevent conflicts with JS and CSS. + embedType: 'iframe', + disable: contentData.displayoptions, + folderName: contentData.foldername, + title: libData.title, + slug: this.h5pUtils.slugify(libData.title) + '-' + contentData.id, + filtered: contentData.filtered, + libraryMajorVersion: libData.majorversion, + libraryMinorVersion: libData.minorversion, + metadata: { + license: 'U' // Stop "invalid selected option in select" for old content without license chosen. + }, + 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; + }); + }); + }); + } + /** * 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. @@ -727,14 +2042,44 @@ export class CoreH5PProvider { }); } + /** + * 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 siteId The site ID. + * @return Promise resolved when done. + */ + protected saveCachedAssets(hash: string, dependencies: {[machineName: string]: CoreH5PContentDependencyData}, + 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 + }; + + 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, siteId?: string): Promise { + protected saveContentData(content: any, folderName: string, fileUrl: string, siteId?: string): Promise { // Save in DB. return this.sitesProvider.getSiteDb(siteId).then((db) => { @@ -744,7 +2089,8 @@ export class CoreH5PProvider { mainlibraryid: content.library.libraryId, timemodified: Date.now(), filtered: null, - foldername: folderName + foldername: folderName, + fileurl: fileUrl }; if (typeof content.id != 'undefined') { @@ -791,10 +2137,13 @@ export class CoreH5PProvider { * 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, siteId?: string): Promise { + 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. @@ -839,7 +2188,10 @@ export class CoreH5PProvider { }); }); }).then(() => { - // @todo: Remove cached asses that use this library. + // Remove cached assets that use this library. + if (this.aggregateAssets && typeof libraryData.libraryId != 'undefined') { + return this.deleteCachedAssets(libraryData.libraryId, folderName, siteId); + } }); })); } @@ -1002,6 +2354,77 @@ export class CoreH5PProvider { }); } + /** + * 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. * @@ -1016,8 +2439,60 @@ export class CoreH5PProvider { 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. */ @@ -1036,6 +2511,14 @@ export type CoreH5PGetTrustedH5PFileResult = { 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. */ @@ -1045,11 +2528,109 @@ export type CoreH5PContentDBData = { mainlibraryid: number; // The library we first instantiate for this node. displayoptions: number; // H5P Button display options. 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; // 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?: string; // Plugin configuration data. +}; + /** * Library data stored in DB. */ diff --git a/src/core/h5p/providers/utils.ts b/src/core/h5p/providers/utils.ts index 3a36071cb..9c568acac 100644 --- a/src/core/h5p/providers/utils.ts +++ b/src/core/h5p/providers/utils.ts @@ -13,6 +13,10 @@ // 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. @@ -20,9 +24,120 @@ import { Injectable } from '@angular/core'; @Injectable() export class CoreH5PUtilsProvider { - constructor() { - // Nothing to do. - } + // 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. @@ -43,6 +158,161 @@ export class CoreH5PUtilsProvider { 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'), + }; + } + /** * Convert list of library parameter values to csv. * @@ -68,4 +338,71 @@ export class CoreH5PUtilsProvider { 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/providers/file.ts b/src/providers/file.ts index dd48837c3..915ac5a36 100644 --- a/src/providers/file.ts +++ b/src/providers/file.ts @@ -1239,4 +1239,19 @@ export class CoreFileProvider { 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; + } } From ea2aa48d77202d5171f9cbb3742cf18ecc53e246 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Mon, 25 Nov 2019 09:19:26 +0100 Subject: [PATCH 182/257] MOBILE-2235 h5p: Include resizer scripts --- src/core/filter/providers/delegate.ts | 1 + src/core/h5p/assets/moodle/js/embed.js | 155 ++++++++++++++++++ .../h5p-player/core-h5p-player.html | 3 +- .../h5p/components/h5p-player/h5p-player.ts | 13 ++ src/core/h5p/providers/h5p.ts | 22 ++- src/providers/utils/dom.ts | 2 +- 6 files changed, 188 insertions(+), 8 deletions(-) create mode 100644 src/core/h5p/assets/moodle/js/embed.js diff --git a/src/core/filter/providers/delegate.ts b/src/core/filter/providers/delegate.ts index d712ffb31..642a454ca 100644 --- a/src/core/filter/providers/delegate.ts +++ b/src/core/filter/providers/delegate.ts @@ -188,6 +188,7 @@ export class CoreFilterDelegate extends CoreDelegate { } promise = promise.then(() => { + return Promise.resolve(this.executeFunctionOnEnabled(filter.filter, 'handleHtml', [container, filter, options, viewContainerRef, component, componentId, siteId])).catch((error) => { this.logger.error('Error handling HTML' + filter.filter, error); diff --git a/src/core/h5p/assets/moodle/js/embed.js b/src/core/h5p/assets/moodle/js/embed.js new file mode 100644 index 000000000..6135db2e5 --- /dev/null +++ b/src/core/h5p/assets/moodle/js/embed.js @@ -0,0 +1,155 @@ +// This file is part of Moodle - http://moodle.org/ +// +// Moodle is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// Moodle is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with Moodle. If not, see . + +/* global H5PEmbedCommunicator:true */ +/** + * When embedded the communicator helps talk to the parent page. + * This is a copy of the H5P.communicator, which we need to communicate in this context + * + * @type {H5PEmbedCommunicator} + * @module core_h5p + * @copyright 2019 Joubel AS + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +H5PEmbedCommunicator = (function() { + /** + * @class + * @private + */ + function Communicator() { + var self = this; + + // Maps actions to functions. + var actionHandlers = {}; + + // Register message listener. + window.addEventListener('message', function receiveMessage(event) { + if (window.parent !== event.source || event.data.context !== 'h5p') { + return; // Only handle messages from parent and in the correct context. + } + + if (actionHandlers[event.data.action] !== undefined) { + actionHandlers[event.data.action](event.data); + } + }, false); + + /** + * Register action listener. + * + * @param {string} action What you are waiting for + * @param {function} handler What you want done + */ + self.on = function(action, handler) { + actionHandlers[action] = handler; + }; + + /** + * Send a message to the all mighty father. + * + * @param {string} action + * @param {Object} [data] payload + */ + self.send = function(action, data) { + if (data === undefined) { + data = {}; + } + data.context = 'h5p'; + data.action = action; + + // Parent origin can be anything. + window.parent.postMessage(data, '*'); + }; + } + + return (window.postMessage && window.addEventListener ? new Communicator() : undefined); +})(); + +document.onreadystatechange = function() { + // Wait for instances to be initialize. + if (document.readyState !== 'complete') { + return; + } + + // Check for H5P iFrame. + var iFrame = document.querySelector('.h5p-iframe'); + if (!iFrame || !iFrame.contentWindow) { + return; + } + var H5P = iFrame.contentWindow.H5P; + + // Check for H5P instances. + if (!H5P || !H5P.instances || !H5P.instances[0]) { + return; + } + + var resizeDelay; + var instance = H5P.instances[0]; + var parentIsFriendly = false; + + // Handle that the resizer is loaded after the iframe. + H5PEmbedCommunicator.on('ready', function() { + H5PEmbedCommunicator.send('hello'); + }); + + // Handle hello message from our parent window. + H5PEmbedCommunicator.on('hello', function() { + // Initial setup/handshake is done. + parentIsFriendly = true; + + // Hide scrollbars for correct size. + iFrame.contentDocument.body.style.overflow = 'hidden'; + + document.body.classList.add('h5p-resizing'); + + // Content need to be resized to fit the new iframe size. + H5P.trigger(instance, 'resize'); + }); + + // When resize has been prepared tell parent window to resize. + H5PEmbedCommunicator.on('resizePrepared', function() { + H5PEmbedCommunicator.send('resize', { + scrollHeight: iFrame.contentDocument.body.scrollHeight + }); + }); + + H5PEmbedCommunicator.on('resize', function() { + H5P.trigger(instance, 'resize'); + }); + + H5P.on(instance, 'resize', function() { + if (H5P.isFullscreen) { + return; // Skip iframe resize. + } + + // Use a delay to make sure iframe is resized to the correct size. + clearTimeout(resizeDelay); + resizeDelay = setTimeout(function() { + // Only resize if the iframe can be resized. + if (parentIsFriendly) { + H5PEmbedCommunicator.send('prepareResize', + { + scrollHeight: iFrame.contentDocument.body.scrollHeight, + clientHeight: iFrame.contentDocument.body.clientHeight + } + ); + } else { + H5PEmbedCommunicator.send('hello'); + } + }, 0); + }); + + // Trigger initial resize for instance. + H5P.trigger(instance, 'resize'); +}; diff --git a/src/core/h5p/components/h5p-player/core-h5p-player.html b/src/core/h5p/components/h5p-player/core-h5p-player.html index 2fb1e9989..3cfd58ccb 100644 --- a/src/core/h5p/components/h5p-player/core-h5p-player.html +++ b/src/core/h5p/components/h5p-player/core-h5p-player.html @@ -9,4 +9,5 @@ - + + diff --git a/src/core/h5p/components/h5p-player/h5p-player.ts b/src/core/h5p/components/h5p-player/h5p-player.ts index 0627e89d6..ccc5b5325 100644 --- a/src/core/h5p/components/h5p-player/h5p-player.ts +++ b/src/core/h5p/components/h5p-player/h5p-player.ts @@ -99,6 +99,8 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy { let promise; + this.addResizerScript(); + if (this.canDownload && (this.state == CoreConstants.DOWNLOADED || this.state == CoreConstants.OUTDATED)) { // Package is downloaded, use the local URL. promise = this.h5pProvider.getContentIndexFileUrl(this.urlParams.url).catch((error) => { @@ -154,6 +156,17 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy { }); } + /** + * Add the resizer script if it hasn't been added already. + */ + protected addResizerScript(): void { + const script = document.createElement('script'); + script.id = 'core-h5p-resizer-script'; + script.type = 'text/javascript'; + script.src = this.h5pProvider.getResizerScriptUrl(); + document.head.appendChild(script); + } + /** * Check if the package can be downloaded. */ diff --git a/src/core/h5p/providers/h5p.ts b/src/core/h5p/providers/h5p.ts index 57d674897..d92f398a7 100644 --- a/src/core/h5p/providers/h5p.ts +++ b/src/core/h5p/providers/h5p.ts @@ -522,20 +522,22 @@ export class CoreH5PProvider { let html = '' + content.title + '' + ''; - // @todo: Load the embed.js to allow communication with the parent window. - // $PAGE->requires->js(new moodle_url('/h5p/js/embed.js')); - // Include the required CSS. result.cssRequires.forEach((cssUrl) => { html += ''; }); // Add the settings. - html += ''; + html += ''; html += ''; // Include the required JS at the beginning of the body, like Moodle web does. + // Load the embed.js to allow communication with the parent window. + html += ''; + result.jsRequires.forEach((jsUrl) => { html += ''; }); @@ -1664,8 +1666,16 @@ export class CoreH5PProvider { * @return The HTML code with the resize script. */ protected getResizeCode(): string { - // @todo return ''; - return ''; + return ''; + } + + /** + * Get the URL to the resizer script. + * + * @return URL. + */ + getResizerScriptUrl(): string { + return this.textUtils.concatenatePaths(this.getCoreH5PPath(), 'js/h5p-resizer.js'); } /** diff --git a/src/providers/utils/dom.ts b/src/providers/utils/dom.ts index 37bb85907..18dec5369 100644 --- a/src/providers/utils/dom.ts +++ b/src/providers/utils/dom.ts @@ -385,7 +385,7 @@ export class CoreDomUtilsProvider { * @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; } From d5e12fb1367bfc88c32985d19fc8e57f7e89f7c4 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Mon, 25 Nov 2019 15:05:49 +0100 Subject: [PATCH 183/257] MOBILE-2235 h5p: Delete index files when updating libs --- .../h5p/components/h5p-player/h5p-player.ts | 19 +++- src/core/h5p/providers/h5p.ts | 87 ++++++++++++++++--- src/core/h5p/providers/pluginfile-handler.ts | 1 + src/providers/plugin-file-delegate.ts | 1 + 4 files changed, 92 insertions(+), 16 deletions(-) diff --git a/src/core/h5p/components/h5p-player/h5p-player.ts b/src/core/h5p/components/h5p-player/h5p-player.ts index ccc5b5325..b4058b4dd 100644 --- a/src/core/h5p/components/h5p-player/h5p-player.ts +++ b/src/core/h5p/components/h5p-player/h5p-player.ts @@ -15,6 +15,7 @@ import { Component, Input, ElementRef, OnInit, OnDestroy, OnChanges, SimpleChange } from '@angular/core'; import { CoreAppProvider } from '@providers/app'; import { CoreEventsProvider } from '@providers/events'; +import { CoreFileProvider } from '@providers/file'; import { CoreFilepoolProvider } from '@providers/filepool'; import { CoreLoggerProvider } from '@providers/logger'; import { CoreSitesProvider } from '@providers/sites'; @@ -62,7 +63,8 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy { protected eventsProvider: CoreEventsProvider, protected appProvider: CoreAppProvider, protected domUtils: CoreDomUtilsProvider, - protected pluginFileDelegate: CorePluginFileDelegate) { + protected pluginFileDelegate: CorePluginFileDelegate, + protected fileProvider: CoreFileProvider) { this.logger = loggerProvider.getInstance('CoreH5PPlayerComponent'); this.siteId = sitesProvider.getCurrentSiteId(); @@ -103,8 +105,19 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy { if (this.canDownload && (this.state == CoreConstants.DOWNLOADED || this.state == CoreConstants.OUTDATED)) { // Package is downloaded, use the local URL. - promise = this.h5pProvider.getContentIndexFileUrl(this.urlParams.url).catch((error) => { - // It seems there was something wrong when creating the index file. Delete the package? + promise = this.h5pProvider.getContentIndexFileUrl(this.urlParams.url, this.siteId).catch(() => { + + // Index file doesn't exist, probably deleted because a lib was updated. Try to create it again. + return this.filepoolProvider.getInternalUrlByUrl(this.siteId, this.urlParams.url).then((path) => { + return this.fileProvider.getFile(path); + }).then((file) => { + return this.h5pProvider.extractH5PFile(this.urlParams.url, file, this.siteId); + }).then(() => { + // File treated. Try to get the index file URL again. + return this.h5pProvider.getContentIndexFileUrl(this.urlParams.url, this.siteId); + }); + }).catch((error) => { + // Still failing. Delete the H5P package? this.logger.error('Error loading downloaded index:', error, this.src); }); } else { diff --git a/src/core/h5p/providers/h5p.ts b/src/core/h5p/providers/h5p.ts index d92f398a7..dde10e3d3 100644 --- a/src/core/h5p/providers/h5p.ts +++ b/src/core/h5p/providers/h5p.ts @@ -25,6 +25,7 @@ import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype'; import { CoreUrlUtilsProvider } from '@providers/utils/url'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreH5PUtilsProvider } from './utils'; +import { FileEntry } from '@ionic-native/file'; /** * Service to provide H5P functionalities. @@ -279,6 +280,11 @@ export class CoreH5PProvider { name: 'hash', type: 'TEXT', notNull: true + }, + { + name: 'foldername', + type: 'TEXT', + notNull: true } ] } @@ -566,7 +572,8 @@ export class CoreH5PProvider { db.deleteRecords(this.CONTENT_TABLE), db.deleteRecords(this.LIBRARIES_TABLE), db.deleteRecords(this.LIBRARY_DEPENDENCIES_TABLE), - db.deleteRecords(this.CONTENTS_LIBRARIES_TABLE) + db.deleteRecords(this.CONTENTS_LIBRARIES_TABLE), + db.deleteRecords(this.LIBRARIES_CACHEDASSETS_TABLE) ]); }); } @@ -575,15 +582,13 @@ export class CoreH5PProvider { * Delete cached assets from DB and filesystem. * * @param libraryId Library identifier. - * @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 deleteCachedAssets(libraryId: number, folderName: string, siteId?: string): Promise { + protected deleteCachedAssets(libraryId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - const db = site.getDb(), - cachedAssetsFolder = this.getCachedAssetsFolderPath(folderName, site.getId()); + const db = site.getDb(); // Get all the hashes that use this library. return db.getRecords(this.LIBRARIES_CACHEDASSETS_TABLE, {libraryid: libraryId}).then((entries) => { @@ -594,6 +599,8 @@ export class CoreH5PProvider { 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); @@ -603,11 +610,6 @@ export class CoreH5PProvider { }); }); - // Also, delete the index.html file. - promises.push(this.fileProvider.removeFile(this.getContentIndexPath(folderName, site.getId())).catch(() => { - // Ignore errors. - })); - return Promise.all(promises).then(() => { return db.deleteRecordsList(this.LIBRARIES_CACHEDASSETS_TABLE, 'hash', hashes); }); @@ -636,6 +638,45 @@ export class CoreH5PProvider { return Promise.all(promises); } + /** + * 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. * @@ -1796,6 +1837,27 @@ export class CoreH5PProvider { }); } + /** + * 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}. * @@ -2198,9 +2260,8 @@ export class CoreH5PProvider { }); }); }).then(() => { - // Remove cached assets that use this library. - if (this.aggregateAssets && typeof libraryData.libraryId != 'undefined') { - return this.deleteCachedAssets(libraryData.libraryId, folderName, siteId); + if (typeof libraryData.libraryId != 'undefined') { + return this.libraryInstalled(libraryData.libraryId, siteId); } }); })); diff --git a/src/core/h5p/providers/pluginfile-handler.ts b/src/core/h5p/providers/pluginfile-handler.ts index a7787a3f0..fed35e88e 100644 --- a/src/core/h5p/providers/pluginfile-handler.ts +++ b/src/core/h5p/providers/pluginfile-handler.ts @@ -21,6 +21,7 @@ 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. diff --git a/src/providers/plugin-file-delegate.ts b/src/providers/plugin-file-delegate.ts index b1c33e1f0..6ff96e5c3 100644 --- a/src/providers/plugin-file-delegate.ts +++ b/src/providers/plugin-file-delegate.ts @@ -15,6 +15,7 @@ import { Injectable } from '@angular/core'; import { CoreLoggerProvider } from './logger'; import { CoreWSExternalFile } from '@providers/ws'; +import { FileEntry } from '@ionic-native/file'; /** * Interface that all plugin file handlers must implement. From ad716ce07ef6845831bd3d85dc71edad3020b668 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Mon, 25 Nov 2019 16:08:58 +0100 Subject: [PATCH 184/257] MOBILE-2235 h5p: Delete content data if original file is deleted --- src/core/h5p/providers/h5p.ts | 36 ++++++++++++++++++++ src/core/h5p/providers/pluginfile-handler.ts | 13 +++++++ src/providers/filepool.ts | 21 +++++++++++- src/providers/plugin-file-delegate.ts | 30 +++++++++++++++- 4 files changed, 98 insertions(+), 2 deletions(-) diff --git a/src/core/h5p/providers/h5p.ts b/src/core/h5p/providers/h5p.ts index dde10e3d3..6dc9f3814 100644 --- a/src/core/h5p/providers/h5p.ts +++ b/src/core/h5p/providers/h5p.ts @@ -617,6 +617,27 @@ export class CoreH5PProvider { }); } + /** + * 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. * @@ -638,6 +659,17 @@ export class CoreH5PProvider { 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. * @@ -1160,9 +1192,13 @@ export class CoreH5PProvider { 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}); }); }); } diff --git a/src/core/h5p/providers/pluginfile-handler.ts b/src/core/h5p/providers/pluginfile-handler.ts index fed35e88e..71b82053d 100644 --- a/src/core/h5p/providers/pluginfile-handler.ts +++ b/src/core/h5p/providers/pluginfile-handler.ts @@ -48,6 +48,19 @@ export class CoreH5PPluginFileHandler implements CorePluginFileHandler { return this.h5pProvider.getTrustedH5PFile(file.fileurl, {}, false, siteId); } + /** + * 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); + } + /** * Given an HTML element, get the URLs of the files that should be downloaded and weren't treated by * CoreDomUtilsProvider.extractDownloadableFilesFromHtml. diff --git a/src/providers/filepool.ts b/src/providers/filepool.ts index ddea2ddbb..5ad03bead 100644 --- a/src/providers/filepool.ts +++ b/src/providers/filepool.ts @@ -2646,7 +2646,22 @@ export class CoreFilepoolProvider { 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. @@ -2668,6 +2683,10 @@ export class CoreFilepoolProvider { return Promise.all(promises).then(() => { this.notifyFileDeleted(siteId, fileId); + + return this.pluginFileDelegate.fileDeleted(fileUrl, path, siteId).catch((error) => { + // Ignore errors. + }); }); }); }); diff --git a/src/providers/plugin-file-delegate.ts b/src/providers/plugin-file-delegate.ts index 6ff96e5c3..01aea603e 100644 --- a/src/providers/plugin-file-delegate.ts +++ b/src/providers/plugin-file-delegate.ts @@ -57,6 +57,16 @@ export interface CorePluginFileHandler { */ canDownloadFile?(file: CoreWSExternalFile, siteId?: string): Promise; + /** + * 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; + /** * Given an HTML element, get the URLs of the files that should be downloaded and weren't treated by * CoreDomUtilsProvider.extractDownloadableFilesFromHtml. @@ -139,6 +149,24 @@ export class CorePluginFileDelegate { return Promise.resolve(file); } + /** + * 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 { + const handler = this.getHandlerForFile({fileurl: fileUrl}); + + if (handler && handler.fileDeleted) { + return handler.fileDeleted(fileUrl, path, siteId); + } + + return Promise.resolve(); + } + /** * Get the handler for a certain pluginfile url. * @@ -310,7 +338,7 @@ export class CorePluginFileDelegate { treatDownloadedFile(fileUrl: string, file: FileEntry, siteId?: string): Promise { const handler = this.getHandlerForFile({fileurl: fileUrl}); - if (handler && handler.getFileSize) { + if (handler && handler.treatDownloadedFile) { return handler.treatDownloadedFile(fileUrl, file, siteId); } From 5903975e8cdb6eb63dbaecf48c1cebfeeff7fc0e Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 26 Nov 2019 08:49:59 +0100 Subject: [PATCH 185/257] MOBILE-2235 h5p: Handle display options --- .../h5p/assets/moodle/js/displayoptions.js | 35 ++++++ .../h5p/components/h5p-player/h5p-player.ts | 4 +- src/core/h5p/providers/h5p.ts | 107 +++++++++++++----- src/providers/utils/url.ts | 10 +- 4 files changed, 124 insertions(+), 32 deletions(-) create mode 100644 src/core/h5p/assets/moodle/js/displayoptions.js diff --git a/src/core/h5p/assets/moodle/js/displayoptions.js b/src/core/h5p/assets/moodle/js/displayoptions.js new file mode 100644 index 000000000..59088886d --- /dev/null +++ b/src/core/h5p/assets/moodle/js/displayoptions.js @@ -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. + +/** + * Handle display options included in the URL and put them in the H5PIntegration object if it exists. + */ + +if (window.H5PIntegration && window.H5PIntegration.contents && location.search) { + var contentData = window.H5PIntegration.contents[Object.keys(window.H5PIntegration.contents)[0]]; + + if (contentData) { + contentData.displayOptions = contentData.displayOptions || {}; + + var search = location.search.replace(/^\?/, ''), + split = search.split('&'); + + split.forEach(function(param) { + var nameAndValue = param.split('='); + if (nameAndValue.length == 2) { + contentData.displayOptions[nameAndValue[0]] = nameAndValue[1] === '1' || nameAndValue[1] === 'true'; + } + }); + } +} diff --git a/src/core/h5p/components/h5p-player/h5p-player.ts b/src/core/h5p/components/h5p-player/h5p-player.ts index b4058b4dd..1a1cc7fb1 100644 --- a/src/core/h5p/components/h5p-player/h5p-player.ts +++ b/src/core/h5p/components/h5p-player/h5p-player.ts @@ -105,7 +105,7 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy { if (this.canDownload && (this.state == CoreConstants.DOWNLOADED || this.state == CoreConstants.OUTDATED)) { // Package is downloaded, use the local URL. - promise = this.h5pProvider.getContentIndexFileUrl(this.urlParams.url, this.siteId).catch(() => { + promise = this.h5pProvider.getContentIndexFileUrl(this.urlParams.url, this.urlParams, this.siteId).catch(() => { // Index file doesn't exist, probably deleted because a lib was updated. Try to create it again. return this.filepoolProvider.getInternalUrlByUrl(this.siteId, this.urlParams.url).then((path) => { @@ -114,7 +114,7 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy { return this.h5pProvider.extractH5PFile(this.urlParams.url, file, this.siteId); }).then(() => { // File treated. Try to get the index file URL again. - return this.h5pProvider.getContentIndexFileUrl(this.urlParams.url, this.siteId); + return this.h5pProvider.getContentIndexFileUrl(this.urlParams.url, this.urlParams, this.siteId); }); }).catch((error) => { // Still failing. Delete the H5P package? diff --git a/src/core/h5p/providers/h5p.ts b/src/core/h5p/providers/h5p.ts index 6dc9f3814..e6c9e76e9 100644 --- a/src/core/h5p/providers/h5p.ts +++ b/src/core/h5p/providers/h5p.ts @@ -100,7 +100,7 @@ export class CoreH5PProvider { notNull: true }, { - name: 'displayoptions', + name: 'displayoptions', // Not used right now, but we keep the field to be consistent with Moodle web. type: 'INTEGER' }, { @@ -494,10 +494,7 @@ export class CoreH5PProvider { return this.sitesProvider.getSite(siteId).then((site) => { - const disable = typeof content.disable != 'undefined' && content.disable != null ? - content.disable : CoreH5PProvider.DISABLE_NONE, - displayOptions = this.getDisplayOptionsForView(disable, id), - contentId = this.getContentId(id), + const contentId = this.getContentId(id), basePath = this.fileProvider.getBasePathInstant(), contentUrl = this.textUtils.concatenatePaths(basePath, this.getContentFolderPath(content.folderName, site.getId())); @@ -506,10 +503,10 @@ export class CoreH5PProvider { library: this.libraryToString(content.library), fullScreen: content.library.fullscreen, exportUrl: '', // We'll never display the download button, so we don't need the exportUrl. - embedCode: this.getEmbedCode(site.getURL(), h5pUrl, displayOptions[CoreH5PProvider.DISPLAY_OPTION_EMBED]), + embedCode: this.getEmbedCode(site.getURL(), h5pUrl, true), resizeCode: this.getResizeCode(), title: content.slug, - displayOptions: displayOptions, + displayOptions: {}, url: this.getEmbedUrl(site.getURL(), h5pUrl), contentUrl: contentUrl, metadata: content.metadata, @@ -537,6 +534,10 @@ export class CoreH5PProvider { html += ''; + // Add our own script to handle the display options. + html += ''; + html += ''; // Include the required JS at the beginning of the body, like Moodle web does. @@ -1218,17 +1219,26 @@ export class CoreH5PProvider { * 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, siteId?: string): Promise { + 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.validateDisplayOptions(this.getDisplayOptionsFromUrlParams(urlParams), data.id); + + return this.urlUtils.addParamsToUrl(url, options, undefined, true); + }); }); + } /** @@ -1486,25 +1496,31 @@ export class CoreH5PProvider { * @return Display options as object. */ getDisplayOptionsForView(disable: number, id: number): CoreH5PDisplayOptions { - const displayOptions = this.getDisplayOptionsAsObject(disable); + return this.validateDisplayOptions(this.getDisplayOptionsAsObject(disable), id); + } - if (this.getOption(CoreH5PProvider.DISPLAY_OPTION_FRAME, true) == false) { - displayOptions[CoreH5PProvider.DISPLAY_OPTION_FRAME] = false; - } else { - displayOptions[CoreH5PProvider.DISPLAY_OPTION_DOWNLOAD] = this.setDisplayOptionOverrides( - CoreH5PProvider.DISPLAY_OPTION_DOWNLOAD, CoreH5PPermission.DOWNLOAD_H5P, id, - displayOptions[CoreH5PProvider.DISPLAY_OPTION_DOWNLOAD]); + /** + * 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 = {}; - 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; - } + if (!params) { + return displayOptions; } - displayOptions[CoreH5PProvider.DISPLAY_OPTION_COPY] = this.hasPermission(CoreH5PPermission.COPY_H5P, id); + 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; } @@ -1734,7 +1750,7 @@ export class CoreH5PProvider { */ getOption(name: string, defaultValue: any = false): any { // For now, all them are disabled by default, so only will be rendered when defined in the displayoptions DB field. - return 2; // CONTROLLED_BY_AUTHOR_DEFAULT_OFF. + return CoreH5PDisplayOptionBehaviour.CONTROLLED_BY_AUTHOR_DEFAULT_OFF; // CONTROLLED_BY_AUTHOR_DEFAULT_OFF. } /** @@ -1845,7 +1861,8 @@ export class CoreH5PProvider { * @return Whether the user has permission to execute an action. */ hasPermission(permission: number, id: number): boolean { - return true; + // H5P capabilities have not been introduced. + return null; } /** @@ -1967,7 +1984,7 @@ export class CoreH5PProvider { params: contentData.jsoncontent, // The embedtype will be always set to 'iframe' to prevent conflicts with JS and CSS. embedType: 'iframe', - disable: contentData.displayoptions, + disable: null, folderName: contentData.foldername, title: libData.title, slug: this.h5pUtils.slugify(libData.title) + '-' + contentData.id, @@ -2193,7 +2210,7 @@ export class CoreH5PProvider { const data: any = { jsoncontent: content.params, - displayoptions: content.disable, + displayoptions: null, mainlibraryid: content.library.libraryId, timemodified: Date.now(), filtered: null, @@ -2563,6 +2580,40 @@ export class CoreH5PProvider { return db.updateRecords(this.CONTENT_TABLE, data, {id: id}); }); } + + /** + * Validate display options, updating them if needed. + * + * @param displayOptions The display options to validate. + * @param id Package ID. + */ + validateDisplayOptions(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) == false) { + 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; + } } /** @@ -2633,7 +2684,7 @@ 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. - displayoptions: number; // H5P Button display options. + displayoptions: number; // H5P Button display options. Not used right now. 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. diff --git a/src/providers/utils/url.ts b/src/providers/utils/url.ts index d2e9e0d47..c04240a6c 100644 --- a/src/providers/utils/url.ts +++ b/src/providers/utils/url.ts @@ -50,13 +50,19 @@ export class CoreUrlUtilsProvider { * @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): string { + addParamsToUrl(url: string, params?: {[key: string]: any}, anchor?: string, boolToNumber?: boolean): string { let separator = url.indexOf('?') != -1 ? '&' : '?'; for (const key in params) { - const value = params[key]; + 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') { From 3da7c99fd83be54ed1ca5b7be271963653368e5c Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 26 Nov 2019 09:42:28 +0100 Subject: [PATCH 186/257] MOBILE-2235 h5p: Download in background after play --- .../h5p/components/h5p-player/h5p-player.ts | 29 +++++++++++++++++++ src/providers/filepool.ts | 12 +++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/core/h5p/components/h5p-player/h5p-player.ts b/src/core/h5p/components/h5p-player/h5p-player.ts index 1a1cc7fb1..11a5901fc 100644 --- a/src/core/h5p/components/h5p-player/h5p-player.ts +++ b/src/core/h5p/components/h5p-player/h5p-player.ts @@ -138,6 +138,13 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy { }).finally(() => { this.loading = false; this.showPackage = true; + + if (this.canDownload && (this.state == CoreConstants.OUTDATED || this.state == CoreConstants.NOT_DOWNLOADED)) { + // Download the package in background if the size is low. + this.downloadInBg().catch((error) => { + this.logger.error('Error downloading H5P in background', error); + }); + } }); } @@ -169,6 +176,28 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy { }); } + /** + * Download the H5P in background if the size is low. + * + * @return Promise resolved when done. + */ + protected downloadInBg(): Promise { + if (this.urlParams && this.src && this.siteCanDownload && this.h5pProvider.canGetTrustedH5PFileInSite() && + this.appProvider.isOnline()) { + + // Get the file size. + return this.pluginFileDelegate.getFileSize({fileurl: this.urlParams.url}, this.siteId).then((size) => { + + if (this.filepoolProvider.shouldDownload(size)) { + // Download the file in background. + this.filepoolProvider.addToQueueByUrl(this.siteId, this.urlParams.url, this.component, this.componentId); + } + }); + } + + return Promise.resolve(); + } + /** * Add the resizer script if it hasn't been added already. */ diff --git a/src/providers/filepool.ts b/src/providers/filepool.ts index 5ad03bead..dca0d1e89 100644 --- a/src/providers/filepool.ts +++ b/src/providers/filepool.ts @@ -772,7 +772,7 @@ export class CoreFilepoolProvider { 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)) { + } else if (this.shouldDownload(size)) { return this.addToQueueByUrl(siteId, fileUrl, component, componentId, timemodified, undefined, undefined, 0, options, revision, true); } @@ -2779,6 +2779,16 @@ 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. * From 56faa66adcff8d42976ec40b3cea4c417ac9c4ce Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 28 Nov 2019 12:26:20 +0100 Subject: [PATCH 187/257] MOBILE-2235 h5p: Implement and use content validator --- scripts/langindex.json | 88 ++ src/assets/lang/en.json | 22 + src/core/h5p/classes/content-validator.ts | 1324 +++++++++++++++++ .../h5p/components/h5p-player/h5p-player.ts | 6 +- src/core/h5p/lang/en.json | 24 +- src/core/h5p/providers/h5p.ts | 291 ++-- src/core/h5p/providers/utils.ts | 21 + src/providers/utils/utils.ts | 8 +- 8 files changed, 1642 insertions(+), 142 deletions(-) create mode 100644 src/core/h5p/classes/content-validator.ts diff --git a/scripts/langindex.json b/scripts/langindex.json index f6be29269..70f4eb012 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -1552,6 +1552,94 @@ "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.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", diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index 0b7468465..5a3093332 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -1550,7 +1550,12 @@ "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)", @@ -1559,7 +1564,11 @@ "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", @@ -1570,18 +1579,24 @@ "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", @@ -1591,14 +1606,18 @@ "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:", @@ -1617,6 +1636,9 @@ "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", diff --git a/src/core/h5p/classes/content-validator.ts b/src/core/h5p/classes/content-validator.ts new file mode 100644 index 000000000..18ae59dc3 --- /dev/null +++ b/src/core/h5p/classes/content-validator.ts @@ -0,0 +1,1324 @@ +// (C) Copyright 2015 Moodle 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 { CoreTextUtilsProvider } from '@providers/utils/text'; +import { CoreUtilsProvider } from '@providers/utils/utils'; +import { CoreH5PProvider, CoreH5PLibraryData, CoreH5PLibraryAddonData, CoreH5PContentDepsTreeDependency } from '../providers/h5p'; +import { CoreH5PUtilsProvider } from '../providers/utils'; +import { TranslateService } from '@ngx-translate/core'; + +/** + * Equivalent to Moodle's H5PContentValidator, but without some of the validations. + * It's also used to build the dependency list. + */ +export class CoreH5PContentValidator { + protected static ALLOWED_STYLEABLE_TAGS = ['span', 'p', 'div', 'h1', 'h2', 'h3', 'td']; + + protected typeMap = { + text: 'validateText', + number: 'validateNumber', + boolean: 'validateBoolean', + list: 'validateList', + group: 'validateGroup', + file: 'validateFile', + image: 'validateImage', + video: 'validateVideo', + audio: 'validateAudio', + select: 'validateSelect', + library: 'validateLibrary', + }; + + protected nextWeight = 1; + protected libraries: {[libString: string]: CoreH5PLibraryData} = {}; + protected dependencies: {[key: string]: CoreH5PContentDepsTreeDependency} = {}; + protected relativePathRegExp = /^((\.\.\/){1,2})(.*content\/)?(\d+|editor)\/(.+)$/; + protected allowedHtml: {[tag: string]: any} = {}; + protected allowedStyles: RegExp[]; + protected metadataSemantics: any[]; + protected copyrightSemantics: any; + + constructor(protected h5pProvider: CoreH5PProvider, + protected h5pUtils: CoreH5PUtilsProvider, + protected textUtils: CoreTextUtilsProvider, + protected utils: CoreUtilsProvider, + protected translate: TranslateService, + protected siteId: string) { } + + /** + * Add Addon library. + * + * @param library The addon library to add. + * @return Promise resolved when done. + */ + addon(library: CoreH5PLibraryAddonData): Promise { + const depKey = 'preloaded-' + library.machineName; + + this.dependencies[depKey] = { + library: library, + type: 'preloaded' + }; + + return this.h5pProvider.findLibraryDependencies(this.dependencies, library, this.nextWeight).then((weight) => { + this.nextWeight = weight; + this.dependencies[depKey].weight = this.nextWeight++; + }); + } + + /** + * Get the flat dependency tree. + * + * @return array + */ + getDependencies(): {[key: string]: CoreH5PContentDepsTreeDependency} { + return this.dependencies; + } + + /** + * Validate metadata + * + * @param metadata Metadata. + * @return Promise resolved with metadata validated & filtered. + */ + validateMetadata(metadata: any): Promise { + const semantics = this.getMetadataSemantics(); + const group = this.utils.clone(metadata || {}); + + // Stop complaining about "invalid selected option in select" for old content without license chosen. + if (typeof group.license == 'undefined') { + group.license = 'U'; + } + + return this.validateGroup(group, {type: 'group', fields: semantics}, false); + } + + /** + * Validate given text value against text semantics. + * + * @param text Text to validate. + * @param semantics Semantics. + * @return Validated text. + */ + validateText(text: string, semantics: any): string { + if (typeof text != 'string') { + text = ''; + } + + if (semantics.tags) { + // Not testing for empty array allows us to use the 4 defaults without specifying them in semantics. + let tags = ['div', 'span', 'p', 'br'].concat(semantics.tags); + + // Add related tags for table etc. + if (tags.indexOf('table') != -1) { + tags = tags.concat(['tr', 'td', 'th', 'colgroup', 'thead', 'tbody', 'tfoot']); + } + if (tags.indexOf('b') != -1) { + tags.push('strong'); + } + if (tags.indexOf('i') != -1) { + tags.push('em'); + } + if (tags.indexOf('ul') != -1 || tags.indexOf('ol') != -1) { + tags.push('li'); + } + if (tags.indexOf('del') != -1 || tags.indexOf('strike') != -1) { + tags.push('s'); + } + + tags = this.utils.uniqueArray(tags); + + // Determine allowed style tags + const stylePatterns: RegExp[] = []; + // All styles must be start to end patterns (^...$) + if (semantics.font) { + if (semantics.font.size) { + stylePatterns.push(/^font-size: *[0-9.]+(em|px|%) *;?$/i); + } + if (semantics.font.family) { + stylePatterns.push(/^font-family: *[-a-z0-9," ]+;?$/i); + } + if (semantics.font.color) { + stylePatterns.push(/^color: *(#[a-f0-9]{3}[a-f0-9]{3}?|rgba?\([0-9, ]+\)) *;?$/i); + } + if (semantics.font.background) { + stylePatterns.push(/^background-color: *(#[a-f0-9]{3}[a-f0-9]{3}?|rgba?\([0-9, ]+\)) *;?$/i); + } + if (semantics.font.spacing) { + stylePatterns.push(/^letter-spacing: *[0-9.]+(em|px|%) *;?$/i); + } + if (semantics.font.height) { + stylePatterns.push(/^line-height: *[0-9.]+(em|px|%|) *;?$/i); + } + } + + // Alignment is allowed for all wysiwyg texts + stylePatterns.push(/^text-align: *(center|left|right);?$/i); + + // Strip invalid HTML tags. + text = this.filterXss(text, tags, stylePatterns); + } else { + // Filter text to plain text. + text = this.textUtils.escapeHTML(text); + } + + // Check if string is within allowed length. + if (typeof semantics.maxLength != 'undefined') { + text = text.substr(0, semantics.maxLength); + } + + return text; + } + + /** + * Validates content files + * + * @param contentPath The path containing content files to validate. + * @param isLibrary Whether it's a library. + * @return True if all files are valid. + */ + validateContentFiles(contentPath: string, isLibrary: boolean = false): boolean { + // Nothing to do, already checked by Moodle. + return true; + } + + /** + * Validate given value against number semantics. + * + * @param num Number to validate. + * @param semantics Semantics. + * @return Validated number. + */ + validateNumber(num: any, semantics: any): number { + // Validate that num is indeed a number. + num = Number(num); + if (isNaN(num)) { + num = 0; + } + // Check if number is within valid bounds. Move within bounds if not. + if (typeof semantics.min != 'undefined' && num < semantics.min) { + num = semantics.min; + } + if (typeof semantics.max != 'undefined' && num > semantics.max) { + num = semantics.max; + } + // Check if number is within allowed bounds even if step value is set. + if (typeof semantics.step != 'undefined') { + const testNumber = num - (typeof semantics.min != 'undefined' ? semantics.min : 0), + rest = testNumber % semantics.step; + if (rest !== 0) { + num -= rest; + } + } + // Check if number has proper number of decimals. + if (typeof semantics.decimals != 'undefined') { + num = num.toFixed(semantics.decimals); + } + + return num; + } + + /** + * Validate given value against boolean semantics. + * + * @param bool Boolean to check. + * @return Validated bool. + */ + validateBoolean(bool: boolean): boolean { + return !!bool; + } + + /** + * Validate select values. + * + * @param select Values to validate. + * @param semantics Semantics. + * @return Validated select. + */ + validateSelect(select: any, semantics: any): any { + const optional = semantics.optional, + options = {}; + let strict = false; + + if (semantics.options && semantics.options.length) { + // We have a strict set of options to choose from. + strict = true; + + semantics.options.forEach((option) => { + // Support optgroup - just flatten options into one. + if (option.type == 'optgroup') { + option.options.forEach((subOption) => { + options[subOption.value] = true; + }); + } else if (option.value) { + options[option.value] = true; + } + }); + } + + if (semantics.multiple) { + // Multi-choice generates array of values. Test each one against valid options, if we are strict. + for (const key in select) { + const value = select[key]; + + if (strict && !optional && !options[value]) { + delete select[key]; + } else { + select[key] = this.textUtils.escapeHTML(value); + } + } + } else { + // Single mode. If we get an array in here, we chop off the first element and use that instead. + if (Array.isArray(select)) { + select = select[0]; + } + + if (strict && !optional && !options[select]) { + select = semantics.options[0].value; + } + select = this.textUtils.escapeHTML(select); + } + + return select; + } + + /** + * Validate given list value against list semantics. + * Will recurse into validating each item in the list according to the type. + * + * @param list List to validate. + * @param semantics Semantics. + * @return Validated list. + */ + validateList(list: any, semantics: any): Promise { + const field = semantics.field, + fn = this[this.typeMap[field.type]].bind(this); + let promise = Promise.resolve(), // Use a chain of promises so the order is kept. + keys = Object.keys(list); + + // Check that list is not longer than allowed length. + if (typeof semantics.max != 'undefined') { + keys = keys.slice(0, semantics.max); + } + + // Validate each element in list. + keys.forEach((key) => { + if (isNaN(parseInt(key, 10))) { + // It's an object and the key isn't an integer. Delete it. + delete list[key]; + } else { + promise = promise.then(() => { + return Promise.resolve(fn(list[key], field)).then((val) => { + if (val === null) { + list.splice(key, 1); + } else { + list[key] = val; + } + }); + }); + } + }); + + return promise.then(() => { + + if (!Array.isArray(list)) { + list = this.utils.objectToArray(list); + } + + if (!list.length) { + return null; + } + + return list; + }); + } + + /** + * Validate a file like object, such as video, image, audio and file. + * + * @param file File to validate. + * @param semantics Semantics. + * @param typeValidKeys List of valid keys. + * @return Promise resolved with the validated file. + */ + protected validateFilelike(file: any, semantics: any, typeValidKeys: string[] = []): Promise { + // Do not allow to use files from other content folders. + const matches = file.path.match(this.relativePathRegExp); + if (matches && matches.length) { + file.path = matches[5]; + } + + // Remove temporary files suffix. + if (file.path.substr(-4, 4) === '#tmp') { + file.path = file.path.substr(0, file.path.length - 4); + } + + // Make sure path and mime does not have any special chars + file.path = this.textUtils.escapeHTML(file.path); + if (file.mime) { + file.mime = this.textUtils.escapeHTML(file.mime); + } + + // Remove attributes that should not exist, they may contain JSON escape code. + let validKeys = ['path', 'mime', 'copyright'].concat(typeValidKeys); + if (semantics.extraAttributes) { + validKeys = validKeys.concat(semantics.extraAttributes); + } + validKeys = this.utils.uniqueArray(validKeys); + + this.filterParams(file, validKeys); + + if (typeof file.width != 'undefined') { + file.width = parseInt(file.width, 10); + } + + if (typeof file.height != 'undefined') { + file.height = parseInt(file.height, 10); + } + + if (file.codecs) { + file.codecs = this.textUtils.escapeHTML(file.codecs); + } + + if (typeof file.bitrate != 'undefined') { + file.bitrate = parseInt(file.bitrate, 10); + } + + if (typeof file.quality != 'undefined') { + if (file.quality === null || typeof file.quality.level == 'undefined' || typeof file.quality.label == 'undefined') { + delete file.quality; + } else { + this.filterParams(file.quality, ['level', 'label']); + file.quality.level = parseInt(file.quality.level); + file.quality.label = this.textUtils.escapeHTML(file.quality.label); + } + } + + if (typeof file.copyright != 'undefined') { + return this.validateGroup(file.copyright, this.getCopyrightSemantics()).then(() => { + return file; + }); + } + + return Promise.resolve(file); + } + + /** + * Validate given file data. + * + * @param file File. + * @param semantics Semantics. + * @return Promise resolved with the validated file. + */ + validateFile(file: any, semantics: any): Promise { + return this.validateFilelike(file, semantics); + } + + /** + * Validate given image data. + * + * @param image Image. + * @param semantics Semantics. + * @return Promise resolved with the validated file. + */ + validateImage(image: any, semantics: any): Promise { + return this.validateFilelike(image, semantics, ['width', 'height', 'originalImage']); + } + + /** + * Validate given video data. + * + * @param video Video. + * @param semantics Semantics. + * @return Promise resolved with the validated file. + */ + validateVideo(video: any, semantics: any): Promise { + let promise = Promise.resolve(); // Use a chain of promises so the order is kept. + + for (const key in video) { + promise = promise.then(() => { + return this.validateFilelike(video[key], semantics, ['width', 'height', 'codecs', 'quality', 'bitrate']); + }); + } + + return promise.then(() => { + return video; + }); + } + + /** + * Validate given audio data. + * + * @param audio Audio. + * @param semantics Semantics. + * @return Promise resolved with the validated file. + */ + validateAudio(audio: any, semantics: any): Promise { + let promise = Promise.resolve(); // Use a chain of promises so the order is kept. + + for (const key in audio) { + promise = promise.then(() => { + return this.validateFilelike(audio[key], semantics); + }); + } + + return promise.then(() => { + return audio; + }); + } + + /** + * Validate given group value against group semantics. + * Will recurse into validating each group member. + * + * @param group Group. + * @param semantics Semantics. + * @param flatten Whether to flatten. + */ + validateGroup(group: any, semantics: any, flatten: boolean = true): Promise { + // Groups with just one field are compressed in the editor to only output he child content. + + const isSubContent = semantics.isSubContent === true; + + if (semantics.fields.length == 1 && flatten && !isSubContent) { + const field = semantics.fields[0], + fn = this[this.typeMap[field.type]].bind(this); + + return Promise.resolve(fn(group, field)); + } else { + let promise = Promise.resolve(); // Use a chain of promises so the order is kept. + + for (const key in group) { + // If subContentId is set, keep value + if (isSubContent && key == 'subContentId') { + continue; + } + + // Find semantics for name=$key + let found = false, + fn = null, + field = null; + + for (let i = 0; i < semantics.fields.length; i++) { + field = semantics.fields[i]; + + if (field.name == key) { + if (semantics.optional) { + field.optional = true; + } + fn = this[this.typeMap[field.type]].bind(this); + found = true; + break; + } + } + + if (found && fn) { + promise = promise.then(() => { + return Promise.resolve(fn(group[key], field)).then((val) => { + group[key] = val; + if (val === null) { + delete group[key]; + } + }); + }); + } else { + // Something exists in content that does not have a corresponding semantics field. Remove it. + delete group.key; + } + } + + return promise.then(() => { + return group; + }); + } + } + + /** + * Validate given library value against library semantics. + * Check if provided library is within allowed options. + * Will recurse into validating the library's semantics too. + * + * @param value Value. + * @param semantics Semantics. + * @return Promise resolved when done. + */ + validateLibrary(value: any, semantics: any): Promise { + if (!value.library) { + return Promise.resolve(); + } + + let promise; + + if (!this.libraries[value.library]) { + const libSpec = this.h5pUtils.libraryFromString(value.library); + + promise = this.h5pProvider.loadLibrary(libSpec.machineName, libSpec.majorVersion, libSpec.minorVersion, this.siteId) + .then((library) => { + this.libraries[value.library] = library; + + return library; + }); + } else { + promise = Promise.resolve(this.libraries[value.library]); + } + + return promise.then((library) => { + // Validate parameters. + return this.validateGroup(value.params, {type: 'group', fields: library.semantics}, false).then((validated) => { + + value.params = validated; + + // Validate subcontent's metadata + if (value.metadata) { + return this.validateMetadata(value.metadata).then((res) => { + value.metadata = res; + }); + } + }).then(() => { + + let validKeys = ['library', 'params', 'subContentId', 'metadata']; + if (semantics.extraAttributes) { + validKeys = this.utils.uniqueArray(validKeys.concat(semantics.extraAttributes)); + } + + this.filterParams(value, validKeys); + + if (value.subContentId && + !value.subContentId.match(/^\{?[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\}?$/)) { + delete value.subContentId; + } + + // Find all dependencies for this library. + const depKey = 'preloaded-' + library.machineName; + if (!this.dependencies[depKey]) { + this.dependencies[depKey] = { + library: library, + type: 'preloaded' + }; + + return this.h5pProvider.findLibraryDependencies(this.dependencies, library, this.nextWeight).then((weight) => { + this.nextWeight = weight; + this.dependencies[depKey].weight = this.nextWeight++; + + return value; + }); + } else { + return value; + } + }); + }); + } + + /** + * Check params for a whitelist of allowed properties. + * + * @param params Object to filter. + * @param whitelist List of keys to keep. + */ + filterParams(params: any, whitelist: string[]): void { + for (const key in params) { + if (whitelist.indexOf(key) == -1) { + delete params[key]; + } + } + } + + /** + * Filters HTML to prevent cross-site-scripting (XSS) vulnerabilities. + * Based on kses by Ulf Harnhammar, see http://sourceforge.net/projects/kses. + * + * @param str The string with raw HTML in it. + * @param allowedTags An array of allowed tags. + * @param allowedStyles Allowed styles. + * @return An XSS safe version of the string. + */ + protected filterXss(str: string, allowedTags?: string[], allowedStyles?: RegExp[]): string { + if (!str || typeof str != 'string') { + return str; + } + + allowedTags = allowedTags || ['a', 'em', 'strong', 'cite', 'blockquote', 'code', 'ul', 'ol', 'li', 'dl', 'dt', 'dd']; + + this.allowedStyles = allowedStyles; + + // Store the text format. + this.filterXssSplit(allowedTags, true); + + // Remove Netscape 4 JS entities. + str = str.replace(/&\s*\{[^}]*(\}\s*;?|$)/g, ''); + + // Defuse all HTML entities. + str = str.replace(/&/g, '&'); + + // Change back only well-formed entities in our whitelist: + // Decimal numeric entities. + str = str.replace(/&#([0-9]+;)/g, '&#$1'); + // Hexadecimal numeric entities. + str = str.replace(/&#[Xx]0*((?:[0-9A-Fa-f]{2})+;)/g, '&#x$1'); + // Named entities. + str = str.replace(/&([A-Za-z][A-Za-z0-9]*;)/g, '&$1'); + + const matches = str.match(/(<(?=[^a-zA-Z!\/])||<[^>]*(>|$)|>)/g); + if (matches && matches.length) { + matches.forEach((match) => { + str = str.replace(match, this.filterXssSplit([match])); + }); + } + + return str; + } + + /** + * Processes an HTML tag. + * + * @param m An array with various meaning depending on the value of store. + * If store is TRUE then the array contains the allowed tags. + * If store is FALSE then the array has one element, the HTML tag to process. + * @param store Whether to store m. + * @return string If the element isn't allowed, an empty string. Otherwise, the cleaned up version of the HTML element. + */ + protected filterXssSplit(m: string[], store: boolean = false): string { + + if (store) { + this.allowedHtml = this.utils.arrayToObject(m); + + return ''; + } + + const str = m[0]; + + if (str.substr(0, 1) != '<') { + // We matched a lone ">" character. + return '>'; + } else if (str.length == 1) { + // We matched a lone "<" character. + return '<'; + } + + const matches = str.match(/^<\s*(\/\s*)?([a-zA-Z0-9\-]+)([^>]*)>?|()$/); + if (!matches) { + // Seriously malformed. + return ''; + } + + const slash = matches[1] ? matches[1].trim() : '', + attrList = matches[3] || '', + comment = matches[4] || ''; + let elem = matches[2] || ''; + + if (comment) { + elem = '!--'; + } + + if (!this.allowedHtml[elem.toLowerCase()]) { + // Disallowed HTML element. + return ''; + } + + if (comment) { + return comment; + } + + if (slash != '') { + return ''; + } + + // Is there a closing XHTML slash at the end of the attributes? + const newAttrList = attrList.replace(/(\s?)\/\s*$/g, '$1'), + xhtmlSlash = attrList != newAttrList ? ' /' : ''; + + // Clean up attributes. + let attr2 = this.filterXssAttributes(newAttrList, + (CoreH5PContentValidator.ALLOWED_STYLEABLE_TAGS.indexOf(elem) != -1 ? this.allowedStyles : null)).join(' '); + attr2 = attr2.replace(/[<>]/g, ''); + attr2 = attr2.length ? ' ' + attr2 : ''; + + return '<' + elem + attr2 + xhtmlSlash + '>'; + } + + /** + * Processes a string of HTML attributes. + * + * @param attr HTML attributes. + * @param allowedStyles Allowed styles. + * @return Cleaned up version of the HTML attributes. + */ + protected filterXssAttributes(attr: string, allowedStyles?: RegExp[]): string[] { + const attrArr = []; + let mode = 0, + attrName = '', + skip = false; + + while (attr.length != 0) { + // Was the last operation successful? + let working = 0, + matches, + thisVal; + + switch (mode) { + case 0: + // Attribute name, href for instance. + matches = attr.match(/^([-a-zA-Z]+)/); + if (matches && matches.length > 1) { + attrName = matches[1].toLowerCase(); + skip = (attrName == 'style' || attrName.substr(0, 2) == 'on'); + working = mode = 1; + attr = attr.replace(/^[-a-zA-Z]+/, ''); + } + break; + + case 1: + // Equals sign or valueless ("selected"). + if (attr.match(/^\s*=\s*/)) { + working = 1; + mode = 2; + attr = attr.replace(/^\s*=\s*/, ''); + break; + } + + if (attr.match(/^\s+/)) { + working = 1; + mode = 0; + if (!skip) { + attrArr.push(attrName); + } + attr = attr.replace(/^\s+/, ''); + } + break; + + case 2: + // Attribute value, a URL after href= for instance. + matches = attr.match(/^"([^"]*)"(\s+|$)/); + if (matches && matches.length > 1) { + if (allowedStyles && attrName === 'style') { + // Allow certain styles. + for (let i = 0; i < allowedStyles.length; i++) { + const pattern = allowedStyles[i]; + if (matches[1].match(pattern)) { + // All patterns are start to end patterns, and CKEditor adds one span per style. + attrArr.push('style="' + matches[1] + '"'); + break; + } + } + break; + } + + thisVal = this.filterXssBadProtocol(matches[1]); + + if (!skip) { + attrArr.push(attrName + '="' + thisVal + '"'); + } + working = 1; + mode = 0; + attr = attr.replace(/^"[^"]*"(\s+|$)/, ''); + break; + } + + matches = attr.match(/^'([^']*)'(\s+|$)/); + if (matches && matches.length > 1) { + thisVal = this.filterXssBadProtocol(matches[1]); + + if (!skip) { + attrArr.push(attrName + '="' + thisVal + '"'); + } + working = 1; + mode = 0; + attr = attr.replace(/^'[^']*'(\s+|$)/, ''); + break; + } + + matches = attr.match(/^([^\s\"']+)(\s+|$)/); + if (matches && matches.length > 1) { + thisVal = this.filterXssBadProtocol(matches[1]); + + if (!skip) { + attrArr.push(attrName + '="' + thisVal + '"'); + } + working = 1; + mode = 0; + attr = attr.replace(/^([^\s\"']+)(\s+|$)/, ''); + } + break; + + default: + } + + if (working == 0) { + // Not well formed; remove and try again. + attr = attr.replace(/^("[^"]*("|$)|\'[^\']*(\'|$)||\S)*\s*/, ''); + mode = 0; + } + } + + // The attribute list ends with a valueless attribute like "selected". + if (mode == 1 && !skip) { + attrArr.push(attrName); + } + + return attrArr; + } + + /** + * Processes an HTML attribute value and strips dangerous protocols from URLs. + * + * @param str The string with the attribute value. + * @param decode Whether to decode entities in the $string. + * @return Cleaned up and HTML-escaped version of $string. + */ + filterXssBadProtocol(str: string, decode: boolean = true): string { + // Get the plain text representation of the attribute value (i.e. its meaning). + if (decode) { + str = this.textUtils.decodeHTMLEntities(str); + } + + return this.textUtils.escapeHTML(this.stripDangerousProtocols(str)); + } + + /** + * Strips dangerous protocols (e.g. 'javascript:') from a URI. + * + * @param uri A plain-text URI that might contain dangerous protocols. + * @return A plain-text URI stripped of dangerous protocols. + */ + protected stripDangerousProtocols(uri: string): string { + + const allowedProtocols = { + ftp: true, + http: true, + https: true, + mailto: true + }; + let before; + + // Iteratively remove any invalid protocol found. + do { + before = uri; + const colonPos = uri.indexOf(':'); + + if (colonPos > 0) { + // We found a colon, possibly a protocol. Verify. + const protocol = uri.substr(0, colonPos); + // If a colon is preceded by a slash, question mark or hash, it cannot possibly be part of the URL scheme. + // This must be a relative URL, which inherits the (safe) protocol of the base document. + if (protocol.match(/[/?#]/)) { + break; + } + // Check if this is a disallowed protocol. + if (!allowedProtocols[protocol.toLowerCase()]) { + uri = uri.substr(colonPos + 1); + } + } + } while (before != uri); + + return uri; + } + + /** + * Get metadata semantics. + * + * @return Semantics. + */ + getMetadataSemantics(): any[] { + + if (this.metadataSemantics) { + return this.metadataSemantics; + } + + const ccVersions = this.getCCVersions(); + + this.metadataSemantics = [ + { + name: 'title', + type: 'text', + label: this.translate.instant('core.h5p.title'), + placeholder: 'La Gioconda' + }, + { + name: 'license', + type: 'select', + label: this.translate.instant('core.h5p.license'), + default: 'U', + options: [ + { + value: 'U', + label: this.translate.instant('core.h5p.undisclosed') + }, + { + type: 'optgroup', + label: this.translate.instant('core.h5p.creativecommons'), + options: [ + { + value: 'CC BY', + label: this.translate.instant('core.h5p.ccattribution'), + versions: ccVersions + }, + { + value: 'CC BY-SA', + label: this.translate.instant('core.h5p.ccattributionsa'), + versions: ccVersions + }, + { + value: 'CC BY-ND', + label: this.translate.instant('core.h5p.ccattributionnd'), + versions: ccVersions + }, + { + value: 'CC BY-NC', + label: this.translate.instant('core.h5p.ccattributionnc'), + versions: ccVersions + }, + { + value: 'CC BY-NC-SA', + label: this.translate.instant('core.h5p.ccattributionncsa'), + versions: ccVersions + }, + { + value: 'CC BY-NC-ND', + label: this.translate.instant('core.h5p.ccattributionncnd'), + versions: ccVersions + }, + { + value: 'CC0 1.0', + label: this.translate.instant('core.h5p.ccpdd') + }, + { + value: 'CC PDM', + label: this.translate.instant('core.h5p.pdm') + }, + ] + }, + { + value: 'GNU GPL', + label: this.translate.instant('core.h5p.gpl') + }, + { + value: 'PD', + label: this.translate.instant('core.h5p.pd') + }, + { + value: 'ODC PDDL', + label: this.translate.instant('core.h5p.pddl') + }, + { + value: 'C', + label: this.translate.instant('core.h5p.copyrightstring') + } + ] + }, + { + name: 'licenseVersion', + type: 'select', + label: this.translate.instant('core.h5p.licenseversion'), + options: ccVersions, + optional: true + }, + { + name: 'yearFrom', + type: 'number', + label: this.translate.instant('core.h5p.yearsfrom'), + placeholder: '1991', + min: '-9999', + max: '9999', + optional: true + }, + { + name: 'yearTo', + type: 'number', + label: this.translate.instant('core.h5p.yearsto'), + placeholder: '1992', + min: '-9999', + max: '9999', + optional: true + }, + { + name: 'source', + type: 'text', + label: this.translate.instant('core.h5p.source'), + placeholder: 'https://', + optional: true + }, + { + name: 'authors', + type: 'list', + field: { + name: 'author', + type: 'group', + fields: [ + { + label: this.translate.instant('core.h5p.authorname'), + name: 'name', + optional: true, + type: 'text' + }, + { + name: 'role', + type: 'select', + label: this.translate.instant('core.h5p.authorrole'), + default: 'Author', + options: [ + { + value: 'Author', + label: this.translate.instant('core.h5p.author') + }, + { + value: 'Editor', + label: this.translate.instant('core.h5p.editor') + }, + { + value: 'Licensee', + label: this.translate.instant('core.h5p.licensee') + }, + { + value: 'Originator', + label: this.translate.instant('core.h5p.originator') + } + ] + } + ] + } + }, + { + name: 'licenseExtras', + type: 'text', + widget: 'textarea', + label: this.translate.instant('core.h5p.licenseextras'), + optional: true, + description: this.translate.instant('core.h5p.additionallicenseinfo') + }, + { + name: 'changes', + type: 'list', + field: { + name: 'change', + type: 'group', + label: this.translate.instant('core.h5p.changelog'), + fields: [ + { + name: 'date', + type: 'text', + label: this.translate.instant('core.h5p.date'), + optional: true + }, + { + name: 'author', + type: 'text', + label: this.translate.instant('core.h5p.changedby'), + optional: true + }, + { + name: 'log', + type: 'text', + widget: 'textarea', + label: this.translate.instant('core.h5p.changedescription'), + placeholder: this.translate.instant('core.h5p.changeplaceholder'), + optional: true + } + ] + } + }, + { + name: 'authorComments', + type: 'text', + widget: 'textarea', + label: this.translate.instant('core.h5p.authorcomments'), + description: this.translate.instant('core.h5p.authorcommentsdescription'), + optional: true + }, + { + name: 'contentType', + type: 'text', + widget: 'none' + }, + { + name: 'defaultLanguage', + type: 'text', + widget: 'none' + } + ]; + + return this.metadataSemantics; + } + + /** + * Get copyright semantics. + * + * @return Semantics. + */ + getCopyrightSemantics(): any { + + if (this.copyrightSemantics) { + return this.copyrightSemantics; + } + + const ccVersions = this.getCCVersions(); + + this.copyrightSemantics = { + name: 'copyright', + type: 'group', + label: this.translate.instant('core.h5p.copyrightinfo'), + fields: [ + { + name: 'title', + type: 'text', + label: this.translate.instant('core.h5p.title'), + placeholder: 'La Gioconda', + optional: true + }, + { + name: 'author', + type: 'text', + label: this.translate.instant('core.h5p.author'), + placeholder: 'Leonardo da Vinci', + optional: true + }, + { + name: 'year', + type: 'text', + label: this.translate.instant('core.h5p.years'), + placeholder: '1503 - 1517', + optional: true + }, + { + name: 'source', + type: 'text', + label: this.translate.instant('core.h5p.source'), + placeholder: 'http://en.wikipedia.org/wiki/Mona_Lisa', + optional: true, + regexp: { + pattern: '^http[s]?://.+', + modifiers: 'i' + } + }, + { + name: 'license', + type: 'select', + label: this.translate.instant('core.h5p.license'), + default: 'U', + options: [ + { + value: 'U', + label: this.translate.instant('core.h5p.undisclosed') + }, + { + value: 'CC BY', + label: this.translate.instant('core.h5p.ccattribution'), + versions: ccVersions + }, + { + value: 'CC BY-SA', + label: this.translate.instant('core.h5p.ccattributionsa'), + versions: ccVersions + }, + { + value: 'CC BY-ND', + label: this.translate.instant('core.h5p.ccattributionnd'), + versions: ccVersions + }, + { + value: 'CC BY-NC', + label: this.translate.instant('core.h5p.ccattributionnc'), + versions: ccVersions + }, + { + value: 'CC BY-NC-SA', + label: this.translate.instant('core.h5p.ccattributionncsa'), + versions: ccVersions + }, + { + value: 'CC BY-NC-ND', + label: this.translate.instant('core.h5p.ccattributionncnd'), + versions: ccVersions + }, + { + value: 'GNU GPL', + label: this.translate.instant('core.h5p.licenseGPL'), + versions: [ + { + value: 'v3', + label: this.translate.instant('core.h5p.licenseV3') + }, + { + value: 'v2', + label: this.translate.instant('core.h5p.licenseV2') + }, + { + value: 'v1', + label: this.translate.instant('core.h5p.licenseV1') + } + ] + }, + { + value: 'PD', + label: this.translate.instant('core.h5p.pd'), + versions: [ + { + value: '-', + label: '-' + }, + { + value: 'CC0 1.0', + label: this.translate.instant('core.h5p.licenseCC010U') + }, + { + value: 'CC PDM', + label: this.translate.instant('core.h5p.pdm') + } + ] + }, + { + value: 'C', + label: this.translate.instant('core.h5p.copyrightstring') + } + ] + }, + { + name: 'version', + type: 'select', + label: this.translate.instant('core.h5p.licenseversion'), + options: [] + } + ] + }; + + return this.copyrightSemantics; + } + + /** + * Get CC versions for semantics. + * + * @return CC versions. + */ + protected getCCVersions(): any[] { + return [ + { + value: '4.0', + label: this.translate.instant('core.h5p.licenseCC40') + }, + { + value: '3.0', + label: this.translate.instant('core.h5p.licenseCC30') + }, + { + value: '2.5', + label: this.translate.instant('core.h5p.licenseCC25') + }, + { + value: '2.0', + label: this.translate.instant('core.h5p.licenseCC20') + }, + { + value: '1.0', + label: this.translate.instant('core.h5p.licenseCC10') + } + ]; + } +} diff --git a/src/core/h5p/components/h5p-player/h5p-player.ts b/src/core/h5p/components/h5p-player/h5p-player.ts index 11a5901fc..f13b33073 100644 --- a/src/core/h5p/components/h5p-player/h5p-player.ts +++ b/src/core/h5p/components/h5p-player/h5p-player.ts @@ -129,8 +129,12 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy { // Local package. this.playerSrc = url; } else { + // Never allow downloading in the app. This will only work if the user is allowed to change the params. + const src = this.src && this.src.replace(CoreH5PProvider.DISPLAY_OPTION_DOWNLOAD + '=1', + CoreH5PProvider.DISPLAY_OPTION_DOWNLOAD + '=0'); + // Get auto-login URL so the user is automatically authenticated. - return this.sitesProvider.getCurrentSite().getAutoLoginUrl(this.src, false).then((url) => { + return this.sitesProvider.getCurrentSite().getAutoLoginUrl(src, false).then((url) => { // Add the preventredirect param so the user can authenticate. this.playerSrc = this.urlUtils.addParamsToUrl(url, {preventredirect: false}); }); diff --git a/src/core/h5p/lang/en.json b/src/core/h5p/lang/en.json index 0cffba19a..a85304502 100644 --- a/src/core/h5p/lang/en.json +++ b/src/core/h5p/lang/en.json @@ -1,5 +1,10 @@ { + "additionallicenseinfo": "Any additional information about the license", "author": "Author", + "authorcomments": "Author comments", + "authorcommentsdescription": "Comments for the editor of the content. (This text will not be published as a part of the copyright info.)", + "authorname": "Author's name", + "authorrole": "Author's role", "by": "by", "cancellabel": "Cancel", "ccattribution": "Attribution (CC BY)", @@ -8,7 +13,11 @@ "ccattributionncsa": "Attribution-NonCommercial-ShareAlike (CC BY-NC-SA)", "ccattributionnd": "Attribution-NoDerivs (CC BY-ND)", "ccattributionsa": "Attribution-ShareAlike (CC BY-SA)", + "ccpdd": "Public Domain Dedication (CC0)", + "changedby": "Changed by", + "changedescription": "Description of change", "changelog": "Changelog", + "changeplaceholder": "Photo cropped, text changed, etc.", "close": "Close", "confirmdialogbody": "Please confirm that you wish to proceed. This action is not reversible.", "confirmdialogheader": "Confirm action", @@ -19,18 +28,24 @@ "contentchanged": "This content has changed since you last used it.", "contenttype": "Content Type", "copyright": "Rights of use", + "copyrightinfo": "Copyright information", "copyrightstring": "Copyright", "copyrighttitle": "View copyright information for this content.", + "creativecommons": "Creative Commons", + "date": "Date", "disablefullscreen": "Disable fullscreen", "download": "Download", "downloadtitle": "Download this content as a H5P file.", + "editor": "Editor", "embed": "Embed", "embedtitle": "View the embed code for this content.", "fullscreen": "Fullscreen", + "gpl": "General Public License v3", "h5ptitle": "Visit H5P.org to check out more cool content.", "hideadvanced": "Hide advanced", "license": "License", "licenseCC010": "CC0 1.0 Universal (CC0 1.0) Public Domain Dedication", + "licenseCC010U": "CC0 1.0 Universal", "licenseCC10": "1.0 Generic", "licenseCC20": "2.0 Generic", "licenseCC25": "2.5 Generic", @@ -40,14 +55,18 @@ "licenseV1": "Version 1", "licenseV2": "Version 2", "licenseV3": "Version 3", + "licensee": "Licensee", "licenseextras": "License Extras", + "licenseversion": "License version", "nocopyright": "No copyright information available for this content.", "offlineDialogBody": "We were unable to send information about your completion of this task. Please check your internet connection.", "offlineDialogHeader": "Your connection to the server was lost", "offlineDialogRetryButtonLabel": "Retry now", "offlineDialogRetryMessage": "Retrying in :num....", "offlineSuccessfulSubmit": "Successfully submitted results.", + "originator": "Originator", "pd": "Public Domain", + "pddl": "Public Domain Dedication and Licence", "pdm": "Public Domain Mark (PDM)", "play": "Play H5P", "resizescript": "Include this script on your website if you want dynamic sizing of the embedded content:", @@ -65,5 +84,8 @@ "thumbnail": "Thumbnail", "title": "Title", "undisclosed": "Undisclosed", - "year": "Year" + "year": "Year", + "years": "Year(s)", + "yearsfrom": "Years (from)", + "yearsto": "Years (to)" } diff --git a/src/core/h5p/providers/h5p.ts b/src/core/h5p/providers/h5p.ts index e6c9e76e9..adb3f4fbd 100644 --- a/src/core/h5p/providers/h5p.ts +++ b/src/core/h5p/providers/h5p.ts @@ -25,6 +25,8 @@ import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype'; import { CoreUrlUtilsProvider } from '@providers/utils/url'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreH5PUtilsProvider } from './utils'; +import { CoreH5PContentValidator } from '../classes/content-validator'; +import { TranslateService } from '@ngx-translate/core'; import { FileEntry } from '@ionic-native/file'; /** @@ -304,7 +306,8 @@ export class CoreH5PProvider { private h5pUtils: CoreH5PUtilsProvider, private filepoolProvider: CoreFilepoolProvider, private utils: CoreUtilsProvider, - private urlUtils: CoreUrlUtilsProvider) { + private urlUtils: CoreUrlUtilsProvider, + private translate: TranslateService) { this.logger = logger.getInstance('CoreH5PProvider'); @@ -415,6 +418,7 @@ export class CoreH5PProvider { * @return Promise resolved with all of the files content in one string. */ protected concatenateFiles(assets: CoreH5PDependencyAsset[], type: string): Promise { + const basePath = this.fileProvider.getBasePathInstant(); let content = '', promise = Promise.resolve(); // Use a chain of promises so the order is kept. @@ -433,39 +437,42 @@ export class CoreH5PProvider { if (matches && matches.length) { matches.forEach((match) => { - let url = match.replace(/(url\([\'"]?|[\'"]?\)$)/i, ''); + let url = match.replace(/(url\(['"]?|['"]?\)$)/ig, ''); if (treated[url] || url.match(/^(data:|([a-z0-9]+:)?\/)/i)) { return; // Not relative or already treated, skip. } + const pathSplit = assetPath.split('/'); treated[url] = url; /* Find "../" in the URL. If it exists, we have to remove "../" and switch the last folder in the - filepath for the first folder in the url. - For instance: - Path: /H5P.Question-1.4/styles/ - Url: ../images/plus-one.svg - We want this: H5P.Question-1.4/images/ITEMID/minus-one.svg. */ + filepath for the first folder in the url. */ if (url.match(/^\.\.\//)) { - const pathSplit = assetPath.split('/'), - urlSplit = url.split('/').filter((i) => { + const urlSplit = url.split('/').filter((i) => { return i; // Remove empty values. }); - // Remove the first element: ../. - urlSplit.unshift(); + // Remove the file name from the asset path. + pathSplit.pop(); + + // Remove the first element from the file URL: ../ . + urlSplit.shift(); // Put the url's first folder into the asset path. pathSplit[pathSplit.length - 1] = urlSplit[0]; urlSplit.shift(); // Create the new URL and replace it in the file contents. - url = '/' + pathSplit.join('/') + '/' + urlSplit.join('/'); + url = pathSplit.join('/') + '/' + urlSplit.join('/'); - fileContent = fileContent.replace(new RegExp(this.textUtils.escapeForRegex(match), 'g'), - 'url("' + url + '")'); + } else { + pathSplit[pathSplit.length - 1] = url; // Put the whole path to the end of the asset path. + url = pathSplit.join('/'); } + + fileContent = fileContent.replace(new RegExp(this.textUtils.escapeForRegex(match), 'g'), + 'url("' + this.textUtils.concatenatePaths(basePath, url) + '")'); }); } @@ -510,11 +517,11 @@ export class CoreH5PProvider { url: this.getEmbedUrl(site.getURL(), h5pUrl), contentUrl: contentUrl, metadata: content.metadata, - contentUserData: { - 0: { + contentUserData: [ + { state: '{}' } - } + ] }; // Get the core H5P assets, needed by the H5P classes to render the H5P content. @@ -859,8 +866,7 @@ export class CoreH5PProvider { return Promise.resolve(null); } - const dependencies = {}, // In web, dependencies are built by the validator. - params = { + const params = { library: this.libraryToString(content.library), params: this.textUtils.parseJSON(content.params, false) }; @@ -869,90 +875,65 @@ export class CoreH5PProvider { return null; } - // Get the main library data. - return this.loadLibrary(content.library.name, content.library.majorVersion, content.library.minorVersion, siteId) - .then((library) => { + const validator = new CoreH5PContentValidator(this, this.h5pUtils, this.textUtils, this.utils, this.translate, siteId); - library.semantics = this.textUtils.parseJSON(library.semantics, ''); + // Validate the main library and its dependencies. + return validator.validateLibrary(params, {options: [params.library]}).then(() => { - const depKey = 'preloaded-' + library.machineName; - let nextWeight; + // Handle addons. + return this.loadAddons(siteId); + }).then((addons) => { + // Validate addons. Use a chain of promises to calculate the weight properly. + let promise = Promise.resolve(); - if (!dependencies[depKey]) { - dependencies[depKey] = { - library: library, - type: 'preloaded' - }; - } + addons.forEach((addon) => { + const addTo = addon.addTo; - // Get the whole library dependency tree. - return this.findLibraryDependencies(dependencies, library, 1, false, siteId).then((weight) => { - nextWeight = weight; - dependencies[depKey].weight = nextWeight++; + 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]; - // Handle addons. - return this.loadAddons(siteId); - }).then((addons) => { - // Get the dependencies of all the addons. Use a chain of promises to calculate the weight properly. - let promise = Promise.resolve(); + if (type && type.text && type.text.regex && + this.h5pUtils.textAddonMatches(params.params, type.text.regex)) { - addons.forEach((addon) => { - const addTo = this.textUtils.parseJSON(addon.addTo, null); + promise = promise.then(() => { + return validator.addon(addon); + }); - 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)) { - - const addonDepKey = 'preloaded-' + addon.machineName; - dependencies[addonDepKey] = { - library: addon, - type: 'preloaded' - }; - - promise = promise.then(() => { - return this.findLibraryDependencies(dependencies, addon, nextWeight).then((weight) => { - nextWeight = weight; - dependencies[addonDepKey].weight = nextWeight++; - }); - }); - - break; - } + // An addon shall only be added once. + break; } } - }); - - return promise; - }).then(() => { - // Update content dependencies. - content.dependencies = dependencies; - - 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; }); + + 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; }); @@ -990,12 +971,13 @@ export class CoreH5PProvider { } library[property].forEach((dependency: CoreH5PLibraryBasicData) => { - const dependencyKey = type + '-' + dependency.machineName; - if (dependencies[dependencyKey]) { - return; // Skip, already have this. - } 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) => { @@ -1424,7 +1406,7 @@ export class CoreH5PProvider { // 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, siteId); + return this.saveCachedAssets(cachedAssetsHash, dependencies, folderName, siteId); }).then(() => { return files; }); @@ -1652,12 +1634,12 @@ export class CoreH5PProvider { } return db.getRecords(this.LIBRARIES_TABLE, conditions); - }).then((libraries) => { + }).then((libraries): any => { if (!libraries.length) { return Promise.reject(null); } - return libraries[0]; + return this.parseLibDBData(libraries[0]); }); } @@ -1681,7 +1663,9 @@ export class CoreH5PProvider { */ protected getLibraryById(id: number, siteId?: string): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { - return db.getRecord(this.LIBRARIES_TABLE, {id: id}); + return db.getRecord(this.LIBRARIES_TABLE, {id: id}).then((library) => { + return this.parseLibDBData(library); + }); }); } @@ -1946,7 +1930,7 @@ export class CoreH5PProvider { const addons = []; for (let i = 0; i < result.rows.length; i++) { - addons.push(result.rows.item(i)); + addons.push(this.parseLibAddonData(result.rows.item(i))); } return addons; @@ -1963,6 +1947,8 @@ export class CoreH5PProvider { * @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) { @@ -1978,31 +1964,35 @@ export class CoreH5PProvider { // Load the main library data. return this.getLibraryById(contentData.mainlibraryid, siteId).then((libData) => { - // 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, - // The embedtype will be always set to 'iframe' to prevent conflicts with JS and CSS. - embedType: '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: { - license: 'U' // Stop "invalid selected option in select" for old content without license chosen. - }, - library: { - id: libData.id, - name: libData.machinename, - majorVersion: libData.majorversion, - minorVersion: libData.minorversion, - embedTypes: libData.embedtypes, - fullscreen: libData.fullscreen - } - }; + // 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 + } + }; + }); }); }); } @@ -2113,6 +2103,31 @@ export class CoreH5PProvider { }); } + /** + * 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. @@ -2172,12 +2187,13 @@ export class CoreH5PProvider { * 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 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}, - siteId?: string): Promise { + folderName: string, siteId?: string): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { const promises = []; @@ -2185,7 +2201,8 @@ export class CoreH5PProvider { for (const key in dependencies) { const data = { hash: key, - libraryid: dependencies[key].libraryId + libraryid: dependencies[key].libraryId, + foldername: folderName }; promises.push(db.insertRecord(this.LIBRARIES_CACHEDASSETS_TABLE, data)); @@ -2279,7 +2296,7 @@ export class CoreH5PProvider { for (const libString in librariesJsonData) { const libraryData = librariesJsonData[libString]; - // Find local library identifier + // Find local library identifier. promises.push(this.getLibraryByData(libraryData).catch(() => { // Not found. }).then((dbData) => { @@ -2422,7 +2439,7 @@ export class CoreH5PProvider { preloadedjs: preloadedJS, preloadedcss: preloadedCSS, droplibrarycss: dropLibraryCSS, - semantics: libraryData.semantics, + semantics: typeof libraryData.semantics != 'undefined' ? JSON.stringify(libraryData.semantics) : null, addto: typeof libraryData.addTo != 'undefined' ? JSON.stringify(libraryData.addTo) : null, }; @@ -2497,8 +2514,8 @@ export class CoreH5PProvider { for (const key in librariesInUse) { const dependency = librariesInUse[key]; - if (dependency.library.dropLibraryCss) { - const split = dependency.library.dropLibraryCss.split(', '); + if (( dependency.library).dropLibraryCss) { + const split = ( dependency.library).dropLibraryCss.split(', '); split.forEach((css) => { dropLibraryCssList[css] = css; @@ -2739,7 +2756,7 @@ export type CoreH5PContentDependencyData = { * Data for each content dependency in the dependency tree. */ export type CoreH5PContentDepsTreeDependency = { - library: CoreH5PLibraryData; // Library data. + library: CoreH5PLibraryData | CoreH5PLibraryAddonData; // Library data. type: string; // Dependency type. weight?: number; // An integer determining the order of the libraries when they are loaded. }; @@ -2786,7 +2803,7 @@ export type CoreH5PLibraryAddonData = { patchVersion: number; // Patch version. preloadedJs?: string; // Comma separated list of scripts to load. preloadedCss?: string; // Comma separated list of stylesheets to load. - addTo?: string; // Plugin configuration data. + addTo?: any; // Plugin configuration data. }; /** @@ -2805,8 +2822,8 @@ export type CoreH5PLibraryDBData = { 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?: string; // The semantics definition in json format. - addto?: string; // Plugin configuration data. + semantics?: any; // The semantics definition. + addto?: any; // Plugin configuration data. }; /** diff --git a/src/core/h5p/providers/utils.ts b/src/core/h5p/providers/utils.ts index 9c568acac..026b2b9b7 100644 --- a/src/core/h5p/providers/utils.ts +++ b/src/core/h5p/providers/utils.ts @@ -313,6 +313,27 @@ export class CoreH5PUtilsProvider { }; } + /** + * 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. * diff --git a/src/providers/utils/utils.ts b/src/providers/utils/utils.ts index c4f051645..624371ac1 100644 --- a/src/providers/utils/utils.ts +++ b/src/providers/utils/utils.ts @@ -135,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 array The array to convert. - * @param propertyName The name of the property to use as the key. + * @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; From 69e4fdd036f3f8a2c4117fcf04171a1af2bd616e Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 28 Nov 2019 17:51:22 +0100 Subject: [PATCH 188/257] MOBILE-2235 iframe: Fix clicks in deep iframe links --- src/providers/utils/iframe.ts | 106 +++++++++++++++++++++------------- 1 file changed, 67 insertions(+), 39 deletions(-) diff --git a/src/providers/utils/iframe.ts b/src/providers/utils/iframe.ts index f02aea2b6..e60c19b58 100644 --- a/src/providers/utils/iframe.ts +++ b/src/providers/utils/iframe.ts @@ -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. +}; From 1a2ea9485f5b55c31cf44cbb19d2eb861237a924 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 4 Dec 2019 13:36:43 +0100 Subject: [PATCH 189/257] MOBILE-2235 h5p: Fix check download for external h5p packages --- src/core/h5p/components/h5p-player/h5p-player.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/core/h5p/components/h5p-player/h5p-player.ts b/src/core/h5p/components/h5p-player/h5p-player.ts index f13b33073..daffc9570 100644 --- a/src/core/h5p/components/h5p-player/h5p-player.ts +++ b/src/core/h5p/components/h5p-player/h5p-player.ts @@ -26,6 +26,7 @@ import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreH5PProvider } from '@core/h5p/providers/h5p'; import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; import { CoreConstants } from '@core/constants'; +import { CoreSite } from '@classes/site'; /** * Component to render an H5P package. @@ -46,6 +47,7 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy { canDownload: boolean; calculating = true; + protected site: CoreSite; protected siteId: string; protected siteCanDownload: boolean; protected observer; @@ -67,7 +69,8 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy { protected fileProvider: CoreFileProvider) { this.logger = loggerProvider.getInstance('CoreH5PPlayerComponent'); - this.siteId = sitesProvider.getCurrentSiteId(); + this.site = sitesProvider.getCurrentSite(); + this.siteId = this.site.getId(); this.siteCanDownload = this.sitesProvider.getCurrentSite().canDownloadFiles(); } @@ -82,7 +85,7 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy { * Detect changes on input properties. */ ngOnChanges(changes: {[name: string]: SimpleChange}): void { - // If it's already playing and the src changes, don't change the player src, the user could lose data. + // If it's already playing there's no need to check if it can be downloaded. if (changes.src && !this.showPackage) { this.checkCanDownload(); } @@ -220,7 +223,7 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy { this.observer && this.observer.off(); this.urlParams = this.urlUtils.extractUrlParams(this.src); - if (this.src && this.siteCanDownload && this.h5pProvider.canGetTrustedH5PFileInSite()) { + if (this.src && this.siteCanDownload && this.h5pProvider.canGetTrustedH5PFileInSite() && this.site.containsUrl(this.src)) { this.calculating = true; From 5dae06ff8106615657f35fcefa5f8ff7b51bdadb Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 4 Dec 2019 13:37:00 +0100 Subject: [PATCH 190/257] MOBILE-2235 h5p: Fix issues identified during PeerReview --- scripts/langindex.json | 1 + .../mod/scorm/providers/prefetch-handler.ts | 5 - .../h5p/components/h5p-player/h5p-player.ts | 16 +- src/core/h5p/providers/h5p.ts | 137 +++++++----------- src/core/h5p/providers/pluginfile-handler.ts | 24 +-- src/providers/file.ts | 23 ++- src/providers/filepool.ts | 2 +- src/providers/plugin-file-delegate.ts | 96 ++++++------ src/providers/utils/dom.ts | 2 +- 9 files changed, 141 insertions(+), 165 deletions(-) diff --git a/scripts/langindex.json b/scripts/langindex.json index 70f4eb012..2b696380e 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -1620,6 +1620,7 @@ "core.h5p.originator": "h5p", "core.h5p.pd": "h5p", "core.h5p.pddl": "h5p", + "core.h5p.play": "local_moodlemobileapp", "core.h5p.pdm": "h5p", "core.h5p.resizescript": "h5p", "core.h5p.resubmitScores": "h5p", diff --git a/src/addon/mod/scorm/providers/prefetch-handler.ts b/src/addon/mod/scorm/providers/prefetch-handler.ts index 7c6dcccde..0d3756312 100644 --- a/src/addon/mod/scorm/providers/prefetch-handler.ts +++ b/src/addon/mod/scorm/providers/prefetch-handler.ts @@ -172,11 +172,6 @@ export class AddonModScormPrefetchHandler extends CoreCourseActivityPrefetchHand return this.filepoolProvider.downloadUrl(siteId, packageUrl, true, this.component, scorm.coursemodule, undefined, this.downloadProgress.bind(this, true, onProgress)); } - }).then(() => { - // Remove the destination folder to prevent having old unused files. - return this.fileProvider.removeDir(dirPath).catch(() => { - // Ignore errors, it might have failed because the folder doesn't exist. - }); }).then(() => { // Get the ZIP file path. return this.filepoolProvider.getFilePathByUrl(siteId, packageUrl); diff --git a/src/core/h5p/components/h5p-player/h5p-player.ts b/src/core/h5p/components/h5p-player/h5p-player.ts index daffc9570..f4c0f70d7 100644 --- a/src/core/h5p/components/h5p-player/h5p-player.ts +++ b/src/core/h5p/components/h5p-player/h5p-player.ts @@ -148,7 +148,7 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy { if (this.canDownload && (this.state == CoreConstants.OUTDATED || this.state == CoreConstants.NOT_DOWNLOADED)) { // Download the package in background if the size is low. - this.downloadInBg().catch((error) => { + this.attemptDownloadInBg().catch((error) => { this.logger.error('Error downloading H5P in background', error); }); } @@ -188,7 +188,7 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy { * * @return Promise resolved when done. */ - protected downloadInBg(): Promise { + protected attemptDownloadInBg(): Promise { if (this.urlParams && this.src && this.siteCanDownload && this.h5pProvider.canGetTrustedH5PFileInSite() && this.appProvider.isOnline()) { @@ -225,8 +225,6 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy { if (this.src && this.siteCanDownload && this.h5pProvider.canGetTrustedH5PFileInSite() && this.site.containsUrl(this.src)) { - this.calculating = true; - this.calculateState(); // Listen for changes in the state. @@ -238,19 +236,21 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy { // An error probably means the file cannot be downloaded or we cannot check it (offline). }); - return; + } else { + this.calculating = false; + this.canDownload = false; } - this.calculating = false; - this.canDownload = false; } /** - * Calcuñate state of the file. + * Calculate state of the file. * * @param fileUrl The H5P file URL. */ protected calculateState(): void { + this.calculating = true; + // Get the status of the file. this.filepoolProvider.getFileStateByUrl(this.siteId, this.urlParams.url).then((state) => { this.canDownload = true; diff --git a/src/core/h5p/providers/h5p.ts b/src/core/h5p/providers/h5p.ts index adb3f4fbd..6750e226c 100644 --- a/src/core/h5p/providers/h5p.ts +++ b/src/core/h5p/providers/h5p.ts @@ -13,7 +13,6 @@ // limitations under the License. import { Injectable } from '@angular/core'; -import { CoreEventsProvider } from '@providers/events'; import { CoreFileProvider } from '@providers/file'; import { CoreFilepoolProvider } from '@providers/filepool'; import { CoreLoggerProvider } from '@providers/logger'; @@ -81,6 +80,10 @@ export class CoreH5PProvider { protected siteSchema: CoreSiteSchema = { name: 'CoreH5PProvider', version: 1, + canBeCleared: [ + this.CONTENT_TABLE, this.LIBRARIES_TABLE, this.LIBRARY_DEPENDENCIES_TABLE, this.CONTENTS_LIBRARIES_TABLE, + this.LIBRARIES_CACHEDASSETS_TABLE + ], tables: [ { name: this.CONTENT_TABLE, @@ -101,10 +104,6 @@ export class CoreH5PProvider { type: 'INTEGER', notNull: true }, - { - name: 'displayoptions', // Not used right now, but we keep the field to be consistent with Moodle web. - type: 'INTEGER' - }, { name: 'foldername', type: 'TEXT', @@ -293,12 +292,11 @@ export class CoreH5PProvider { ] }; - protected ROOT_CACHE_KEY = 'mmH5P:'; + protected ROOT_CACHE_KEY = 'CoreH5P:'; protected logger; constructor(logger: CoreLoggerProvider, - eventsProvider: CoreEventsProvider, private sitesProvider: CoreSitesProvider, private textUtils: CoreTextUtilsProvider, private fileProvider: CoreFileProvider, @@ -312,12 +310,6 @@ export class CoreH5PProvider { this.logger = logger.getInstance('CoreH5PProvider'); this.sitesProvider.registerSiteSchema(this.siteSchema); - - eventsProvider.on(CoreEventsProvider.SITE_STORAGE_DELETED, (data) => { - this.deleteAllData(data.siteId).catch((error) => { - this.logger.error('Error deleting all H5P data from site.', error); - }); - }); } /** @@ -568,24 +560,6 @@ export class CoreH5PProvider { }); } - /** - * Delete all the H5P data from the DB of a certain site. - * - * @param siteId Site ID. - * @return Promise resolved when done. - */ - protected deleteAllData(siteId: string): Promise { - return this.sitesProvider.getSiteDb(siteId).then((db) => { - return Promise.all([ - db.deleteRecords(this.CONTENT_TABLE), - db.deleteRecords(this.LIBRARIES_TABLE), - db.deleteRecords(this.LIBRARY_DEPENDENCIES_TABLE), - db.deleteRecords(this.CONTENTS_LIBRARIES_TABLE), - db.deleteRecords(this.LIBRARIES_CACHEDASSETS_TABLE) - ]); - }); - } - /** * Delete cached assets from DB and filesystem. * @@ -784,14 +758,8 @@ export class CoreH5PProvider { const folderName = this.mimeUtils.removeExtension(file.name), destFolder = this.textUtils.concatenatePaths(CoreFileProvider.TMPFOLDER, 'h5p/' + folderName); - // Make sure the dest dir doesn't exist already. - return this.fileProvider.removeDir(destFolder).catch(() => { - // Ignore errors. - }).then(() => { - return this.fileProvider.createDir(destFolder); - }).then(() => { - return this.fileProvider.unzipFile(file.toURL(), destFolder); - }).then(() => { + // 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) => { @@ -830,11 +798,6 @@ export class CoreH5PProvider { return Promise.reject(error); }); }); - }).then(() => { - // Remove tmp folder. - return this.fileProvider.removeDir(destFolder).catch(() => { - // Ignore errors, it will be deleted eventually. - }); }).then(() => { // Create the content player. @@ -843,6 +806,11 @@ export class CoreH5PProvider { 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. + }); }); }); }); @@ -1002,6 +970,40 @@ export class CoreH5PProvider { }); } + /** + * 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. * @@ -1215,7 +1217,7 @@ export class CoreH5PProvider { }).then((url) => { // Add display options to the URL. return this.getContentDataByUrl(fileUrl, siteId).then((data) => { - const options = this.validateDisplayOptions(this.getDisplayOptionsFromUrlParams(urlParams), data.id); + const options = this.fixDisplayOptions(this.getDisplayOptionsFromUrlParams(urlParams), data.id); return this.urlUtils.addParamsToUrl(url, options, undefined, true); }); @@ -1478,7 +1480,7 @@ export class CoreH5PProvider { * @return Display options as object. */ getDisplayOptionsForView(disable: number, id: number): CoreH5PDisplayOptions { - return this.validateDisplayOptions(this.getDisplayOptionsAsObject(disable), id); + return this.fixDisplayOptions(this.getDisplayOptionsAsObject(disable), id); } /** @@ -1733,8 +1735,8 @@ export class CoreH5PProvider { * @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 displayoptions DB field. - return CoreH5PDisplayOptionBehaviour.CONTROLLED_BY_AUTHOR_DEFAULT_OFF; // CONTROLLED_BY_AUTHOR_DEFAULT_OFF. + // 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; } /** @@ -1776,7 +1778,7 @@ export class CoreH5PProvider { * * @param url The file URL. * @param options Options. - * @param ignoreCache Whether to ignore cache.. + * @param ignoreCache Whether to ignore cache. * @param siteId Site ID. If not defined, current site. * @return Promise resolved with the file data. */ @@ -2135,7 +2137,6 @@ export class CoreH5PProvider { * * @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. */ protected processH5PFiles(destFolder: string, entries: (DirectoryEntry | FileEntry)[]) @@ -2227,7 +2228,6 @@ export class CoreH5PProvider { const data: any = { jsoncontent: content.params, - displayoptions: null, mainlibraryid: content.library.libraryId, timemodified: Date.now(), filtered: null, @@ -2597,40 +2597,6 @@ export class CoreH5PProvider { return db.updateRecords(this.CONTENT_TABLE, data, {id: id}); }); } - - /** - * Validate display options, updating them if needed. - * - * @param displayOptions The display options to validate. - * @param id Package ID. - */ - validateDisplayOptions(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) == false) { - 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; - } } /** @@ -2701,7 +2667,6 @@ 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. - displayoptions: number; // H5P Button display options. Not used right now. 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. diff --git a/src/core/h5p/providers/pluginfile-handler.ts b/src/core/h5p/providers/pluginfile-handler.ts index 71b82053d..a152945c0 100644 --- a/src/core/h5p/providers/pluginfile-handler.ts +++ b/src/core/h5p/providers/pluginfile-handler.ts @@ -37,17 +37,6 @@ export class CoreH5PPluginFileHandler implements CorePluginFileHandler { protected fileProvider: CoreFileProvider, protected h5pProvider: CoreH5PProvider) { } - /** - * 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. - */ - canDownloadFile(file: CoreWSExternalFile, siteId?: string): Promise { - return this.h5pProvider.getTrustedH5PFile(file.fileurl, {}, false, siteId); - } - /** * React to a file being deleted. * @@ -61,6 +50,17 @@ export class CoreH5PPluginFileHandler implements CorePluginFileHandler { 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 * CoreDomUtilsProvider.extractDownloadableFilesFromHtml. @@ -68,7 +68,7 @@ export class CoreH5PPluginFileHandler implements CorePluginFileHandler { * @param container Container where to get the URLs from. * @return {string[]} List of URLs. */ - getDownloadableFiles(container: HTMLElement): string[] { + getDownloadableFilesFromHTML(container: HTMLElement): string[] { const iframes = Array.from(container.querySelectorAll('iframe.h5p-iframe')); const urls = []; diff --git a/src/providers/file.ts b/src/providers/file.ts index 915ac5a36..9dedaedbf 100644 --- a/src/providers/file.ts +++ b/src/providers/file.ts @@ -762,7 +762,7 @@ export class CoreFileProvider { * @return Promise resolved when the entry is moved. */ moveDir(originalPath: string, newPath: string, destDirExists?: boolean): Promise { - return this.moveFileOrDir(originalPath, newPath, true); + return this.moveFileOrDir(originalPath, newPath, true, destDirExists); } /** @@ -775,7 +775,7 @@ export class CoreFileProvider { * @return Promise resolved when the entry is moved. */ moveFile(originalPath: string, newPath: string, destDirExists?: boolean): Promise { - return this.moveFileOrDir(originalPath, newPath, false); + return this.moveFileOrDir(originalPath, newPath, false, destDirExists); } /** @@ -991,11 +991,26 @@ export class CoreFileProvider { * @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)); diff --git a/src/providers/filepool.ts b/src/providers/filepool.ts index dca0d1e89..07058ee71 100644 --- a/src/providers/filepool.ts +++ b/src/providers/filepool.ts @@ -1339,7 +1339,7 @@ export class CoreFilepoolProvider { */ protected fixPluginfileURL(siteId: string, fileUrl: string, timemodified: number = 0): Promise { - return this.pluginFileDelegate.canDownloadFile({fileurl: fileUrl, timemodified: timemodified}).then((file) => { + return this.pluginFileDelegate.getDownloadableFile({fileurl: fileUrl, timemodified: timemodified}).then((file) => { return this.sitesProvider.getSite(siteId).then((site) => { return site.checkAndFixPluginfileURL(file.fileurl); diff --git a/src/providers/plugin-file-delegate.ts b/src/providers/plugin-file-delegate.ts index 01aea603e..f5582a28a 100644 --- a/src/providers/plugin-file-delegate.ts +++ b/src/providers/plugin-file-delegate.ts @@ -48,15 +48,6 @@ export interface CorePluginFileHandler { */ getComponentRevisionReplace?(args: string[]): string; - /** - * 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. - */ - canDownloadFile?(file: CoreWSExternalFile, siteId?: string): Promise; - /** * React to a file being deleted. * @@ -67,6 +58,15 @@ export interface CorePluginFileHandler { */ 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 * CoreDomUtilsProvider.extractDownloadableFilesFromHtml. @@ -74,7 +74,7 @@ export interface CorePluginFileHandler { * @param container Container where to get the URLs from. * @return {string[]} List of URLs. */ - getDownloadableFiles?(container: HTMLElement): string[]; + getDownloadableFilesFromHTML?(container: HTMLElement): string[]; /** * Get a file size. @@ -116,39 +116,6 @@ export class CorePluginFileDelegate { this.logger = logger.getInstance('CorePluginFileDelegate'); } - /** - * 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. - */ - canDownloadFile(file: CoreWSExternalFile, siteId?: string): Promise { - const handler = this.getHandlerForFile(file); - - return this.canHandlerDownloadFile(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 canHandlerDownloadFile(file: CoreWSExternalFile, handler: CorePluginFileHandler, siteId?: string) - : Promise { - - if (handler && handler.canDownloadFile) { - return handler.canDownloadFile(file, siteId).then((newFile) => { - return newFile || file; - }); - } - - return Promise.resolve(file); - } - /** * React to a file being deleted. * @@ -167,6 +134,39 @@ export class CorePluginFileDelegate { 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 handler for a certain pluginfile url. * @@ -201,14 +201,14 @@ export class CorePluginFileDelegate { * @param container Container where to get the URLs from. * @return List of URLs. */ - getDownloadableFiles(container: HTMLElement): string[] { + getDownloadableFilesFromHTML(container: HTMLElement): string[] { let files = []; for (const component in this.handlers) { const handler = this.handlers[component]; - if (handler && handler.getDownloadableFiles) { - files = files.concat(handler.getDownloadableFiles(container)); + if (handler && handler.getDownloadableFilesFromHTML) { + files = files.concat(handler.getDownloadableFilesFromHTML(container)); } } @@ -256,8 +256,8 @@ export class CorePluginFileDelegate { const handler = this.getHandlerForFile(file); // First of all check if file can be downloaded. - return this.canHandlerDownloadFile(file, handler, siteId).then((canDownload) => { - if (!canDownload) { + return this.getHandlerDownloadableFile(file, handler, siteId).then((file) => { + if (!file) { return 0; } diff --git a/src/providers/utils/dom.ts b/src/providers/utils/dom.ts index 18dec5369..564b889c2 100644 --- a/src/providers/utils/dom.ts +++ b/src/providers/utils/dom.ts @@ -286,7 +286,7 @@ export class CoreDomUtilsProvider { } // Now get other files from plugin file handlers. - urls = urls.concat(this.pluginFileDelegate.getDownloadableFiles(element)); + urls = urls.concat(this.pluginFileDelegate.getDownloadableFilesFromHTML(element)); return urls; } From 40fc0e2b0080169462bb85d820d05a060c2eb4ce Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 4 Dec 2019 13:27:41 +0100 Subject: [PATCH 191/257] MOBILE-2235 core: Use isStateDownloaded where it should --- src/components/file/file.ts | 2 +- src/core/course/providers/helper.ts | 2 +- .../providers/module-prefetch-delegate.ts | 18 ++++++++++++------ .../h5p/components/h5p-player/h5p-player.ts | 6 ++++-- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/src/components/file/file.ts b/src/components/file/file.ts index b8e7a7965..57c509107 100644 --- a/src/components/file/file.ts +++ b/src/components/file/file.ts @@ -169,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; diff --git a/src/core/course/providers/helper.ts b/src/core/course/providers/helper.ts index 82b9d03d6..9cfb9385c 100644 --- a/src/core/course/providers/helper.ts +++ b/src/core/course/providers/helper.ts @@ -1089,7 +1089,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) { diff --git a/src/core/course/providers/module-prefetch-delegate.ts b/src/core/course/providers/module-prefetch-delegate.ts index 58cda1d91..6e05b283b 100644 --- a/src/core/course/providers/module-prefetch-delegate.ts +++ b/src/core/course/providers/module-prefetch-delegate.ts @@ -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. @@ -258,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); @@ -881,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 @@ -927,7 +933,7 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { 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 {}; } diff --git a/src/core/h5p/components/h5p-player/h5p-player.ts b/src/core/h5p/components/h5p-player/h5p-player.ts index f4c0f70d7..1166c0136 100644 --- a/src/core/h5p/components/h5p-player/h5p-player.ts +++ b/src/core/h5p/components/h5p-player/h5p-player.ts @@ -25,6 +25,7 @@ import { CoreUrlUtilsProvider } from '@providers/utils/url'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreH5PProvider } from '@core/h5p/providers/h5p'; import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; +import { CoreFileHelperProvider } from '@providers/file-helper'; import { CoreConstants } from '@core/constants'; import { CoreSite } from '@classes/site'; @@ -66,7 +67,8 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy { protected appProvider: CoreAppProvider, protected domUtils: CoreDomUtilsProvider, protected pluginFileDelegate: CorePluginFileDelegate, - protected fileProvider: CoreFileProvider) { + protected fileProvider: CoreFileProvider, + protected fileHelper: CoreFileHelperProvider) { this.logger = loggerProvider.getInstance('CoreH5PPlayerComponent'); this.site = sitesProvider.getCurrentSite(); @@ -106,7 +108,7 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy { this.addResizerScript(); - if (this.canDownload && (this.state == CoreConstants.DOWNLOADED || this.state == CoreConstants.OUTDATED)) { + if (this.canDownload && this.fileHelper.isStateDownloaded(this.state)) { // Package is downloaded, use the local URL. promise = this.h5pProvider.getContentIndexFileUrl(this.urlParams.url, this.urlParams, this.siteId).catch(() => { From e0849668e3818e3018349baba2170c8535c95dad Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Mon, 9 Dec 2019 15:34:08 +0100 Subject: [PATCH 192/257] MOBILE-2235 h5p: Support and use isEnabled function in file delegate --- .../folder/providers/pluginfile-handler.ts | 9 +++ .../mod/forum/providers/prefetch-handler.ts | 2 +- .../glossary/providers/prefetch-handler.ts | 2 +- .../mod/imscp/providers/pluginfile-handler.ts | 9 +++ .../mod/lesson/providers/prefetch-handler.ts | 3 +- .../mod/page/providers/pluginfile-handler.ts | 9 +++ .../mod/quiz/providers/prefetch-handler.ts | 2 +- .../resource/providers/pluginfile-handler.ts | 9 +++ .../mod/scorm/providers/pluginfile-handler.ts | 9 +++ src/classes/delegate.ts | 12 ++-- .../course/classes/module-prefetch-handler.ts | 4 +- src/core/course/providers/helper.ts | 6 +- src/core/h5p/providers/pluginfile-handler.ts | 11 ++- src/core/question/providers/helper.ts | 2 +- src/providers/filepool.ts | 53 +++++++++++++++ src/providers/plugin-file-delegate.ts | 67 +++++-------------- src/providers/utils/dom.ts | 17 +++-- upgrade.txt | 4 ++ 18 files changed, 160 insertions(+), 70 deletions(-) diff --git a/src/addon/mod/folder/providers/pluginfile-handler.ts b/src/addon/mod/folder/providers/pluginfile-handler.ts index cc4190e0b..35e816d94 100644 --- a/src/addon/mod/folder/providers/pluginfile-handler.ts +++ b/src/addon/mod/folder/providers/pluginfile-handler.ts @@ -47,4 +47,13 @@ export class AddonModFolderPluginFileHandler implements CorePluginFileHandler { // Component + Filearea + Revision return '/mod_folder/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/forum/providers/prefetch-handler.ts b/src/addon/mod/forum/providers/prefetch-handler.ts index 2f1e05b5d..743d99b97 100644 --- a/src/addon/mod/forum/providers/prefetch-handler.ts +++ b/src/addon/mod/forum/providers/prefetch-handler.ts @@ -96,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)); } }); diff --git a/src/addon/mod/glossary/providers/prefetch-handler.ts b/src/addon/mod/glossary/providers/prefetch-handler.ts index 34a94b142..590294ad7 100644 --- a/src/addon/mod/glossary/providers/prefetch-handler.ts +++ b/src/addon/mod/glossary/providers/prefetch-handler.ts @@ -93,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)); } }); diff --git a/src/addon/mod/imscp/providers/pluginfile-handler.ts b/src/addon/mod/imscp/providers/pluginfile-handler.ts index d67c455a3..4d283a90e 100644 --- a/src/addon/mod/imscp/providers/pluginfile-handler.ts +++ b/src/addon/mod/imscp/providers/pluginfile-handler.ts @@ -54,4 +54,13 @@ export class AddonModImscpPluginFileHandler implements CorePluginFileHandler { // 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/lesson/providers/prefetch-handler.ts b/src/addon/mod/lesson/providers/prefetch-handler.ts index f0732535e..46ef36db2 100644 --- a/src/addon/mod/lesson/providers/prefetch-handler.ts +++ b/src/addon/mod/lesson/providers/prefetch-handler.ts @@ -419,7 +419,8 @@ export class AddonModLessonPrefetchHandler extends CoreCourseActivityPrefetchHan return; } answerPage.answerdata.answers.forEach((answer) => { - files.push(...this.domUtils.extractDownloadableFilesFromHtmlAsFakeFileObjects(answer[0])); + files.push(...this.filepoolProvider.extractDownloadableFilesFromHtmlAsFakeFileObjects( + answer[0])); }); }); diff --git a/src/addon/mod/page/providers/pluginfile-handler.ts b/src/addon/mod/page/providers/pluginfile-handler.ts index c17e6b626..1c93e3e21 100644 --- a/src/addon/mod/page/providers/pluginfile-handler.ts +++ b/src/addon/mod/page/providers/pluginfile-handler.ts @@ -47,4 +47,13 @@ export class AddonModPagePluginFileHandler implements CorePluginFileHandler { // Component + Filearea + Revision return '/mod_page/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/quiz/providers/prefetch-handler.ts b/src/addon/mod/quiz/providers/prefetch-handler.ts index fdd66c7e5..0040dd8e8 100644 --- a/src/addon/mod/quiz/providers/prefetch-handler.ts +++ b/src/addon/mod/quiz/providers/prefetch-handler.ts @@ -126,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)); } })); } diff --git a/src/addon/mod/resource/providers/pluginfile-handler.ts b/src/addon/mod/resource/providers/pluginfile-handler.ts index b0b41368d..f9179a955 100644 --- a/src/addon/mod/resource/providers/pluginfile-handler.ts +++ b/src/addon/mod/resource/providers/pluginfile-handler.ts @@ -47,4 +47,13 @@ export class AddonModResourcePluginFileHandler implements CorePluginFileHandler // 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/scorm/providers/pluginfile-handler.ts b/src/addon/mod/scorm/providers/pluginfile-handler.ts index ccb1a3a6b..6cd13e393 100644 --- a/src/addon/mod/scorm/providers/pluginfile-handler.ts +++ b/src/addon/mod/scorm/providers/pluginfile-handler.ts @@ -47,4 +47,13 @@ export class AddonModScormPluginFileHandler implements CorePluginFileHandler { // Component + Filearea + Revision return '/mod_scorm/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/classes/delegate.ts b/src/classes/delegate.ts index db31ff907..e342c1e62 100644 --- a/src/classes/delegate.ts +++ b/src/classes/delegate.ts @@ -237,14 +237,16 @@ export class CoreDelegate { * @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; } @@ -282,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(() => { diff --git a/src/core/course/classes/module-prefetch-handler.ts b/src/core/course/classes/module-prefetch-handler.ts index 0448dcd33..815cbf5c4 100644 --- a/src/core/course/classes/module-prefetch-handler.ts +++ b/src/core/course/classes/module-prefetch-handler.ts @@ -195,12 +195,12 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref 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 []; diff --git a/src/core/course/providers/helper.ts b/src/core/course/providers/helper.ts index 9cfb9385c..0501ba682 100644 --- a/src/core/course/providers/helper.ts +++ b/src/core/course/providers/helper.ts @@ -438,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 = { @@ -454,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; } } @@ -1449,7 +1449,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) diff --git a/src/core/h5p/providers/pluginfile-handler.ts b/src/core/h5p/providers/pluginfile-handler.ts index a152945c0..13657fc2d 100644 --- a/src/core/h5p/providers/pluginfile-handler.ts +++ b/src/core/h5p/providers/pluginfile-handler.ts @@ -63,7 +63,7 @@ export class CoreH5PPluginFileHandler implements CorePluginFileHandler { /** * Given an HTML element, get the URLs of the files that should be downloaded and weren't treated by - * CoreDomUtilsProvider.extractDownloadableFilesFromHtml. + * CoreFilepoolProvider.extractDownloadableFilesFromHtml. * * @param container Container where to get the URLs from. * @return {string[]} List of URLs. @@ -103,6 +103,15 @@ export class CoreH5PPluginFileHandler implements CorePluginFileHandler { }); } + /** + * 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. * diff --git a/src/core/question/providers/helper.ts b/src/core/question/providers/helper.ts index 4b7da5ea3..0cb77a0b0 100644 --- a/src/core/question/providers/helper.ts +++ b/src/core/question/providers/helper.ts @@ -516,7 +516,7 @@ export class CoreQuestionHelperProvider { */ prefetchQuestionFiles(question: any, component?: string, componentId?: string | number, siteId?: string, usageId?: number) : Promise { - const urls = this.domUtils.extractDownloadableFilesFromHtml(question.html); + const urls = this.filepoolProvider.extractDownloadableFilesFromHtml(question.html); if (!component) { component = CoreQuestionProvider.COMPONENT; diff --git a/src/providers/filepool.ts b/src/providers/filepool.ts index 07058ee71..9e8fa5790 100644 --- a/src/providers/filepool.ts +++ b/src/providers/filepool.ts @@ -1239,6 +1239,59 @@ 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. diff --git a/src/providers/plugin-file-delegate.ts b/src/providers/plugin-file-delegate.ts index f5582a28a..17354d4b5 100644 --- a/src/providers/plugin-file-delegate.ts +++ b/src/providers/plugin-file-delegate.ts @@ -13,18 +13,17 @@ // 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. - */ - name: string; +export interface CorePluginFileHandler extends CoreDelegateHandler { /** * The "component" of the handler. It should match the "component" of pluginfile URLs. @@ -69,7 +68,7 @@ export interface CorePluginFileHandler { /** * Given an HTML element, get the URLs of the files that should be downloaded and weren't treated by - * CoreDomUtilsProvider.extractDownloadableFilesFromHtml. + * CoreFilepoolProvider.extractDownloadableFilesFromHtml. * * @param container Container where to get the URLs from. * @return {string[]} List of URLs. @@ -108,12 +107,13 @@ export interface CorePluginFileHandler { * 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); } /** @@ -167,18 +167,6 @@ export class CorePluginFileDelegate { return Promise.resolve(file); } - /** - * Get the handler for a certain pluginfile url. - * - * @param component Component of the plugin. - * @return Handler. Undefined if no handler found for the plugin. - */ - protected getPluginHandler(component: string): CorePluginFileHandler { - if (typeof this.handlers[component] != 'undefined') { - return this.handlers[component]; - } - } - /** * Get the RegExp of the component and filearea described in the URL. * @@ -187,7 +175,7 @@ export class CorePluginFileDelegate { */ 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); @@ -196,7 +184,7 @@ export class CorePluginFileDelegate { /** * Given an HTML element, get the URLs of the files that should be downloaded and weren't treated by - * CoreDomUtilsProvider.extractDownloadableFilesFromHtml. + * CoreFilepoolProvider.extractDownloadableFilesFromHtml. * * @param container Container where to get the URLs from. * @return List of URLs. @@ -204,8 +192,8 @@ export class CorePluginFileDelegate { getDownloadableFilesFromHTML(container: HTMLElement): string[] { let files = []; - for (const component in this.handlers) { - const handler = this.handlers[component]; + for (const component in this.enabledHandlers) { + const handler = this.enabledHandlers[component]; if (handler && handler.getDownloadableFilesFromHTML) { files = files.concat(handler.getDownloadableFilesFromHTML(container)); @@ -278,8 +266,8 @@ export class CorePluginFileDelegate { * @return Handler. */ protected getHandlerForFile(file: CoreWSExternalFile): CorePluginFileHandler { - for (const component in this.handlers) { - const handler = this.handlers[component]; + for (const component in this.enabledHandlers) { + const handler = this.enabledHandlers[component]; if (handler && handler.shouldHandleFile && handler.shouldHandleFile(file)) { return handler; @@ -287,25 +275,6 @@ export class CorePluginFileDelegate { } } - /** - * Register a handler. - * - * @param handler The handler to register. - * @return True if registered successfully, false otherwise. - */ - registerHandler(handler: CorePluginFileHandler): boolean { - if (typeof this.handlers[handler.component || handler.name] !== 'undefined') { - this.logger.log(`Handler '${handler.component}' already registered`); - - return false; - } - - this.logger.log(`Registered handler '${handler.component}'`); - this.handlers[handler.component || handler.name] = handler; - - return true; - } - /** * Removes the revision number from a file URL. * @@ -315,7 +284,7 @@ export class CorePluginFileDelegate { */ 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); diff --git a/src/providers/utils/dom.ts b/src/providers/utils/dom.ts index 564b889c2..6bc976dd6 100644 --- a/src/providers/utils/dom.ts +++ b/src/providers/utils/dom.ts @@ -22,7 +22,7 @@ import { TranslateService } from '@ngx-translate/core'; import { CoreTextUtilsProvider } from './text'; import { CoreAppProvider } from '../app'; import { CoreConfigProvider } from '../config'; -import { CorePluginFileDelegate } from '../plugin-file-delegate'; +import { CoreLoggerProvider } from '../logger'; import { CoreUrlUtilsProvider } from './url'; import { CoreFileProvider } from '@providers/file'; import { CoreConstants } from '@core/constants'; @@ -62,6 +62,7 @@ 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, @@ -76,7 +77,9 @@ export class CoreDomUtilsProvider { private sanitizer: DomSanitizer, private popoverCtrl: PopoverController, private fileProvider: CoreFileProvider, - private pluginFileDelegate: CorePluginFileDelegate) { + loggerProvider: CoreLoggerProvider) { + + this.logger = loggerProvider.getInstance('CoreDomUtilsProvider'); // Check if debug messages should be displayed. configProvider.get(CoreConstants.SETTINGS_DEBUG_DISPLAY, false).then((debugDisplay) => { @@ -260,9 +263,13 @@ export class CoreDomUtilsProvider { * * @param html HTML code. * @return List of file urls. + * @deprecated since 3.8. Use CoreFilepoolProvider.extractDownloadableFilesFromHtml instead. */ extractDownloadableFilesFromHtml(html: string): string[] { - let urls = []; + this.logger.error('The function extractDownloadableFilesFromHtml has been moved to CoreFilepoolProvider.' + + ' Please use that function instead of this one.'); + + const urls = []; let elements; const element = this.convertToElement(html); @@ -285,9 +292,6 @@ export class CoreDomUtilsProvider { } } - // Now get other files from plugin file handlers. - urls = urls.concat(this.pluginFileDelegate.getDownloadableFilesFromHTML(element)); - return urls; } @@ -296,6 +300,7 @@ export class CoreDomUtilsProvider { * * @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); 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. From 96ccb97ed05ddc98a89766e7904dec412c9a1aa0 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 10 Dec 2019 09:08:00 +0100 Subject: [PATCH 193/257] MOBILE-3247 survey: Fix issues with last 2 questions in survey This was a regression caused by MOBILE-3109 --- src/addon/mod/survey/providers/helper.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/addon/mod/survey/providers/helper.ts b/src/addon/mod/survey/providers/helper.ts index 69807e090..dc2836b01 100644 --- a/src/addon/mod/survey/providers/helper.ts +++ b/src/addon/mod/survey/providers/helper.ts @@ -110,7 +110,7 @@ export class AddonModSurveyHelperProvider { return; } - } else if (q1.multi && q1.multi.length === 0) { + } else if (q1.multiArray && q1.multiArray.length === 0) { // It's a single question. q1.name = 'q' + q1.id; q1.num = num++; From 6e5f8319e4c1f505e7ad12238c9ce3da93b907ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 10 Dec 2019 12:13:55 +0100 Subject: [PATCH 194/257] MOBILE-3213 lang: Use a variable to determine minimum version --- scripts/langindex.json | 2 +- src/assets/lang/en.json | 5 ++--- src/classes/site.ts | 4 +++- src/core/login/lang/en.json | 3 +-- src/lang/en.json | 2 +- src/providers/sites.ts | 19 +++++++++++-------- src/providers/ws.ts | 5 +++-- 7 files changed, 22 insertions(+), 18 deletions(-) diff --git a/scripts/langindex.json b/scripts/langindex.json index 2b696380e..d5a8c1769 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -1620,8 +1620,8 @@ "core.h5p.originator": "h5p", "core.h5p.pd": "h5p", "core.h5p.pddl": "h5p", - "core.h5p.play": "local_moodlemobileapp", "core.h5p.pdm": "h5p", + "core.h5p.play": "local_moodlemobileapp", "core.h5p.resizescript": "h5p", "core.h5p.resubmitScores": "h5p", "core.h5p.reuse": "h5p", diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index 5a3093332..93693ba6f 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -1328,7 +1328,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.", @@ -1670,7 +1670,6 @@ "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.checksiteversion": "Check that your site uses Moodle 3.1 or later.", "core.login.confirmdeletesite": "Are you sure you want to delete the site {{sitename}}?", "core.login.connect": "Connect!", "core.login.connecttomoodle": "Connect to Moodle", @@ -1700,7 +1699,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 site version. The Moodle app only supports Moodle systems 3.1 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.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", diff --git a/src/classes/site.ts b/src/classes/site.ts index 574f6be44..e7776a33c 100644 --- a/src/classes/site.ts +++ b/src/classes/site.ts @@ -212,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 = [ @@ -657,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. diff --git a/src/core/login/lang/en.json b/src/core/login/lang/en.json index 0e7cbf3cf..fe816b435 100644 --- a/src/core/login/lang/en.json +++ b/src/core/login/lang/en.json @@ -2,7 +2,6 @@ "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.", @@ -38,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 site version. The Moodle app only supports Moodle systems 3.1 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.

", + "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/lang/en.json b/src/lang/en.json index bb56a4a34..4701fe667 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.", diff --git a/src/providers/sites.ts b/src/providers/sites.ts index 857a52058..0dafd3e3c 100644 --- a/src/providers/sites.ts +++ b/src/providers/sites.ts @@ -371,7 +371,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}); } }); }); @@ -463,7 +463,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); @@ -508,7 +509,7 @@ export class CoreSitesProvider { 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')) { @@ -550,7 +551,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 }; @@ -582,7 +583,7 @@ export class CoreSitesProvider { } } }, () => { - return Promise.reject(this.translate.instant('core.cannotconnect')); + return Promise.reject(this.translate.instant('core.cannotconnect', {$a: CoreSite.MINIMUM_MOODLE_VERSION})); }); } @@ -676,7 +677,8 @@ export class CoreSitesProvider { */ protected treatInvalidAppVersion(result: number, siteUrl: string, siteId?: string): Promise { let errorCode, - errorKey; + errorKey, + translateParams; switch (result) { case this.MOODLE_APP: @@ -690,6 +692,7 @@ export class CoreSitesProvider { default: errorCode = 'invalidmoodleversion'; errorKey = 'core.login.invalidmoodleversion'; + translateParams = {$a: CoreSite.MINIMUM_MOODLE_VERSION}; } let promise; @@ -702,7 +705,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 }); @@ -757,7 +760,7 @@ export class CoreSitesProvider { } const version31 = 2016052300, - release31 = '3.1'; + release31 = CoreSite.MINIMUM_MOODLE_VERSION; // Try to validate by version. if (info.version) { diff --git a/src/providers/ws.ts b/src/providers/ws.ts index e98455c4a..cf8aa1ef8 100644 --- a/src/providers/ws.ts +++ b/src/providers/ws.ts @@ -315,11 +315,12 @@ export class CoreWSProvider { * * @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 { From 49d33fb9e4cb4288a8b92591fc1bfb0b3dfbd2c9 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 10 Dec 2019 13:29:20 +0100 Subject: [PATCH 195/257] MOBILE-3213 h5p: Fix resize in h5p.org packages --- .../h5p-player/core-h5p-player.html | 2 +- .../h5p/components/h5p-player/h5p-player.ts | 22 +++++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/core/h5p/components/h5p-player/core-h5p-player.html b/src/core/h5p/components/h5p-player/core-h5p-player.html index 3cfd58ccb..e98b6ab63 100644 --- a/src/core/h5p/components/h5p-player/core-h5p-player.html +++ b/src/core/h5p/components/h5p-player/core-h5p-player.html @@ -9,5 +9,5 @@ - + diff --git a/src/core/h5p/components/h5p-player/h5p-player.ts b/src/core/h5p/components/h5p-player/h5p-player.ts index 1166c0136..359be26cc 100644 --- a/src/core/h5p/components/h5p-player/h5p-player.ts +++ b/src/core/h5p/components/h5p-player/h5p-player.ts @@ -106,8 +106,6 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy { let promise; - this.addResizerScript(); - if (this.canDownload && this.fileHelper.isStateDownloaded(this.state)) { // Package is downloaded, use the local URL. promise = this.h5pProvider.getContentIndexFileUrl(this.urlParams.url, this.urlParams, this.siteId).catch(() => { @@ -145,6 +143,7 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy { }); } }).finally(() => { + this.addResizerScript(); this.loading = false; this.showPackage = true; @@ -211,6 +210,11 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy { * Add the resizer script if it hasn't been added already. */ protected addResizerScript(): void { + if (document.head.querySelector('#core-h5p-resizer-script') != null) { + // Script already added, don't add it again. + return; + } + const script = document.createElement('script'); script.id = 'core-h5p-resizer-script'; script.type = 'text/javascript'; @@ -264,6 +268,20 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy { }); } + /** + * Send hello to the H5P iframe. + * + * @param iframe The iframe. + */ + sendHello(iframe?: HTMLIFrameElement): void { + const ready = { + context: 'h5p', + action: 'ready' + }; + + iframe.contentWindow.postMessage(ready, '*'); + } + /** * Component destroyed. */ From 0824045e9af4face19b09a36acf1a141adf0dc63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 10 Dec 2019 15:11:20 +0100 Subject: [PATCH 196/257] MOBILE-3213 lang: Fix minimum version type --- src/classes/site.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/classes/site.ts b/src/classes/site.ts index e7776a33c..845a9496f 100644 --- a/src/classes/site.ts +++ b/src/classes/site.ts @@ -212,7 +212,7 @@ export class CoreSite { 3.6: 2018120300, 3.7: 2019052000 }; - static MINIMUM_MOODLE_VERSION = 3.1; + static MINIMUM_MOODLE_VERSION = '3.1'; // Possible cache update frequencies. protected UPDATE_FREQUENCIES = [ From b427c8a52aa6b52461e8cdc692cf610215009b9a Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 10 Dec 2019 16:20:06 +0100 Subject: [PATCH 197/257] MOBILE-3213 h5p: Support fullscreen in H5P iframe --- config.xml | 7 ------- src/components/iframe/core-iframe.html | 2 +- src/components/iframe/iframe.ts | 4 ++++ src/core/h5p/components/h5p-player/core-h5p-player.html | 2 +- src/core/h5p/components/h5p-player/h5p-player.scss | 2 +- 5 files changed, 7 insertions(+), 10 deletions(-) diff --git a/config.xml b/config.xml index d7439c757..89d2f5b5e 100644 --- a/config.xml +++ b/config.xml @@ -79,18 +79,11 @@ - - - diff --git a/src/components/iframe/core-iframe.html b/src/components/iframe/core-iframe.html index 7bdeac425..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.ts b/src/components/iframe/iframe.ts index f6517fde6..516012a8f 100644 --- a/src/components/iframe/iframe.ts +++ b/src/components/iframe/iframe.ts @@ -21,6 +21,7 @@ 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({ @@ -33,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; @@ -46,6 +48,7 @@ export class CoreIframeComponent implements OnInit, OnChanges { protected sanitizer: DomSanitizer, protected navCtrl: NavController, protected urlUtils: CoreUrlUtilsProvider, + protected utils: CoreUtilsProvider, @Optional() protected svComponent: CoreSplitViewComponent) { this.logger = logger.getInstance('CoreIframe'); @@ -60,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); diff --git a/src/core/h5p/components/h5p-player/core-h5p-player.html b/src/core/h5p/components/h5p-player/core-h5p-player.html index e98b6ab63..22981316f 100644 --- a/src/core/h5p/components/h5p-player/core-h5p-player.html +++ b/src/core/h5p/components/h5p-player/core-h5p-player.html @@ -9,5 +9,5 @@
- + diff --git a/src/core/h5p/components/h5p-player/h5p-player.scss b/src/core/h5p/components/h5p-player/h5p-player.scss index 8a4875ac9..e4ccc242d 100644 --- a/src/core/h5p/components/h5p-player/h5p-player.scss +++ b/src/core/h5p/components/h5p-player/h5p-player.scss @@ -10,7 +10,7 @@ ion-app.app-root core-h5p-player { background: url('../assets/img/icons/h5p.svg') center top 25px / 100px auto no-repeat $core-h5p-placeholder-bg-color; color: $core-h5p-placeholder-text-color; - .icon { + .icon:not([color="success"]) { color: $core-h5p-placeholder-text-color; } From 2846e6838a9abd6c14c4fbb662ba732b8c0e9eb1 Mon Sep 17 00:00:00 2001 From: Noel De Martin Date: Tue, 10 Dec 2019 17:06:04 +0100 Subject: [PATCH 198/257] MOBILE-2159 geolocation: Improve error message when permission is denied --- src/addon/mod/data/fields/latlong/component/latlong.ts | 10 ++++++++++ src/addon/mod/data/lang/en.json | 1 + src/assets/lang/en.json | 1 + 3 files changed, 12 insertions(+) diff --git a/src/addon/mod/data/fields/latlong/component/latlong.ts b/src/addon/mod/data/fields/latlong/component/latlong.ts index 767487e9d..013c4586e 100644 --- a/src/addon/mod/data/fields/latlong/component/latlong.ts +++ b/src/addon/mod/data/fields/latlong/component/latlong.ts @@ -117,9 +117,19 @@ export class AddonModDataFieldLatlongComponent extends AddonModDataFieldPluginCo 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.isPositionError(error) && error.code === error.PERMISSION_DENIED) { + this.domUtils.showErrorModal('addon.mod_data.locationpermissiondenied', true); + + return; + } + this.domUtils.showErrorModalDefault(error, 'Error getting location'); }).finally(() => { modal.dismiss(); }); } + + protected isPositionError(error?: any): error is PositionError { + return error && 'code' in error && 'PERMISSION_DENIED' in error; + } } diff --git a/src/addon/mod/data/lang/en.json b/src/addon/mod/data/lang/en.json index 54de61215..de89c6bca 100644 --- a/src/addon/mod/data/lang/en.json +++ b/src/addon/mod/data/lang/en.json @@ -22,6 +22,7 @@ "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", diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index 93693ba6f..e0fb0fd71 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -501,6 +501,7 @@ "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", From 74362d2abab22e9799a8dd31db140c4f0728a534 Mon Sep 17 00:00:00 2001 From: Noel De Martin Date: Tue, 10 Dec 2019 17:23:26 +0100 Subject: [PATCH 199/257] MOBILE-2159 geolocation: Add new message key to langindex.json --- scripts/langindex.json | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/langindex.json b/scripts/langindex.json index d5a8c1769..5200cc6d6 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -502,6 +502,7 @@ "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", From 8d30c0be97e6d205bba14764217441ce1234b95d Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 11 Dec 2019 08:21:20 +0100 Subject: [PATCH 200/257] MOBILE-3213 core: Run script that removes types from JSDoc --- .../block/myoverview/components/myoverview/myoverview.ts | 2 +- src/addon/calendar/pages/list/list.ts | 2 -- src/addon/mod/data/fields/latlong/component/latlong.ts | 2 +- src/addon/mod/forum/providers/forum.ts | 2 +- src/classes/site.ts | 2 +- src/core/course/providers/helper.ts | 2 +- src/core/filter/providers/default-filter.ts | 2 +- src/core/filter/providers/delegate.ts | 2 +- src/core/h5p/classes/content-validator.ts | 4 ++-- src/core/h5p/providers/pluginfile-handler.ts | 2 +- src/providers/app.ts | 2 +- src/providers/plugin-file-delegate.ts | 2 +- src/providers/utils/text.ts | 6 +++--- 13 files changed, 15 insertions(+), 17 deletions(-) diff --git a/src/addon/block/myoverview/components/myoverview/myoverview.ts b/src/addon/block/myoverview/components/myoverview/myoverview.ts index 26c82500a..b6f062542 100644 --- a/src/addon/block/myoverview/components/myoverview/myoverview.ts +++ b/src/addon/block/myoverview/components/myoverview/myoverview.ts @@ -327,7 +327,7 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem /** * Set selected courses filter. * - * @param {string} filter Filter name to set. + * @param filter Filter name to set. */ protected setCourseFilter(filter: string): void { this.selectedFilter = filter; diff --git a/src/addon/calendar/pages/list/list.ts b/src/addon/calendar/pages/list/list.ts index a9857a2cf..250c9054b 100644 --- a/src/addon/calendar/pages/list/list.ts +++ b/src/addon/calendar/pages/list/list.ts @@ -437,8 +437,6 @@ export class AddonCalendarListPage implements OnDestroy { }); } - /** - */ protected filterEvents(): void { this.filteredEvents = this.calendarHelper.getFilteredEvents(this.events, this.filter, this.categories); } diff --git a/src/addon/mod/data/fields/latlong/component/latlong.ts b/src/addon/mod/data/fields/latlong/component/latlong.ts index 767487e9d..2e154961e 100644 --- a/src/addon/mod/data/fields/latlong/component/latlong.ts +++ b/src/addon/mod/data/fields/latlong/component/latlong.ts @@ -101,7 +101,7 @@ export class AddonModDataFieldLatlongComponent extends AddonModDataFieldPluginCo /** * Get user location. * - * @param {Event} $event The event. + * @param $event The event. */ getLocation(event: Event): void { event.preventDefault(); diff --git a/src/addon/mod/forum/providers/forum.ts b/src/addon/mod/forum/providers/forum.ts index 2b354bfb8..a852c2aec 100644 --- a/src/addon/mod/forum/providers/forum.ts +++ b/src/addon/mod/forum/providers/forum.ts @@ -600,7 +600,7 @@ export class AddonModForumProvider { * @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. + * 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 { diff --git a/src/classes/site.ts b/src/classes/site.ts index 574f6be44..5d738185b 100644 --- a/src/classes/site.ts +++ b/src/classes/site.ts @@ -1903,7 +1903,7 @@ export class CoreSite { * Get a certain cache expiration delay. * * @param updateFrequency The update frequency of the entry. - * @return {number} Expiration delay. + * @return Expiration delay. */ getExpirationDelay(updateFrequency?: number): number { let expirationDelay = this.UPDATE_FREQUENCIES[updateFrequency] || this.UPDATE_FREQUENCIES[CoreSite.FREQUENCY_USUALLY]; diff --git a/src/core/course/providers/helper.ts b/src/core/course/providers/helper.ts index 0501ba682..ffbc281b5 100644 --- a/src/core/course/providers/helper.ts +++ b/src/core/course/providers/helper.ts @@ -1127,7 +1127,7 @@ export class CoreCourseHelperProvider { * @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. + * 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. diff --git a/src/core/filter/providers/default-filter.ts b/src/core/filter/providers/default-filter.ts index f5f307500..aec2580e1 100644 --- a/src/core/filter/providers/default-filter.ts +++ b/src/core/filter/providers/default-filter.ts @@ -65,7 +65,7 @@ export class CoreFilterDefaultHandler implements CoreFilterHandler { /** * 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; diff --git a/src/core/filter/providers/delegate.ts b/src/core/filter/providers/delegate.ts index 642a454ca..fef46c423 100644 --- a/src/core/filter/providers/delegate.ts +++ b/src/core/filter/providers/delegate.ts @@ -236,7 +236,7 @@ export class CoreFilterDelegate extends CoreDelegate { * @param filter Filter to check. * @param options Options passed to the filters. * @param site Site. If not defined, current site. - * @return {Promise} Promise resolved with true: whether the filter should be applied. + * @return Promise resolved with true: whether the filter should be applied. */ shouldBeApplied(filters: CoreFilterFilter[], options: CoreFilterFormatTextOptions, site?: CoreSite): Promise { // Wait for filters to be initialized. diff --git a/src/core/h5p/classes/content-validator.ts b/src/core/h5p/classes/content-validator.ts index 18ae59dc3..3d4baac9f 100644 --- a/src/core/h5p/classes/content-validator.ts +++ b/src/core/h5p/classes/content-validator.ts @@ -681,8 +681,8 @@ export class CoreH5PContentValidator { * Processes an HTML tag. * * @param m An array with various meaning depending on the value of store. - * If store is TRUE then the array contains the allowed tags. - * If store is FALSE then the array has one element, the HTML tag to process. + * If store is TRUE then the array contains the allowed tags. + * If store is FALSE then the array has one element, the HTML tag to process. * @param store Whether to store m. * @return string If the element isn't allowed, an empty string. Otherwise, the cleaned up version of the HTML element. */ diff --git a/src/core/h5p/providers/pluginfile-handler.ts b/src/core/h5p/providers/pluginfile-handler.ts index 13657fc2d..3a38e6381 100644 --- a/src/core/h5p/providers/pluginfile-handler.ts +++ b/src/core/h5p/providers/pluginfile-handler.ts @@ -66,7 +66,7 @@ export class CoreH5PPluginFileHandler implements CorePluginFileHandler { * CoreFilepoolProvider.extractDownloadableFilesFromHtml. * * @param container Container where to get the URLs from. - * @return {string[]} List of URLs. + * @return List of URLs. */ getDownloadableFilesFromHTML(container: HTMLElement): string[] { const iframes = Array.from(container.querySelectorAll('iframe.h5p-iframe')); diff --git a/src/providers/app.ts b/src/providers/app.ts index 7aa6a6a70..f2071b076 100644 --- a/src/providers/app.ts +++ b/src/providers/app.ts @@ -164,7 +164,7 @@ export class CoreAppProvider { /** * Returns whether the user agent is controlled by automation. I.e. Behat testing. * - * @return {boolean} True if the user agent is controlled by automation, false otherwise. + * @return True if the user agent is controlled by automation, false otherwise. */ static isAutomated(): boolean { return !!navigator.webdriver; diff --git a/src/providers/plugin-file-delegate.ts b/src/providers/plugin-file-delegate.ts index 17354d4b5..469518698 100644 --- a/src/providers/plugin-file-delegate.ts +++ b/src/providers/plugin-file-delegate.ts @@ -71,7 +71,7 @@ export interface CorePluginFileHandler extends CoreDelegateHandler { * CoreFilepoolProvider.extractDownloadableFilesFromHtml. * * @param container Container where to get the URLs from. - * @return {string[]} List of URLs. + * @return List of URLs. */ getDownloadableFilesFromHTML?(container: HTMLElement): string[]; diff --git a/src/providers/utils/text.ts b/src/providers/utils/text.ts index 0a858127e..d4826c942 100644 --- a/src/providers/utils/text.ts +++ b/src/providers/utils/text.ts @@ -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) { @@ -742,7 +742,7 @@ export class CoreTextUtilsProvider { * @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. + * 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 { From f675a790ea6b958df223869a6a7f343515a03cd6 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 11 Dec 2019 08:52:38 +0100 Subject: [PATCH 201/257] MOBILE-3213 core: Lock plugin and libraries versions --- config.xml | 44 ++++++------ package-lock.json | 176 +++++++++++++-------------------------------- package.json | 178 +++++++++++++++++++++++----------------------- 3 files changed, 161 insertions(+), 237 deletions(-) diff --git a/config.xml b/config.xml index 89d2f5b5e..5c29845ee 100644 --- a/config.xml +++ b/config.xml @@ -141,36 +141,36 @@
- - + + - - - - + + + + - - + + - - + + - - - + + + - - - - - - - - - + + + + + + + + + diff --git a/package-lock.json b/package-lock.json index 2b45e89dd..3c9f1767d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -503,8 +503,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -525,14 +524,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -547,20 +544,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -677,8 +671,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -690,7 +683,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -705,7 +697,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -713,14 +704,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -739,7 +728,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -820,8 +808,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -833,7 +820,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -919,8 +905,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -956,7 +941,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -976,7 +960,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -1020,14 +1003,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -2650,9 +2631,9 @@ "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", @@ -3176,9 +3157,9 @@ "integrity": "sha512-YqrZfYgbGTS20SFID0mrRxud312VH072QVlFonCAkPgtGg1Svy7lELOCNFd+KU7t4mVtZeTEjZPEeefvjaetwQ==" }, "cordova-plugin-ionic-keyboard": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/cordova-plugin-ionic-keyboard/-/cordova-plugin-ionic-keyboard-2.1.3.tgz", - "integrity": "sha512-6ucQ6FdlLdBm8kJfFnzozmBTjru/0xekHP/dAhjoCZggkGRlgs8TsUJFkxa/bV+qi7Dlo50JjmpE4UMWAO+aOQ==" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cordova-plugin-ionic-keyboard/-/cordova-plugin-ionic-keyboard-2.2.0.tgz", + "integrity": "sha512-yDUG+9ieKVRitq5mGlNxjaZh/MgEhFFIgTIPhqSbUaQ8UuZbawy5mhJAVClqY97q8/rcQtL6dCDa7x2sEtCLcA==" }, "cordova-plugin-local-notification": { "version": "git+https://github.com/moodlemobile/cordova-plugin-local-notification.git#c7430c4f4f8b8c82d051aea49d87da0b4f6a8b1e", @@ -5243,8 +5224,7 @@ }, "ansi-regex": { "version": "2.1.1", - "bundled": true, - "optional": true + "bundled": true }, "aproba": { "version": "1.2.0", @@ -5262,13 +5242,11 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, - "optional": true + "bundled": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5281,18 +5259,15 @@ }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "concat-map": { "version": "0.0.1", - "bundled": true, - "optional": true + "bundled": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, - "optional": true + "bundled": true }, "core-util-is": { "version": "1.0.2", @@ -5395,8 +5370,7 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, - "optional": true + "bundled": true }, "ini": { "version": "1.3.5", @@ -5406,7 +5380,6 @@ "is-fullwidth-code-point": { "version": "1.0.0", "bundled": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5419,20 +5392,17 @@ "minimatch": { "version": "3.0.4", "bundled": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true, - "optional": true + "bundled": true }, "minipass": { "version": "2.3.5", "bundled": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -5449,7 +5419,6 @@ "mkdirp": { "version": "0.5.1", "bundled": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -5528,8 +5497,7 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "optional": true + "bundled": true }, "object-assign": { "version": "4.1.1", @@ -5539,7 +5507,6 @@ "once": { "version": "1.4.0", "bundled": true, - "optional": true, "requires": { "wrappy": "1" } @@ -5615,8 +5582,7 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, - "optional": true + "bundled": true }, "safer-buffer": { "version": "2.1.2", @@ -5646,7 +5612,6 @@ "string-width": { "version": "1.0.2", "bundled": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5664,7 +5629,6 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5703,13 +5667,11 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, - "optional": true + "bundled": true }, "yallist": { "version": "3.0.3", - "bundled": true, - "optional": true + "bundled": true } } }, @@ -6136,8 +6098,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -6158,14 +6119,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -6180,20 +6139,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -6310,8 +6266,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -6323,7 +6278,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -6338,7 +6292,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -6346,14 +6299,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -6372,7 +6323,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -6453,8 +6403,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -6466,7 +6415,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -6552,8 +6500,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -6589,7 +6536,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -6609,7 +6555,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -6653,14 +6598,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, @@ -12926,8 +12869,7 @@ "ansi-regex": { "version": "2.1.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "aproba": { "version": "1.2.0", @@ -12948,14 +12890,12 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, - "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -12970,20 +12910,17 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "core-util-is": { "version": "1.0.2", @@ -13100,8 +13037,7 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "ini": { "version": "1.3.5", @@ -13113,7 +13049,6 @@ "version": "1.0.0", "bundled": true, "dev": true, - "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -13128,7 +13063,6 @@ "version": "3.0.4", "bundled": true, "dev": true, - "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -13136,14 +13070,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -13162,7 +13094,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -13243,8 +13174,7 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "object-assign": { "version": "4.1.1", @@ -13256,7 +13186,6 @@ "version": "1.4.0", "bundled": true, "dev": true, - "optional": true, "requires": { "wrappy": "1" } @@ -13342,8 +13271,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -13379,7 +13307,6 @@ "version": "1.0.2", "bundled": true, "dev": true, - "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -13399,7 +13326,6 @@ "version": "3.0.1", "bundled": true, "dev": true, - "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -13443,14 +13369,12 @@ "wrappy": { "version": "1.0.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "yallist": { "version": "3.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, diff --git a/package.json b/package.json index 8544d10eb..3d9213173 100644 --- a/package.json +++ b/package.json @@ -40,106 +40,106 @@ "windows.store": "npx electron-windows-store --input-directory .\\desktop\\dist\\win-unpacked --output-directory .\\desktop\\store -a .\\resources\\desktop -m .\\desktop\\assets\\windows\\AppXManifest.xml --package-version 0.0.0.0 --package-name MoodleDesktop" }, "dependencies": { - "@angular/animations": "^5.2.10", - "@angular/common": "^5.2.10", - "@angular/compiler": "^5.2.10", - "@angular/compiler-cli": "^5.2.10", - "@angular/core": "^5.2.10", - "@angular/forms": "^5.2.10", - "@angular/http": "^5.2.10", - "@angular/platform-browser": "^5.2.10", - "@angular/platform-browser-dynamic": "^5.2.10", - "@ionic-native/badge": "^4.17.0", - "@ionic-native/camera": "^4.17.0", - "@ionic-native/clipboard": "^4.17.0", - "@ionic-native/core": "^4.11.0", - "@ionic-native/device": "^4.17.0", - "@ionic-native/file": "^4.17.0", - "@ionic-native/file-opener": "^4.17.0", - "@ionic-native/file-transfer": "^4.17.0", + "@angular/animations": "5.2.10", + "@angular/common": "5.2.10", + "@angular/compiler": "5.2.10", + "@angular/compiler-cli": "5.2.10", + "@angular/core": "5.2.10", + "@angular/forms": "5.2.10", + "@angular/http": "5.2.10", + "@angular/platform-browser": "5.2.10", + "@angular/platform-browser-dynamic": "5.2.10", + "@ionic-native/badge": "4.17.0", + "@ionic-native/camera": "4.17.0", + "@ionic-native/clipboard": "4.17.0", + "@ionic-native/core": "4.11.0", + "@ionic-native/device": "4.17.0", + "@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", - "@ionic-native/local-notifications": "^4.17.0", - "@ionic-native/media-capture": "^4.17.0", - "@ionic-native/network": "^4.17.0", - "@ionic-native/push": "^4.17.0", - "@ionic-native/screen-orientation": "^4.17.0", - "@ionic-native/splash-screen": "^4.17.0", - "@ionic-native/sqlite": "^4.17.0", - "@ionic-native/status-bar": "^4.17.0", - "@ionic-native/web-intent": "^4.17.0", - "@ionic-native/zip": "^4.17.0", - "@ngx-translate/core": "^8.0.0", - "@ngx-translate/http-loader": "^2.0.1", - "@types/cordova": "^0.0.34", - "@types/cordova-plugin-file-transfer": "^0.0.3", - "@types/cordova-plugin-globalization": "^0.0.3", - "@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", + "@ionic-native/globalization": "4.17.0", + "@ionic-native/in-app-browser": "4.17.0", + "@ionic-native/keyboard": "4.17.0", + "@ionic-native/local-notifications": "4.17.0", + "@ionic-native/media-capture": "4.17.0", + "@ionic-native/network": "4.17.0", + "@ionic-native/push": "4.17.0", + "@ionic-native/screen-orientation": "4.17.0", + "@ionic-native/splash-screen": "4.17.0", + "@ionic-native/sqlite": "4.17.0", + "@ionic-native/status-bar": "4.17.0", + "@ionic-native/web-intent": "4.17.0", + "@ionic-native/zip": "4.17.0", + "@ngx-translate/core": "8.0.0", + "@ngx-translate/http-loader": "2.0.1", + "@types/cordova": "0.0.34", + "@types/cordova-plugin-file-transfer": "0.0.3", + "@types/cordova-plugin-globalization": "0.0.3", + "@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.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-android-support-gradle-release": "3.0.1", + "cordova-clipboard": "1.3.0", "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.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", + "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.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.2.0", "cordova-plugin-local-notification": "git+https://github.com/moodlemobile/cordova-plugin-local-notification.git#moodle", - "cordova-plugin-media-capture": "^3.0.3", - "cordova-plugin-network-information": "^2.0.2", - "cordova-plugin-screen-orientation": "^3.0.2", - "cordova-plugin-splashscreen": "^5.0.3", - "cordova-plugin-statusbar": "^2.4.3", - "cordova-plugin-whitelist": "^1.3.4", - "cordova-plugin-zip": "^3.1.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", + "cordova-plugin-media-capture": "3.0.3", + "cordova-plugin-network-information": "2.0.2", + "cordova-plugin-screen-orientation": "3.0.2", + "cordova-plugin-splashscreen": "5.0.3", + "cordova-plugin-statusbar": "2.4.3", + "cordova-plugin-whitelist": "1.3.4", + "cordova-plugin-zip": "3.1.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", + "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", + "moment": "2.22.2", + "nl.kingsquare.cordova.background-audio": "1.0.1", + "phonegap-plugin-multidex": "1.0.0", "phonegap-plugin-push": "git+https://github.com/moodlemobile/phonegap-plugin-push.git#moodle-v3", - "promise.prototype.finally": "^3.1.0", - "rxjs": "^5.5.11", - "sw-toolbox": "^3.6.0", - "ts-md5": "^1.2.4", - "web-animations-js": "^2.3.1", - "zone.js": "^0.8.26" + "promise.prototype.finally": "3.1.0", + "rxjs": "5.5.11", + "sw-toolbox": "3.6.0", + "ts-md5": "1.2.4", + "web-animations-js": "2.3.1", + "zone.js": "0.8.26" }, "devDependencies": { "@ionic/app-scripts": "3.2.2", - "electron-builder-lib": "^20.23.1", - "electron-rebuild": "^1.8.1", + "electron-builder-lib": "20.23.1", + "electron-rebuild": "1.8.1", "gulp": "4.0.2", - "gulp-clip-empty-files": "^0.1.2", - "gulp-concat": "^2.6.1", - "gulp-flatten": "^0.4.0", - "gulp-rename": "^1.3.0", - "gulp-slash": "^1.1.3", - "gulp-util": "^3.0.8", - "node-loader": "^0.6.0", - "through": "^2.3.8", - "typescript": "^2.6.2", - "webpack-merge": "^4.1.2" + "gulp-clip-empty-files": "0.1.2", + "gulp-concat": "2.6.1", + "gulp-flatten": "0.4.0", + "gulp-rename": "1.3.0", + "gulp-slash": "1.1.3", + "gulp-util": "3.0.8", + "node-loader": "0.6.0", + "through": "2.3.8", + "typescript": "2.6.2", + "webpack-merge": "4.1.2" }, "browser": { "electron": false @@ -243,4 +243,4 @@ "deleteAppDataOnUninstall": true } } -} \ No newline at end of file +} From 3d67ee62dee58afdabd194fa318e57094fabc5d1 Mon Sep 17 00:00:00 2001 From: Noel De Martin Date: Wed, 11 Dec 2019 10:18:21 +0100 Subject: [PATCH 202/257] MOBILE-2159 geolocation: Replace type guard with simpler code --- src/addon/mod/data/fields/latlong/component/latlong.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/addon/mod/data/fields/latlong/component/latlong.ts b/src/addon/mod/data/fields/latlong/component/latlong.ts index 013c4586e..e322abf07 100644 --- a/src/addon/mod/data/fields/latlong/component/latlong.ts +++ b/src/addon/mod/data/fields/latlong/component/latlong.ts @@ -117,7 +117,7 @@ export class AddonModDataFieldLatlongComponent extends AddonModDataFieldPluginCo 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.isPositionError(error) && error.code === error.PERMISSION_DENIED) { + if (this.isPermissionDeniedError(error)) { this.domUtils.showErrorModal('addon.mod_data.locationpermissiondenied', true); return; @@ -129,7 +129,7 @@ export class AddonModDataFieldLatlongComponent extends AddonModDataFieldPluginCo }); } - protected isPositionError(error?: any): error is PositionError { - return error && 'code' in error && 'PERMISSION_DENIED' in error; + protected isPermissionDeniedError(error?: any): boolean { + return error && 'code' in error && 'PERMISSION_DENIED' in error && error.code === error.PERMISSION_DENIED; } } From 5337c77b7e042a8386474009df88156134d75071 Mon Sep 17 00:00:00 2001 From: Noel De Martin Date: Tue, 10 Dec 2019 13:39:55 +0100 Subject: [PATCH 203/257] MOBILE-3245 grades: Fix course data population in grades --- src/core/course/providers/helper.ts | 4 +- src/core/courses/providers/courses.ts | 11 +++-- src/core/courses/providers/helper.ts | 2 +- src/core/grades/providers/helper.ts | 58 ++++++++++++++++++++------- src/providers/sites.ts | 32 ++++++++++++++- 5 files changed, 84 insertions(+), 23 deletions(-) diff --git a/src/core/course/providers/helper.ts b/src/core/course/providers/helper.ts index 82b9d03d6..580971705 100644 --- a/src/core/course/providers/helper.ts +++ b/src/core/course/providers/helper.ts @@ -919,7 +919,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++) { @@ -931,7 +931,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) { diff --git a/src/core/courses/providers/courses.ts b/src/core/courses/providers/courses.ts index 226dca101..f869e467a 100644 --- a/src/core/courses/providers/courses.ts +++ b/src/core/courses/providers/courses.ts @@ -15,7 +15,7 @@ import { Injectable } from '@angular/core'; import { CoreEventsProvider } from '@providers/events'; import { CoreLoggerProvider } from '@providers/logger'; -import { CoreSitesProvider } from '@providers/sites'; +import { CoreSitesProvider, ReadingStrategy } from '@providers/sites'; import { CoreSite } from '@classes/site'; /** @@ -774,18 +774,21 @@ export class CoreCoursesProvider { * @param siteId Site to get the courses from. If not defined, use current site. * @return Promise resolved with the courses. */ - getUserCourses(preferCache?: boolean, siteId?: string): Promise { + getUserCourses(preferCache?: boolean, siteId?: string, strategy?: ReadingStrategy): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const userId = site.getUserId(), data: any = { userid: userId }, + strategyPreSets = strategy + ? this.sitesProvider.getReadingStrategyPreSets(strategy) + : { omitExpires: !!preferCache }, preSets = { cacheKey: this.getUserCoursesCacheKey(), getCacheUsingCacheKey: true, - omitExpires: !!preferCache, - updateFrequency: CoreSite.FREQUENCY_RARELY + updateFrequency: CoreSite.FREQUENCY_RARELY, + ...strategyPreSets, }; if (site.isVersionGreaterEqualThan('3.7')) { diff --git a/src/core/courses/providers/helper.ts b/src/core/courses/providers/helper.ts index 0d98fe61e..30e3ab092 100644 --- a/src/core/courses/providers/helper.ts +++ b/src/core/courses/providers/helper.ts @@ -107,7 +107,7 @@ export class CoreCoursesHelperProvider { return Promise.resolve(); } - let coursesInfo = [], + let coursesInfo = {}, courseInfoAvailable = false; const site = this.sitesProvider.getCurrentSite(), diff --git a/src/core/grades/providers/helper.ts b/src/core/grades/providers/helper.ts index 236f05cc8..889523c56 100644 --- a/src/core/grades/providers/helper.ts +++ b/src/core/grades/providers/helper.ts @@ -15,7 +15,7 @@ import { Injectable } from '@angular/core'; import { NavController } from 'ionic-angular'; import { CoreLoggerProvider } from '@providers/logger'; -import { CoreSitesProvider } from '@providers/sites'; +import { CoreSitesProvider, ReadingStrategy } from '@providers/sites'; import { CoreCoursesProvider } from '@core/courses/providers/courses'; import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreGradesProvider } from './grades'; @@ -198,22 +198,50 @@ export class CoreGradesHelperProvider { * @param grades Grades to get the data for. * @return Promise always resolved. Resolve param is the formatted grades. */ - getGradesCourseData(grades: any): Promise { - // Using cache for performance reasons. - return this.coursesProvider.getUserCourses(true).then((courses) => { - const indexedCourses = {}; - courses.forEach((course) => { - indexedCourses[course.id] = course; - }); + async getGradesCourseData(grades: any[]): Promise { + // Obtain courses from cache to prevent network requests. + const courses = await this.coursesProvider.getUserCourses(undefined, undefined, ReadingStrategy.OnlyCache); - grades.forEach((grade) => { - if (typeof indexedCourses[grade.courseid] != 'undefined') { - grade.courseFullName = indexedCourses[grade.courseid].fullname; - } - }); + const coursesMap = this.utils.arrayToObject(courses, 'id'); + const coursesWereMissing = this.addCourseData(grades, coursesMap); - return grades; - }); + // If any course wasn't found, make a network request. + if (coursesWereMissing) { + const coursesPromise = this.coursesProvider.isGetCoursesByFieldAvailable() + ? this.coursesProvider.getCoursesByField('ids', grades.map((grade) => grade.courseid)) + : this.coursesProvider.getUserCourses(undefined, undefined, ReadingStrategy.PreferNetwork); + + const courses = await coursesPromise; + + const coursesMap = this.utils.arrayToObject(courses, 'id'); + + this.addCourseData(grades, coursesMap); + } + + return grades.filter((grade) => grade.courseFullName !== undefined); + } + + /** + * Adds course data to grades. + * + * @param grades Array of grades to populate. + * @param courses HashMap of courses to read data from. + * @return Boolean indicating if some courses were not found. + */ + protected addCourseData(grades: any[], courses: any): boolean { + let someCoursesAreMissing = false; + + for (const grade of grades) { + if (!(grade.courseid in courses)) { + someCoursesAreMissing = true; + + continue; + } + + grade.courseFullName = courses[grade.courseid].fullname; + } + + return someCoursesAreMissing; } /** diff --git a/src/providers/sites.ts b/src/providers/sites.ts index 857a52058..0094d7096 100644 --- a/src/providers/sites.ts +++ b/src/providers/sites.ts @@ -26,7 +26,7 @@ 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'; @@ -158,6 +158,12 @@ export interface CoreSiteSchema { migrate?(db: SQLiteDB, oldVersion: number, siteId: string): Promise | void; } +export const enum ReadingStrategy { + 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 @@ -1730,4 +1736,28 @@ export class CoreSitesProvider { return reset; } + + /** + * Returns presets for a given reading strategy. + * + * @param strategy Reading strategy. + * @return PreSets options object. + */ + getReadingStrategyPreSets(strategy: ReadingStrategy): CoreSiteWSPreSets { + switch (strategy) { + case ReadingStrategy.PreferCache: + return { + omitExpires: true, + }; + case ReadingStrategy.OnlyCache: + return { + omitExpires: true, + forceOffline: true, + }; + case ReadingStrategy.PreferNetwork: + default: + return {}; + } + } + } From cb4c666cb78830cd0f000dacdc2b6f0f75898519 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 11 Dec 2019 11:47:00 +0100 Subject: [PATCH 204/257] MOBILE-3213 h5p: Send a resize event instead of a ready event --- .../h5p/components/h5p-player/core-h5p-player.html | 2 +- src/core/h5p/components/h5p-player/h5p-player.ts | 14 ++++---------- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/src/core/h5p/components/h5p-player/core-h5p-player.html b/src/core/h5p/components/h5p-player/core-h5p-player.html index 22981316f..bdd88f6c4 100644 --- a/src/core/h5p/components/h5p-player/core-h5p-player.html +++ b/src/core/h5p/components/h5p-player/core-h5p-player.html @@ -9,5 +9,5 @@ - + diff --git a/src/core/h5p/components/h5p-player/h5p-player.ts b/src/core/h5p/components/h5p-player/h5p-player.ts index 359be26cc..4804c4dbf 100644 --- a/src/core/h5p/components/h5p-player/h5p-player.ts +++ b/src/core/h5p/components/h5p-player/h5p-player.ts @@ -269,17 +269,11 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy { } /** - * Send hello to the H5P iframe. - * - * @param iframe The iframe. + * H5P iframe has been loaded. */ - sendHello(iframe?: HTMLIFrameElement): void { - const ready = { - context: 'h5p', - action: 'ready' - }; - - iframe.contentWindow.postMessage(ready, '*'); + iframeLoaded(): void { + // Send a resize event to the window so H5P package recalculates the size. + window.dispatchEvent(new Event('resize')); } /** From 36a070d98f98d17155609c36b0cad139fe6b0a17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 11 Dec 2019 13:26:57 +0100 Subject: [PATCH 205/257] MOBILE-3213 tabs: Add border on side placement --- src/components/ion-tabs/ion-tabs.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/ion-tabs/ion-tabs.scss b/src/components/ion-tabs/ion-tabs.scss index ec2e85b66..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 { From accb83b3394b52d6897a391f36b7aa9c65efebe2 Mon Sep 17 00:00:00 2001 From: Noel De Martin Date: Wed, 11 Dec 2019 13:32:00 +0100 Subject: [PATCH 206/257] MOBILE-3245 grades: Fix network methods calls when populating grades --- src/core/grades/providers/helper.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/core/grades/providers/helper.ts b/src/core/grades/providers/helper.ts index 889523c56..ae8cc9071 100644 --- a/src/core/grades/providers/helper.ts +++ b/src/core/grades/providers/helper.ts @@ -200,15 +200,22 @@ export class CoreGradesHelperProvider { */ async getGradesCourseData(grades: any[]): Promise { // Obtain courses from cache to prevent network requests. - const courses = await this.coursesProvider.getUserCourses(undefined, undefined, ReadingStrategy.OnlyCache); + let coursesWereMissing; - const coursesMap = this.utils.arrayToObject(courses, 'id'); - const coursesWereMissing = this.addCourseData(grades, coursesMap); + try { + const courses = await this.coursesProvider.getUserCourses(undefined, undefined, ReadingStrategy.OnlyCache); + + const coursesMap = this.utils.arrayToObject(courses, 'id'); + + coursesWereMissing = this.addCourseData(grades, coursesMap); + } catch (error) { + coursesWereMissing = true; + } // If any course wasn't found, make a network request. if (coursesWereMissing) { const coursesPromise = this.coursesProvider.isGetCoursesByFieldAvailable() - ? this.coursesProvider.getCoursesByField('ids', grades.map((grade) => grade.courseid)) + ? this.coursesProvider.getCoursesByField('ids', grades.map((grade) => grade.courseid).join(',')) : this.coursesProvider.getUserCourses(undefined, undefined, ReadingStrategy.PreferNetwork); const courses = await coursesPromise; From 5ba3c03d91f021fb660201d1b864c2dd550ef523 Mon Sep 17 00:00:00 2001 From: Noel De Martin Date: Wed, 11 Dec 2019 13:33:31 +0100 Subject: [PATCH 207/257] MOBILE-3245 naming: Prefix ReadingStrategy name with CoreSites --- src/core/courses/providers/courses.ts | 4 ++-- src/core/grades/providers/helper.ts | 6 +++--- src/providers/sites.ts | 10 +++++----- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/core/courses/providers/courses.ts b/src/core/courses/providers/courses.ts index f869e467a..b0529d926 100644 --- a/src/core/courses/providers/courses.ts +++ b/src/core/courses/providers/courses.ts @@ -15,7 +15,7 @@ import { Injectable } from '@angular/core'; import { CoreEventsProvider } from '@providers/events'; import { CoreLoggerProvider } from '@providers/logger'; -import { CoreSitesProvider, ReadingStrategy } from '@providers/sites'; +import { CoreSitesProvider, CoreSitesReadingStrategy } from '@providers/sites'; import { CoreSite } from '@classes/site'; /** @@ -774,7 +774,7 @@ export class CoreCoursesProvider { * @param siteId Site to get the courses from. If not defined, use current site. * @return Promise resolved with the courses. */ - getUserCourses(preferCache?: boolean, siteId?: string, strategy?: ReadingStrategy): Promise { + getUserCourses(preferCache?: boolean, siteId?: string, strategy?: CoreSitesReadingStrategy): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const userId = site.getUserId(), diff --git a/src/core/grades/providers/helper.ts b/src/core/grades/providers/helper.ts index ae8cc9071..7bdfc7b67 100644 --- a/src/core/grades/providers/helper.ts +++ b/src/core/grades/providers/helper.ts @@ -15,7 +15,7 @@ import { Injectable } from '@angular/core'; import { NavController } from 'ionic-angular'; import { CoreLoggerProvider } from '@providers/logger'; -import { CoreSitesProvider, ReadingStrategy } from '@providers/sites'; +import { CoreSitesProvider, CoreSitesReadingStrategy } from '@providers/sites'; import { CoreCoursesProvider } from '@core/courses/providers/courses'; import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreGradesProvider } from './grades'; @@ -203,7 +203,7 @@ export class CoreGradesHelperProvider { let coursesWereMissing; try { - const courses = await this.coursesProvider.getUserCourses(undefined, undefined, ReadingStrategy.OnlyCache); + const courses = await this.coursesProvider.getUserCourses(undefined, undefined, CoreSitesReadingStrategy.OnlyCache); const coursesMap = this.utils.arrayToObject(courses, 'id'); @@ -216,7 +216,7 @@ export class CoreGradesHelperProvider { if (coursesWereMissing) { const coursesPromise = this.coursesProvider.isGetCoursesByFieldAvailable() ? this.coursesProvider.getCoursesByField('ids', grades.map((grade) => grade.courseid).join(',')) - : this.coursesProvider.getUserCourses(undefined, undefined, ReadingStrategy.PreferNetwork); + : this.coursesProvider.getUserCourses(undefined, undefined, CoreSitesReadingStrategy.PreferNetwork); const courses = await coursesPromise; diff --git a/src/providers/sites.ts b/src/providers/sites.ts index 0094d7096..3458aaefd 100644 --- a/src/providers/sites.ts +++ b/src/providers/sites.ts @@ -158,7 +158,7 @@ export interface CoreSiteSchema { migrate?(db: SQLiteDB, oldVersion: number, siteId: string): Promise | void; } -export const enum ReadingStrategy { +export const enum CoreSitesReadingStrategy { OnlyCache, PreferCache, PreferNetwork, @@ -1743,18 +1743,18 @@ export class CoreSitesProvider { * @param strategy Reading strategy. * @return PreSets options object. */ - getReadingStrategyPreSets(strategy: ReadingStrategy): CoreSiteWSPreSets { + getReadingStrategyPreSets(strategy: CoreSitesReadingStrategy): CoreSiteWSPreSets { switch (strategy) { - case ReadingStrategy.PreferCache: + case CoreSitesReadingStrategy.PreferCache: return { omitExpires: true, }; - case ReadingStrategy.OnlyCache: + case CoreSitesReadingStrategy.OnlyCache: return { omitExpires: true, forceOffline: true, }; - case ReadingStrategy.PreferNetwork: + case CoreSitesReadingStrategy.PreferNetwork: default: return {}; } From 4c09231e14663376de197f774f0d92123299cab9 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 11 Dec 2019 15:24:41 +0100 Subject: [PATCH 208/257] MOBILE-3213 core: Don't use tokenpluginfile for customcert --- src/classes/site.ts | 7 ++----- src/providers/utils/url.ts | 27 +++++++++++++++++++++------ 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/classes/site.ts b/src/classes/site.ts index 5d738185b..11312b0d1 100644 --- a/src/classes/site.ts +++ b/src/classes/site.ts @@ -1923,8 +1923,8 @@ export class CoreSite { * @return Promise resolved with boolean: whether it works or not. */ checkTokenPluginFile(url: string): Promise { - if (!this.infos || !this.infos.userprivateaccesskey) { - // No access key, cannot use tokenpluginfile. + 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. @@ -1935,9 +1935,6 @@ export class CoreSite { } else if (!this.appProvider.isOnline()) { // Not online, cannot check it. Assume it's working, but don't save the result. return Promise.resolve(true); - } else if (!this.urlUtils.isPluginFileUrl(url)) { - // Not a pluginfile URL, ignore it. - return Promise.resolve(false); } url = this.fixPluginfileURL(url); diff --git a/src/providers/utils/url.ts b/src/providers/utils/url.ts index c04240a6c..4cb7b8cce 100644 --- a/src/providers/utils/url.ts +++ b/src/providers/utils/url.ts @@ -89,6 +89,22 @@ export class CoreUrlUtilsProvider { 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. * @@ -151,8 +167,10 @@ export class CoreUrlUtilsProvider { 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 (!accessKey && url.indexOf('token=') != -1) { + if (!canUseTokenPluginFile && url.indexOf('token=') != -1) { return url; } @@ -161,11 +179,8 @@ export class CoreUrlUtilsProvider { return url; } - const hasSlashParams = !url.match(/[\&?]file=/); - - if (accessKey && hasSlashParams) { - // We have the user access key, use tokenpluginfile.php. - // Do not use it without slash params, the URL doesn't work. + if (canUseTokenPluginFile) { + // Use tokenpluginfile.php. url = url.replace(/(\/webservice)?\/pluginfile\.php/, '/tokenpluginfile.php/' + accessKey); return url; From 07b32226bdfde46c886b682368cd845180b10c71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 11 Dec 2019 15:46:05 +0100 Subject: [PATCH 209/257] MOBILE-3213 resource: Fix resource icon set --- src/addon/mod/resource/providers/module-handler.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/addon/mod/resource/providers/module-handler.ts b/src/addon/mod/resource/providers/module-handler.ts index 5b0d8686e..735bcae53 100644 --- a/src/addon/mod/resource/providers/module-handler.ts +++ b/src/addon/mod/resource/providers/module-handler.ts @@ -222,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); } From ee4bf2944c9df7ad52abcd21d69fa231d152bbb2 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 12 Dec 2019 09:26:40 +0100 Subject: [PATCH 210/257] MOBILE-3213 core: Fix exception when invalidate site plugin content --- src/core/siteplugins/providers/siteplugins.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/siteplugins/providers/siteplugins.ts b/src/core/siteplugins/providers/siteplugins.ts index 6ebcef76a..7b3b9c03b 100644 --- a/src/core/siteplugins/providers/siteplugins.ts +++ b/src/core/siteplugins/providers/siteplugins.ts @@ -336,6 +336,8 @@ export class CoreSitePluginsProvider { */ invalidateCallWS(method: string, data: any, preSets?: CoreSiteWSPreSets, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { + preSets = preSets || {}; + return site.invalidateWsCacheForKey(preSets.cacheKey || this.getCallWSCacheKey(method, data)); }); } From ef268e032cb1dfad32faee007078dfb2ae5a4dbf Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 12 Dec 2019 11:03:40 +0100 Subject: [PATCH 211/257] MOBILE-3213 core: Revert keyboard plugin to 2.1.3 The 2.2.0 version causes the input text to be behind the keyboard when sending messages. --- config.xml | 2 +- package-lock.json | 6 +++--- package.json | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/config.xml b/config.xml index 5c29845ee..ed799b098 100644 --- a/config.xml +++ b/config.xml @@ -160,7 +160,7 @@ - + diff --git a/package-lock.json b/package-lock.json index 3c9f1767d..15de2157d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3157,9 +3157,9 @@ "integrity": "sha512-YqrZfYgbGTS20SFID0mrRxud312VH072QVlFonCAkPgtGg1Svy7lELOCNFd+KU7t4mVtZeTEjZPEeefvjaetwQ==" }, "cordova-plugin-ionic-keyboard": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cordova-plugin-ionic-keyboard/-/cordova-plugin-ionic-keyboard-2.2.0.tgz", - "integrity": "sha512-yDUG+9ieKVRitq5mGlNxjaZh/MgEhFFIgTIPhqSbUaQ8UuZbawy5mhJAVClqY97q8/rcQtL6dCDa7x2sEtCLcA==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/cordova-plugin-ionic-keyboard/-/cordova-plugin-ionic-keyboard-2.1.3.tgz", + "integrity": "sha512-6ucQ6FdlLdBm8kJfFnzozmBTjru/0xekHP/dAhjoCZggkGRlgs8TsUJFkxa/bV+qi7Dlo50JjmpE4UMWAO+aOQ==" }, "cordova-plugin-local-notification": { "version": "git+https://github.com/moodlemobile/cordova-plugin-local-notification.git#c7430c4f4f8b8c82d051aea49d87da0b4f6a8b1e", diff --git a/package.json b/package.json index 3d9213173..28d6cc054 100644 --- a/package.json +++ b/package.json @@ -97,7 +97,7 @@ "cordova-plugin-geolocation": "4.0.2", "cordova-plugin-globalization": "1.11.0", "cordova-plugin-inappbrowser": "3.1.0", - "cordova-plugin-ionic-keyboard": "2.2.0", + "cordova-plugin-ionic-keyboard": "2.1.3", "cordova-plugin-local-notification": "git+https://github.com/moodlemobile/cordova-plugin-local-notification.git#moodle", "cordova-plugin-media-capture": "3.0.3", "cordova-plugin-network-information": "2.0.2", From 4727194f5446a2f3749ffbcd493131b33e8b77ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 12 Dec 2019 13:19:47 +0100 Subject: [PATCH 212/257] MOBILE-3213 scripts: Detect wrong 2.4 version on langs --- scripts/lang_functions.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/scripts/lang_functions.php b/scripts/lang_functions.php index af1e2ab3b..0fa57b1f4 100644 --- a/scripts/lang_functions.php +++ b/scripts/lang_functions.php @@ -232,6 +232,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++; } From 9e9b4bcbbe9b5cdea8e25ffdb056f5563a0e4d4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 12 Dec 2019 13:20:03 +0100 Subject: [PATCH 213/257] MOBILE-3213 strings: Have a better CLI interface --- scripts/lang_functions.php | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/scripts/lang_functions.php b/scripts/lang_functions.php index 0fa57b1f4..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; } @@ -248,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); @@ -258,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) { @@ -275,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) { @@ -304,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; From 86785a42887c92766a4c1324bf40d483d3be2450 Mon Sep 17 00:00:00 2001 From: Noel De Martin Date: Thu, 12 Dec 2019 16:10:54 +0100 Subject: [PATCH 214/257] MOBILE-3232 filters: Disable mathjax options menu --- src/addon/filter/mathjaxloader/providers/handler.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/addon/filter/mathjaxloader/providers/handler.ts b/src/addon/filter/mathjaxloader/providers/handler.ts index 0ef005bc7..beb3a1cb0 100644 --- a/src/addon/filter/mathjaxloader/providers/handler.ts +++ b/src/addon/filter/mathjaxloader/providers/handler.ts @@ -51,11 +51,7 @@ export class AddonFilterMathJaxLoaderHandler extends CoreFilterDefaultHandler { "[a11y]/accessibility-menu.js" ], jax: ["input/TeX","input/MathML","output/SVG"], - menuSettings: { - zoom: "Double-Click", - mpContext: true, - mpMouse: true - }, + showMathMenu: false, errorSettings: { message: ["!"] }, skipStartupTypeset: true, messageStyle: "none" From b74381c62687ea9ee19d8e116fcd32b8334becda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 12 Dec 2019 16:40:47 +0100 Subject: [PATCH 215/257] MOBILE-3213 folder: Fix opening subfolders --- src/addon/mod/folder/components/index/index.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/addon/mod/folder/components/index/index.ts b/src/addon/mod/folder/components/index/index.ts index ae0bcfae0..573b4dfb6 100644 --- a/src/addon/mod/folder/components/index/index.ts +++ b/src/addon/mod/folder/components/index/index.ts @@ -34,7 +34,6 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo component = AddonModFolderProvider.COMPONENT; canGetFolder: boolean; contents: any; - moduleContents: any; constructor(injector: Injector, private folderProvider: AddonModFolderProvider, private courseProvider: CoreCourseProvider, private appProvider: CoreAppProvider, private folderHelper: AddonModFolderHelperProvider) { @@ -51,7 +50,7 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo if (this.path) { // Subfolder. Use module param. - this.showModuleData(this.module); + this.showModuleData(this.module, this.module.contents); this.loaded = true; this.refreshIcon = 'refresh'; } else { @@ -81,16 +80,16 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo * Convenience function to set scope data using module. * @param module Module to show. */ - protected showModuleData(module: any): void { + protected showModuleData(module: any, folderContents: any): void { this.description = module.intro || module.description; this.dataRetrieved.emit(module); if (this.path) { // Subfolder. - this.contents = this.moduleContents; + this.contents = folderContents; } else { - this.contents = this.folderHelper.formatContents(this.moduleContents); + this.contents = this.folderHelper.formatContents(folderContents); } } @@ -101,12 +100,13 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo * @return Promise resolved when done. */ protected fetchContent(refresh?: boolean): Promise { - let promise; + let promise, + folderContents = this.module.contents; if (this.canGetFolder) { promise = this.folderProvider.getFolder(this.courseId, this.module.id).then((folder) => { return this.courseProvider.loadModuleContents(this.module, this.courseId).then(() => { - this.moduleContents = this.module.contents; + folderContents = this.module.contents; return folder; }); @@ -118,14 +118,14 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo folder.contents = this.module.contents; } this.module = folder; - this.moduleContents = folder.contents; + folderContents = folder.contents; return folder; }); } return promise.then((folder) => { - this.showModuleData(folder); + this.showModuleData(folder, folderContents); }).finally(() => { this.fillContextMenu(refresh); }); From 34b6ab1fe1dbe1ceb88333e3309d83dfa8018cbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 12 Dec 2019 17:06:37 +0100 Subject: [PATCH 216/257] MOBILE-3213 workshop: Dimme submit task when not possible --- .../components/index/addon-mod-workshop-index.html | 6 +++--- src/addon/mod/workshop/components/index/index.ts | 8 +++++++- src/addon/mod/workshop/pages/phase/phase.html | 2 +- src/addon/mod/workshop/pages/phase/phase.ts | 2 ++ 4 files changed, 13 insertions(+), 5 deletions(-) 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 7d398d32c..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 @@ -18,14 +18,14 @@

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

- +

{{task.title}}

-

{{ task.details }}

+

@@ -94,7 +94,7 @@ - + - {{ note.content }} + From 268e45ef09c3052928e370e1a0457818d13d5e1d Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 12 Dec 2019 13:12:17 +0100 Subject: [PATCH 221/257] MOBILE-3213 badges: Don't show alignment if broken --- .../pages/issued-badge/issued-badge.html | 2 +- src/addon/badges/providers/badges.ts | 26 ++++++++++++------- .../entries/addon-blog-entries.html | 2 +- 3 files changed, 18 insertions(+), 12 deletions(-) diff --git a/src/addon/badges/pages/issued-badge/issued-badge.html b/src/addon/badges/pages/issued-badge/issued-badge.html index 75ee12adf..ec4dc0e4f 100644 --- a/src/addon/badges/pages/issued-badge/issued-badge.html +++ b/src/addon/badges/pages/issued-badge/issued-badge.html @@ -156,7 +156,7 @@

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

-

<{{ relatedBadge.name }}

+

{{ relatedBadge.name }}

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

diff --git a/src/addon/badges/providers/badges.ts b/src/addon/badges/providers/badges.ts index bfb70431e..bce298dc4 100644 --- a/src/addon/badges/providers/badges.ts +++ b/src/addon/badges/providers/badges.ts @@ -91,6 +91,12 @@ export class AddonBadgesProvider { // 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; @@ -175,20 +181,20 @@ export type AddonBadgesUserBadge = { 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. + 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. + 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. diff --git a/src/addon/blog/components/entries/addon-blog-entries.html b/src/addon/blog/components/entries/addon-blog-entries.html index 557bf3ed3..a77b45b2f 100644 --- a/src/addon/blog/components/entries/addon-blog-entries.html +++ b/src/addon/blog/components/entries/addon-blog-entries.html @@ -5,7 +5,7 @@ {{ 'addon.blog.showonlyyourentries' | translate }} - > + From 72a2b571cdc20dbf881b86b43df8138a45624ada Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 13 Dec 2019 08:30:54 +0100 Subject: [PATCH 222/257] MOBILE-3213 folder: Fix PTR in folder --- src/addon/mod/folder/components/index/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/addon/mod/folder/components/index/index.ts b/src/addon/mod/folder/components/index/index.ts index 573b4dfb6..922ec3836 100644 --- a/src/addon/mod/folder/components/index/index.ts +++ b/src/addon/mod/folder/components/index/index.ts @@ -105,7 +105,7 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo if (this.canGetFolder) { promise = this.folderProvider.getFolder(this.courseId, this.module.id).then((folder) => { - return this.courseProvider.loadModuleContents(this.module, this.courseId).then(() => { + return this.courseProvider.loadModuleContents(this.module, this.courseId, undefined, false, refresh).then(() => { folderContents = this.module.contents; return folder; From 5053274a85964bf6781afae8c6023e52cc44c38a Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 13 Dec 2019 09:26:04 +0100 Subject: [PATCH 223/257] MOBILE-3213 resource: Fix PTR on mod resource --- src/core/course/providers/helper.ts | 85 ++++++++++++++++++----------- 1 file changed, 54 insertions(+), 31 deletions(-) diff --git a/src/core/course/providers/helper.ts b/src/core/course/providers/helper.ts index ffbc281b5..1f393b487 100644 --- a/src/core/course/providers/helper.ts +++ b/src/core/course/providers/helper.ts @@ -661,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, @@ -678,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; @@ -735,6 +709,55 @@ 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. * From 63bff7c4f84afa9bc7f82c7ecda0f65abf29c839 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 13 Dec 2019 10:29:10 +0100 Subject: [PATCH 224/257] MOBILE-3213 forum: Fix discussion links when nested view is on --- .../mod/forum/pages/discussion/discussion.ts | 18 +++++++++++------- .../forum/providers/discussion-link-handler.ts | 2 +- src/addon/mod/forum/providers/forum.ts | 9 ++------- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/src/addon/mod/forum/pages/discussion/discussion.ts b/src/addon/mod/forum/pages/discussion/discussion.ts index cc4cd42f6..e49b8684d 100644 --- a/src/addon/mod/forum/pages/discussion/discussion.ts +++ b/src/addon/mod/forum/pages/discussion/discussion.ts @@ -326,10 +326,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. @@ -364,7 +371,7 @@ 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.loadDiscussion(this.forumId, this.discussionId)); } @@ -373,12 +380,9 @@ export class AddonModForumDiscussionPage implements OnDestroy { }).catch(() => { // Ignore errors. }).then(() => { - 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.'); } diff --git a/src/addon/mod/forum/providers/discussion-link-handler.ts b/src/addon/mod/forum/providers/discussion-link-handler.ts index e884b6545..546e477f0 100644 --- a/src/addon/mod/forum/providers/discussion-link-handler.ts +++ b/src/addon/mod/forum/providers/discussion-link-handler.ts @@ -51,7 +51,7 @@ export class AddonModForumDiscussionLinkHandler extends CoreContentLinksHandlerB 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) diff --git a/src/addon/mod/forum/providers/forum.ts b/src/addon/mod/forum/providers/forum.ts index a852c2aec..96fd64ce1 100644 --- a/src/addon/mod/forum/providers/forum.ts +++ b/src/addon/mod/forum/providers/forum.ts @@ -278,14 +278,9 @@ export class AddonModForumProvider { * @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; } /** From 438ae42a169ba6e3885e64797b95901628519fd6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 13 Dec 2019 11:10:28 +0100 Subject: [PATCH 225/257] MOBILE-3213 style: Fix some darkmode styles --- src/app/app.scss | 5 +++++ src/components/rich-text-editor/rich-text-editor.scss | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/app/app.scss b/src/app/app.scss index a77cf58e8..6e71d9caf 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -367,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; @@ -376,6 +379,8 @@ ion-app.app-root { font-size: 24px; ion-icon { font-size: 24px; + + } } diff --git a/src/components/rich-text-editor/rich-text-editor.scss b/src/components/rich-text-editor/rich-text-editor.scss index d4ba94b06..e2dd2ac07 100644 --- a/src/components/rich-text-editor/rich-text-editor.scss +++ b/src/components/rich-text-editor/rich-text-editor.scss @@ -7,7 +7,7 @@ ion-app.app-root core-rich-text-editor { display: flex; flex-direction: column; @include darkmode() { - background-color: $black; + background-color: $gray-darker; } .core-rte-editor, .core-textarea { @@ -17,7 +17,7 @@ ion-app.app-root core-rich-text-editor { resize: none; background-color: $white; @include darkmode() { - background-color: $black; + background-color: $gray-darker; color: $white; } } From 9ee135e0b59504ddf20c3ff0fa6da402cca24c90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 13 Dec 2019 12:13:33 +0100 Subject: [PATCH 226/257] MOBILE-3213 settings: Hide space usage values when loading --- src/core/settings/pages/space-usage/space-usage.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/settings/pages/space-usage/space-usage.html b/src/core/settings/pages/space-usage/space-usage.html index a21f0208d..f4d247a89 100644 --- a/src/core/settings/pages/space-usage/space-usage.html +++ b/src/core/settings/pages/space-usage/space-usage.html @@ -12,8 +12,8 @@

{{ site.fullName }}

-

{{ site.spaceUsage | coreBytesToSize }}

-

{{ 'core.settings.entriesincache' | translate: { $a: site.cacheEntries } }}

+

{{ site.spaceUsage | coreBytesToSize }}

+

{{ 'core.settings.entriesincache' | translate: { $a: site.cacheEntries } }}

@@ -13,7 +13,7 @@
- +
diff --git a/src/addon/block/recentlyaccessedcourses/components/recentlyaccessedcourses/recentlyaccessedcourses.ts b/src/addon/block/recentlyaccessedcourses/components/recentlyaccessedcourses/recentlyaccessedcourses.ts index aa7dbdd02..4b61bfeb1 100644 --- a/src/addon/block/recentlyaccessedcourses/components/recentlyaccessedcourses/recentlyaccessedcourses.ts +++ b/src/addon/block/recentlyaccessedcourses/components/recentlyaccessedcourses/recentlyaccessedcourses.ts @@ -37,10 +37,13 @@ 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.'; @@ -58,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()); @@ -154,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/starredcourses/components/starredcourses/addon-block-starredcourses.html b/src/addon/block/starredcourses/components/starredcourses/addon-block-starredcourses.html index a6b1d49b7..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 }}

-
+
@@ -13,7 +13,7 @@
- +
diff --git a/src/addon/block/starredcourses/components/starredcourses/starredcourses.ts b/src/addon/block/starredcourses/components/starredcourses/starredcourses.ts index e5cabc99c..8acddb2a2 100644 --- a/src/addon/block/starredcourses/components/starredcourses/starredcourses.ts +++ b/src/addon/block/starredcourses/components/starredcourses/starredcourses.ts @@ -37,10 +37,13 @@ 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.'; @@ -58,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()); @@ -154,5 +168,6 @@ export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent im ngOnDestroy(): void { this.isDestroyed = true; this.coursesObserver && this.coursesObserver.off(); + this.updateSiteObserver && this.updateSiteObserver.off(); } } From 81d45266ca74de898545fa0b5f04f746c9a55a0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 17 Dec 2019 16:39:46 +0100 Subject: [PATCH 249/257] MOBILE-3213 styles: Fix styles on timeline block --- .../timeline/components/events/events.scss | 29 +++++++++++++++++++ src/theme/dark.scss | 1 + 2 files changed, 30 insertions(+) diff --git a/src/addon/block/timeline/components/events/events.scss b/src/addon/block/timeline/components/events/events.scss index 76da48072..e284f0d24 100644 --- a/src/addon/block/timeline/components/events/events.scss +++ b/src/addon/block/timeline/components/events/events.scss @@ -14,3 +14,32 @@ ion-app.app-root addon-block-timeline-events { pointer-events: auto; } } + +ion-app.app-root core-courses-course-progress addon-block-timeline-events { + @include media-breakpoint-up(md) { + .hidden-tablet { + display: block !important; + opacity: 1 !important; + &.button[disabled] { + opacity: .4 !important; + } + } + .hidden-phone { + display: none !important; + opacity: 0 !important; + } + } + @include media-breakpoint-up(lg) { + .hidden-tablet { + display: none !important; + opacity: 0 !important; + } + .hidden-phone { + display: block !important; + opacity: 1 !important; + &.button[disabled] { + opacity: .4 !important; + } + } + } +} diff --git a/src/theme/dark.scss b/src/theme/dark.scss index 0e034de87..01c373d06 100644 --- a/src/theme/dark.scss +++ b/src/theme/dark.scss @@ -25,6 +25,7 @@ ion-app.app-root .ion-page { color: $core-dark-link-color; } + .core-tabs-bar, .core-tabs-bar *, .core-tabs-bar .tab-slide, .ion-page, From 82c0385fe5d523ebee308e006e5f286ca432ac91 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 17 Dec 2019 16:04:47 +0100 Subject: [PATCH 250/257] MOBILE-3213 core: Handle anchors when cleaning extensions --- src/providers/utils/mimetype.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/providers/utils/mimetype.ts b/src/providers/utils/mimetype.ts index 19c54139c..c162f3dc7 100644 --- a/src/providers/utils/mimetype.ts +++ b/src/providers/utils/mimetype.ts @@ -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); } From c255feeeefa3077439721535a32f8130c12efd07 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 17 Dec 2019 17:07:40 +0100 Subject: [PATCH 251/257] MOBILE-3213 assign: Discard grade drafts after successful sync --- src/addon/mod/assign/providers/assign-sync.ts | 38 ++++++++++++++----- 1 file changed, 29 insertions(+), 9 deletions(-) diff --git a/src/addon/mod/assign/providers/assign-sync.ts b/src/addon/mod/assign/providers/assign-sync.ts index 41b43f74e..8b5bcfbb1 100644 --- a/src/addon/mod/assign/providers/assign-sync.ts +++ b/src/addon/mod/assign/providers/assign-sync.ts @@ -29,6 +29,7 @@ import { CoreSyncBaseProvider } from '@classes/base-sync'; import { AddonModAssignProvider, AddonModAssignAssign } from './assign'; import { AddonModAssignOfflineProvider } from './assign-offline'; import { AddonModAssignSubmissionDelegate } from './submission-delegate'; +import { AddonModAssignFeedbackDelegate } from './feedback-delegate'; /** * Data returned by an assign sync. @@ -55,13 +56,22 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider { protected componentTranslate: string; - constructor(loggerProvider: CoreLoggerProvider, sitesProvider: CoreSitesProvider, appProvider: CoreAppProvider, - syncProvider: CoreSyncProvider, textUtils: CoreTextUtilsProvider, translate: TranslateService, - private courseProvider: CoreCourseProvider, private eventsProvider: CoreEventsProvider, - private assignProvider: AddonModAssignProvider, private assignOfflineProvider: AddonModAssignOfflineProvider, - private utils: CoreUtilsProvider, private submissionDelegate: AddonModAssignSubmissionDelegate, - private gradesHelper: CoreGradesHelperProvider, timeUtils: CoreTimeUtilsProvider, - private logHelper: CoreCourseLogHelperProvider) { + constructor(loggerProvider: CoreLoggerProvider, + sitesProvider: CoreSitesProvider, + appProvider: CoreAppProvider, + syncProvider: CoreSyncProvider, + textUtils: CoreTextUtilsProvider, + translate: TranslateService, + timeUtils: CoreTimeUtilsProvider, + protected courseProvider: CoreCourseProvider, + protected eventsProvider: CoreEventsProvider, + protected assignProvider: AddonModAssignProvider, + protected assignOfflineProvider: AddonModAssignOfflineProvider, + protected utils: CoreUtilsProvider, + protected submissionDelegate: AddonModAssignSubmissionDelegate, + protected feedbackDelegate: AddonModAssignFeedbackDelegate, + protected gradesHelper: CoreGradesHelperProvider, + protected logHelper: CoreCourseLogHelperProvider) { super('AddonModAssignSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, timeUtils); @@ -403,9 +413,19 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider { return this.assignProvider.submitGradingFormOnline(assign.id, userId, offlineData.grade, offlineData.attemptnumber, offlineData.addattempt, offlineData.workflowstate, offlineData.applytoall, offlineData.outcomes, offlineData.plugindata, siteId).then(() => { + // Grades sent. + // Discard grades drafts. + const promises = []; + if (status.feedback && status.feedback.plugins) { + status.feedback.plugins.forEach((plugin) => { + promises.push(this.feedbackDelegate.discardPluginFeedbackData(assign.id, userId, plugin, siteId)); + }); + } - // Grades sent, update cached data. No need to block the user for this. - this.assignProvider.getSubmissionStatus(assign.id, userId, undefined, false, true, true, siteId); + // Update cached data. + promises.push(this.assignProvider.getSubmissionStatus(assign.id, userId, undefined, false, true, true, siteId)); + + return Promise.all(promises); }).catch((error) => { if (error && this.utils.isWebServiceError(error)) { // The WebService has thrown an error, this means it cannot be submitted. Discard the offline data. From a0cd139d9cadfabff1d5ff2c5e6e61c46e3dfc2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 18 Dec 2019 10:34:39 +0100 Subject: [PATCH 252/257] MOBILE-3213 workshop: Fix static definition --- src/addon/mod/workshop/providers/helper.ts | 2 +- src/addon/mod/workshop/providers/workshop.ts | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/addon/mod/workshop/providers/helper.ts b/src/addon/mod/workshop/providers/helper.ts index 66ae24140..ad82e99e9 100644 --- a/src/addon/mod/workshop/providers/helper.ts +++ b/src/addon/mod/workshop/providers/helper.ts @@ -68,7 +68,7 @@ export class AddonModWorkshopHelperProvider { const task = this.getTask(tasks, taskCode); if (task) { - return task.completed; + return !!task.completed; } // Task not found, assume true. diff --git a/src/addon/mod/workshop/providers/workshop.ts b/src/addon/mod/workshop/providers/workshop.ts index 866b321a8..ec8fc292e 100644 --- a/src/addon/mod/workshop/providers/workshop.ts +++ b/src/addon/mod/workshop/providers/workshop.ts @@ -33,9 +33,9 @@ export class AddonModWorkshopProvider { static PHASE_ASSESSMENT = 30; static PHASE_EVALUATION = 40; static PHASE_CLOSED = 50; - static EXAMPLES_VOLUNTARY: 0; - static EXAMPLES_BEFORE_SUBMISSION: 1; - static EXAMPLES_BEFORE_ASSESSMENT: 2; + static EXAMPLES_VOLUNTARY = 0; + static EXAMPLES_BEFORE_SUBMISSION = 1; + static EXAMPLES_BEFORE_ASSESSMENT = 2; static SUBMISSION_TYPE_DISABLED = 0; static SUBMISSION_TYPE_AVAILABLE = 1; static SUBMISSION_TYPE_REQUIRED = 2; From 4601df6fc4385bb6c68eda7f56142d4b533171ae Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 18 Dec 2019 10:50:10 +0100 Subject: [PATCH 253/257] MOBILE-3213 data: Fix latlong geo links --- .../data/fields/latlong/component/latlong.ts | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/src/addon/mod/data/fields/latlong/component/latlong.ts b/src/addon/mod/data/fields/latlong/component/latlong.ts index 869f19c63..912f628d3 100644 --- a/src/addon/mod/data/fields/latlong/component/latlong.ts +++ b/src/addon/mod/data/fields/latlong/component/latlong.ts @@ -13,6 +13,7 @@ // 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'; @@ -30,8 +31,11 @@ export class AddonModDataFieldLatlongComponent extends AddonModDataFieldPluginCo north: number; east: number; - constructor(protected fb: FormBuilder, private platform: Platform, private geolocation: Geolocation, - private domUtils: CoreDomUtilsProvider) { + constructor(protected fb: FormBuilder, + protected platform: Platform, + protected geolocation: Geolocation, + protected domUtils: CoreDomUtilsProvider, + protected sanitizer: DomSanitizer) { super(fb); } @@ -58,16 +62,19 @@ export class AddonModDataFieldLatlongComponent extends AddonModDataFieldPluginCo * @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); } } From bd4db3ff014507bede7100095364a0d16495fe86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 18 Dec 2019 11:32:38 +0100 Subject: [PATCH 254/257] MOBILE-3213 database: Do not allow to set widths or heights --- src/addon/mod/data/components/index/index.ts | 4 +++- src/addon/mod/data/data.scss | 13 +++++++++++++ src/addon/mod/data/providers/helper.ts | 6 ++++-- 3 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/addon/mod/data/components/index/index.ts b/src/addon/mod/data/components/index/index.ts index af1ab14b6..ebd311e52 100644 --- a/src/addon/mod/data/components/index/index.ts +++ b/src/addon/mod/data/components/index/index.ts @@ -286,6 +286,7 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp let entriesHTML = this.dataHelper.getTemplate(this.data, 'listtemplateheader', this.fieldsArray); + console.error(entriesHTML); // Get first entry from the whole list. if (!this.search.searching || !this.firstEntry) { this.firstEntry = this.entries[0].id; @@ -305,7 +306,8 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp }); entriesHTML += this.dataHelper.getTemplate(this.data, 'listtemplatefooter', this.fieldsArray); - this.entriesRendered = entriesHTML; + this.entriesRendered = this.domUtils.fixHtml(entriesHTML); + console.error(entriesHTML); // Pass the input data to the component. this.jsData = { diff --git a/src/addon/mod/data/data.scss b/src/addon/mod/data/data.scss index 1bce48a43..f99ba5150 100644 --- a/src/addon/mod/data/data.scss +++ b/src/addon/mod/data/data.scss @@ -3,6 +3,7 @@ white-space: normal; word-break: break-word; padding: $content-padding; + @include safe-area-padding-horizontal($content-padding !important, $content-padding !important); background-color: $white; border-top-width: 1px; border-bottom-width: 1px; @@ -31,6 +32,18 @@ @extend .col; min-height: auto; } + + // Do not let block elements to define widths or heights. + address, article, aside, blockquote, canvas, dd, div, dl, dt, fieldset, figcaption, figure, footer, form, h1, h2, h3, h4, h5, h6, + header, hr, li, main, nav, noscript, ol, p, pre, section, table, tfoot, ul, video { + width: auto !important; + height: auto !important; + min-width: auto !important; + min-height: auto !important; + // Avoid having one entry over another. + max-height: none !important; + + } } page-addon-mod-data-search, diff --git a/src/addon/mod/data/providers/helper.ts b/src/addon/mod/data/providers/helper.ts index f59e62e5d..2d48ef390 100644 --- a/src/addon/mod/data/providers/helper.ts +++ b/src/addon/mod/data/providers/helper.ts @@ -599,8 +599,10 @@ export class AddonModDataHelperProvider { getTemplate(data: any, type: string, fields: any[]): string { let template = data[type] || this.getDefaultTemplate(type, fields); - // Try to fix syntax errors so the template can be parsed by Angular. - template = this.domUtils.fixHtml(template); + if (type != 'listtemplateheader' && type != 'listtemplatefooter') { + // Try to fix syntax errors so the template can be parsed by Angular. + template = this.domUtils.fixHtml(template); + } // Add core-link directive to links. template = template.replace(/]*href="[^>]*)>/ig, (match, attributes) => { From bed497d54d1cee15ec3b5451d163e1593ad855bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 18 Dec 2019 11:46:46 +0100 Subject: [PATCH 255/257] MOBILE-3213 style: Add position fallback --- src/theme/variables.scss | 1 + 1 file changed, 1 insertion(+) diff --git a/src/theme/variables.scss b/src/theme/variables.scss index ebab93935..8688e1339 100644 --- a/src/theme/variables.scss +++ b/src/theme/variables.scss @@ -474,6 +474,7 @@ $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; From 7a1b49cae12c3ef62e19140e41cb21667dd26756 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 18 Dec 2019 12:36:57 +0100 Subject: [PATCH 256/257] MOBILE-3213 workshop: Disable refreshers when inform --- .../mod/workshop/components/index/index.ts | 1 + .../workshop/pages/assessment/assessment.html | 2 +- .../edit-submission/edit-submission.html | 3 --- .../pages/edit-submission/edit-submission.ts | 20 ------------------- .../workshop/pages/submission/submission.html | 2 +- .../workshop/pages/submission/submission.ts | 4 ++++ 6 files changed, 7 insertions(+), 25 deletions(-) diff --git a/src/addon/mod/workshop/components/index/index.ts b/src/addon/mod/workshop/components/index/index.ts index ebef564d2..d14b2b04e 100644 --- a/src/addon/mod/workshop/components/index/index.ts +++ b/src/addon/mod/workshop/components/index/index.ts @@ -154,6 +154,7 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity promises.push(this.workshopProvider.invalidateReviewerAssesmentsData(this.workshop.id)); } promises.push(this.workshopProvider.invalidateGradesData(this.workshop.id)); + promises.push(this.workshopProvider.invalidateWorkshopWSData(this.workshop.id)); } return Promise.all(promises); diff --git a/src/addon/mod/workshop/pages/assessment/assessment.html b/src/addon/mod/workshop/pages/assessment/assessment.html index 96b65ea32..76229b26a 100644 --- a/src/addon/mod/workshop/pages/assessment/assessment.html +++ b/src/addon/mod/workshop/pages/assessment/assessment.html @@ -9,7 +9,7 @@ - + diff --git a/src/addon/mod/workshop/pages/edit-submission/edit-submission.html b/src/addon/mod/workshop/pages/edit-submission/edit-submission.html index c1530b1f2..71b1e535d 100644 --- a/src/addon/mod/workshop/pages/edit-submission/edit-submission.html +++ b/src/addon/mod/workshop/pages/edit-submission/edit-submission.html @@ -9,9 +9,6 @@ - - -
diff --git a/src/addon/mod/workshop/pages/edit-submission/edit-submission.ts b/src/addon/mod/workshop/pages/edit-submission/edit-submission.ts index 10864c6ee..816a29b46 100644 --- a/src/addon/mod/workshop/pages/edit-submission/edit-submission.ts +++ b/src/addon/mod/workshop/pages/edit-submission/edit-submission.ts @@ -274,26 +274,6 @@ export class AddonModWorkshopEditSubmissionPage implements OnInit, OnDestroy { return false; } - /** - * Pull to refresh. - * - * @param refresher Refresher. - */ - refreshSubmission(refresher: any): void { - if (this.loaded) { - const promises = []; - - promises.push(this.workshopProvider.invalidateSubmissionData(this.workshopId, this.submission.id)); - promises.push(this.workshopProvider.invalidateSubmissionsData(this.workshopId)); - - Promise.all(promises).finally(() => { - return this.fetchSubmissionData(); - }).finally(() => { - refresher.complete(); - }); - } - } - /** * Save the submission. */ diff --git a/src/addon/mod/workshop/pages/submission/submission.html b/src/addon/mod/workshop/pages/submission/submission.html index cb56ca01f..41fbecd1d 100644 --- a/src/addon/mod/workshop/pages/submission/submission.html +++ b/src/addon/mod/workshop/pages/submission/submission.html @@ -12,7 +12,7 @@ - + diff --git a/src/addon/mod/workshop/pages/submission/submission.ts b/src/addon/mod/workshop/pages/submission/submission.ts index 464b2e3e3..3fb884e5f 100644 --- a/src/addon/mod/workshop/pages/submission/submission.ts +++ b/src/addon/mod/workshop/pages/submission/submission.ts @@ -373,6 +373,10 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy { promises.push(this.workshopProvider.invalidateAssessmentData(this.workshopId, this.assessmentId)); } + if (this.assessmentUserId) { + promises.push(this.workshopProvider.invalidateReviewerAssesmentsData(this.workshopId, this.assessmentId)); + } + return Promise.all(promises).finally(() => { this.eventsProvider.trigger(AddonModWorkshopProvider.ASSESSMENT_INVALIDATED, this.siteId); From 8b0b9082d279bd16af067a48b68b8dca288f7488 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 18 Dec 2019 14:02:16 +0100 Subject: [PATCH 257/257] MOBILE-3213 database: Remove console.error calls --- src/addon/mod/data/components/index/index.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/addon/mod/data/components/index/index.ts b/src/addon/mod/data/components/index/index.ts index ebd311e52..b63f00ce0 100644 --- a/src/addon/mod/data/components/index/index.ts +++ b/src/addon/mod/data/components/index/index.ts @@ -286,7 +286,6 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp let entriesHTML = this.dataHelper.getTemplate(this.data, 'listtemplateheader', this.fieldsArray); - console.error(entriesHTML); // Get first entry from the whole list. if (!this.search.searching || !this.firstEntry) { this.firstEntry = this.entries[0].id; @@ -307,7 +306,6 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp entriesHTML += this.dataHelper.getTemplate(this.data, 'listtemplatefooter', this.fieldsArray); this.entriesRendered = this.domUtils.fixHtml(entriesHTML); - console.error(entriesHTML); // Pass the input data to the component. this.jsData = {

5+wln$y*3R59i_s|+)9TcpW$PIXBEyl+0PM3c;Z?mA3y zRT39z%n(`}WB2dYdi=+bk%z}_=ymSW0;{H(JREnUL7D}yC0DmGvNdUPEfRbP@jCMe zNPJojMGN%FLkGXqy1L)WBcoHEhR{&s{M?ZYomL%0(=;NQUU zU=28Y*~Wf-{?sYk#qhx;@q?tQy^qmicf(uTiGTVzA9`Qg5$#%7U&>zx?>0&WHl2Il z0HqB`*4n6quw4$CC4s~9iW`c4ebDW#TPGgk34tK=DP-4*H&7@3`{Qu_%R7Ae`SsA( z|GtjF*Lm=DE&K}yU-958CjL7SzGC86O#F(8Uor7(b@&wtzarsRB>akmUy<-D5`IO( zuSob63BMxYS0wz3gkO>HD-wQ1!mmj96$$_UL_!sY-;t8(nqCB+Jhjd+?x#y}h+yzU ztB{yb@UqUz!qV}$h3#=G>+_b@PBu2+*VMwo$-+WKd};E3T@V?KK!zp%zc0XcCFOn@ PVdzgjKh~YR`rH2j#qDb@ diff --git a/resources/android/splash/drawable-port-hdpi-screen.png b/resources/android/splash/drawable-port-hdpi-screen.png index 8256b58de7ee9e57261c08de3776f6493bb69838..8c7353e9d34e0477d8e587c125b3a8ebf22be408 100644 GIT binary patch literal 10835 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 literal 14867 zcmeIZS6EZa8$OD<)vX9_6~s;5Do8JiQUxqTNN5s5FWHEKp%XeGK}A5RQUXeg0s#^r zh(PFx^d=xR^xiw6gpfe~`JKCSah`Mgzu4=UHS?@VX00_}zIo^U-kE%SZm7d`k^dqG z2L~5WR~yX1acY`_H7F=Op z{NtbtHsIh0y2HWo<~;|;5xeTmG6#pR0td(HzZ@KDpE)@AJkzUac5%T|*V3DVgY(+|&Qlzz={GnyRR08OKZXR1ZcHFP3;Jek?erAm5t~(9Re$OJ zWAiRt^oWuI<^bT-s8Uh$cKn-OW*zVRs6J9^B`}^*{TnO zH-FijeZ^aK?&_Z@<6B>Ty>tw!u37azIW#peYS8yL(xrxdp{)gmf3mM;P#zf6)!V8f5B?hl|3Sh3ZAke1yD-NaV7D?|QI65U z+SFmzjf|a=zx~IJpKq7y=F@|rad;LSd%w&yGIEB2h`a@R{b)y>l(TG_rvmBJs#QU{?w-(`C`q9`_k>79P2qBnaMiXdSCEBma%e#ttfQW+-B60 zG5OmND-a@0F&6(hvXK>EamMc1p9&n-&D^+AOXZ((J2f74)fNQoQ>mfL-}P#3nw+`i zjcV_m8m7Vy!D>fx%S@-1>I|c`DSwmmU*4XHv;57$apRw#|G(L^wxG(_S{ikv{d(31 zS_sD-`P0!zskpVa-=Les?y4Dij?e3VDjZk|kHlM-1={FDxu)i>Zf#M9imc0n_MJ1} z9g?fd%b#eO5@$a9{Hc(7?;g9=1o5>F-mGAh=$1xw9m$FjP8xElUJwUw>eTwKYMeto4f(_zLt;(IzLj>D` zauQxl2lTcsK+H;Q_>7414FdF^bKO)3kyzMkzk4xV%kb6hFrufz#0w^Sv=y zuPcDy(9qDJ6Z%qlFdeS3({#kWz{Et6D3GVsMo{SIJmAgEt-8MZ6SCEl2o2h}chK^;h`s61%g%J_f)yE6 zcf?>U)R~u92%b2*xe1XBPMn12NhHnk^70i$3_W_1Mxt?PUEevYFmh!YDAOhPQWy|F zt74X#YgO_hELqk%_q=exBO{IdB)`pZNuV{yy%xM1E0` zn@S~}u%h8mlP3`gDKdMYqLSY!SEEm{@s6pkt`48;d@s%M=bzi6ckk8Gy++Q91V2}v z3N956m_Fq;@&nNP!r0BtEmaeF^x3*%U)wn6?b}nnzP>BPC#%JK-TtIR+inhpbEhBH zZ~Q%Za)LT$kXbeEB)X%<(Q-B(BE@!*8?o-GxpR^3-Ku$DT#iGWh8gsPn&Qhgp;ATX z2m+|d@cyNqGXF!1#yE}G37|#@sWzQa%{5;gE^L8+%^b^2FVKK~z3@lCyHUkjb^pzQ zIMm+l37>w0pre&MQwg4d-rjGqv9Wcro0)8zLZKKX*Ws^EFKd)dPK#n3r3-cK&rbYG z1U0%vcvSQxCnqW0&Fm#9Kg@m2(#{=uW$NK-oO66NES{z%IO>?P(>Wex=VCy5Ce|)K z!_|9YwfR}$&+R{wa&hV2k#iPo*R5X3P4j8wQeLJnVMH_uY3GBW?M}~%tt$+-LRgG_ zzK|Wh>ciP6bneIWXHD(mmW2&+Fev{gzry$}OL?vDPo-#jFw;ouoMi5**uUZZi=cjP z^x86iNpnifF_!SFU$jsn+x21SF*VJ+!^2K;!$w-`yR8&093yKOuU5T~D(r4vs!NA;Ufturq;2#nt}J!+UM*H2tqPR{K~NvnI_yY7%f zjMZo%#&MsCwmFa+uiF~CGgbO*bxa0*zHzH&_@==ZF=ZraJZW=u?sDtKs{o4^Jxj^* zG`Akmn0Y77EeTXd1p?rPgx^xWf;@KD&I{e7Xh5Z|(|TM^U*kTN`RG}!y^w6} zbG%Pk8*##zA`msXMi^2L(XTa#NhB&^VfY@@LFs6q87v9b?%(WKK@-R{ zJ8E(RVQlY^-NNoy0`S~8qSiPtObO?ZJN`I|M`+&>&|0VGzQ|PT9BSM4<}4N?yJhr= zhv4_V)XXbu$!%E4o9ZPLJ9eVxNi2)VNbnk@3Z~N`=Za@|g5=mi`6`vjiZ zf5kvz5-}0Dlu^A|y;jD?E;fvC>3H?XPHM=1{$4zvmp|THY9hk$5E;_ZJn+I!O2Z9T z8Y>EddX{oz<3Mugi$(S>l;#mNx0kv|H$8=-_h>{a$gPxD$eWO*s7YA<4Y)bZ-zxVn zCLC>_AVoj!0&TI#RDlq2+%4WQ=Q$ig3IB_Pn$8B?%)D9wwZU#P-OP&KR}^p=&r>7(uO zX9$gIU8xQ5jE1tbRgj^De%$1@Rj#e%(V7OA7Xy4RP&H{hvPi}-Iq`~zKJy3-LhWA$ znUCMuFHXr?H_)~z+=!e!ehfimm|>k&WoY>Z?wgV`4|Y;Hb5yzyPHHbz9NC9+82trI zSUzEFQOYTH1+O}jaTYS%B2!%<%?Q*V`E2~eVqcM?#ba|wpvNJoUs83n25)pa%Ye=x z<@k(1L9)%?(RW@+6I>TGbD4+MM7WW3-*XIP-7NShkKLual72IC`~Vf}3k-uEd=Nu? z!Ti`liYF(zds`)NJMC_8@b3uOMY!eWb}0{r0RZf#L~AvS^yJAu%$je-e1ewhJHt2; z966(evD}tq^35Il3ejI+`s7F&?3*{BCgw`#k}oZ3 zS#+KQcJ@shu6&&{ty~YP+PrN_XI#VfJxk+Qnv&+i2leowH4jZ;uhRxb(J?v+skWO0 zr7N9`@8)?{XmzR}Z};)L@o|TPjc**avsb3gE15Q3JsPxh5*1ziKkTC2DJ-!4Oaob_izaLu(#Rhm77V?=hKq=fW!+=;D31ZFbGy-E==f zHH5b>Fp)UPURwVh?;3;H1RAY#)P-z2UJ~t%7eGlVX zl9ZH_FTK8KaIR%imZbH+zFO}Kn1*a;ZaWtG28C`fT2uLV6bZxvWZ@VfY}=;u*D7}Y{K<`xkbM_#dMj+-PXx04TN^g)x5N_ga%JM^Syr6)I@wK@w;#T zYx7cX`)0xBk-Ia`T78fo9#aJA;-tLQ9&-NoC*!wm0)s`WP21f>lB{MGG972rwjA9L z9G<~U;Q(BnUVD!wVV;i?wsddkdWi58)TUs*+X%E_^32tCGoqx~4Sd%OzGey%sTLbc zR2X%5g}92x;gwOkD5UUIB)_oShuJG$z$f3c0%$*vL~C!F;84M|^O*2MP1bk(R7eG6 zGO4+uTOdk;A(ZT=dpw4%x1A{nK@6vwXMD{ym2^g1Stl_MYH z;-TDmJSbptGAv{ZLLFSF3XOeUb|~wxdD-Zmp2)Z{fZYbBKrNCtE+ol$zY(n zX1il@9)9w`p0}D$!pgHDO5BkSftTR@Sw+l#t%Pbv$DJbOG8KJo{&7jVu9RQc6x3H2 zzy2pe+A8;TMZ0gLs6U5)!>eLhvO^-C6@-P1TyMF*m{zi0t~z|}4oe6@8n-V-r`?ku zx835$$}_D_sxW$?)>tAeo80@0_KwFvtio1SdG|G7mv3w0y^8*gKLSqUAW{a$Ho_C^ zDPMKzAV^O|t73P0`YV3nW}TD0DJ6X%FSau4n{#3Q7v6%XA3#$v*wJ0MVqCmS+AZiR zdWxD6}=-*k0}*p(DAkTQdY2MB&_=uczOAvC{unb!FkIkche0$-OGv`N3FYgh;H`!RGV* z*f^l^EnLxwz?qaV|F1eyK0p)hluolll&AG!zP{B(2NOt$t5@3nf$aH%IH{xG8DN)! zAC|6-I|k4I?UggogwZ@;jAr%qW^k2JWS*4O5vt0(&Y<^KHg-sk{!Nqads!MeA3-hH zLR(oiT>mz8zSXi-8u9e=_zn!#3dcD(1{}xiHyVO*M!mDrJecDYcqw;*Y~FVT-nHA* zv`iB?jRiSsqU8_`+v9$$Ap@#BOyG=GUgWkoxa525vHiW9KNBsSobpDkqTtlVYi|VF zi+$$n%yE#u=dkCkavF9a@P$`uE{$6FADiYx zCaJc0CepC_$>uZw?q<^P^wLJc6tk=^f@QjLdn%s!6|$U9MpO+;ZdefJGv)I=>se-p zmtYT>PCwT5`VLx4|GIy}8k{hIUoH>HU4E~(;3yZ8{FV1V{*6c4;KIFljD#&Jbl`5bVKCa@>#B)Q3$$Px zs>z$#znxQg1yc@>A5T9kWpg4cM$62f-((3-{*JyrQzlhzm;yz{B{0a?Ljw_ltiQe* z+tb_q0m|1=jvWIqDIFnxDfEDykCciCw>0T6bAeZK-|p$tr&1}M?8t$WPi~-@4_e46 zuL2dyum$SVp~lrO^A?qr7>Y zzL|x~Z6-;Xo@{_T`8L3+g)9rIH^6R{8<-xo`7c0er>d%SD2`L^D7%%$sSDPHuu$2l z)vp7n{EI2f(yVMd^^Re7z>%K2{J9^k=@*Z$2v%_2X)W?4zQ2Ymf7%{`GU;tMZ!=KO zCt5S`5D~^!31H?XV zV3fe_v5C*@E4WE61ydJyAu9CpHQiDX@s7C8Za4pLQ)doG$c^k5`Ax2o6hy%rDAh5xb=&URohQvPt=H>T zeVX%-R_#4n=nZEf7v-uP6|-ul8M{t`*&W_NEkMcfS_`Z{_yoa8Xzt3-I|`;ZS4>KY zF)n$sDe?Rb-pL(aQ;%9mAjC*IWM2XH})%P6Cu4TlmX&$+6Z9XhNx~C$(rUj zCD-UXWWxIql5!GA<}>y)KH2_G+9OlrVK2qeW5-q@J>-msHKomiiJU8%da0ZG`3$Tj5khQG)}glhau=OUQhncLQOH^h0qG$?tr?*5e10$vm{`j=zCw$|^Z%+Bz-%PH4 z?Gn9(c?d8Agw_pR=p|jmm-TR~tLwa{0b0{`(N>i4?}G|M?v{Dqf4HUH@zr-xXj|7* ztHvg;j5OiTDFZv~RRly*&M7-tmpULxiV5LIymhNKRhv~Kt$@^r*Hd!A_3TOS9&U8n zPc8v}Omz{t=wQu2;A%@zW$B4hPa~6S)%6Ya(gf4I!KrP&hQ>bYH4Sw9+U6~JU2GAE zq9I5sJ-LhfQBc2$5;yIPyq+QANL>_(a5g(w`(P9hH}3!=cZYXt(nuSs^o$;-*5=lW zo)(Ln!<>y5jDv~6ExTgk&4#@tOA&ExG1coxCGT3DlMz-MsaZAtjsWq05y{Uujf2md zbu>HV{B04KyI#N_d3T4d87AyTUTj{sy0CBdgKkycOW0Oe7;Tn?XK$D*U#4Xjz?fSFRyH{%LcK_Pt^K)Z+xCKY>hjyvSnQDvj1kq`?-G9+QkRau|_$w zJOIv~(T$x{L7N+6#!gblgM6u<|1w9j?{v9lH zRQ4U~EH~xrw?8@&HKe&Vl18f`wA)r;O0d>l&?d`FrIl7GZxg>^I?Q}I*Z!ef(w2dV z^nH-{{m#wP58poA{iSGC7gWyHNL7QorjTA)G5-qE9O`bh7tf8B66?R0+Rkh!E^yn` z@;UU}g8#PfO9})Vk@NzSKD^|6O{;ThgffO648QQ3>3X{Ku*y?P| ziIPZWOyMvihIIO>;a;0F#P4*Jk$IG4b{*jNFVfYN#d z2RY$oPoO6vv<-IX{bhA^@|b9#p?{#-Go(~SsG^RE6nit)Pw!zTw<%z#--{TvP(5k5 z$zq4EzQ|6%ob?|Bhj1H2MKH`nX~rd5`FwYK3Nf6V%vg)-Qk4YGk2-dgE33WVhW~Xv zTx1!xNm)aF7MoDp$7;A{bHW1Tn*nqZ7Zqv!;#8ros5C?eZ&XOwPhy?dT$2U2-6 zd^+BYv}`rx327qyiC^;-Z+W>_RNl&md{M@G%%b@XlN?S z-3$_>h@+ZSIkqIy8@#qU{vEcy%@y+GRN@&ntWs^eycd5NCy0)gI7IA!Doptx+Ri&R zzm{SSFA1)Kk{(J>`hM`$aFot{d|Jcr+AF1Q>*%C%?UkNWsKVk>iSVbZ^38OlvT7w$ zM>>>>-QaS(U*HGk21*erPo`Tdz4HQC@_GOsS$)BCgi|M5ZX zf@_8lDF%<|sPNE8t)$o=aF9>aISo{7_Dr@u6}mr_uFKiv*VorikhE6jMe4tls?3$T)?g~r$9ByXNe z%y>~1nxdPKr5c4+IvOvC%E^JQL}DbZ4#Rf#(fKuGXz-T5(0$~mQoxasx0xpq41{N~qeZ1Ud;A`lhs?@M!olb`6BPCP&crLZ(Kqh0s$B5WCoGuqZ3L*lDpQech?NeJAuoh2 zrdV%qH)x3*k&^b5wItn&UlvDv;bgOxo2F~E!nKLFB#$@z8(O}bxcwNZBvn?P=D+>( zn)Zq1w0F0vj`vbG`u@j?H@m#<6dynYCOIS0t1u-IcOZ^SC*@v@`t_Ad1Yzq*Sm#%Hg?{+;+x^5B|~h@8X~-XzVVL{Hm=XDJTBI4bM1 zmDT)p=39s?#d7iEz&!wJ|DG7R=G#F_L)9X=A+*B-DoJ z6RJy!v~7*e3g_j{tqyTR!#?G%M4pDn^DIZdGjZ64V=iMiFy`q6Pn`nDQ1Eo81VJQZmxit@f0vRK-Db z%aT>eCXhSXYUP?%v8|NXq4ss)LHNy@!#7cACUp#dGJQ*Og_H6`^LdR+KR)ZWQM~o` zqNm$RN$iGWYtw^D!$E;baWe9F`xQ?FrZclRgBi%p;?|ViFHXqPrk+UvB}fAaGF70% z$U4X-`=@$RrONq(^X+#~rv9Ou(W69v%C2yqs7=~_v=NBZ`|+go?-p5E(AKdCKKNLc zO+8}!9>jIhTrWb|u0-FbLFkJi#_*+9?30{uafNOI;Tn188eGVBgf}M3vpnm7yV--O zhyxSIgL6m~`={wrRddd!1a8DIX0zTrkjs@r>p>kzudP;iVl8D)Y zdWsaieGr`PXfCGqrWLeX#OP<~!?}djr+Dl3?M+Xn#?YF(X@AWy4SL#SN3M8uGm{7L z!AmdbXx5!_gC3Nq@D94p(I!1(4MCb!Jdt6A30A&(n{w9gvBPXf>=YoX2kg^e3JRfJ z22<9*r};oM^&4QAJLDA#$eo29xL4$7IQ@;}d7)kq-iA8$^*8=O-f%sisO z4zK!v5Aw{Jqcka7R1LFV8MJXe203Ag0Yzeoa`Zue>A+1xG1-3XkmL+XPaO*?hDd-m z`UONxVxsy8U6F=I*8qBxsRmMG4zA+Rti9e4G}O0XVA3|k)i`SYDwcexhrP^79A_$b zbUl1VKvpwN&A%p6;d{}$X`)pr`AC0+#xwpU8HWIqcf0s{u^KCUt zI#vZ7ZKsO-oPic^7VbsZfIvQBZJuj9SqUPY2Ow*+aE#7-HYD}?*STVh}PCi(>iGG z34TRr{IPvYpiAiCq6f;(yL`5PlFUdG(U)3(*V453gHz7O2k*JE-&Y3c`yD&ukl{du z5wFHH$h2p3aOM$TW@^T_KTaYl9z zm9|m^t?xc$!J;4mJz5bHvvip$ws^5|2UGVbjI$Ws_inzEOH(eXd5RZH{Cno!2cZdY z>*JBLH^(o@oVw&m`ZcC*TFKVjam5MYL91k8s`Ay13Gm&7Ez6s%@eFFu2?RB9 zrthaS&f-$fAn&UFHhkug^}n?go2-T~C;=&wc=c3EQ_4n=Pw8dzoemM;L@Qggc4^&P zr6Pg{kzYMASRpXOUdqNRDyuQBK!_&f;tI=J-exbA&FX*|wVsUpuPxGxUeR5E8O`%V z&_uEE&Sj5wKV+^$oNQ#{aIA*j&e^GCsf2|3JC0`<>jn*NK#TTGhvQ)WboX5{q*MUz&+!Wa^dGiaI>cVZL+}?17uTBr$Kn-B+_Zd)O2-9N|_}qg_?GXyq^Ah zyrk}-52qny2hUbP>nvBNZb`!KHF0J73)h^u!HY@y&6me-DldI-r8tEyWyHv8t~}wL zM3|`PP^XEWHLmbMm2)O(Ne?P*rS#32#E?&;P86+`){{>jGd;HXv*%G#{sUoMWZ~dc zq_8^76Ch!pR$@0PX$l579gFZzF*ZCeeK|A?`Xk_;w>#|%-T}qiQl8~WzhpsaLdGo( zL^f8yd@v4fy|1h_`yKHC3uFmYt1@=B@Vn{fBxhqU=&qyg^%U|+L?S}(aN+5Mx6o47dbT1s_~<;3tg2Ca zak0tK@MUaf-c!+wvgQ9&UXk9O>A!Sm#uWs#1j`s`Jn04UNowxLHM&pCa9)buJ$56y zK$^2=D^O2*clac$SOOqSttER$1RQq5d^{nhE2i?p+hVsbrk&vOtFg`9D$jZaCbJLs z&D#yq(8tjNtYNA}S9;zogJ51wJp)4R8qoPR6VEAyO|@ovT%|e;!mh@~_zBU3+m@H`wU0KDN)p5FC!Gg+M~6lp~Nn$_$S zF|yl;*%u>yO2?FmU&?HvUSrEz1$({LjXLJ5na@gcUiXf`y`iRBG9$;iM&q(%di(lb zbas)T?Vq-5KJ0x9g2Ex|_2C%#lqBW>6bnL4PWPG*;FIRLRSkO!rUw4y!-(wI*c?i< zE0Uid{nDTcpv1}EZo_V$lN#NDl7HNCkGINgGf;)NZ@SFKs+sgQC@$pw%Ad%~N+J5r z)#Y4qSewZ8S*WA-OiGh??CF!f_Tj!(-Huz;qr+Bt9R}+9?wf1@6jeIj>-7Gk8RFqo zortxS^-uzv$$>BoG;?&SbIU?<-y>0EH8H89TtB{gv8~HSRGIY>z-fq1yPM~yC3lfb z5kJvz^{RfoNAvFn0s+0oPHjT><;R1^_^|~iw-Fn$cg@6cR;E6CEAWZk7Fy7a?4sHh6qnHuXTKF;+(}9WwcQ9)+`! zXBU@b`!C8Op^fH_K2qwuym?76aiQ@d5t(X3k*UI!VjDh;9j^a6X28#?j}GHa>62YE zYGiB%_?3CUcrp&Y(Vrt($~IEc?3KAT@wXeDwwZLG`av)WCVrBK>gZV zI4Vi&O%BA$kdYb8SiX72P(+==LU5Hod$o6=+Og9hL*BQiz}D(HK0}$ga1?AG$zkMZ z04;B(_ysEYl3^R!0x{7}%>x4$7|4yoxk(w$yv5>QbGzR6w&p79vRaady5lrfhxR}8 z=*K-sc9d`0&T2emklE(okz7A1v&$!&h-8LeVsBkKXV(;`La1(K3Df0TuZYtF=rLW? z!>rnE$ljaDac)@aG{>7$r!W1N{bz&!{NTS~@E;WX zhlKwj;eSZ@e-aRmV2yL9L;-;_tb{QE&D^onZ)03agq?eGDzbENxyzuyfSV z;;=~Qb#~tGtB3dx$q!yW>>@){uQf536%rH7sB|m+QfM3yS3wLs8OFHvEI4XPCJk`n z+HXSurQdtc6-}EW0 za&gAE;hdawi7osdFMqFI>lL4$S=#1Rz+Hhur{(?rS^%a3zz6t`9M!!<{g(3mp?^#N zssB;-5B-m_f9QYO_x1mh9RQC1v-@{b{hz!4*#6y8--h4)HI_t9@Xq)A4|tE7vq$oN+wAsz@9vCU`bke`?BxUuw=V!*w3>41)gVog^3hJ zWkNau3j|9b%T5<^n$C}9yZS%Pg_Z8`9obG$Ff!@3(V;EAQ>X0>;#sI>mVpc~>@sYc)Yv){+>{gRqE0x2}{E8 z1P)7S!rGpY;{`$NekT=3E_K@W=xVXMC)ZCkY!u;mHR>~|*T!P}Y;`OqL2u$iGNsRb z4J+C}TY_$(6=nw0!q?Y^hK6=_b{vW<*|U9Cj+qbc3tFa}iKYh=>~k+Vv?4|kt1qun z)^a}d{F;=-O81rS*WID9qP)1jAi? z7sE+Gov%q+w>0bPTb9xa@^{54e|2YxHcq+Nqiz7FTujC*d2ph46Ne}^QD{CPstnj| zzsa4dflL;4T_r7^m!5AjA~z9-K^*^!W)DyjCiP@~H%xF^&3H`L)xD3d(Ilc0-MF@f zEF$`BCPG|)DQ~kaB!9Xxt~YKgj1Z7bz(UQCGDfA7$F`g0{Q_UGxutUpT?d!4Rou5+ z!`-+qd8v+jHHx3msReGf9hLKyzf}J^{5H({X;Qh3GLs^9;i5zhAfGeU!s?eW7jL=#4iE9-MdPRN7-%8t;yjaA!;v@C#UsTc4 zZ1zTl5Z>Qgu?$=O(bdEfE>hoKvT`#n9Bh!@M`ofTI^;G$puvj=gw7`Aq75MWIx$y@ zltC6TpBDYXjT)Y%4taJBh^Q$UxclGBT*sD=2HbT_JgtJZBePH&npUYS9fj9 zz^vk7NvGU^M=^fFy-wu1p-01ccxL5pYbY_qe5hBX%ffT8YD&<*V-1e6UbcBflUbl^ z9E|tT9F)b-B}@H3DH%K;$Ak009^r&@1294aUQS9hxA#R|h{gCv;`0R<&>6-RM?3t6 zXaQ^3;$FM!hD%%JqsqE5ujAmD9Ih^ ze*cP+(48!)Xiurtn?Frx_(BS`l*lNAylz`Ow>BjpT^Y1V0?jq*p6yg4q}MDMpPte1 zwRk<`|B0pE^2!5>Iff3s6~o$iyUUV3+G>(2HBazcjj=zfuREE4bS^B48| z_YPMpWeFK+E4Fh7^!G?&b7VzQf@~0@F||xQ%c*g+Q95OnrFh~nYreZ~F|48DpnAm2 z{`0X(!e!iuoNgJu&>Dd-Wg%4Iqc_D(RHaUWXoV_n; zm6`Ul51rvN)oR9OI*Mz|$mZR<$9K@IDh^SMkGDywm&u#t+$LuE2P={Lw^Y!&r;M8k zsV=p(9mgJ1tB8U58A5iUjnKdoqTwBK{p{p?U;*J7$N0FIp@uJiO^Pm%GMs5etqAJS z+W#l|O0TxwjXaT>!Yp?G8B*50#Kc@XCY=~xKuiR8^KSPQtWAIsx#LZlsu5y3T+6&W zLG3n&&Ke2pKhIjCzK3O~&jgJAq_xKNcherve5n_G0E2|qN7}ALI8C1Eab>By0qM+x z(<#w;xyP~6qd~DU+mni23<3M4t;A`-D<+U z4)j`oZ*!l*59K6sa?o{ArzY4>W+vsLtJ!|JMc07N%#Q zFsb|i>5RH|Cna8c@=1Ez3PMRS_(b;79 zd}qCPREto;TO3m#Q%3`e&($bYt`ol>NN2V3w}lFsFOzBP4FZ${6?Li@~%$zB>W&b=|a5JAyl- zB@eo}o!-SP%E%iSiS~bqt}R(*S^buOvioh7NhQeP&56Z}H>#Q`UJEX!B{9$Y6w+|a z3`P-nZzhn8SIM!qW?epp2jyNY&^SpW=QuJ2ClB6t2WN$f<@5f|0sG`s)!Pfo0J*E> zjOyPRD`teBDNs;5cM<>qj)EW2=}L-Qczx2XDEEUgYO4X@kPapr+A~>;TVhLXI*WsD|7eUCm`^H#d36 zr$)7c9`9L(e8AL_<0b{WlA77SWYg)<3eb-jVxYqWmrs3p^3nppIttN*VkbvE6*s)+9e)I9s@i`d~vm~tgrok3$7LU!-4V>){~@P_-C@!i@u&%YML z<%9-qB=YWQ=yj`YC@tGeCXk_98ZJATBp%j2eLQA7LCA9NkW!|JaY}{(!-LglgZh)_2{1Xx|6UKd1Wd@#l9- o{qOGosrz^M`ptgFWef{gdNJFb#W|I*5AXnM3y68;Wv{#c1M};>&j0`b literal 5368 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 diff --git a/resources/android/splash/drawable-port-mdpi-screen.png b/resources/android/splash/drawable-port-mdpi-screen.png index ac8009538039cd7c1cbe1987a603c13e188f07e7..a3f133a165f9245acb5e398ee6cfd4216997f6e0 100644 GIT binary patch literal 6232 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 KDtY+FtN#LVds>nJ diff --git a/resources/android/splash/drawable-port-xhdpi-screen.png b/resources/android/splash/drawable-port-xhdpi-screen.png index 3defde5164ec075e936267e3a1fca70a14ef348c..777b8c86f47bafd98390608a46eb11ca25b022cf 100644 GIT binary patch literal 19067 zcmeIacT|(xwm0lP7E}~;BO*1rX(B>IgisT=C6$lE_n;HS> zy+a~3(xpRyK!DI85CSB$ypQ{y`<{F5x#t_>zGHm%8}IneA7S!5$&FTfpgKFtvzXAlp~JdYh?=lb#b=~!~= zm1D=Q9@D)4>l0t%>LgpD5#QKi&`J$B7 z?Ybu>b363*@mHhTPg+Lf>>szpJbm4OJpHNUw+9#Vf4{vjahtW3=4p530zF;%_@{cR zCkXU;LeXCyNzADD>6r6V^b77|$AXU^yCCrI+ zr17UeZvx~r$FXC7NA4ny{kZvq01W<~{EOZHE0g(GN&c6z`*HK1$?k_-|C#LmL4<#k z-M`rV%^ra1`|B+JHz(8hpFj9lnf%L;{%$$?>*56H@;|EdUlsgs!uTIvmHwNu|MxZb z|9(TNFM6--lKB)z@YiBye6*~bx%j6;`LO>U?uR>=n4$G+%7p2lO_T=M+GHbjE3ZGvxh&u{=7jkyK3#MHOPA9UBL8!nT`eG{5^ zP~34*e+U@j5|14H21yx?0EAmvIXx=h(()ch-FRqjGsDqW63`&3aHQ-wT9|8hTF-z- zZuP1HprFBO2tYY62mp_q2%iRqcU1xSbDHD7dh0Yt<4?iI@BZ5Z!N(ha0-kLA>8}s` zr?;*uoM1c6K?>(Tc#^}la^lCkwS$+_ntx$VojA0h!WR)gQ>r=by?Fz1TD)PDC<#@#MpZB|CmE(?^s?lJ1^M{`-f|vZeJ`MqESu`pho>62iuL)M0}2SV%)AkNyz< zZ%&V*dRY7P0lnS~7`~=P@pXvErI@B~^S9Ka__lYruJFem9Ev;+;}9f%|6csG^=!o- zfTlDW6M0*oZobnHQe+)$p*KQ6fy;&&TC}Gd(~ZsHoV}RZhjUSpJr(a%hH(4q0eug6 zo~M!NCV%P?O9_5MnD+>TKG-Y+25B+1XKZllI2*^KnXAh0K1;sqE8!Pqy3tbEK!F=G ztr2DUG9`bq+(#Q@XYpYC0quaf19} zrqy~-w1B~05CC+SWymKO67=zL^+I|8piA!4f3S)DddqpVSOmO73ZORZ5c4dQgi}Tz z=8C>fJkFso{YJ9z+vTgwTIt*@G;d6cARA{3abKlVu^Q zrvNCB>dxrwcQwwS)e-_x0QBbH_)eAa3Fd4L8tSD5(%RVsy}nO3hOnLIdvEpHsY~qb zpgL`CmJkhFD<2rnHyfDWxel1+AJfx8seFR6XQP2{jO3LC2J`!8SwOAQ)B2QN#A*f$ zGjG6|9VY4MBc?Md)5%uN?|8J7`#DLIwvVgtBCoe&EiLzzE!I!yWw(+>W8C`63@JMV zV*!DchQnb$XEGQR9J0F}UiD-rB9xtHcw~eMNa6UYb5~@Z-skCsv=0e1hn-j6_<9`V zJN?Imtbh$y5{EBU;pLN~`8r6diTq}(3f8J7C~0b2rDNDyabd;4F#yREv=-y$T!G|s zn(A%T@7atkJ?f1(x{LU(lk?8B-q&4Ctr?hRRX`h89cNO+<&-iqV(t(ao3ll@z2!+hnwl64&!JkPDCyKu@i0>@`e=r`dFBFC$lN0 ze|9(SGU;)>9cmM!*s3gjowU6&bBLFe!WA40exhmjR#m9G@g9@k&tggCeL0vTs#BAB#*7!s8FudiQ%S|TlsatP!#;Oy6bvlK*M%DW4K9<&}PT7@R~U4E1$%+gCdic zKkbp0V%&Ii7J2399C%s3s*P+y&~4~GoZSRu=)X8mb1HHk7NuIAYX`%v^aB|?Gf`#i z2}deN6nu*~S{h0=Y@c~Rpn@{!NsRZ$o9kwB<3+Z$^rRduw&ugiW>t9wRtnc3#^O?@ z^=nN{&$(vC;OyY1G}(UIJ9}DqQwqX>wJ|kUp-u8_AHU6fr^wtQHy|f9TOUB;;C}8d zHSK_R9YU&qeEhAACaN9Zl_+C-z}Qp^=MaRrT0+uiiE)0I`!RyzX!e@z9`@R|727|Z z`ch(NI@qqP(H(@1NA(RJ^$l8KbLt)#!V-^=BO57F-hJ<(HYKsoU01nr1g;&hrHm-^ z3NfC9D=e_ImvsH)8CE6=&Rp^Hb|8b!^02N)-i<{@@l{2Oxq^lfm2*;VRX`r_OA^D)mmg|x?t>h}&Vj^O zflVBmCoav)#ia+8-@f&}cJ{bsj;R?j0eC;0l?jH|lUsUz*mcwkA-9xS8^Y;OZahwp%|MFS3i81J= zmWLG9hKgE^x8t?KNOQb=K5ujxPd4X0$IGyT!%+*rgz^-6*xzH0X|N~gL}qbv#7eg- z>EZn4OAD>mR-nUvCiI6>PrNtti{dXId@L|6jGQ7@hwt7)cv*8wrh~16?)^NhKijhe z87eyz&qQ;BpJvN(SvOkS=;#9WEb|57pth6*D@glQW*qPw1zh}N?-KJ&ilEf`EhvlG z%PF9M(AJLfF0<_tI?iV0XW(h^QSK&k&jEMPWVi|D#4ICETlCK3)@I>T$wF(ken0%Oim7a?Erk%c3z1ga~U>uxd0To5&Z*ljEH`~w* z!g%g5f5*wIIa#zT6BY!_{ZX2=x-+|X5ne4blARGIg1kNwVY{TA$=$QVp%uybe@K8N zyHxAzSIq)UfzZb*Y<6#@2sZatE$lqMygz*r@Zyqn)b{kXHkXNc=UXhoCw zSu@murA5*j==#3TxbQe#JY@ItNvknxKl5u69meS>x) z$sdMzXrZ!&#F8rXuC2?!eqmCvj#t0k8zh`pQ1Q;W zV^VcKE2ql0b{f!$Yxbi#h_SiK>xGeAHIBl(yJs_Jyt5y~LNCpqGOvY1l1j_lePGPf z0SDPy(Xj6oW5FE=)4=IvM50S`<&tiyPSxC!X?kRxAyV(gN9~N5GERD)34sT6*{3Ab zEXuXJZdIypvgYK2lhT82?RZT zKI1M`-RY|P#myYb^zUkR-O1jTjxQXS2XmyPq`JGiV-0n3+S=N}!ov3U_L@0iflV*Z zz!^KK8Vq{4QY7hPY;voFw|yOlXX!;gC8_msm86>rEk2SLbKMOFJkoZV<7j`;#J z|BZtqXe+Ae0qnfR!8~+`3+Y#!_>u5@=q)AH74B#(95moeY5x3}Pim@hTHrX?4p6m&F(aTk1Gr__s zPXbnt788Ty$^N5Xg;gCDnCB2pc3Qnh?J8`+A#YY`X`Tx`s{5;D<1|Y%$^8_~z;l35 zfeq8dIc38bqP>be!d=U1Z=d%p33GQ;n zX7|P!-&kE|XWv{baKJ~XT?gXE;rPIi+!fNb+jJK|BALe>oR&RdxI3ZqN~G} zy{wzit={-qUfSi6`eFB^rqfm-{0ZJFkj&XXX*$&H`K+SE(I{{^p_$`DPMs@8KDl2! zRo#LTQRf#d9P}{)Y26>1lY_Fci9wH+TI`Kh#0SaPB)ja}@4fBk;`DJD5dtFMQ0GrG zKjLy28db=PagKfz6m*!yfoW)q;#GLMiblO)(Dtk%x|e$1OHgNo1y85!8FO5j7+vmA z$6d$tLhp3F(3@Pd`V!TFMJJwcoT%~L@>>cv@-$HM&s{K*#fGg7$keS{Sq!!N=TY?& zm9I6dFDT1vJO@2iSqNYH<7%BZb1nikBp7CyU~AI);xvX%VGo~twD)+hUVEKruZw(j zXZJiecj0!wuu9XrZw+eZp|B3wk+_zQ&(?kVvsz#km-OR}*A$w>rWv!*tVt@$L=EvM z`HsWDngOr!i>`EA!nrHZb^MX^pn6T_N=2XF)to%{>*sCNdl#!W*Rub350P)suP&EJ ze!Hf*M7%vx|0T`tNE}tnAn;E~$0%`{R9Rh;=Hx4UYrI=1?N^I;zSrASk&WBZn4{}O zK+*+!Foo`53#T76vXpK>xDa>lJC?2q^MoN~*J zzz7H&&VXiQk3^xfDVq}T$QK-E@JaO<;@cgNrMN)D_9~y{gbuekMaG%N#(g=3ES(ge zN$-&g3M|ZakG=#%0;882Wn5QBKP!YqUK#P7o*oh3k<^*Y)DAFcIzX^?{t!`hvFYfE zb9y>ljQ@~{UU?}L>ZCWek@~Criz$@5(;;T5m3PHaWmTI0zFpG z$^c#^scQd2lHdyl9h-*LnRNA4D!|T_3^CRiuI5Sxw}lm!e1TYwrze=k%oC zB(rYc^jinNFC@)z1={u^4u@YKVwdVPWW&SsE19-SMhXLYwiR@0SeYZl3h`{qu?YV& zs0!^L@5>5gzl=K+{4~2Gaj>)4Z@c?a`Fv|@9$-*%+3fu5gA}Nf* zHBC*JC}jIwLVPm45y7!mVvDs^*BF3aER0KXrg)++ID_Od<3;uU+j}5lsTw35eOu`% zLaH~lyHUhwu-ZzYLT)1Tvz`K1wnIYk0U5invve)>(9r=C<}&Mvi?hCJ6S8|vJ3LRW zU{ivPB2+kBcgMIil^jD}iV`P@=qd_OmS_F^>2Unkc`1sdtYs zBef8hZf=UAWYThtC~=aVV5o)vVpbDHifnAuE?zm&t)HOcviZkU!}H^O@*U(-w(aq` z@kJBp^Yx!Gsfjy~O+}V*^!+m+Y8rjM#Pr_{eN2Nu%_dck}VJ zh~CT6DXD=WRC3v zOS>FEF)}$XsN~h_UIAxHM#Rl&?u^<-1KCR?vD`rqcIZ|6;D%U)LG|HG@vN=Zl=)6a zc#8Hmn6z8BQ}Arz50S%j35UAB+<3X-1DP9(ems_U8+Xvgzb35VqgGVuy8|04WmDyK z(`9%qwLe&!)!2VL!t8Uepc+_g4lDM}Mb8P2WoIXE4xTQd!!jUm-Z4mGXO7^ zPJPw6iij1~cp_c?_NI};Ea+Wx3{Ml1$F*|T%fO_sMR4nxnfcppkfdtOhXIYb{4U~5 z8HZ*zWN$(FviF`)T&F?9q4Kjdq_*jn=JPCM{E|_pX7(gVU@3->>AN#R>)w3n2}eqi zSmPL~WQG^}WB;Re-}!I!tR3$m9S>*t!J599;spT;Y75%%u%*lwUsAsX10|!SNh1*tTe!J_l zjS0DLK`!}sEWnSPcG{daeuwB}=J-shcPk;?Pr;FycYO*IN8gE)4U0Fae>`NDLl2O! zgh=L6lk5VlMu^p(?u|oncHh)7im)#`m3&SbAAmT;7Fak_+$2+jz65O$?fYGq*28KA zA|}7*`Sw0v34%&bc)h^6!6big2T7}XbrQ34*Hw9NeGBQYUPOLFTDoYnhWFOaZe{0N z>5rv=5KHJVBykpGq#*(;Gggy;WP`BL3+*wRV>aq(@r4AA2 zVm>vJyazt$gIoG966{beUs)*E#SxazoZYVAc2xj|5nzc%@BDQO-&gsV*I9~LABfe6&3JE;pZ~NDesFjo*XXl zh9xC%d26=ddpC&2==s4CZRkRb-SMI0cut_Igq}ypn}f<0hB~s8sZAEBV80W)(4 z=juuOWeamJ+l7WRn@LmW>@Grfdu3&|crwL>7Q;Aqk=8NP(Rb^dm~yX?a&_@}Kh9@7 zZC|>$yLesN<^joe{A6t4TK-i6&n*GhR9cW}?E<*N!(R+FuM3qNpCwZbua zaBS+EjO^fA<2%9bt(HSFpX|`O-jc$>B3W8=q*gic(`=gw^0cQ?&25+HouF6AKTPE$ z!4IexkwbZge-=o9sB=L#hSe6D&RdBW>AYR-BTk^ZU#^|S!;}aMxz48kdmj<%>J=I{ zK3kOM)FqgxB-jQdM@^s=R0;0qx|dq7NvAy?TQ78o(mQy>n1CO`^we=%xXJUL#608B z6__!)P0Q2&R?Bm)k*xkA98aO6(c13`Ys67yW~~*!Y=6nlgxf;moIS^`YP4ODgeC4~ zfSZb!=f37QZzE7ciJ?QIoM3=@ErX2nrN$Ku>3Mje5uY1;2NV^3@JE5M%rfrE=88a_yIac6IHrM@ja<3{>woTjH+b7;}Fop8#7` zm}anG#IDuYP`4{3ayX};=FK%;4_y|a@?Nb6$&xFBu@sndDT_A?>3t7sv9N2^mpmJ6 z<)EVrGDR^%R~6qtx-iS0I=AR&bLjHBjqMfzMGXSo`Fo2aI)ES5Eui>quT)8r=$)?o zRLOz2ko!8yCf+FHoue9t_~b2o@bq-UFXLFC%IJ2`3_EBZ!R=J6_VZD0R5ewcB#?pM z1QkXiZo80 z%zpKoVk!G=(4lmUg+plx#RPs99ym78N6$5JBm7awIn_V(lQ4g=i3&)1M=58yNXV6K z3Qu3O8uJ9wu~`W%iy5}YOHEXtsmBR|_i4RD^Y^OQG-$S_&JlJ&GQIjYgRrlIB9NAr z)<;j6HLaA)Xs_+fiDsa-^8Uy-XjIu)2N5p$Vf8{9J&Beq_ou`Pm!euDc%u0su0XLBev+ zp4Q-#!**+z715C-~!dh;T!Vrq|%& zh6BBN)^YUDvw10d1Dln`4|i(>-X@C2b$96Mjnle@%!gsOtk zCWq~^`#-G*Tm$l}XqI)i-tMk5x4(rweP-u$}LYSD8Z z3?wxvsVQSw2&07PD{J*cw~W}Ek1E(N^h6%swXrr`ql~~`V|Y_YxQg28#0lkO`QpLW zJS!K6#?5bn%tD#k2PZWACl)iKCby*TAvUbfVA^BF?PLX}_DQ=$`Q~4-)RAcgmNJlb zYN$JGa!oeqv5sJJS!}H)z7nHfw+8tMUmF6F)W!S3}i3C}h)65I?{x&o2O)A=M}YlupUb;Xp~e zQU8+BxInP&;WUG)(%1)aB-T_fhPj@{v={j8EZyg-lsvkY`pJ?p*;l|-+07*Yx^8Va zz}*e0BvM%}F>t2wei2##cjV+3VoR`Z+n{M%L1pbD&SGFk*BptIhRXs=@9t)!8wxyl zY`XuL_QiK%$Uq^j?f0LK1AqW;pl}XUZ1aM#ZNL@~z15AlLzjj!_FX434H8F2lid=u z0Fl{+p?$kG^|yC_X@8;MVbxHOJ7m4BpXac7Nt$U^t4$eNOmt`Zy%B>H!=~(BjAN6BPae`ocZQT3k2YrofAeCp|-U7gy1>oOH0&=GaK_>UN{iHL724$fXP=|X- z9{+?hx0K)22DtsuZWTL^F(@XjNZ)_G&whF8Wdz{!-Rd3c66#P%-!M;5=g|{NuYLr#5tww|kl55av_kAT;Haas^E)FY6XuqDuOK{{WkmuW*-xstJ6Ju-bc-ftzU^m(~TF z#oJMkZir`2xY5hKdmibrp9VBBa1hBQRseJISH$D(`CpvD7!}$>yBx3Q30kA;y+{7F zlhh2`6S-5aYX?5t{OewSbatj=fX(27^Qber1IQ8Sn(g0s27azEX*(XMO&S$W-`=LB z6fmLJ!|`UW7F@vAP->x83;dHuBeIl=c9D`oOD99GT0SV>LnQT!Cy(N*r)$k$*f(V} z%fTR=Dv&KuX)JdL77kcBvN3iF>J2QjPo}b6Uw#_v=n+bwcY`JDQVoc|-HE z#HpDZ)qz?zM{`XFn(gDtD$+Te(KRq|m+DQG`GXILAsH9X=Fdt0}?_dABNd`O82FW2WAc`xR4!51y8nf^eL2%D9z*tWW}MDH)P z8#caI8Iy4Utgl#RA!w){>^O?MwY#xlZ4W~>70Ly<$5t8c+=F>}-f@ZENblN}at@yUW{rEU_h7)MS05H9 zcdPJVrp(F_YhU%j7^b;QRK6Y6{AE*Nr8u8u>m31)U9ts+Ry1yPppasrb>;#=6;d8oeORFf!I2N;P*o^k%1Qf-=#5EhgKp1bT=#=nTQO-TdZZ z^+s9Ml1`EwMsGb!My1- zmb~;d@PTq~183Y8dAkLS7;9bqhUk3DRL3>f_XbP~C6lcP)2nzVc@WkWUPbSP5JQ)u z*3Oy;mApG2WXfm?g?&ScXjIO%%Zg9AbrRS;Ikj^|d0!6mR>tUiQ=HnV;wv`X~U*l*7eLyNnKKPRL;Izl+K2S=gAdcxKeVQ<6&Kg4sS zl&b-lwZ+f9+^*0%`h+b7GLj!Oe=i3xu+Z`OA6PicTBw&1D;2yl&yMk+#oi7=yy}}z zRVF6;-;aeERPTMJEgPkgged196=&kF<4ZqnjAOA9XkORgSf^0PNQLgAe*IFa0RK-X z*Z>H%^Hl|fC_cPU5KKqglN*P{nFI`l~jWUEY19 zzX?`9*`bHVBUVxo=`Pw<>qRm7%xCQud!d4m_TpNfcI4^Sw+BaqoLSoa5LU~h-WfS@ zWc`Z#9@K9$Y4O`dIYIM2m;m~1KDkY>+2uKA91NXZ*W3c!9n4Xy@M=nb%-ppSp zhUe=s!fS9^&6S&q;bOcAN53VL?gFs#+DigU8efg_W@A~hM+y7 zA%{N_I;uWc*%MB%`mm5+*m(}`So(T7^2FC~HR+Y)zbBE0x_t5{ zJRb~Jdk!IAjD0_==jdQigiv^BaI9U}0XK(CuG^4uuAE-NlHaw$CO! zO`-KIw&?LlGmLFWHla+N(3+`eu)-ZEh20@w?PqCh`Yt_nW3DbN&W}zSlRCck=+_@3 z7Z4EK8rM#^uYox`ur1f+=`|}HIX2$Pb9A-t5<@vK-x+pJG=00GaL52695<9VJAnH+IVV1$sG&C5KdkvzvB0b4oiAaW zc-Q53Jwn^w{j4wW~ma9{Uic`7nXyfhyMT>`sa_`zJME8tG zDcJeAw`q7HOhrw5FM?0W*2RVXCMz(%dgBy;U?&)QyAT~uCoQy!%>%08uAV?8YAZAq3TY?wAG&DK?>WS~Jh@J3aa+W1FSK#A3Pz+`*yK->-i+3^ zo(<~x5P4$C3R>bmv=d;?N7_n8cQ5naHOkYAh{mVO-+`XSuq!fn3MmiWx zpqI$)IZ{dyQW8wj)eq`rB(Npa`fhvuxduGp7I2B%*Lgn`C_51q)S=PObrY!*9ra~p zo^!$wb_Log?+Oxx;Ho$FIuh19M0~1^G^)=8D2#cu6>oHrbBF4@cL|hQdLWU`X9?69 z-4tP725}HOzkN~g`!|qO-X1R(;>W|t8I+ZE`%(_RJy+qWy$@vovDSB^D85oL1Y2;Z zE7@wWIluL3>3H+)5nprfbYYlz|8{Ks`+=S7mPm68r@>8Opo6EeL zB!o^SevcD+{`2tNsasGTq7-gAz^UjqNwCw3`JT0kCDZb`28#M8s)Eu+C;}P-aVmZb z2)WUA(3^#W4+k@{V+j&nDOctTlaVg4-@Rs|Z!rgdbhDg4sS%xppZ?&~=>EWPu-fI0OO>}P zvAl#(K54!ZaM?{&RuAzxg#L!-Q&Mx&uV-wqEM55X<-)!>!C5Y-nIWPpI@v!v<1cv( zpBTN`C{>`N=bOyk7wlX-O_X1BRc`^1&2zHtSBCqGJbt;m4KeL?4dy;Fvbxe7vyikm zve^ogP~HC~HrA0KIpTZ$aH)rp%6g_S>t6vusl3HT&5kwnH{%jgjjw>^r+T?ADrAP1~Z<3IH zJsDU!C19?QPTBvhmN<}hGmiEpZS%D648`@%4YWwzO7rkG_~#*3k7VZt?%!MdqE8;hJZq0_ zyxj%c*3@XJGNY}gVg#F~il)R4#*>8E69laNSJ&yLu>izyjcIgU)Yitv+R$qPMQ)Si z>Tk7wRJ0!Fbys1-b7A8l>fM1G%|it1L7NmU+NMv=%5qAOyo?F?>J-H?<-~*9YkOmC zpR#Hvaj?eaV zNm++cBUJH`j_}C(+V5{m>aL77y+SHtOceAIaH2@4I z$+FTw?eIEVSHC5!qS96VlqpNAdMc!~4xG1~ClXz^zzwR;lCq{+TN(v^e6DwyT3ha` zH#F6gmhfH(4a%{8%lbK?um0G~2Xhyy{Nl&uQ#~uejo5$Ls zeyQtODgX&qfwJ(ko*bCoJ^#RUbc-j-G!c@6*ak4i($cE_c1#Kn@oc8|3wx~;cgc*v zf}Se5*E6)zZ0!g)X_ALExk`ga+9eWv;2PM2Z?g-Mf-X$Wu;UXBbje5!9ye8dH6yD( zLQfQpPRl7>roJIbY2!!0{jw1;8cZgk0}N*8HL*s1v8HbOQ*RV_ z-gW&}lnv6l-LR2@ZDvRAO%XKrh2+F6ztvNUu<`gacm+qVz$ONxyjK+L6qDG>k23RL zY@^W@Vk)Y7_G?7sfHZuqUKG&cs3GM!Pnm#cR&oh+F!7H^G(HeDNM1RU}R@(|V-kT9Y- zNndaHBjH*n*uIBONW6^lrBqHYVTstx-ebqka{o9Nz$0P9Tg|R>tOaRJ>#x5;A4T~8DEt3? zn!~?N1o+=4WcTSO0-D|Mce-K&}9Xq5K{B3J3?d`GbTV|4-x=&?*ny z{2Tef0J!>lcK^urZ|r`I;h(Yl2d;nP^H;9_V)t*;_t#ncZ%*dV|6F+gAjAKlD}U9T rzsd0bP-Xsu@czZ_|3CKkyV11mB5XyguMk+Hj%hy7yXMT7t`3TFymUS~|&Po#$MEe{BEL z$MX;12>i)Uk+8sFXR_BtxR%zgeP5niv`WejXlX^h^8VvwU>Z}XN-v8-o}AZE=p95t zCyLgMdzAm3G}H4OBJ{W4w*7HV|3GTWkA>%=-(5@@Ef&+G%1-M$>_7eecjs>2`0AJV zkNXN9J$lhS6^vX)Lpxt}3I~G23BV zwX~+p*Z=JXj>*Q~1A#dE+izR7e%rET4-k{DZMC$1{O*4z{}+(|CBy#~!v8A6|El8u zc7*@!hX30Q|9|d=Pq0tLT3QW+cA-1rrzwwAomn3kuB}*!U>OIEWomi05nwFoS?*3P zEl2+YH@8n?s>v{T3);tD)_IQmpH(8Rt~k)FymSqv(2Q9Go1l@#sbLtBx5KT>sjPrT z&{1=}{_T71w(NPOIt6@U`HZI;Sz@JbhSF-PbUBLVW&!k>+1T@3>L?_%s3DA$aMUZm zN1={q4scHN2+y-&gS2YdR%|}!O86sNu+GM-@4bK9nyg{eB-nbPqw@? ziu08G<6~-0lr4){JCl&|iLJ67<2Sc!-T3OOgIZeOesfhz>(`&3Y|(mhzL;}IsJItkN+l}7h8`)W(MZa`3Exx^As6lqr#9ANNZSCfk`P5;H*2`UA z9Xt=bJu5qk(T04w&eZYeFxgaEw zx;_zsuW#;lo`}F^CU){`mZmyvTo6m|mC6hrkC$gm4z~gxsbRHTaqYxnHH_uTJCRGZ z(yUs|--Bd)`Xu}5gI4m!_aBb8xE6`ypy`hKmJf|1knGfsPk)!2tmu9bI7Y?6cbe$V`g#t>qf%dD3nH~e0BGtu$7sz12f8YHm+ z;lQQ#77wAN?OU`ww{M}T#vKubxwdpV{r`qw-d)y_A#KIeCMfFl1a_w#c;uL0&jkub zg^2)pAG2}@SEK{K+iB+ZzSV4pu0vcEn@dh(d6aLNe*eppjtE^(@v0N8cjkCu1SZ{G zy^=e>55Mt;C3%gp#C&0%8Ws~B9ewR~`iooX9V=Zt0~f^SI!I<5W-b_GGG7KC`t-Cl z4Txaw4IU;^epQlo^bO5{Tb7Gis{{h#p4Xf)XFl1TiAIsb#~Uu<_UPFk!?5GFORfWn zrH~4J!Y0yHixK!Q*Iry+ZYS8S<>|PE_Py{`2TdZ8$d_V3rP`Sbr{trx?)iF2UDvKv z$-DpJL1Rznmb)N%Ya(A2>z2@J}goSYmS9IT2hQ5cu>y^}OLz?lEwRg-v{dHBlTz7U9B>h>E7 zUEmrb(;bPh(&kKIeEPf#w>UTB^WhF)?aEwqpT(A_EH7P;IJoz&lX_!Ta!aS{XCPPa z2b+Z2DH?6aN=@(%CHHWecUOBDvt9a1sGl^uDy-kTA^AQ|j<8b8w>)FB-*M!T?hJh2 zUEEfD2(rF~I}`rH2mSHAyXUPkW_G13SlkK-W)=4(^3gFdIurigR-UEeGpzRFii)AA z!mxq~Aid@55t?;Z!MWW2WXCD-=XbaCe0BE2bj}22&fn-%Sotw)(6k1E{`m7Td*t(+ zF}I{oVX&mV5*S--nQO$74zV(uiJ3~r+;+JaQ*;bHsM9MHwOifQNH&tM5h)I5q^$2Z zx^5|oZt$L^Ob6yzVOH+TB_NF?Lpf_i7qLk;K(Wfvb1-BK^GJ+#*q8i>Z|X1wtp|Ye zHOofoQxzKJyckJcM!&o%x>Fx6{m((2XCFJy%jvO za;`%uDAAVLahVGd-?8ke3*Eu%43)lpEHV*{;1nQa<85f78JvadsW9%X3di|2LS94% zw1}PtEFfQjS*;tSS{Q-jCIyIk?ci36a$V1{V&F`^GfZL*+pgt0QA*LG4~ z^r5=iPGQC5KMih2$2PCz?dhw%M2yt*$&*nJWGs#q?j048IyFlm|MDg9vA&lMG6NMU&6)FHlc`s7DND{ zn7>tuc0ncVZJqx~|M#y-diz&{?D9T;x^Fea;z-*wmaZ5L0MTqk2$=~VpUphmHNTY@ zi9pzskm4ad?#8=>$rh7l)L4CY>K|4Q-{;)=7tDkaYsvDKHiw8j#K2sv_+uG@E~LKZ zKwnzLdRW~GYTng=I$C_GG%gO-vhq)0UqywW=x&v>`cR#ptJ&DQN~v}ubnQ>H5mT&? z=GB1FES_yC<&kvrK7*rNZ*xiVlCn#7OHFA{cD+X&XM)I82VwEecweLPIO&_uA6xzL zn=b=v-42b#YJ*=S&Ti;g=J*=*>!?Yn3?guoB!wfP*mF!hD?gH|Gm)f&u!JFPycouH zg@&`8cwd-TG{h@)lwaqE2^5S}{3AG->zlf^vcvV=T@*<>t-{V5-Io)axaq+R>CT(F z7R79wsAd!GbS)oL&{O?o0qYaSdVYSXYq50?Nk5NKwS7_<9d+Xmn|F5}J81Ou5IIBh zaPQBoa@GhZ-VMpUgaE{joT4?Zrf9U{Kyx z&s)s5tVEV+M^uqiueYj%|7W}^w$3j-K>)8P-JFW4apv8Q;=1h6yiJyeNb4AhWD8og z<|Aw#9N{bFl(i{3e{J$KA|K`F-b$xLZmfii?xHHev9hqjDK&pLWkm{&3uswc8jS(D ztZ(DuHXHg7#OE2GW!-ID#6+xY$qzl*XQSLiGs0zjs4SIw8A)s;*HsCSV)r$HaeTqs znN^{oah3)3VF)8p^(WJugR7xfUX(ovjB!1@eu`qYo=*3Afq?Cu-124%y)%g;>uJ0RSuff&X%NoOrhW84_`sQ8wmg{p#$)$H&6mmL(5ZyR|muP$p-WKj^cIzX7s8jI$%{NgY$#G!%GK@$TkKM(-Yn&%z`unxo*zq6Z2r zE|L(iO%+`d%ptoptDYMT(A7$MZE%p!35U%j7ortyo>R_)V;@PS_i=+J zs^TgK@>;!px<~i*S+=F_P18ew}d-E^HBYExM61%&b_PlMXMUj^o#I{Zg4g zWYjPCZpumO+FGU_Zs4m*c842Hhf_RL$Ss+|cr!%&c9O#j$6v@lQtvyQea7xh{2KYp zs&2f-Sv_25r0T6RQZNGb?1iAOFv$)me#fXO%`U=QA}3aLD8)E-DXvr3jiahQ;($c{ zw~&S|1m)g^Q!gLlC!I+Zj4;p)NDXGAl~iXqAeBvrQObBe6x^WxSSFhsGzMe{;ffKy zZX)w|ak&8uf{fVDSuJTR)I?dg)-56Wawn9bC@X9XDi?)uV1~>36K7v-9WHGeTE36X z=P@YW?O_$RXj~n?sv_L^*NzeTsM}PZN~&n#8i1`XVm*KmI;`)*IIipJJV=ynQCn(h zU-+uzRJ3xmo^bRFpmpJ_JXDTFsDi!#uEC+9=(sq+Uwh`~It7e_@ zPYuO1)y7MJDOt3lFK1H9q|9A7O#WI7Zgetf^jehcvgYyNOlZq9t74}k>%9@h9HPqC zfzZah)^Uu<40EwozA>tmzcw-@HS@ziqvJcpM5XX^zF1i8)GqwFn2Up&j%9V*kYm(; z3M6<&c^mS$_JO?WtyOnUW=Et!vhm7t=s+RqLr6wODO$%)h8n4_W*IDjx5KVaRgRzq z^lI5lv0l!&;hV?Ixm=k=sf}`;3x@L}%ytpY0^&QfdJrbLveiCB(Xk~zW^v$rM#Lsv zF&1csp9H7ZDP;5;;oaCI%86cxGP0yueY$176BpXT(+u5~dS5M~Pk9+qPublRD-{{fi(bY@zRRHfEwz08s5|{o6_>j=5k^AzBz`hB=zb2i5tEsD`okbq2yMS;u8kCawDN=CWccmW2UA+=N)i- zJB7wI-$O-K@Yuq3ZvtO*n;jioVxd4yWhTCS<53 zc)D0E67wW@O$LE*rg>t3Lq@j(t)J?bG^Lo5Pay0MepOUb;+*hH=Nap(c{yZLkYjeW1S%D+X%gwhsXEp&%QU+Y?9{aVF}=ak<8DKb!Wm6-3gVzT4d z9^$v$dcuh4V?_^_d_p+EE%TM_U7aN_Jr9GYSYtD@#p-{d3QZx0sQK8^F)G^%CLV>L z2Y9-6{epZeHb-ltWg@nBUeD!6?JyepTo)~=@@$lPTFkb_rpOR zV&-0f@^!E1{T08D`I+!k!DJ^LSeBvnq%zU*^*2{V@Y&(dW*|8=ojFobKv51*d`E2W z`;fucCl^zRTBcX^ke86eDyd>wQ#8#;N#S8)8)yXu(u)2vbB zAgf+_mwB(Yl4Z;y4NFg5f2Oh!T`7f17ut{+rn|7})isL7qK=Mv@D~q5R~*y7{$=A} zzKF-AR1V12PIL>VZyL=uTXlcpp8QbHT)MC@C$AOJ=x(3dckiE1eAuvg&)wXv^XzNp z6-M|bZ?~ngv6{}Uw=#M=+HL_j^6hh{nz6iYC1NsQOivczPufkxYcionXYuSVtluH zvu`9(KA7pUhc9YxE;++wvz@jDf$p;5%~wMNyFn}MyEQYZ-ehxrW7r*)i~FJEjGHAK z20nOoudmHQf$kLg^mcJtYDqnX@m4Y;wb82g`VId6!6_sL;|{dS-g#>%GFtg`anll% zcPxBWarR)Jq}6Y#P_yUlr^}yjBX1_etBr;)TqDlJH{yS_WO7 z06hls)puM!H7(~KHs9UBdz0!5>djSN&o;ucu7>yopI_MfV7q1+KN7CQ7UsM(YML2G z$dV8N%;xSRSro;X$l2mFoi=>3_X%w}342HgHu8=>kA|jn{Sgs6^@2a{g&&$x9GDt0 zeeg-%DyA-HfhNG)2sOcoTbF~7i}zzbHhyS%W?szYWFA`*xEBtWOyPzi61THSF6idL z>9A%nTEaui6k8vj1<*nwKo9_Zc>VPd6~@7wfA+!e!ThFk&#wGiIA77_Kv{WH+IkK3 z=uYGqn0-MgZgFI}TOUH>lfl52H>2{AyyhQD#6v z!M-ks2o8+9d{|>9j7k#o@|*i-!z}c^hvgx0=}v#iwNV8y&SI&RaNq z@-(|Ghx7VsU?$@m169R=C2y(7muW|>k@%NOUkA>2wJ02g+otJNuu++7Qpq z=f{Zp^$gQP#@Fr^M+noJ-YuTWwJbYPK?%*ZoOg3K4W~YZxcJtLn-MYgw0X@@a*}2x zrS~5)NlgTnBBaZNW`g}_@s@bB9vtYyDv4e@v8mh;pTBa@*X1C4;2}`=vwh1qwCf&% z<|=}JIB<7p4o_RgW9^EI1vQ?~YL>hE@&1kJPalpS^G8ov0&j&hlk)Y7qI$=b_E|!j) zGcqKVvE6FXXQ@@Hjv*nYpMtgVing!*f=V#ULNEov@1V!gfObY*pKJqQf@wwmxo;jI_87}+dM{0(L@t;l25iv zfu(=Te}5(7Fy#QMFW9`-+_f0wsSE36~Y z^$c@q(ZdW)hQ`@@W22Iih`pA9hzz?dZDp;m>Vwf-h=#r5_Ft#~Md(B-k0*_a{K$JAQ;~;7GlN$*0ua%Oj^%9^*=dP9+{t{wU||(z z!zTd0`4@Ed9(%5vWjaYbEEERU%6WYNauiaV%suMA^F0s@8woohmijQl<#mC%X$X<+ z-+^Q7(Hw0}bc=X1-)i#wdZ*Wv3%&t#c+83Jo7q3>Z=je?Ecq?WY?p!|dg&x=EUacL zSK5dfCt?xCBa7mhybrP`y2dtoBjM7AyFr@&)+#nWC9T}z3h&NPx{h?nXDip!f)76K zg(e0AV4cuffftOL)jF$}04_^tDRV1=kQSwF#E8ezO-L~Y!zr?Vw9M3&c4PQ3>4 z(^My&Q9ZdkC4YTdVZr)nshErnXbi!tkzMYzbnfFBG}d02W8^*FV3iRQYx`3JGk?Yt zW3gTg@qH25D+sOHxp&_eCME9l+Br{y)Uy!dhDGO~peSII0tn=R*xK@fi7uX>*=;dt(MD&y(I<7#yUu8Jv<;#j2Fi?h z1S7G+@n7IPQ-aKBQrp7JU)K*;kTu+#f~N<=l{edhyB&%z#`=y?Uk^cPS^l6ovk~-^ z3|Xk7IiIAm+|3A;ckBkKYTAO;Fd3_%BE07>X#QHZE@qV&5U~v0jR`L8DY{(xuAY%G z17_z>pSO;x7MS#sy`uv_o7@`AYHqdr`XTP-(myp|xXOVMy2i7O$n;@|1eQhCe6QpY zW1IMq3^|11DyXhAQa*Dra&3e8uLGncZYGA?*7o)6nRaw!aEn^GMp?wxU_RQkq*V8i zE)}Br-xiOpnAK{?loH-8o#zvu@GmAONU z;j+%I^~NMxefT<|;Ue*n&<5igKv$3hd&7y&y?+OJlOBzh0<{xgBdZeo5F%y2jNALb zBV~#MHO`_cU-BpIfN~iDijjRzDs6``@P6ptd4`BhT!KDn*PFX^phiAuWIPsneyzT$ z2`3K&=sCa5=5#F34o?DvX4%`IllzItwG(Pdpw+q@LLJTRasqlHN|k%3%S^&2A*^$F zkmX*;lZX#wt~Sl4Z4`t2qzQf~o8p(o_2JeZ^BdgERfQDbsgaFpJ6qLx!l%U7Nq4== zM{DtT^?(Mw30iJ~s!ov(lyCg~Hw-y_0t+vJXl53=4K|WGRUblEyFZW9O#Kja%gmY+so!9`(NZ_dFER0Wknx z%sj+3zCX}Y9sZ-t-15!T7+Ji9v(5F^4M5vz$H%hw@HMYA;8Z4@bWX^azrZpL2#D?8 zFpDKleY*X-DEQ$h*KsQMfmJX0m&#_4Xra(7FbNqx<0KMV)u z57MhKtMpnRAw4CB7D+oF5O)1VK+g=MfK)oFOD!w^;yj~Rrl}y|Q2aNZuCd|6x+xFQO+lY;UVKcEA zd_4Wtz7P7>0|El>0|0b1?Z({mMHx?qPm9NFER{Q9XMc}ggo>XGN#)L@{e&fhz~ZF5 zldR0$o<>>r7q8+vYWhWYpg@5HR7+${Z7uxEjT>vmq+&EOEGQ{+F+{RCL2<9GK*g2R zcmpJB+e1|BWGyEkqHwTmwER1J{(%ig+lQzND9Gl_@Vscwi?MM>{1N95lD?q6{m`y-qUs^|^vo(JC3tjJ!qR-xbq z;4$j{Uy@S(eHe$Z2bO4apf+j-T3L0Mu`u1xu^JGYv^n_sup+g5Q0S*BnyAHW5Us+$ z%6R*Z{v1d56&EKhVa^q}&$I0LG8<*T3j@%EH7Aeg_YB_TJql@!rV+NSG1(6D7i$w~ ztwR;`q0Cf!aan)9OZ4@(L;TRzRy<3(-kSOE(A1Nm%We9J`H8ntgo)^|q(gm;cv=Tt zIa4P4aCdGBPNx>fRCJ_OzcG$2iM|%sr$+u4f@pLX-U_UBw-b(u`9qiSpWpsR$h#vS zo$vY?*nf{%>I8S5Lg~1IXth`~-NfSQ{)Ecm=QHDNEQeh8v7ATkK+==scv8?0ra5(p zqChxYy8`)Gl^3X?w>9b#W0dA){J<|y`Dw)7i$ zS)Ux<#Q*OJ=EFXlu2pu}F)-sI3^&?ZHg$Vj4_!b2f)O?SV1I7V zOeliQx{Skk`FJ3s3kM4-%0d1UR8D!Fkz^L=O*RE@1M>jd7+PZP{wP7dwD7s}wPFH5 z=BC1R{26qgMcqTEYb#nculETDoG*UN7mSL%GJDk8H#TnD4i>!5X#G^(L$^BRWkm(0=TqCW&6Q@_ za%XOh()+mcuPP>*bi@76`TE`;=zs3*_#%kY>CHnqv?OrY9#Zvf7L@yBsQZqyVt;3y z-iXT7tRd`utRC;W^c?!S`ULtqXBd5bvnJCiN|j+R)nOQ-{Ys9;X75sfM-DWewlTWB#dDYh#=10OuHC*1OXq5+{mBjZlZ;8Hwso3jtxbi4)=f3_`3+C`;`u z{wPFEh0;c^-nkv*coUHljDjfcK{s!UK977la%>ifE(-vRr;r))5&{%}j|C7^kLMRi=X~v80)Qpn|8O zU%uZ*duw1iqUYZTLTFePq{kvq>ChQj^vJj2&$27GmUZG0bkXO6W@GZsq*em%4!Mr5 z5(U;)w054b!~BbVvn%h4Va0A!*PGW<&f%gZ^PX5k1IJM0@YOr{I}LLQe*2|W&NJ75 zM3!P?$kcWcST*fgg<5XK$=z^lL}^tMz;jI|_RFr4Sf!ReQqGB)K-}<_$fQhxCpm$y zXdn##8cR8&Dhf6euJvq~?l_HBy=AZEc#m&y=zK9;AAm0qyxCzzUVN6ZUNE-d#RC6y ziQ>MVh0ou;zkjciIjpY;n*fp(?j!sDA)v6H5`D1&IZ))ZpE&(#dq!69ERrfXsK6rh z#KXlE&%8KVXMWP?V73U_<{fVAak#G7snezIYBf5VRzt1Ldmv-B2(OA?x(kQQN#~gN zJRX-+UBYeN5*xCo|8}^h$(*pS8&C4hlu9~vHt+4d%((Lz79lK{ZB)b}#eRvDgl#+a z23hOZpxz^EFWGedu^Ja1_@`}t4XZhzIN?Q>DIZ+RzMjrBAX@otyPBx*U@YmXI&4=7 z&}UX-CjjYr^Gbm`k@;>1WZurPxwm_geHGNil>9^0%Q#jvzJkHVig8NVC(1#kf5f3X$;f^sOp*nJ&k*X(zp z!QsU%D?_%p6I3$dB9AJl=L8b+2X`4#Ol9XcuXJnfqtUGV%6cC7yAMHZi#sp)Wq?eZ zxYm&+xiAp}a-+@1*j)5N#D@`C4z+Bu+t@oWpzDr^x?i9Z%+l-0y2b&XweAXCAj844 zH(Y0T>NdGp6jSO-Jpxzt6KpP}&q>=tntW0NW$@z0QqKT{X-7m5@@M$!tViK(kXK$j18n0sreq8=#L|zqgSxiyTc8&l3Rq!&nDy!gjAE@(0|kKVY$~~TkJI{S zfcARW073bCM2^n$m1i4&C&g4hzy2!oBO(m>`vtq1GFne4VV_}n+{iE=P<*7j78btl z#~BqgxSS{^#irbOo%cwemc>cUY*`$K&epWKBzp0Zd^Y!eof2e{C5O&l1nd#ACfTAO z0YRvQfKx}EVv{=Ui;ud!*L5@8g~kaTkodSf1S<%a1WsR z*+)}p8H7ExCcWOge~I3mFJ7|3h`H?wspuDreJW-{-ptQq!!b3?XGeAjU;PWqt8*T^ zHt&b|bQS23GnnNL2p0Kx;Swn|N~adcaDa0d78&%;IJH!7W`Q4lO$x4eG(_*sd!v9dUVG? z1|AY*I_-G)Z=OkaArKQiLs$9fAc7^THHLMSL~LAAw}T0lLvDYu$V&)^ z^0r&JHV=4^?4ie#c^Tq+w{ySc*66M|80$TBab#s6xw3CQh}gqnfiZ7o#6r=WvU{^A zu-sO`;9zK1cYJg8VQ5Wqve$!oM=oH_yg%af_1fcq!#) z=>;!8^gzCSh2TCEnaW^BS09%|86MJXKro zgZ)$Pqq5K4%;_ouLqxvP<~X!9iqrvep27^QYJG8awZpzxIRSDOFWbotVoZnlp` zbD0T;6Gx}3;K3g8YE!=2g>Ta3xfmN08N!r@`QQ(G`-5qgAvdp>(C-P}%bx!eXSsJX~uS=Fi=Xx_yEjWIjF}Nk(b;s)E zsh(jk*%L5={W7h1@m^xBsJ4VXs=6ku`RUkPfv9oOV8ZCZo^WgTndl_X$Mk~YiqOU0Uw)h`Ohd@@J;72#8?e%f8NaRd3JX< zIj`OTeDkPoOxa_=J{koWXJbnMSnOf47cvk&il4!eDtQFkzkT zpB02XdJ&7dK4NA@qKsX26Ix7|&b!Nhj_``Qc6hJU;lfX2OfY*Jnn^C+*fRs5HS?E$ z@-s^I_P6dGqr?!Jah?EcNpyik7}++Vj^L zoXz#b+Z#`ALy>5JGc|KbS~`y{`rLoB_YRbug}&2PAjLVZMNrbZ-%BF%)ooUKp;|(+7iVs zjSA*So<%+@WvcO z5CKOI-%S}CVdHrOh*_2ab+m?B@U5O@6*J|M!)jR-GmY^pBGEjzyp28gMmF^iFWZOV zGSfEfjj4CM?#Rrgtw(qx!rgkJx;#rFTX-9ReP!(%b%LVSJl~g1DJ)aKyQJ4$gh+HB z8(ce?HrR(uvAq+;ZP!;Ha`Jrv_zZJutJu=9M2m%0J#Xfn(HJ-PVv#|Y>f5P%xNn?3 zcMDnF7K`JJ`{q%ViVTtf-(R}>ED7ut19xR*HsSu zzYhg5+Ug!=UbPNFK5t!l8t93)o2l->iN3|6N5T;^3n2~CwVYwgjZKhoFT2ta5B;3! zK2~EBV32v;BR;3vc}7`9Ol+xl1_-_o+!W>-bCKe#{wzpTY$&SE0J{d0MX@gFgdc#+ zT`j394D5417HjHu{31>|Zk4(N?2TNu2`4I&H8&2I0rmi0$ZXo1!-kLHbT#W&y-&7CIN15`Izg0%-={WnL7V5lm#0k2@5qe3+%6QeT>1H|h^mogX?u z)>lLNDl$_r+qqnP_1QD;n&^>Gc~2WL!H^alUj685X%%6FgUT#1=iq(m2w7`jkV$VO zA;53~+^l;iEOj-VnvEmHTHy3;$5hbBkdpgcvNH>fRQ&9Xo;yt~80d`Jk$c00LBJU~ zP4@VC)V~=_@|KLr5O{)TeW?CUqvYF?W4uQf-E(H0=;?lW(767w{=M;)X$=spKO@lm z8)|B7lJbx22y!wX5uA|nALnOX3{N{a`Oovj(eP-9Z|1vw9{9z?TIbm#-q_yT!MS?i z*^im9Y`j8JO>g{LgviFrN1ziy-bt^kx3f&^CTg5#>lAFn3;sty!}*s~B{SAW0TdbF zoS98JubDoiYvFXktQVR#Y-A!7=^t4%pQr_y7Q5TWnx(~D>58C8huVnY+&ddRhHO}N z zw~tbYm=OIGS{7AYL;8=k{2#>^P216#&cwa>BgiZ(v(68dLD)wZxI1*VM65sYG$|cg zwEWtJ`s*c@>4**BBQT4#k6>=2pV2lfi3FJQQ287nT;QP5jNy4_-60b_KS(BTUxB*= z^X~dZZbcipswDZRJdSURf&i%KI{;{FAzXN_*}&J31!Q*hCv92mSC#XSRK_X)@{OmW zPY(NXM9toaFhGOTJXwx6v+aK7mpa`?Hb`#@p8vZh`7ft?k50wue~76_kFHp6h>5xy zTiF{bOB<_oa~<=BoI$e{IDXFD;T9O+NCu+^%xsjkn#bz<8lSxAw(d3K!)3iyqUsLy z>TZxUo;temde6QtuV5JQySH*HLG}R;VtKpw3(To6_D|k_R>lCQ{8DaT7n9eaXVbMD z9H3gp1Icw`wBZ6Qy4Q@%IL};p2e)9R zGXQrd1Q&N}-?ycv#o>j#Pav@5(fk*SUx8HkhABcUhG!RMIQC-IxzrTRd)?`TyWW&S zLD_sQTM41AeH~0oD21Y6B|d6Lnw;*VdKT048bcm&@b<4}x0=)XIR79peT>t0N`~Pu zCD+zxNAhf}9qssiy>?)t=)`=rrpv)tbvR1y_R?kx?T|zF)&5=K%eNXLDDK{acicmR z_~E~z$)noX*zAe$kK5*rdl+rhlGIptjY8KD8IFQr#}*o=2|t?_ennunoAk<*zAZAr zPo7Zwio(rEs{@&d8ihaTlL?R;p5Cnv?CY32dfdIZ;`P{O)!p4(S;PpS+Ai{%3OrIq zQX(9@07*Z;%>nb06!)hM_2kttb~EaVz_WYl28DpL1z^A1STP-}cmeVQxTlYl%6+b_ zE`YvD@-1Jk2sA5mPG5Qf>pWZ@o~@&~k}b{*%PaS`;|J~2z9W)_if`%E$k)WCy%y{{ zL4Q{SRH~Swq3NxZkHU#r$VK*FiP_$42;hK=UVa?mXO{}0j|iGH7XbN(UCKc!qjl&w zL&w*VuWSlWCF&1+nU#R|AE{lEIqh}uxfTNYL<0Kx;@wiejbA<@p1bDn2CZf92C3&O ziMQ&b;HE5+yc6*wi(UO6iQn_^dzn9%4jKx7REygEj>J3Wk)dnC5#frrvVQ_}=UmL=TdtPO zkg+~k<;P%eW_Y#{8lH`n2&yI-IU=JID`ju{x}4`4M&~nvTb?1Wb)H?plwT@MAUS3s zHBi8YSs-0Jlk=&AecdP9NI?r+95;>6^^VTn+;3FcnQK>>TMVX_ROSJ*Z+#$FvYG0A z+75kzr;}mqMz1{0@<^I=mnWvWl(WO-YH|&ry7Y2gCyI8LI+zV)=dW3mv{YtDQ^=)zQ7h0k zdqvLccHx4gvaelG%|@a3b5@dUlpi2w>)nT~Dzwjn_@1O)>%OS)lQzE5_BCYD>)jmn zl~`ip5s>4>CfI(@=PVc!x;!*t7D5>n&d!?nCg1Io3yIZHPgDcj9F7x!Jz6&&^Xc3I^oTT3 zoxESY1W4qY22|y1hRb(jt4>Rm_p<_1WimJ%INjX3+-K#b7;{J+@n(Ro&!*dc8-`zB z7}e?eGm2gpUDPmHgph!sGZt15__|5uKJ|4mhkd|P;+VK7`aF8afnqCMH!4$p9whi} zN9R=}zbVkID)tD>W9G;0MsofxFmodd1}#Ob_eMjs78E<33ET)zkyZ zlIfow!RW=zJ}ZFStfVT}%@3&b!Jfqcm}N0Sl~};k0Hbc&_`{stCeyAHIDyuHj{x90 zSj}L-S#L`MMqW~yWS8oJA-Gk~$Tf8N0r2y!X~CRPau|qz(#K{= zq~X-tDDN6UBzHjGs^!93X9+NN;bbw!+{3<#&I86nJ2K%+a}8hy5__Q~wKxn5tbHLT z_S?Jn03&EtK9j<3gz@#w-6M)p`I{__t>F@d-xTx*I1QUan;2a5mABYo5%y)Ut~h(IVy@k z`UClpi9OT%hE)N@JOXNWAD~LTzdEW4>B1d%Pum!ZI0F}gkY)*f2T`*^QKL5s#td<< zn8`7ML_dU#R}{tddi&h$ntdmF63eBrO`2oI5lFTA2m0*%=+4?ydYw#FUqw8I@UiQK z#$_@RA(H2Se?On%rCK)fRXvKSb>?>kwzeWMWinbV8*v5b21=x>yGBCS|Ao?dP!yOE z0g}TTMJ8xs4eAZWPwZ{A*zV~H2YrcIPWUX^YC}6VsoPOVGxCdmc&4Q1+r(B~yHi$t z8LlA=MI;)i=#EcY88;5*JQS8f#FH9e)}ta#?9t~@`J7za{flrt!VA74ZeE^_Yzv$1 z=p);CCm~iWE$v3*V90_yG)R?fZ2}sb{{a0_g!p)e)2a#i;#RJ5Fr=2PO-KI74OqnS zjcG&B34>T%Fi^?ZHBi@qz741Ke~f(5?3j^IB_&u|cF^b_nxBb_ z6w}=$@n(ny_z)-25$cpQA5_a+c?(9dt#8?QnzVZ67L>VkD&W{Kzn3I*XtF)$cS}j+@=*8F4$3SR?)#-N!WhAiGYhP+MEJp_7sZ}iIQ5bpR1OIKlDkq&Ly8{LbnIGfmF91vai42s%H{Th z3dCd&wY;D-q&oIQDyW$Ic(ClTdH`_ab@k<1I#@Zp#I8Kpe!lN2ZE{lycyIbCe)euH z_g|bc2pR|?SA}^=Mc%5H5${K38gXE2yy^Y#QcxZhVa{~DG7@*K&QaS!cNwVx5K1Go04-&-WknV3Ylx+8#@5YwYtCQ$8^UF?be=}yP99t4`Tsus#Pp+Z*vx71>R8XBNh3j zs^hKKE5&}9@3vLG=zVJ=gLp0{=}bQhlofDpsb`2)7+#X8uu}wMul)42*rrlPhWK}v zd=_7zu2=cpx#mfvjF+FI{o^n!qE2iv*)pLWhQD(O6p<8^GiOr@S_X02&lxA9V0D&7lJrs<-X zWJ(UYX6mLEDq>#nLg-GeS`dREc=gXFM< zjW0jeCZzGU|Did%}9JmfuHSWFz|22Je(`r2dSD_N`rzW`onR07u!?Hny=EhtoTZvq-D$5tLIf4;eKiHKJPnzHDp zNb$cmY-bPKr1)o=a<0 ztLPobZG5=cOdy=gyF%wAPs9b_48kX#807L{fL5votqO8=gzDb5Em%S2YPT4@I@)h- z#1VV`Z3E_)$O!1&?Dt)SP`Ez^`4KsAa{eIr??mHT@(bH?`rqyEaN1*t>R}pc)IK`H z$X8D74fKLXWpxFT6bx0W=qf%nDqN+K)>;TsfT^~}c9GRw-`C@bxIbR>n@ ziT2?C>-pxMEh)jLo-*K3TkC4<#`ZfqxqnSG)K$M(WcWa1f8G|-WeK%i4{52CpN_pE zw+A;2ZK0qq;Ydz?7*BYuMNM;zgVb%8(k>m;xLP`lJ~jy_N*B|48wH)gdN92IxNt&A z#^#q5pul8}wV-)oe^ys4-gTY=Qgw%RBs3@!dW_>OtG%!>*HgLo5kK8w)*<~r<0lS8 zTl%f?OXmz^^>)D&P1@)d^yR!ms&*PpALfQ8Kj{k;5A2oEiumHvoi=nvIC5DUj$GJ= zYhKC#n{DLklmM{&8`aiI{^Hi{sWnXb%7=Vu*e2gTaj_qxt`_a%w{J)o8ccrKj-i?E zf~(T51x;nsxXhBuDmukD(9*hNHsRIwrU{}=>^?BchOWxfbh(`ywVxHxD~K9vWrAJjscJPN@$tP#$0Bg|>gPPP!K!v{vMuae%}CYw z6fL;AxW4>nib1>y^CC5fyfV-e#`y;km1B}dH={CTj*aGRWssBdFAT794gQB^du)Gu$qZ{jn|=RXwDQ>$a4XNu@?oYBQcf zJ90aO@6Q2n#1{lMgglJ_)c4=hVw96}KUG_>Ng5u@muwGhjy*=|PsFhc>_;+FM#&sX zx2;TcRAL*{wBH#_354`^j$rXhU|j$tZoCXni?WhyPfO()=+PMVCHnjqV~Y9JfrV%l zMx-f7x~5R0v30g?uYG&BJL0NAjjT#Gx<}1BE?JJT5$yVI)h!m5>;Y}2g^&ba3YUk~ zle~Fd%dB|enpiLq;ZLnEOUmLiRwSa~d&&klw7XH1m!w+fpD`jDj-UIw=-{DmEp+z~ zC0P_u)u7?+68XLT(C`afj!gdI99Sub(y_kE4z?>0o`e@-Fe9u zltwM{w;!T!S>z`x%G+cpi32poOHed}sl({_cRK6EGnARljY+R}4y@ya!F#?L&#nDn zA&Y^e-Nj(cj?M(JC2*Wah!LYTuN@4;`Wop_({Ya*vx~6=r>bhSH`g_)-DhaDf^z9h zJzT#4VpgN-YRBN=X(ms)r9ioLOt8jF=Z0_p1!u+7KD;k6gf~gDissi3jJJlW)x4%> zovu{6yI4ro{hN5+x;!HPVrA2=j4p!i7mGWj4hX-9SelooDw3a@xJ*N*gmzo~qty#h zI2E)!L;yX+1>g}u;q?pyj)ZB{dkO~!WqV!H*BDcK$spP#C6Bv;bqW%EDPx(*giB%v zEAg2mw@%WsrjoWV&w<9uX74=iibPSqG1%U5Fw~qiH1T1jqLwMm=Xsmn90Lwxe%50H z{l@YF*jn8VX8NiJ7C)3HM0&pj=9#~bFVOGdHDvzgr}b_!$I~3oPvrw<&Lwj$nRCfp zOh387pjlYV!eSN{v#^+ji3Le^l9@R1HaAa0Od{!d-bVee0 zt@41m?{|TDyLfn>aQB3Jd4XpS3&v|g4y-~QK6a@2 IySNMg0ng}{0RR91 diff --git a/resources/android/splash/drawable-port-xxhdpi-screen.png b/resources/android/splash/drawable-port-xxhdpi-screen.png index 00c8a805534a8a84ba0a926c0a9cfe1ebf8fffae..58f7d76b17f2efa307eb87070dee29c51f1c9101 100644 GIT binary patch literal 25073 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 AVE_OC literal 32738 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 SvIS5AK2xLfh6Sgu{`6n>ZQ?}$ literal 42758 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