diff --git a/.travis.yml b/.travis.yml index 4e41b34b3..60d343646 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ -sudo: required +os: linux dist: bionic group: edge @@ -23,4 +23,3 @@ script: after_success: - scripts/ci.sh - \ No newline at end of file diff --git a/GoogleService-Info.plist b/GoogleService-Info.plist index b80564cf9..f6d1bd44c 100644 --- a/GoogleService-Info.plist +++ b/GoogleService-Info.plist @@ -2,39 +2,39 @@ - AD_UNIT_ID_FOR_BANNER_TEST - ca-app-pub-3940256099942544/2934735716 - AD_UNIT_ID_FOR_INTERSTITIAL_TEST - ca-app-pub-3940256099942544/4411468910 - CLIENT_ID - 694767596569-c2cjrca92k99f6nkp3363lsb7ljhdgdr.apps.googleusercontent.com - REVERSED_CLIENT_ID - com.googleusercontent.apps.694767596569-c2cjrca92k99f6nkp3363lsb7ljhdgdr - API_KEY - AIzaSyA-77ZjkxII6GV97CC9rdUl83rzdEXu_rM - GCM_SENDER_ID - 694767596569 - PLIST_VERSION - 1 - BUNDLE_ID - com.moodle.moodlemobile - PROJECT_ID - moodlemobile-push - STORAGE_BUCKET - moodlemobile-push.appspot.com - IS_ADS_ENABLED - - IS_ANALYTICS_ENABLED - - IS_APPINVITE_ENABLED - - IS_GCM_ENABLED - - IS_SIGNIN_ENABLED - - GOOGLE_APP_ID - 1:694767596569:ios:a4cdad4d168c9d1a - DATABASE_URL - https://moodlemobile-push.firebaseio.com + AD_UNIT_ID_FOR_BANNER_TEST + + AD_UNIT_ID_FOR_INTERSTITIAL_TEST + + CLIENT_ID + + REVERSED_CLIENT_ID + + API_KEY + + GCM_SENDER_ID + + PLIST_VERSION + 1 + BUNDLE_ID + com.moodle.moodlemobile + PROJECT_ID + moodlemobile-push + STORAGE_BUCKET + + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + + DATABASE_URL + \ No newline at end of file diff --git a/PACKAGE_PROBLEMS.md b/PACKAGE_PROBLEMS.md new file mode 100644 index 000000000..9b193aa7e --- /dev/null +++ b/PACKAGE_PROBLEMS.md @@ -0,0 +1,15 @@ +Package updates known problems +================= + +@ionic/app-scripts 3.2.3 shows error Cannot find type definition file for '@types'. on ngc build. + +com-darryncampbell-cordova-plugin-intent 2.0.0 onwards needs Android X Support. Unsupported on PGB. + +typescript is needed to be less than 2.7 for @angular/compiler-cli + +cordova-plugin-ionic-keyboard has problems on greater versions than 2.1.3 + +jszip has problems with "lie" dependency on greater versions than 3.1 + +promise.prototype.finally has problems on greater versions than 3.1 + diff --git a/config.xml b/config.xml index 30b0d5543..b56522924 100644 --- a/config.xml +++ b/config.xml @@ -1,5 +1,5 @@ - + Moodle Moodle official app Moodle Mobile team @@ -23,7 +23,7 @@ - + @@ -122,6 +122,9 @@ We need your location so you can attach it as part of your submissions. + + We need your location so you can attach it as part of your submissions. + @@ -181,9 +184,6 @@ - - - diff --git a/config/sass.config.js b/config/sass.config.js index b06b858f3..e13f6ff2e 100644 --- a/config/sass.config.js +++ b/config/sass.config.js @@ -5,5 +5,11 @@ module.exports = { 'node_modules/ionicons/dist/scss', 'node_modules/ionic-angular/fonts', 'node_modules/font-awesome/scss' + ], + includeFiles: [ + /\.(s(c|a)ss)$/i + ], + excludeFiles: [ + /\.(wp).(scss)$/i ] }; \ No newline at end of file diff --git a/config/uglifyjs.config.js b/config/uglifyjs.config.js new file mode 100644 index 000000000..38a263a29 --- /dev/null +++ b/config/uglifyjs.config.js @@ -0,0 +1,19 @@ +// Check https://github.com/mishoo/UglifyJS2/tree/harmony#minify-options-structure +module.exports = { + /** + * mangle: uglify 2's mangle option + */ + mangle: { + keep_classnames: true, + keep_fnames: true + }, + /** + * compress: uglify 2's compress option + */ + compress: { + toplevel: true, + pure_getters: true + }, + keep_classnames: true, + keep_fnames: true +} \ No newline at end of file diff --git a/desktop/assets/windows/AppXManifest.xml b/desktop/assets/windows/AppXManifest.xml index 4923f504d..f7726e61b 100644 --- a/desktop/assets/windows/AppXManifest.xml +++ b/desktop/assets/windows/AppXManifest.xml @@ -6,7 +6,7 @@ + Version="3.8.1.0" /> Moodle Desktop Moodle Pty Ltd. diff --git a/google-services.json b/google-services.json index ff50800df..c1c3ad8bf 100644 --- a/google-services.json +++ b/google-services.json @@ -1,30 +1,30 @@ { "project_info": { - "project_number": "694767596569", - "firebase_url": "https://moodlemobile-push.firebaseio.com", - "project_id": "moodlemobile-push", - "storage_bucket": "moodlemobile-push.appspot.com" + "project_number": "", + "firebase_url": "", + "project_id": "", + "storage_bucket": "" }, "client": [ { "client_info": { - "mobilesdk_app_id": "1:694767596569:android:a4cdad4d168c9d1a", + "mobilesdk_app_id": "1:111111111111:android:1111111111111111", "android_client_info": { "package_name": "com.moodle.moodlemobile" } }, "oauth_client": [ { - "client_id": "694767596569-icveqqa2n56oh44l6ev1dr2oh67nh8il.apps.googleusercontent.com", + "client_id": "", "client_type": 3 } ], "api_key": [ { - "current_key": "AIzaSyCb2zogu0P_aZ2LNgdwzshWExITPKTXJyk" + "current_key": "" }, { - "current_key": "AIzaSyDRT1HwT0gSsTty0whOVtoNKAh8SPrJXLE" + "current_key": "" } ], "services": { diff --git a/gulpfile.js b/gulpfile.js index 426ea8cc2..fbeead754 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -5,12 +5,12 @@ var gulp = require('gulp'), path = require('path'), slash = require('gulp-slash'), clipEmptyFiles = require('gulp-clip-empty-files'), - gutil = require('gulp-util'), + File = require('vinyl'), flatten = require('gulp-flatten'), npmPath = require('path'), concat = require('gulp-concat'), - bufferFrom = require('buffer-from') - File = gutil.File, + htmlmin = require('gulp-htmlmin'), + bufferFrom = require('buffer-from'), exec = require('child_process').exec, license = '' + '// (C) Copyright 2015 Moodle Pty Ltd.\n' + @@ -294,6 +294,12 @@ gulp.task('copy-component-templates', function(done) { gulp.src(templatesSrc, { allowEmpty: true }) .pipe(flatten()) + // Check options here: https://github.com/kangax/html-minifier + .pipe(htmlmin({ + collapseWhitespace: true, + removeComments: true, + caseSensitive: true + })) .pipe(gulp.dest(templatesDest)) .on('end', done); }); @@ -311,6 +317,11 @@ function getReplace(capture, baseDir, paths, parsedFiles) { var parse = path.parse(path.resolve(baseDir, capture + '.scss')); var file = parse.dir + '/' + parse.name; + if (file.slice(-3) === '.wp') { + console.log('Windows Phone not supported "' + capture); + // File was already parsed, leave the import commented. + return '// @import "' + capture + '";'; + } if (!fs.existsSync(file + '.scss')) { // File not found, might be a partial file. diff --git a/licenses.json b/licenses.json new file mode 100644 index 000000000..27529e772 --- /dev/null +++ b/licenses.json @@ -0,0 +1,4356 @@ +{ + "@angular/animations@5.2.10": { + "licenses": "MIT", + "repository": "https://github.com/angular/angular", + "publisher": "angular", + "licenseFile": "README.md" + }, + "@angular/common@5.2.10": { + "licenses": "MIT", + "repository": "https://github.com/angular/angular", + "publisher": "angular", + "licenseFile": "README.md" + }, + "@angular/compiler-cli@5.2.10": { + "licenses": "MIT", + "repository": "https://github.com/angular/angular", + "licenseFile": "README.md" + }, + "@angular/compiler@5.2.10": { + "licenses": "MIT", + "repository": "https://github.com/angular/angular", + "publisher": "angular", + "licenseFile": "README.md" + }, + "@angular/core@5.2.10": { + "licenses": "MIT", + "repository": "https://github.com/angular/angular", + "publisher": "angular", + "licenseFile": "README.md" + }, + "@angular/forms@5.2.10": { + "licenses": "MIT", + "repository": "https://github.com/angular/angular", + "publisher": "angular", + "licenseFile": "README.md" + }, + "@angular/http@5.2.10": { + "licenses": "MIT", + "repository": "https://github.com/angular/angular", + "publisher": "angular", + "licenseFile": "README.md" + }, + "@angular/platform-browser-dynamic@5.2.10": { + "licenses": "MIT", + "repository": "https://github.com/angular/angular", + "publisher": "angular", + "licenseFile": "README.md" + }, + "@angular/platform-browser@5.2.10": { + "licenses": "MIT", + "repository": "https://github.com/angular/angular", + "publisher": "angular", + "licenseFile": "README.md" + }, + "@ionic-native/badge@4.17.0": { + "licenses": "MIT", + "repository": "https://github.com/ionic-team/ionic-native", + "publisher": "ionic", + "licenseFile": "README.md" + }, + "@ionic-native/camera@4.17.0": { + "licenses": "MIT", + "repository": "https://github.com/ionic-team/ionic-native", + "publisher": "ionic", + "licenseFile": "README.md" + }, + "@ionic-native/clipboard@4.17.0": { + "licenses": "MIT", + "repository": "https://github.com/ionic-team/ionic-native", + "publisher": "ionic", + "licenseFile": "README.md" + }, + "@ionic-native/core@4.11.0": { + "licenses": "MIT", + "repository": "https://github.com/ionic-team/ionic-native", + "publisher": "ionic", + "licenseFile": "README.md" + }, + "@ionic-native/device@4.17.0": { + "licenses": "MIT", + "repository": "https://github.com/ionic-team/ionic-native", + "publisher": "ionic", + "licenseFile": "README.md" + }, + "@ionic-native/file-opener@4.17.0": { + "licenses": "MIT", + "repository": "https://github.com/ionic-team/ionic-native", + "publisher": "ionic", + "licenseFile": "README.md" + }, + "@ionic-native/file-transfer@4.17.0": { + "licenses": "MIT", + "repository": "https://github.com/ionic-team/ionic-native", + "publisher": "ionic", + "licenseFile": "README.md" + }, + "@ionic-native/file@4.17.0": { + "licenses": "MIT", + "repository": "https://github.com/ionic-team/ionic-native", + "publisher": "ionic", + "licenseFile": "README.md" + }, + "@ionic-native/geolocation@4.17.0": { + "licenses": "MIT", + "repository": "https://github.com/ionic-team/ionic-native", + "publisher": "ionic", + "licenseFile": "README.md" + }, + "@ionic-native/globalization@4.17.0": { + "licenses": "MIT", + "repository": "https://github.com/ionic-team/ionic-native", + "publisher": "ionic", + "licenseFile": "README.md" + }, + "@ionic-native/in-app-browser@4.17.0": { + "licenses": "MIT", + "repository": "https://github.com/ionic-team/ionic-native", + "publisher": "ionic", + "licenseFile": "README.md" + }, + "@ionic-native/keyboard@4.17.0": { + "licenses": "MIT", + "repository": "https://github.com/ionic-team/ionic-native", + "publisher": "ionic", + "licenseFile": "README.md" + }, + "@ionic-native/local-notifications@4.17.0": { + "licenses": "MIT", + "repository": "https://github.com/ionic-team/ionic-native", + "publisher": "ionic", + "licenseFile": "README.md" + }, + "@ionic-native/media-capture@4.17.0": { + "licenses": "MIT", + "repository": "https://github.com/ionic-team/ionic-native", + "publisher": "ionic", + "licenseFile": "README.md" + }, + "@ionic-native/network@4.17.0": { + "licenses": "MIT", + "repository": "https://github.com/ionic-team/ionic-native", + "publisher": "ionic", + "licenseFile": "README.md" + }, + "@ionic-native/push@4.17.0": { + "licenses": "MIT", + "repository": "https://github.com/ionic-team/ionic-native", + "publisher": "ionic", + "licenseFile": "README.md" + }, + "@ionic-native/screen-orientation@4.17.0": { + "licenses": "MIT", + "repository": "https://github.com/ionic-team/ionic-native", + "publisher": "ionic", + "licenseFile": "README.md" + }, + "@ionic-native/splash-screen@4.17.0": { + "licenses": "MIT", + "repository": "https://github.com/ionic-team/ionic-native", + "publisher": "ionic", + "licenseFile": "README.md" + }, + "@ionic-native/sqlite@4.17.0": { + "licenses": "MIT", + "repository": "https://github.com/ionic-team/ionic-native", + "publisher": "ionic", + "licenseFile": "README.md" + }, + "@ionic-native/status-bar@4.17.0": { + "licenses": "MIT", + "repository": "https://github.com/ionic-team/ionic-native", + "publisher": "ionic", + "licenseFile": "README.md" + }, + "@ionic-native/web-intent@4.17.0": { + "licenses": "MIT", + "repository": "https://github.com/ionic-team/ionic-native", + "publisher": "ionic", + "licenseFile": "README.md" + }, + "@ionic-native/zip@4.17.0": { + "licenses": "MIT", + "repository": "https://github.com/ionic-team/ionic-native", + "publisher": "ionic", + "licenseFile": "README.md" + }, + "@mrmlnc/readdir-enhanced@2.2.1": { + "licenses": "MIT", + "repository": "https://github.com/bigstickcarpet/readdir-enhanced", + "publisher": "James Messinger", + "url": "http://bigstickcarpet.com", + "licenseFile": "LICENSE" + }, + "@ngx-translate/core@8.0.0": { + "licenses": "MIT", + "repository": "https://github.com/ngx-translate/core", + "publisher": "Olivier Combe", + "licenseFile": "LICENSE" + }, + "@ngx-translate/http-loader@2.0.1": { + "licenses": "MIT", + "repository": "https://github.com/ngx-translate/http-loader", + "publisher": "Olivier Combe", + "licenseFile": "LICENSE" + }, + "@nodelib/fs.stat@1.1.3": { + "licenses": "MIT", + "repository": "https://github.com/nodelib/nodelib/tree/master/packages/fs/fs.stat", + "licenseFile": "README.md" + }, + "@types/events@3.0.0": { + "licenses": "MIT", + "repository": "https://github.com/DefinitelyTyped/DefinitelyTyped", + "licenseFile": "LICENSE" + }, + "@types/glob@7.1.1": { + "licenses": "MIT", + "repository": "https://github.com/DefinitelyTyped/DefinitelyTyped", + "licenseFile": "LICENSE" + }, + "@types/minimatch@3.0.3": { + "licenses": "MIT", + "repository": "https://github.com/DefinitelyTyped/DefinitelyTyped", + "licenseFile": "LICENSE" + }, + "@types/node@8.10.19": { + "licenses": "MIT", + "repository": "https://github.com/DefinitelyTyped/DefinitelyTyped", + "licenseFile": "LICENSE" + }, + "abbrev@1.1.1": { + "licenses": "ISC", + "repository": "https://github.com/isaacs/abbrev-js", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "licenseFile": "LICENSE" + }, + "accepts@1.3.5": { + "licenses": "MIT", + "repository": "https://github.com/jshttp/accepts", + "licenseFile": "LICENSE" + }, + "ajv@6.10.2": { + "licenses": "MIT", + "repository": "https://github.com/epoberezkin/ajv", + "publisher": "Evgeny Poberezkin", + "licenseFile": "LICENSE" + }, + "ajv@6.9.1": { + "licenses": "MIT", + "repository": "https://github.com/epoberezkin/ajv", + "publisher": "Evgeny Poberezkin", + "licenseFile": "LICENSE" + }, + "android-versions@1.4.0": { + "licenses": "MIT", + "repository": "https://github.com/dvoiss/android-versions", + "publisher": "dvoiss", + "licenseFile": "README.md" + }, + "ansi-align@2.0.0": { + "licenses": "ISC", + "repository": "https://github.com/nexdrew/ansi-align", + "publisher": "nexdrew", + "licenseFile": "LICENSE" + }, + "ansi-escapes@3.2.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/ansi-escapes", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "ansi-regex@2.1.1": { + "licenses": "MIT", + "repository": "https://github.com/chalk/ansi-regex", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "ansi-regex@3.0.0": { + "licenses": "MIT", + "repository": "https://github.com/chalk/ansi-regex", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "ansi-regex@4.1.0": { + "licenses": "MIT", + "repository": "https://github.com/chalk/ansi-regex", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "ansi-styles@3.2.1": { + "licenses": "MIT", + "repository": "https://github.com/chalk/ansi-styles", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "ansi@0.3.1": { + "licenses": "MIT", + "repository": "https://github.com/TooTallNate/ansi.js", + "publisher": "Nathan Rajlich", + "email": "nathan@tootallnate.net", + "url": "http://tootallnate.net", + "licenseFile": "LICENSE" + }, + "anymatch@1.3.2": { + "licenses": "ISC", + "repository": "https://github.com/es128/anymatch", + "publisher": "Elan Shanker", + "url": "http://github.com/es128", + "licenseFile": "LICENSE" + }, + "aproba@1.2.0": { + "licenses": "ISC", + "repository": "https://github.com/iarna/aproba", + "publisher": "Rebecca Turner", + "email": "me@re-becca.org", + "licenseFile": "LICENSE" + }, + "are-we-there-yet@1.1.5": { + "licenses": "ISC", + "repository": "https://github.com/iarna/are-we-there-yet", + "publisher": "Rebecca Turner", + "url": "http://re-becca.org", + "licenseFile": "LICENSE" + }, + "arr-diff@2.0.0": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/arr-diff", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "arr-diff@4.0.0": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/arr-diff", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "arr-flatten@1.1.0": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/arr-flatten", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "arr-union@3.1.0": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/arr-union", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "array-find-index@1.0.2": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/array-find-index", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "array-flatten@1.1.1": { + "licenses": "MIT", + "repository": "https://github.com/blakeembrey/array-flatten", + "publisher": "Blake Embrey", + "email": "hello@blakeembrey.com", + "url": "http://blakeembrey.me", + "licenseFile": "LICENSE" + }, + "array-union@1.0.2": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/array-union", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "array-uniq@1.0.3": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/array-uniq", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "array-unique@0.2.1": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/array-unique", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "array-unique@0.3.2": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/array-unique", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "asn1@0.2.3": { + "licenses": "MIT", + "repository": "https://github.com/mcavage/node-asn1", + "publisher": "Mark Cavage", + "email": "mcavage@gmail.com", + "licenseFile": "LICENSE" + }, + "assert-plus@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/mcavage/node-assert-plus", + "publisher": "Mark Cavage", + "email": "mcavage@gmail.com", + "licenseFile": "README.md" + }, + "assign-symbols@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/assign-symbols", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "async-each@1.0.1": { + "licenses": "MIT", + "repository": "https://github.com/paulmillr/async-each", + "publisher": "Paul Miller", + "url": "http://paulmillr.com/", + "licenseFile": "README.md" + }, + "async@2.6.2": { + "licenses": "MIT", + "repository": "https://github.com/caolan/async", + "publisher": "Caolan McMahon", + "licenseFile": "LICENSE" + }, + "asynckit@0.4.0": { + "licenses": "MIT", + "repository": "https://github.com/alexindigo/asynckit", + "publisher": "Alex Indigo", + "email": "iam@alexindigo.com", + "licenseFile": "LICENSE" + }, + "atob@2.1.1": { + "licenses": "(MIT OR Apache-2.0)", + "repository": "git://git.coolaj86.com/coolaj86/atob.js", + "publisher": "AJ ONeal", + "email": "coolaj86@gmail.com", + "url": "https://coolaj86.com", + "licenseFile": "LICENSE" + }, + "aws-sign2@0.7.0": { + "licenses": "Apache-2.0", + "repository": "https://github.com/mikeal/aws-sign", + "publisher": "Mikeal Rogers", + "email": "mikeal.rogers@gmail.com", + "url": "http://www.futurealoof.com", + "licenseFile": "LICENSE" + }, + "aws4@1.8.0": { + "licenses": "MIT", + "repository": "https://github.com/mhart/aws4", + "publisher": "Michael Hart", + "email": "michael.hart.au@gmail.com", + "url": "http://github.com/mhart", + "licenseFile": "LICENSE" + }, + "babel-plugin-add-header-comment@1.0.3": { + "licenses": "MIT", + "repository": "https://github.com/shopify/babel-plugin-add-header-comment", + "publisher": "Shopify Inc.", + "licenseFile": "LICENSE.md" + }, + "balanced-match@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/juliangruber/balanced-match", + "publisher": "Julian Gruber", + "email": "mail@juliangruber.com", + "url": "http://juliangruber.com", + "licenseFile": "LICENSE.md" + }, + "base64-js@1.3.0": { + "licenses": "MIT", + "repository": "https://github.com/beatgammit/base64-js", + "publisher": "T. Jameson Little", + "email": "t.jameson.little@gmail.com", + "licenseFile": "LICENSE" + }, + "base@0.11.2": { + "licenses": "MIT", + "repository": "https://github.com/node-base/base", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "bcrypt-pbkdf@1.0.1": { + "licenses": "BSD-3-Clause", + "licenseFile": "README.md" + }, + "big-integer@1.6.44": { + "licenses": "Unlicense", + "repository": "https://github.com/peterolson/BigInteger.js", + "publisher": "Peter Olson", + "email": "peter.e.c.olson+npm@gmail.com", + "licenseFile": "LICENSE" + }, + "binary-extensions@1.11.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/binary-extensions", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "body-parser@1.18.3": { + "licenses": "MIT", + "repository": "https://github.com/expressjs/body-parser", + "licenseFile": "LICENSE" + }, + "boxen@1.3.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/boxen", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "bplist-creator@0.0.7": { + "licenses": "MIT", + "repository": "https://github.com/nearinfinity/node-bplist-creator", + "publisher": "https://github.com/nearinfinity/node-bplist-parser.git", + "licenseFile": "README.md" + }, + "bplist-parser@0.0.6": { + "licenses": "MIT", + "repository": "https://github.com/nearinfinity/node-bplist-parser", + "publisher": "Joe Ferner", + "email": "joe.ferner@nearinfinity.com", + "licenseFile": "README.md" + }, + "bplist-parser@0.1.1": { + "licenses": "MIT", + "repository": "https://github.com/nearinfinity/node-bplist-parser", + "publisher": "Joe Ferner", + "email": "joe.ferner@nearinfinity.com", + "licenseFile": "README.md" + }, + "brace-expansion@1.1.11": { + "licenses": "MIT", + "repository": "https://github.com/juliangruber/brace-expansion", + "publisher": "Julian Gruber", + "email": "mail@juliangruber.com", + "url": "http://juliangruber.com", + "licenseFile": "LICENSE" + }, + "braces@1.8.5": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/braces", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "braces@2.3.2": { + "licenses": "MIT", + "repository": "https://github.com/micromatch/braces", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "buffer-from@1.1.0": { + "licenses": "MIT", + "repository": "https://github.com/LinusU/buffer-from", + "licenseFile": "readme.md" + }, + "builtin-modules@1.1.1": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/builtin-modules", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "builtins@1.0.3": { + "licenses": "MIT", + "repository": "https://github.com/juliangruber/builtins", + "licenseFile": "License" + }, + "bytes@3.0.0": { + "licenses": "MIT", + "repository": "https://github.com/visionmedia/bytes.js", + "publisher": "TJ Holowaychuk", + "email": "tj@vision-media.ca", + "url": "http://tjholowaychuk.com", + "licenseFile": "LICENSE" + }, + "cache-base@1.0.1": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/cache-base", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "call-me-maybe@1.0.1": { + "licenses": "MIT", + "repository": "https://github.com/limulus/call-me-maybe", + "publisher": "Eric McCarthy", + "email": "eric@limulus.net", + "url": "http://www.limulus.net/", + "licenseFile": "LICENSE" + }, + "callsites@3.1.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/callsites", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "camelcase@4.1.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/camelcase", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "capture-stack-trace@1.0.1": { + "licenses": "MIT", + "repository": "https://github.com/floatdrop/capture-stack-trace", + "publisher": "Vsevolod Strukchinsky", + "email": "floatdrop@gmail.com", + "url": "github.com/floatdrop", + "licenseFile": "license" + }, + "caseless@0.12.0": { + "licenses": "Apache-2.0", + "repository": "https://github.com/mikeal/caseless", + "publisher": "Mikeal Rogers", + "email": "mikeal.rogers@gmail.com", + "licenseFile": "LICENSE" + }, + "chalk@2.4.1": { + "licenses": "MIT", + "repository": "https://github.com/chalk/chalk", + "licenseFile": "license" + }, + "chalk@2.4.2": { + "licenses": "MIT", + "repository": "https://github.com/chalk/chalk", + "licenseFile": "license" + }, + "chardet@0.7.0": { + "licenses": "MIT", + "repository": "https://github.com/runk/node-chardet", + "publisher": "Dmitry Shirokov", + "email": "deadrunk@gmail.com", + "licenseFile": "LICENSE" + }, + "chart.js@2.7.2": { + "licenses": "MIT", + "repository": "https://github.com/chartjs/Chart.js", + "licenseFile": "LICENSE.md" + }, + "chartjs-color-string@0.5.0": { + "licenses": "MIT", + "repository": "https://github.com/chartjs/chartjs-color-string", + "publisher": "Heather Arthur", + "email": "fayearthur@gmail.com", + "licenseFile": "LICENSE" + }, + "chartjs-color@2.2.0": { + "licenses": "MIT", + "repository": "https://github.com/chartjs/chartjs-color", + "licenseFile": "LICENSE" + }, + "chokidar@1.7.0": { + "licenses": "MIT", + "repository": "https://github.com/paulmillr/chokidar", + "publisher": "Paul Miller", + "url": "http://paulmillr.com", + "licenseFile": "README.md" + }, + "chownr@1.1.1": { + "licenses": "ISC", + "repository": "https://github.com/isaacs/chownr", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/", + "licenseFile": "LICENSE" + }, + "ci-info@1.1.3": { + "licenses": "MIT", + "repository": "https://github.com/watson/ci-info", + "publisher": "Thomas Watson Steen", + "email": "w@tson.dk", + "url": "https://twitter.com/wa7son", + "licenseFile": "LICENSE" + }, + "class-utils@0.3.6": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/class-utils", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "cli-boxes@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/cli-boxes", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "cli-cursor@2.1.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/cli-cursor", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "cli-width@2.2.0": { + "licenses": "ISC", + "repository": "https://github.com/knownasilya/cli-width", + "publisher": "Ilya Radchenko", + "email": "ilya@burstcreations.com", + "licenseFile": "LICENSE" + }, + "code-point-at@1.1.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/code-point-at", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "collection-visit@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/collection-visit", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "color-convert@0.5.3": { + "licenses": "MIT*", + "repository": "https://github.com/harthur/color-convert", + "publisher": "Heather Arthur", + "email": "fayearthur@gmail.com", + "licenseFile": "LICENSE" + }, + "color-convert@1.9.2": { + "licenses": "MIT", + "repository": "https://github.com/Qix-/color-convert", + "publisher": "Heather Arthur", + "email": "fayearthur@gmail.com", + "licenseFile": "LICENSE" + }, + "color-name@1.1.1": { + "licenses": "MIT", + "repository": "https://github.com/dfcreative/color-name", + "publisher": "DY", + "email": "dfcreative@gmail.com", + "licenseFile": "LICENSE" + }, + "color-name@1.1.3": { + "licenses": "MIT", + "repository": "https://github.com/dfcreative/color-name", + "publisher": "DY", + "email": "dfcreative@gmail.com", + "licenseFile": "LICENSE" + }, + "com-darryncampbell-cordova-plugin-intent@1.3.0": { + "licenses": "MIT", + "repository": "https://github.com/darryncampbell/darryncampbell-cordova-plugin-intent", + "publisher": "Darryn Campbell", + "licenseFile": "LICENSE" + }, + "combined-stream@1.0.6": { + "licenses": "MIT", + "repository": "https://github.com/felixge/node-combined-stream", + "publisher": "Felix Geisendörfer", + "email": "felix@debuggable.com", + "url": "http://debuggable.com/", + "licenseFile": "License" + }, + "component-emitter@1.2.1": { + "licenses": "MIT", + "repository": "https://github.com/component/emitter", + "licenseFile": "LICENSE" + }, + "compressible@2.0.17": { + "licenses": "MIT", + "repository": "https://github.com/jshttp/compressible", + "licenseFile": "LICENSE" + }, + "compression@1.7.4": { + "licenses": "MIT", + "repository": "https://github.com/expressjs/compression", + "licenseFile": "LICENSE" + }, + "concat-map@0.0.1": { + "licenses": "MIT", + "repository": "https://github.com/substack/node-concat-map", + "publisher": "James Halliday", + "email": "mail@substack.net", + "url": "http://substack.net", + "licenseFile": "LICENSE" + }, + "conf@1.4.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/conf", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "configstore@3.1.2": { + "licenses": "BSD-2-Clause", + "repository": "https://github.com/yeoman/configstore", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "configstore@4.0.0": { + "licenses": "BSD-2-Clause", + "repository": "https://github.com/yeoman/configstore", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "console-control-strings@1.1.0": { + "licenses": "ISC", + "repository": "https://github.com/iarna/console-control-strings", + "publisher": "Rebecca Turner", + "email": "me@re-becca.org", + "url": "http://re-becca.org/", + "licenseFile": "LICENSE" + }, + "content-disposition@0.5.2": { + "licenses": "MIT", + "repository": "https://github.com/jshttp/content-disposition", + "licenseFile": "LICENSE" + }, + "content-type@1.0.4": { + "licenses": "MIT", + "repository": "https://github.com/jshttp/content-type", + "publisher": "Douglas Christopher Wilson", + "email": "doug@somethingdoug.com", + "licenseFile": "LICENSE" + }, + "cookie-signature@1.0.6": { + "licenses": "MIT", + "repository": "https://github.com/visionmedia/node-cookie-signature", + "publisher": "TJ Holowaychuk", + "email": "tj@learnboost.com", + "licenseFile": "Readme.md" + }, + "cookie@0.3.1": { + "licenses": "MIT", + "repository": "https://github.com/jshttp/cookie", + "publisher": "Roman Shtylman", + "email": "shtylman@gmail.com", + "licenseFile": "LICENSE" + }, + "copy-descriptor@0.1.1": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/copy-descriptor", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "cordova-android-support-gradle-release@3.0.1": { + "licenses": "MIT", + "publisher": "Dave Alden", + "licenseFile": "README.md" + }, + "cordova-android@8.0.0": { + "licenses": "Apache-2.0", + "repository": "https://github.com/apache/cordova-android", + "publisher": "Apache Software Foundation", + "licenseFile": "LICENSE", + "noticeFile": "node_modules/cordova-android/NOTICE" + }, + "cordova-app-hello-world@4.0.0": { + "licenses": "Apache-2.0", + "repository": "https://github.com/apache/cordova-app-hello-world", + "publisher": "Apache Software Foundation", + "licenseFile": "LICENSE", + "noticeFile": "node_modules/cordova-app-hello-world/NOTICE" + }, + "cordova-clipboard@1.3.0": { + "licenses": "MIT", + "repository": "https://github.com/ihadeed/cordova-clipboard", + "publisher": "Ibrahim Hadeed", + "licenseFile": "LICENSE" + }, + "cordova-common@3.2.0": { + "licenses": "Apache-2.0", + "repository": "https://github.com/apache/cordova-common", + "publisher": "Apache Software Foundation", + "licenseFile": "README.md" + }, + "cordova-create@2.0.0": { + "licenses": "Apache-2.0", + "repository": "https://github.com/apache/cordova-create", + "publisher": "Apache Software Foundation", + "licenseFile": "LICENSE" + }, + "cordova-fetch@2.0.1": { + "licenses": "Apache-2.0", + "repository": "https://github.com/apache/cordova-fetch", + "publisher": "Apache Software Foundation", + "licenseFile": "README.md" + }, + "cordova-ios@5.0.1": { + "licenses": "Apache-2.0", + "repository": "https://github.com/apache/cordova-ios", + "publisher": "Apache Software Foundation", + "licenseFile": "LICENSE", + "noticeFile": "node_modules/cordova-ios/NOTICE" + }, + "cordova-lib@9.0.1": { + "licenses": "Apache-2.0", + "repository": "https://github.com/apache/cordova-lib", + "publisher": "Apache Software Foundation", + "licenseFile": "LICENSE", + "noticeFile": "node_modules/cordova-lib/NOTICE" + }, + "cordova-plugin-badge@0.8.8": { + "licenses": "Apache*", + "repository": "https://github.com/katzer/cordova-plugin-badge", + "publisher": "Sebastián Katzer", + "licenseFile": "LICENSE" + }, + "cordova-plugin-camera@4.1.0": { + "licenses": "Apache-2.0", + "repository": "https://github.com/apache/cordova-plugin-camera", + "publisher": "Apache Software Foundation", + "licenseFile": "LICENSE", + "noticeFile": "node_modules/cordova-plugin-camera/NOTICE" + }, + "cordova-plugin-customurlscheme@4.4.0": { + "licenses": "MIT", + "repository": "https://github.com/EddyVerbruggen/Custom-URL-scheme", + "publisher": "Eddy Verbruggen", + "email": "eddyverbruggen@gmail.com", + "url": "https://github.com/EddyVerbruggen", + "licenseFile": "README.md" + }, + "cordova-plugin-device@2.0.3": { + "licenses": "Apache-2.0", + "repository": "https://github.com/apache/cordova-plugin-device", + "publisher": "Apache Software Foundation", + "licenseFile": "LICENSE", + "noticeFile": "node_modules/cordova-plugin-device/NOTICE" + }, + "cordova-plugin-file-opener2@2.2.1": { + "licenses": "MIT", + "repository": "https://github.com/pwlin/cordova-plugin-file-opener2", + "publisher": "pwlin05@gmail.com", + "licenseFile": "LICENSE" + }, + "cordova-plugin-file-transfer@1.7.1": { + "licenses": "Apache-2.0", + "repository": "https://github.com/apache/cordova-plugin-file-transfer", + "publisher": "Apache Software Foundation", + "licenseFile": "LICENSE", + "noticeFile": "node_modules/cordova-plugin-file-transfer/NOTICE" + }, + "cordova-plugin-file@6.0.2": { + "licenses": "Apache-2.0", + "repository": "https://github.com/apache/cordova-plugin-file", + "publisher": "Apache Software Foundation", + "licenseFile": "LICENSE", + "noticeFile": "node_modules/cordova-plugin-file/NOTICE" + }, + "cordova-plugin-geolocation@4.0.2": { + "licenses": "Apache-2.0", + "repository": "https://github.com/apache/cordova-plugin-geolocation", + "publisher": "Apache Software Foundation", + "licenseFile": "LICENSE", + "noticeFile": "node_modules/cordova-plugin-geolocation/NOTICE" + }, + "cordova-plugin-globalization@1.11.0": { + "licenses": "Apache-2.0", + "repository": "https://github.com/apache/cordova-plugin-globalization", + "publisher": "Apache Software Foundation", + "licenseFile": "LICENSE", + "noticeFile": "node_modules/cordova-plugin-globalization/NOTICE" + }, + "cordova-plugin-inappbrowser@3.1.0": { + "licenses": "Apache-2.0", + "repository": "https://github.com/apache/cordova-plugin-inappbrowser", + "publisher": "Apache Software Foundation", + "licenseFile": "LICENSE", + "noticeFile": "node_modules/cordova-plugin-inappbrowser/NOTICE" + }, + "cordova-plugin-ionic-keyboard@2.1.3": { + "licenses": "MIT*", + "repository": "https://github.com/ionic-team/cordova-plugin-ionic-keyboard", + "publisher": "Apache Software Foundation", + "licenseFile": "LICENSE" + }, + "cordova-plugin-local-notification@0.9.0-beta.3": { + "licenses": "Apache*", + "repository": "https://github.com/katzer/cordova-plugin-local-notifications", + "publisher": "Sebastián Katzer", + "licenseFile": "LICENSE" + }, + "cordova-plugin-media-capture@3.0.3": { + "licenses": "Apache-2.0", + "repository": "https://github.com/apache/cordova-plugin-media-capture", + "publisher": "Apache Software Foundation", + "licenseFile": "LICENSE", + "noticeFile": "node_modules/cordova-plugin-media-capture/NOTICE" + }, + "cordova-plugin-network-information@2.0.2": { + "licenses": "Apache-2.0", + "repository": "https://github.com/apache/cordova-plugin-network-information", + "publisher": "Apache Software Foundation", + "licenseFile": "LICENSE", + "noticeFile": "node_modules/cordova-plugin-network-information/NOTICE" + }, + "cordova-plugin-screen-orientation@3.0.2": { + "licenses": "Apache-2.0", + "repository": "https://github.com/apache/cordova-plugin-screen-orientation", + "publisher": "Apache Software Foundation", + "licenseFile": "LICENSE" + }, + "cordova-plugin-splashscreen@5.0.3": { + "licenses": "Apache-2.0", + "repository": "https://github.com/apache/cordova-plugin-splashscreen", + "publisher": "Apache Software Foundation", + "licenseFile": "LICENSE", + "noticeFile": "node_modules/cordova-plugin-splashscreen/NOTICE" + }, + "cordova-plugin-statusbar@2.4.3": { + "licenses": "Apache-2.0", + "repository": "https://github.com/apache/cordova-plugin-statusbar", + "publisher": "Apache Software Foundation", + "licenseFile": "LICENSE", + "noticeFile": "node_modules/cordova-plugin-statusbar/NOTICE" + }, + "cordova-plugin-whitelist@1.3.4": { + "licenses": "Apache-2.0", + "repository": "https://github.com/apache/cordova-plugin-whitelist", + "publisher": "Apache Software Foundation", + "licenseFile": "LICENSE", + "noticeFile": "node_modules/cordova-plugin-whitelist/NOTICE" + }, + "cordova-plugin-zip@3.1.0": { + "licenses": "BSD*", + "repository": "https://github.com/MobileChromeApps/cordova-plugin-zip", + "licenseFile": "LICENSE" + }, + "cordova-serve@3.0.0": { + "licenses": "Apache-2.0", + "repository": "https://github.com/apache/cordova-serve", + "publisher": "Apache Software Foundation", + "licenseFile": "LICENSE", + "noticeFile": "node_modules/cordova-serve/NOTICE" + }, + "cordova-sqlite-storage-dependencies@2.1.0": { + "licenses": "Unlicense", + "repository": "https://github.com/litehelpers/Cordova-sqlite-storage-dependencies", + "publisher": "Christopher J. Brody", + "licenseFile": "README.md" + }, + "cordova-sqlite-storage@3.4.0": { + "licenses": "MIT", + "repository": "https://github.com/xpbrew/cordova-sqlite-storage", + "publisher": "various", + "licenseFile": "LICENSE.md" + }, + "cordova-support-google-services@1.2.1": { + "licenses": "MIT", + "repository": "https://github.com/chemerisuk/cordova-support-google-services", + "publisher": "Maksim Chemerisuk", + "email": "chemerisuk@gmail.com", + "url": "https://github.com/chemerisuk", + "licenseFile": "LICENSE" + }, + "cordova@9.0.0": { + "licenses": "Apache-2.0", + "repository": "https://github.com/apache/cordova-cli", + "publisher": "Anis Kadri", + "licenseFile": "LICENSE", + "noticeFile": "node_modules/cordova/NOTICE" + }, + "core-js@2.3.0": { + "licenses": "MIT", + "repository": "https://github.com/zloirock/core-js", + "licenseFile": "LICENSE" + }, + "core-util-is@1.0.2": { + "licenses": "MIT", + "repository": "https://github.com/isaacs/core-util-is", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/", + "licenseFile": "LICENSE" + }, + "create-error-class@3.0.2": { + "licenses": "MIT", + "repository": "https://github.com/floatdrop/create-error-class", + "publisher": "Vsevolod Strukchinsky", + "email": "floatdrop@gmail.com", + "url": "github.com/floatdrop", + "licenseFile": "license" + }, + "cross-spawn@5.1.0": { + "licenses": "MIT", + "repository": "https://github.com/IndigoUnited/node-cross-spawn", + "publisher": "IndigoUnited", + "email": "hello@indigounited.com", + "url": "http://indigounited.com", + "licenseFile": "LICENSE" + }, + "cross-spawn@6.0.5": { + "licenses": "MIT", + "repository": "https://github.com/moxystudio/node-cross-spawn", + "publisher": "André Cruz", + "email": "andre@moxy.studio", + "licenseFile": "LICENSE" + }, + "crypto-random-string@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/crypto-random-string", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "currently-unhandled@0.4.1": { + "licenses": "MIT", + "repository": "https://github.com/jamestalmage/currently-unhandled", + "publisher": "James Talmage", + "email": "james@talmage.io", + "url": "github.com/jamestalmage", + "licenseFile": "license" + }, + "dashdash@1.14.1": { + "licenses": "MIT", + "repository": "https://github.com/trentm/node-dashdash", + "publisher": "Trent Mick", + "email": "trentm@gmail.com", + "url": "http://trentm.com", + "licenseFile": "LICENSE.txt" + }, + "debug@2.6.9": { + "licenses": "MIT", + "repository": "https://github.com/visionmedia/debug", + "publisher": "TJ Holowaychuk", + "email": "tj@vision-media.ca", + "licenseFile": "LICENSE" + }, + "debug@4.1.1": { + "licenses": "MIT", + "repository": "https://github.com/visionmedia/debug", + "publisher": "TJ Holowaychuk", + "email": "tj@vision-media.ca", + "licenseFile": "LICENSE" + }, + "decode-uri-component@0.2.0": { + "licenses": "MIT", + "repository": "https://github.com/SamVerschueren/decode-uri-component", + "publisher": "Sam Verschueren", + "email": "sam.verschueren@gmail.com", + "url": "github.com/SamVerschueren", + "licenseFile": "license" + }, + "dedent@0.7.0": { + "licenses": "MIT", + "repository": "https://github.com/dmnd/dedent", + "publisher": "Desmond Brand", + "email": "dmnd@desmondbrand.com", + "url": "http://desmondbrand.com", + "licenseFile": "LICENSE" + }, + "deep-equal@1.0.1": { + "licenses": "MIT", + "repository": "https://github.com/substack/node-deep-equal", + "publisher": "James Halliday", + "email": "mail@substack.net", + "url": "http://substack.net", + "licenseFile": "LICENSE" + }, + "deep-extend@0.6.0": { + "licenses": "MIT", + "repository": "https://github.com/unclechu/node-deep-extend", + "publisher": "Viacheslav Lotsmanov", + "email": "lotsmanov89@gmail.com", + "licenseFile": "LICENSE" + }, + "define-properties@1.1.2": { + "licenses": "MIT", + "repository": "https://github.com/ljharb/define-properties", + "publisher": "Jordan Harband", + "licenseFile": "LICENSE" + }, + "define-property@0.2.5": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/define-property", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "define-property@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/define-property", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "define-property@2.0.2": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/define-property", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "defined@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/substack/defined", + "publisher": "James Halliday", + "email": "mail@substack.net", + "url": "http://substack.net", + "licenseFile": "LICENSE" + }, + "delayed-stream@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/felixge/node-delayed-stream", + "publisher": "Felix Geisendörfer", + "email": "felix@debuggable.com", + "url": "http://debuggable.com/", + "licenseFile": "License" + }, + "delegates@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/visionmedia/node-delegates", + "licenseFile": "License" + }, + "dep-graph@1.1.0": { + "licenses": "MIT*", + "repository": "https://github.com/TrevorBurnham/dep-graph", + "publisher": "Trevor Burnham", + "url": "http://trevorburnham.com", + "licenseFile": "README.mdown" + }, + "depd@1.1.2": { + "licenses": "MIT", + "repository": "https://github.com/dougwilson/nodejs-depd", + "publisher": "Douglas Christopher Wilson", + "email": "doug@somethingdoug.com", + "licenseFile": "LICENSE" + }, + "destroy@1.0.4": { + "licenses": "MIT", + "repository": "https://github.com/stream-utils/destroy", + "publisher": "Jonathan Ong", + "email": "me@jongleberry.com", + "url": "http://jongleberry.com", + "licenseFile": "LICENSE" + }, + "detect-indent@5.0.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/detect-indent", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "detect-libc@1.0.3": { + "licenses": "Apache-2.0", + "repository": "https://github.com/lovell/detect-libc", + "publisher": "Lovell Fuller", + "email": "npm@lovell.info", + "licenseFile": "LICENSE" + }, + "dir-glob@2.2.2": { + "licenses": "MIT", + "repository": "https://github.com/kevva/dir-glob", + "publisher": "Kevin Mårtensson", + "email": "kevinmartensson@gmail.com", + "url": "github.com/kevva", + "licenseFile": "license" + }, + "dot-prop@4.2.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/dot-prop", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "duplexer3@0.1.4": { + "licenses": "BSD-3-Clause", + "repository": "https://github.com/floatdrop/duplexer3", + "publisher": "Conrad Pankoff", + "email": "deoxxa@fknsrs.biz", + "url": "http://www.fknsrs.biz/", + "licenseFile": "LICENSE.md" + }, + "ecc-jsbn@0.1.1": { + "licenses": "MIT", + "repository": "https://github.com/quartzjer/ecc-jsbn", + "publisher": "Jeremie Miller", + "email": "jeremie@jabber.org", + "url": "http://jeremie.com/", + "licenseFile": "LICENSE" + }, + "editor@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/substack/node-editor", + "publisher": "James Halliday", + "email": "mail@substack.net", + "url": "http://substack.net", + "licenseFile": "LICENSE" + }, + "ee-first@1.1.1": { + "licenses": "MIT", + "repository": "https://github.com/jonathanong/ee-first", + "publisher": "Jonathan Ong", + "email": "me@jongleberry.com", + "url": "http://jongleberry.com", + "licenseFile": "LICENSE" + }, + "elementtree@0.1.7": { + "licenses": "Apache-2.0", + "repository": "https://github.com/racker/node-elementtree", + "publisher": "Rackspace US, Inc.", + "licenseFile": "LICENSE.txt", + "noticeFile": "node_modules/elementtree/NOTICE" + }, + "encodeurl@1.0.2": { + "licenses": "MIT", + "repository": "https://github.com/pillarjs/encodeurl", + "licenseFile": "LICENSE" + }, + "end-of-stream@1.4.1": { + "licenses": "MIT", + "repository": "https://github.com/mafintosh/end-of-stream", + "publisher": "Mathias Buus", + "email": "mathiasbuus@gmail.com", + "licenseFile": "LICENSE" + }, + "endent@1.3.0": { + "licenses": "MIT", + "repository": "https://github.com/ZhouHansen/endent", + "publisher": "zhouhancheng", + "email": "z308114274@gmail.com", + "licenseFile": "LICENSE" + }, + "env-paths@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/env-paths", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "es-abstract@1.12.0": { + "licenses": "MIT", + "repository": "https://github.com/ljharb/es-abstract", + "publisher": "Jordan Harband", + "email": "ljharb@gmail.com", + "url": "http://ljharb.codes", + "licenseFile": "LICENSE" + }, + "es-to-primitive@1.1.1": { + "licenses": "MIT", + "repository": "https://github.com/ljharb/es-to-primitive", + "publisher": "Jordan Harband", + "licenseFile": "LICENSE" + }, + "es6-promise-plugin@4.2.2": { + "licenses": "MIT", + "repository": "https://github.com/vstirbu/PromisesPlugin", + "licenseFile": "LICENSE" + }, + "es6-promise@3.0.2": { + "licenses": "MIT", + "repository": "https://github.com/jakearchibald/ES6-Promises", + "publisher": "Yehuda Katz, Tom Dale, Stefan Penner and contributors", + "url": "Conversion to ES6 API by Jake Archibald", + "licenseFile": "LICENSE" + }, + "escape-html@1.0.3": { + "licenses": "MIT", + "repository": "https://github.com/component/escape-html", + "licenseFile": "LICENSE" + }, + "escape-string-regexp@1.0.5": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/escape-string-regexp", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "etag@1.8.1": { + "licenses": "MIT", + "repository": "https://github.com/jshttp/etag", + "licenseFile": "LICENSE" + }, + "execa@0.7.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/execa", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "execa@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/execa", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "expand-brackets@0.1.5": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/expand-brackets", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "expand-brackets@2.1.4": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/expand-brackets", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "expand-range@1.8.2": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/expand-range", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "express@4.16.4": { + "licenses": "MIT", + "repository": "https://github.com/expressjs/express", + "publisher": "TJ Holowaychuk", + "email": "tj@vision-media.ca", + "licenseFile": "LICENSE" + }, + "extend-shallow@2.0.1": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/extend-shallow", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "extend-shallow@3.0.2": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/extend-shallow", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "extend@3.0.2": { + "licenses": "MIT", + "repository": "https://github.com/justmoon/node-extend", + "publisher": "Stefan Thomas", + "email": "justmoon@members.fsf.org", + "url": "http://www.justmoon.net", + "licenseFile": "LICENSE" + }, + "external-editor@3.1.0": { + "licenses": "MIT", + "repository": "https://github.com/mrkmg/node-external-editor", + "publisher": "Kevin Gravier", + "email": "kevin@mrkmg.com", + "url": "https://mrkmg.com", + "licenseFile": "LICENSE" + }, + "extglob@0.3.2": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/extglob", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "extglob@2.0.4": { + "licenses": "MIT", + "repository": "https://github.com/micromatch/extglob", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "extsprintf@1.3.0": { + "licenses": "MIT", + "repository": "https://github.com/davepacheco/node-extsprintf", + "licenseFile": "LICENSE" + }, + "fast-deep-equal@2.0.1": { + "licenses": "MIT", + "repository": "https://github.com/epoberezkin/fast-deep-equal", + "publisher": "Evgeny Poberezkin", + "licenseFile": "LICENSE" + }, + "fast-glob@2.2.7": { + "licenses": "MIT", + "repository": "https://github.com/mrmlnc/fast-glob", + "publisher": "Denis Malinochkin", + "url": "canonium.com", + "licenseFile": "LICENSE" + }, + "fast-json-parse@1.0.3": { + "licenses": "MIT", + "repository": "https://github.com/mcollina/fast-json-parse", + "publisher": "Matteo Collina", + "email": "hello@matteocollina.com", + "licenseFile": "LICENSE" + }, + "fast-json-stable-stringify@2.0.0": { + "licenses": "MIT", + "repository": "https://github.com/epoberezkin/fast-json-stable-stringify", + "publisher": "James Halliday", + "email": "mail@substack.net", + "url": "http://substack.net", + "licenseFile": "LICENSE" + }, + "figures@2.0.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/figures", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "filename-regex@2.0.1": { + "licenses": "MIT", + "repository": "https://github.com/regexhq/filename-regex", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "fill-range@2.2.4": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/fill-range", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "fill-range@4.0.0": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/fill-range", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "finalhandler@1.1.1": { + "licenses": "MIT", + "repository": "https://github.com/pillarjs/finalhandler", + "publisher": "Douglas Christopher Wilson", + "email": "doug@somethingdoug.com", + "licenseFile": "LICENSE" + }, + "find-up@2.1.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/find-up", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "font-awesome@4.7.0": { + "licenses": "(OFL-1.1 AND MIT)", + "repository": "https://github.com/FortAwesome/Font-Awesome", + "publisher": "Dave Gandy", + "email": "dave@fontawesome.io", + "url": "http://twitter.com/davegandy", + "licenseFile": "README.md" + }, + "for-each@0.3.3": { + "licenses": "MIT", + "repository": "https://github.com/Raynos/for-each", + "publisher": "Raynos", + "email": "raynos2@gmail.com", + "licenseFile": "LICENSE" + }, + "for-in@1.0.2": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/for-in", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "for-own@0.1.5": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/for-own", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "foreach@2.0.5": { + "licenses": "MIT", + "repository": "https://github.com/manuelstofer/foreach", + "publisher": "Manuel Stofer", + "email": "manuel@takimata.ch", + "licenseFile": "LICENSE" + }, + "forever-agent@0.6.1": { + "licenses": "Apache-2.0", + "repository": "https://github.com/mikeal/forever-agent", + "publisher": "Mikeal Rogers", + "email": "mikeal.rogers@gmail.com", + "url": "http://www.futurealoof.com", + "licenseFile": "LICENSE" + }, + "form-data@2.3.3": { + "licenses": "MIT", + "repository": "https://github.com/form-data/form-data", + "publisher": "Felix Geisendörfer", + "email": "felix@debuggable.com", + "url": "http://debuggable.com/", + "licenseFile": "License" + }, + "forwarded@0.1.2": { + "licenses": "MIT", + "repository": "https://github.com/jshttp/forwarded", + "licenseFile": "LICENSE" + }, + "fragment-cache@0.2.1": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/fragment-cache", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "fresh@0.5.2": { + "licenses": "MIT", + "repository": "https://github.com/jshttp/fresh", + "publisher": "TJ Holowaychuk", + "email": "tj@vision-media.ca", + "url": "http://tjholowaychuk.com", + "licenseFile": "LICENSE" + }, + "fs-extra@7.0.1": { + "licenses": "MIT", + "repository": "https://github.com/jprichardson/node-fs-extra", + "publisher": "JP Richardson", + "email": "jprichardson@gmail.com", + "licenseFile": "LICENSE" + }, + "fs-extra@8.1.0": { + "licenses": "MIT", + "repository": "https://github.com/jprichardson/node-fs-extra", + "publisher": "JP Richardson", + "email": "jprichardson@gmail.com", + "licenseFile": "LICENSE" + }, + "fs-minipass@1.2.5": { + "licenses": "ISC", + "repository": "https://github.com/npm/fs-minipass", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/", + "licenseFile": "LICENSE" + }, + "fs.realpath@1.0.0": { + "licenses": "ISC", + "repository": "https://github.com/isaacs/fs.realpath", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/", + "licenseFile": "LICENSE" + }, + "fsevents@1.2.9": { + "licenses": "MIT", + "repository": "https://github.com/strongloop/fsevents", + "publisher": "Philipp Dunkel", + "email": "pip@pipobscure.com", + "licenseFile": "LICENSE" + }, + "function-bind@1.1.1": { + "licenses": "MIT", + "repository": "https://github.com/Raynos/function-bind", + "publisher": "Raynos", + "email": "raynos2@gmail.com", + "licenseFile": "LICENSE" + }, + "gauge@2.7.4": { + "licenses": "ISC", + "repository": "https://github.com/iarna/gauge", + "publisher": "Rebecca Turner", + "email": "me@re-becca.org", + "licenseFile": "LICENSE" + }, + "get-stream@3.0.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/get-stream", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "get-stream@4.1.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/get-stream", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "get-value@2.0.6": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/get-value", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "getpass@0.1.7": { + "licenses": "MIT", + "repository": "https://github.com/arekinath/node-getpass", + "publisher": "Alex Wilson", + "email": "alex.wilson@joyent.com", + "licenseFile": "LICENSE" + }, + "glob-base@0.3.0": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/glob-base", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "glob-parent@2.0.0": { + "licenses": "ISC", + "repository": "https://github.com/es128/glob-parent", + "publisher": "Elan Shanker", + "licenseFile": "LICENSE" + }, + "glob-parent@3.1.0": { + "licenses": "ISC", + "repository": "https://github.com/es128/glob-parent", + "publisher": "Elan Shanker", + "url": "https://github.com/es128", + "licenseFile": "LICENSE" + }, + "glob-to-regexp@0.3.0": { + "licenses": "BSD*", + "repository": "https://github.com/fitzgen/glob-to-regexp", + "publisher": "Nick Fitzgerald", + "email": "fitzgen@gmail.com", + "licenseFile": "README.md" + }, + "glob@7.1.2": { + "licenses": "ISC", + "repository": "https://github.com/isaacs/node-glob", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/", + "licenseFile": "LICENSE" + }, + "glob@7.1.3": { + "licenses": "ISC", + "repository": "https://github.com/isaacs/node-glob", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/", + "licenseFile": "LICENSE" + }, + "glob@7.1.4": { + "licenses": "ISC", + "repository": "https://github.com/isaacs/node-glob", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/", + "licenseFile": "LICENSE" + }, + "global-dirs@0.1.1": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/global-dirs", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "globby@9.2.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/globby", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "got@6.7.1": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/got", + "licenseFile": "license" + }, + "graceful-fs@4.1.11": { + "licenses": "ISC", + "repository": "https://github.com/isaacs/node-graceful-fs", + "licenseFile": "LICENSE" + }, + "graceful-fs@4.2.2": { + "licenses": "ISC", + "repository": "https://github.com/isaacs/node-graceful-fs", + "licenseFile": "LICENSE" + }, + "har-schema@2.0.0": { + "licenses": "ISC", + "repository": "https://github.com/ahmadnassri/har-schema", + "publisher": "Ahmad Nassri", + "email": "ahmad@ahmadnassri.com", + "url": "https://www.ahmadnassri.com/", + "licenseFile": "LICENSE" + }, + "har-validator@5.1.3": { + "licenses": "MIT", + "repository": "https://github.com/ahmadnassri/node-har-validator", + "publisher": "Ahmad Nassri", + "email": "ahmad@ahmadnassri.com", + "url": "https://www.ahmadnassri.com/", + "licenseFile": "LICENSE" + }, + "has-flag@3.0.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/has-flag", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "has-unicode@2.0.1": { + "licenses": "ISC", + "repository": "https://github.com/iarna/has-unicode", + "publisher": "Rebecca Turner", + "email": "me@re-becca.org", + "licenseFile": "LICENSE" + }, + "has-value@0.3.1": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/has-value", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "has-value@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/has-value", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "has-values@0.1.4": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/has-values", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "has-values@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/has-values", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "has@1.0.3": { + "licenses": "MIT", + "repository": "https://github.com/tarruda/has", + "publisher": "Thiago de Arruda", + "email": "tpadilha84@gmail.com", + "licenseFile": "LICENSE-MIT" + }, + "hosted-git-info@2.6.0": { + "licenses": "ISC", + "repository": "https://github.com/npm/hosted-git-info", + "publisher": "Rebecca Turner", + "email": "me@re-becca.org", + "url": "http://re-becca.org", + "licenseFile": "LICENSE" + }, + "hosted-git-info@2.8.4": { + "licenses": "ISC", + "repository": "https://github.com/npm/hosted-git-info", + "publisher": "Rebecca Turner", + "email": "me@re-becca.org", + "url": "http://re-becca.org", + "licenseFile": "LICENSE" + }, + "http-errors@1.6.3": { + "licenses": "MIT", + "repository": "https://github.com/jshttp/http-errors", + "publisher": "Jonathan Ong", + "email": "me@jongleberry.com", + "url": "http://jongleberry.com", + "licenseFile": "LICENSE" + }, + "http-signature@1.2.0": { + "licenses": "MIT", + "repository": "https://github.com/joyent/node-http-signature", + "publisher": "Joyent, Inc", + "licenseFile": "LICENSE" + }, + "iconv-lite@0.4.23": { + "licenses": "MIT", + "repository": "https://github.com/ashtuchkin/iconv-lite", + "publisher": "Alexander Shtuchkin", + "email": "ashtuchkin@gmail.com", + "licenseFile": "LICENSE" + }, + "iconv-lite@0.4.24": { + "licenses": "MIT", + "repository": "https://github.com/ashtuchkin/iconv-lite", + "publisher": "Alexander Shtuchkin", + "email": "ashtuchkin@gmail.com", + "licenseFile": "LICENSE" + }, + "ignore-walk@3.0.1": { + "licenses": "ISC", + "repository": "https://github.com/isaacs/ignore-walk", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/", + "licenseFile": "LICENSE" + }, + "ignore@4.0.6": { + "licenses": "MIT", + "repository": "https://github.com/kaelzhang/node-ignore", + "publisher": "kael", + "licenseFile": "LICENSE-MIT" + }, + "immediate@3.0.6": { + "licenses": "MIT", + "repository": "https://github.com/calvinmetcalf/immediate", + "licenseFile": "LICENSE.txt" + }, + "import-fresh@3.1.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/import-fresh", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "import-lazy@2.1.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/import-lazy", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "imurmurhash@0.1.4": { + "licenses": "MIT", + "repository": "https://github.com/jensyt/imurmurhash-js", + "publisher": "Jens Taylor", + "email": "jensyt@gmail.com", + "url": "https://github.com/homebrewing", + "licenseFile": "README.md" + }, + "indent-string@3.2.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/indent-string", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "inflight@1.0.6": { + "licenses": "ISC", + "repository": "https://github.com/npm/inflight", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/", + "licenseFile": "LICENSE" + }, + "inherits@2.0.3": { + "licenses": "ISC", + "repository": "https://github.com/isaacs/inherits", + "licenseFile": "LICENSE" + }, + "inherits@2.0.4": { + "licenses": "ISC", + "repository": "https://github.com/isaacs/inherits", + "licenseFile": "LICENSE" + }, + "ini@1.3.5": { + "licenses": "ISC", + "repository": "https://github.com/isaacs/ini", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/", + "licenseFile": "LICENSE" + }, + "init-package-json@1.10.3": { + "licenses": "ISC", + "repository": "https://github.com/npm/init-package-json", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/", + "licenseFile": "LICENSE" + }, + "inquirer@6.5.2": { + "licenses": "MIT", + "repository": "https://github.com/SBoudrias/Inquirer.js", + "publisher": "Simon Boudrias", + "email": "admin@simonboudrias.com", + "licenseFile": "LICENSE" + }, + "insight@0.10.3": { + "licenses": "BSD-2-Clause", + "repository": "https://github.com/yeoman/insight", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "install@0.8.9": { + "licenses": "MIT", + "repository": "https://github.com/benjamn/install", + "publisher": "Ben Newman", + "email": "bn@cs.stanford.edu", + "licenseFile": "LICENSE" + }, + "ionic-angular@3.9.3": { + "licenses": "MIT", + "repository": "https://github.com/ionic-team/ionic-v3", + "licenseFile": "README.md" + }, + "ionicons@3.0.0": { + "licenses": "MIT", + "repository": "https://github.com/driftyco/ionicons", + "publisher": "Ben Sperry", + "url": "https://twitter.com/bensperry", + "licenseFile": "LICENSE" + }, + "ios-sim@8.0.2": { + "licenses": "MIT", + "repository": "https://github.com/ios-control/ios-sim", + "publisher": "Shazron Abdullah", + "licenseFile": "LICENSE" + }, + "ip-regex@2.1.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/ip-regex", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "ipaddr.js@1.8.0": { + "licenses": "MIT", + "repository": "https://github.com/whitequark/ipaddr.js", + "publisher": "whitequark", + "email": "whitequark@whitequark.org", + "licenseFile": "README.md" + }, + "is-accessor-descriptor@0.1.6": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/is-accessor-descriptor", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "is-accessor-descriptor@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/is-accessor-descriptor", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "is-binary-path@1.0.1": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/is-binary-path", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "is-buffer@1.1.6": { + "licenses": "MIT", + "repository": "https://github.com/feross/is-buffer", + "publisher": "Feross Aboukhadijeh", + "email": "feross@feross.org", + "url": "http://feross.org/", + "licenseFile": "LICENSE" + }, + "is-builtin-module@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/is-builtin-module", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "is-callable@1.1.3": { + "licenses": "MIT", + "repository": "https://github.com/ljharb/is-callable", + "publisher": "Jordan Harband", + "email": "ljharb@gmail.com", + "url": "http://ljharb.codes", + "licenseFile": "LICENSE" + }, + "is-ci@1.1.0": { + "licenses": "MIT", + "repository": "https://github.com/watson/is-ci", + "publisher": "Thomas Watson Steen", + "email": "w@tson.dk", + "url": "https://twitter.com/wa7son", + "licenseFile": "LICENSE" + }, + "is-data-descriptor@0.1.4": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/is-data-descriptor", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "is-data-descriptor@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/is-data-descriptor", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "is-date-object@1.0.1": { + "licenses": "MIT", + "repository": "https://github.com/ljharb/is-date-object", + "publisher": "Jordan Harband", + "licenseFile": "LICENSE" + }, + "is-descriptor@0.1.6": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/is-descriptor", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "is-descriptor@1.0.2": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/is-descriptor", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "is-dotfile@1.0.3": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/is-dotfile", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "is-equal-shallow@0.1.3": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/is-equal-shallow", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "is-extendable@0.1.1": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/is-extendable", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "is-extendable@1.0.1": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/is-extendable", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "is-extglob@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/is-extglob", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "is-extglob@2.1.1": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/is-extglob", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "is-fullwidth-code-point@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/is-fullwidth-code-point", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "is-fullwidth-code-point@2.0.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/is-fullwidth-code-point", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "is-glob@2.0.1": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/is-glob", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "is-glob@3.1.0": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/is-glob", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "is-glob@4.0.1": { + "licenses": "MIT", + "repository": "https://github.com/micromatch/is-glob", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "is-installed-globally@0.1.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/is-installed-globally", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "is-npm@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/is-npm", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "http://sindresorhus.com", + "licenseFile": "readme.md" + }, + "is-number@2.1.0": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/is-number", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "is-number@3.0.0": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/is-number", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "is-number@4.0.0": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/is-number", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "is-obj@1.0.1": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/is-obj", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "is-odd@2.0.0": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/is-odd", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "is-path-inside@1.0.1": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/is-path-inside", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "is-plain-object@2.0.4": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/is-plain-object", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "is-posix-bracket@0.1.1": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/is-posix-bracket", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "is-primitive@2.0.0": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/is-primitive", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "is-promise@2.1.0": { + "licenses": "MIT", + "repository": "https://github.com/then/is-promise", + "publisher": "ForbesLindesay", + "licenseFile": "LICENSE" + }, + "is-redirect@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/is-redirect", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "is-regex@1.0.4": { + "licenses": "MIT", + "repository": "https://github.com/ljharb/is-regex", + "publisher": "Jordan Harband", + "licenseFile": "LICENSE" + }, + "is-retry-allowed@1.2.0": { + "licenses": "MIT", + "repository": "https://github.com/floatdrop/is-retry-allowed", + "publisher": "Vsevolod Strukchinsky", + "email": "floatdrop@gmail.com", + "url": "github.com/floatdrop", + "licenseFile": "license" + }, + "is-stream@1.1.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/is-stream", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "is-symbol@1.0.1": { + "licenses": "MIT", + "repository": "https://github.com/ljharb/is-symbol", + "publisher": "Jordan Harband", + "licenseFile": "LICENSE" + }, + "is-typedarray@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/hughsk/is-typedarray", + "publisher": "Hugh Kennedy", + "email": "hughskennedy@gmail.com", + "url": "http://hughsk.io/", + "licenseFile": "LICENSE.md" + }, + "is-url@1.2.4": { + "licenses": "MIT", + "repository": "https://github.com/segmentio/is-url", + "licenseFile": "LICENSE-MIT" + }, + "is-windows@1.0.2": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/is-windows", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "is-wsl@1.1.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/is-wsl", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "isarray@0.0.1": { + "licenses": "MIT", + "repository": "https://github.com/juliangruber/isarray", + "publisher": "Julian Gruber", + "email": "mail@juliangruber.com", + "url": "http://juliangruber.com", + "licenseFile": "README.md" + }, + "isarray@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/juliangruber/isarray", + "publisher": "Julian Gruber", + "email": "mail@juliangruber.com", + "url": "http://juliangruber.com", + "licenseFile": "README.md" + }, + "isexe@2.0.0": { + "licenses": "ISC", + "repository": "https://github.com/isaacs/isexe", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/", + "licenseFile": "LICENSE" + }, + "isobject@2.1.0": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/isobject", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "isobject@3.0.1": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/isobject", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "isstream@0.1.2": { + "licenses": "MIT", + "repository": "https://github.com/rvagg/isstream", + "publisher": "Rod Vagg", + "email": "rod@vagg.org", + "licenseFile": "LICENSE.md" + }, + "jsbn@0.1.1": { + "licenses": "MIT", + "repository": "https://github.com/andyperlitch/jsbn", + "publisher": "Tom Wu", + "licenseFile": "LICENSE" + }, + "json-parse-better-errors@1.0.2": { + "licenses": "MIT", + "repository": "https://github.com/zkat/json-parse-better-errors", + "publisher": "Kat Marchán", + "email": "kzm@zkat.tech", + "licenseFile": "LICENSE.md" + }, + "json-schema-traverse@0.4.1": { + "licenses": "MIT", + "repository": "https://github.com/epoberezkin/json-schema-traverse", + "publisher": "Evgeny Poberezkin", + "licenseFile": "LICENSE" + }, + "json-schema@0.2.3": { + "licenses": [ + "AFLv2.1", + "BSD" + ], + "repository": "https://github.com/kriszyp/json-schema", + "publisher": "Kris Zyp", + "licenseFile": "README.md" + }, + "json-stringify-safe@5.0.1": { + "licenses": "ISC", + "repository": "https://github.com/isaacs/json-stringify-safe", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me", + "licenseFile": "LICENSE" + }, + "jsonfile@4.0.0": { + "licenses": "MIT", + "repository": "https://github.com/jprichardson/node-jsonfile", + "publisher": "JP Richardson", + "email": "jprichardson@gmail.com", + "licenseFile": "LICENSE" + }, + "jsprim@1.4.1": { + "licenses": "MIT", + "repository": "https://github.com/joyent/node-jsprim", + "licenseFile": "LICENSE" + }, + "jszip@3.1.5": { + "licenses": "(MIT OR GPL-3.0)", + "repository": "https://github.com/Stuk/jszip", + "publisher": "Stuart Knightley", + "email": "stuart@stuartk.com", + "licenseFile": "LICENSE.markdown" + }, + "kind-of@3.2.2": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/kind-of", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "kind-of@4.0.0": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/kind-of", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "kind-of@5.1.0": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/kind-of", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "kind-of@6.0.2": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/kind-of", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "latest-version@3.1.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/latest-version", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "lie@3.1.1": { + "licenses": "MIT", + "repository": "https://github.com/calvinmetcalf/lie", + "licenseFile": "license.md" + }, + "locate-path@2.0.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/locate-path", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "lodash.debounce@4.0.8": { + "licenses": "MIT", + "repository": "https://github.com/lodash/lodash", + "publisher": "John-David Dalton", + "email": "john.david.dalton@gmail.com", + "url": "http://allyoucanleet.com/", + "licenseFile": "LICENSE" + }, + "lodash@4.17.15": { + "licenses": "MIT", + "repository": "https://github.com/lodash/lodash", + "publisher": "John-David Dalton", + "email": "john.david.dalton@gmail.com", + "licenseFile": "LICENSE" + }, + "loud-rejection@2.2.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/loud-rejection", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "lowercase-keys@1.0.1": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/lowercase-keys", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "lru-cache@4.1.5": { + "licenses": "ISC", + "repository": "https://github.com/isaacs/node-lru-cache", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "licenseFile": "LICENSE" + }, + "macos-release@2.3.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/macos-release", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "make-dir@1.3.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/make-dir", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "map-cache@0.2.2": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/map-cache", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "map-visit@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/map-visit", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "math-random@1.0.4": { + "licenses": "MIT", + "repository": "https://github.com/michaelrhodes/math-random", + "publisher": "Michael Rhodes", + "licenseFile": "readme.md" + }, + "mathjax@2.7.7": { + "licenses": "Apache-2.0", + "repository": "https://github.com/mathjax/MathJax", + "licenseFile": "LICENSE" + }, + "md5-file@4.0.0": { + "licenses": "MIT", + "repository": "https://github.com/roryrjb/md5-file", + "publisher": "Rory Bradford", + "email": "rory@dysfunctionalprogramming.com", + "licenseFile": "LICENSE.md" + }, + "media-typer@0.3.0": { + "licenses": "MIT", + "repository": "https://github.com/jshttp/media-typer", + "publisher": "Douglas Christopher Wilson", + "email": "doug@somethingdoug.com", + "licenseFile": "LICENSE" + }, + "merge-descriptors@1.0.1": { + "licenses": "MIT", + "repository": "https://github.com/component/merge-descriptors", + "publisher": "Jonathan Ong", + "email": "me@jongleberry.com", + "url": "http://jongleberry.com", + "licenseFile": "LICENSE" + }, + "merge2@1.3.0": { + "licenses": "MIT", + "repository": "https://github.com/teambition/merge2", + "licenseFile": "LICENSE" + }, + "methods@1.1.2": { + "licenses": "MIT", + "repository": "https://github.com/jshttp/methods", + "licenseFile": "LICENSE" + }, + "micromatch@2.3.11": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/micromatch", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "micromatch@3.1.10": { + "licenses": "MIT", + "repository": "https://github.com/micromatch/micromatch", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "mime-db@1.38.0": { + "licenses": "MIT", + "repository": "https://github.com/jshttp/mime-db", + "licenseFile": "LICENSE" + }, + "mime-db@1.42.0": { + "licenses": "MIT", + "repository": "https://github.com/jshttp/mime-db", + "licenseFile": "LICENSE" + }, + "mime-types@2.1.22": { + "licenses": "MIT", + "repository": "https://github.com/jshttp/mime-types", + "licenseFile": "LICENSE" + }, + "mime@1.4.1": { + "licenses": "MIT", + "repository": "https://github.com/broofa/node-mime", + "publisher": "Robert Kieffer", + "email": "robert@broofa.com", + "url": "http://github.com/broofa", + "licenseFile": "LICENSE" + }, + "mimic-fn@1.2.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/mimic-fn", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "minimatch@3.0.4": { + "licenses": "ISC", + "repository": "https://github.com/isaacs/minimatch", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me", + "licenseFile": "LICENSE" + }, + "minimist@0.0.8": { + "licenses": "MIT", + "repository": "https://github.com/substack/minimist", + "publisher": "James Halliday", + "email": "mail@substack.net", + "url": "http://substack.net", + "licenseFile": "LICENSE" + }, + "minimist@1.2.0": { + "licenses": "MIT", + "repository": "https://github.com/substack/minimist", + "publisher": "James Halliday", + "email": "mail@substack.net", + "url": "http://substack.net", + "licenseFile": "LICENSE" + }, + "minipass@2.3.5": { + "licenses": "ISC", + "repository": "https://github.com/isaacs/minipass", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/", + "licenseFile": "LICENSE" + }, + "minizlib@1.2.1": { + "licenses": "MIT", + "repository": "https://github.com/isaacs/minizlib", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/", + "licenseFile": "LICENSE" + }, + "mixin-deep@1.3.2": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/mixin-deep", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "mkdirp@0.5.1": { + "licenses": "MIT", + "repository": "https://github.com/substack/node-mkdirp", + "publisher": "James Halliday", + "email": "mail@substack.net", + "url": "http://substack.net", + "licenseFile": "LICENSE" + }, + "moment@2.22.2": { + "licenses": "MIT", + "repository": "https://github.com/moment/moment", + "publisher": "Iskren Ivov Chernev", + "email": "iskren.chernev@gmail.com", + "url": "https://github.com/ichernev", + "licenseFile": "LICENSE" + }, + "moodlemobile@3.8.1": { + "licenses": "Apache-2.0", + "repository": "https://github.com/moodlehq/moodlemobile2", + "publisher": "Moodle Pty Ltd.", + "email": "mobile@moodle.com", + "licenseFile": "LICENSE", + "noticeFile": "NOTICE" + }, + "ms@2.0.0": { + "licenses": "MIT", + "repository": "https://github.com/zeit/ms", + "licenseFile": "license.md" + }, + "ms@2.1.1": { + "licenses": "MIT", + "repository": "https://github.com/zeit/ms", + "licenseFile": "license.md" + }, + "mute-stream@0.0.7": { + "licenses": "ISC", + "repository": "https://github.com/isaacs/mute-stream", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/", + "licenseFile": "LICENSE" + }, + "mute-stream@0.0.8": { + "licenses": "ISC", + "repository": "https://github.com/isaacs/mute-stream", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/", + "licenseFile": "LICENSE" + }, + "nan@2.14.0": { + "licenses": "MIT", + "repository": "https://github.com/nodejs/nan", + "licenseFile": "LICENSE.md" + }, + "nanomatch@1.2.9": { + "licenses": "MIT", + "repository": "https://github.com/micromatch/nanomatch", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "needle@2.3.0": { + "licenses": "MIT", + "repository": "https://github.com/tomas/needle", + "publisher": "Tomás Pollak", + "email": "tomas@forkhq.com", + "licenseFile": "license.txt" + }, + "negotiator@0.6.1": { + "licenses": "MIT", + "repository": "https://github.com/jshttp/negotiator", + "licenseFile": "LICENSE" + }, + "nice-try@1.0.5": { + "licenses": "MIT", + "repository": "https://github.com/electerious/nice-try", + "licenseFile": "LICENSE" + }, + "nl.kingsquare.cordova.background-audio@1.0.1": { + "licenses": "MIT", + "repository": "https://github.com/AubreyHewes/cordova-background-audio", + "licenseFile": "LICENSE" + }, + "node-pre-gyp@0.12.0": { + "licenses": "BSD-3-Clause", + "repository": "https://github.com/mapbox/node-pre-gyp", + "publisher": "Dane Springmeyer", + "email": "dane@mapbox.com", + "licenseFile": "LICENSE" + }, + "nopt@1.0.9": { + "licenses": "MIT", + "repository": "https://github.com/isaacs/nopt", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/", + "licenseFile": "LICENSE" + }, + "nopt@4.0.1": { + "licenses": "ISC", + "repository": "https://github.com/npm/nopt", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/", + "licenseFile": "LICENSE" + }, + "normalize-package-data@2.4.0": { + "licenses": "BSD-2-Clause", + "repository": "https://github.com/npm/normalize-package-data", + "publisher": "Meryn Stol", + "email": "merynstol@gmail.com", + "licenseFile": "LICENSE" + }, + "normalize-path@2.1.1": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/normalize-path", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "npm-bundled@1.0.6": { + "licenses": "ISC", + "repository": "https://github.com/npm/npm-bundled", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/", + "licenseFile": "LICENSE" + }, + "npm-package-arg@6.1.1": { + "licenses": "ISC", + "repository": "https://github.com/npm/npm-package-arg", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/", + "licenseFile": "LICENSE" + }, + "npm-packlist@1.4.1": { + "licenses": "ISC", + "repository": "https://github.com/npm/npm-packlist", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/", + "licenseFile": "LICENSE" + }, + "npm-run-path@2.0.2": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/npm-run-path", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "npmlog@4.1.2": { + "licenses": "ISC", + "repository": "https://github.com/npm/npmlog", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/", + "licenseFile": "LICENSE" + }, + "number-is-nan@1.0.1": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/number-is-nan", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "oauth-sign@0.9.0": { + "licenses": "Apache-2.0", + "repository": "https://github.com/mikeal/oauth-sign", + "publisher": "Mikeal Rogers", + "email": "mikeal.rogers@gmail.com", + "url": "http://www.futurealoof.com", + "licenseFile": "LICENSE" + }, + "object-assign@4.1.1": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/object-assign", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "object-copy@0.1.0": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/object-copy", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "object-inspect@1.6.0": { + "licenses": "MIT", + "repository": "https://github.com/substack/object-inspect", + "publisher": "James Halliday", + "email": "mail@substack.net", + "url": "http://substack.net", + "licenseFile": "LICENSE" + }, + "object-keys@1.0.11": { + "licenses": "MIT", + "repository": "https://github.com/ljharb/object-keys", + "publisher": "Jordan Harband", + "email": "ljharb@gmail.com", + "url": "http://ljharb.codes", + "licenseFile": "LICENSE" + }, + "object-visit@1.0.1": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/object-visit", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "object.omit@2.0.1": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/object.omit", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "object.pick@1.3.0": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/object.pick", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "objectorarray@1.0.3": { + "licenses": "ISC", + "repository": "https://github.com/ZhouHansen/objectnotnull", + "publisher": "zhouhancheng", + "email": "z308114274@gmail.com", + "licenseFile": "LICENSE" + }, + "on-finished@2.3.0": { + "licenses": "MIT", + "repository": "https://github.com/jshttp/on-finished", + "licenseFile": "LICENSE" + }, + "on-headers@1.0.2": { + "licenses": "MIT", + "repository": "https://github.com/jshttp/on-headers", + "publisher": "Douglas Christopher Wilson", + "email": "doug@somethingdoug.com", + "licenseFile": "LICENSE" + }, + "once@1.4.0": { + "licenses": "ISC", + "repository": "https://github.com/isaacs/once", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/", + "licenseFile": "LICENSE" + }, + "onetime@2.0.1": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/onetime", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "opn@5.5.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/opn", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "os-homedir@1.0.2": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/os-homedir", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "os-name@3.1.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/os-name", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "os-tmpdir@1.0.2": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/os-tmpdir", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "osenv@0.1.5": { + "licenses": "ISC", + "repository": "https://github.com/npm/osenv", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/", + "licenseFile": "LICENSE" + }, + "p-finally@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/p-finally", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "p-limit@1.3.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/p-limit", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "p-locate@2.0.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/p-locate", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "p-try@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/p-try", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "p-try@2.2.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/p-try", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "package-json@4.0.1": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/package-json", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "pako@1.0.6": { + "licenses": "(MIT AND Zlib)", + "repository": "https://github.com/nodeca/pako", + "licenseFile": "LICENSE" + }, + "parent-module@1.0.1": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/parent-module", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "parse-glob@3.0.4": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/parse-glob", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "parseurl@1.3.2": { + "licenses": "MIT", + "repository": "https://github.com/pillarjs/parseurl", + "licenseFile": "LICENSE" + }, + "pascalcase@0.1.1": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/pascalcase", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "path-dirname@1.0.2": { + "licenses": "MIT", + "repository": "https://github.com/es128/path-dirname", + "publisher": "Elan Shanker", + "licenseFile": "license" + }, + "path-exists@3.0.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/path-exists", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "path-is-absolute@1.0.1": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/path-is-absolute", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "path-is-inside@1.0.2": { + "licenses": "(WTFPL OR MIT)", + "repository": "https://github.com/domenic/path-is-inside", + "publisher": "Domenic Denicola", + "email": "d@domenic.me", + "url": "https://domenic.me", + "licenseFile": "LICENSE.txt" + }, + "path-key@2.0.1": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/path-key", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "path-parse@1.0.6": { + "licenses": "MIT", + "repository": "https://github.com/jbgutierrez/path-parse", + "publisher": "Javier Blanco", + "email": "http://jbgutierrez.info", + "licenseFile": "LICENSE" + }, + "path-to-regexp@0.1.7": { + "licenses": "MIT", + "repository": "https://github.com/component/path-to-regexp", + "licenseFile": "LICENSE" + }, + "path-to-regexp@1.7.0": { + "licenses": "MIT", + "repository": "https://github.com/pillarjs/path-to-regexp", + "licenseFile": "LICENSE" + }, + "path-type@3.0.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/path-type", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "performance-now@2.1.0": { + "licenses": "MIT", + "repository": "https://github.com/braveg1rl/performance-now", + "publisher": "Braveg1rl", + "email": "braveg1rl@outlook.com", + "licenseFile": "license.txt" + }, + "phonegap-plugin-multidex@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/phonegap/phonegap-plugin-multidex", + "publisher": "Adobe PhoneGap Team", + "licenseFile": "README.md" + }, + "phonegap-plugin-push@2.2.3": { + "licenses": "MIT", + "repository": "https://github.com/phonegap/phonegap-plugin-push", + "publisher": "Adobe PhoneGap Team", + "licenseFile": "README.md" + }, + "pify@3.0.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/pify", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "pify@4.0.1": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/pify", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "pkg-up@2.0.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/pkg-up", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "plist@3.0.1": { + "licenses": "MIT", + "repository": "https://github.com/TooTallNate/node-plist", + "publisher": "Nathan Rajlich", + "email": "nathan@tootallnate.net", + "licenseFile": "LICENSE" + }, + "posix-character-classes@0.1.1": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/posix-character-classes", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "prepend-http@1.0.4": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/prepend-http", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "preserve@0.2.0": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/preserve", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "process-nextick-args@1.0.7": { + "licenses": "MIT", + "repository": "https://github.com/calvinmetcalf/process-nextick-args", + "licenseFile": "license.md" + }, + "process-nextick-args@2.0.0": { + "licenses": "MIT", + "repository": "https://github.com/calvinmetcalf/process-nextick-args", + "licenseFile": "license.md" + }, + "promise.prototype.finally@3.1.0": { + "licenses": "MIT", + "repository": "https://github.com/es-shims/Promise.prototype.finally", + "publisher": "Jordan Harband", + "licenseFile": "LICENSE" + }, + "promzard@0.3.0": { + "licenses": "ISC", + "repository": "https://github.com/isaacs/promzard", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/", + "licenseFile": "LICENSE" + }, + "properties-parser@0.3.1": { + "licenses": "MIT", + "repository": "https://github.com/xavi-/node-properties-parser", + "licenseFile": "README.markdown" + }, + "proxy-addr@2.0.4": { + "licenses": "MIT", + "repository": "https://github.com/jshttp/proxy-addr", + "publisher": "Douglas Christopher Wilson", + "email": "doug@somethingdoug.com", + "licenseFile": "LICENSE" + }, + "pseudomap@1.0.2": { + "licenses": "ISC", + "repository": "https://github.com/isaacs/pseudomap", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/", + "licenseFile": "LICENSE" + }, + "psl@1.1.29": { + "licenses": "MIT", + "repository": "https://github.com/wrangr/psl", + "publisher": "Lupo Montero", + "licenseFile": "README.md" + }, + "pump@3.0.0": { + "licenses": "MIT", + "repository": "https://github.com/mafintosh/pump", + "publisher": "Mathias Buus Madsen", + "email": "mathiasbuus@gmail.com", + "licenseFile": "LICENSE" + }, + "punycode@1.4.1": { + "licenses": "MIT", + "repository": "https://github.com/bestiejs/punycode.js", + "publisher": "Mathias Bynens", + "url": "https://mathiasbynens.be/", + "licenseFile": "LICENSE-MIT.txt" + }, + "punycode@2.1.1": { + "licenses": "MIT", + "repository": "https://github.com/bestiejs/punycode.js", + "publisher": "Mathias Bynens", + "url": "https://mathiasbynens.be/", + "licenseFile": "LICENSE-MIT.txt" + }, + "q@1.5.1": { + "licenses": "MIT", + "repository": "https://github.com/kriskowal/q", + "publisher": "Kris Kowal", + "email": "kris@cixar.com", + "url": "https://github.com/kriskowal", + "licenseFile": "LICENSE" + }, + "qs@6.5.2": { + "licenses": "BSD-3-Clause", + "repository": "https://github.com/ljharb/qs", + "licenseFile": "LICENSE" + }, + "randomatic@3.1.1": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/randomatic", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "range-parser@1.2.0": { + "licenses": "MIT", + "repository": "https://github.com/jshttp/range-parser", + "publisher": "TJ Holowaychuk", + "email": "tj@vision-media.ca", + "url": "http://tjholowaychuk.com", + "licenseFile": "LICENSE" + }, + "raw-body@2.3.3": { + "licenses": "MIT", + "repository": "https://github.com/stream-utils/raw-body", + "publisher": "Jonathan Ong", + "email": "me@jongleberry.com", + "url": "http://jongleberry.com", + "licenseFile": "LICENSE" + }, + "rc@1.2.8": { + "licenses": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "repository": "https://github.com/dominictarr/rc", + "publisher": "Dominic Tarr", + "email": "dominic.tarr@gmail.com", + "url": "dominictarr.com", + "licenseFile": "LICENSE.APACHE2" + }, + "read-chunk@3.2.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/read-chunk", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "read-package-json@2.1.0": { + "licenses": "ISC", + "repository": "https://github.com/npm/read-package-json", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/", + "licenseFile": "LICENSE" + }, + "read@1.0.7": { + "licenses": "ISC", + "repository": "https://github.com/isaacs/read", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/", + "licenseFile": "LICENSE" + }, + "readable-stream@2.0.6": { + "licenses": "MIT", + "repository": "https://github.com/nodejs/readable-stream", + "licenseFile": "LICENSE" + }, + "readable-stream@2.3.6": { + "licenses": "MIT", + "repository": "https://github.com/nodejs/readable-stream", + "licenseFile": "LICENSE" + }, + "readdirp@2.1.0": { + "licenses": "MIT", + "repository": "https://github.com/thlorenz/readdirp", + "publisher": "Thorsten Lorenz", + "email": "thlorenz@gmx.de", + "url": "thlorenz.com", + "licenseFile": "LICENSE" + }, + "reflect-metadata@0.1.12": { + "licenses": "Apache-2.0", + "repository": "https://github.com/rbuckton/reflect-metadata", + "publisher": "Ron Buckton", + "email": "ron.buckton@microsoft.com", + "url": "http://github.com/rbuckton", + "licenseFile": "LICENSE" + }, + "regex-cache@0.4.4": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/regex-cache", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "regex-not@1.0.2": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/regex-not", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "registry-auth-token@3.4.0": { + "licenses": "MIT", + "repository": "https://github.com/rexxars/registry-auth-token", + "publisher": "Espen Hovlandsdal", + "email": "espen@hovlandsdal.com", + "licenseFile": "LICENSE" + }, + "registry-url@3.1.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/registry-url", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "remove-trailing-separator@1.1.0": { + "licenses": "ISC", + "repository": "https://github.com/darsain/remove-trailing-separator", + "publisher": "darsain", + "licenseFile": "license" + }, + "repeat-element@1.1.2": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/repeat-element", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "repeat-string@1.6.1": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/repeat-string", + "publisher": "Jon Schlinkert", + "url": "http://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "request@2.88.0": { + "licenses": "Apache-2.0", + "repository": "https://github.com/request/request", + "publisher": "Mikeal Rogers", + "email": "mikeal.rogers@gmail.com", + "licenseFile": "LICENSE" + }, + "resolve-from@4.0.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/resolve-from", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "resolve-url@0.2.1": { + "licenses": "MIT", + "repository": "https://github.com/lydell/resolve-url", + "publisher": "Simon Lydell", + "licenseFile": "LICENSE" + }, + "resolve@1.11.1": { + "licenses": "MIT", + "repository": "https://github.com/browserify/resolve", + "publisher": "James Halliday", + "email": "mail@substack.net", + "url": "http://substack.net", + "licenseFile": "LICENSE" + }, + "resolve@1.12.0": { + "licenses": "MIT", + "repository": "https://github.com/browserify/resolve", + "publisher": "James Halliday", + "email": "mail@substack.net", + "url": "http://substack.net", + "licenseFile": "LICENSE" + }, + "restore-cursor@2.0.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/restore-cursor", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "resumer@0.0.0": { + "licenses": "MIT", + "repository": "https://github.com/substack/resumer", + "publisher": "James Halliday", + "email": "mail@substack.net", + "url": "http://substack.net", + "licenseFile": "LICENSE" + }, + "ret@0.1.15": { + "licenses": "MIT", + "repository": "https://github.com/fent/ret.js", + "publisher": "Roly Fentanes", + "url": "https://github.com/fent", + "licenseFile": "LICENSE" + }, + "rimraf@2.6.3": { + "licenses": "ISC", + "repository": "https://github.com/isaacs/rimraf", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/", + "licenseFile": "LICENSE" + }, + "run-async@2.3.0": { + "licenses": "MIT", + "repository": "https://github.com/SBoudrias/run-async", + "publisher": "Simon Boudrias", + "email": "admin@simonboudrias.com", + "licenseFile": "LICENSE" + }, + "rxjs@5.5.11": { + "licenses": "Apache-2.0", + "repository": "https://github.com/ReactiveX/RxJS", + "publisher": "Ben Lesh", + "email": "ben@benlesh.com", + "licenseFile": "LICENSE.txt" + }, + "rxjs@6.5.3": { + "licenses": "Apache-2.0", + "repository": "https://github.com/reactivex/rxjs", + "publisher": "Ben Lesh", + "email": "ben@benlesh.com", + "licenseFile": "LICENSE.txt" + }, + "safe-buffer@5.1.2": { + "licenses": "MIT", + "repository": "https://github.com/feross/safe-buffer", + "publisher": "Feross Aboukhadijeh", + "email": "feross@feross.org", + "url": "http://feross.org", + "licenseFile": "LICENSE" + }, + "safe-regex@1.1.0": { + "licenses": "MIT", + "repository": "https://github.com/substack/safe-regex", + "publisher": "James Halliday", + "email": "mail@substack.net", + "url": "http://substack.net", + "licenseFile": "LICENSE" + }, + "safer-buffer@2.1.2": { + "licenses": "MIT", + "repository": "https://github.com/ChALkeR/safer-buffer", + "publisher": "Nikita Skovoroda", + "email": "chalkerx@gmail.com", + "url": "https://github.com/ChALkeR", + "licenseFile": "LICENSE" + }, + "sax@1.1.4": { + "licenses": "ISC", + "repository": "https://github.com/isaacs/sax-js", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/", + "licenseFile": "LICENSE" + }, + "sax@1.2.4": { + "licenses": "ISC", + "repository": "https://github.com/isaacs/sax-js", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/", + "licenseFile": "LICENSE" + }, + "semver-diff@2.1.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/semver-diff", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "http://sindresorhus.com", + "licenseFile": "license" + }, + "semver@5.5.0": { + "licenses": "ISC", + "repository": "https://github.com/npm/node-semver", + "licenseFile": "LICENSE" + }, + "semver@5.6.0": { + "licenses": "ISC", + "repository": "https://github.com/npm/node-semver", + "licenseFile": "LICENSE" + }, + "semver@5.7.0": { + "licenses": "ISC", + "repository": "https://github.com/npm/node-semver", + "licenseFile": "LICENSE" + }, + "semver@5.7.1": { + "licenses": "ISC", + "repository": "https://github.com/npm/node-semver", + "licenseFile": "LICENSE" + }, + "send@0.16.2": { + "licenses": "MIT", + "repository": "https://github.com/pillarjs/send", + "publisher": "TJ Holowaychuk", + "email": "tj@vision-media.ca", + "licenseFile": "LICENSE" + }, + "serve-static@1.13.2": { + "licenses": "MIT", + "repository": "https://github.com/expressjs/serve-static", + "publisher": "Douglas Christopher Wilson", + "email": "doug@somethingdoug.com", + "licenseFile": "LICENSE" + }, + "serviceworker-cache-polyfill@4.0.0": { + "licenses": "MIT", + "repository": "https://github.com/coonsta/cache-polyfill", + "licenseFile": "LICENSE" + }, + "set-blocking@2.0.0": { + "licenses": "ISC", + "repository": "https://github.com/yargs/set-blocking", + "publisher": "Ben Coe", + "email": "ben@npmjs.com", + "licenseFile": "LICENSE.txt" + }, + "set-immediate-shim@1.0.1": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/set-immediate-shim", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "readme.md" + }, + "set-value@2.0.1": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/set-value", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "setprototypeof@1.1.0": { + "licenses": "ISC", + "repository": "https://github.com/wesleytodd/setprototypeof", + "publisher": "Wes Todd", + "licenseFile": "LICENSE" + }, + "shebang-command@1.2.0": { + "licenses": "MIT", + "repository": "https://github.com/kevva/shebang-command", + "publisher": "Kevin Martensson", + "email": "kevinmartensson@gmail.com", + "url": "github.com/kevva", + "licenseFile": "license" + }, + "shebang-regex@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/shebang-regex", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "shelljs@0.2.6": { + "licenses": "BSD*", + "repository": "https://github.com/arturadib/shelljs", + "publisher": "Artur Adib", + "email": "aadib@mozilla.com", + "licenseFile": "LICENSE" + }, + "shelljs@0.5.3": { + "licenses": "BSD*", + "repository": "https://github.com/arturadib/shelljs", + "publisher": "Artur Adib", + "email": "arturadib@gmail.com", + "licenseFile": "LICENSE" + }, + "signal-exit@3.0.2": { + "licenses": "ISC", + "repository": "https://github.com/tapjs/signal-exit", + "publisher": "Ben Coe", + "email": "ben@npmjs.com", + "licenseFile": "LICENSE.txt" + }, + "simctl@2.0.0": { + "licenses": "MIT", + "repository": "https://github.com/ios-control/simctl", + "publisher": "Shazron Abdullah", + "licenseFile": "LICENSE" + }, + "simple-plist@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/wollardj/node-simple-plist", + "publisher": "Joe Wollard", + "licenseFile": "LICENSE" + }, + "slash@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/slash", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "http://sindresorhus.com", + "licenseFile": "readme.md" + }, + "slash@2.0.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/slash", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "snapdragon-node@2.1.1": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/snapdragon-node", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "snapdragon-util@3.0.1": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/snapdragon-util", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "snapdragon@0.8.2": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/snapdragon", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "source-map-resolve@0.5.2": { + "licenses": "MIT", + "repository": "https://github.com/lydell/source-map-resolve", + "publisher": "Simon Lydell", + "licenseFile": "LICENSE" + }, + "source-map-support@0.5.6": { + "licenses": "MIT", + "repository": "https://github.com/evanw/node-source-map-support", + "licenseFile": "LICENSE.md" + }, + "source-map-url@0.4.0": { + "licenses": "MIT", + "repository": "https://github.com/lydell/source-map-url", + "publisher": "Simon Lydell", + "licenseFile": "LICENSE" + }, + "source-map@0.5.7": { + "licenses": "BSD-3-Clause", + "repository": "https://github.com/mozilla/source-map", + "publisher": "Nick Fitzgerald", + "email": "nfitzgerald@mozilla.com", + "licenseFile": "LICENSE" + }, + "source-map@0.6.1": { + "licenses": "BSD-3-Clause", + "repository": "https://github.com/mozilla/source-map", + "publisher": "Nick Fitzgerald", + "email": "nfitzgerald@mozilla.com", + "licenseFile": "LICENSE" + }, + "spdx-correct@3.0.0": { + "licenses": "Apache-2.0", + "repository": "https://github.com/jslicense/spdx-correct.js", + "publisher": "Kyle E. Mitchell", + "email": "kyle@kemitchell.com", + "url": "https://kemitchell.com", + "licenseFile": "LICENSE" + }, + "spdx-exceptions@2.1.0": { + "licenses": "CC-BY-3.0", + "repository": "https://github.com/kemitchell/spdx-exceptions.json", + "publisher": "The Linux Foundation", + "licenseFile": "README.md" + }, + "spdx-expression-parse@3.0.0": { + "licenses": "MIT", + "repository": "https://github.com/jslicense/spdx-expression-parse.js", + "publisher": "Kyle E. Mitchell", + "email": "kyle@kemitchell.com", + "url": "http://kemitchell.com", + "licenseFile": "LICENSE" + }, + "spdx-license-ids@3.0.0": { + "licenses": "CC0-1.0", + "repository": "https://github.com/shinnn/spdx-license-ids", + "publisher": "Shinnosuke Watanabe", + "url": "https://github.com/shinnn", + "licenseFile": "README.md" + }, + "split-string@3.1.0": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/split-string", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "sshpk@1.14.2": { + "licenses": "MIT", + "repository": "https://github.com/arekinath/node-sshpk", + "publisher": "Joyent, Inc", + "licenseFile": "LICENSE" + }, + "static-extend@0.1.2": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/static-extend", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "statuses@1.4.0": { + "licenses": "MIT", + "repository": "https://github.com/jshttp/statuses", + "licenseFile": "LICENSE" + }, + "stream-buffers@2.2.0": { + "licenses": "Unlicense", + "repository": "https://github.com/samcday/node-stream-buffer", + "publisher": "Sam Day", + "email": "me@samcday.com.au", + "licenseFile": "README.md" + }, + "string-width@1.0.2": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/string-width", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "string-width@2.1.1": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/string-width", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "string.prototype.codepointat@0.2.1": { + "licenses": "MIT", + "repository": "https://github.com/mathiasbynens/String.prototype.codePointAt", + "publisher": "Mathias Bynens", + "url": "https://mathiasbynens.be/", + "licenseFile": "LICENSE-MIT.txt" + }, + "string.prototype.trim@1.1.2": { + "licenses": "MIT", + "repository": "https://github.com/es-shims/String.prototype.trim", + "publisher": "Jordan Harband", + "email": "ljharb@gmail.com", + "url": "http://ljharb.codes", + "licenseFile": "LICENSE" + }, + "string_decoder@0.10.31": { + "licenses": "MIT", + "repository": "https://github.com/rvagg/string_decoder", + "licenseFile": "LICENSE" + }, + "string_decoder@1.1.1": { + "licenses": "MIT", + "repository": "https://github.com/nodejs/string_decoder", + "licenseFile": "LICENSE" + }, + "strip-ansi@3.0.1": { + "licenses": "MIT", + "repository": "https://github.com/chalk/strip-ansi", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "strip-ansi@4.0.0": { + "licenses": "MIT", + "repository": "https://github.com/chalk/strip-ansi", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "strip-ansi@5.2.0": { + "licenses": "MIT", + "repository": "https://github.com/chalk/strip-ansi", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "strip-bom@3.0.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/strip-bom", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "strip-eof@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/strip-eof", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "strip-json-comments@2.0.1": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/strip-json-comments", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "supports-color@5.4.0": { + "licenses": "MIT", + "repository": "https://github.com/chalk/supports-color", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "sw-toolbox@3.6.0": { + "licenses": "Apache-2.0", + "repository": "https://github.com/GoogleChrome/sw-toolbox", + "licenseFile": "LICENSE" + }, + "symbol-observable@1.0.1": { + "licenses": "MIT", + "repository": "https://github.com/blesh/symbol-observable", + "publisher": "Ben Lesh", + "email": "ben@benlesh.com", + "licenseFile": "license" + }, + "tail@0.4.0": { + "licenses": "MIT*", + "repository": "https://github.com/lucagrulla/node-tail", + "publisher": "Luca Grulla", + "licenseFile": "README.md" + }, + "tape@4.11.0": { + "licenses": "MIT", + "repository": "https://github.com/substack/tape", + "publisher": "James Halliday", + "email": "mail@substack.net", + "url": "http://substack.net", + "licenseFile": "LICENSE" + }, + "tar@4.4.8": { + "licenses": "ISC", + "repository": "https://github.com/npm/node-tar", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/", + "licenseFile": "LICENSE" + }, + "term-size@1.2.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/term-size", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "through@2.3.8": { + "licenses": "MIT", + "repository": "https://github.com/dominictarr/through", + "publisher": "Dominic Tarr", + "email": "dominic.tarr@gmail.com", + "url": "dominictarr.com", + "licenseFile": "LICENSE.APACHE2" + }, + "timed-out@4.0.1": { + "licenses": "MIT", + "repository": "https://github.com/floatdrop/timed-out", + "publisher": "Vsevolod Strukchinsky", + "email": "floatdrop@gmail.com", + "licenseFile": "license" + }, + "tmp@0.0.33": { + "licenses": "MIT", + "repository": "https://github.com/raszi/node-tmp", + "publisher": "KARASZI István", + "email": "github@spam.raszi.hu", + "url": "http://raszi.hu/", + "licenseFile": "LICENSE" + }, + "to-object-path@0.3.0": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/to-object-path", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "to-regex-range@2.1.1": { + "licenses": "MIT", + "repository": "https://github.com/micromatch/to-regex-range", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "to-regex@3.0.2": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/to-regex", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "tough-cookie@2.4.3": { + "licenses": "BSD-3-Clause", + "repository": "https://github.com/salesforce/tough-cookie", + "publisher": "Jeremy Stashewsky", + "email": "jstash@gmail.com", + "licenseFile": "LICENSE" + }, + "tough-cookie@3.0.1": { + "licenses": "BSD-3-Clause", + "repository": "https://github.com/salesforce/tough-cookie", + "publisher": "Jeremy Stashewsky", + "email": "jstash@gmail.com", + "licenseFile": "LICENSE" + }, + "ts-md5@1.2.4": { + "licenses": "MIT", + "repository": "https://github.com/cotag/ts-md5", + "licenseFile": "README.md" + }, + "tsickle@0.27.5": { + "licenses": "MIT", + "repository": "https://github.com/angular/tsickle", + "licenseFile": "LICENSE" + }, + "tslib@1.9.2": { + "licenses": "Apache-2.0", + "repository": "https://github.com/Microsoft/tslib", + "publisher": "Microsoft Corp.", + "licenseFile": "LICENSE.txt" + }, + "tunnel-agent@0.6.0": { + "licenses": "Apache-2.0", + "repository": "https://github.com/mikeal/tunnel-agent", + "publisher": "Mikeal Rogers", + "email": "mikeal.rogers@gmail.com", + "url": "http://www.futurealoof.com", + "licenseFile": "LICENSE" + }, + "tweetnacl@0.14.5": { + "licenses": "Unlicense", + "repository": "https://github.com/dchest/tweetnacl-js", + "publisher": "TweetNaCl-js contributors", + "licenseFile": "LICENSE" + }, + "type-is@1.6.16": { + "licenses": "MIT", + "repository": "https://github.com/jshttp/type-is", + "licenseFile": "LICENSE" + }, + "typescript@2.6.2": { + "licenses": "Apache-2.0", + "repository": "https://github.com/Microsoft/TypeScript", + "publisher": "Microsoft Corp.", + "licenseFile": "LICENSE.txt" + }, + "underscore@1.2.1": { + "licenses": "MIT*", + "repository": "https://github.com/documentcloud/underscore", + "publisher": "Jeremy Ashkenas", + "email": "jeremy@documentcloud.org", + "licenseFile": "LICENSE" + }, + "underscore@1.9.1": { + "licenses": "MIT", + "repository": "https://github.com/jashkenas/underscore", + "publisher": "Jeremy Ashkenas", + "email": "jeremy@documentcloud.org", + "licenseFile": "LICENSE" + }, + "union-value@1.0.1": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/union-value", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "unique-string@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/unique-string", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "universalify@0.1.1": { + "licenses": "MIT", + "repository": "https://github.com/RyanZim/universalify", + "publisher": "Ryan Zimmerman", + "email": "opensrc@ryanzim.com", + "licenseFile": "LICENSE" + }, + "unorm@1.6.0": { + "licenses": "MIT*", + "repository": "https://github.com/walling/unorm", + "publisher": "Bjarke Walling", + "email": "bwp@bwp.dk", + "licenseFile": "LICENSE.md" + }, + "unpipe@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/stream-utils/unpipe", + "publisher": "Douglas Christopher Wilson", + "email": "doug@somethingdoug.com", + "licenseFile": "LICENSE" + }, + "unset-value@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/unset-value", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "unzip-response@2.0.1": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/unzip-response", + "licenseFile": "license" + }, + "update-notifier@2.5.0": { + "licenses": "BSD-2-Clause", + "repository": "https://github.com/yeoman/update-notifier", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "https://sindresorhus.com", + "licenseFile": "license" + }, + "uri-js@4.2.2": { + "licenses": "BSD-2-Clause", + "repository": "https://github.com/garycourt/uri-js", + "publisher": "Gary Court", + "email": "gary.court@gmail.com", + "licenseFile": "README.md" + }, + "urix@0.1.0": { + "licenses": "MIT", + "repository": "https://github.com/lydell/urix", + "publisher": "Simon Lydell", + "licenseFile": "LICENSE" + }, + "url-parse-lax@1.0.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/url-parse-lax", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "use@3.1.0": { + "licenses": "MIT", + "repository": "https://github.com/jonschlinkert/use", + "publisher": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert", + "licenseFile": "LICENSE" + }, + "util-deprecate@1.0.2": { + "licenses": "MIT", + "repository": "https://github.com/TooTallNate/util-deprecate", + "publisher": "Nathan Rajlich", + "email": "nathan@tootallnate.net", + "url": "http://n8.io/", + "licenseFile": "LICENSE" + }, + "utils-merge@1.0.1": { + "licenses": "MIT", + "repository": "https://github.com/jaredhanson/utils-merge", + "publisher": "Jared Hanson", + "email": "jaredhanson@gmail.com", + "url": "http://www.jaredhanson.net/", + "licenseFile": "LICENSE" + }, + "uuid@3.3.2": { + "licenses": "MIT", + "repository": "https://github.com/kelektiv/node-uuid", + "licenseFile": "LICENSE.md" + }, + "valid-identifier@0.0.2": { + "licenses": "Apache-2.0", + "repository": "https://github.com/purplecabbage/valid-identifier", + "publisher": "Jesse MacFadyen", + "licenseFile": "LICENSE" + }, + "validate-npm-package-license@3.0.3": { + "licenses": "Apache-2.0", + "repository": "https://github.com/kemitchell/validate-npm-package-license.js", + "publisher": "Kyle E. Mitchell", + "email": "kyle@kemitchell.com", + "url": "https://kemitchell.com", + "licenseFile": "LICENSE" + }, + "validate-npm-package-name@3.0.0": { + "licenses": "ISC", + "repository": "https://github.com/npm/validate-npm-package-name", + "publisher": "zeke", + "licenseFile": "LICENSE" + }, + "vary@1.1.2": { + "licenses": "MIT", + "repository": "https://github.com/jshttp/vary", + "publisher": "Douglas Christopher Wilson", + "email": "doug@somethingdoug.com", + "licenseFile": "LICENSE" + }, + "verror@1.10.0": { + "licenses": "MIT", + "repository": "https://github.com/davepacheco/node-verror", + "licenseFile": "LICENSE" + }, + "web-animations-js@2.3.1": { + "licenses": "Apache-2.0", + "repository": "https://github.com/web-animations/web-animations-js", + "licenseFile": "README.md" + }, + "which@1.3.1": { + "licenses": "ISC", + "repository": "https://github.com/isaacs/node-which", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me", + "licenseFile": "LICENSE" + }, + "wide-align@1.1.3": { + "licenses": "ISC", + "repository": "https://github.com/iarna/wide-align", + "publisher": "Rebecca Turner", + "email": "me@re-becca.org", + "url": "http://re-becca.org/", + "licenseFile": "LICENSE" + }, + "widest-line@2.0.1": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/widest-line", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "windows-release@3.2.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/windows-release", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "with-open-file@0.1.6": { + "licenses": "MIT", + "repository": "https://github.com/raphinesse/with-open-file", + "publisher": "Raphael von der Grün", + "email": "raphinesse@gmail.com", + "licenseFile": "LICENSE" + }, + "wrappy@1.0.2": { + "licenses": "ISC", + "repository": "https://github.com/npm/wrappy", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/", + "licenseFile": "LICENSE" + }, + "write-file-atomic@2.4.3": { + "licenses": "ISC", + "repository": "https://github.com/iarna/write-file-atomic", + "publisher": "Rebecca Turner", + "email": "me@re-becca.org", + "url": "http://re-becca.org", + "licenseFile": "LICENSE" + }, + "xcode@2.0.0": { + "licenses": "Apache-2.0", + "repository": "https://github.com/apache/cordova-node-xcode", + "publisher": "Apache Software Foundation", + "licenseFile": "LICENSE" + }, + "xdg-basedir@3.0.0": { + "licenses": "MIT", + "repository": "https://github.com/sindresorhus/xdg-basedir", + "publisher": "Sindre Sorhus", + "email": "sindresorhus@gmail.com", + "url": "sindresorhus.com", + "licenseFile": "license" + }, + "xml-escape@1.1.0": { + "licenses": "MIT*", + "repository": "https://github.com/miketheprogrammer/xml-escape", + "publisher": "Michael Hernandez - michael.hernandez1988@gmail.com", + "licenseFile": "LICENSE" + }, + "xmlbuilder@9.0.7": { + "licenses": "MIT", + "repository": "https://github.com/oozcitak/xmlbuilder-js", + "publisher": "Ozgur Ozcitak", + "email": "oozcitak@gmail.com", + "licenseFile": "LICENSE" + }, + "xmldom@0.1.27": { + "licenses": "LGPL", + "repository": "https://github.com/jindw/xmldom", + "publisher": "jindw", + "email": "jindw@xidea.org", + "url": "http://www.xidea.org", + "licenseFile": "LICENSE" + }, + "yallist@2.1.2": { + "licenses": "ISC", + "repository": "https://github.com/isaacs/yallist", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/", + "licenseFile": "LICENSE" + }, + "yallist@3.0.3": { + "licenses": "ISC", + "repository": "https://github.com/isaacs/yallist", + "publisher": "Isaac Z. Schlueter", + "email": "i@izs.me", + "url": "http://blog.izs.me/", + "licenseFile": "LICENSE" + }, + "zone.js@0.8.26": { + "licenses": "MIT", + "repository": "https://github.com/angular/zone.js", + "publisher": "Brian Ford", + "licenseFile": "LICENSE" + } +} diff --git a/package-lock.json b/package-lock.json index 4f44b8353..88471ab97 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "moodlemobile", - "version": "3.8.0", + "version": "3.8.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -31,33 +31,33 @@ } }, "@angular/animations": { - "version": "5.2.10", - "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-5.2.10.tgz", - "integrity": "sha512-QNYXqnti8BeFriNaZ/juLnO6l0MVlVNUmLycC9ma+pdTiEJl8rtgZ0WXxgOCjScyKpInkWn2J+m9FI/78SYFpw==", + "version": "5.2.11", + "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-5.2.11.tgz", + "integrity": "sha512-J7wKHkFn3wV28/Y1Qm4yjGXVCwXzj1JR5DRjGDTFnxTRacUFx7Nj0ApGhN0b2+V0NOvgxQOvEW415Y22kGoblw==", "requires": { "tslib": "^1.7.1" } }, "@angular/common": { - "version": "5.2.10", - "resolved": "https://registry.npmjs.org/@angular/common/-/common-5.2.10.tgz", - "integrity": "sha512-4zgI/Q6bo/KCvrDPf8gc2IpTJ3PFKgd9RF4jZuh1uc+uEYTAj396tDF8o412AJ/iwtYOHWUx+YgzAvT8dHXZ5Q==", + "version": "5.2.11", + "resolved": "https://registry.npmjs.org/@angular/common/-/common-5.2.11.tgz", + "integrity": "sha512-LniJjGAeftUJDJh+2+LEjltcGen08C/VMxQ/eUYmesytKy1sN+MWzh3GbpKfEWtWmyUsYTG9lAAJNo3L3jPwsw==", "requires": { "tslib": "^1.7.1" } }, "@angular/compiler": { - "version": "5.2.10", - "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-5.2.10.tgz", - "integrity": "sha512-FI9ip+aWGpKQB+VfNbFQ+wyh0K4Th8Q/MrHxW6CN4BYVAfFtfORRohvyyYk0sRxuQO8JFN3W/FFfdXjuL+cZKw==", + "version": "5.2.11", + "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-5.2.11.tgz", + "integrity": "sha512-ICvB1ud1mxaXUYLb8vhJqiLhGBVocAZGxoHTglv6hMkbrRYcnlB3FZJFOzBvtj+krkd1jamoYLI43UAmesqQ6Q==", "requires": { "tslib": "^1.7.1" } }, "@angular/compiler-cli": { - "version": "5.2.10", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-5.2.10.tgz", - "integrity": "sha512-RhI26rVALRn3LrU0CAIEj56m60vLyCd8e2Ah79yRP6vlXL8k6SylXytUljTeXIBtiVu2Bi1qKGf2s1X674GzCw==", + "version": "5.2.11", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-5.2.11.tgz", + "integrity": "sha512-dwrQ0yxoCM/XzKzlm7pTsyg4/6ECjT9emZufGj8t12bLMO8NDn1IJOsqXJA1+onEgQKhlr0Ziwi+96TvDTb1Cg==", "requires": { "chokidar": "^1.4.2", "minimist": "^1.2.0", @@ -66,159 +66,151 @@ } }, "@angular/core": { - "version": "5.2.10", - "resolved": "https://registry.npmjs.org/@angular/core/-/core-5.2.10.tgz", - "integrity": "sha512-glDuTtHTcAVhfU3NVewxz/W+Iweq5IaeW2tnMa+RKLopYk9fRs8eR5iTixTGvegwKR770vfXg/gR7P6Ii5cYGQ==", + "version": "5.2.11", + "resolved": "https://registry.npmjs.org/@angular/core/-/core-5.2.11.tgz", + "integrity": "sha512-h2vpvXNAdOqKzbVaZcHnHGMT5A8uDnizk6FgGq6SPyw9s3d+/VxZ9LJaPjUk3g2lICA7og1tUel+2YfF971MlQ==", "requires": { "tslib": "^1.7.1" } }, "@angular/forms": { - "version": "5.2.10", - "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-5.2.10.tgz", - "integrity": "sha512-XQR4cd1Eey9aDT3CxQ6pbBWSBEg1408ZV/EUblKgMgt4k8PfDiuLSbF+MI/TOYAg3UkcVAxN1no4hWtkou8Rpw==", - "requires": { - "tslib": "^1.7.1" - } - }, - "@angular/http": { - "version": "5.2.10", - "resolved": "https://registry.npmjs.org/@angular/http/-/http-5.2.10.tgz", - "integrity": "sha512-euEJbxpH+pKBAwGUSo7XvNdods/kY6I4s8OUaJPUMtraQkhE6TJ0OMYvnqmGbdLimsg3ZMxqm54jCOjj9saEOQ==", + "version": "5.2.11", + "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-5.2.11.tgz", + "integrity": "sha512-wBllFlIubPclAFRXUc84Kc7TMeKOftzrQraVZ7ooTNeFLLa/FZLN2K8HGyRde8X/XDsMu1XAmjNfkz++spwTzA==", "requires": { "tslib": "^1.7.1" } }, "@angular/platform-browser": { - "version": "5.2.10", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-5.2.10.tgz", - "integrity": "sha512-oF1A0BS1wpTlxfv6YytkFCkztvvtVllnh5IUnoyV+siVT3qogKat9ZmzCmcDJ5SvIDYkY+rXBhumyFzBZ5ufFg==", + "version": "5.2.11", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-5.2.11.tgz", + "integrity": "sha512-6YZ4IpBFqXx88vEzBZG2WWnaSYXbFWDgG0iT+bZPHAfwsbmqbcMcs7Ogu+XZ4VmK02dTqbrFh7U4P2W+sqrzow==", "requires": { "tslib": "^1.7.1" } }, "@angular/platform-browser-dynamic": { - "version": "5.2.10", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-5.2.10.tgz", - "integrity": "sha512-TquhIkyR6uXfmzk6RiEl5+8Kk653Wqe4F+pKn5gFi+Z6cDm4nkDlT9kgT3e6c08JHw9fGGAvNmsalq7oS+PnNg==", + "version": "5.2.11", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-5.2.11.tgz", + "integrity": "sha512-5kKPNULcXNwkyBjpHfF+pq+Yxi8Zl866YSOK9t8txoiQ9Ctw97kMkEJcTetk6MJgBp/NP3YyjtoTAm8oXLerug==", "requires": { "tslib": "^1.7.1" } }, "@ionic-native/badge": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/@ionic-native/badge/-/badge-4.17.0.tgz", - "integrity": "sha512-Xv8SQsZzhY161C7EZ53J/iRNy8J8eOMMFz/4phi5YRmd/gbxSE6iPDdS5OUktpSHSDDHXxzofIhbh1PABAHbkA==" + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@ionic-native/badge/-/badge-4.20.0.tgz", + "integrity": "sha512-5IGGFH6Gl5sXtKg7YeyXtVBDDmD0oU/bilfGQ3MzgMsk8iochx1d+RbmnaVe4oCRJhIc47rSteYulmm+5M5hyg==" }, "@ionic-native/camera": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/@ionic-native/camera/-/camera-4.17.0.tgz", - "integrity": "sha512-C/T7yEZ11kSrF89rJwoaRa3F/ez8Wuz7Htb9jW71GBdAEt/wjegKBUDwB+ij/r8dzlBkdNWh1m6ax4NfJUhYpQ==" + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@ionic-native/camera/-/camera-4.20.0.tgz", + "integrity": "sha512-WnfQq8RV+7ezOqpCyNx9Xgpy7Y8TZehGLSxZXnCqCbFZ72CpC70Q5AV/eTIRGiKkotx2U6nUopYF+gTj1cunFA==" }, "@ionic-native/clipboard": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/@ionic-native/clipboard/-/clipboard-4.17.0.tgz", - "integrity": "sha512-spDY3NIPg+nDDYgy63NLwJWVs37d1iZMYHfzcf/8CAK+H+VeSnS3CDoXdsL9ga3Y/YuR6uItf3Q8xhUTUPgljA==" + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@ionic-native/clipboard/-/clipboard-4.20.0.tgz", + "integrity": "sha512-2rYK41wnxY1UnBAHyANb1hyGyM/hdfR5qg8QhTCCe8kQSpt7XApE5QVlxmytdtoYWNqF6/yNiXzYKrJjUpsn0A==" }, "@ionic-native/core": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/@ionic-native/core/-/core-4.11.0.tgz", - "integrity": "sha512-bBFE6bM0P254CWmqAqWe2ju7WPo51yn/vIoBrEb5SX2Iousn+1xYwuIO93n9+nlx17EI7Uz+h2mPej4Chi7hLQ==" + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@ionic-native/core/-/core-4.20.0.tgz", + "integrity": "sha512-8ppRT4zXKwtalv9HJomLQjDnMfPAiKdNUQSSKpwZePmI+8TpYRL7UNr0o0hmjiTXx1oGcKQFzHpDc1M2yeR3BA==" }, "@ionic-native/device": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/@ionic-native/device/-/device-4.17.0.tgz", - "integrity": "sha512-hboYQYlrDSI5ix0s9PDA2Ff+WO5I7lSuRnqFqg/YOmiwmTW/MeL4Fk3WVJ7ZplAqGJD2UimqDoyZXdw6tQga/g==" + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@ionic-native/device/-/device-4.20.0.tgz", + "integrity": "sha512-ogHZwlC1GLbj2sL/eRp+RDs7bWc1AuwKNhgtDLE3yjXey09I5ErkADLydugMTEYoU/Wja9+YjXdZGymuaHwgNg==" }, "@ionic-native/file": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/@ionic-native/file/-/file-4.17.0.tgz", - "integrity": "sha512-16nqDPGY/dbtJCRHIra0DskghXWHbRYfJYGsYSioMWodqoDRLyjYYEc0Z1qZXL8Ei6X1IdmgtICgjC9gnsWRiw==" + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@ionic-native/file/-/file-4.20.0.tgz", + "integrity": "sha512-iIq/wXTw1vLMB/19dhDQEHCB3SwfiwU9X8HivM6pEvW5eFr2BkR371/1KF2LcmSDNjctG4HvrjTnRpH1uKfUfQ==" }, "@ionic-native/file-opener": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/@ionic-native/file-opener/-/file-opener-4.17.0.tgz", - "integrity": "sha512-yqdOLqEr2yGqdSvp01oxi8sr5hriySHWYG8treNaGQxoZfeCN0aAFNR8ZgAWmeH2KWiLSvPNbQx+Ii15r+TKbA==" + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@ionic-native/file-opener/-/file-opener-4.20.0.tgz", + "integrity": "sha512-LN7aCikXQ4Bbikmn2vRjCdnNMu3cp7Nf8wlDP/qy/qN7SChTB7lvstoeCZKNYj4EFmTkE75179WCsv1CexGOpA==" }, "@ionic-native/file-transfer": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/@ionic-native/file-transfer/-/file-transfer-4.17.0.tgz", - "integrity": "sha512-GtCnjCMH02r5BLONYkPjcYDAXSHmrO9t8jPY5LoI29bx2B6vwX+NFBARHPNSFPEwRi1zZKs/J0uJ1hkhDzOvxg==" + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@ionic-native/file-transfer/-/file-transfer-4.20.0.tgz", + "integrity": "sha512-eZymJgIXQ4Vh8YaoHRKBJYM/1ynGYeQvLOvQks+yvlwp2tzVh4q1IVjvvA2O50zGRVFHGgdyuG4YDMWMZEZzdw==" }, "@ionic-native/geolocation": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/@ionic-native/geolocation/-/geolocation-4.17.0.tgz", - "integrity": "sha512-VgMOAP/Kjnpx4cwufFbx/DeoSoERfHt7X+I5aiOVo3J9w2rICUK3jyEccWVP0FPAfcIMrJoofpFK88TeY7O+tg==" + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@ionic-native/geolocation/-/geolocation-4.20.0.tgz", + "integrity": "sha512-2w/KpWuXVDqGMSDLhBut0Domy3yGolpPTvff/8xP8HGxxf1bcEKQLfoY68viOSuTPt7sg33rWWAKHf2/YOI6Lg==" }, "@ionic-native/globalization": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/@ionic-native/globalization/-/globalization-4.17.0.tgz", - "integrity": "sha512-H1fbQil4psgN5QVo+NDXd+nRKFDMPDWWijtVHyoIbVqwFhChHc3aZknrMu7trlfX0RFZxeZYUciFGfj10TfBdA==" + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@ionic-native/globalization/-/globalization-4.20.0.tgz", + "integrity": "sha512-zyxaW+vZb1OHeDgGbrZHQe3hy30K4YeKjGr8KNGcwq+k2ZHkfqo/H6XIwf2m/UlFTgacvdR9XZtfP+6N0suybg==" }, "@ionic-native/in-app-browser": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/@ionic-native/in-app-browser/-/in-app-browser-4.17.0.tgz", - "integrity": "sha512-iEOSyhpxj0btN5S9x86+/d2Zq28OxnNvP/jqOADZnolAdVeyDuDg6dORUbywoCGAf5PKUOXDbOFKGdyJAEV5EA==" + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@ionic-native/in-app-browser/-/in-app-browser-4.20.0.tgz", + "integrity": "sha512-y04sEK2ssb0ipeZTB3HPjVpGTYJUkPupjrAPx9hvbzWB0g1CHRWcsnQHezxItGz/jCQzUAKOjxQZ23oy8Qf4Bg==" }, "@ionic-native/keyboard": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/@ionic-native/keyboard/-/keyboard-4.17.0.tgz", - "integrity": "sha512-2BHO1bV4mehWZNfdsWQ/uojxYFNvk4I6u0KYnNb61RiJRY83joCEw3oFkOMRGLZthPf6TN1cueZUIAGMHXA3nA==" + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@ionic-native/keyboard/-/keyboard-4.20.0.tgz", + "integrity": "sha512-GA720A+sFvYVZ/84PaZQqm+zcUu7gS/DRKNU9UGwhBdjQUmUn15EaFF+vuyxLigUfJ0YRxmAOcBDBEiIL9pDaQ==" }, "@ionic-native/local-notifications": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/@ionic-native/local-notifications/-/local-notifications-4.17.0.tgz", - "integrity": "sha512-NGLGtGRduRU3f/4N2nv4hF550+NkJ9CP7mOS9vlZcZJBzlIup9X67u1M2j/+KFOpWqzS2avZ1gvZbxOmCjPNPw==" + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@ionic-native/local-notifications/-/local-notifications-4.20.0.tgz", + "integrity": "sha512-Ht/0zau8/2+G/bH/okXXhhWB6YrkCNL2QxVJHQ2dophXFGxQPOZAN3CKWhuQSjfbr76fa2nvQXF6jsXLpIR/ng==" }, "@ionic-native/media-capture": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/@ionic-native/media-capture/-/media-capture-4.17.0.tgz", - "integrity": "sha512-C3TBHxbPF1HDtt0h2Esg6ASofgorWKAbfIpUpZ6UoGFO++HJyZyrxTxPRaU37vvhO9kUcF29rweZge8XvH9OLw==" + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@ionic-native/media-capture/-/media-capture-4.20.0.tgz", + "integrity": "sha512-2jNNYwlk1JVJyMFb2p5xVDKc9pxI7o5WpkYWbac4+bZnOW/ZYtRI0at85JgQY6RZ6S0HOh/KyFEYeRXfeL86vA==" }, "@ionic-native/network": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/@ionic-native/network/-/network-4.17.0.tgz", - "integrity": "sha512-isAZx0CWIGHxl4u7GYkwkertNrSarNXhQQ4iQgqOefDJUtOj78zJsYNVxUWk1MiqTyQrnV9H7+WHWf62JAR82w==" + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@ionic-native/network/-/network-4.20.0.tgz", + "integrity": "sha512-BBSeaJJEd9KpzJmgkZwti0pzWeDO3bIVyl6/+c/Ivi1ytmJI2kL0bQcukLuABv02rCeKczVuzUgL0D4dGWtLqA==" }, "@ionic-native/push": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/@ionic-native/push/-/push-4.17.0.tgz", - "integrity": "sha512-hOM7CwBbZXHq31DNrTqEVcaS/W9uZcgm/gv9iu2hMtYlVwaM3ppvCC/SQuOOaS1elyfMhM5rzXP6n3csifcjDA==" + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@ionic-native/push/-/push-4.20.0.tgz", + "integrity": "sha512-IgzaZd8KSPLwyLX1emRijlQ0Vfa3RlPPBx370lVH32c8zG3DFH1xfQQbb39KF3qmX5b6so0pGGA2holSUwVm2w==" }, "@ionic-native/screen-orientation": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/@ionic-native/screen-orientation/-/screen-orientation-4.17.0.tgz", - "integrity": "sha512-NSN5I6y8Wq3xQV/fnsZdhb7iXnIyJ6SZmCw6aVJEX3rZUy48lwr/KlC4uR3S6NStBXnuWuZjFy7PITQl93UbGQ==" + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@ionic-native/screen-orientation/-/screen-orientation-4.20.0.tgz", + "integrity": "sha512-vQaADwj93OFoRwDOJd/67pP/ESaXtfVfL8vinFpsBtlkBvouSTrjEzEht7ZrVfkTuKuDGFzvbFqB8DxLNf6mUg==" }, "@ionic-native/splash-screen": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/@ionic-native/splash-screen/-/splash-screen-4.17.0.tgz", - "integrity": "sha512-LRLkw+wyuN3WcH5aE2W6qyBa/CTFsfQU8pjPaTmgsanZXa8A0B1XcJKzCw27cAQZc5oqozWEIYQSyAtKEVP2FA==" + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@ionic-native/splash-screen/-/splash-screen-4.20.0.tgz", + "integrity": "sha512-Fubxp7mexLrkfW3BC0433IXQELKhRMB/fETPtG8BKcMiETp03Xu0xNSGpZceBhs2aDjZQhE57tcwwEzM+oHLTA==" }, "@ionic-native/sqlite": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/@ionic-native/sqlite/-/sqlite-4.17.0.tgz", - "integrity": "sha512-6Nkmecor6Qs9+hKSl2MfgDcjretV9SeWX2TxTOvm2KXumFEOXp7uv7kilz/epKVyybfkFKJQ5n77zsVQByFTbg==" + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@ionic-native/sqlite/-/sqlite-4.20.0.tgz", + "integrity": "sha512-kxQ+AQ+8yZitIjxoQ/Ke2dCeaprlDf/G8zV0uj3pN4c/F68d6OW2v2eHpuY6Ae2QGI0WSmEdQRgsjm3hjaHy2A==" }, "@ionic-native/status-bar": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/@ionic-native/status-bar/-/status-bar-4.17.0.tgz", - "integrity": "sha512-lFlhDRNbErvJorHl0qUzRCEmmGd2j8LHpvNZJLwD3IvKI4v+IgethSCA+R5V0pmOpN+aVSLDUgr3aJW439en0w==" + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@ionic-native/status-bar/-/status-bar-4.20.0.tgz", + "integrity": "sha512-y/Sqo4c0rQcLVhF/mj72wcn2ReKOW5SxBOD5I7YILNfXvEpDlUgWUxkTM5gNZn3ecCbYFrypNXIn2tsxZBAd+w==" }, "@ionic-native/web-intent": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/@ionic-native/web-intent/-/web-intent-4.17.0.tgz", - "integrity": "sha512-8HOIOIuv660/mg2fCE9ccn13nS9tsMCP3+d8ciDt1lhNjnkjsdJBA0Tk/UyjnSY3CIcs260FBlp3ucFzxUZMoA==" + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@ionic-native/web-intent/-/web-intent-4.20.0.tgz", + "integrity": "sha512-oufDpyHdgfZYH7+xevDLfORV81Cd58ld0wnaZpUGSPKSwGkrQoqBccy7K6dX6k+iTK0o159ubaKys7gxpiArBg==" }, "@ionic-native/zip": { - "version": "4.17.0", - "resolved": "https://registry.npmjs.org/@ionic-native/zip/-/zip-4.17.0.tgz", - "integrity": "sha512-tv3R0fvOsGRHQO8ILKElG2DAJESsMsRJqdZ7VkvzepXu2WAYYMNIK/YNNJESy9sQWfGruq9aj94d6p0NMOdtng==" + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@ionic-native/zip/-/zip-4.20.0.tgz", + "integrity": "sha512-k21k+EcRPU/jpv4U55qP+aaDVIr/1Ta5/TU4KPuOxvWrSdqKHAObtkN3CfCNd3GoQEjvIE9T3nnXmnolq9yCTg==" }, "@ionic/app-scripts": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@ionic/app-scripts/-/app-scripts-3.2.2.tgz", - "integrity": "sha512-ZpeAafEboO/xHnmgy61ZOsaMGpoer621YX3q09x+BA2sbcuqOEsMKV91BRZmghoiHlwBB9woxXRyAwuYIAASHw==", + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/@ionic/app-scripts/-/app-scripts-3.2.3.tgz", + "integrity": "sha512-t1kL9NqY/sVLfAIVKfN4VJigPqMWs5euKxig3sjsrakSQ9z4njmbDghzAC4taefIpYxLGMOeWgKwrrxOiLkdWQ==", "dev": true, "requires": { "@angular-devkit/build-optimizer": "0.0.35", @@ -313,9 +305,9 @@ } }, "chokidar": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.1.tgz", - "integrity": "sha512-gfw3p2oQV2wEt+8VuMlNsPjCxDxvvgnm/kz+uATu805mWVF8IJN7uz9DN7iBz+RMJISmiVbCOBFs9qBGMjtPfQ==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", + "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", "dev": true, "requires": { "anymatch": "^2.0.0", @@ -329,7 +321,7 @@ "normalize-path": "^3.0.0", "path-is-absolute": "^1.0.0", "readdirp": "^2.2.1", - "upath": "^1.1.0" + "upath": "^1.1.1" } }, "expand-brackets": { @@ -483,535 +475,6 @@ } } }, - "fsevents": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.7.tgz", - "integrity": "sha512-Pxm6sI2MeBD7RdD12RYsqaP0nMiwx8eZBXCa6z2L+mRHm2DYrOYwihmhjpkdjUHwQhslWQjRpEgNq4XvBmaAuw==", - "dev": true, - "optional": true, - "requires": { - "nan": "^2.9.2", - "node-pre-gyp": "^0.10.0" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "dev": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true, - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "dev": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "debug": { - "version": "2.6.9", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ms": "2.0.0" - } - }, - "deep-extend": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "dev": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.3", - "bundled": true, - "dev": true, - "optional": true, - "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" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.24", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true, - "dev": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true, - "dev": true - }, - "minipass": { - "version": "2.3.5", - "bundled": true, - "dev": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.2.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "dev": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "needle": { - "version": "2.2.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "debug": "^2.1.2", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.10.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "optional": true - }, - "npm-packlist": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "rc": { - "version": "1.2.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.3.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.6.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true, - "dev": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "dev": true, - "optional": true - }, - "semver": { - "version": "5.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "tar": { - "version": "4.4.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.3.4", - "minizlib": "^1.1.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.2" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "wide-align": { - "version": "1.1.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "dev": true - }, - "yallist": { - "version": "3.0.3", - "bundled": true, - "dev": true - } - } - }, "glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", @@ -1069,9 +532,9 @@ "dev": true }, "is-glob": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", - "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", "dev": true, "requires": { "is-extglob": "^2.1.1" @@ -1104,9 +567,9 @@ "dev": true }, "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, "micromatch": { @@ -1146,6 +609,12 @@ "micromatch": "^3.1.10", "readable-stream": "^2.0.2" } + }, + "upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true } } }, @@ -1176,12 +645,14 @@ "@types/cordova": { "version": "0.0.34", "resolved": "https://registry.npmjs.org/@types/cordova/-/cordova-0.0.34.tgz", - "integrity": "sha1-6nrd907Ow9dimCegw54smt3HPQQ=" + "integrity": "sha1-6nrd907Ow9dimCegw54smt3HPQQ=", + "dev": true }, "@types/cordova-plugin-file": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/@types/cordova-plugin-file/-/cordova-plugin-file-4.3.2.tgz", "integrity": "sha512-kCvVcLxo2M6NxBCngW8JuFlS3YpsqE6VcH9PKnOdWwtpCBTi7WXhQU9kb0h0/TVX5Bp8ab+qLz0mm9wSkcXdXg==", + "dev": true, "requires": { "cordova-plugin-file": "*" } @@ -1190,6 +661,7 @@ "version": "0.0.3", "resolved": "https://registry.npmjs.org/@types/cordova-plugin-file-transfer/-/cordova-plugin-file-transfer-0.0.3.tgz", "integrity": "sha1-u6d+jQTQejlRR5eiA8JXxbECNoA=", + "dev": true, "requires": { "@types/cordova-plugin-file": "*" } @@ -1197,12 +669,14 @@ "@types/cordova-plugin-globalization": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/@types/cordova-plugin-globalization/-/cordova-plugin-globalization-0.0.3.tgz", - "integrity": "sha1-ySA6HENtPS0DBXiffJwrq6i6KK0=" + "integrity": "sha1-ySA6HENtPS0DBXiffJwrq6i6KK0=", + "dev": true }, "@types/cordova-plugin-network-information": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/@types/cordova-plugin-network-information/-/cordova-plugin-network-information-0.0.3.tgz", - "integrity": "sha1-+iycaufkxX8Tt39pXaTtuzr6oBY=" + "integrity": "sha1-+iycaufkxX8Tt39pXaTtuzr6oBY=", + "dev": true }, "@types/events": { "version": "3.0.0", @@ -1225,14 +699,15 @@ "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==" }, "@types/node": { - "version": "8.10.19", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.19.tgz", - "integrity": "sha512-+PU57o6DtOSx0/algmxgCwWrmCiomwC/K+LPfXonT0tQMbNTjHEqVzwL9dFEhFoPmLFIiSWjRorLH6Z0hJMT+Q==" + "version": "8.10.59", + "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.59.tgz", + "integrity": "sha512-8RkBivJrDCyPpBXhVZcjh7cQxVBSmRk9QM7hOketZzp6Tg79c0N8kkpAIito9bnJ3HCVCHVYz+KHTEbfQNfeVQ==" }, "@types/promise.prototype.finally": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@types/promise.prototype.finally/-/promise.prototype.finally-2.0.2.tgz", - "integrity": "sha512-Fs99h+iFQZ4ZY2vO3+uJCrx+5KQnJ4FPerZ3oT/1L5aA7vnmK/d7Z/Ml1yHtNCh9UQcjFTR4Xo/Jss2f39Fgtw==" + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/promise.prototype.finally/-/promise.prototype.finally-2.0.4.tgz", + "integrity": "sha512-bkzsMz11tGI5r/ZhJIVI1+QSWKKBcX/Llv0zoM2ufOH+o/3sLXwE5xCW0OUX81xPa4EGOyPKFlNrH9GH+IF4lg==", + "dev": true }, "abbrev": { "version": "1.1.1", @@ -1249,9 +724,9 @@ } }, "acorn": { - "version": "5.7.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", - "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", "dev": true }, "acorn-dynamic-import": { @@ -1272,14 +747,21 @@ } }, "ajv": { - "version": "6.10.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz", - "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==", + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.11.0.tgz", + "integrity": "sha512-nCprB/0syFYy9fVYU1ox1l2KN8S9I+tziH8D4zdZuLT3N6RMlGSGt5FSTpAiHB/Whv8Qs1cWHma1aMKZyaHRKA==", "requires": { - "fast-deep-equal": "^2.0.1", + "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" + }, + "dependencies": { + "fast-deep-equal": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz", + "integrity": "sha512-8UEa58QDLauDNfpbrX55Q9jrGHThw2ZMdOky5Gl1CDtVeJDPVrG4Jxx1N8jw2gkWaff5UUuX1KJd+9zGe2B+ZA==" + } } }, "ajv-keywords": { @@ -1297,6 +779,17 @@ "kind-of": "^3.0.2", "longest": "^1.0.1", "repeat-string": "^1.5.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "amdefine": { @@ -1516,12 +1009,6 @@ "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" }, - "array-differ": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", - "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", - "dev": true - }, "array-each": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", @@ -1538,6 +1025,11 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, + "array-ify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-ify/-/array-ify-1.0.0.tgz", + "integrity": "sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4=" + }, "array-initial": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", @@ -1874,8 +1366,7 @@ }, "kind-of": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + "resolved": "" } } }, @@ -1893,12 +1384,6 @@ "tweetnacl": "^0.14.3" } }, - "beeper": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", - "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", @@ -2346,6 +1831,16 @@ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" }, + "camel-case": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz", + "integrity": "sha1-yjw2iKTpzzpM2nd9xNy8cTJJz3M=", + "dev": true, + "requires": { + "no-case": "^2.2.0", + "upper-case": "^1.1.1" + } + }, "camelcase": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", @@ -2404,9 +1899,9 @@ "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" }, "chart.js": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.7.2.tgz", - "integrity": "sha512-90wl3V9xRZ8tnMvMlpcW+0Yg13BelsGS9P9t0ClaDxv/hdypHDr/YAGf+728m11P5ljwyB0ZHfPKCapZFqSqYA==", + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-2.9.3.tgz", + "integrity": "sha512-+2jlOobSk52c1VU6fzkh3UwqHMdSlgH1xFv9FKMqHiNCpXsGPQa/+81AFa+i3jZ253Mq9aAycPwDjnn1XbRNNw==", "requires": { "chartjs-color": "^2.1.0", "moment": "^2.10.2" @@ -2445,6 +1940,12 @@ "readdirp": "^2.0.0" } }, + "chownr": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz", + "integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==", + "dev": true + }, "chromium-pickle-js": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", @@ -2514,12 +2015,6 @@ "restore-cursor": "^2.0.0" } }, - "cli-spinners": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-1.3.1.tgz", - "integrity": "sha512-1QL4544moEsDVH9T/l6Cemov/37iv1RtoKf7NJ04A60+4MREXNfx/QvavbH6QoGdsD4N4Mwy49cmaINR/o2mdg==", - "dev": true - }, "cli-width": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", @@ -2555,9 +2050,9 @@ "dev": true }, "cloneable-readable": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.2.tgz", - "integrity": "sha512-Bq6+4t+lbM8vhTs/Bef5c5AdEMtapp/iFb6+s4/Hh9MVTt8OLKH7ZOOZSCT+Ys7hsHvqv0GuMPJ1lnQJVHvxpg==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", + "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", "dev": true, "requires": { "inherits": "^2.0.1", @@ -2624,12 +2119,6 @@ "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", "dev": true }, - "colors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.0.tgz", - "integrity": "sha512-EDpX3a7wHMWFA7PUHWPHNWqOxIIRSJetuwl0AS5Oi/5FMV8kWm69RTlgm00GKjBO1xFHMtBbL49yRtMMdticBw==", - "dev": true - }, "com-darryncampbell-cordova-plugin-intent": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/com-darryncampbell-cordova-plugin-intent/-/com-darryncampbell-cordova-plugin-intent-1.3.0.tgz", @@ -2649,6 +2138,25 @@ "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", "dev": true }, + "compare-func": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/compare-func/-/compare-func-1.3.2.tgz", + "integrity": "sha1-md0LpFfh+bxyKxLAjsM+6rMfpkg=", + "requires": { + "array-ify": "^1.0.0", + "dot-prop": "^3.0.0" + }, + "dependencies": { + "dot-prop": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-3.0.0.tgz", + "integrity": "sha1-G3CK8JSknJoOfbyteQq6U52sEXc=", + "requires": { + "is-obj": "^1.0.0" + } + } + } + }, "compare-version": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/compare-version/-/compare-version-0.1.2.tgz", @@ -2847,16 +2355,16 @@ } }, "cordova-android": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/cordova-android/-/cordova-android-8.0.0.tgz", - "integrity": "sha512-Ipv8HbVJpxEyYFSFLTEOaLRp0yxBtJVNbgSuDEB4naa34FzQaRWSNiiMcPJnO+x3hRXNt7pcwa46hARNzhn7+w==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/cordova-android/-/cordova-android-8.1.0.tgz", + "integrity": "sha512-eAY6g9q3raJ4P03wNdSWC5MOW1EfxoomWNXsPhi7T6Q9yAqmxqn0sLEUjLL1Ib0LCH3nKQWBXdxapQ5LgbHu+g==", "requires": { - "android-versions": "^1.3.0", - "cordova-common": "^3.1.0", - "elementtree": "^0.1.7", + "android-versions": "^1.4.0", + "compare-func": "^1.3.2", + "cordova-common": "^3.2.0", "nopt": "^4.0.1", "properties-parser": "^0.3.1", - "q": "^1.4.1", + "q": "^1.5.1", "shelljs": "^0.5.3" }, "dependencies": { @@ -3035,15 +2543,16 @@ } }, "cordova-ios": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/cordova-ios/-/cordova-ios-5.0.1.tgz", - "integrity": "sha512-JcFyDmlyzY2OQJo0eHpuFERFqvO4daHl8HL96RhUVjJVtuoqXHsOF0xTuQSAqIbefelMPEWwY3Lc/dvT4ttTwQ==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/cordova-ios/-/cordova-ios-5.1.1.tgz", + "integrity": "sha512-asZMCj44JMe/PMrDIRC97GStPCH+GpaMNVe8hdmu8WWXJzMzRNRRJ339YYU89jitWf9ZKMdyBgrnSnQi5bJ/ZQ==", "requires": { "cordova-common": "^3.1.0", "ios-sim": "^8.0.1", "nopt": "^4.0.1", "plist": "^3.0.1", "q": "^1.5.1", + "semver": "^6.3.0", "shelljs": "^0.5.3", "unorm": "^1.4.1", "xcode": "^2.0.0", @@ -3058,6 +2567,11 @@ "abbrev": "1", "osenv": "^0.1.4" } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" } } }, @@ -3117,9 +2631,9 @@ "integrity": "sha512-fCLhWjWYn49q3X5xaypAPgTz6MAWSKFFQvD2Gpi5SuVlrRPRphtX2jIqR2zCBuDTBR082QVnlc+yUDXt65Mjgw==" }, "cordova-plugin-customurlscheme": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/cordova-plugin-customurlscheme/-/cordova-plugin-customurlscheme-4.4.0.tgz", - "integrity": "sha512-7VPJnNfvfZQSU1IdhJX7BpDgvC7bEe+Kfg9Cj8guSoZDcTi378qQFb6VOwthT8hwGXx2bZzWf0qnTZdRlLQy+Q==" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cordova-plugin-customurlscheme/-/cordova-plugin-customurlscheme-5.0.0.tgz", + "integrity": "sha512-AuVlQyq88cCxGyoYvQyx2N6igLZ5aHxEqGch2vdu1EWvxHzsjj33VFgA3+tjzJUmYYgIN4QYTkyOVsLIMozIPg==" }, "cordova-plugin-device": { "version": "2.0.3", @@ -3132,9 +2646,9 @@ "integrity": "sha512-m7cughw327CjONN/qjzsTpSesLaeybksQh420/gRuSXJX5Zt9NfgsSbqqKDon6jnQ9Mm7h7imgyO2uJ34XMBtA==" }, "cordova-plugin-file-opener2": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/cordova-plugin-file-opener2/-/cordova-plugin-file-opener2-2.2.1.tgz", - "integrity": "sha512-yeN242U6T+TDlrJ5m00br+lAKsf2fHXn1u1TsDxB5fFUGINZUYLKthEctCMFkQUnURWk+Nh6tc+WtdQjY581Uw==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cordova-plugin-file-opener2/-/cordova-plugin-file-opener2-3.0.0.tgz", + "integrity": "sha512-yQcJ5coOlfkDcTfIhFJEL2A7SWtLhy50y51Cb+EEkI7Y0lP74Ec2tsMtIOhe9i8wPSoSfnDcN77Hj6CSeIjogA==" }, "cordova-plugin-file-transfer": { "version": "1.7.1", @@ -3152,9 +2666,9 @@ "integrity": "sha1-6sMVgQAphJOvowvolA5pj2HvvP4=" }, "cordova-plugin-inappbrowser": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cordova-plugin-inappbrowser/-/cordova-plugin-inappbrowser-3.1.0.tgz", - "integrity": "sha512-YqrZfYgbGTS20SFID0mrRxud312VH072QVlFonCAkPgtGg1Svy7lELOCNFd+KU7t4mVtZeTEjZPEeefvjaetwQ==" + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cordova-plugin-inappbrowser/-/cordova-plugin-inappbrowser-3.2.0.tgz", + "integrity": "sha512-tYsK0H9M8POmJTVnfyIsiRgoOxnypa9IQIbf/Hsgi7vbgUYRHtBUfvXwq4RhMqLIVrCeJLXF2hTXTDNY0a/eTA==" }, "cordova-plugin-ionic-keyboard": { "version": "2.1.3", @@ -3162,7 +2676,7 @@ "integrity": "sha512-6ucQ6FdlLdBm8kJfFnzozmBTjru/0xekHP/dAhjoCZggkGRlgs8TsUJFkxa/bV+qi7Dlo50JjmpE4UMWAO+aOQ==" }, "cordova-plugin-local-notification": { - "version": "git+https://github.com/moodlemobile/cordova-plugin-local-notification.git#c7430c4f4f8b8c82d051aea49d87da0b4f6a8b1e", + "version": "git+https://github.com/moodlemobile/cordova-plugin-local-notification.git#0bb96b757fb484553ceabf35a59802f7983a2836", "from": "git+https://github.com/moodlemobile/cordova-plugin-local-notification.git#moodle" }, "cordova-plugin-media-capture": { @@ -3213,22 +2727,24 @@ } }, "cordova-sqlite-storage": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/cordova-sqlite-storage/-/cordova-sqlite-storage-3.4.0.tgz", - "integrity": "sha512-Uavq3HulVIYXxTFCp5aafiQhYrZF0/cGlyN76RYhIftcD5IRhza9+ghhV5abJYvuGlzY+p9dM5hPcjnYxfAH+g==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cordova-sqlite-storage/-/cordova-sqlite-storage-4.0.0.tgz", + "integrity": "sha512-/n5KT3TyRAC7QRe9A4Sn7bMpdsBJ6aMmHat2PsMxFZBot45SOxbAEgfGmXtq0e7OEdVzk573sIn42bLS6lNLjQ==", "requires": { - "cordova-sqlite-storage-dependencies": "2.1.0" + "cordova-sqlite-storage-dependencies": "2.1.1" + }, + "dependencies": { + "cordova-sqlite-storage-dependencies": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/cordova-sqlite-storage-dependencies/-/cordova-sqlite-storage-dependencies-2.1.1.tgz", + "integrity": "sha512-1lV5Pg1FttjBmGO8z4gxtuA4BbPKtgTfUEh1Vx4boa41inizyxaowRyTeaaqEhi5gmYAaX8sRTABm9U/XckRFg==" + } } }, - "cordova-sqlite-storage-dependencies": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cordova-sqlite-storage-dependencies/-/cordova-sqlite-storage-dependencies-2.1.0.tgz", - "integrity": "sha512-m0cPOWPzckAqS0/e7v+xtcM+FrHrw63qgh5T91JdkXMKCK8sN9bDoqVNJHZ5E9y7sRO7liMUIDm6Dz439RYqGA==" - }, "cordova-support-google-services": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/cordova-support-google-services/-/cordova-support-google-services-1.2.1.tgz", - "integrity": "sha512-EnFjKAE9oI2uzyUvEfWpLgTM200nuJVvShaA4yyz9wMKBUN+H/BRG1byd1ibZz3sSihNKi3FxjQPxmmEn6/IfA==" + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/cordova-support-google-services/-/cordova-support-google-services-1.3.2.tgz", + "integrity": "sha512-RtEWzULreUX662MFWopGhFispLiHX7gUf2GijPOC2mY2oCNuUobj2mO4tl5q7PYbOreSxq+PrSekhmS6TAAWdw==" }, "core-js": { "version": "2.3.0", @@ -3357,12 +2873,6 @@ "integrity": "sha1-6vQ5/U1ISK105cx9vvIAZyueNFs=", "dev": true }, - "dateformat": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", - "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=", - "dev": true - }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -3420,6 +2930,23 @@ "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=", "dev": true }, + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "dev": true, + "requires": { + "clone": "^1.0.2" + }, + "dependencies": { + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", + "dev": true + } + } + }, "define-properties": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", @@ -3468,11 +2995,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" } } }, @@ -3652,41 +3174,6 @@ "dotenv-defaults": "^1.0.2" } }, - "duplexer2": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", - "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", - "dev": true, - "requires": { - "readable-stream": "~1.1.9" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - } - } - }, "duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", @@ -3904,41 +3391,384 @@ } }, "electron-rebuild": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/electron-rebuild/-/electron-rebuild-1.8.1.tgz", - "integrity": "sha512-aCNKjlHyY4yqmavth/BVbmYhuL/3hrLIhg5P0fIQzGrlFA4V9TmA8FTlhjq4fT+FgDOynPBhHS5ZWLMU6ZUh3A==", + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/electron-rebuild/-/electron-rebuild-1.10.0.tgz", + "integrity": "sha512-n10i30GJg7JH8yZL3ZY3x80YtKmSYuuN8cl+3Feljm+sQDU4rUW1jbnYGu0eUHlK3kPOiNWPtW7srGcwZ9p1zQ==", "dev": true, "requires": { - "colors": "^1.2.0", - "debug": "^2.6.3", + "colors": "^1.3.3", + "debug": "^4.1.1", "detect-libc": "^1.0.3", - "fs-extra": "^3.0.1", - "node-abi": "^2.0.0", - "node-gyp": "^3.6.0", - "ora": "^1.2.0", - "rimraf": "^2.6.1", - "spawn-rx": "^2.0.10", - "yargs": "^7.0.2" + "fs-extra": "^8.1.0", + "node-abi": "^2.11.0", + "node-gyp": "^6.0.1", + "ora": "^3.4.0", + "spawn-rx": "^3.0.0", + "yargs": "^14.2.0" }, "dependencies": { - "fs-extra": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-3.0.1.tgz", - "integrity": "sha1-N5TzeMWLNC6n27sjCVEJxLO2IpE=", + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^3.0.0", + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "cli-spinners": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.2.0.tgz", + "integrity": "sha512-tgU3fKwzYjiLEQgPMD9Jt+JjHVL9kW93FiIMX/l7rivvOD4/LL0Mf7gda3+4U2KJBloybwgj5KEoQgGRioMiKQ==", + "dev": true + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "dev": true + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "env-paths": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.0.tgz", + "integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "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==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", "universalify": "^0.1.0" } }, - "jsonfile": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-3.0.1.tgz", - "integrity": "sha1-pezG9l9T9mLEQVx2daAzHQmS7GY=", + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "requires": { - "graceful-fs": "^4.1.6" + "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" + } + }, + "graceful-fs": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node-abi": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.14.0.tgz", + "integrity": "sha512-y54KGgEOHnRHlGQi7E5UiryRkH8bmksmQLj/9iLAjoje743YS+KaKB/sDYXgqtT0J16JT3c3AYJZNI98aU/kYg==", + "dev": true, + "requires": { + "semver": "^5.4.1" + } + }, + "node-gyp": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-6.1.0.tgz", + "integrity": "sha512-h4A2zDlOujeeaaTx06r4Vy+8MZ1679lU+wbCKDS4ZtvY2A37DESo37oejIw0mtmR3+rvNwts5B6Kpt1KrNYdNw==", + "dev": true, + "requires": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.2", + "mkdirp": "^0.5.1", + "nopt": "^4.0.1", + "npmlog": "^4.1.2", + "request": "^2.88.0", + "rimraf": "^2.6.3", + "semver": "^5.7.1", + "tar": "^4.4.12", + "which": "^1.3.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "nopt": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.1.tgz", + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", + "dev": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "ora": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-3.4.0.tgz", + "integrity": "sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==", + "dev": true, + "requires": { + "chalk": "^2.4.2", + "cli-cursor": "^2.1.0", + "cli-spinners": "^2.0.0", + "log-symbols": "^2.2.0", + "strip-ansi": "^5.2.0", + "wcwidth": "^1.0.1" + } + }, + "p-limit": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.2.tgz", + "integrity": "sha512-WGR+xHecKTr7EbUEhyLSh5Dube9JtdiG78ufaeLxTgpudf/20KqyMioIUZJAezlTIi6evxuoUs9YXc11cU+yzQ==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "rxjs": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", + "integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==", + "dev": true, + "requires": { + "tslib": "^1.9.0" + } + }, + "spawn-rx": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/spawn-rx/-/spawn-rx-3.0.0.tgz", + "integrity": "sha512-dw4Ryg/KMNfkKa5ezAR5aZe9wNwPdKlnHEXtHOjVnyEDSPQyOpIPPRtcIiu7127SmtHhaCjw21yC43HliW0iIg==", + "dev": true, + "requires": { + "debug": "^2.5.1", + "lodash.assign": "^4.2.0", + "rxjs": "^6.3.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "tar": { + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", + "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", + "dev": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "yargs": { + "version": "14.2.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.2.tgz", + "integrity": "sha512-/4ld+4VV5RnrynMhPZJ/ZpOCGSCeghMykZ3BhdFBDa9Wy/RH6uEGNWDJog+aUlq+9OM1CFTgtYRW5Is1Po9NOA==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "decamelize": "^1.2.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^15.0.0" + } + }, + "yargs-parser": { + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.0.tgz", + "integrity": "sha512-xLTUnCMc4JhxrPEPUYD5IBR1mWCK/aT6+RJ/K29JY2y1vD+FhtgKK0AXRWvI262q3QSffAQuTouFIKUuHX89wQ==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" } } } @@ -3979,6 +3809,12 @@ "minimalistic-crypto-utils": "^1.0.0" } }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, "emojis-list": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", @@ -4394,11 +4230,6 @@ "time-stamp": "^1.0.0" } }, - "fast-deep-equal": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", - "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=" - }, "fast-glob": { "version": "2.2.7", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz", @@ -4668,9 +4499,9 @@ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" }, "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" }, "micromatch": { "version": "3.1.10", @@ -5037,9 +4868,9 @@ "dev": true }, "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, "micromatch": { @@ -5192,6 +5023,15 @@ } } }, + "fs-minipass": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "dev": true, + "requires": { + "minipass": "^2.6.0" + } + }, "fs-mkdirp-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz", @@ -5219,21 +5059,25 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "optional": true }, "ansi-regex": { "version": "2.1.1", - "bundled": true + "resolved": false, + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "aproba": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "optional": true }, "are-we-there-yet": { "version": "1.1.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "optional": true, "requires": { "delegates": "^1.0.0", @@ -5242,11 +5086,13 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true + "resolved": false, + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, "brace-expansion": { "version": "1.1.11", - "bundled": true, + "resolved": false, + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5254,29 +5100,35 @@ }, "chownr": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", "optional": true }, "code-point-at": { "version": "1.1.0", - "bundled": true + "resolved": false, + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "concat-map": { "version": "0.0.1", - "bundled": true + "resolved": false, + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "console-control-strings": { "version": "1.1.0", - "bundled": true + "resolved": false, + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "core-util-is": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "optional": true }, "debug": { "version": "4.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "optional": true, "requires": { "ms": "^2.1.1" @@ -5284,22 +5136,26 @@ }, "deep-extend": { "version": "0.6.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "optional": true }, "delegates": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "optional": true }, "detect-libc": { "version": "1.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "optional": true }, "fs-minipass": { "version": "1.2.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "optional": true, "requires": { "minipass": "^2.2.1" @@ -5307,12 +5163,14 @@ }, "fs.realpath": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "optional": true }, "gauge": { "version": "2.7.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "optional": true, "requires": { "aproba": "^1.0.3", @@ -5327,7 +5185,8 @@ }, "glob": { "version": "7.1.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "optional": true, "requires": { "fs.realpath": "^1.0.0", @@ -5340,12 +5199,14 @@ }, "has-unicode": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "optional": true }, "iconv-lite": { "version": "0.4.24", - "bundled": true, + "resolved": false, + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "optional": true, "requires": { "safer-buffer": ">= 2.1.2 < 3" @@ -5353,7 +5214,8 @@ }, "ignore-walk": { "version": "3.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "optional": true, "requires": { "minimatch": "^3.0.4" @@ -5361,7 +5223,8 @@ }, "inflight": { "version": "1.0.6", - "bundled": true, + "resolved": false, + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "optional": true, "requires": { "once": "^1.3.0", @@ -5370,39 +5233,46 @@ }, "inherits": { "version": "2.0.3", - "bundled": true + "resolved": false, + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" }, "ini": { "version": "1.3.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "requires": { "number-is-nan": "^1.0.0" } }, "isarray": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "optional": true }, "minimatch": { "version": "3.0.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { "brace-expansion": "^1.1.7" } }, "minimist": { "version": "0.0.8", - "bundled": true + "resolved": false, + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" }, "minipass": { "version": "2.3.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -5410,7 +5280,8 @@ }, "minizlib": { "version": "1.2.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", "optional": true, "requires": { "minipass": "^2.2.1" @@ -5418,14 +5289,16 @@ }, "mkdirp": { "version": "0.5.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "requires": { "minimist": "0.0.8" } }, "ms": { "version": "2.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "optional": true }, "nan": { @@ -5436,7 +5309,8 @@ }, "needle": { "version": "2.3.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==", "optional": true, "requires": { "debug": "^4.1.0", @@ -5446,7 +5320,8 @@ }, "node-pre-gyp": { "version": "0.12.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", "optional": true, "requires": { "detect-libc": "^1.0.2", @@ -5463,7 +5338,8 @@ }, "nopt": { "version": "4.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "optional": true, "requires": { "abbrev": "1", @@ -5472,12 +5348,14 @@ }, "npm-bundled": { "version": "1.0.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", "optional": true }, "npm-packlist": { "version": "1.4.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", "optional": true, "requires": { "ignore-walk": "^3.0.1", @@ -5486,7 +5364,8 @@ }, "npmlog": { "version": "4.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "optional": true, "requires": { "are-we-there-yet": "~1.1.2", @@ -5497,33 +5376,39 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true + "resolved": false, + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "object-assign": { "version": "4.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "optional": true }, "once": { "version": "1.4.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { "wrappy": "1" } }, "os-homedir": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "optional": true }, "os-tmpdir": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "optional": true }, "osenv": { "version": "0.1.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "optional": true, "requires": { "os-homedir": "^1.0.0", @@ -5532,17 +5417,20 @@ }, "path-is-absolute": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "optional": true }, "process-nextick-args": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "optional": true }, "rc": { "version": "1.2.8", - "bundled": true, + "resolved": false, + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "optional": true, "requires": { "deep-extend": "^0.6.0", @@ -5553,14 +5441,16 @@ "dependencies": { "minimist": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "optional": true } } }, "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "optional": true, "requires": { "core-util-is": "~1.0.0", @@ -5574,7 +5464,8 @@ }, "rimraf": { "version": "2.6.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "optional": true, "requires": { "glob": "^7.1.3" @@ -5582,36 +5473,43 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true + "resolved": false, + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, "safer-buffer": { "version": "2.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "optional": true }, "sax": { "version": "1.2.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "optional": true }, "semver": { "version": "5.7.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", "optional": true }, "set-blocking": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "optional": true }, "signal-exit": { "version": "3.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "optional": true }, "string-width": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5620,7 +5518,8 @@ }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "optional": true, "requires": { "safe-buffer": "~5.1.0" @@ -5628,19 +5527,22 @@ }, "strip-ansi": { "version": "3.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "requires": { "ansi-regex": "^2.0.0" } }, "strip-json-comments": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "optional": true }, "tar": { "version": "4.4.8", - "bundled": true, + "resolved": false, + "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", "optional": true, "requires": { "chownr": "^1.1.1", @@ -5654,12 +5556,14 @@ }, "util-deprecate": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "optional": true }, "wide-align": { "version": "1.1.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "optional": true, "requires": { "string-width": "^1.0.2 || 2" @@ -5667,11 +5571,13 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true + "resolved": false, + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "yallist": { "version": "3.0.3", - "bundled": true + "resolved": false, + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==" } } }, @@ -6091,24 +5997,28 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true, "optional": true }, "ansi-regex": { "version": "2.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, "aproba": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true, "optional": true }, "are-we-there-yet": { "version": "1.1.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "dev": true, "optional": true, "requires": { @@ -6118,12 +6028,14 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, "brace-expansion": { "version": "1.1.11", - "bundled": true, + "resolved": false, + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "requires": { "balanced-match": "^1.0.0", @@ -6132,34 +6044,40 @@ }, "chownr": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", "dev": true, "optional": true }, "code-point-at": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true }, "concat-map": { "version": "0.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "dev": true }, "core-util-is": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true, "optional": true }, "debug": { "version": "4.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "optional": true, "requires": { @@ -6168,25 +6086,29 @@ }, "deep-extend": { "version": "0.6.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true, "optional": true }, "delegates": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "dev": true, "optional": true }, "detect-libc": { "version": "1.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "dev": true, "optional": true }, "fs-minipass": { "version": "1.2.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "dev": true, "optional": true, "requires": { @@ -6195,13 +6117,15 @@ }, "fs.realpath": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true, "optional": true }, "gauge": { "version": "2.7.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "dev": true, "optional": true, "requires": { @@ -6217,7 +6141,8 @@ }, "glob": { "version": "7.1.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "optional": true, "requires": { @@ -6231,13 +6156,15 @@ }, "has-unicode": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "dev": true, "optional": true }, "iconv-lite": { "version": "0.4.24", - "bundled": true, + "resolved": false, + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, "optional": true, "requires": { @@ -6246,7 +6173,8 @@ }, "ignore-walk": { "version": "3.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "dev": true, "optional": true, "requires": { @@ -6255,7 +6183,8 @@ }, "inflight": { "version": "1.0.6", - "bundled": true, + "resolved": false, + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "optional": true, "requires": { @@ -6265,18 +6194,21 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, "ini": { "version": "1.3.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "dev": true, "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "requires": { "number-is-nan": "^1.0.0" @@ -6284,13 +6216,15 @@ }, "isarray": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true, "optional": true }, "minimatch": { "version": "3.0.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -6298,12 +6232,14 @@ }, "minimist": { "version": "0.0.8", - "bundled": true, + "resolved": false, + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, "minipass": { "version": "2.3.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "dev": true, "requires": { "safe-buffer": "^5.1.2", @@ -6312,7 +6248,8 @@ }, "minizlib": { "version": "1.2.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", "dev": true, "optional": true, "requires": { @@ -6321,7 +6258,8 @@ }, "mkdirp": { "version": "0.5.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { "minimist": "0.0.8" @@ -6329,13 +6267,15 @@ }, "ms": { "version": "2.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", "dev": true, "optional": true }, "needle": { "version": "2.3.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-QBZu7aAFR0522EyaXZM0FZ9GLpq6lvQ3uq8gteiDUp7wKdy0lSd2hPlgFwVuW1CBkfEs9PfDQsQzZghLs/psdg==", "dev": true, "optional": true, "requires": { @@ -6346,7 +6286,8 @@ }, "node-pre-gyp": { "version": "0.12.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-4KghwV8vH5k+g2ylT+sLTjy5wmUOb9vPhnM8NHvRf9dHmnW/CndrFXy2aRPaPST6dugXSdHXfeaHQm77PIz/1A==", "dev": true, "optional": true, "requires": { @@ -6364,7 +6305,8 @@ }, "nopt": { "version": "4.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "dev": true, "optional": true, "requires": { @@ -6374,13 +6316,15 @@ }, "npm-bundled": { "version": "1.0.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g==", "dev": true, "optional": true }, "npm-packlist": { "version": "1.4.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw==", "dev": true, "optional": true, "requires": { @@ -6390,7 +6334,8 @@ }, "npmlog": { "version": "4.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "dev": true, "optional": true, "requires": { @@ -6402,18 +6347,21 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true }, "object-assign": { "version": "4.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true, "optional": true }, "once": { "version": "1.4.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { "wrappy": "1" @@ -6421,19 +6369,22 @@ }, "os-homedir": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true, "optional": true }, "os-tmpdir": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true, "optional": true }, "osenv": { "version": "0.1.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "dev": true, "optional": true, "requires": { @@ -6443,19 +6394,22 @@ }, "path-is-absolute": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true, "optional": true }, "process-nextick-args": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "dev": true, "optional": true }, "rc": { "version": "1.2.8", - "bundled": true, + "resolved": false, + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, "optional": true, "requires": { @@ -6467,7 +6421,8 @@ "dependencies": { "minimist": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true, "optional": true } @@ -6475,7 +6430,8 @@ }, "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "optional": true, "requires": { @@ -6490,7 +6446,8 @@ }, "rimraf": { "version": "2.6.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "dev": true, "optional": true, "requires": { @@ -6499,42 +6456,49 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, "safer-buffer": { "version": "2.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true, "optional": true }, "sax": { "version": "1.2.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true, "optional": true }, "semver": { "version": "5.7.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==", "dev": true, "optional": true }, "set-blocking": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true, "optional": true }, "signal-exit": { "version": "3.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true, "optional": true }, "string-width": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { "code-point-at": "^1.0.0", @@ -6544,7 +6508,8 @@ }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "optional": true, "requires": { @@ -6553,7 +6518,8 @@ }, "strip-ansi": { "version": "3.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { "ansi-regex": "^2.0.0" @@ -6561,13 +6527,15 @@ }, "strip-json-comments": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true, "optional": true }, "tar": { "version": "4.4.8", - "bundled": true, + "resolved": false, + "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", "dev": true, "optional": true, "requires": { @@ -6582,13 +6550,15 @@ }, "util-deprecate": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true, "optional": true }, "wide-align": { "version": "1.1.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "dev": true, "optional": true, "requires": { @@ -6597,12 +6567,14 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, "yallist": { "version": "3.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", "dev": true } } @@ -6700,8 +6672,7 @@ }, "kind-of": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "resolved": "", "dev": true }, "micromatch": { @@ -6824,9 +6795,9 @@ } }, "globule": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.1.tgz", - "integrity": "sha512-g7QtgWF4uYSL5/dn71WxubOrS7JVGCnFPEnoeChJmBnyR9Mw8nGoEwOgJL/RC2Te0WhbsEUCejfH8SZNJ+adYQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.0.tgz", + "integrity": "sha512-YlD4kdMqRCQHrhVdonet4TdRtv1/sZKepvoxNT4Nrhrp5HI8XFfc8kFlGlBn2myBo80aGp8Eft259mbcUJhgSg==", "dev": true, "requires": { "glob": "~7.1.1", @@ -6942,10 +6913,41 @@ "through2": "^2.0.0" } }, + "gulp-htmlmin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/gulp-htmlmin/-/gulp-htmlmin-5.0.1.tgz", + "integrity": "sha512-ASlyDPZOSKjHYUifYV0rf9JPDflN9IRIb8lw2vRqtYMC4ljU3zAmnnaVXwFQ3H+CfXxZSUesZ2x7jrnPJu93jA==", + "dev": true, + "requires": { + "html-minifier": "^3.5.20", + "plugin-error": "^1.0.1", + "through2": "^2.0.3" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "plugin-error": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/plugin-error/-/plugin-error-1.0.1.tgz", + "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", + "dev": true, + "requires": { + "ansi-colors": "^1.0.1", + "arr-diff": "^4.0.0", + "arr-union": "^3.1.0", + "extend-shallow": "^3.0.2" + } + } + } + }, "gulp-rename": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-1.3.0.tgz", - "integrity": "sha512-nEuZB7/9i0IZ8AXORTizl2QLP9tcC9uWc/s329zElBLJw1CfOhmMXBxwVlCRKjDyrWuhVP0uBKl61KeQ32TiCg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/gulp-rename/-/gulp-rename-2.0.0.tgz", + "integrity": "sha512-97Vba4KBzbYmR5VBs9mWmK+HwIf5mj+/zioxfZhOKeXtx5ZjBk57KFlePf5nxq9QsTtFl0ejnHE3zTC9MHXqyQ==", "dev": true }, "gulp-slash": { @@ -7000,121 +7002,6 @@ } } }, - "gulp-util": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", - "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", - "dev": true, - "requires": { - "array-differ": "^1.0.0", - "array-uniq": "^1.0.2", - "beeper": "^1.0.0", - "chalk": "^1.0.0", - "dateformat": "^2.0.0", - "fancy-log": "^1.1.0", - "gulplog": "^1.0.0", - "has-gulplog": "^0.1.0", - "lodash._reescape": "^3.0.0", - "lodash._reevaluate": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.template": "^3.0.0", - "minimist": "^1.1.0", - "multipipe": "^0.1.2", - "object-assign": "^3.0.0", - "replace-ext": "0.0.1", - "through2": "^2.0.0", - "vinyl": "^0.5.0" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=", - "dev": true - }, - "clone-stats": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", - "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", - "dev": true - }, - "lodash.template": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", - "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", - "dev": true, - "requires": { - "lodash._basecopy": "^3.0.0", - "lodash._basetostring": "^3.0.0", - "lodash._basevalues": "^3.0.0", - "lodash._isiterateecall": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0", - "lodash.keys": "^3.0.0", - "lodash.restparam": "^3.0.0", - "lodash.templatesettings": "^3.0.0" - } - }, - "lodash.templatesettings": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", - "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", - "dev": true, - "requires": { - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0" - } - }, - "object-assign": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", - "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", - "dev": true - }, - "replace-ext": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", - "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", - "dev": true - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - }, - "vinyl": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", - "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", - "dev": true, - "requires": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", - "replace-ext": "0.0.1" - } - } - } - }, "gulplog": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", @@ -7183,15 +7070,6 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, - "has-gulplog": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", - "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", - "dev": true, - "requires": { - "sparkles": "^1.0.0" - } - }, "has-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz", @@ -7278,6 +7156,12 @@ "minimalistic-assert": "^1.0.1" } }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, "hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -7303,6 +7187,47 @@ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz", "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==" }, + "html-minifier": { + "version": "3.5.21", + "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.21.tgz", + "integrity": "sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA==", + "dev": true, + "requires": { + "camel-case": "3.0.x", + "clean-css": "4.2.x", + "commander": "2.17.x", + "he": "1.2.x", + "param-case": "2.1.x", + "relateurl": "0.2.x", + "uglify-js": "3.4.x" + }, + "dependencies": { + "commander": { + "version": "2.17.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", + "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", + "dev": true + }, + "uglify-js": { + "version": "3.4.10", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz", + "integrity": "sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==", + "dev": true, + "requires": { + "commander": "~2.19.0", + "source-map": "~0.6.1" + }, + "dependencies": { + "commander": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", + "dev": true + } + } + } + } + }, "http-errors": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", @@ -7598,9 +7523,9 @@ "dev": true }, "ionic-angular": { - "version": "3.9.3", - "resolved": "https://registry.npmjs.org/ionic-angular/-/ionic-angular-3.9.3.tgz", - "integrity": "sha512-EOuz9nyu0lV4KbqN+HipXuhoMRMDfprZeckg7H1Z+9AZpK9d7VzaiweyRNG5iaLp+cGCzBIQCfWhwlCo2vyuUA==" + "version": "3.9.9", + "resolved": "https://registry.npmjs.org/ionic-angular/-/ionic-angular-3.9.9.tgz", + "integrity": "sha512-XJGWbBrLEPRZDAPDOv0mFUHuAkqld/YqepPEBi9j6z235gbfX1CJL5A4xciCq0UMh5UDPlTt/CvRy8cRt9XCgQ==" }, "ionicons": { "version": "3.0.0", @@ -7659,6 +7584,16 @@ "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "requires": { "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "is-arrayish": { @@ -7707,6 +7642,16 @@ "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "requires": { "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "is-date-object": { @@ -7812,6 +7757,16 @@ "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", "requires": { "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "is-obj": { @@ -8121,12 +8076,9 @@ "dev": true }, "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "requires": { - "is-buffer": "^1.1.5" - } + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" }, "last-run": { "version": "1.1.1", @@ -8266,121 +8218,23 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" }, - "lodash._basecopy": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", - "dev": true - }, - "lodash._basetostring": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", - "integrity": "sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U=", - "dev": true - }, - "lodash._basevalues": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", - "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=", - "dev": true - }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", - "dev": true - }, - "lodash._isiterateecall": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", - "dev": true - }, - "lodash._reescape": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", - "integrity": "sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo=", - "dev": true - }, - "lodash._reevaluate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", - "integrity": "sha1-WLx0xAZklTrgsSTYBpltrKQx4u0=", - "dev": true - }, "lodash._reinterpolate": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", "dev": true }, - "lodash._root": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", - "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", - "dev": true - }, "lodash.assign": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz", "integrity": "sha1-DZnzzNem0mHRm9rrkkUAXShYCOc=", "dev": true }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=", - "dev": true - }, "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=" }, - "lodash.escape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", - "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", - "dev": true, - "requires": { - "lodash._root": "^3.0.0" - } - }, - "lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", - "dev": true - }, - "lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", - "dev": true - }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "dev": true, - "requires": { - "lodash._getnative": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" - } - }, - "lodash.mergewith": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz", - "integrity": "sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ==", - "dev": true - }, - "lodash.restparam": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", - "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", - "dev": true - }, "lodash.template": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-4.5.0.tgz", @@ -8425,6 +8279,12 @@ "signal-exit": "^3.0.0" } }, + "lower-case": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz", + "integrity": "sha1-miyr0bno4K6ZOkv31YdcOcQujqw=", + "dev": true + }, "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", @@ -8479,9 +8339,9 @@ }, "dependencies": { "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true } } @@ -8792,9 +8652,9 @@ "dev": true }, "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, "micromatch": { @@ -8921,6 +8781,16 @@ "object.omit": "^2.0.0", "parse-glob": "^3.0.4", "regex-cache": "^0.4.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "miller-rabin": { @@ -8981,6 +8851,33 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" }, + "minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + }, + "dependencies": { + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } + }, + "minizlib": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "dev": true, + "requires": { + "minipass": "^2.9.0" + } + }, "mixin-deep": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", @@ -9016,24 +8913,15 @@ } }, "moment": { - "version": "2.22.2", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz", - "integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y=" + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, - "multipipe": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", - "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=", - "dev": true, - "requires": { - "duplexer2": "0.0.2" - } - }, "mute-stdout": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/mute-stdout/-/mute-stdout-1.0.1.tgz", @@ -9049,7 +8937,8 @@ "version": "2.10.0", "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", - "dev": true + "dev": true, + "optional": true }, "nanomatch": { "version": "1.2.9", @@ -9081,9 +8970,9 @@ "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" }, "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" } } }, @@ -9114,13 +9003,13 @@ "resolved": "https://registry.npmjs.org/nl.kingsquare.cordova.background-audio/-/nl.kingsquare.cordova.background-audio-1.0.1.tgz", "integrity": "sha1-Gx1NzaijXAx/x5UzN7FzsUtPmGA=" }, - "node-abi": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.4.3.tgz", - "integrity": "sha512-b656V5C0628gOOA2kwcpNA/bxdlqYF9FvxJ+qqVX0ctdXNVZpS8J6xEUYir3WAKc7U0BH/NRlSpNbGsy+azjeg==", + "no-case": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz", + "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==", "dev": true, "requires": { - "semver": "^5.4.1" + "lower-case": "^1.1.1" } }, "node-gyp": { @@ -9357,9 +9246,9 @@ "dev": true }, "node-sass": { - "version": "4.11.0", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.11.0.tgz", - "integrity": "sha512-bHUdHTphgQJZaF1LASx0kAviPH7sGlcyNhWade4eVIpFp6tsn7SV8xNMTbsQFpEV9VXpnwTTnNYlfsZXgGgmkA==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.13.1.tgz", + "integrity": "sha512-TTWFx+ZhyDx1Biiez2nB0L3YrCZ/8oHagaDalbuBSlqXgUPsdkUSzJsVxeDO9LtPB49+Fh3WQl3slABo6AotNw==", "dev": true, "requires": { "async-foreach": "^0.1.3", @@ -9369,12 +9258,10 @@ "get-stdin": "^4.0.1", "glob": "^7.0.3", "in-publish": "^2.0.0", - "lodash.assign": "^4.2.0", - "lodash.clonedeep": "^4.3.2", - "lodash.mergewith": "^4.6.0", + "lodash": "^4.17.15", "meow": "^3.7.0", "mkdirp": "^0.5.1", - "nan": "^2.10.0", + "nan": "^2.13.2", "node-gyp": "^3.8.0", "npmlog": "^4.0.0", "request": "^2.88.0", @@ -9412,6 +9299,12 @@ "which": "^1.2.9" } }, + "nan": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", + "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "dev": true + }, "supports-color": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", @@ -9546,6 +9439,14 @@ "requires": { "is-descriptor": "^0.1.0" } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } } } }, @@ -9726,18 +9627,6 @@ "is-wsl": "^1.1.0" } }, - "ora": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ora/-/ora-1.4.0.tgz", - "integrity": "sha512-iMK1DOQxzzh2MBlVsU42G80mnrvUhqsMh74phHtDlrcTZPK0pH6o7l7DRshK+0YsxDyEuaOkziVdvM3T0QTzpw==", - "dev": true, - "requires": { - "chalk": "^2.1.0", - "cli-cursor": "^2.1.0", - "cli-spinners": "^1.0.1", - "log-symbols": "^2.1.0" - } - }, "ordered-read-streams": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", @@ -9833,6 +9722,15 @@ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==" }, + "param-case": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz", + "integrity": "sha1-35T9jPZTHs915r75oIWPvHK+Ikc=", + "dev": true, + "requires": { + "no-case": "^2.2.0" + } + }, "parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -10300,9 +10198,9 @@ "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==" }, "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" } } }, @@ -10557,6 +10455,12 @@ "rc": "^1.0.1" } }, + "relateurl": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=", + "dev": true + }, "remove-bom-buffer": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/remove-bom-buffer/-/remove-bom-buffer-3.0.0.tgz", @@ -11048,9 +10952,9 @@ "dev": true }, "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, "micromatch": { @@ -11085,9 +10989,9 @@ } }, "rxjs": { - "version": "5.5.11", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.11.tgz", - "integrity": "sha512-3bjO7UwWfA2CV7lmwYMBzj4fQ6Cq+ftHc2MvUe+WMS7wcdJ1LosDWmdjPQanYp2dBRj572p7PeU81JUxHKOcBA==", + "version": "5.5.12", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.12.tgz", + "integrity": "sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw==", "requires": { "symbol-observable": "1.0.1" } @@ -11419,8 +11323,7 @@ }, "kind-of": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + "resolved": "" } } }, @@ -11430,6 +11333,16 @@ "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", "requires": { "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "source-list-map": { @@ -11475,17 +11388,6 @@ "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==", "dev": true }, - "spawn-rx": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/spawn-rx/-/spawn-rx-2.0.12.tgz", - "integrity": "sha512-gOPXiQQFQ9lTOLuys0iMn3jfxxv9c7zzwhbYLOEbQGvEShHVJ5sSR1oD3Daj88os7jKArDYT7rbOKdvNhe7iEg==", - "dev": true, - "requires": { - "debug": "^2.5.1", - "lodash.assign": "^4.2.0", - "rxjs": "^5.1.1" - } - }, "spdx-correct": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", @@ -11969,6 +11871,16 @@ "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", "requires": { "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "to-regex": { @@ -11997,6 +11909,16 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "requires": { "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "requires": { + "is-buffer": "^1.1.5" + } + } } } } @@ -12044,9 +11966,9 @@ } }, "ts-md5": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/ts-md5/-/ts-md5-1.2.4.tgz", - "integrity": "sha512-oW+rNjc9CAhalPFzbPWsLqPLzdNcJ8iSm+OXO+Uv+99r3PzCJuM5sVc0bO1eS+4LD2xv+nfU7ylBdwoemUV9Yw==" + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/ts-md5/-/ts-md5-1.2.7.tgz", + "integrity": "sha512-emODogvKGWi1KO1l9c6YxLMBn6CEH3VrH5mVPIyOtxBG52BvV4jP3GWz6bOZCz61nLgBc3ffQYE4+EHfCD+V7w==" }, "tsickle": { "version": "0.27.5", @@ -12405,6 +12327,12 @@ } } }, + "upper-case": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz", + "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=", + "dev": true + }, "uri-js": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", @@ -12460,9 +12388,9 @@ }, "dependencies": { "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==" } } }, @@ -12881,24 +12809,28 @@ "dependencies": { "abbrev": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true, "optional": true }, "ansi-regex": { "version": "2.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, "aproba": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", "dev": true, "optional": true }, "are-we-there-yet": { "version": "1.1.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", "dev": true, "optional": true, "requires": { @@ -12908,12 +12840,14 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", "dev": true }, "brace-expansion": { "version": "1.1.11", - "bundled": true, + "resolved": false, + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "requires": { "balanced-match": "^1.0.0", @@ -12922,34 +12856,40 @@ }, "chownr": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g==", "dev": true, "optional": true }, "code-point-at": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", "dev": true }, "concat-map": { "version": "0.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", "dev": true }, "core-util-is": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true, "optional": true }, "debug": { "version": "2.6.9", - "bundled": true, + "resolved": false, + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "optional": true, "requires": { @@ -12958,25 +12898,29 @@ }, "deep-extend": { "version": "0.6.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true, "optional": true }, "delegates": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", "dev": true, "optional": true }, "detect-libc": { "version": "1.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", "dev": true, "optional": true }, "fs-minipass": { "version": "1.2.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ==", "dev": true, "optional": true, "requires": { @@ -12985,13 +12929,15 @@ }, "fs.realpath": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true, "optional": true }, "gauge": { "version": "2.7.4", - "bundled": true, + "resolved": false, + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", "dev": true, "optional": true, "requires": { @@ -13007,7 +12953,8 @@ }, "glob": { "version": "7.1.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "dev": true, "optional": true, "requires": { @@ -13021,13 +12968,15 @@ }, "has-unicode": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", "dev": true, "optional": true }, "iconv-lite": { "version": "0.4.24", - "bundled": true, + "resolved": false, + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, "optional": true, "requires": { @@ -13036,7 +12985,8 @@ }, "ignore-walk": { "version": "3.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ==", "dev": true, "optional": true, "requires": { @@ -13045,7 +12995,8 @@ }, "inflight": { "version": "1.0.6", - "bundled": true, + "resolved": false, + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "optional": true, "requires": { @@ -13055,18 +13006,21 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, "ini": { "version": "1.3.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "dev": true, "optional": true }, "is-fullwidth-code-point": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "requires": { "number-is-nan": "^1.0.0" @@ -13074,13 +13028,15 @@ }, "isarray": { "version": "1.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", "dev": true, "optional": true }, "minimatch": { "version": "3.0.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { "brace-expansion": "^1.1.7" @@ -13088,12 +13044,14 @@ }, "minimist": { "version": "0.0.8", - "bundled": true, + "resolved": false, + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, "minipass": { "version": "2.3.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA==", "dev": true, "requires": { "safe-buffer": "^5.1.2", @@ -13102,7 +13060,8 @@ }, "minizlib": { "version": "1.2.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA==", "dev": true, "optional": true, "requires": { @@ -13111,7 +13070,8 @@ }, "mkdirp": { "version": "0.5.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { "minimist": "0.0.8" @@ -13119,13 +13079,15 @@ }, "ms": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true, "optional": true }, "needle": { "version": "2.2.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-HyoqEb4wr/rsoaIDfTH2aVL9nWtQqba2/HvMv+++m8u0dz808MaagKILxtfeSN7QU7nvbQ79zk3vYOJp9zsNEA==", "dev": true, "optional": true, "requires": { @@ -13136,7 +13098,8 @@ }, "node-pre-gyp": { "version": "0.10.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-d1xFs+C/IPS8Id0qPTZ4bUT8wWryfR/OzzAFxweG+uLN85oPzyo2Iw6bVlLQ/JOdgNonXLCoRyqDzDWq4iw72A==", "dev": true, "optional": true, "requires": { @@ -13154,7 +13117,8 @@ }, "nopt": { "version": "4.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-0NRoWv1UFRk8jHUFYC0NF81kR00=", "dev": true, "optional": true, "requires": { @@ -13164,13 +13128,15 @@ }, "npm-bundled": { "version": "1.0.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-m/e6jgWu8/v5niCUKQi9qQl8QdeEduFA96xHDDzFGqly0OOjI7c+60KM/2sppfnUU9JJagf+zs+yGhqSOFj71g==", "dev": true, "optional": true }, "npm-packlist": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-7Mni4Z8Xkx0/oegoqlcao/JpPCPEMtUvsmB0q7mgvlMinykJLSRTYuFqoQLYgGY8biuxIeiHO+QNJKbCfljewQ==", "dev": true, "optional": true, "requires": { @@ -13180,7 +13146,8 @@ }, "npmlog": { "version": "4.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", "dev": true, "optional": true, "requires": { @@ -13192,18 +13159,21 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", "dev": true }, "object-assign": { "version": "4.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", "dev": true, "optional": true }, "once": { "version": "1.4.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { "wrappy": "1" @@ -13211,19 +13181,22 @@ }, "os-homedir": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", "dev": true, "optional": true }, "os-tmpdir": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", "dev": true, "optional": true }, "osenv": { "version": "0.1.5", - "bundled": true, + "resolved": false, + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", "dev": true, "optional": true, "requires": { @@ -13233,19 +13206,22 @@ }, "path-is-absolute": { "version": "1.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", "dev": true, "optional": true }, "process-nextick-args": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", "dev": true, "optional": true }, "rc": { "version": "1.2.8", - "bundled": true, + "resolved": false, + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", "dev": true, "optional": true, "requires": { @@ -13257,7 +13233,8 @@ "dependencies": { "minimist": { "version": "1.2.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true, "optional": true } @@ -13265,7 +13242,8 @@ }, "readable-stream": { "version": "2.3.6", - "bundled": true, + "resolved": false, + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "optional": true, "requires": { @@ -13280,7 +13258,8 @@ }, "rimraf": { "version": "2.6.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", "dev": true, "optional": true, "requires": { @@ -13289,42 +13268,49 @@ }, "safe-buffer": { "version": "5.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, "safer-buffer": { "version": "2.1.2", - "bundled": true, + "resolved": false, + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true, "optional": true }, "sax": { "version": "1.2.4", - "bundled": true, + "resolved": false, + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", "dev": true, "optional": true }, "semver": { "version": "5.6.0", - "bundled": true, + "resolved": false, + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", "dev": true, "optional": true }, "set-blocking": { "version": "2.0.0", - "bundled": true, + "resolved": false, + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", "dev": true, "optional": true }, "signal-exit": { "version": "3.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true, "optional": true }, "string-width": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { "code-point-at": "^1.0.0", @@ -13334,7 +13320,8 @@ }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": false, + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "optional": true, "requires": { @@ -13343,7 +13330,8 @@ }, "strip-ansi": { "version": "3.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { "ansi-regex": "^2.0.0" @@ -13351,13 +13339,15 @@ }, "strip-json-comments": { "version": "2.0.1", - "bundled": true, + "resolved": false, + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true, "optional": true }, "tar": { "version": "4.4.8", - "bundled": true, + "resolved": false, + "integrity": "sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ==", "dev": true, "optional": true, "requires": { @@ -13372,13 +13362,15 @@ }, "util-deprecate": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true, "optional": true }, "wide-align": { "version": "1.1.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", "dev": true, "optional": true, "requires": { @@ -13387,12 +13379,14 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, + "resolved": false, + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, "yallist": { "version": "3.0.3", - "bundled": true, + "resolved": false, + "integrity": "sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A==", "dev": true } } @@ -13490,8 +13484,7 @@ }, "kind-of": { "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "resolved": "", "dev": true }, "micromatch": { @@ -13534,10 +13527,19 @@ } } }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "dev": true, + "requires": { + "defaults": "^1.0.3" + } + }, "web-animations-js": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/web-animations-js/-/web-animations-js-2.3.1.tgz", - "integrity": "sha1-Om2bwVGWN3qQ+OKAP6UmIWWwRRA=" + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/web-animations-js/-/web-animations-js-2.3.2.tgz", + "integrity": "sha512-TOMFWtQdxzjWp8qx4DAraTWTsdhxVSiWa6NkPFSaPtZ1diKUxTn4yTix73A1euG1WbSOMMPcY51cnjTIHrGtDA==" }, "webpack": { "version": "3.12.0", @@ -13764,12 +13766,12 @@ } }, "webpack-merge": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.1.2.tgz", - "integrity": "sha512-/0QYwW/H1N/CdXYA2PNPVbsxO3u2Fpz34vs72xm03SRfg6bMNGfMJIQEpQjKRvkG2JvT6oRJFpDtSrwbX8Jzvw==", + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz", + "integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==", "dev": true, "requires": { - "lodash": "^4.17.5" + "lodash": "^4.17.15" } }, "webpack-sources": { @@ -14093,9 +14095,9 @@ } }, "zone.js": { - "version": "0.8.26", - "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.8.26.tgz", - "integrity": "sha512-W9Nj+UmBJG251wkCacIkETgra4QgBo/vgoEkb4a2uoLzpQG7qF9nzwoLXWU5xj3Fg2mxGvEDh47mg24vXccYjA==" + "version": "0.8.29", + "resolved": "https://registry.npmjs.org/zone.js/-/zone.js-0.8.29.tgz", + "integrity": "sha512-mla2acNCMkWXBD+c+yeUrBUrzOxYMNFdQ6FGfigGGtEVBPJx07BQeJekjt9DmH1FtZek4E9rE1eRR9qQpxACOQ==" } } } diff --git a/package.json b/package.json index e276ffee2..ee8d9a7fc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "moodlemobile", - "version": "3.8.0", + "version": "3.8.1", "description": "The official app for Moodle.", "author": { "name": "Moodle Pty Ltd.", @@ -9,6 +9,7 @@ "config": { "ionic_webpack": "./config/webpack.config.js", "ionic_copy": "./config/copy.config.js", + "ionic_uglifyjs": "./config/uglifyjs.config.js", "ionic_sass": "./config/sass.config.js" }, "repository": { @@ -40,63 +41,56 @@ "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/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", + "@angular/animations": "5.2.11", + "@angular/common": "5.2.11", + "@angular/compiler": "5.2.11", + "@angular/compiler-cli": "5.2.11", + "@angular/core": "5.2.11", + "@angular/forms": "5.2.11", + "@angular/platform-browser": "5.2.11", + "@angular/platform-browser-dynamic": "5.2.11", + "@ionic-native/badge": "4.20.0", + "@ionic-native/camera": "4.20.0", + "@ionic-native/clipboard": "4.20.0", + "@ionic-native/core": "4.20.0", + "@ionic-native/device": "4.20.0", + "@ionic-native/file": "4.20.0", + "@ionic-native/file-opener": "4.20.0", + "@ionic-native/file-transfer": "4.20.0", + "@ionic-native/geolocation": "4.20.0", + "@ionic-native/globalization": "4.20.0", + "@ionic-native/in-app-browser": "4.20.0", + "@ionic-native/keyboard": "4.20.0", + "@ionic-native/local-notifications": "4.20.0", + "@ionic-native/media-capture": "4.20.0", + "@ionic-native/network": "4.20.0", + "@ionic-native/push": "4.20.0", + "@ionic-native/screen-orientation": "4.20.0", + "@ionic-native/splash-screen": "4.20.0", + "@ionic-native/sqlite": "4.20.0", + "@ionic-native/status-bar": "4.20.0", + "@ionic-native/web-intent": "4.20.0", + "@ionic-native/zip": "4.20.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", + "ajv": "6.11.0", + "chart.js": "2.9.3", "com-darryncampbell-cordova-plugin-intent": "1.3.0", "cordova": "9.0.0", - "cordova-android": "8.0.0", + "cordova-android": "8.1.0", "cordova-android-support-gradle-release": "3.0.1", "cordova-clipboard": "1.3.0", - "cordova-ios": "5.0.1", + "cordova-ios": "5.1.1", "cordova-plugin-badge": "0.8.8", "cordova-plugin-camera": "4.1.0", - "cordova-plugin-customurlscheme": "4.4.0", + "cordova-plugin-customurlscheme": "5.0.0", "cordova-plugin-device": "2.0.3", "cordova-plugin-file": "6.0.2", - "cordova-plugin-file-opener2": "2.2.1", + "cordova-plugin-file-opener2": "3.0.0", "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-inappbrowser": "3.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", @@ -106,41 +100,48 @@ "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", + "cordova-sqlite-storage": "4.0.0", + "cordova-support-google-services": "1.3.2", "es6-promise-plugin": "4.2.2", "font-awesome": "4.7.0", - "ionic-angular": "3.9.3", + "ionic-angular": "3.9.9", "ionicons": "3.0.0", "jszip": "3.1.5", "mathjax": "2.7.7", - "moment": "2.22.2", + "moment": "2.24.0", "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", + "rxjs": "5.5.12", "sw-toolbox": "3.6.0", - "ts-md5": "1.2.4", - "web-animations-js": "2.3.1", - "zone.js": "0.8.26" + "ts-md5": "1.2.7", + "web-animations-js": "2.3.2", + "zone.js": "0.8.29" }, "devDependencies": { - "@ionic/app-scripts": "3.2.2", + "@ionic/app-scripts": "3.2.3", + "@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.59", + "@types/promise.prototype.finally": "2.0.4", "electron-builder-lib": "20.23.1", - "electron-rebuild": "1.8.1", + "electron-rebuild": "1.10.0", "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-htmlmin": "5.0.1", + "gulp-rename": "2.0.0", "gulp-slash": "1.1.3", - "gulp-util": "3.0.8", "lodash.template": "4.5.0", "node-loader": "0.6.0", "through": "2.3.8", "typescript": "2.6.2", - "webpack-merge": "4.1.2" + "vinyl": "2.2.0", + "webpack-merge": "4.2.2" }, "browser": { "electron": false @@ -224,7 +225,7 @@ "category": "public.app-category.education", "icon": "resources/desktop/icon.icns", "target": "mas", - "bundleVersion": "3.8.0", + "bundleVersion": "3.8.1", "extendInfo": { "ElectronTeamID": "2NU57U5PAW" } diff --git a/scripts/aot.sh b/scripts/aot.sh deleted file mode 100755 index 4d3680e8a..000000000 --- a/scripts/aot.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/bash -source "scripts/functions.sh" - -print_title "NPM packages list" - -# List first level of installed libraries so we can check the installed versions. -npm list --depth=0 - -if [ "$TRAVIS_BRANCH" == 'master' ] && [ ! -z $GIT_TOKEN ] && [ "$TRAVIS_REPO_SLUG" == 'moodlehq/moodleapp' ]; then - print_title "Update langpacks" - cd scripts - ./update_lang.sh - cd .. - - print_title "Update generated lang files" - git remote set-url origin https://$GIT_TOKEN@github.com/$TRAVIS_REPO_SLUG.git - git fetch -q origin - git add -A src/assets/lang - git add */en.json - git add src/config.json - git commit -m 'Update lang files [ci skip]' - git push origin HEAD:$TRAVIS_BRANCH -fi - -print_title "AOT Compilation" -sed -ie $'s~throw new Error("No ResourceLoader.*~url = "templates/" + url;\\\nvar resolve;\\\nvar reject;\\\nvar promise = new Promise(function (res, rej) {\\\nresolve = res;\\\nreject = rej;\\\n});\\\nvar xhr = new XMLHttpRequest();\\\nxhr.open("GET", url, true);\\\nxhr.responseType = "text";\\\nxhr.onload = function () {\\\nvar response = xhr.response || xhr.responseText;\\\nvar status = xhr.status === 1223 ? 204 : xhr.status;\\\nif (status === 0) {\\\nstatus = response ? 200 : 0;\\\n}\\\nif (200 <= status \&\& status <= 300) {\\\nresolve(response);\\\n}\\\nelse {\\\nreject("Failed to load " + url);\\\n}\\\n};\\\nxhr.onerror = function () { reject("Failed to load " + url); };\\\nxhr.send();\\\nreturn promise;\\\n~g' node_modules/@angular/platform-browser-dynamic/esm5/platform-browser-dynamic.js -sed -ie "s/context\.isProd || hasArg('--minifyJs')/hasArg('--minifyJs')/g" node_modules/@ionic/app-scripts/dist/util/config.js -sed -ie "s/context\.isProd || hasArg('--optimizeJs')/hasArg('--optimizeJs')/g" node_modules/@ionic/app-scripts/dist/util/config.js -npm run ionic:build -- --prod - - -if [ $TRAVIS_BRANCH == 'integration' ] || [ $TRAVIS_BRANCH == 'master' ] || [ $TRAVIS_BRANCH == 'desktop' ] ; then - if [ ! -z $GIT_ORG_PRIVATE ] && [ ! -z $GIT_TOKEN ] ; then - if [ "$TRAVIS_REPO_SLUG" == 'moodlehq/moodleapp' ]; then - print_title "Mirror repository" - git remote add mirror https://$GIT_TOKEN@github.com/$GIT_ORG_PRIVATE/moodleapp.git - git fetch -q mirror - git push -f mirror HEAD:$TRAVIS_BRANCH - git push mirror --tags - else - print_title "Run scripts" - git clone --depth 1 https://$GIT_TOKEN@github.com/$GIT_ORG_PRIVATE/apps-scripts.git ../scripts - cp ../scripts/build.sh scripts/ - ./scripts/build.sh - fi - fi -fi diff --git a/scripts/ci.sh b/scripts/ci.sh index a8aaec045..0fe5022a1 100755 --- a/scripts/ci.sh +++ b/scripts/ci.sh @@ -1,8 +1,81 @@ #!/bin/bash +source "scripts/functions.sh" -if [ $TRAVIS_EVENT_TYPE == 'cron' ] ; then +if [ "$TRAVIS_EVENT_TYPE" == 'cron' ] ; then # Tests scripts. - echo 'CRON NOT IMPLEMENTED YET' + print_error 'CRON NOT IMPLEMENTED YET' else - ./scripts/aot.sh + if [ -z $GIT_ORG_PRIVATE ] || [ -z $GIT_TOKEN ]; then + print_error "Env vars not correctly defined" + exit 1 + fi + + # List first level of installed libraries so we can check the installed versions. + print_title "NPM packages list" + npm list --depth=0 + + if [ "$TRAVIS_REPO_SLUG" == 'moodlehq/moodleapp' ]; then + if [ "$TRAVIS_BRANCH" == 'master' ]; then + print_title "Update langpacks" + cd scripts + ./update_lang.sh + cd .. + + print_title "Update generated lang files" + git remote set-url origin https://$GIT_TOKEN@github.com/$TRAVIS_REPO_SLUG.git + git fetch -q origin + git add -A src/assets/lang + git add */en.json + git add src/config.json + git commit -m 'Update lang files [ci skip]' + + print_title "Update Licenses" + npm install -g license-checker + + jq --version + license-checker --json --production --relativeLicensePath > licenses.json + jq 'del(.[].path)' licenses.json > licenses_old.json + mv licenses_old.json licenses.json + licenses=`jq -r 'keys[]' licenses.json` + echo "{" > licensesurl.json + first=1 + for license in $licenses; do + obj=`jq --arg lic $license '.[$lic]' licenses.json` + licensePath=`echo $obj | jq -r '.licenseFile'` + file="" + if [[ ! -z "$licensePath" ]] || [[ "$licensePath" != "null" ]]; then + file=$(basename $licensePath) + if [ $first -eq 1 ] ; then + first=0 + echo "\"$license\" : { \"licenseFile\" : \"$file\"}" >> licensesurl.json + else + echo ",\"$license\" : { \"licenseFile\" : \"$file\"}" >> licensesurl.json + fi + fi + done + echo "}" >> licensesurl.json + + jq -s '.[0] * .[1]' licenses.json licensesurl.json > licenses_old.json + mv licenses_old.json licenses.json + rm licensesurl.json + + git add licenses.json + git commit -m 'Update licenses [ci skip]' + + git push origin HEAD:$TRAVIS_BRANCH + fi + + if [ "$TRAVIS_BRANCH" == 'integration' ] || [ "$TRAVIS_BRANCH" == 'master' ] || [ "$TRAVIS_BRANCH" == 'desktop' ] ; then + print_title "Mirror repository" + git remote add mirror https://$GIT_TOKEN@github.com/$GIT_ORG_PRIVATE/moodleapp.git + git fetch -q --unshallow mirror + git push -f mirror HEAD:$TRAVIS_BRANCH + git push mirror --tags + fi + elif [ "$TRAVIS_REPO_SLUG" == "$GIT_ORG_PRIVATE/moodleapp" ]; then + print_title "Run scripts" + git clone --depth 1 https://$GIT_TOKEN@github.com/$GIT_ORG_PRIVATE/apps-scripts.git ../scripts + cp ../scripts/build.sh scripts/ + ./scripts/build.sh + fi fi diff --git a/scripts/create_langindex.sh b/scripts/create_langindex.sh index 1988778e4..d35ba1d74 100755 --- a/scripts/create_langindex.sh +++ b/scripts/create_langindex.sh @@ -3,8 +3,8 @@ source "functions.sh" #Saves or updates a key on langindex_old.json function save_key { - key=$1 - found=$2 + local key=$1 + local found=$2 print_ok "$key=$found" echo "{\"$key\": \"$found\"}" > langindex_old.json @@ -14,19 +14,17 @@ function save_key { #Removes a key on langindex_old.json function remove_key { - key=$1 - found=$2 + local key=$1 - print_ok "$key=$found" - echo "{\"$key\": \"$found\"}" > langindex_old.json - jq -s '.[0] - .[1]' langindex.json langindex_old.json > langindex_new.json + cat langindex.json | jq 'del(."'$key'")' > langindex_new.json mv langindex_new.json langindex.json + print_ok "Deleted unused key $key" } #Check if and i exists in php file function exists_in_file { - file=$1 - id=$2 + local file=$1 + local id=$2 file=`echo $file | sed s/^mod_workshop_assessment/workshopform/1` file=`echo $file | sed s/^mod_assign_/assign/1` @@ -45,7 +43,7 @@ function exists_in_file { #Checks if a key exists on the original local_moodlemobileapp.php function exists_in_mobile { - file='local_moodlemobileapp' + local file='local_moodlemobileapp' exists_in_file $file $key } @@ -114,12 +112,12 @@ function find_single_matches { #Tries to gues the file where the id will be found. function guess_file { - key=$1 - value=$2 + local key=$1 + local value=$2 - type=`echo $key | cut -d'.' -f1` - component=`echo $key | cut -d'.' -f2` - plainid=`echo $key | cut -d'.' -f3-` + local type=`echo $key | cut -d'.' -f1` + local component=`echo $key | cut -d'.' -f2` + local plainid=`echo $key | cut -d'.' -f3-` if [ -z "$plainid" ]; then plainid=$component @@ -161,23 +159,52 @@ function guess_file { fi } +function current_translation_exists { + local key=$1 + local current=$2 + local file=$3 + + plainid=`echo $key | cut -d'.' -f3-` + + if [ -z "$plainid" ]; then + plainid=`echo $key | cut -d'.' -f2` + fi + + local currentFile=`echo $current | cut -d'/' -f1` + local currentStr=`echo $current | cut -d'/' -f2-` + if [ $currentFile == $current ]; then + currentStr=$plainid + fi + + exists_in_file $currentFile $currentStr + if [ $found == 0 ]; then + # Translation not found. + exec="jq -r .\"$key\" $file" + value=`$exec` + + print_error "Translation of '$currentStr' not found in '$currentFile'" + + guess_file $key "$value" + fi +} + #Finds if there's a better file where to get the id from. function find_better_file { - key=$1 - value=$2 - current=$3 + local key=$1 + local value=$2 + local current=$3 - type=`echo $key | cut -d'.' -f1` - component=`echo $key | cut -d'.' -f2` - plainid=`echo $key | cut -d'.' -f3-` + local type=`echo $key | cut -d'.' -f1` + local component=`echo $key | cut -d'.' -f2` + local plainid=`echo $key | cut -d'.' -f3-` if [ -z "$plainid" ]; then plainid=$component component='moodle' fi - currentFile=`echo $current | cut -d'/' -f1` - currentStr=`echo $current | cut -d'/' -f2-` + local currentFile=`echo $current | cut -d'/' -f1` + local currentStr=`echo $current | cut -d'/' -f2-` if [ $currentFile == $current ]; then currentStr=$plainid fi @@ -217,7 +244,7 @@ function find_better_file { fi } -#Parses the file. +# Parses the file. function parse_file { findbetter=$2 keys=`jq -r 'keys[]' $1` @@ -226,12 +253,43 @@ function parse_file { exec="jq -r .\"$key\" langindex.json" found=`$exec` - exec="jq -r .\"$key\" $1" - value=`$exec` if [ -z "$found" ] || [ "$found" == 'null' ]; then + exec="jq -r .\"$key\" $1" + value=`$exec` guess_file $key "$value" - elif [ ! -z "$findbetter" ]; then - find_better_file "$key" "$value" "$found" + else + if [ ! -z "$findbetter" ]; then + exec="jq -r .\"$key\" $1" + value=`$exec` + find_better_file "$key" "$value" "$found" + elif [ "$found" != 'local_moodlemobileapp' ]; then + current_translation_exists "$key" "$found" "$1" + fi + fi + done + + # Do some cleanup + langkeys=`jq -r 'keys[]' langindex.json` + findkeys="${keys[@]}" + for key in $langkeys; do + # Check if already used. + array_contains "$key" "$findkeys" + + if [ -z "$found" ] || [ "$found" == 'null' ]; then + remove_key $key + fi + done +} + +# Checks if an array contains an string. +function array_contains { + local hayjack=$2 + local needle=$1 + found='' + for i in $hayjack; do + if [ "$i" == "$needle" ] ; then + found=$i + return fi done } diff --git a/scripts/lang_functions.php b/scripts/lang_functions.php index 7765841ca..5f46d4623 100644 --- a/scripts/lang_functions.php +++ b/scripts/lang_functions.php @@ -208,7 +208,7 @@ function build_lang($lang, $keys) { // Apply translations. if (!$string) { if (TOTRANSLATE) { - echo "\n\t\To translate $value->string on $value->file"; + echo "\n\t\tTo translate $value->string on $value->file"; } continue; } @@ -217,9 +217,12 @@ function build_lang($lang, $keys) { // Not yet translated. Do not override. if ($langFile && is_array($langFile) && isset($langFile[$key])) { $translations[$key] = $langFile[$key]; - $local++; + + if ($value->file == 'local_moodlemobileapp') { + $local++; + } } - if (TOTRANSLATE) { + if (TOTRANSLATE && !isset($string[$value->string])) { echo "\n\t\tTo translate $value->string on $value->file"; } continue; @@ -265,7 +268,7 @@ function build_lang($lang, $keys) { } function progressbar($percentage) { - $done = $percentage/10; + $done = floor($percentage/10); return "\t".str_repeat('=', $done) . str_repeat('-', 10-$done); } diff --git a/scripts/langindex.json b/scripts/langindex.json index ed6b18340..b8e6572af 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -29,7 +29,6 @@ "addon.block_activitymodules.pluginname": "block_activity_modules", "addon.block_badges.pluginname": "block_badges", "addon.block_blogmenu.pluginname": "block_blog_menu", - "addon.block_blogrecent.nocourses": "block_blog_recent", "addon.block_blogrecent.pluginname": "block_blog_recent", "addon.block_blogtags.pluginname": "block_blog_tags", "addon.block_calendarmonth.pluginname": "block_calendar_month", @@ -427,6 +426,8 @@ "addon.mod_assign_submission_onlinetext.wordlimitexceeded": "assignsubmission_onlinetext", "addon.mod_book.errorchapter": "book", "addon.mod_book.modulenameplural": "book", + "addon.mod_book.navnexttitle": "book", + "addon.mod_book.navprevtitle": "book", "addon.mod_book.tagarea_book_chapters": "book", "addon.mod_book.toc": "book", "addon.mod_chat.beep": "chat", @@ -1323,12 +1324,12 @@ "core.all": "moodle", "core.allgroups": "moodle", "core.allparticipants": "moodle", - "core.android": "local_moodlemobileapp", "core.answer": "moodle", "core.answered": "quiz", "core.areyousure": "moodle", "core.back": "moodle", "core.block.blocks": "moodle", + "core.browser": "local_moodlemobileapp", "core.cancel": "moodle", "core.cannotconnect": "local_moodlemobileapp", "core.cannotdownloadfiles": "local_moodlemobileapp", @@ -1355,7 +1356,6 @@ "core.comments.savecomment": "moodle", "core.comments.warningcommentsnotsent": "local_moodlemobileapp", "core.commentscount": "moodle", - "core.commentsnotworking": "local_moodlemobileapp", "core.completion-alt-auto-fail": "completion", "core.completion-alt-auto-n": "completion", "core.completion-alt-auto-n-override": "completion", @@ -1467,6 +1467,7 @@ "core.deleteduser": "bulkusers", "core.deleting": "local_moodlemobileapp", "core.description": "moodle", + "core.desktop": "local_moodlemobileapp", "core.dfdaymonthyear": "local_moodlemobileapp", "core.dfdayweekmonth": "local_moodlemobileapp", "core.dffulldate": "local_moodlemobileapp", @@ -1477,11 +1478,27 @@ "core.digitalminor_desc": "moodle", "core.discard": "local_moodlemobileapp", "core.dismiss": "local_moodlemobileapp", + "core.displayoptions": "atto_media", "core.done": "survey", "core.download": "moodle", "core.downloaded": "local_moodlemobileapp", "core.downloading": "local_moodlemobileapp", "core.edit": "moodle", + "core.editor.autosavesucceeded": "editor_atto", + "core.editor.bold": "atto_bold/pluginname", + "core.editor.clear": "atto_clear/pluginname", + "core.editor.h3": "atto_title", + "core.editor.h4": "atto_title", + "core.editor.h5": "atto_title", + "core.editor.hidetoolbar": "local_moodlemobileapp", + "core.editor.italic": "atto_italic/pluginname", + "core.editor.orderedlist": "atto_orderedlist/pluginname", + "core.editor.p": "atto_title", + "core.editor.strike": "atto_strike/pluginname", + "core.editor.textrecovered": "editor_atto", + "core.editor.toggle": "local_moodlemobileapp", + "core.editor.underline": "atto_underline/pluginname", + "core.editor.unorderedlist": "atto_unorderedlist/pluginname", "core.emptysplit": "local_moodlemobileapp", "core.error": "moodle", "core.errorchangecompletion": "local_moodlemobileapp", @@ -1619,6 +1636,7 @@ "core.h5p.offlineDialogRetryButtonLabel": "h5p", "core.h5p.offlineDialogRetryMessage": "h5p", "core.h5p.offlineSuccessfulSubmit": "h5p", + "core.h5p.offlinedisabled": "local_moodlemobileapp", "core.h5p.originator": "h5p", "core.h5p.pd": "h5p", "core.h5p.pddl": "h5p", @@ -1653,7 +1671,6 @@ "core.imageviewer": "local_moodlemobileapp", "core.info": "moodle", "core.invalidformdata": "error", - "core.ios": "local_moodlemobileapp", "core.labelsep": "langconfig", "core.lastaccess": "moodle", "core.lastdownloaded": "local_moodlemobileapp", @@ -1674,7 +1691,6 @@ "core.login.changepasswordinstructions": "local_moodlemobileapp", "core.login.changepasswordlogoutinstructions": "local_moodlemobileapp", "core.login.changepasswordreconnectinstructions": "local_moodlemobileapp", - "core.login.checksiteversion": "local_moodlemobileapp", "core.login.confirmdeletesite": "local_moodlemobileapp", "core.login.connect": "local_moodlemobileapp", "core.login.connecttomoodle": "local_moodlemobileapp", @@ -1689,7 +1705,6 @@ "core.login.emailconfirmsentnoemail": "local_moodlemobileapp", "core.login.emailconfirmsentsuccess": "moodle", "core.login.emailnotmatch": "local_moodlemobileapp", - "core.login.enterthewordsabove": "auth", "core.login.erroraccesscontrolalloworigin": "local_moodlemobileapp", "core.login.errordeletesite": "local_moodlemobileapp", "core.login.errorupdatesite": "local_moodlemobileapp", @@ -1697,7 +1712,6 @@ "core.login.firsttime": "moodle", "core.login.forcepasswordchangenotice": "moodle", "core.login.forgotten": "moodle", - "core.login.getanothercaptcha": "auth", "core.login.help": "moodle", "core.login.helpmelogin": "local_moodlemobileapp", "core.login.instructions": "auth", @@ -1710,9 +1724,6 @@ "core.login.invalidurl": "scorm", "core.login.invalidvaluemax": "local_moodlemobileapp", "core.login.invalidvaluemin": "local_moodlemobileapp", - "core.login.legacymoodleversion": "local_moodlemobileapp", - "core.login.legacymoodleversiondesktop": "local_moodlemobileapp", - "core.login.legacymoodleversiondesktopdownloadold": "local_moodlemobileapp", "core.login.localmobileunexpectedresponse": "local_moodlemobileapp", "core.login.loggedoutssodescription": "local_moodlemobileapp", "core.login.login": "moodle", @@ -1767,7 +1778,6 @@ "core.login.visitchangepassword": "local_moodlemobileapp", "core.login.webservicesnotenabled": "local_moodlemobileapp", "core.lostconnection": "local_moodlemobileapp", - "core.mainmenu.appsettings": "local_moodlemobileapp", "core.mainmenu.changesite": "local_moodlemobileapp", "core.mainmenu.help": "moodle", "core.mainmenu.logout": "moodle", @@ -1898,7 +1908,8 @@ "core.sending": "chat", "core.serverconnection": "error", "core.settings.about": "local_moodlemobileapp", - "core.settings.appready": "local_moodlemobileapp", + "core.settings.appsettings": "local_moodlemobileapp", + "core.settings.appversion": "local_moodlemobileapp", "core.settings.cannotsyncoffline": "local_moodlemobileapp", "core.settings.cannotsyncwithoutwifi": "local_moodlemobileapp", "core.settings.colorscheme": "local_moodlemobileapp", @@ -1906,6 +1917,7 @@ "core.settings.colorscheme-dark": "local_moodlemobileapp", "core.settings.colorscheme-light": "local_moodlemobileapp", "core.settings.compilationinfo": "local_moodlemobileapp", + "core.settings.copyinfo": "local_moodlemobileapp", "core.settings.cordovadevicemodel": "local_moodlemobileapp", "core.settings.cordovadeviceosversion": "local_moodlemobileapp", "core.settings.cordovadeviceplatform": "local_moodlemobileapp", @@ -1918,7 +1930,6 @@ "core.settings.deletesitefilestitle": "local_moodlemobileapp", "core.settings.deviceinfo": "local_moodlemobileapp", "core.settings.deviceos": "local_moodlemobileapp", - "core.settings.devicewebworkers": "local_moodlemobileapp", "core.settings.disableall": "message", "core.settings.disabled": "lesson", "core.settings.displayformat": "local_moodlemobileapp", @@ -1935,6 +1946,7 @@ "core.settings.filesystemroot": "local_moodlemobileapp", "core.settings.fontsize": "local_moodlemobileapp", "core.settings.fontsizecharacter": "block_accessibility/char", + "core.settings.forcedsetting": "local_moodlemobileapp", "core.settings.general": "moodle", "core.settings.language": "moodle", "core.settings.license": "moodle", @@ -1946,19 +1958,24 @@ "core.settings.navigatorlanguage": "local_moodlemobileapp", "core.settings.navigatoruseragent": "local_moodlemobileapp", "core.settings.networkstatus": "local_moodlemobileapp", + "core.settings.opensourcelicenses": "local_moodlemobileapp", + "core.settings.preferences": "moodle", "core.settings.privacypolicy": "local_moodlemobileapp", + "core.settings.publisher": "local_moodlemobileapp", "core.settings.pushid": "local_moodlemobileapp", "core.settings.reportinbackground": "local_moodlemobileapp", + "core.settings.screen": "local_moodlemobileapp", "core.settings.settings": "moodle", "core.settings.showdownloadoptions": "local_moodlemobileapp", + "core.settings.siteinfo": "local_moodlemobileapp", "core.settings.sites": "moodle", "core.settings.spaceusage": "local_moodlemobileapp", + "core.settings.spaceusagehelp": "local_moodlemobileapp", "core.settings.synchronization": "local_moodlemobileapp", "core.settings.synchronizenow": "local_moodlemobileapp", + "core.settings.synchronizenowhelp": "local_moodlemobileapp", "core.settings.syncsettings": "local_moodlemobileapp", "core.settings.total": "moodle", - "core.settings.versioncode": "local_moodlemobileapp", - "core.settings.versionname": "local_moodlemobileapp", "core.settings.wificonnection": "local_moodlemobileapp", "core.sharedfiles.chooseaccountstorefile": "local_moodlemobileapp", "core.sharedfiles.chooseactionrepeatedfile": "local_moodlemobileapp", @@ -2003,18 +2020,18 @@ "core.submit": "moodle", "core.success": "moodle", "core.tablet": "local_moodlemobileapp", - "core.tag.defautltagcoll": "moodle", + "core.tag.defautltagcoll": "tag", "core.tag.errorareanotsupported": "local_moodlemobileapp", - "core.tag.inalltagcoll": "moodle", - "core.tag.itemstaggedwith": "moodle", - "core.tag.notagsfound": "moodle", - "core.tag.searchtags": "moodle", - "core.tag.showingfirsttags": "moodle", + "core.tag.inalltagcoll": "tag", + "core.tag.itemstaggedwith": "tag", + "core.tag.notagsfound": "tag", + "core.tag.searchtags": "tag", + "core.tag.showingfirsttags": "tag", "core.tag.tag": "moodle", - "core.tag.tagarea_course": "moodle", - "core.tag.tagarea_course_modules": "moodle", - "core.tag.tagarea_post": "moodle", - "core.tag.tagarea_user": "moodle", + "core.tag.tagarea_course": "tag", + "core.tag.tagarea_course_modules": "tag", + "core.tag.tagarea_post": "tag", + "core.tag.tagarea_user": "tag", "core.tag.tags": "moodle", "core.tag.warningareasnotsupported": "local_moodlemobileapp", "core.teachers": "moodle", @@ -2076,7 +2093,6 @@ "core.whoops": "local_moodlemobileapp", "core.whyisthishappening": "local_moodlemobileapp", "core.whyisthisrequired": "moodle", - "core.windowsphone": "local_moodlemobileapp", "core.wsfunctionnotavailable": "local_moodlemobileapp", "core.year": "moodle", "core.years": "moodle", diff --git a/src/addon/block/activitymodules/components/activitymodules/activitymodules.scss b/src/addon/block/activitymodules/components/activitymodules/activitymodules.scss index cc70ceb22..83a108a27 100644 --- a/src/addon/block/activitymodules/components/activitymodules/activitymodules.scss +++ b/src/addon/block/activitymodules/components/activitymodules/activitymodules.scss @@ -15,12 +15,3 @@ ion-app.app-root.ios addon-block-activitymodules { height: 24px; } } - -ion-app.app-root.wp addon-block-activitymodules { - .core-module-icon { - margin-top: $item-wp-padding-top; - margin-bottom: $item-wp-padding-bottom; - width: 24px; - height: 24px; - } -} diff --git a/src/addon/block/activitymodules/components/activitymodules/addon-block-activitymodules.html b/src/addon/block/activitymodules/components/activitymodules/addon-block-activitymodules.html index 342e38924..fc407eb4b 100644 --- a/src/addon/block/activitymodules/components/activitymodules/addon-block-activitymodules.html +++ b/src/addon/block/activitymodules/components/activitymodules/addon-block-activitymodules.html @@ -2,7 +2,7 @@

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

- + {{ entry.name }} diff --git a/src/addon/block/myoverview/components/myoverview/myoverview.ts b/src/addon/block/myoverview/components/myoverview/myoverview.ts index b6f062542..0f3cce93b 100644 --- a/src/addon/block/myoverview/components/myoverview/myoverview.ts +++ b/src/addon/block/myoverview/components/myoverview/myoverview.ts @@ -218,7 +218,8 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem this.showFilters.favourite = this.getShowFilterValue( this.showSelectorFilter && typeof courses[0].isfavourite != 'undefined' && - (!config || config.displaygroupingstarred.value == '1'), + (!config || (config.displaygroupingstarred && config.displaygroupingstarred.value == '1') || + (config.displaygroupingfavourites && config.displaygroupingfavourites.value == '1')), this.courses.favourite.length === 0); this.showFilters.custom = this.getShowFilterValue(this.showSelectorFilter && config && diff --git a/src/addon/calendar/calendar.module.ts b/src/addon/calendar/calendar.module.ts index 06a8bbf6c..b0c7ecd7b 100644 --- a/src/addon/calendar/calendar.module.ts +++ b/src/addon/calendar/calendar.module.ts @@ -25,7 +25,6 @@ import { CoreCronDelegate } from '@providers/cron'; import { CoreInitDelegate } from '@providers/init'; import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; import { CoreLoginHelperProvider } from '@core/login/providers/helper'; -import { CoreUpdateManagerProvider } from '@providers/update-manager'; import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate'; import { AddonCalendarComponentsModule } from './components/components.module'; @@ -56,7 +55,7 @@ export const ADDON_CALENDAR_PROVIDERS: any[] = [ export class AddonCalendarModule { constructor(mainMenuDelegate: CoreMainMenuDelegate, calendarHandler: AddonCalendarMainMenuHandler, initDelegate: CoreInitDelegate, calendarProvider: AddonCalendarProvider, loginHelper: CoreLoginHelperProvider, - localNotificationsProvider: CoreLocalNotificationsProvider, updateManager: CoreUpdateManagerProvider, + localNotificationsProvider: CoreLocalNotificationsProvider, cronDelegate: CoreCronDelegate, syncHandler: AddonCalendarSyncCronHandler, contentLinksDelegate: CoreContentLinksDelegate, viewLinkHandler: AddonCalendarViewLinkHandler) { @@ -88,18 +87,5 @@ export class AddonCalendarModule { }); } }); - - // Allow migrating the table from the old app to the new schema. - // In the old app some calculated properties were stored when it shouldn't. Filter only the fields we want. - updateManager.registerSiteTableMigration({ - name: 'calendar_events', - newName: AddonCalendarProvider.EVENTS_TABLE, - filterFields: ['id', 'name', 'description', 'format', 'eventtype', 'courseid', 'timestart', 'timeduration', - 'categoryid', 'groupid', 'userid', 'instance', 'modulename', 'timemodified', 'repeatid', 'visible', 'uuid', - 'sequence', 'subscriptionid'] - }); - - // Migrate the component name. - updateManager.registerLocalNotifComponentMigration('mmaCalendarComponent', AddonCalendarProvider.COMPONENT); } } diff --git a/src/addon/calendar/components/calendar/calendar.scss b/src/addon/calendar/components/calendar/calendar.scss index d04e2091b..436e2d210 100644 --- a/src/addon/calendar/components/calendar/calendar.scss +++ b/src/addon/calendar/components/calendar/calendar.scss @@ -89,6 +89,7 @@ ion-app.app-root addon-calendar-calendar { @include border-end(1px, solid, $calendar-border-color); overflow: hidden; min-height: 60px; + cursor: pointer; &:first-child { @include padding(null, null, null, 10px); @@ -131,6 +132,7 @@ ion-app.app-root addon-calendar-calendar { border-radius: 50%; } &.dayblank { + cursor: auto; background-color: $gray-lighter; @include darkmode() { background-color: $black; diff --git a/src/addon/calendar/components/upcoming-events/upcoming-events.scss b/src/addon/calendar/components/upcoming-events/upcoming-events.scss new file mode 100644 index 000000000..b8c06c58f --- /dev/null +++ b/src/addon/calendar/components/upcoming-events/upcoming-events.scss @@ -0,0 +1,5 @@ +ion-app.app-root addon-calendar-upcoming-events { + .addon-calendar-event { + cursor: pointer; + } +} diff --git a/src/addon/calendar/pages/day/day.html b/src/addon/calendar/pages/day/day.html index d81aff79e..a48f1d51f 100644 --- a/src/addon/calendar/pages/day/day.html +++ b/src/addon/calendar/pages/day/day.html @@ -48,7 +48,7 @@ - +

@@ -61,7 +61,7 @@ {{ 'core.deletedoffline' | translate }} -
+
diff --git a/src/addon/calendar/pages/edit-event/edit-event.html b/src/addon/calendar/pages/edit-event/edit-event.html index 9eae210f8..49c0bb32f 100644 --- a/src/addon/calendar/pages/edit-event/edit-event.html +++ b/src/addon/calendar/pages/edit-event/edit-event.html @@ -9,7 +9,7 @@ -
+

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

@@ -86,7 +86,7 @@

{{ 'core.description' | translate }}

- +
diff --git a/src/addon/calendar/pages/edit-event/edit-event.module.ts b/src/addon/calendar/pages/edit-event/edit-event.module.ts index 32a1ac65a..5821de8a0 100644 --- a/src/addon/calendar/pages/edit-event/edit-event.module.ts +++ b/src/addon/calendar/pages/edit-event/edit-event.module.ts @@ -17,6 +17,7 @@ import { IonicPageModule } from 'ionic-angular'; import { TranslateModule } from '@ngx-translate/core'; import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreEditorComponentsModule } from '@core/editor/components/components.module'; import { AddonCalendarEditEventPage } from './edit-event'; @NgModule({ @@ -26,6 +27,7 @@ import { AddonCalendarEditEventPage } from './edit-event'; imports: [ CoreComponentsModule, CoreDirectivesModule, + CoreEditorComponentsModule, IonicPageModule.forChild(AddonCalendarEditEventPage), TranslateModule.forChild() ], diff --git a/src/addon/calendar/pages/edit-event/edit-event.scss b/src/addon/calendar/pages/edit-event/edit-event.scss index 6426ce3f1..055fc5f3e 100644 --- a/src/addon/calendar/pages/edit-event/edit-event.scss +++ b/src/addon/calendar/pages/edit-event/edit-event.scss @@ -14,13 +14,6 @@ ion-app.app-root page-addon-calendar-edit-event { @include padding-horizontal($datetime-md-padding-start - $text-input-md-margin-start, null); } } - &.item-wp { - @include padding-horizontal($item-wp-padding-start * 2, null); - - ion-input { - @include padding-horizontal($datetime-wp-padding-start - $text-input-wp-margin-start, null); - } - } } .addon-calendar-eventtype-container.item-select-disabled { diff --git a/src/addon/calendar/pages/edit-event/edit-event.ts b/src/addon/calendar/pages/edit-event/edit-event.ts index 293d1fd62..5de3c1fe2 100644 --- a/src/addon/calendar/pages/edit-event/edit-event.ts +++ b/src/addon/calendar/pages/edit-event/edit-event.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, OnInit, OnDestroy, Optional, ViewChild } from '@angular/core'; +import { Component, OnInit, OnDestroy, Optional, ViewChild, ElementRef } from '@angular/core'; import { FormControl, FormGroup, FormBuilder, Validators } from '@angular/forms'; import { IonicPage, NavController, NavParams } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; @@ -25,7 +25,7 @@ import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCoursesProvider } from '@core/courses/providers/courses'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; -import { CoreRichTextEditorComponent } from '@components/rich-text-editor/rich-text-editor.ts'; +import { CoreEditorRichTextEditorComponent } from '@core/editor/components/rich-text-editor/rich-text-editor.ts'; import { AddonCalendarProvider, AddonCalendarGetAccessInfoResult, AddonCalendarEvent } from '../../providers/calendar'; import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline'; import { AddonCalendarHelperProvider } from '../../providers/helper'; @@ -43,7 +43,8 @@ import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; }) export class AddonCalendarEditEventPage implements OnInit, OnDestroy { - @ViewChild(CoreRichTextEditorComponent) descriptionEditor: CoreRichTextEditorComponent; + @ViewChild(CoreEditorRichTextEditorComponent) descriptionEditor: CoreEditorRichTextEditorComponent; + @ViewChild('editEventForm') formElement: ElementRef; title: string; dateFormat: string; @@ -496,6 +497,8 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { this.calendarProvider.submitEvent(this.eventId, data).then((result) => { event = result.event; + this.domUtils.triggerFormSubmittedEvent(this.formElement, result.sent, this.currentSite.getId()); + if (result.sent) { // Event created or edited, invalidate right days & months. const numberOfRepetitions = formData.repeat ? formData.repeats : @@ -557,6 +560,9 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { discard(): void { this.domUtils.showConfirm(this.translate.instant('core.areyousure')).then(() => { this.calendarOffline.deleteEvent(this.eventId).then(() => { + + this.domUtils.triggerFormCancelledEvent(this.formElement, this.currentSite.getId()); + this.returnToList(); }).catch(() => { // Shouldn't happen. @@ -572,16 +578,18 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { * * @return Resolved if we can leave it, rejected if not. */ - ionViewCanLeave(): boolean | Promise { - + async ionViewCanLeave(): Promise { if (this.calendarHelper.hasEventDataChanged(this.eventForm.value, this.originalData)) { // Show confirmation if some data has been modified. - return this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit')); - } else { - return Promise.resolve(); + await this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit')); } + + this.domUtils.triggerFormCancelledEvent(this.formElement, this.currentSite.getId()); } + /** + * Unblock sync. + */ protected unblockSync(): void { if (this.eventId) { this.syncProvider.unblockOperation(AddonCalendarProvider.COMPONENT, this.eventId); diff --git a/src/addon/competency/pages/competency/competency.html b/src/addon/competency/pages/competency/competency.html index 664ca9393..e3cd3ef36 100644 --- a/src/addon/competency/pages/competency/competency.html +++ b/src/addon/competency/pages/competency/competency.html @@ -25,7 +25,7 @@ {{ competency.competency.comppath.framework.name }}  /  - {{ ancestor.name }} + {{ ancestor.name }} {{ ancestor.name }}  /  @@ -35,7 +35,7 @@
{{ 'addon.competency.nocrossreferencedcompetencies' | translate }}

- + {{ relatedcomp.shortname }} - {{ relatedcomp.idnumber }}

diff --git a/src/addon/files/pages/list/list.html b/src/addon/files/pages/list/list.html index b119855ca..3b9207168 100644 --- a/src/addon/files/pages/list/list.html +++ b/src/addon/files/pages/list/list.html @@ -23,7 +23,7 @@ - +

{{file.filename}}

diff --git a/src/addon/messages/components/components.module.ts b/src/addon/messages/components/components.module.ts index 337c794e3..3e27e38bb 100644 --- a/src/addon/messages/components/components.module.ts +++ b/src/addon/messages/components/components.module.ts @@ -19,6 +19,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; import { CorePipesModule } from '@pipes/pipes.module'; +import { CoreSearchComponentsModule } from '@core/search/components/components.module'; import { AddonMessagesDiscussionsComponent } from '../components/discussions/discussions'; import { AddonMessagesConfirmedContactsComponent } from '../components/confirmed-contacts/confirmed-contacts'; import { AddonMessagesContactRequestsComponent } from '../components/contact-requests/contact-requests'; @@ -37,7 +38,8 @@ import { AddonMessagesContactsComponent } from '../components/contacts/contacts' TranslateModule.forChild(), CoreComponentsModule, CoreDirectivesModule, - CorePipesModule + CorePipesModule, + CoreSearchComponentsModule, ], providers: [ ], diff --git a/src/addon/messages/components/contacts/addon-messages-contacts.html b/src/addon/messages/components/contacts/addon-messages-contacts.html index 8d0f1f543..0b4167b04 100644 --- a/src/addon/messages/components/contacts/addon-messages-contacts.html +++ b/src/addon/messages/components/contacts/addon-messages-contacts.html @@ -3,7 +3,7 @@ - + diff --git a/src/addon/messages/components/discussions/addon-messages-discussions.html b/src/addon/messages/components/discussions/addon-messages-discussions.html index 0094d0dc5..87154cbfe 100644 --- a/src/addon/messages/components/discussions/addon-messages-discussions.html +++ b/src/addon/messages/components/discussions/addon-messages-discussions.html @@ -3,7 +3,7 @@ - + diff --git a/src/addon/messages/messages.module.ts b/src/addon/messages/messages.module.ts index a4bdffa38..69b1b0860 100644 --- a/src/addon/messages/messages.module.ts +++ b/src/addon/messages/messages.module.ts @@ -38,7 +38,6 @@ import { CoreSettingsDelegate } from '@core/settings/providers/delegate'; import { AddonMessagesSettingsHandler } from './providers/settings-handler'; import { CorePushNotificationsDelegate } from '@core/pushnotifications/providers/delegate'; import { CoreUtilsProvider } from '@providers/utils/utils'; -import { CoreUpdateManagerProvider } from '@providers/update-manager'; // List of providers (without handlers). export const ADDON_MESSAGES_PROVIDERS: any[] = [ @@ -75,7 +74,7 @@ export class AddonMessagesModule { userDelegate: CoreUserDelegate, cronDelegate: CoreCronDelegate, syncHandler: AddonMessagesSyncCronHandler, network: Network, zone: NgZone, messagesSync: AddonMessagesSyncProvider, appProvider: CoreAppProvider, localNotifications: CoreLocalNotificationsProvider, messagesProvider: AddonMessagesProvider, - sitesProvider: CoreSitesProvider, linkHelper: CoreContentLinksHelperProvider, updateManager: CoreUpdateManagerProvider, + sitesProvider: CoreSitesProvider, linkHelper: CoreContentLinksHelperProvider, settingsHandler: AddonMessagesSettingsHandler, settingsDelegate: CoreSettingsDelegate, pushNotificationsDelegate: CorePushNotificationsDelegate, utils: CoreUtilsProvider, addContactHandler: AddonMessagesAddContactUserHandler, blockContactHandler: AddonMessagesBlockContactUserHandler, @@ -136,21 +135,5 @@ export class AddonMessagesModule { // Listen for clicks in simulated push notifications. localNotifications.registerClick(AddonMessagesProvider.PUSH_SIMULATION_COMPONENT, notificationClicked); } - - // Allow migrating the table from the old app to the new schema. - updateManager.registerSiteTableMigration({ - name: 'mma_messages_offline_messages', - newName: AddonMessagesOfflineProvider.MESSAGES_TABLE, - fields: [ - { - name: 'textformat', - delete: true - } - ] - }); - - // Migrate the component name. - updateManager.registerLocalNotifComponentMigration('mmaMessagesPushSimulation', - AddonMessagesProvider.PUSH_SIMULATION_COMPONENT); } } diff --git a/src/addon/messages/pages/discussion/discussion.ts b/src/addon/messages/pages/discussion/discussion.ts index 3a56f49c6..a5b79b15f 100644 --- a/src/addon/messages/pages/discussion/discussion.ts +++ b/src/addon/messages/pages/discussion/discussion.ts @@ -366,9 +366,12 @@ export class AddonMessagesDiscussionPage implements OnDestroy { return; } + // Don't use domUtils.getScrollHeight because it gives an outdated value after receiving a new message. + const scrollHeight = this.content && this.content.getScrollElement() ? this.content.getScrollElement().scrollHeight : 0; + // Check if we are at the bottom to scroll it after render. // Use a 5px error margin because in iOS there is 1px difference for some reason. - this.scrollBottom = Math.abs(this.domUtils.getScrollHeight(this.content) - this.domUtils.getScrollTop(this.content) - + this.scrollBottom = Math.abs(scrollHeight - this.domUtils.getScrollTop(this.content) - this.domUtils.getContentHeight(this.content)) < 5; if (this.messagesBeingSent > 0) { diff --git a/src/addon/messages/pages/search/search.html b/src/addon/messages/pages/search/search.html index 99a5675b0..8f21234e0 100644 --- a/src/addon/messages/pages/search/search.html +++ b/src/addon/messages/pages/search/search.html @@ -9,7 +9,7 @@ - + diff --git a/src/addon/messages/pages/search/search.module.ts b/src/addon/messages/pages/search/search.module.ts index 9dc8db426..ba6796a0e 100644 --- a/src/addon/messages/pages/search/search.module.ts +++ b/src/addon/messages/pages/search/search.module.ts @@ -19,6 +19,7 @@ import { AddonMessagesSearchPage } from './search'; import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; import { CorePipesModule } from '@pipes/pipes.module'; +import { CoreSearchComponentsModule } from '@core/search/components/components.module'; import { AddonMessagesComponentsModule } from '../../components/components.module'; @NgModule({ @@ -29,9 +30,10 @@ import { AddonMessagesComponentsModule } from '../../components/components.modul CoreComponentsModule, CoreDirectivesModule, CorePipesModule, + CoreSearchComponentsModule, AddonMessagesComponentsModule, IonicPageModule.forChild(AddonMessagesSearchPage), - TranslateModule.forChild() + TranslateModule.forChild(), ], }) export class AddonMessagesSearchPageModule {} diff --git a/src/addon/messages/pages/settings/settings.html b/src/addon/messages/pages/settings/settings.html index 0172888d9..c955efb2e 100644 --- a/src/addon/messages/pages/settings/settings.html +++ b/src/addon/messages/pages/settings/settings.html @@ -1,6 +1,6 @@ - {{ 'addon.messages.messagepreferences' | translate }} + {{ 'addon.messages.messages' | translate }} diff --git a/src/addon/messages/providers/settings-handler.ts b/src/addon/messages/providers/settings-handler.ts index 6c4d35447..b3a4b62df 100644 --- a/src/addon/messages/providers/settings-handler.ts +++ b/src/addon/messages/providers/settings-handler.ts @@ -44,9 +44,9 @@ export class AddonMessagesSettingsHandler implements CoreSettingsHandler { getDisplayData(): CoreSettingsHandlerData { return { icon: 'chatbubbles', - title: 'addon.messages.messagepreferences', + title: 'addon.messages.messages', page: 'AddonMessagesSettingsPage', - class: 'addon-messages-settings-handler' + class: 'addon-messages-settings-handler', }; } diff --git a/src/addon/mod/assign/assign.module.ts b/src/addon/mod/assign/assign.module.ts index 7a6edbfd6..fcea1f157 100644 --- a/src/addon/mod/assign/assign.module.ts +++ b/src/addon/mod/assign/assign.module.ts @@ -34,7 +34,6 @@ import { AddonModAssignListLinkHandler } from './providers/list-link-handler'; import { AddonModAssignPushClickHandler } from './providers/push-click-handler'; import { AddonModAssignSubmissionModule } from './submission/submission.module'; import { AddonModAssignFeedbackModule } from './feedback/feedback.module'; -import { CoreUpdateManagerProvider } from '@providers/update-manager'; // List of providers (without handlers). export const ADDON_MOD_ASSIGN_PROVIDERS: any[] = [ @@ -73,7 +72,7 @@ export const ADDON_MOD_ASSIGN_PROVIDERS: any[] = [ export class AddonModAssignModule { constructor(moduleDelegate: CoreCourseModuleDelegate, moduleHandler: AddonModAssignModuleHandler, prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModAssignPrefetchHandler, - cronDelegate: CoreCronDelegate, syncHandler: AddonModAssignSyncCronHandler, updateManager: CoreUpdateManagerProvider, + cronDelegate: CoreCronDelegate, syncHandler: AddonModAssignSyncCronHandler, contentLinksDelegate: CoreContentLinksDelegate, linkHandler: AddonModAssignIndexLinkHandler, listLinkHandler: AddonModAssignListLinkHandler, pushNotificationsDelegate: CorePushNotificationsDelegate, pushClickHandler: AddonModAssignPushClickHandler) { @@ -84,57 +83,5 @@ export class AddonModAssignModule { contentLinksDelegate.registerHandler(linkHandler); contentLinksDelegate.registerHandler(listLinkHandler); pushNotificationsDelegate.registerClickHandler(pushClickHandler); - - // Allow migrating the tables from the old app to the new schema. - updateManager.registerSiteTablesMigration([ - { - name: 'mma_mod_assign_submissions', - newName: AddonModAssignOfflineProvider.SUBMISSIONS_TABLE, - fields: [ - { - name: 'assignmentid', - newName: 'assignid' - }, - { - name: 'submitted', - type: 'boolean' - }, - { - name: 'submissionstatement', - type: 'boolean' - }, - { - name: 'plugindata', - type: 'object' - } - ] - }, - { - name: 'mma_mod_assign_submissions_grading', - newName: AddonModAssignOfflineProvider.SUBMISSIONS_GRADES_TABLE, - fields: [ - { - name: 'assignmentid', - newName: 'assignid' - }, - { - name: 'addattempt', - type: 'boolean' - }, - { - name: 'applytoall', - type: 'boolean' - }, - { - name: 'outcomes', - type: 'object' - }, - { - name: 'plugindata', - type: 'object' - } - ] - } - ]); } } diff --git a/src/addon/mod/assign/components/index/addon-mod-assign-index.html b/src/addon/mod/assign/components/index/addon-mod-assign-index.html index 4ccbf69c2..467f9e49d 100644 --- a/src/addon/mod/assign/components/index/addon-mod-assign-index.html +++ b/src/addon/mod/assign/components/index/addon-mod-assign-index.html @@ -15,7 +15,7 @@ - + diff --git a/src/addon/mod/assign/feedback/comments/comments.module.ts b/src/addon/mod/assign/feedback/comments/comments.module.ts index 491a88fde..cb23c6983 100644 --- a/src/addon/mod/assign/feedback/comments/comments.module.ts +++ b/src/addon/mod/assign/feedback/comments/comments.module.ts @@ -21,6 +21,7 @@ import { AddonModAssignFeedbackCommentsComponent } from './component/comments'; import { AddonModAssignFeedbackDelegate } from '../../providers/feedback-delegate'; import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreEditorComponentsModule } from '@core/editor/components/components.module'; @NgModule({ declarations: [ @@ -31,7 +32,8 @@ import { CoreDirectivesModule } from '@directives/directives.module'; IonicModule, TranslateModule.forChild(), CoreComponentsModule, - CoreDirectivesModule + CoreDirectivesModule, + CoreEditorComponentsModule, ], providers: [ AddonModAssignFeedbackCommentsHandler diff --git a/src/addon/mod/assign/feedback/comments/component/addon-mod-assign-feedback-comments.html b/src/addon/mod/assign/feedback/comments/component/addon-mod-assign-feedback-comments.html index 6a2fff370..75892386b 100644 --- a/src/addon/mod/assign/feedback/comments/component/addon-mod-assign-feedback-comments.html +++ b/src/addon/mod/assign/feedback/comments/component/addon-mod-assign-feedback-comments.html @@ -19,5 +19,6 @@ - + + diff --git a/src/addon/mod/assign/pages/edit-feedback-modal/edit-feedback-modal.html b/src/addon/mod/assign/pages/edit-feedback-modal/edit-feedback-modal.html index a51daa3fd..381d0ff43 100644 --- a/src/addon/mod/assign/pages/edit-feedback-modal/edit-feedback-modal.html +++ b/src/addon/mod/assign/pages/edit-feedback-modal/edit-feedback-modal.html @@ -9,7 +9,7 @@ - + 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 41c02baaf..2ebb374f6 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 @@ -12,9 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, Input } from '@angular/core'; +import { Component, Input, ViewChild, ElementRef } from '@angular/core'; import { IonicPage, ViewController, NavParams } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; +import { CoreEventsProvider } from '@providers/events'; +import { CoreSitesProvider } from '@providers/sites'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { AddonModAssignFeedbackDelegate } from '../../providers/feedback-delegate'; import { @@ -36,10 +38,17 @@ export class AddonModAssignEditFeedbackModalPage { @Input() plugin: AddonModAssignPlugin; // The plugin object. @Input() userId: number; // The user ID of the submission. + @ViewChild('editFeedbackForm') formElement: ElementRef; + protected forceLeave = false; // To allow leaving the page without checking for changes. - constructor(params: NavParams, protected viewCtrl: ViewController, protected domUtils: CoreDomUtilsProvider, - protected translate: TranslateService, protected feedbackDelegate: AddonModAssignFeedbackDelegate) { + constructor(params: NavParams, + protected viewCtrl: ViewController, + protected domUtils: CoreDomUtilsProvider, + protected translate: TranslateService, + protected feedbackDelegate: AddonModAssignFeedbackDelegate, + protected eventsProvider: CoreEventsProvider, + protected sitesProvider: CoreSitesProvider) { this.assign = params.get('assign'); this.submission = params.get('submission'); @@ -52,16 +61,17 @@ export class AddonModAssignEditFeedbackModalPage { * * @return Resolved if we can leave it, rejected if not. */ - ionViewCanLeave(): boolean | Promise { + async ionViewCanLeave(): Promise { if (this.forceLeave) { - return true; + return; } - return this.hasDataChanged().then((changed) => { - if (changed) { - return this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit')); - } - }); + const changed = await this.hasDataChanged(); + if (changed) { + await this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit')); + } + + this.domUtils.triggerFormCancelledEvent(this.formElement, this.sitesProvider.getCurrentSiteId()); } /** @@ -82,6 +92,8 @@ export class AddonModAssignEditFeedbackModalPage { e.preventDefault(); e.stopPropagation(); + this.domUtils.triggerFormSubmittedEvent(this.formElement, false, this.sitesProvider.getCurrentSiteId()); + // Close the modal, sending the input data. this.forceLeave = true; this.closeModal(this.getInputData()); diff --git a/src/addon/mod/assign/pages/edit/edit.html b/src/addon/mod/assign/pages/edit/edit.html index c85acfcf9..f849d0390 100644 --- a/src/addon/mod/assign/pages/edit/edit.html +++ b/src/addon/mod/assign/pages/edit/edit.html @@ -13,7 +13,7 @@ -
+ diff --git a/src/addon/mod/assign/pages/edit/edit.ts b/src/addon/mod/assign/pages/edit/edit.ts index 7eccd3c20..87eb9ee29 100644 --- a/src/addon/mod/assign/pages/edit/edit.ts +++ b/src/addon/mod/assign/pages/edit/edit.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, OnInit, OnDestroy } from '@angular/core'; +import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core'; import { IonicPage, NavController, NavParams } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; import { CoreEventsProvider } from '@providers/events'; @@ -34,6 +34,9 @@ import { AddonModAssignHelperProvider } from '../../providers/helper'; templateUrl: 'edit.html', }) export class AddonModAssignEditPage implements OnInit, OnDestroy { + + @ViewChild('editSubmissionForm') formElement: ElementRef; + title: string; // Title to display. assign: AddonModAssignAssign; // Assignment. courseId: number; // Course ID the assignment belongs to. @@ -82,20 +85,21 @@ export class AddonModAssignEditPage implements OnInit, OnDestroy { * * @return Resolved if we can leave it, rejected if not. */ - ionViewCanLeave(): boolean | Promise { + async ionViewCanLeave(): Promise { if (this.forceLeave) { - return true; + return; } // Check if data has changed. - return this.hasDataChanged().then((changed) => { - if (changed) { - return this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit')); - } - }).then(() => { - // Nothing has changed or user confirmed to leave. Clear temporary data from plugins. - this.assignHelper.clearSubmissionPluginTmpData(this.assign, this.userSubmission, this.getInputData()); - }); + const changed = await this.hasDataChanged(); + if (changed) { + await this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit')); + } + + // Nothing has changed or user confirmed to leave. Clear temporary data from plugins. + this.assignHelper.clearSubmissionPluginTmpData(this.assign, this.userSubmission, this.getInputData()); + + this.domUtils.triggerFormCancelledEvent(this.formElement, this.sitesProvider.getCurrentSiteId()); } /** @@ -265,69 +269,74 @@ export class AddonModAssignEditPage implements OnInit, OnDestroy { * * @return Promise resolved when done. */ - protected saveSubmission(): Promise { + protected async saveSubmission(): Promise { const inputData = this.getInputData(); if (this.submissionStatement && (!inputData.submissionstatement || inputData.submissionstatement === 'false')) { - return Promise.reject(this.translate.instant('addon.mod_assign.acceptsubmissionstatement')); + throw this.translate.instant('addon.mod_assign.acceptsubmissionstatement'); } let modal = this.domUtils.showModalLoading(); + let size; // Get size to ask for confirmation. - return this.assignHelper.getSubmissionSizeForEdit(this.assign, this.userSubmission, inputData).catch(() => { + try { + size = await this.assignHelper.getSubmissionSizeForEdit(this.assign, this.userSubmission, inputData); + } catch (error) { // Error calculating size, return -1. - return -1; - }).then((size) => { - modal.dismiss(); + size = -1; + } + modal.dismiss(); + + try { // Confirm action. - return this.fileUploaderHelper.confirmUploadFile(size, true, this.allowOffline); - }).then(() => { + await this.fileUploaderHelper.confirmUploadFile(size, true, this.allowOffline); + modal = this.domUtils.showModalLoading('core.sending', true); - return this.prepareSubmissionData(inputData).then((pluginData) => { - if (!Object.keys(pluginData).length) { - // Nothing to save. - return; - } + const pluginData = await this.prepareSubmissionData(inputData); + if (!Object.keys(pluginData).length) { + // Nothing to save. + return; + } - let promise; + let sent: boolean; - if (this.saveOffline) { - // Save submission in offline. - promise = this.assignOfflineProvider.saveSubmission(this.assign.id, this.courseId, pluginData, - this.userSubmission.timemodified, !this.assign.submissiondrafts, this.userId); - } 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); - } + if (this.saveOffline) { + // Save submission in offline. + sent = false; + await this.assignOfflineProvider.saveSubmission(this.assign.id, this.courseId, pluginData, + this.userSubmission.timemodified, !this.assign.submissiondrafts, this.userId); + } else { + // Try to send it to server. + sent = await this.assignProvider.saveSubmission(this.assign.id, this.courseId, pluginData, this.allowOffline, + this.userSubmission.timemodified, !!this.assign.submissiondrafts, this.userId); + } - return promise.then(() => { - // Clear temporary data from plugins. - return this.assignHelper.clearSubmissionPluginTmpData(this.assign, this.userSubmission, inputData); - }).then(() => { - // Submission saved, trigger event. - const params = { - assignmentId: this.assign.id, - submissionId: this.userSubmission.id, - userId: this.userId, - }; + // Clear temporary data from plugins. + await this.assignHelper.clearSubmissionPluginTmpData(this.assign, this.userSubmission, inputData); - this.eventsProvider.trigger(AddonModAssignProvider.SUBMISSION_SAVED_EVENT, params, - this.sitesProvider.getCurrentSiteId()); + // Submission saved, trigger events. + this.domUtils.triggerFormSubmittedEvent(this.formElement, sent, this.sitesProvider.getCurrentSiteId()); - if (!this.assign.submissiondrafts) { - // No drafts allowed, so it was submitted. Trigger event. - this.eventsProvider.trigger(AddonModAssignProvider.SUBMITTED_FOR_GRADING_EVENT, params, - this.sitesProvider.getCurrentSiteId()); - } - }); - }); - }).finally(() => { + const params = { + assignmentId: this.assign.id, + submissionId: this.userSubmission.id, + userId: this.userId, + }; + + this.eventsProvider.trigger(AddonModAssignProvider.SUBMISSION_SAVED_EVENT, params, + this.sitesProvider.getCurrentSiteId()); + + if (!this.assign.submissiondrafts) { + // No drafts allowed, so it was submitted. Trigger event. + this.eventsProvider.trigger(AddonModAssignProvider.SUBMITTED_FOR_GRADING_EVENT, params, + this.sitesProvider.getCurrentSiteId()); + } + } finally { modal.dismiss(); - }); + } } /** diff --git a/src/addon/mod/assign/providers/prefetch-handler.ts b/src/addon/mod/assign/providers/prefetch-handler.ts index 56d0b4ba0..e67846720 100644 --- a/src/addon/mod/assign/providers/prefetch-handler.ts +++ b/src/addon/mod/assign/providers/prefetch-handler.ts @@ -354,15 +354,7 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan }).then(() => { // Participiants already fetched, we don't need to ignore cache now. return this.assignHelper.getParticipants(assign, group.id, false, siteId).then((participants) => { - const promises = []; - - participants.forEach((participant) => { - if (participant.profileimageurl) { - promises.push(this.filepoolProvider.addToQueueByUrl(siteId, participant.profileimageurl)); - } - }); - - return Promise.all(promises); + return this.userProvider.prefetchUserAvatars(participants, 'profileimageurl', siteId); }).catch(() => { // Fail silently (Moodle < 3.2). }); @@ -443,7 +435,11 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan // Prefetch grade items. if (userId) { - promises.push(this.gradesHelper.getGradeModuleItems(courseId, moduleId, userId, undefined, siteId, true)); + promises.push(this.courseProvider.getModuleBasicGradeInfo(moduleId, siteId).then((gradeInfo) => { + if (gradeInfo) { + promises.push(this.gradesHelper.getGradeModuleItems(courseId, moduleId, userId, undefined, siteId, true)); + } + })); } // Prefetch feedback. 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 a629ab325..a659af7a9 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 @@ -15,6 +15,6 @@

{{ '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 70ee9d183..a788de667 100644 --- a/src/addon/mod/assign/submission/onlinetext/component/onlinetext.ts +++ b/src/addon/mod/assign/submission/onlinetext/component/onlinetext.ts @@ -14,6 +14,7 @@ import { Component, OnInit, ElementRef } from '@angular/core'; import { FormBuilder, FormControl } from '@angular/forms'; +import { CoreSitesProvider } from '@providers/sites'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { AddonModAssignProvider } from '../../../providers/assign'; @@ -35,16 +36,23 @@ export class AddonModAssignSubmissionOnlineTextComponent extends AddonModAssignS text: string; loaded: boolean; wordLimitEnabled: boolean; + currentUserId: number; protected wordCountTimeout: any; protected element: HTMLElement; - constructor(protected fb: FormBuilder, protected domUtils: CoreDomUtilsProvider, protected textUtils: CoreTextUtilsProvider, - protected assignProvider: AddonModAssignProvider, protected assignOfflineProvider: AddonModAssignOfflineProvider, - element: ElementRef) { + constructor( + protected fb: FormBuilder, + protected domUtils: CoreDomUtilsProvider, + protected textUtils: CoreTextUtilsProvider, + protected assignProvider: AddonModAssignProvider, + protected assignOfflineProvider: AddonModAssignOfflineProvider, + element: ElementRef, + sitesProvider: CoreSitesProvider) { super(); this.element = element.nativeElement; + this.currentUserId = sitesProvider.getCurrentSiteUserId(); } /** diff --git a/src/addon/mod/assign/submission/onlinetext/onlinetext.module.ts b/src/addon/mod/assign/submission/onlinetext/onlinetext.module.ts index 88de4de1d..5ac1e16f8 100644 --- a/src/addon/mod/assign/submission/onlinetext/onlinetext.module.ts +++ b/src/addon/mod/assign/submission/onlinetext/onlinetext.module.ts @@ -21,6 +21,7 @@ import { AddonModAssignSubmissionOnlineTextComponent } from './component/onlinet import { AddonModAssignSubmissionDelegate } from '../../providers/submission-delegate'; import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreEditorComponentsModule } from '@core/editor/components/components.module'; @NgModule({ declarations: [ @@ -31,7 +32,8 @@ import { CoreDirectivesModule } from '@directives/directives.module'; IonicModule, TranslateModule.forChild(), CoreComponentsModule, - CoreDirectivesModule + CoreDirectivesModule, + CoreEditorComponentsModule, ], providers: [ AddonModAssignSubmissionOnlineTextHandler diff --git a/src/addon/mod/book/components/index/addon-mod-book-index.html b/src/addon/mod/book/components/index/addon-mod-book-index.html index 6bb6c36d1..6fcc9f70d 100644 --- a/src/addon/mod/book/components/index/addon-mod-book-index.html +++ b/src/addon/mod/book/components/index/addon-mod-book-index.html @@ -19,13 +19,13 @@
- +
{{ 'core.tag.tags' | translate }}:
- +
diff --git a/src/addon/mod/book/components/index/index.ts b/src/addon/mod/book/components/index/index.ts index 40717a6fc..50c69e8bd 100644 --- a/src/addon/mod/book/components/index/index.ts +++ b/src/addon/mod/book/components/index/index.ts @@ -17,7 +17,9 @@ import { Content, ModalController } from 'ionic-angular'; import { CoreAppProvider } from '@providers/app'; import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseModuleMainResourceComponent } from '@core/course/classes/main-resource-component'; -import { AddonModBookProvider, AddonModBookContentsMap, AddonModBookTocChapter } from '../../providers/book'; +import { + AddonModBookProvider, AddonModBookContentsMap, AddonModBookTocChapter, AddonModBookBook, AddonModBookNavStyle +} from '../../providers/book'; import { AddonModBookPrefetchHandler } from '../../providers/prefetch-handler'; import { CoreTagProvider } from '@core/tag/providers/tag'; @@ -33,13 +35,18 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp component = AddonModBookProvider.COMPONENT; chapterContent: string; - previousChapter: string; - nextChapter: string; + previousChapter: AddonModBookTocChapter; + nextChapter: AddonModBookTocChapter; tagsEnabled: boolean; + displayNavBar = true; + previousNavBarTitle: string; + nextNavBarTitle: string; protected chapters: AddonModBookTocChapter[]; protected currentChapter: string; protected contentsMap: AddonModBookContentsMap; + protected book: AddonModBookBook; + protected displayTitlesInNavBar = false; constructor(injector: Injector, private bookProvider: AddonModBookProvider, private courseProvider: CoreCourseProvider, private appProvider: CoreAppProvider, private prefetchDelegate: AddonModBookPrefetchHandler, @@ -69,7 +76,8 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp moduleId: this.module.id, chapters: this.chapters, selected: this.currentChapter, - courseId: this.courseId + courseId: this.courseId, + book: this.book, }, { cssClass: 'core-modal-lateral', showBackdrop: true, enableBackdropDismiss: true, @@ -97,7 +105,7 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp if (chapterId && chapterId != this.currentChapter) { this.loaded = false; this.refreshIcon = 'spinner'; - this.loadChapter(chapterId); + this.loadChapter(chapterId, true); } } @@ -119,19 +127,24 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp protected fetchContent(refresh?: boolean): Promise { const promises = []; let downloadFailed = false; + let downloadFailError; // Try to get the book data. promises.push(this.bookProvider.getBook(this.courseId, this.module.id).then((book) => { + this.book = book; this.dataRetrieved.emit(book); this.description = book.intro; + this.displayNavBar = book.navstyle != AddonModBookNavStyle.TOC_ONLY; + this.displayTitlesInNavBar = book.navstyle == AddonModBookNavStyle.TEXT; }).catch(() => { // Ignore errors since this WS isn't available in some Moodle versions. })); // Download content. This function also loads module contents if needed. - promises.push(this.prefetchDelegate.download(this.module, this.courseId).catch(() => { + promises.push(this.prefetchDelegate.download(this.module, this.courseId).catch((error) => { // Mark download as failed but go on since the main files could have been downloaded. downloadFailed = true; + downloadFailError = error; if (!this.module.contents.length) { // Try to load module contents for offline usage. @@ -160,10 +173,10 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp } // Show chapter. - return this.loadChapter(this.currentChapter).then(() => { + return this.loadChapter(this.currentChapter, refresh).then(() => { if (downloadFailed && this.appProvider.isOnline()) { // We could load the main file but the download failed. Show error message. - this.domUtils.showErrorModal('core.errordownloadingsomefiles', true); + this.showErrorDownloadingSomeFiles(downloadFailError); } }).catch(() => { // Ignore errors, they're handled inside the loadChapter function. @@ -177,9 +190,10 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp * Load a book chapter. * * @param chapterId Chapter to load. + * @param logChapterId Whether chapter ID should be passed to the log view function. * @return Promise resolved when done. */ - protected loadChapter(chapterId: string): Promise { + protected loadChapter(chapterId: string, logChapterId: boolean): Promise { this.currentChapter = chapterId; this.domUtils.scrollToTop(this.content); @@ -188,10 +202,15 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp this.previousChapter = this.bookProvider.getPreviousChapter(this.chapters, chapterId); this.nextChapter = this.bookProvider.getNextChapter(this.chapters, chapterId); + this.previousNavBarTitle = this.previousChapter && this.displayTitlesInNavBar ? + this.translate.instant('addon.mod_book.navprevtitle', {$a: this.previousChapter.title}) : ''; + this.nextNavBarTitle = this.nextChapter && this.displayTitlesInNavBar ? + this.translate.instant('addon.mod_book.navnexttitle', {$a: this.nextChapter.title}) : ''; + // Chapter loaded, log view. We don't return the promise because we don't want to block the user for this. - this.bookProvider.logView(this.module.instance, chapterId, this.module.name).then(() => { + this.bookProvider.logView(this.module.instance, logChapterId ? chapterId : undefined, this.module.name).then(() => { // Module is completed when last chapter is viewed, so we only check completion if the last is reached. - if (this.nextChapter == '0') { + if (!this.nextChapter) { this.courseProvider.checkModuleCompletion(this.courseId, this.module.completiondata); } }).catch(() => { diff --git a/src/addon/mod/book/lang/en.json b/src/addon/mod/book/lang/en.json index 4f8f32f54..200e96ce1 100644 --- a/src/addon/mod/book/lang/en.json +++ b/src/addon/mod/book/lang/en.json @@ -1,6 +1,8 @@ { "errorchapter": "Error reading chapter of book.", "modulenameplural": "Books", + "navnexttitle": "Next: {{$a}}", + "navprevtitle": "Previous: {{$a}}", "tagarea_book_chapters": "Book chapters", "toc": "Table of contents" } \ No newline at end of file diff --git a/src/addon/mod/book/pages/toc/toc.html b/src/addon/mod/book/pages/toc/toc.html index 01fcaa92b..5e6ec0247 100644 --- a/src/addon/mod/book/pages/toc/toc.html +++ b/src/addon/mod/book/pages/toc/toc.html @@ -12,7 +12,11 @@ diff --git a/src/addon/mod/book/pages/toc/toc.scss b/src/addon/mod/book/pages/toc/toc.scss new file mode 100644 index 000000000..2ed350b72 --- /dev/null +++ b/src/addon/mod/book/pages/toc/toc.scss @@ -0,0 +1,7 @@ +ion-app.app-root page-addon-mod-book-toc { + .addon-mod-book-bullet { + font-weight: bold; + font-size: 1.5em; + margin-right: 3px; + } +} diff --git a/src/addon/mod/book/pages/toc/toc.ts b/src/addon/mod/book/pages/toc/toc.ts index 3df7cffc0..d6dc1b3f8 100644 --- a/src/addon/mod/book/pages/toc/toc.ts +++ b/src/addon/mod/book/pages/toc/toc.ts @@ -14,7 +14,7 @@ import { Component } from '@angular/core'; import { IonicPage, NavParams, ViewController } from 'ionic-angular'; -import { AddonModBookTocChapter } from '../../providers/book'; +import { AddonModBookTocChapter, AddonModBookBook, AddonModBookNumbering } from '../../providers/book'; /** * Modal to display the TOC of a book. @@ -29,12 +29,24 @@ export class AddonModBookTocPage { chapters: AddonModBookTocChapter[]; selected: number; courseId: number; + showNumbers = true; + addPadding = true; + showBullets = false; + + protected book: AddonModBookBook; constructor(navParams: NavParams, private viewCtrl: ViewController) { this.moduleId = navParams.get('moduleId'); this.chapters = navParams.get('chapters') || []; this.selected = navParams.get('selected'); this.courseId = navParams.get('courseId'); + this.book = navParams.get('book'); + + if (this.book) { + this.showNumbers = this.book.numbering == AddonModBookNumbering.NUMBERS; + this.showBullets = this.book.numbering == AddonModBookNumbering.BULLETS; + this.addPadding = this.book.numbering != AddonModBookNumbering.NONE; + } } /** diff --git a/src/addon/mod/book/providers/book.ts b/src/addon/mod/book/providers/book.ts index 6944ea73e..95d3264fd 100644 --- a/src/addon/mod/book/providers/book.ts +++ b/src/addon/mod/book/providers/book.ts @@ -13,7 +13,6 @@ // limitations under the License. import { Injectable } from '@angular/core'; -import { Http, Response } from '@angular/http'; import { CoreFileProvider } from '@providers/file'; import { CoreFilepoolProvider } from '@providers/filepool'; import { CoreLoggerProvider } from '@providers/logger'; @@ -25,7 +24,26 @@ 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'; -import { CoreWSExternalWarning, CoreWSExternalFile } from '@providers/ws'; +import { CoreWSProvider, CoreWSExternalWarning, CoreWSExternalFile } from '@providers/ws'; + +/** + * Constants to define how the chapters and subchapters of a book should be displayed in that table of contents. + */ +export const enum AddonModBookNumbering { + NONE = 0, + NUMBERS = 1, + BULLETS = 2, + INDENTED = 3, +} + +/** + * Constants to define the navigation style used within a book. + */ +export const enum AddonModBookNavStyle { + TOC_ONLY = 0, + IMAGE = 1, + TEXT = 2, +} /** * Service that provides some features for books. @@ -37,10 +55,16 @@ export class AddonModBookProvider { protected ROOT_CACHE_KEY = 'mmaModBook:'; protected logger; - constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private textUtils: CoreTextUtilsProvider, - private fileProvider: CoreFileProvider, private filepoolProvider: CoreFilepoolProvider, private http: Http, - private utils: CoreUtilsProvider, private courseProvider: CoreCourseProvider, private domUtils: CoreDomUtilsProvider, - private logHelper: CoreCourseLogHelperProvider) { + constructor(logger: CoreLoggerProvider, + protected sitesProvider: CoreSitesProvider, + protected textUtils: CoreTextUtilsProvider, + protected fileProvider: CoreFileProvider, + protected filepoolProvider: CoreFilepoolProvider, + protected wsProvider: CoreWSProvider, + protected utils: CoreUtilsProvider, + protected courseProvider: CoreCourseProvider, + protected domUtils: CoreDomUtilsProvider, + protected logHelper: CoreCourseLogHelperProvider) { this.logger = logger.getInstance('AddonModBookProvider'); } @@ -128,19 +152,11 @@ export class AddonModBookProvider { return this.sitesProvider.getCurrentSite().checkAndFixPluginfileURL(indexUrl); } - return promise.then((url) => { - // Fetch the URL content. - const promise = this.http.get(url).toPromise(); + return promise.then(async (url) => { + const content = await this.wsProvider.getText(url); - return promise.then((response: Response): any => { - const content = response.text(); - if (typeof content !== 'string') { - return Promise.reject(null); - } else { - // Now that we have the content, we update the SRC to point back to the external resource. - return this.domUtils.restoreSourcesInHtml(content, contentsMap[chapterId].paths); - } - }); + // Now that we have the content, we update the SRC to point back to the external resource. + return this.domUtils.restoreSourcesInHtml(content, contentsMap[chapterId].paths); }); } @@ -218,15 +234,15 @@ export class AddonModBookProvider { * * @param chapters The chapters list. * @param chapterId The current chapter. - * @return The next chapter id. + * @return The next chapter. */ - getNextChapter(chapters: AddonModBookTocChapter[], chapterId: string): string { - let next = '0'; + getNextChapter(chapters: AddonModBookTocChapter[], chapterId: string): AddonModBookTocChapter { + let next: AddonModBookTocChapter; for (let i = 0; i < chapters.length; i++) { if (chapters[i].id == chapterId) { if (typeof chapters[i + 1] != 'undefined') { - next = chapters[i + 1].id; + next = chapters[i + 1]; break; } } @@ -240,16 +256,16 @@ export class AddonModBookProvider { * * @param chapters The chapters list. * @param chapterId The current chapter. - * @return The next chapter id. + * @return The next chapter. */ - getPreviousChapter(chapters: AddonModBookTocChapter[], chapterId: string): string { - let previous = '0'; + getPreviousChapter(chapters: AddonModBookTocChapter[], chapterId: string): AddonModBookTocChapter { + let previous: AddonModBookTocChapter; for (let i = 0; i < chapters.length; i++) { if (chapters[i].id == chapterId) { break; } - previous = chapters[i].id; + previous = chapters[i]; } return previous; diff --git a/src/addon/mod/chat/pages/sessions/sessions.html b/src/addon/mod/chat/pages/sessions/sessions.html index acffafa23..b77fe3601 100644 --- a/src/addon/mod/chat/pages/sessions/sessions.html +++ b/src/addon/mod/chat/pages/sessions/sessions.html @@ -20,7 +20,7 @@ {{ 'addon.mod_chat.showincompletesessions' | translate }} - diff --git a/src/addon/mod/chat/providers/prefetch-handler.ts b/src/addon/mod/chat/providers/prefetch-handler.ts index ecf8cde63..f5c5900b1 100644 --- a/src/addon/mod/chat/providers/prefetch-handler.ts +++ b/src/addon/mod/chat/providers/prefetch-handler.ts @@ -182,9 +182,7 @@ export class AddonModChatPrefetchHandler extends CoreCourseActivityPrefetchHandl }); const userIds = Object.keys(users).map(Number); - return this.userProvider.prefetchProfiles(userIds, courseId, siteId).catch(() => { - // Ignore errors, some users might not exist. - }); + return this.userProvider.prefetchProfiles(userIds, courseId, siteId); }); } } diff --git a/src/addon/mod/choice/choice.module.ts b/src/addon/mod/choice/choice.module.ts index ec5b9dc85..5e00daa85 100644 --- a/src/addon/mod/choice/choice.module.ts +++ b/src/addon/mod/choice/choice.module.ts @@ -26,7 +26,6 @@ import { AddonModChoicePrefetchHandler } from './providers/prefetch-handler'; import { AddonModChoiceSyncProvider } from './providers/sync'; import { AddonModChoiceSyncCronHandler } from './providers/sync-cron-handler'; import { AddonModChoiceOfflineProvider } from './providers/offline'; -import { CoreUpdateManagerProvider } from '@providers/update-manager'; // List of providers (without handlers). export const ADDON_MOD_CHOICE_PROVIDERS: any[] = [ @@ -56,7 +55,7 @@ export class AddonModChoiceModule { constructor(moduleDelegate: CoreCourseModuleDelegate, moduleHandler: AddonModChoiceModuleHandler, prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModChoicePrefetchHandler, contentLinksDelegate: CoreContentLinksDelegate, linkHandler: AddonModChoiceLinkHandler, - cronDelegate: CoreCronDelegate, syncHandler: AddonModChoiceSyncCronHandler, updateManager: CoreUpdateManagerProvider, + cronDelegate: CoreCronDelegate, syncHandler: AddonModChoiceSyncCronHandler, listLinkHandler: AddonModChoiceListLinkHandler) { moduleDelegate.registerHandler(moduleHandler); @@ -64,21 +63,5 @@ export class AddonModChoiceModule { contentLinksDelegate.registerHandler(linkHandler); contentLinksDelegate.registerHandler(listLinkHandler); cronDelegate.register(syncHandler); - - // Allow migrating the tables from the old app to the new schema. - updateManager.registerSiteTableMigration({ - name: 'mma_mod_choice_offline_responses', - newName: AddonModChoiceOfflineProvider.CHOICE_TABLE, - fields: [ - { - name: 'responses', - type: 'object' - }, - { - name: 'deleting', - type: 'boolean' - } - ] - }); } } diff --git a/src/addon/mod/data/data.module.ts b/src/addon/mod/data/data.module.ts index 3f4a8d963..f3b8c0f7b 100644 --- a/src/addon/mod/data/data.module.ts +++ b/src/addon/mod/data/data.module.ts @@ -36,7 +36,6 @@ import { AddonModDataDefaultFieldHandler } from './providers/default-field-handl import { CoreTagAreaDelegate } from '@core/tag/providers/area-delegate'; import { AddonModDataTagAreaHandler } from './providers/tag-area-handler'; import { AddonModDataFieldModule } from './fields/field.module'; -import { CoreUpdateManagerProvider } from '@providers/update-manager'; // List of providers (without handlers). export const ADDON_MOD_DATA_PROVIDERS: any[] = [ @@ -77,7 +76,7 @@ export class AddonModDataModule { constructor(moduleDelegate: CoreCourseModuleDelegate, moduleHandler: AddonModDataModuleHandler, prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModDataPrefetchHandler, contentLinksDelegate: CoreContentLinksDelegate, linkHandler: AddonModDataLinkHandler, - cronDelegate: CoreCronDelegate, syncHandler: AddonModDataSyncCronHandler, updateManager: CoreUpdateManagerProvider, + cronDelegate: CoreCronDelegate, syncHandler: AddonModDataSyncCronHandler, approveLinkHandler: AddonModDataApproveLinkHandler, deleteLinkHandler: AddonModDataDeleteLinkHandler, showLinkHandler: AddonModDataShowLinkHandler, editLinkHandler: AddonModDataEditLinkHandler, listLinkHandler: AddonModDataListLinkHandler, tagAreaDelegate: CoreTagAreaDelegate, @@ -93,21 +92,5 @@ export class AddonModDataModule { contentLinksDelegate.registerHandler(listLinkHandler); cronDelegate.register(syncHandler); tagAreaDelegate.registerHandler(tagAreaHandler); - - // Allow migrating the tables from the old app to the new schema. - updateManager.registerSiteTableMigration({ - name: 'mma_mod_data_entry', - newName: AddonModDataOfflineProvider.DATA_ENTRY_TABLE, - fields: [ - { - name: 'fields', - type: 'object' - }, - { - name: 'dataAndEntry', - delete: true - } - ] - }); } } diff --git a/src/addon/mod/data/data.scss b/src/addon/mod/data/data.scss index f99ba5150..78160c7d2 100644 --- a/src/addon/mod/data/data.scss +++ b/src/addon/mod/data/data.scss @@ -107,14 +107,6 @@ page-addon-mod-data-edit { } } - .input-wp input { - @include padding-horizontal(null, ($item-wp-padding-end / 2)); - border-bottom: 1px solid $list-wp-border-color; - &:focus { - border-color: $text-input-wp-highlight-color; - } - } - ion-select { width: 100%; @include position(null, null, null, 0); diff --git a/src/addon/mod/data/fields/field.module.ts b/src/addon/mod/data/fields/field.module.ts index 0e0467799..9a850d4f6 100644 --- a/src/addon/mod/data/fields/field.module.ts +++ b/src/addon/mod/data/fields/field.module.ts @@ -40,7 +40,7 @@ import { AddonModDataFieldUrlModule } from './url/url.module'; AddonModDataFieldRadiobuttonModule, AddonModDataFieldTextModule, AddonModDataFieldTextareaModule, - AddonModDataFieldUrlModule + AddonModDataFieldUrlModule, ], providers: [ ], diff --git a/src/addon/mod/data/fields/latlong/component/addon-mod-data-field-latlong.html b/src/addon/mod/data/fields/latlong/component/addon-mod-data-field-latlong.html index 5b22625b9..ba9a05723 100644 --- a/src/addon/mod/data/fields/latlong/component/addon-mod-data-field-latlong.html +++ b/src/addon/mod/data/fields/latlong/component/addon-mod-data-field-latlong.html @@ -10,7 +10,7 @@ °E -
+
-
+ diff --git a/src/addon/mod/data/pages/search/search.ts b/src/addon/mod/data/pages/search/search.ts index 5aff12b6b..3b9ab3953 100644 --- a/src/addon/mod/data/pages/search/search.ts +++ b/src/addon/mod/data/pages/search/search.ts @@ -12,9 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component } from '@angular/core'; +import { Component, ViewChild, ElementRef } from '@angular/core'; import { IonicPage, NavParams, ViewController } from 'ionic-angular'; import { FormBuilder, FormGroup } from '@angular/forms'; +import { CoreEventsProvider } from '@providers/events'; +import { CoreSitesProvider } from '@providers/sites'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreTextUtilsProvider } from '@providers/utils/text'; @@ -32,6 +34,8 @@ import { CoreTagProvider } from '@core/tag/providers/tag'; templateUrl: 'search.html', }) export class AddonModDataSearchPage { + @ViewChild('searchFormEl') formElement: ElementRef; + search: any; fields: any; data: any; @@ -41,10 +45,17 @@ export class AddonModDataSearchPage { jsData: any; fieldsArray: any; - constructor(params: NavParams, private viewCtrl: ViewController, fb: FormBuilder, protected utils: CoreUtilsProvider, - protected domUtils: CoreDomUtilsProvider, protected fieldsDelegate: AddonModDataFieldsDelegate, - protected textUtils: CoreTextUtilsProvider, protected dataHelper: AddonModDataHelperProvider, - private tagProvider: CoreTagProvider) { + constructor(params: NavParams, + protected viewCtrl: ViewController, + fb: FormBuilder, + protected utils: CoreUtilsProvider, + protected domUtils: CoreDomUtilsProvider, + protected fieldsDelegate: AddonModDataFieldsDelegate, + protected textUtils: CoreTextUtilsProvider, + protected dataHelper: AddonModDataHelperProvider, + protected tagProvider: CoreTagProvider, + protected eventsProvider: CoreEventsProvider, + protected sitesProvider: CoreSitesProvider) { this.search = params.get('search'); this.fields = params.get('fields'); this.data = params.get('data'); @@ -175,6 +186,12 @@ export class AddonModDataSearchPage { * @param data Data to return to the page. */ closeModal(data?: any): void { + if (typeof data == 'undefined') { + this.domUtils.triggerFormCancelledEvent(this.formElement, this.sitesProvider.getCurrentSiteId()); + } else { + this.domUtils.triggerFormSubmittedEvent(this.formElement, false, this.sitesProvider.getCurrentSiteId()); + } + this.viewCtrl.dismiss(data); } diff --git a/src/addon/mod/data/providers/data.ts b/src/addon/mod/data/providers/data.ts index 0e70b084c..481896b28 100644 --- a/src/addon/mod/data/providers/data.ts +++ b/src/addon/mod/data/providers/data.ts @@ -126,7 +126,8 @@ export class AddonModDataProvider { .then((entry) => { return { // Return provissional entry Id. - newentryid: entry + newentryid: entry, + sent: false, }; }); }; @@ -142,7 +143,11 @@ export class AddonModDataProvider { return storeOffline(); } - return this.addEntryOnline(dataId, contents, groupId, siteId).catch((error) => { + return this.addEntryOnline(dataId, contents, groupId, siteId).then((result) => { + result.sent = true; + + return result; + }).catch((error) => { if (this.utils.isWebServiceError(error)) { // The WebService has thrown an error, this means that responses cannot be submitted. return Promise.reject(error); @@ -194,7 +199,12 @@ export class AddonModDataProvider { const storeOffline = (): Promise => { const action = approve ? 'approve' : 'disapprove'; - return this.dataOffline.saveEntry(dataId, entryId, action, courseId, undefined, undefined, undefined, siteId); + return this.dataOffline.saveEntry(dataId, entryId, action, courseId, undefined, undefined, undefined, siteId) + .then(() => { + return { + sent: false, + }; + }); }; // Get if the opposite action is not synced. @@ -210,7 +220,11 @@ export class AddonModDataProvider { return storeOffline(); } - return this.approveEntryOnline(entryId, approve, siteId).catch((error) => { + return this.approveEntryOnline(entryId, approve, siteId).then(() => { + return { + sent: true, + }; + }).catch((error) => { if (this.utils.isWebServiceError(error)) { // The WebService has thrown an error, this means that responses cannot be submitted. return Promise.reject(error); @@ -288,7 +302,12 @@ export class AddonModDataProvider { // Convenience function to store a data to be synchronized later. const storeOffline = (): Promise => { - return this.dataOffline.saveEntry(dataId, entryId, 'delete', courseId, undefined, undefined, undefined, siteId); + return this.dataOffline.saveEntry(dataId, entryId, 'delete', courseId, undefined, undefined, undefined, siteId) + .then(() => { + return { + sent: false, + }; + }); }; let justAdded = false; @@ -318,7 +337,11 @@ export class AddonModDataProvider { return storeOffline(); } - return this.deleteEntryOnline(entryId, siteId).catch((error) => { + return this.deleteEntryOnline(entryId, siteId).then(() => { + return { + sent: true, + }; + }).catch((error) => { if (this.utils.isWebServiceError(error)) { // The WebService has thrown an error, this means that responses cannot be submitted. return Promise.reject(error); @@ -368,7 +391,8 @@ export class AddonModDataProvider { return this.dataOffline.saveEntry(dataId, entryId, 'edit', courseId, undefined, contents, undefined, siteId) .then(() => { return { - updated: true + updated: true, + sent: false, }; }); }; @@ -408,6 +432,7 @@ export class AddonModDataProvider { return this.addEntry(dataId, entryId, courseId, contents, groupId, fields, siteId, forceOffline) .then((result) => { result.updated = true; + result.sent = true; return result; }); @@ -418,7 +443,11 @@ export class AddonModDataProvider { return storeOffline(); } - return this.editEntryOnline(entryId, contents, siteId).catch((error) => { + return this.editEntryOnline(entryId, contents, siteId).then((result) => { + result.sent = true; + + return result; + }).catch((error) => { if (this.utils.isWebServiceError(error)) { // The WebService has thrown an error, this means that responses cannot be submitted. return Promise.reject(error); diff --git a/src/addon/mod/feedback/feedback.module.ts b/src/addon/mod/feedback/feedback.module.ts index 8478d9254..d816ab94a 100644 --- a/src/addon/mod/feedback/feedback.module.ts +++ b/src/addon/mod/feedback/feedback.module.ts @@ -34,7 +34,6 @@ import { AddonModFeedbackPushClickHandler } from './providers/push-click-handler import { AddonModFeedbackSyncProvider } from './providers/sync'; import { AddonModFeedbackSyncCronHandler } from './providers/sync-cron-handler'; import { AddonModFeedbackOfflineProvider } from './providers/offline'; -import { CoreUpdateManagerProvider } from '@providers/update-manager'; // List of providers (without handlers). export const ADDON_MOD_FEEDBACK_PROVIDERS: any[] = [ @@ -73,7 +72,7 @@ export class AddonModFeedbackModule { prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModFeedbackPrefetchHandler, contentLinksDelegate: CoreContentLinksDelegate, linkHandler: AddonModFeedbackLinkHandler, cronDelegate: CoreCronDelegate, syncHandler: AddonModFeedbackSyncCronHandler, - analysisLinkHandler: AddonModFeedbackAnalysisLinkHandler, updateManager: CoreUpdateManagerProvider, + analysisLinkHandler: AddonModFeedbackAnalysisLinkHandler, showEntriesLinkHandler: AddonModFeedbackShowEntriesLinkHandler, showNonRespondentsLinkHandler: AddonModFeedbackShowNonRespondentsLinkHandler, completeLinkHandler: AddonModFeedbackCompleteLinkHandler, @@ -91,17 +90,5 @@ export class AddonModFeedbackModule { contentLinksDelegate.registerHandler(listLinkHandler); cronDelegate.register(syncHandler); pushNotificationsDelegate.registerClickHandler(pushClickHandler); - - // Allow migrating the tables from the old app to the new schema. - updateManager.registerSiteTableMigration({ - name: 'mma_mod_feedback_responses', - newName: AddonModFeedbackOfflineProvider.FEEDBACK_TABLE, - fields: [ - { - name: 'responses', - type: 'object' - } - ] - }); } } diff --git a/src/addon/mod/feedback/pages/form/form.scss b/src/addon/mod/feedback/pages/form/form.scss index 95b1e2c54..496e55b0a 100644 --- a/src/addon/mod/feedback/pages/form/form.scss +++ b/src/addon/mod/feedback/pages/form/form.scss @@ -9,9 +9,6 @@ ion-app.app-root page-addon-mod-feedback-form { .item-ios .addon-mod_feedback-form-content { @include margin($item-ios-padding-media-top, $item-ios-padding-start, $item-ios-padding-media-bottom, 0); } - .item-wp .addon-mod_feedback-form-content { - @include margin($item-wp-padding-media-top, ($item-wp-padding-end / 2), $item-wp-padding-media-bottom, 0); - } .addon-mod_feedback-postfix { font-size: 1.4rem; } diff --git a/src/addon/mod/folder/components/index/addon-mod-folder-index.html b/src/addon/mod/folder/components/index/addon-mod-folder-index.html index eb68f6b4c..03eb3838a 100644 --- a/src/addon/mod/folder/components/index/addon-mod-folder-index.html +++ b/src/addon/mod/folder/components/index/addon-mod-folder-index.html @@ -4,7 +4,7 @@ - + @@ -17,7 +17,7 @@ - +

{{file.name}}

diff --git a/src/addon/mod/forum/components/components.module.ts b/src/addon/mod/forum/components/components.module.ts index 5a3180af3..e9cdcba14 100644 --- a/src/addon/mod/forum/components/components.module.ts +++ b/src/addon/mod/forum/components/components.module.ts @@ -26,6 +26,7 @@ import { AddonModForumIndexComponent } from './index/index'; import { AddonModForumPostComponent } from './post/post'; import { AddonForumDiscussionOptionsMenuComponent } from './discussion-options-menu/discussion-options-menu'; import { AddonForumPostOptionsMenuComponent } from './post-options-menu/post-options-menu'; +import { CoreEditorComponentsModule } from '@core/editor/components/components.module'; @NgModule({ declarations: [ @@ -43,7 +44,8 @@ import { AddonForumPostOptionsMenuComponent } from './post-options-menu/post-opt CorePipesModule, CoreCourseComponentsModule, CoreRatingComponentsModule, - CoreTagComponentsModule + CoreTagComponentsModule, + CoreEditorComponentsModule, ], providers: [ ], 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 986c6da0c..e941c514a 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 @@ -43,7 +43,7 @@
- +

@@ -57,17 +57,17 @@

{{ 'core.notsent' | translate }}

-
+
- +

-
@@ -94,7 +94,7 @@ -
+
diff --git a/src/addon/mod/forum/components/post/addon-mod-forum-post.html b/src/addon/mod/forum/components/post/addon-mod-forum-post.html index 4aec682b6..10c1f36fc 100644 --- a/src/addon/mod/forum/components/post/addon-mod-forum-post.html +++ b/src/addon/mod/forum/components/post/addon-mod-forum-post.html @@ -10,7 +10,7 @@ - @@ -26,7 +26,7 @@ - @@ -57,18 +57,18 @@
- + {{ 'addon.mod_forum.subject' | translate }} - + {{ 'addon.mod_forum.message' | translate }} - + {{ 'addon.mod_forum.privatereply' | translate }} - + @@ -88,5 +88,5 @@ - + diff --git a/src/addon/mod/forum/components/post/post.ts b/src/addon/mod/forum/components/post/post.ts index 8900dc980..cf4354222 100644 --- a/src/addon/mod/forum/components/post/post.ts +++ b/src/addon/mod/forum/components/post/post.ts @@ -12,7 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, Input, Output, Optional, EventEmitter, OnInit, OnDestroy } from '@angular/core'; +import { + Component, Input, Output, Optional, EventEmitter, OnInit, OnDestroy, ViewChild, ElementRef, OnChanges, SimpleChange +} from '@angular/core'; import { FormControl } from '@angular/forms'; import { Content, PopoverController, ModalController } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; @@ -37,7 +39,7 @@ import { AddonForumPostOptionsMenuComponent } from '../post-options-menu/post-op selector: 'addon-mod-forum-post', templateUrl: 'addon-mod-forum-post.html', }) -export class AddonModForumPostComponent implements OnInit, OnDestroy { +export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges { @Input() post: any; // Post. @Input() courseId: number; // Post's course ID. @Input() discussionId: number; // Post's' discussion ID. @@ -50,8 +52,11 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy { @Input() accessInfo: any; // Forum access information. @Input() parentSubject?: string; // Subject of parent post. @Input() ratingInfo?: CoreRatingInfo; // Rating info item. + @Input() leavingPage?: boolean; // Whether the page that contains this post is being left and will be destroyed. @Output() onPostChange: EventEmitter; // Event emitted when a reply is posted or modified. + @ViewChild('replyFormEl') formElement: ElementRef; + messageControl = new FormControl(); uniqueId: string; @@ -100,6 +105,16 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy { (this.forumProvider.isDeletePostAvailable() || this.forumProvider.isUpdatePostAvailable())); } + /** + * Detect changes on input properties. + */ + ngOnChanges(changes: {[name: string]: SimpleChange}): void { + if (changes.leavingPage && this.leavingPage) { + // Download all courses is enabled now, initialize it. + this.domUtils.triggerFormCancelledEvent(this.formElement, this.sitesProvider.getCurrentSiteId()); + } + } + /** * Deletes an online post. */ @@ -408,6 +423,8 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy { this.onPostChange.emit(); + this.domUtils.triggerFormSubmittedEvent(this.formElement, sent, this.sitesProvider.getCurrentSiteId()); + if (this.syncId) { this.syncProvider.unblockOperation(AddonModForumProvider.COMPONENT, this.syncId); } @@ -426,6 +443,8 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy { // Reset data. this.setReplyFormData(); + this.domUtils.triggerFormCancelledEvent(this.formElement, this.sitesProvider.getCurrentSiteId()); + if (this.syncId) { this.syncProvider.unblockOperation(AddonModForumProvider.COMPONENT, this.syncId); } diff --git a/src/addon/mod/forum/forum.module.ts b/src/addon/mod/forum/forum.module.ts index f38bd9d5f..a41da53ae 100644 --- a/src/addon/mod/forum/forum.module.ts +++ b/src/addon/mod/forum/forum.module.ts @@ -33,7 +33,6 @@ import { AddonModForumPostLinkHandler } from './providers/post-link-handler'; import { AddonModForumPushClickHandler } from './providers/push-click-handler'; import { AddonModForumTagAreaHandler } from './providers/tag-area-handler'; import { AddonModForumComponentsModule } from './components/components.module'; -import { CoreUpdateManagerProvider } from '@providers/update-manager'; // List of providers (without handlers). export const ADDON_MOD_FORUM_PROVIDERS: any[] = [ @@ -70,7 +69,7 @@ export class AddonModForumModule { prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModForumPrefetchHandler, cronDelegate: CoreCronDelegate, syncHandler: AddonModForumSyncCronHandler, linksDelegate: CoreContentLinksDelegate, indexHandler: AddonModForumIndexLinkHandler, discussionHandler: AddonModForumDiscussionLinkHandler, - updateManager: CoreUpdateManagerProvider, listLinkHandler: AddonModForumListLinkHandler, + listLinkHandler: AddonModForumListLinkHandler, pushNotificationsDelegate: CorePushNotificationsDelegate, pushClickHandler: AddonModForumPushClickHandler, postLinkHandler: AddonModForumPostLinkHandler, tagAreaDelegate: CoreTagAreaDelegate, tagAreaHandler: AddonModForumTagAreaHandler) { @@ -84,41 +83,5 @@ export class AddonModForumModule { linksDelegate.registerHandler(postLinkHandler); pushNotificationsDelegate.registerClickHandler(pushClickHandler); tagAreaDelegate.registerHandler(tagAreaHandler); - - // Allow migrating the tables from the old app to the new schema. - updateManager.registerSiteTablesMigration([ - { - name: 'mma_mod_forum_offline_discussions', - newName: AddonModForumOfflineProvider.DISCUSSIONS_TABLE, - fields: [ - { - name: 'forumAndUser', - delete: true - }, - { - name: 'options', - type: 'object' - } - ] - }, - { - name: 'mma_mod_forum_offline_replies', - newName: AddonModForumOfflineProvider.REPLIES_TABLE, - fields: [ - { - name: 'forumAndUser', - delete: true - }, - { - name: 'discussionAndUser', - delete: true - }, - { - name: 'options', - type: 'object' - } - ] - } - ]); } } diff --git a/src/addon/mod/forum/pages/discussion/discussion.html b/src/addon/mod/forum/pages/discussion/discussion.html index 577fe6c57..2cb0c0e84 100644 --- a/src/addon/mod/forum/pages/discussion/discussion.html +++ b/src/addon/mod/forum/pages/discussion/discussion.html @@ -42,13 +42,13 @@
- +
- + @@ -60,7 +60,7 @@ - +
diff --git a/src/addon/mod/forum/pages/discussion/discussion.ts b/src/addon/mod/forum/pages/discussion/discussion.ts index 48fa5d18a..adf2ebbe2 100644 --- a/src/addon/mod/forum/pages/discussion/discussion.ts +++ b/src/addon/mod/forum/pages/discussion/discussion.ts @@ -81,6 +81,7 @@ export class AddonModForumDiscussionPage implements OnDestroy { cmId: number; canPin = false; availabilityMessage: string; + leavingPage = false; protected forumId: number; protected postId: number; @@ -251,20 +252,17 @@ export class AddonModForumDiscussionPage implements OnDestroy { * * @return Resolved if we can leave it, rejected if not. */ - ionViewCanLeave(): boolean | Promise { - let promise: any; + async ionViewCanLeave(): Promise { if (this.forumHelper.hasPostDataChanged(this.replyData, this.originalData)) { // Show confirmation if some data has been modified. - promise = this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit')); - } else { - promise = Promise.resolve(); + await this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit')); } - return promise.then(() => { - // Delete the local files from the tmp folder. - this.uploaderProvider.clearTmpFiles(this.replyData.files); - }); + // Delete the local files from the tmp folder. + this.uploaderProvider.clearTmpFiles(this.replyData.files); + + this.leavingPage = true; } /** diff --git a/src/addon/mod/forum/pages/edit-post/addon-mod-forum-edit-post.html b/src/addon/mod/forum/pages/edit-post/addon-mod-forum-edit-post.html index 0a934fa27..4ffd4c50e 100644 --- a/src/addon/mod/forum/pages/edit-post/addon-mod-forum-edit-post.html +++ b/src/addon/mod/forum/pages/edit-post/addon-mod-forum-edit-post.html @@ -9,14 +9,14 @@ - +
{{ 'addon.mod_forum.subject' | translate }} - + {{ 'addon.mod_forum.message' | translate }} - + @@ -36,5 +36,5 @@ - +
\ No newline at end of file diff --git a/src/addon/mod/forum/pages/edit-post/edit-post.module.ts b/src/addon/mod/forum/pages/edit-post/edit-post.module.ts index 07f409742..89cf9ad4d 100644 --- a/src/addon/mod/forum/pages/edit-post/edit-post.module.ts +++ b/src/addon/mod/forum/pages/edit-post/edit-post.module.ts @@ -19,6 +19,7 @@ import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; import { AddonModForumComponentsModule } from '../../components/components.module'; import { AddonModForumEditPostPage } from './edit-post'; +import { CoreEditorComponentsModule } from '@core/editor/components/components.module'; @NgModule({ declarations: [ @@ -28,6 +29,7 @@ import { AddonModForumEditPostPage } from './edit-post'; CoreComponentsModule, CoreDirectivesModule, AddonModForumComponentsModule, + CoreEditorComponentsModule, IonicPageModule.forChild(AddonModForumEditPostPage), TranslateModule.forChild() ], diff --git a/src/addon/mod/forum/pages/edit-post/edit-post.ts b/src/addon/mod/forum/pages/edit-post/edit-post.ts index 6795c92dc..152d15077 100644 --- a/src/addon/mod/forum/pages/edit-post/edit-post.ts +++ b/src/addon/mod/forum/pages/edit-post/edit-post.ts @@ -12,11 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component } from '@angular/core'; +import { Component, ViewChild, ElementRef } from '@angular/core'; import { FormControl } from '@angular/forms'; import { IonicPage, ViewController, NavParams } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader'; +import { CoreSitesProvider } from '@providers/sites'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { AddonModForumProvider } from '../../providers/forum'; import { AddonModForumHelperProvider } from '../../providers/helper'; @@ -30,6 +31,8 @@ import { AddonModForumHelperProvider } from '../../providers/helper'; templateUrl: 'addon-mod-forum-edit-post.html', }) export class AddonModForumEditPostPage { + @ViewChild('editFormEl') formElement: ElementRef; + component: string; // Component this post belong to. componentId: number; // Component ID. forum: any; // The forum the post belongs to. Required for attachments and offline posts. @@ -48,7 +51,8 @@ export class AddonModForumEditPostPage { protected domUtils: CoreDomUtilsProvider, protected uploaderProvider: CoreFileUploaderProvider, protected forumHelper: AddonModForumHelperProvider, - protected translate: TranslateService) { + protected translate: TranslateService, + protected sitesProvider: CoreSitesProvider) { const post = params.get('post'); this.component = params.get('component'); @@ -114,7 +118,13 @@ export class AddonModForumEditPostPage { * * @param data Data to return to the page. */ - closeModal(data: any): void { + closeModal(data: any, ): void { + if (data) { + this.domUtils.triggerFormSubmittedEvent(this.formElement, false, this.sitesProvider.getCurrentSiteId()); + } else { + this.domUtils.triggerFormCancelledEvent(this.formElement, this.sitesProvider.getCurrentSiteId()); + } + this.viewCtrl.dismiss(data); } diff --git a/src/addon/mod/forum/pages/new-discussion/new-discussion.html b/src/addon/mod/forum/pages/new-discussion/new-discussion.html index 95bb6e5ce..dfed2ccbe 100644 --- a/src/addon/mod/forum/pages/new-discussion/new-discussion.html +++ b/src/addon/mod/forum/pages/new-discussion/new-discussion.html @@ -12,14 +12,14 @@ - +
{{ 'addon.mod_forum.subject' | translate }} - + {{ 'addon.mod_forum.message' | translate }} - + @@ -29,21 +29,21 @@ {{ 'addon.mod_forum.posttomygroups' | translate }} - + {{ 'addon.mod_forum.group' | translate }} - + {{ group.name }} {{ 'addon.mod_forum.discussionsubscription' | translate }} - + {{ 'addon.mod_forum.discussionpinned' | translate }} - + @@ -57,6 +57,6 @@ - +
diff --git a/src/addon/mod/forum/pages/new-discussion/new-discussion.module.ts b/src/addon/mod/forum/pages/new-discussion/new-discussion.module.ts index c0add2f4c..0bb9e4038 100644 --- a/src/addon/mod/forum/pages/new-discussion/new-discussion.module.ts +++ b/src/addon/mod/forum/pages/new-discussion/new-discussion.module.ts @@ -18,6 +18,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; import { AddonModForumNewDiscussionPage } from './new-discussion'; +import { CoreEditorComponentsModule } from '@core/editor/components/components.module'; @NgModule({ declarations: [ @@ -26,6 +27,7 @@ import { AddonModForumNewDiscussionPage } from './new-discussion'; imports: [ CoreComponentsModule, CoreDirectivesModule, + CoreEditorComponentsModule, IonicPageModule.forChild(AddonModForumNewDiscussionPage), TranslateModule.forChild() ], 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 091753865..d9951cc49 100644 --- a/src/addon/mod/forum/pages/new-discussion/new-discussion.ts +++ b/src/addon/mod/forum/pages/new-discussion/new-discussion.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, OnDestroy, Optional, ViewChild } from '@angular/core'; +import { Component, OnDestroy, Optional, ViewChild, ElementRef } from '@angular/core'; import { FormControl } from '@angular/forms'; import { IonicPage, NavController, NavParams } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; @@ -25,7 +25,7 @@ import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; -import { CoreRichTextEditorComponent } from '@components/rich-text-editor/rich-text-editor.ts'; +import { CoreEditorRichTextEditorComponent } from '@core/editor/components/rich-text-editor/rich-text-editor.ts'; import { AddonModForumProvider } from '../../providers/forum'; import { AddonModForumOfflineProvider } from '../../providers/offline'; import { AddonModForumHelperProvider } from '../../providers/helper'; @@ -41,7 +41,8 @@ import { AddonModForumSyncProvider } from '../../providers/sync'; }) export class AddonModForumNewDiscussionPage implements OnDestroy { - @ViewChild(CoreRichTextEditorComponent) messageEditor: CoreRichTextEditorComponent; + @ViewChild('newDiscFormEl') formElement: ElementRef; + @ViewChild(CoreEditorRichTextEditorComponent) messageEditor: CoreEditorRichTextEditorComponent; component = AddonModForumProvider.COMPONENT; messageControl = new FormControl(); @@ -74,6 +75,7 @@ export class AddonModForumNewDiscussionPage implements OnDestroy { protected syncObserver: any; protected isDestroyed = false; protected originalData: any; + protected forceLeave = false; constructor(navParams: NavParams, private navCtrl: NavController, @@ -408,6 +410,7 @@ export class AddonModForumNewDiscussionPage implements OnDestroy { this.newDiscussion.postToAllGroups = false; this.messageEditor.clearText(); this.originalData = this.utils.clone(this.newDiscussion); + this.forceLeave = true; // Avoid asking for confirmation. // Trigger view event, to highlight the current opened discussion in the split view. this.eventsProvider.trigger(AddonModForumProvider.VIEW_DISCUSSION_EVENT, { @@ -415,7 +418,7 @@ export class AddonModForumNewDiscussionPage implements OnDestroy { discussion: 0 }, this.sitesProvider.getCurrentSiteId()); } else { - this.originalData = null; // Avoid asking for confirmation. + this.forceLeave = true; // Avoid asking for confirmation. this.navCtrl.pop(); } } @@ -477,6 +480,8 @@ export class AddonModForumNewDiscussionPage implements OnDestroy { this.domUtils.showErrorModalDefault(null, 'addon.mod_forum.errorposttoallgroups', true); } + this.domUtils.triggerFormSubmittedEvent(this.formElement, !!discussionIds, this.sitesProvider.getCurrentSiteId()); + this.returnToDiscussions(discussionIds, discTimecreated); }).catch((message) => { this.domUtils.showErrorModalDefault(message, 'addon.mod_forum.cannotcreatediscussion', true); @@ -498,6 +503,8 @@ export class AddonModForumNewDiscussionPage implements OnDestroy { })); return Promise.all(promises).then(() => { + this.domUtils.triggerFormCancelledEvent(this.formElement, this.sitesProvider.getCurrentSiteId()); + this.returnToDiscussions(); }); }).catch(() => { @@ -517,20 +524,22 @@ export class AddonModForumNewDiscussionPage implements OnDestroy { * * @return Resolved if we can leave it, rejected if not. */ - ionViewCanLeave(): boolean | Promise { - let promise: any; + async ionViewCanLeave(): Promise { + if (this.forceLeave) { + return; + } if (this.forumHelper.hasPostDataChanged(this.newDiscussion, this.originalData)) { // Show confirmation if some data has been modified. - promise = this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit')); - } else { - promise = Promise.resolve(); + await this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit')); } - return promise.then(() => { - // Delete the local files from the tmp folder. - this.uploaderProvider.clearTmpFiles(this.newDiscussion.files); - }); + // Delete the local files from the tmp folder. + this.uploaderProvider.clearTmpFiles(this.newDiscussion.files); + + if (this.formElement) { + this.domUtils.triggerFormCancelledEvent(this.formElement, this.sitesProvider.getCurrentSiteId()); + } } /** diff --git a/src/addon/mod/forum/providers/forum.ts b/src/addon/mod/forum/providers/forum.ts index 96fd64ce1..3e5f6f956 100644 --- a/src/addon/mod/forum/providers/forum.ts +++ b/src/addon/mod/forum/providers/forum.ts @@ -37,6 +37,7 @@ export class AddonModForumProvider { static VIEW_DISCUSSION_EVENT = 'addon_mod_forum_view_discussion'; static CHANGE_DISCUSSION_EVENT = 'addon_mod_forum_change_discussion_status'; static MARK_READ_EVENT = 'addon_mod_forum_mark_read'; + static LEAVING_POSTS_PAGE = 'addon_mod_forum_leaving_posts_page'; static PREFERENCE_SORTORDER = 'forum_discussionlistsortorder'; static SORTORDER_LASTPOST_DESC = 1; @@ -244,13 +245,14 @@ export class AddonModForumProvider { * Check if a user can post to all groups. * * @param forumId Forum 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) */ - canAddDiscussionToAll(forumId: number): Promise { - return this.canAddDiscussion(forumId, AddonModForumProvider.ALL_PARTICIPANTS); + canAddDiscussionToAll(forumId: number, siteId?: string): Promise { + return this.canAddDiscussion(forumId, AddonModForumProvider.ALL_PARTICIPANTS, siteId); } /** diff --git a/src/addon/mod/forum/providers/prefetch-handler.ts b/src/addon/mod/forum/providers/prefetch-handler.ts index 743d99b97..bf741d780 100644 --- a/src/addon/mod/forum/providers/prefetch-handler.ts +++ b/src/addon/mod/forum/providers/prefetch-handler.ts @@ -107,12 +107,13 @@ export class AddonModForumPrefetchHandler extends CoreCourseActivityPrefetchHand * Get the posts to be prefetched. * * @param forum Forum instance. + * @param siteId Site ID. If not defined, current site. * @return Promise resolved with array of posts. */ - protected getPostsForPrefetch(forum: any): Promise { + protected getPostsForPrefetch(forum: any, siteId?: string): Promise { const promises = this.forumProvider.getAvailableSortOrders().map((sortOrder) => { // Get discussions in first 2 pages. - return this.forumProvider.getDiscussionsInPages(forum.id, sortOrder.value, false, 2).then((response) => { + return this.forumProvider.getDiscussionsInPages(forum.id, sortOrder.value, false, 2, 0, siteId).then((response) => { if (response.error) { return Promise.reject(null); } @@ -120,7 +121,7 @@ export class AddonModForumPrefetchHandler extends CoreCourseActivityPrefetchHand const promises = []; response.discussions.forEach((discussion) => { - promises.push(this.forumProvider.getDiscussionPosts(discussion.discussion)); + promises.push(this.forumProvider.getDiscussionPosts(discussion.discussion, siteId)); }); return Promise.all(promises); @@ -200,41 +201,31 @@ export class AddonModForumPrefetchHandler extends CoreCourseActivityPrefetchHand */ protected prefetchForum(module: any, courseId: number, single: boolean, siteId: string): Promise { // Get the forum data. - return this.forumProvider.getForum(courseId, module.id).then((forum) => { + return this.forumProvider.getForum(courseId, module.id, siteId).then((forum) => { const promises = []; // Prefetch the posts. - promises.push(this.getPostsForPrefetch(forum).then((posts) => { + promises.push(this.getPostsForPrefetch(forum, siteId).then((posts) => { const promises = []; - // Gather user profile images. - const avatars = {}; // List of user avatars, preventing duplicates. - - posts.forEach((post) => { - if (post.userpictureurl) { - avatars[post.userpictureurl] = true; - } - }); - - // Prefetch intro files, attachments, embedded files and user avatars. - const avatarFiles = Object.keys(avatars).map((url) => { - return { fileurl: url }; - }); - const files = this.getIntroFilesFromInstance(module, forum).concat(this.getPostsFiles(posts)).concat(avatarFiles); + const files = this.getIntroFilesFromInstance(module, forum).concat(this.getPostsFiles(posts)); promises.push(this.filepoolProvider.addFilesToQueue(siteId, files, this.component, module.id)); // Prefetch groups data. - promises.push(this.prefetchGroupsInfo(forum, courseId, forum.cancreatediscussions)); + promises.push(this.prefetchGroupsInfo(forum, courseId, forum.cancreatediscussions, siteId)); + + // Prefetch avatars. + promises.push(this.userProvider.prefetchUserAvatars(posts, 'userpictureurl', siteId)); return Promise.all(promises); })); // Prefetch access information. - promises.push(this.forumProvider.getAccessInformation(forum.id)); + promises.push(this.forumProvider.getAccessInformation(forum.id, false, siteId)); // Prefetch sort order preference. if (this.forumProvider.isDiscussionListSortingAvailable()) { - promises.push(this.userProvider.getUserPreference(AddonModForumProvider.PREFERENCE_SORTORDER)); + promises.push(this.userProvider.getUserPreference(AddonModForumProvider.PREFERENCE_SORTORDER, siteId)); } return Promise.all(promises); @@ -247,30 +238,31 @@ export class AddonModForumPrefetchHandler extends CoreCourseActivityPrefetchHand * @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. + * @param siteId Site ID. If not defined, current site. * @return Promise resolved when group data has been prefetched. */ - protected prefetchGroupsInfo(forum: any, courseId: number, canCreateDiscussions: boolean): any { + protected prefetchGroupsInfo(forum: any, courseId: number, canCreateDiscussions: boolean, siteId?: string): any { // Check group mode. - return this.groupsProvider.getActivityGroupMode(forum.cmid).then((mode) => { + return this.groupsProvider.getActivityGroupMode(forum.cmid, siteId).then((mode) => { if (mode !== CoreGroupsProvider.SEPARATEGROUPS && mode !== CoreGroupsProvider.VISIBLEGROUPS) { // Activity doesn't use groups. Prefetch canAddDiscussionToAll to determine if user can pin/attach. - return this.forumProvider.canAddDiscussionToAll(forum.id).catch(() => { + return this.forumProvider.canAddDiscussionToAll(forum.id, siteId).catch(() => { // Ignore errors. }); } // Activity uses groups, prefetch allowed groups. - return this.groupsProvider.getActivityAllowedGroups(forum.cmid).then((result) => { + return this.groupsProvider.getActivityAllowedGroups(forum.cmid, undefined, siteId).then((result) => { if (mode === CoreGroupsProvider.SEPARATEGROUPS) { // Groups are already filtered by WS. Prefetch canAddDiscussionToAll to determine if user can pin/attach. - return this.forumProvider.canAddDiscussionToAll(forum.id).catch(() => { + return this.forumProvider.canAddDiscussionToAll(forum.id, siteId).catch(() => { // Ignore errors. }); } if (canCreateDiscussions) { // Prefetch data to check the visible groups when creating discussions. - return this.forumProvider.canAddDiscussionToAll(forum.id).catch(() => { + return this.forumProvider.canAddDiscussionToAll(forum.id, siteId).catch(() => { // The call failed, let's assume he can't. return { status: false @@ -284,7 +276,7 @@ export class AddonModForumPrefetchHandler extends CoreCourseActivityPrefetchHand // The user can't post to all groups, let's check which groups he can post to. const groupPromises = []; result.groups.forEach((group) => { - groupPromises.push(this.forumProvider.canAddDiscussion(forum.id, group.id).catch(() => { + groupPromises.push(this.forumProvider.canAddDiscussion(forum.id, group.id, siteId).catch(() => { // Ignore errors. })); }); diff --git a/src/addon/mod/glossary/components/components.module.ts b/src/addon/mod/glossary/components/components.module.ts index 6567b7ca8..ca3ffdcce 100644 --- a/src/addon/mod/glossary/components/components.module.ts +++ b/src/addon/mod/glossary/components/components.module.ts @@ -20,6 +20,7 @@ import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; import { CorePipesModule } from '@pipes/pipes.module'; import { CoreCourseComponentsModule } from '@core/course/components/components.module'; +import { CoreSearchComponentsModule } from '@core/search/components/components.module'; import { AddonModGlossaryIndexComponent } from './index/index'; import { AddonModGlossaryModePickerPopoverComponent } from './mode-picker/mode-picker'; @@ -35,7 +36,8 @@ import { AddonModGlossaryModePickerPopoverComponent } from './mode-picker/mode-p CoreComponentsModule, CoreDirectivesModule, CorePipesModule, - CoreCourseComponentsModule + CoreCourseComponentsModule, + CoreSearchComponentsModule, ], providers: [ ], diff --git a/src/addon/mod/glossary/components/index/addon-mod-glossary-index.html b/src/addon/mod/glossary/components/index/addon-mod-glossary-index.html index fb1fcde3a..235c51297 100644 --- a/src/addon/mod/glossary/components/index/addon-mod-glossary-index.html +++ b/src/addon/mod/glossary/components/index/addon-mod-glossary-index.html @@ -1,7 +1,10 @@ - + @@ -29,16 +32,16 @@ {{ 'core.hasdatatosync' | translate:{$a: moduleName} }} - + - + {{ 'addon.mod_glossary.entriestobesynced' | translate }} -

{{entry.concept}}

+

@@ -50,7 +53,7 @@ -

{{entry.concept}}

+

diff --git a/src/addon/mod/glossary/components/index/index.ts b/src/addon/mod/glossary/components/index/index.ts index cc1550134..41bfbd760 100644 --- a/src/addon/mod/glossary/components/index/index.ts +++ b/src/addon/mod/glossary/components/index/index.ts @@ -25,7 +25,7 @@ import { AddonModGlossarySyncProvider } from '../../providers/sync'; import { AddonModGlossaryModePickerPopoverComponent } from '../mode-picker/mode-picker'; import { AddonModGlossaryPrefetchHandler } from '../../providers/prefetch-handler'; -type FetchMode = 'author_all' | 'cat_all' | 'newest_first' | 'recently_updated' | 'search' | 'letter_all'; +type FetchMode = 'author_all' | 'cat_all' | 'newest_first' | 'recently_updated' | 'letter_all'; /** * Component that displays a glossary entry page. @@ -41,8 +41,6 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity component = AddonModGlossaryProvider.COMPONENT; moduleName = 'glossary'; - fetchMode: FetchMode; - viewMode: string; isSearch = false; entries = []; offlineEntries = []; @@ -60,6 +58,10 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity protected showDivider: (entry: any, previous?: any) => boolean; protected getDivider: (entry: any) => string; protected addEntryObserver: any; + protected fetchMode: FetchMode; + protected viewMode: string; + protected fetchedEntriesCanLoadMore = false; + protected fetchedEntries = []; hasOfflineRatings: boolean; protected ratingOfflineObserver: any; @@ -187,6 +189,21 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity Array.prototype.push.apply(this.entries, result.entries); } else { this.entries = result.entries; + + if (this.splitviewCtrl.isOn()) { + // Load the first entry. + if (this.entries.length > 0) { + const found = this.selectedEntry && this.entries.some((entry) => entry.id == this.selectedEntry); + + // The current selected entry is not found in the current list, open first item. + if (!found) { + this.openEntry(this.entries[0].id); + } + } else { + this.selectedEntry = null; + this.splitviewCtrl.emptyDetails(); + } + } } this.canLoadMore = this.entries.length < result.count; }).catch((error) => { @@ -252,6 +269,7 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity */ protected switchMode(mode: FetchMode): void { this.fetchMode = mode; + this.isSearch = false; switch (mode) { case 'author_all': @@ -294,15 +312,6 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity this.getDivider = null; this.showDivider = (): boolean => false; break; - case 'search': - // Search for entries. - this.viewMode = 'search'; - this.fetchFunction = this.glossaryProvider.getEntriesBySearch; - this.fetchInvalidate = this.glossaryProvider.invalidateEntriesBySearch; - this.fetchArguments = null; // Dynamically set later. - this.getDivider = null; - this.showDivider = (): boolean => false; - break; case 'letter_all': default: // Consider it is 'letter_all'. @@ -311,7 +320,12 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity this.fetchFunction = this.glossaryProvider.getEntriesByLetter; this.fetchInvalidate = this.glossaryProvider.invalidateEntriesByLetter; this.fetchArguments = [this.glossary.id, 'ALL']; - this.getDivider = (entry: any): string => entry.concept.substr(0, 1).toUpperCase(); + this.getDivider = (entry: any): string => { + // Try to get the first letter without HTML tags. + const noTags = this.textUtils.cleanTags(entry.concept); + + return (noTags || entry.concept).substr(0, 1).toUpperCase(); + }; this.showDivider = (entry?: any, previous?: any): boolean => { return !previous || this.getDivider(entry) != this.getDivider(previous); }; @@ -341,26 +355,15 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity */ openModePicker(event: MouseEvent): void { const popover = this.popoverCtrl.create(AddonModGlossaryModePickerPopoverComponent, { - glossary: this.glossary, - selectedMode: this.fetchMode + browsemodes: this.glossary.browsemodes, + selectedMode: this.isSearch ? '' : this.fetchMode }); - popover.onDidDismiss((newMode: FetchMode) => { - if (newMode === this.fetchMode) { - return; - } - - this.loadingMessage = this.translate.instant('core.loading'); - this.domUtils.scrollToTop(this.content); - this.switchMode(newMode); - - if (this.fetchMode === 'search') { - // If it's not an instant search, then we reset the values. - this.entries = []; - this.canLoadMore = false; - } else { - this.loaded = false; - this.loadContent(); + popover.onDidDismiss((mode: FetchMode) => { + if (mode !== this.fetchMode) { + this.changeFetchMode(mode); + } else if (this.isSearch) { + this.toggleSearch(); } }); @@ -369,6 +372,44 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity }); } + /** + * Toggles between search and fetch mode. + */ + toggleSearch(): void { + if (this.isSearch) { + this.isSearch = false; + this.entries = this.fetchedEntries; + this.canLoadMore = this.fetchedEntriesCanLoadMore; + this.switchMode(this.fetchMode); + } else { + // Search for entries. + this.fetchFunction = this.glossaryProvider.getEntriesBySearch; + this.fetchInvalidate = this.glossaryProvider.invalidateEntriesBySearch; + this.fetchArguments = null; // Dynamically set later. + this.getDivider = null; + this.showDivider = (): boolean => false; + this.isSearch = true; + + this.fetchedEntries = this.entries; + this.fetchedEntriesCanLoadMore = this.canLoadMore; + this.canLoadMore = false; + this.entries = []; + } + } + + /** + * Change fetch mode + * @param {FetchMode} mode [description] + */ + changeFetchMode(mode: FetchMode): void { + this.isSearch = false; + this.loadingMessage = this.translate.instant('core.loading'); + this.domUtils.scrollToTop(this.content); + this.switchMode(mode); + this.loaded = false; + this.loadContent(); + } + /** * Opens an entry. * 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 d7e51905c..baac2a4f4 100644 --- a/src/addon/mod/glossary/components/mode-picker/mode-picker.ts +++ b/src/addon/mod/glossary/components/mode-picker/mode-picker.ts @@ -27,14 +27,10 @@ export class AddonModGlossaryModePickerPopoverComponent { selectedMode: string; constructor(navParams: NavParams, private viewCtrl: ViewController) { - this.selectedMode = navParams.get('selectedMode'); - const glossary = navParams.get('glossary'); + this.selectedMode = navParams.get('selectedMode') || ''; + const browsemodes = navParams.get('browsemodes'); - // Preparing browse modes. - this.modes = [ - {key: 'search', langkey: 'addon.mod_glossary.bysearch'} - ]; - glossary.browsemodes.forEach((mode) => { + browsemodes.forEach((mode) => { switch (mode) { case 'letter' : this.modes.push({key: 'letter_all', langkey: 'addon.mod_glossary.byalphabet'}); diff --git a/src/addon/mod/glossary/glossary.module.ts b/src/addon/mod/glossary/glossary.module.ts index e1ad371ea..06ba0a136 100644 --- a/src/addon/mod/glossary/glossary.module.ts +++ b/src/addon/mod/glossary/glossary.module.ts @@ -31,7 +31,6 @@ import { AddonModGlossaryListLinkHandler } from './providers/list-link-handler'; import { AddonModGlossaryEditLinkHandler } from './providers/edit-link-handler'; import { AddonModGlossaryTagAreaHandler } from './providers/tag-area-handler'; import { AddonModGlossaryComponentsModule } from './components/components.module'; -import { CoreUpdateManagerProvider } from '@providers/update-manager'; // List of providers (without handlers). export const ADDON_MOD_GLOSSARY_PROVIDERS: any[] = [ @@ -67,7 +66,7 @@ export class AddonModGlossaryModule { prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModGlossaryPrefetchHandler, cronDelegate: CoreCronDelegate, syncHandler: AddonModGlossarySyncCronHandler, linksDelegate: CoreContentLinksDelegate, indexHandler: AddonModGlossaryIndexLinkHandler, discussionHandler: AddonModGlossaryEntryLinkHandler, - updateManager: CoreUpdateManagerProvider, listLinkHandler: AddonModGlossaryListLinkHandler, + listLinkHandler: AddonModGlossaryListLinkHandler, editLinkHandler: AddonModGlossaryEditLinkHandler, tagAreaDelegate: CoreTagAreaDelegate, tagAreaHandler: AddonModGlossaryTagAreaHandler) { @@ -79,29 +78,5 @@ export class AddonModGlossaryModule { linksDelegate.registerHandler(listLinkHandler); linksDelegate.registerHandler(editLinkHandler); tagAreaDelegate.registerHandler(tagAreaHandler); - - // Allow migrating the tables from the old app to the new schema. - updateManager.registerSiteTableMigration({ - name: 'mma_mod_glossary_add_entry', - newName: AddonModGlossaryOfflineProvider.ENTRIES_TABLE, - fields: [ - { - name: 'glossaryAndConcept', - delete: true - }, - { - name: 'glossaryAndUser', - delete: true - }, - { - name: 'options', - type: 'object' - }, - { - name: 'attachments', - type: 'object' - } - ] - }); } } diff --git a/src/addon/mod/glossary/pages/edit/edit.html b/src/addon/mod/glossary/pages/edit/edit.html index 3c3bfa0d7..98bf66757 100644 --- a/src/addon/mod/glossary/pages/edit/edit.html +++ b/src/addon/mod/glossary/pages/edit/edit.html @@ -8,24 +8,24 @@ - +
{{ 'addon.mod_glossary.concept' | translate }} - + {{ 'addon.mod_glossary.definition' | translate }} - + {{ 'addon.mod_glossary.categories' | translate }} - + {{ category.name }} {{ 'addon.mod_glossary.aliases' | translate }} - + {{ 'addon.mod_glossary.attachment' | translate }} @@ -33,17 +33,17 @@ {{ 'addon.mod_glossary.linking' | translate }} {{ 'addon.mod_glossary.entryusedynalink' | translate }} - + {{ 'addon.mod_glossary.casesensitive' | translate }} - + {{ 'addon.mod_glossary.fullmatch' | translate }} - + - +
diff --git a/src/addon/mod/glossary/pages/edit/edit.module.ts b/src/addon/mod/glossary/pages/edit/edit.module.ts index 0f606e93e..926f32ad6 100644 --- a/src/addon/mod/glossary/pages/edit/edit.module.ts +++ b/src/addon/mod/glossary/pages/edit/edit.module.ts @@ -18,6 +18,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; import { AddonModGlossaryEditPage } from './edit'; +import { CoreEditorComponentsModule } from '@core/editor/components/components.module'; @NgModule({ declarations: [ @@ -26,6 +27,7 @@ import { AddonModGlossaryEditPage } from './edit'; imports: [ CoreComponentsModule, CoreDirectivesModule, + CoreEditorComponentsModule, IonicPageModule.forChild(AddonModGlossaryEditPage), TranslateModule.forChild() ], diff --git a/src/addon/mod/glossary/pages/edit/edit.ts b/src/addon/mod/glossary/pages/edit/edit.ts index 7bb9e60eb..5bc201f8c 100644 --- a/src/addon/mod/glossary/pages/edit/edit.ts +++ b/src/addon/mod/glossary/pages/edit/edit.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, OnInit } from '@angular/core'; +import { Component, OnInit, ViewChild, ElementRef } from '@angular/core'; import { FormControl } from '@angular/forms'; import { IonicPage, NavController, NavParams } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; @@ -34,6 +34,8 @@ import { AddonModGlossaryHelperProvider } from '../../providers/helper'; templateUrl: 'edit.html', }) export class AddonModGlossaryEditPage implements OnInit { + @ViewChild('editFormEl') formElement: ElementRef; + component = AddonModGlossaryProvider.COMPONENT; loaded = false; entry = { @@ -51,6 +53,7 @@ export class AddonModGlossaryEditPage implements OnInit { attachments = []; definitionControl = new FormControl(); categories = []; + editorExtraParams: {[name: string]: any} = {}; protected courseId: number; protected module: any; @@ -113,6 +116,10 @@ export class AddonModGlossaryEditPage implements OnInit { this.originalData.files = files.slice(); }); } + + if (entry.id) { + this.editorExtraParams.id = entry.id; + } } this.definitionControl.setValue(this.entry.definition); @@ -140,20 +147,20 @@ export class AddonModGlossaryEditPage implements OnInit { * * @return Resolved if we can leave it, rejected if not. */ - ionViewCanLeave(): boolean | Promise { - let promise: any; - - if (!this.saved && this.glossaryHelper.hasEntryDataChanged(this.entry, this.attachments, this.originalData)) { - // Show confirmation if some data has been modified. - promise = this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit')); - } else { - promise = Promise.resolve(); + async ionViewCanLeave(): Promise { + if (this.saved) { + return; } - return promise.then(() => { - // Delete the local files from the tmp folder. - this.uploaderProvider.clearTmpFiles(this.attachments); - }); + if (this.glossaryHelper.hasEntryDataChanged(this.entry, this.attachments, this.originalData)) { + // Show confirmation if some data has been modified. + await this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit')); + } + + // Delete the local files from the tmp folder. + this.uploaderProvider.clearTmpFiles(this.attachments); + + this.domUtils.triggerFormCancelledEvent(this.formElement, this.sitesProvider.getCurrentSiteId()); } /** @@ -246,6 +253,8 @@ export class AddonModGlossaryEditPage implements OnInit { }; this.eventsProvider.trigger(AddonModGlossaryProvider.ADD_ENTRY_EVENT, data, this.sitesProvider.getCurrentSiteId()); + this.domUtils.triggerFormSubmittedEvent(this.formElement, !!entryId, this.sitesProvider.getCurrentSiteId()); + this.saved = true; this.navCtrl.pop(); }).catch((error) => { diff --git a/src/addon/mod/glossary/pages/entry/entry.html b/src/addon/mod/glossary/pages/entry/entry.html index 47cc11542..fc67df806 100644 --- a/src/addon/mod/glossary/pages/entry/entry.html +++ b/src/addon/mod/glossary/pages/entry/entry.html @@ -9,7 +9,7 @@ - +

diff --git a/src/addon/mod/glossary/providers/prefetch-handler.ts b/src/addon/mod/glossary/providers/prefetch-handler.ts index 590294ad7..30bdf4903 100644 --- a/src/addon/mod/glossary/providers/prefetch-handler.ts +++ b/src/addon/mod/glossary/providers/prefetch-handler.ts @@ -26,6 +26,7 @@ import { AddonModGlossaryProvider } from './glossary'; import { AddonModGlossarySyncProvider } from './sync'; import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; +import { CoreUserProvider } from '@core/user/providers/user'; /** * Handler to prefetch forums. @@ -48,7 +49,8 @@ export class AddonModGlossaryPrefetchHandler extends CoreCourseActivityPrefetchH pluginFileDelegate: CorePluginFileDelegate, protected glossaryProvider: AddonModGlossaryProvider, protected commentsProvider: CoreCommentsProvider, - protected syncProvider: AddonModGlossarySyncProvider) { + protected syncProvider: AddonModGlossarySyncProvider, + protected userProvider: CoreUserProvider) { super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper, pluginFileDelegate); @@ -165,35 +167,29 @@ export class AddonModGlossaryPrefetchHandler extends CoreCourseActivityPrefetchH // Fetch all entries to get information from. promises.push(this.glossaryProvider.fetchAllEntries(this.glossaryProvider.getEntriesByLetter, [glossary.id, 'ALL'], false, false, siteId).then((entries) => { - const promises = [], - commentsEnabled = !this.commentsProvider.areCommentsDisabledInSite(), - avatars = {}; // List of user avatars, preventing duplicates. + const promises = []; + const commentsEnabled = !this.commentsProvider.areCommentsDisabledInSite(); entries.forEach((entry) => { // Don't fetch individual entries, it's too many WS calls. - if (entry.userpictureurl) { - avatars[entry.userpictureurl] = true; - } - if (glossary.allowcomments && commentsEnabled) { promises.push(this.commentsProvider.getComments('module', glossary.coursemodule, 'mod_glossary', entry.id, 'glossary_entry', 0, siteId)); } }); - // Prefetch intro files, entries files and user avatars. - const avatarFiles = Object.keys(avatars).map((url) => { - return { fileurl: url }; - }); - const files = this.getFilesFromGlossaryAndEntries(module, glossary, entries).concat(avatarFiles); + const files = this.getFilesFromGlossaryAndEntries(module, glossary, entries); promises.push(this.filepoolProvider.addFilesToQueue(siteId, files, this.component, module.id)); + // Prefetch user avatars. + promises.push(this.userProvider.prefetchUserAvatars(entries, 'userpictureurl', siteId)); + return Promise.all(promises); })); // Get all categories. - promises.push(this.glossaryProvider.getAllCategories(glossary.id)); + promises.push(this.glossaryProvider.getAllCategories(glossary.id, siteId)); // Prefetch data for link handlers. promises.push(this.courseProvider.getModuleBasicInfo(module.id, siteId)); diff --git a/src/addon/mod/imscp/components/index/index.ts b/src/addon/mod/imscp/components/index/index.ts index c6690b324..a17aa8de4 100644 --- a/src/addon/mod/imscp/components/index/index.ts +++ b/src/addon/mod/imscp/components/index/index.ts @@ -76,6 +76,7 @@ export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceCom */ protected fetchContent(refresh?: boolean): Promise { let downloadFailed = false; + let downloadFailError; const promises = []; promises.push(this.imscpProvider.getImscp(this.courseId, this.module.id).then((imscp) => { @@ -83,9 +84,10 @@ export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceCom this.dataRetrieved.emit(imscp); })); - promises.push(this.imscpPrefetch.download(this.module, this.courseId).catch(() => { + promises.push(this.imscpPrefetch.download(this.module, this.courseId).catch((error) => { // Mark download as failed but go on since the main files could have been downloaded. downloadFailed = true; + downloadFailError = error; return this.courseProvider.loadModuleContents(this.module, this.courseId).catch((error) => { // Error getting module contents, fail. @@ -109,7 +111,7 @@ export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceCom }).then(() => { if (downloadFailed && this.appProvider.isOnline()) { // We could load the main file but the download failed. Show error message. - this.domUtils.showErrorModal('core.errordownloadingsomefiles', true); + this.showErrorDownloadingSomeFiles(downloadFailError); } }).finally(() => { diff --git a/src/addon/mod/label/label.scss b/src/addon/mod/label/label.scss index 46d2a8600..111c3c1d5 100644 --- a/src/addon/mod/label/label.scss +++ b/src/addon/mod/label/label.scss @@ -1,5 +1,6 @@ -.item.core-course-module-handler.addon-mod-label-handler { +ion-app.app-root .item.core-course-module-handler.addon-mod-label-handler { align-items: center; + cursor: auto; &:hover { opacity: 1; @@ -13,7 +14,3 @@ .ios .item.core-course-module-handler.addon-mod-label-handler .item-inner { padding-bottom: $item-ios-padding-bottom; } - -.wp .item.core-course-module-handler.addon-mod-label-handler .item-inner { - padding-bottom: $item-wp-padding-bottom; -} diff --git a/src/addon/mod/lesson/components/index/addon-mod-lesson-index.html b/src/addon/mod/lesson/components/index/addon-mod-lesson-index.html index 2bf84b83a..f7ac672e0 100644 --- a/src/addon/mod/lesson/components/index/addon-mod-lesson-index.html +++ b/src/addon/mod/lesson/components/index/addon-mod-lesson-index.html @@ -34,7 +34,7 @@ -
+ {{ 'addon.mod_lesson.enterpassword' | translate }} @@ -244,7 +244,7 @@ {{ 'addon.mod_lesson.overview' | translate }} - +

{{ student.fullname }}

diff --git a/src/addon/mod/lesson/components/index/index.ts b/src/addon/mod/lesson/components/index/index.ts index 17dec12ee..c8c4fb1ed 100644 --- a/src/addon/mod/lesson/components/index/index.ts +++ b/src/addon/mod/lesson/components/index/index.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, Optional, Injector, Input, ViewChild } from '@angular/core'; +import { Component, Optional, Injector, Input, ViewChild, ElementRef } from '@angular/core'; import { Content, NavController } from 'ionic-angular'; import { CoreGroupsProvider, CoreGroupInfo } from '@providers/groups'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; @@ -35,6 +35,7 @@ import { CoreTabsComponent } from '@components/tabs/tabs'; }) export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityComponent { @ViewChild(CoreTabsComponent) tabsComponent: CoreTabsComponent; + @ViewChild('passwordForm') formElement: ElementRef; @Input() group: number; // The group to display. @Input() action: string; // The "action" to display first. @@ -584,6 +585,8 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo this.loaded = true; this.refreshIcon = 'refresh'; this.syncIcon = 'sync'; + + this.domUtils.triggerFormSubmittedEvent(this.formElement, true, this.siteId); }); } diff --git a/src/addon/mod/lesson/lesson.module.ts b/src/addon/mod/lesson/lesson.module.ts index 8fcce6995..29d874b01 100644 --- a/src/addon/mod/lesson/lesson.module.ts +++ b/src/addon/mod/lesson/lesson.module.ts @@ -31,7 +31,6 @@ import { AddonModLessonGradeLinkHandler } from './providers/grade-link-handler'; import { AddonModLessonReportLinkHandler } from './providers/report-link-handler'; import { AddonModLessonListLinkHandler } from './providers/list-link-handler'; import { AddonModLessonPushClickHandler } from './providers/push-click-handler'; -import { CoreUpdateManagerProvider } from '@providers/update-manager'; // List of providers (without handlers). export const ADDON_MOD_LESSON_PROVIDERS: any[] = [ @@ -67,7 +66,7 @@ export class AddonModLessonModule { prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModLessonPrefetchHandler, cronDelegate: CoreCronDelegate, syncHandler: AddonModLessonSyncCronHandler, linksDelegate: CoreContentLinksDelegate, indexHandler: AddonModLessonIndexLinkHandler, gradeHandler: AddonModLessonGradeLinkHandler, - reportHandler: AddonModLessonReportLinkHandler, updateManager: CoreUpdateManagerProvider, + reportHandler: AddonModLessonReportLinkHandler, listLinkHandler: AddonModLessonListLinkHandler, pushNotificationsDelegate: CorePushNotificationsDelegate, pushClickHandler: AddonModLessonPushClickHandler) { @@ -79,71 +78,5 @@ export class AddonModLessonModule { linksDelegate.registerHandler(reportHandler); linksDelegate.registerHandler(listLinkHandler); pushNotificationsDelegate.registerClickHandler(pushClickHandler); - - // Allow migrating the tables from the old app to the new schema. - updateManager.registerSiteTablesMigration([ - { - name: 'mma_mod_lesson_password', - newName: AddonModLessonProvider.PASSWORD_TABLE, - fields: [ - { - name: 'id', - newName: 'lessonid' - } - ] - }, - { - name: 'mma_mod_lesson_retakes', - newName: AddonModLessonOfflineProvider.RETAKES_TABLE, - fields: [ - { - name: 'finished', - type: 'boolean' - }, - { - name: 'outoftime', - type: 'boolean' - } - ] - }, - { - name: 'mma_mod_lesson_page_attempts', - newName: AddonModLessonOfflineProvider.PAGE_ATTEMPTS_TABLE, - fields: [ - { - name: 'lessonAndPage', - delete: true - }, - { - name: 'lessonAndRetake', - delete: true - }, - { - name: 'lessonAndRetakeAndType', - delete: true - }, - { - name: 'lessonAndRetakeAndPage', - delete: true - }, - { - name: 'data', - type: 'object' - }, - { - name: 'correct', - type: 'boolean' - }, - { - name: 'userAnswer', - type: 'object' - } - ] - }, - { - name: 'mma_mod_lesson_retakes_finished_sync', - newName: AddonModLessonSyncProvider.RETAKES_FINISHED_TABLE - } - ]); } } diff --git a/src/addon/mod/lesson/pages/password-modal/password-modal.html b/src/addon/mod/lesson/pages/password-modal/password-modal.html index b673d540c..3c35a3dcc 100644 --- a/src/addon/mod/lesson/pages/password-modal/password-modal.html +++ b/src/addon/mod/lesson/pages/password-modal/password-modal.html @@ -9,7 +9,7 @@ - + {{ 'addon.mod_lesson.enterpassword' | translate }} 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 13fbfcdb1..03278e4c0 100644 --- a/src/addon/mod/lesson/pages/password-modal/password-modal.ts +++ b/src/addon/mod/lesson/pages/password-modal/password-modal.ts @@ -12,8 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component } from '@angular/core'; +import { Component, ViewChild, ElementRef } from '@angular/core'; import { IonicPage, ViewController } from 'ionic-angular'; +import { CoreEventsProvider } from '@providers/events'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; /** * Modal that asks the password for a lesson. @@ -24,8 +27,12 @@ import { IonicPage, ViewController } from 'ionic-angular'; templateUrl: 'password-modal.html', }) export class AddonModLessonPasswordModalPage { + @ViewChild('passwordForm') formElement: ElementRef; - constructor(protected viewCtrl: ViewController) { } + constructor(protected viewCtrl: ViewController, + protected eventsProvider: CoreEventsProvider, + protected sitesProvider: CoreSitesProvider, + protected domUtils: CoreDomUtilsProvider) { } /** * Send the password back. @@ -37,6 +44,8 @@ export class AddonModLessonPasswordModalPage { e.preventDefault(); e.stopPropagation(); + this.domUtils.triggerFormSubmittedEvent(this.formElement, false, this.sitesProvider.getCurrentSiteId()); + this.viewCtrl.dismiss(password.value); } @@ -44,6 +53,8 @@ export class AddonModLessonPasswordModalPage { * Close modal. */ closeModal(): void { + this.domUtils.triggerFormCancelledEvent(this.formElement, this.sitesProvider.getCurrentSiteId()); + this.viewCtrl.dismiss(); } } diff --git a/src/addon/mod/lesson/pages/player/player.html b/src/addon/mod/lesson/pages/player/player.html index 13c7e7f0b..dfe4c3e86 100644 --- a/src/addon/mod/lesson/pages/player/player.html +++ b/src/addon/mod/lesson/pages/player/player.html @@ -38,7 +38,7 @@ - + @@ -57,7 +57,7 @@ - +

{{ 'addon.mod_lesson.youranswer' | translate }}

diff --git a/src/addon/mod/lesson/pages/player/player.module.ts b/src/addon/mod/lesson/pages/player/player.module.ts index 361ae19ab..c6dd35097 100644 --- a/src/addon/mod/lesson/pages/player/player.module.ts +++ b/src/addon/mod/lesson/pages/player/player.module.ts @@ -18,6 +18,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; import { AddonModLessonPlayerPage } from './player'; +import { CoreEditorComponentsModule } from '@core/editor/components/components.module'; @NgModule({ declarations: [ @@ -26,6 +27,7 @@ import { AddonModLessonPlayerPage } from './player'; imports: [ CoreComponentsModule, CoreDirectivesModule, + CoreEditorComponentsModule, IonicPageModule.forChild(AddonModLessonPlayerPage), TranslateModule.forChild() ], diff --git a/src/addon/mod/lesson/pages/player/player.scss b/src/addon/mod/lesson/pages/player/player.scss index 750d00287..b31e8cab1 100644 --- a/src/addon/mod/lesson/pages/player/player.scss +++ b/src/addon/mod/lesson/pages/player/player.scss @@ -49,10 +49,6 @@ ion-app.app-root page-addon-mod-lesson-player { border-bottom: 1px solid $list-md-border-color; } - .item-wp table td { - border-bottom: 1px solid $list-wp-border-color; - } - .item-ios table { @extend .card-ios; @include darkmode() { @@ -68,12 +64,4 @@ ion-app.app-root page-addon-mod-lesson-player { 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/lesson/pages/player/player.ts b/src/addon/mod/lesson/pages/player/player.ts index c29ad99b5..d8fa3b011 100644 --- a/src/addon/mod/lesson/pages/player/player.ts +++ b/src/addon/mod/lesson/pages/player/player.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, OnInit, OnDestroy, ViewChild, ChangeDetectorRef } from '@angular/core'; +import { Component, OnInit, OnDestroy, ViewChild, ChangeDetectorRef, ElementRef } from '@angular/core'; import { FormBuilder, FormGroup } from '@angular/forms'; import { IonicPage, NavParams, Content, PopoverController, ModalController, Modal, NavController } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; @@ -41,6 +41,7 @@ import { AddonModLessonHelperProvider } from '../../providers/helper'; }) export class AddonModLessonPlayerPage implements OnInit, OnDestroy { @ViewChild(Content) content: Content; + @ViewChild('questionFormEl') formElement: ElementRef; component = AddonModLessonProvider.COMPONENT; LESSON_EOL = AddonModLessonProvider.LESSON_EOL; @@ -136,19 +137,19 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy { * * @return Resolved if we can leave it, rejected if not. */ - ionViewCanLeave(): boolean | Promise { + async ionViewCanLeave(): Promise { if (this.forceLeave) { - return true; + return; } if (this.question && !this.eolData && !this.processData && this.originalData) { // Question shown. Check if there is any change. if (!this.utils.basicLeftCompare(this.questionForm.getRawValue(), this.originalData, 3)) { - return this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit')); + await this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit')); } } - return Promise.resolve(); + this.domUtils.triggerFormCancelledEvent(this.formElement, this.sitesProvider.getCurrentSiteId()); } /** @@ -540,15 +541,20 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy { * Process a page, sending some data. * * @param data The data to send. + * @param formSubmitted Whether a form was submitted. * @return Promise resolved when done. */ - protected processPage(data: any): Promise { + protected processPage(data: any, formSubmitted?: boolean): Promise { this.loaded = false; const args = [this.lesson, this.courseId, this.pageData, data, this.password, this.review, this.offline, this.accessInfo, this.jumps]; return this.callFunction(this.lessonProvider.processPage.bind(this.lessonProvider), args, 6, 8).then((result) => { + if (formSubmitted) { + this.domUtils.triggerFormSubmittedEvent(this.formElement, result.sent, this.sitesProvider.getCurrentSiteId()); + } + if (!this.offline && !this.review && this.lessonProvider.isLessonOffline(this.lesson)) { // Lesson allows offline and the user changed some data in server. Update cached data. const retake = this.accessInfo.attemptscount; @@ -637,7 +643,7 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy { // Use getRawValue to include disabled values. const data = this.lessonHelper.prepareQuestionData(this.question, this.questionForm.getRawValue()); - this.processPage(data).finally(() => { + this.processPage(data, true).finally(() => { this.loaded = true; }); } diff --git a/src/addon/mod/lesson/providers/lesson.ts b/src/addon/mod/lesson/providers/lesson.ts index b7fabe1f1..a4db30c6f 100644 --- a/src/addon/mod/lesson/providers/lesson.ts +++ b/src/addon/mod/lesson/providers/lesson.ts @@ -3089,6 +3089,7 @@ export class AddonModLessonProvider { result.warnings = []; result.displaymenu = pageData.displaymenu; // Keep the same value since we can't calculate it in offline. result.messages = this.getPageProcessMessages(lesson, accessInfo, result, review, jumps); + result.sent = false; Object.assign(result, calculatedData); return result; @@ -3104,6 +3105,8 @@ export class AddonModLessonProvider { review: review }, this.sitesProvider.getCurrentSiteId()); + response.sent = true; + return response; }); } diff --git a/src/addon/mod/page/components/index/index.ts b/src/addon/mod/page/components/index/index.ts index 9ee38717e..b8300246a 100644 --- a/src/addon/mod/page/components/index/index.ts +++ b/src/addon/mod/page/components/index/index.ts @@ -78,11 +78,13 @@ export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComp */ protected fetchContent(refresh?: boolean): Promise { let downloadFailed = false; + let downloadFailError; // Download content. This function also loads module contents if needed. - return this.pagePrefetch.download(this.module, this.courseId).catch(() => { + return this.pagePrefetch.download(this.module, this.courseId).catch((error) => { // Mark download as failed but go on since the main files could have been downloaded. downloadFailed = true; + downloadFailError = error; }).then(() => { if (!this.module.contents.length) { // Try to load module contents for offline usage. @@ -132,7 +134,7 @@ export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComp if (downloadFailed && this.appProvider.isOnline()) { // We could load the main file but the download failed. Show error message. - this.domUtils.showErrorModal('core.errordownloadingsomefiles', true); + this.showErrorDownloadingSomeFiles(downloadFailError); } })); diff --git a/src/addon/mod/page/providers/helper.ts b/src/addon/mod/page/providers/helper.ts index 43887b52a..d95ff5a3b 100644 --- a/src/addon/mod/page/providers/helper.ts +++ b/src/addon/mod/page/providers/helper.ts @@ -20,7 +20,7 @@ import { CoreFilepoolProvider } from '@providers/filepool'; import { AddonModPageProvider } from './page'; import { CoreFileProvider } from '@providers/file'; import { CoreSitesProvider } from '@providers/sites'; -import { Http, Response } from '@angular/http'; +import { CoreWSProvider } from '@providers/ws'; /** * Service that provides some features for page. @@ -30,9 +30,13 @@ export class AddonModPageHelperProvider { protected logger; - constructor(logger: CoreLoggerProvider, private domUtils: CoreDomUtilsProvider, private filepoolProvider: CoreFilepoolProvider, - private fileProvider: CoreFileProvider, private textUtils: CoreTextUtilsProvider, private http: Http, - private sitesProvider: CoreSitesProvider) { + constructor(logger: CoreLoggerProvider, + protected domUtils: CoreDomUtilsProvider, + protected filepoolProvider: CoreFilepoolProvider, + protected fileProvider: CoreFileProvider, + protected textUtils: CoreTextUtilsProvider, + protected wsProvider: CoreWSProvider, + protected sitesProvider: CoreSitesProvider) { this.logger = logger.getInstance('AddonModPageHelperProvider'); } @@ -79,21 +83,12 @@ export class AddonModPageHelperProvider { promise = this.sitesProvider.getCurrentSite().checkAndFixPluginfileURL(indexUrl); } - return promise.then((url) => { + return promise.then(async (url) => { + const content = await this.wsProvider.getText(url); - // Fetch the URL content. - const promise = this.http.get(url).toPromise(); - - return promise.then((response: Response): any => { - const content = response.text(); - if (typeof content !== 'string') { - return Promise.reject(null); - } - - // Now that we have the content, we update the SRC to point back to the external resource. - // That will be caught by core-format-text. - return this.domUtils.restoreSourcesInHtml(content, paths); - }); + // Now that we have the content, we update the SRC to point back to the external resource. + // That will be caught by core-format-text. + return this.domUtils.restoreSourcesInHtml(content, paths); }); } diff --git a/src/addon/mod/quiz/accessrules/password/password.module.ts b/src/addon/mod/quiz/accessrules/password/password.module.ts index 8063d7685..941af9fed 100644 --- a/src/addon/mod/quiz/accessrules/password/password.module.ts +++ b/src/addon/mod/quiz/accessrules/password/password.module.ts @@ -20,7 +20,6 @@ import { CoreComponentsModule } from '@components/components.module'; import { AddonModQuizAccessPasswordHandler } from './providers/handler'; import { AddonModQuizAccessPasswordComponent } from './component/password'; import { AddonModQuizAccessRuleDelegate } from '../../providers/access-rules-delegate'; -import { CoreUpdateManagerProvider } from '@providers/update-manager'; @NgModule({ declarations: [ @@ -43,14 +42,7 @@ import { CoreUpdateManagerProvider } from '@providers/update-manager'; ] }) export class AddonModQuizAccessPasswordModule { - constructor(accessRuleDelegate: AddonModQuizAccessRuleDelegate, handler: AddonModQuizAccessPasswordHandler, - updateManager: CoreUpdateManagerProvider) { + constructor(accessRuleDelegate: AddonModQuizAccessRuleDelegate, handler: AddonModQuizAccessPasswordHandler) { accessRuleDelegate.registerHandler(handler); - - // Allow migrating the tables from the old app to the new schema. - updateManager.registerSiteTableMigration({ - name: 'mod_quiz_access_password', - newName: AddonModQuizAccessPasswordHandler.PASSWORD_TABLE - }); } } 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 a522eae7b..88de59576 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 @@ -52,7 +52,7 @@
-
+ {{ 'addon.mod_quiz.preview' | translate }} {{ attempt.attempt }} diff --git a/src/addon/mod/quiz/pages/attempt/attempt.html b/src/addon/mod/quiz/pages/attempt/attempt.html index 2998605d7..791991ada 100644 --- a/src/addon/mod/quiz/pages/attempt/attempt.html +++ b/src/addon/mod/quiz/pages/attempt/attempt.html @@ -31,7 +31,7 @@

- diff --git a/src/addon/mod/quiz/pages/player/player.html b/src/addon/mod/quiz/pages/player/player.html index e632829c1..9936714cf 100644 --- a/src/addon/mod/quiz/pages/player/player.html +++ b/src/addon/mod/quiz/pages/player/player.html @@ -45,7 +45,7 @@
- +
@@ -118,8 +118,8 @@

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

{{message}}

- {{ 'core.openinbrowser' | translate }} + diff --git a/src/addon/mod/quiz/pages/player/player.ts b/src/addon/mod/quiz/pages/player/player.ts index 81f7989d5..908918fbf 100644 --- a/src/addon/mod/quiz/pages/player/player.ts +++ b/src/addon/mod/quiz/pages/player/player.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, OnInit, OnDestroy, ViewChild, ChangeDetectorRef, ViewChildren, QueryList } from '@angular/core'; +import { Component, OnInit, OnDestroy, ViewChild, ChangeDetectorRef, ViewChildren, QueryList, ElementRef } from '@angular/core'; import { IonicPage, NavParams, Content, PopoverController, ModalController, Modal, NavController } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; import { CoreEventsProvider } from '@providers/events'; @@ -41,6 +41,7 @@ import { Subscription } from 'rxjs'; export class AddonModQuizPlayerPage implements OnInit, OnDestroy { @ViewChild(Content) content: Content; @ViewChildren(CoreQuestionComponent) questionComponents: QueryList; + @ViewChild('quizForm') formElement: ElementRef; quiz: any; // The quiz the attempt belongs to. attempt: any; // The attempt being attempted. @@ -136,26 +137,28 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { * * @return Resolved if we can leave it, rejected if not. */ - ionViewCanLeave(): boolean | Promise { + async ionViewCanLeave(): Promise { if (this.forceLeave) { - return true; + return; } if (this.questions && this.questions.length && !this.showSummary) { // Save answers. const modal = this.domUtils.showModalLoading('core.sending', true); - return this.processAttempt(false, false).catch(() => { + try { + await this.processAttempt(false, false); + } catch (error) { // Save attempt failed. Show confirmation. modal.dismiss(); - return this.domUtils.showConfirm(this.translate.instant('addon.mod_quiz.confirmleavequizonerror')); - }).finally(() => { - modal.dismiss(); - }); - } + await this.domUtils.showConfirm(this.translate.instant('addon.mod_quiz.confirmleavequizonerror')); - return Promise.resolve(); + this.domUtils.triggerFormCancelledEvent(this.formElement, this.sitesProvider.getCurrentSiteId()); + } finally { + modal.dismiss(); + } + } } /** @@ -585,6 +588,10 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { // Answers saved, cancel auto save. this.autoSave.cancelAutoSave(); this.autoSave.hideAutoSaveError(); + + if (this.formElement) { + this.domUtils.triggerFormSubmittedEvent(this.formElement, !this.offline, this.sitesProvider.getCurrentSiteId()); + } }); } diff --git a/src/addon/mod/quiz/pages/preflight-modal/preflight-modal.html b/src/addon/mod/quiz/pages/preflight-modal/preflight-modal.html index c00c08277..69d32d7ab 100644 --- a/src/addon/mod/quiz/pages/preflight-modal/preflight-modal.html +++ b/src/addon/mod/quiz/pages/preflight-modal/preflight-modal.html @@ -10,7 +10,7 @@ - + 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 590d03137..794b15b8e 100644 --- a/src/addon/mod/quiz/pages/preflight-modal/preflight-modal.ts +++ b/src/addon/mod/quiz/pages/preflight-modal/preflight-modal.ts @@ -12,10 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, OnInit, Injector, ViewChild } from '@angular/core'; +import { Component, OnInit, Injector, ViewChild, ElementRef } from '@angular/core'; import { IonicPage, ViewController, NavParams, Content } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; import { FormBuilder, FormGroup } from '@angular/forms'; +import { CoreEventsProvider } from '@providers/events'; import { CoreSitesProvider } from '@providers/sites'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { AddonModQuizAccessRuleDelegate } from '../../providers/access-rules-delegate'; @@ -31,6 +32,7 @@ import { AddonModQuizAccessRuleDelegate } from '../../providers/access-rules-del export class AddonModQuizPreflightModalPage implements OnInit { @ViewChild(Content) content: Content; + @ViewChild('preflightFormEl') formElement: ElementRef; preflightForm: FormGroup; title: string; @@ -43,9 +45,15 @@ export class AddonModQuizPreflightModalPage implements OnInit { protected siteId: string; protected rules: string[]; - constructor(params: NavParams, fb: FormBuilder, translate: TranslateService, sitesProvider: CoreSitesProvider, - protected viewCtrl: ViewController, protected accessRuleDelegate: AddonModQuizAccessRuleDelegate, - protected injector: Injector, protected domUtils: CoreDomUtilsProvider) { + constructor(params: NavParams, + fb: FormBuilder, + translate: TranslateService, + sitesProvider: CoreSitesProvider, + protected viewCtrl: ViewController, + protected accessRuleDelegate: AddonModQuizAccessRuleDelegate, + protected injector: Injector, + protected domUtils: CoreDomUtilsProvider, + protected eventsProvider: CoreEventsProvider) { this.title = params.get('title') || translate.instant('addon.mod_quiz.startattempt'); this.quiz = params.get('quiz'); @@ -112,6 +120,8 @@ export class AddonModQuizPreflightModalPage implements OnInit { this.domUtils.showErrorModal('core.errorinvalidform', true); } } else { + this.domUtils.triggerFormSubmittedEvent(this.formElement, false, this.siteId); + this.viewCtrl.dismiss(this.preflightForm.value); } } @@ -120,6 +130,8 @@ export class AddonModQuizPreflightModalPage implements OnInit { * Close modal. */ closeModal(): void { + this.domUtils.triggerFormCancelledEvent(this.formElement, this.siteId); + this.viewCtrl.dismiss(); } } diff --git a/src/addon/mod/quiz/providers/helper.ts b/src/addon/mod/quiz/providers/helper.ts index 706c43f8a..c83293381 100644 --- a/src/addon/mod/quiz/providers/helper.ts +++ b/src/addon/mod/quiz/providers/helper.ts @@ -111,9 +111,10 @@ export class AddonModQuizHelperProvider { */ getPreflightData(quiz: any, accessInfo: any, attempt: any, prefetch?: boolean, title?: string, siteId?: string): Promise { const notSupported: string[] = []; + const rules = accessInfo && accessInfo.activerulenames; // Check if there is any unsupported rule. - accessInfo.activerulenames.forEach((rule) => { + rules.forEach((rule) => { if (!this.accessRuleDelegate.isAccessRuleSupported(rule)) { notSupported.push(rule); } @@ -131,7 +132,7 @@ export class AddonModQuizHelperProvider { attempt: attempt, prefetch: !!prefetch, siteId: siteId, - rules: accessInfo.activerulenames + rules: rules }); modal.present(); @@ -296,7 +297,7 @@ export class AddonModQuizHelperProvider { validatePreflightData(quiz: any, accessInfo: any, preflightData: any, attempt: any, offline?: boolean, prefetch?: boolean, siteId?: string): Promise { - const rules = accessInfo.activerulenames; + const rules = accessInfo && accessInfo.activerulenames; let promise; if (attempt) { diff --git a/src/addon/mod/quiz/providers/prefetch-handler.ts b/src/addon/mod/quiz/providers/prefetch-handler.ts index 0040dd8e8..5ec4d9cbf 100644 --- a/src/addon/mod/quiz/providers/prefetch-handler.ts +++ b/src/addon/mod/quiz/providers/prefetch-handler.ts @@ -160,7 +160,7 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl quiz, accessInfo, preflightData, attempt, false, true, title, siteId); } else { // Get some fixed preflight data from access rules (data that doesn't require user interaction). - const rules = accessInfo.activerulenames; + const rules = accessInfo && accessInfo.activerulenames; promise = this.accessRuleDelegate.getFixedPreflightData(rules, quiz, preflightData, attempt, true, siteId).then(() => { if (!attempt) { diff --git a/src/addon/mod/quiz/quiz.module.ts b/src/addon/mod/quiz/quiz.module.ts index e77749f5f..62e92519f 100644 --- a/src/addon/mod/quiz/quiz.module.ts +++ b/src/addon/mod/quiz/quiz.module.ts @@ -32,7 +32,6 @@ import { AddonModQuizReviewLinkHandler } from './providers/review-link-handler'; import { AddonModQuizListLinkHandler } from './providers/list-link-handler'; import { AddonModQuizPushClickHandler } from './providers/push-click-handler'; import { AddonModQuizComponentsModule } from './components/components.module'; -import { CoreUpdateManagerProvider } from '@providers/update-manager'; // Access rules. import { AddonModQuizAccessDelayBetweenAttemptsModule } from './accessrules/delaybetweenattempts/delaybetweenattempts.module'; @@ -90,7 +89,7 @@ export class AddonModQuizModule { prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModQuizPrefetchHandler, cronDelegate: CoreCronDelegate, syncHandler: AddonModQuizSyncCronHandler, linksDelegate: CoreContentLinksDelegate, indexHandler: AddonModQuizIndexLinkHandler, gradeHandler: AddonModQuizGradeLinkHandler, - reviewHandler: AddonModQuizReviewLinkHandler, updateManager: CoreUpdateManagerProvider, + reviewHandler: AddonModQuizReviewLinkHandler, listLinkHandler: AddonModQuizListLinkHandler, pushNotificationsDelegate: CorePushNotificationsDelegate, pushClickHandler: AddonModQuizPushClickHandler) { @@ -102,21 +101,5 @@ export class AddonModQuizModule { linksDelegate.registerHandler(reviewHandler); linksDelegate.registerHandler(listLinkHandler); pushNotificationsDelegate.registerClickHandler(pushClickHandler); - - // Allow migrating the tables from the old app to the new schema. - updateManager.registerSiteTableMigration({ - name: 'mod_quiz_attempts', - newName: AddonModQuizOfflineProvider.ATTEMPTS_TABLE, - fields: [ - { - name: 'quizAndUser', - delete: true - }, - { - name: 'finished', - type: 'boolean' - } - ] - }); } } diff --git a/src/addon/mod/resource/components/index/index.ts b/src/addon/mod/resource/components/index/index.ts index 12913fe40..8875bae06 100644 --- a/src/addon/mod/resource/components/index/index.ts +++ b/src/addon/mod/resource/components/index/index.ts @@ -14,6 +14,7 @@ import { Component, Injector } from '@angular/core'; import { CoreAppProvider } from '@providers/app'; +import { CoreFilepoolProvider } from '@providers/filepool'; import { CoreSitesProvider } from '@providers/sites'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCourseProvider } from '@core/course/providers/course'; @@ -38,10 +39,15 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource contentText: string; displayDescription = true; - constructor(injector: Injector, private resourceProvider: AddonModResourceProvider, private courseProvider: CoreCourseProvider, - private appProvider: CoreAppProvider, private prefetchHandler: AddonModResourcePrefetchHandler, - private resourceHelper: AddonModResourceHelperProvider, private sitesProvider: CoreSitesProvider, - private utils: CoreUtilsProvider) { + constructor(injector: Injector, + protected resourceProvider: AddonModResourceProvider, + protected courseProvider: CoreCourseProvider, + protected appProvider: CoreAppProvider, + protected prefetchHandler: AddonModResourcePrefetchHandler, + protected resourceHelper: AddonModResourceHelperProvider, + protected sitesProvider: CoreSitesProvider, + protected utils: CoreUtilsProvider, + protected filepoolProvider: CoreFilepoolProvider) { super(injector); } @@ -104,10 +110,12 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource if (this.resourceHelper.isDisplayedInIframe(this.module)) { let downloadFailed = false; + let downloadFailError; - return this.prefetchHandler.download(this.module, this.courseId).catch(() => { + return this.prefetchHandler.download(this.module, this.courseId).catch((error) => { // Mark download as failed but go on since the main files could have been downloaded. downloadFailed = true; + downloadFailError = error; }).then(() => { return this.resourceHelper.getIframeSrc(this.module).then((src) => { this.mode = 'iframe'; @@ -125,7 +133,7 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource if (downloadFailed && this.appProvider.isOnline()) { // We could load the main file but the download failed. Show error message. - this.domUtils.showErrorModal('core.errordownloadingsomefiles', true); + this.showErrorDownloadingSomeFiles(downloadFailError); } }); }); @@ -147,15 +155,23 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource /** * Opens a file. + * + * @return Promise resolved when done. */ - open(): void { - this.prefetchHandler.isDownloadable(this.module, this.courseId).then((downloadable) => { + async open(): Promise { + let downloadable = await this.prefetchHandler.isDownloadable(this.module, this.courseId); + + if (downloadable) { + // Check if the main file is downloadle. + // This isn't done in "isDownloadable" to prevent extra WS calls in the course page. + downloadable = await this.resourceHelper.isMainFileDownloadable(this.module); + if (downloadable) { - this.resourceHelper.openModuleFile(this.module, this.courseId); - } else { - // The resource cannot be downloaded, open the activity in browser. - return this.sitesProvider.getCurrentSite().openInBrowserWithAutoLoginIfSameSite(this.module.url); + return this.resourceHelper.openModuleFile(this.module, this.courseId); } - }); + } + + // The resource cannot be downloaded, open the activity in browser. + return this.sitesProvider.getCurrentSite().openInBrowserWithAutoLoginIfSameSite(this.module.url); } } diff --git a/src/addon/mod/resource/providers/helper.ts b/src/addon/mod/resource/providers/helper.ts index 401783103..cdbf17270 100644 --- a/src/addon/mod/resource/providers/helper.ts +++ b/src/addon/mod/resource/providers/helper.ts @@ -20,6 +20,7 @@ import { AddonModResourceProvider } from './resource'; import { CoreSitesProvider } from '@providers/sites'; import { CoreFilepoolProvider } from '@providers/filepool'; import { CoreFileProvider } from '@providers/file'; +import { CoreFileHelperProvider } from '@providers/file-helper'; import { CoreAppProvider } from '@providers/app'; import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype'; import { CoreTextUtilsProvider } from '@providers/utils/text'; @@ -36,11 +37,17 @@ export class AddonModResourceHelperProvider { // Display using object tag. protected DISPLAY_EMBED = 1; - constructor(private courseProvider: CoreCourseProvider, private domUtils: CoreDomUtilsProvider, - private resourceProvider: AddonModResourceProvider, private courseHelper: CoreCourseHelperProvider, - private textUtils: CoreTextUtilsProvider, private mimetypeUtils: CoreMimetypeUtilsProvider, - private fileProvider: CoreFileProvider, private appProvider: CoreAppProvider, - private filepoolProvider: CoreFilepoolProvider, private sitesProvider: CoreSitesProvider) { + constructor(protected courseProvider: CoreCourseProvider, + protected domUtils: CoreDomUtilsProvider, + protected resourceProvider: AddonModResourceProvider, + protected courseHelper: CoreCourseHelperProvider, + protected textUtils: CoreTextUtilsProvider, + protected mimetypeUtils: CoreMimetypeUtilsProvider, + protected fileProvider: CoreFileProvider, + protected appProvider: CoreAppProvider, + protected filepoolProvider: CoreFilepoolProvider, + protected sitesProvider: CoreSitesProvider, + protected fileHelper: CoreFileHelperProvider) { } /** @@ -136,6 +143,23 @@ export class AddonModResourceHelperProvider { return mimetype == 'text/html'; } + /** + * Check if main file of resource is downloadable. + * + * @param module Module instance. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: whether main file is downloadable. + */ + isMainFileDownloadable(module: any, siteId?: string): Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + const mainFile = module.contents[0]; + const fileUrl = this.fileHelper.getFileUrl(mainFile); + const timemodified = this.fileHelper.getFileTimemodified(mainFile); + + return this.filepoolProvider.isFileDownloadable(siteId, fileUrl, timemodified); + } + /** * Check if the resource is a Nextcloud file. * 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 c7f421705..a9fcfd058 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 @@ -98,7 +98,7 @@

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

diff --git a/src/addon/mod/scorm/scorm.module.ts b/src/addon/mod/scorm/scorm.module.ts index 7542353a7..8676d3769 100644 --- a/src/addon/mod/scorm/scorm.module.ts +++ b/src/addon/mod/scorm/scorm.module.ts @@ -28,7 +28,6 @@ import { AddonModScormGradeLinkHandler } from './providers/grade-link-handler'; import { AddonModScormListLinkHandler } from './providers/list-link-handler'; import { AddonModScormSyncProvider } from './providers/scorm-sync'; import { AddonModScormComponentsModule } from './components/components.module'; -import { CoreUpdateManagerProvider } from '@providers/update-manager'; // List of providers (without handlers). export const ADDON_MOD_SCORM_PROVIDERS: any[] = [ @@ -62,7 +61,7 @@ export class AddonModScormModule { prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModScormPrefetchHandler, cronDelegate: CoreCronDelegate, syncHandler: AddonModScormSyncCronHandler, linksDelegate: CoreContentLinksDelegate, indexHandler: AddonModScormIndexLinkHandler, gradeHandler: AddonModScormGradeLinkHandler, - updateManager: CoreUpdateManagerProvider, listLinkHandler: AddonModScormListLinkHandler) { + listLinkHandler: AddonModScormListLinkHandler) { moduleDelegate.registerHandler(moduleHandler); prefetchDelegate.registerHandler(prefetchHandler); @@ -70,41 +69,5 @@ export class AddonModScormModule { linksDelegate.registerHandler(indexHandler); linksDelegate.registerHandler(gradeHandler); linksDelegate.registerHandler(listLinkHandler); - - // Allow migrating the tables from the old app to the new schema. - updateManager.registerSiteTablesMigration([ - { - name: 'mod_scorm_offline_attempts', - newName: AddonModScormOfflineProvider.ATTEMPTS_TABLE, - fields: [ - { - name: 'snapshot', - type: 'object' - }, - { - name: 'scormAndUser', - delete: true - } - ] - }, - { - name: 'mod_scorm_offline_scos_tracks', - newName: AddonModScormOfflineProvider.TRACKS_TABLE, - fields: [ - { - name: 'value', - type: 'object' - }, - { - name: 'scormUserAttempt', - delete: true - }, - { - name: 'scormUserAttemptSynced', - delete: true - } - ] - } - ]); } } diff --git a/src/addon/mod/survey/survey.module.ts b/src/addon/mod/survey/survey.module.ts index f57b47080..53829079e 100644 --- a/src/addon/mod/survey/survey.module.ts +++ b/src/addon/mod/survey/survey.module.ts @@ -27,7 +27,6 @@ import { AddonModSurveyPrefetchHandler } from './providers/prefetch-handler'; import { AddonModSurveySyncProvider } from './providers/sync'; import { AddonModSurveySyncCronHandler } from './providers/sync-cron-handler'; import { AddonModSurveyOfflineProvider } from './providers/offline'; -import { CoreUpdateManagerProvider } from '@providers/update-manager'; // List of providers (without handlers). export const ADDON_MOD_SURVEY_PROVIDERS: any[] = [ @@ -59,7 +58,7 @@ export class AddonModSurveyModule { constructor(moduleDelegate: CoreCourseModuleDelegate, moduleHandler: AddonModSurveyModuleHandler, prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModSurveyPrefetchHandler, contentLinksDelegate: CoreContentLinksDelegate, linkHandler: AddonModSurveyLinkHandler, - cronDelegate: CoreCronDelegate, syncHandler: AddonModSurveySyncCronHandler, updateManager: CoreUpdateManagerProvider, + cronDelegate: CoreCronDelegate, syncHandler: AddonModSurveySyncCronHandler, listLinkHandler: AddonModSurveyListLinkHandler) { moduleDelegate.registerHandler(moduleHandler); @@ -67,17 +66,5 @@ export class AddonModSurveyModule { contentLinksDelegate.registerHandler(linkHandler); contentLinksDelegate.registerHandler(listLinkHandler); cronDelegate.register(syncHandler); - - // Allow migrating the tables from the old app to the new schema. - updateManager.registerSiteTableMigration({ - name: 'mma_mod_survey_answers', - newName: AddonModSurveyOfflineProvider.SURVEY_TABLE, - fields: [ - { - name: 'answers', - type: 'object' - } - ] - }); } } 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 af57b0dbe..e043a6299 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 @@ -2,7 +2,8 @@ @@ -25,13 +25,11 @@ - -

{{ section.name }}

-
- - +

{{ section.name }}

+

+ {{ section.totalSize | coreBytesToSize }} - +

@@ -39,21 +37,20 @@
-
- - - {{ module.name }} - - - - {{ module.totalSize | coreBytesToSize }} - - - -
+ +

+ + {{ module.name }} +

+

+ + {{ module.totalSize | coreBytesToSize }} +

+ +
diff --git a/src/addon/storagemanager/pages/course-storage/course-storage.scss b/src/addon/storagemanager/pages/course-storage/course-storage.scss index 2c03e4984..77e6022d7 100644 --- a/src/addon/storagemanager/pages/course-storage/course-storage.scss +++ b/src/addon/storagemanager/pages/course-storage/course-storage.scss @@ -13,12 +13,7 @@ ion-app.app-root page-addon-storagemanager-course-storage { font-weight: bold; font-size: 2rem; } - .size { - margin-top: 4px; - } - .size ion-icon { - margin-right: 4px; - } + .core-module-icon { margin-right: 4px; width: 16px; diff --git a/src/addon/storagemanager/pages/course-storage/course-storage.ts b/src/addon/storagemanager/pages/course-storage/course-storage.ts index eb605fc57..ff9196403 100644 --- a/src/addon/storagemanager/pages/course-storage/course-storage.ts +++ b/src/addon/storagemanager/pages/course-storage/course-storage.ts @@ -19,6 +19,7 @@ import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module- import { CoreCourseHelperProvider } from '@core/course/providers/helper'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { TranslateService } from '@ngx-translate/core'; +import { CoreConstants } from '@core/constants'; /** * Page that displays the amount of file storage used by each activity on the course, and allows @@ -84,6 +85,10 @@ export class AddonStorageManagerCourseStoragePage { Promise.all(allPromises).then(() => { this.loaded = true; + + if (this.totalSize == 0) { + this.markCourseAsNotDownloaded(); + } }); }); } @@ -162,6 +167,25 @@ export class AddonStorageManagerCourseStoragePage { modal.dismiss(); this.domUtils.showErrorModalDefault(error, this.translate.instant('core.errordeletefile')); + }).finally(() => { + // @TODO This is a workaround that should be more specific solving MOBILE-3305. + // Also should take into account all modules are not downloaded. + + // Mark course as not downloaded if course size is 0. + if (this.totalSize == 0) { + this.markCourseAsNotDownloaded(); + } }); } + + /** + * Mark course as not downloaded. + */ + protected markCourseAsNotDownloaded(): void { + // @TODO This is a workaround that should be more specific solving MOBILE-3305. + // Also should take into account all modules are not downloaded. + // Check after MOBILE-3188 is integrated. + + this.courseProvider.setCourseStatus(this.course.id, CoreConstants.NOT_DOWNLOADED); + } } diff --git a/src/addon/userprofilefield/textarea/component/addon-user-profile-field-textarea.html b/src/addon/userprofilefield/textarea/component/addon-user-profile-field-textarea.html index 1d149a8ed..8908db521 100644 --- a/src/addon/userprofilefield/textarea/component/addon-user-profile-field-textarea.html +++ b/src/addon/userprofilefield/textarea/component/addon-user-profile-field-textarea.html @@ -9,5 +9,5 @@ {{ field.name }} - + \ No newline at end of file diff --git a/src/addon/userprofilefield/textarea/textarea.module.ts b/src/addon/userprofilefield/textarea/textarea.module.ts index f64fc285d..68ad0e678 100644 --- a/src/addon/userprofilefield/textarea/textarea.module.ts +++ b/src/addon/userprofilefield/textarea/textarea.module.ts @@ -20,6 +20,7 @@ import { CoreUserProfileFieldDelegate } from '@core/user/providers/user-profile- import { AddonUserProfileFieldTextareaComponent } from './component/textarea'; import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreEditorComponentsModule } from '@core/editor/components/components.module'; @NgModule({ declarations: [ @@ -29,7 +30,8 @@ import { CoreDirectivesModule } from '@directives/directives.module'; IonicModule, TranslateModule.forChild(), CoreComponentsModule, - CoreDirectivesModule + CoreDirectivesModule, + CoreEditorComponentsModule, ], providers: [ AddonUserProfileFieldTextareaHandler diff --git a/src/app/app.md.scss b/src/app/app.md.scss index a1efde285..eb38e754c 100644 --- a/src/app/app.md.scss +++ b/src/app/app.md.scss @@ -68,6 +68,9 @@ ion-app.app-root.md { .action-sheet-title, .action-sheet-container > .action-sheet-group:first-child { box-shadow: 0 3px 5px $gray; + @include darkmode() { + box-shadow: 0 3px 5px $black; + } } .action-sheet-title { diff --git a/src/app/app.module.ts b/src/app/app.module.ts index f28974676..81fdb84a7 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -17,7 +17,6 @@ import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; import { NgModule, COMPILER_OPTIONS } from '@angular/core'; import { IonicApp, IonicModule, Platform, Content, ScrollEvent, Config, Refresher } from 'ionic-angular'; import { assert } from 'ionic-angular/util/util'; -import { HttpModule } from '@angular/http'; import { HttpClient, HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http'; import { JitCompilerFactory } from '@angular/platform-browser-dynamic'; import { LocationStrategy } from '@angular/common'; @@ -61,6 +60,9 @@ import { CoreSyncProvider } from '@providers/sync'; import { CoreFileHelperProvider } from '@providers/file-helper'; import { CoreCustomURLSchemesProvider } from '@providers/urlschemes'; +// Handlers. +import { CoreSiteInfoCronHandler } from '@providers/handlers/site-info-cron-handler'; + // Core modules. import { CoreComponentsModule } from '@components/components.module'; import { CoreEmulatorModule } from '@core/emulator/emulator.module'; @@ -84,6 +86,8 @@ import { CoreRatingModule } from '@core/rating/rating.module'; import { CoreTagModule } from '@core/tag/tag.module'; import { CoreFilterModule } from '@core/filter/filter.module'; import { CoreH5PModule } from '@core/h5p/h5p.module'; +import { CoreSearchModule } from '@core/search/search.module'; +import { CoreEditorModule } from '@core/editor/editor.module'; // Addon modules. import { AddonBadgesModule } from '@addon/badges/badges.module'; @@ -198,7 +202,6 @@ export const WP_PROVIDER: any = null; BrowserModule, BrowserAnimationsModule, HttpClientModule, // HttpClient is used to make JSON requests. It fails for HEAD requests because there is no content. - HttpModule, IonicModule.forRoot(MoodleMobileApp, { pageTransition: 'core-page-transition' }), @@ -232,6 +235,8 @@ export const WP_PROVIDER: any = null; CoreTagModule, CoreFilterModule, CoreH5PModule, + CoreSearchModule, + CoreEditorModule, AddonBadgesModule, AddonBlogModule, AddonCalendarModule, @@ -329,6 +334,7 @@ export const WP_PROVIDER: any = null; CoreSyncProvider, CoreFileHelperProvider, CoreCustomURLSchemesProvider, + CoreSiteInfoCronHandler, { provide: HTTP_INTERCEPTORS, useClass: CoreInterceptor, @@ -341,8 +347,17 @@ export const WP_PROVIDER: any = null; ] }) export class AppModule { - constructor(platform: Platform, initDelegate: CoreInitDelegate, updateManager: CoreUpdateManagerProvider, config: Config, - sitesProvider: CoreSitesProvider, fileProvider: CoreFileProvider, private eventsProvider: CoreEventsProvider) { + constructor( + platform: Platform, + initDelegate: CoreInitDelegate, + updateManager: CoreUpdateManagerProvider, + config: Config, + sitesProvider: CoreSitesProvider, + fileProvider: CoreFileProvider, + private eventsProvider: CoreEventsProvider, + cronDelegate: CoreCronDelegate, + siteInfoCronHandler: CoreSiteInfoCronHandler, + ) { // Register a handler for platform ready. initDelegate.registerProcess({ name: 'CorePlatformReady', @@ -373,6 +388,9 @@ export class AppModule { // Execute the init processes. initDelegate.executeInitProcesses(); + // Register handlers. + cronDelegate.register(siteInfoCronHandler); + // Set transition animation. config.setTransition('core-page-transition', CorePageTransition); config.setTransition('core-modal-lateral-transition', CoreModalLateralTransition); diff --git a/src/app/app.scss b/src/app/app.scss index 75f830591..cc99bbfa8 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -12,7 +12,7 @@ // default Sass variables, belong in "theme/variables.scss". // // To declare rules for a specific mode, create a child rule -// for the .md, .ios, or .wp mode classes. The mode class is +// for the .md, or .ios mode classes. The mode class is // automatically applied to the element in the app. // Alignment @@ -106,19 +106,24 @@ ion-app.app-root { .item h2 { text-overflow: inherit; overflow: inherit; + ion-icon, core-icon { + vertical-align: bottom; + } } .core-nav-item-selected, .item.core-nav-item-selected { @include core-selected-item($core-splitview-selected); } - // Recover borders on items inside cards. + // Recover borders on items inside cards and lists. .card.with-borders .core-as-item, + .list.with-borders .core-as-item, .core-as-item { @include core-as-items(); } - .card.with-borders .item { + .card.with-borders .item, + .list.with-borders .item { @include core-items(); } @@ -205,6 +210,10 @@ ion-app.app-root { background-color: $gray-light; } + ion-note.core-note-block { + display: block; + } + // Form items // ------------------------- @@ -333,6 +342,10 @@ ion-app.app-root { width: 100%; } + &[fullonclick="true"] { + cursor: pointer; + } + audio, video, a, iframe { pointer-events: auto; } @@ -371,6 +384,7 @@ ion-app.app-root { background-color: rgba(0, 0, 0, .5); } text-align: center; + cursor: pointer; width: 32px; height: 32px; @@ -428,8 +442,7 @@ ion-app.app-root { } @each $color-name, $color-base, $color-contrast in get-colors($colors) { &.select-md-#{$color-name}, - &.select-ios-#{$color-name}, - &.select-wp-#{$color-name} { + &.select-ios-#{$color-name} { color: $color-base; .select-icon .select-icon-inner { @@ -468,11 +481,6 @@ ion-app.app-root { box-shadow: $card-ios-box-shadow; } - &.select-wp, - &.button-wp { - background: $card-wp-background-color; - } - .select-text { white-space: normal; } @@ -506,9 +514,7 @@ ion-app.app-root { &.select-md, &.button-md, &.select-ios, - &.button-ios, - &.select-wp, - &.button-wp { + &.button-ios { background: $core-dark-item-bg-color; } } @@ -663,6 +669,9 @@ ion-app.app-root { .action-sheet-wrapper { .action-sheet-button.action-sheet-cancel { color: $core-action-sheet-cancel-color; + @include darkmode() { + color: $core-dark-action-sheet-cancel-color; + } } .action-sheet-selected { color: $core-color; @@ -671,6 +680,7 @@ ion-app.app-root { .alert-message { overflow-y: auto; + user-select: text; } ion-alert.core-nohead { @@ -680,9 +690,6 @@ ion-app.app-root { &.alert-ios .alert-message { padding-top: $alert-ios-message-padding-bottom; } - &.alert-wp .alert-message { - padding-top: $alert-wp-message-padding-bottom; - } .alert-head { display: none; } @@ -730,11 +737,6 @@ ion-app.app-root { max-height: $toolbar-ios-height; } - .header .toolbar-wp { - height: $toolbar-wp-height; - max-height: $toolbar-wp-height; - } - .header .toolbar-md { height: $toolbar-md-height; max-height: $toolbar-md-height; @@ -800,11 +802,6 @@ ion-app.app-root { @include ios-input-highlight($color-base); } - &.item-wp .text-input, - &.item-wp.item-input.ng-valid.item-input-has-value:not(.input-has-focus):not(.item-input-has-focus) .text-input, - &.item-wp.item-input.ng-valid.input-has-value:not(.input-has-focus):not(.item-input-has-focus) .text-input { - border-color: $color-base; - } } .core-#{$color-name}-selected-item { @@ -1081,6 +1078,7 @@ ion-app.app-root { } .core-expandable { + cursor: pointer; ion-icon[item-start] + .item-inner { @include margin-horizontal(0px, null); } @@ -1101,6 +1099,16 @@ ion-app.app-root { transition: opacity 100ms ease-in-out; opacity: .1; } + + a.item, + .core-clickable, + select { + cursor: pointer; + } + + a.autolink.glossary:hover { + cursor: help; + } } @each $color-name, $color-base, $color-contrast in get-colors($colors) { @@ -1111,7 +1119,6 @@ ion-app.app-root { } .item-divider-md-#{$color-name} h2, - .item-divider-wp-#{$color-name} h2, .item-divider-ios-#{$color-name} h2 { color: $color-contrast; } @@ -1167,6 +1174,16 @@ ion-app.platform-desktop { white-space: normal !important; } + audio, video { + &::-webkit-media-controls-overlay-play-button, + &::-webkit-media-controls-play-button, + &::-webkit-media-controls-timeline, + &::-webkit-media-controls-mute-button, + &::-webkit-media-controls-fullscreen-button { + cursor: pointer; + } + } + &.platform-windows { video::-webkit-media-text-track-display { font-size: 0.6em; diff --git a/src/app/app.wp.scss b/src/app/app.wp.scss deleted file mode 100644 index 97a8317ae..000000000 --- a/src/app/app.wp.scss +++ /dev/null @@ -1,63 +0,0 @@ -ion-app.app-root.wp { - .button-wp { - min-height: $button-wp-height; - } - - // Light buttons color. - .button-wp-light { - color: color($colors, primary, base); - } - - .col[align-self-stretch] .card-wp { - height: calc(100% - #{($card-wp-margin-end + $card-wp-margin-start)}); - } - - .bar-buttons core-context-menu .button-clear-wp { - color: $toolbar-wp-button-color; - } - - .item-wp ion-spinner[item-start] + .item-inner, - .item-wp ion-spinner[item-start] + .item-input, - .item-wp ion-icon[item-start] + .item-inner, - .item-wp ion-icon[item-start] + .item-input, - .item-wp img[item-start] + .item-inner, - .item-wp img[item-start] + .item-input { - @include margin-horizontal(($item-wp-padding-start / 2), null); - } - - .item-wp ion-avatar[item-start] + .item-inner, - .item-wp ion-avatar[item-start] + .item-input { - @include margin-horizontal(0, null); - } - - @each $color-name, $color-base, $color-contrast in get-colors($colors-wp) { - .core-#{$color-name}-card { - @extend .card-wp ; - @extend .card-content-wp; - - @include darkmode() { - background-color: $core-dark-item-bg-color; - } - - &[icon-start] { - @include safe-area-padding(null, null, null, $card-wp-padding-left * 2 + 20); - - ion-icon { - @include safe-area-position(null, null, null, $card-wp-padding-left); - } - } - } - } - - .core-avoid-header ion-content { - top: $navbar-wp-height; - height: calc(100% - #{($navbar-wp-height)}); - } - - // Different levels of padding. - @for $i from 0 through 15 { - .core-padding-#{$i} { - @include padding(null, null, null, 15px * $i + $item-wp-padding-start); - } - } -} \ No newline at end of file diff --git a/src/assets/lang/ar.json b/src/assets/lang/ar.json index 91b156306..ccfedf209 100644 --- a/src/assets/lang/ar.json +++ b/src/assets/lang/ar.json @@ -977,11 +977,9 @@ "core.login.credentialsdescription": "من فضلك قم بإدخال اسم المستخدم وكلمة المرور للدخول إلى", "core.login.emailconfirmsent": "

تم إرسال بريد إلى {{$a}}

\n

والذي يحتوي على إرشادات سهلة تعينك على إتمام التسجيل

\n

إذا استمرت المشاكل اتصل بمدير هذا الموقع

", "core.login.emailconfirmsentsuccess": "تم إرسال رسالة التأكيد الإلكترونية بنجاح", - "core.login.enterthewordsabove": "أدخل الكلمات أعلاه", "core.login.firsttime": "هل هذه هي المرة الأولى لك؟ الاشتراك من هنا", "core.login.forcepasswordchangenotice": "يجب عليك تغير كلمة المرور ليتسنى لك الاستمرار", "core.login.forgotten": "هل نسيت اسم الدخول أو كلمة المرور؟", - "core.login.getanothercaptcha": "احصل على كاباتشا أخرى", "core.login.help": "مساعدة", "core.login.helpmelogin": "

من أجل تسجيل الدخول، يرجى التحقق مما يلي:

\n١- إصدار موقع التعلم الإلكتروني مودل هو الإصدار ٢.٤ أو أعلى.
\n٢- قد مكن مسؤول موقع التعلم الإلكتروني مودل الوصول عبر تطبيق مودل.

\n

لو لم تستطع الدخول من خلال موقع التعلم الإلكتروني مودل الخاص بك قم بالاتصال بمدير موقع التعلم الإلكتروني مودل الخاص بك وأطلب منه زيارة موقع وثائق موودل للحصول على معلومات أكثر تفصيلا والمساعدة.

\nلاختبار التطبيق باستخدام الموقع التجريبي لمودل ضع student أو teacher في حقل عنوان الموقع وانقر على زر دخول", "core.login.instructions": "التعليمات", @@ -1179,6 +1177,7 @@ "core.strftimetime": "%I:%M %p", "core.submit": "سلم", "core.success": "نجاح", + "core.tag.searchtags": "البحث في الوسوم", "core.tag.tag": "علامة", "core.tag.tags": "علامات", "core.teachers": "معلمون", diff --git a/src/assets/lang/bg.json b/src/assets/lang/bg.json index fc2086c83..baac3a20d 100644 --- a/src/assets/lang/bg.json +++ b/src/assets/lang/bg.json @@ -1127,11 +1127,9 @@ "core.login.createaccount": "Създаване на моята регистрация", "core.login.createuserandpass": "Изберете Вашето име и парола", "core.login.emailconfirmsent": "

Трябва вече да е изпратен имейл на вашия адрес {{$a}}

Той съдържа лесни инструкции как да завършите регистрацията си.

Ако продължите да срещате трудности, се обърнете към администратора на сайта.

", - "core.login.enterthewordsabove": "Въведете думите, изобразени по-горе", "core.login.firsttime": "За първи път ли сте тук?", "core.login.forcepasswordchangenotice": "Трябва да промените Вашата парола, преди да продължите", "core.login.forgotten": "Забравени потребителско име или парола?", - "core.login.getanothercaptcha": "Получаване на друга CAPTCHA", "core.login.help": "Помощ", "core.login.helpmelogin": "

Има много хиляди сайтове със система Moodle по света. Това приложение може да Ви свърже само със сайтове, които специално разрешават достъп с мобилно приложение.

Ако не можете да се свържете със своя сайт, трябва да се обърнете към администратор на Moodle системата на сайта, и да го помолите да прочете информацията от адрес: http://docs.moodle.org/en/Mobile_app

За да пробвате приложението с демонстрационен сайт, напишете teacher или student в полето Адрес на сайта и щракнете Бутон за добавяне.

", "core.login.instructions": "Инструкции", @@ -1323,6 +1321,7 @@ "core.strftimetime": "%H:%M", "core.submit": "Изпълняване", "core.success": "Успешно", + "core.tag.searchtags": "Търсене в етикети", "core.tag.tags": "Етикети", "core.teachers": "Преподаватели", "core.thisdirection": "ltr", diff --git a/src/assets/lang/ca.json b/src/assets/lang/ca.json index f223d7f8f..ee550fc75 100644 --- a/src/assets/lang/ca.json +++ b/src/assets/lang/ca.json @@ -1353,7 +1353,6 @@ "core.comments.savecomment": "Desa el comentari", "core.comments.warningcommentsnotsent": "No es poden sincronitzar els comentaris. {{error}}", "core.commentscount": "Comentaris ({{$a}})", - "core.commentsnotworking": "No s'han pogut recuperar els comentaris", "core.completion-alt-auto-fail": "Completat: {{$a}} (no han aconseguit assolir la qualificació)", "core.completion-alt-auto-n": "Incomplet: {{$a}}", "core.completion-alt-auto-n-override": "No completada: {{$a.modname}} (establert per {{$a.overrideuser}})", @@ -1671,7 +1670,6 @@ "core.login.changepasswordinstructions": "No podeu canviar la vostra contrasenya a l'aplicació. Feu clic al botó següent per obrir el lloc en un navegador web per canviar la vostra contrasenya. Tingueu en compte que heu de tancar el navegador després de canviar la contrasenya, ja que no se us redirigirà a l'aplicació.", "core.login.changepasswordlogoutinstructions": "Si preferiu canviar de lloc o sortir de sessió, feu clic al botó següent:", "core.login.changepasswordreconnectinstructions": "Feu clic al botó següent per tornar a connectar al lloc. (Tingueu en compte que si no canvieu la contrasenya amb èxit, tornareu a la pantalla anterior).", - "core.login.checksiteversion": "Comproveu que el vostre lloc utilitza Moodle 2.4 o superior.", "core.login.confirmdeletesite": "Esteu segur que voleu eliminar el lloc {{sitename}}?", "core.login.connect": "Connecta", "core.login.connecttomoodle": "Connecta al Moodle", @@ -1686,7 +1684,6 @@ "core.login.emailconfirmsentnoemail": "

S'ha enviat un correu a la vostra adreça electrònica.

Conté instruccions per completar el registre.

Si encara teniu problemes, contacteu amb l'administrador del vostre lloc.

", "core.login.emailconfirmsentsuccess": "El correu de confirmació s'ha enviat correctament.", "core.login.emailnotmatch": "Les adreces de correu electrònic no coincideixen", - "core.login.enterthewordsabove": "Introduïu les paraules de dalt", "core.login.erroraccesscontrolalloworigin": "La crida Cross-Origin que esteu intentant ha sigut rebutjada. Si us plau, visiteu https://docs.moodle.org/dev/Moodle_Mobile_development_using_Chrome_or_Chromium per més informació", "core.login.errordeletesite": "S'ha produït un error en eliminar aquest lloc. Si us plau, intenteu-ho més tard.", "core.login.errorupdatesite": "S'ha produït un error actualitzant el testimoni d'aquest lloc.", @@ -1694,7 +1691,6 @@ "core.login.firsttime": "És la primera vegada que veniu aquí?", "core.login.forcepasswordchangenotice": "Heu de canviar la contrasenya abans de continuar", "core.login.forgotten": "Heu oblidat el nom d'usuari o la contrasenya?", - "core.login.getanothercaptcha": "Obté un altre CAPTCHA", "core.login.help": "Ajuda", "core.login.helpmelogin": "

Hi ha molts llocs Moodle al món. Aquesta aplicació només pot connectar-se als llocs Moodle que hagin habilitat l'accés a l'aplicació Moodle.

Si no pots connectar-te al teu lloc Moodle, contacta l'administrador del lloc i demana-li que llegeixi http://docs.moodle.org/en/Mobile_app

Si vols provar l'aplicació en un lloc Moodle de mostra, escriu teacher o student al camp Adreça del lloc i fes clic al Botó Connecta.

", "core.login.instructions": "Instruccions", @@ -1707,9 +1703,6 @@ "core.login.invalidurl": "S'ha especificat un URL no vàlid", "core.login.invalidvaluemax": "El valor màxim és {{$a}}", "core.login.invalidvaluemin": "El valor mínim és {{$a}}", - "core.login.legacymoodleversion": "Estàs provant de connectar a una versió de Moodle no admesa. Descarrega l'aplicació Moodle Classic per accedir a aquest lloc.", - "core.login.legacymoodleversiondesktop": "Estàs provant de connectar a {{$a}}.

Aquest lloc té una versió de Moodle antiga no suportada per l'aplicació Moodle Desktop.

Si aquest lloc és teu, contacta el teu Moodle Partner local per obtenir assistència i actualitzar-lo.

Vés a la nostra pàgina de contacte per sol·licitar assistència.", - "core.login.legacymoodleversiondesktopdownloadold": "

De manera alternativa, encara podeu accedir a aquest lloc amb la versió no suportada de l'aplicació que podeu descarregar d'aquí.", "core.login.localmobileunexpectedresponse": "Les característiques addicionals de Moodle Mobile han tornat una resposta inesperada, us heu d'autenticar fent servir el servei estàndard de Mobile.", "core.login.loggedoutssodescription": "Heu de tornar-vos a auteniticar. Cal que entreu al lloc des d'una finestra de navegador.", "core.login.login": "Inicia la sessió", @@ -2000,8 +1993,18 @@ "core.submit": "Tramet", "core.success": "Èxit", "core.tablet": "Tablet", + "core.tag.defautltagcoll": "Col·lecció per defecte", "core.tag.errorareanotsupported": "Aquesta àrea d'etiquetes no està suportada a l'aplicació.", + "core.tag.inalltagcoll": "Arreu", + "core.tag.itemstaggedwith": "{{$a.tagarea}} etiquetada amb «{{$a.tag}}»", + "core.tag.notagsfound": "No s'han trobat etiquetes coincidents amb «{{$a}}»", + "core.tag.searchtags": "Cerca etiquetes", + "core.tag.showingfirsttags": "S'estan mostrant les {{$a}} etiquetes més populars", "core.tag.tag": "Etiqueta", + "core.tag.tagarea_course": "Cursos", + "core.tag.tagarea_course_modules": "Activitats i recursos", + "core.tag.tagarea_post": "Entrades del blog", + "core.tag.tagarea_user": "Interessos de l'usuari", "core.tag.tags": "Etiquetes", "core.tag.warningareasnotsupported": "No es mostren algunes àrees d'etiquetes perquè no estan suportades per l'aplicació.", "core.teachers": "Professors", diff --git a/src/assets/lang/cs.json b/src/assets/lang/cs.json index 4cef0d72c..3f100b527 100644 --- a/src/assets/lang/cs.json +++ b/src/assets/lang/cs.json @@ -1353,7 +1353,6 @@ "core.comments.savecomment": "Uložit komentář", "core.comments.warningcommentsnotsent": "Komentáře se nepodařilo synchronizovat. {{error}}", "core.commentscount": "Komentáře ({{$a}})", - "core.commentsnotworking": "Komentáře nemohou být obnoveny", "core.completion-alt-auto-fail": "Dokončeno: {{$a}} (nebylo dosaženo požadované známky)", "core.completion-alt-auto-n": "Nedokončeno: {{$a}}", "core.completion-alt-auto-n-override": "Nedokončeno: {{$a.modname}} (nastaveno {{$a.overrideuser}})", @@ -1671,7 +1670,6 @@ "core.login.changepasswordinstructions": "V aplikaci nemůžete změnit své heslo. Kliknutím na následující tlačítko otevřete stránky ve webovém prohlížeči a změňte své heslo. Vezměte v úvahu, že po změně hesla je třeba prohlížeč zavřít, protože nebudete přesměrováni na aplikaci.", "core.login.changepasswordlogoutinstructions": "Pokud chcete změnit stránky nebo se odhlásit, klikněte na následující tlačítko:", "core.login.changepasswordreconnectinstructions": "Klepnutím na následující tlačítko se znovu připojíte ke stránkám. (Vezměte na vědomí, že pokud jste heslo nezměnili úspěšně, vrátíte se na předchozí obrazovku).", - "core.login.checksiteversion": "Zkontrolujte, zda web používá Moodle 2.4 nebo novější.", "core.login.confirmdeletesite": "Jste si jisti, že chcete smazat web {{sitename}}?", "core.login.connect": "Připojit!", "core.login.connecttomoodle": "Připojit k Moodle", @@ -1686,7 +1684,6 @@ "core.login.emailconfirmsentnoemail": "

Byla odeslána e-mailová adresa na vaši adresu.

Obsahuje jednoduché pokyny k dokončení registrace.

Pokud máte nadále potíže, obraťte se na správce stránek.

", "core.login.emailconfirmsentsuccess": "Potvrzovací e-mail byl úspěšně odeslán", "core.login.emailnotmatch": "E-maily se neshodují", - "core.login.enterthewordsabove": "Vložte výše uvedená slova", "core.login.erroraccesscontrolalloworigin": "Cross-Origin volání - váš pokus o provedení byl odmítnut. Zkontrolujte prosím https://docs.moodle.org/dev/Moodle_Mobile_development_using_Chrome_or_Chromium", "core.login.errordeletesite": "Při odstraňování této stránky došlo k chybě. Prosím, zkuste to znovu.", "core.login.errorupdatesite": "Došlo k chybě při aktualizaci tokenu webu.", @@ -1694,7 +1691,6 @@ "core.login.firsttime": "Jste tady poprvé?", "core.login.forcepasswordchangenotice": "Před pokračováním si musíte změnit heslo.", "core.login.forgotten": "Zapomněli jste své uživatelské jméno či heslo?", - "core.login.getanothercaptcha": "Získat jiné CAPTCHA", "core.login.help": "Nápověda", "core.login.helpmelogin": "

Existuje mnoho tisíců stránek Moodle po celém světě. Tato aplikace se může připojit pouze ke stránkám Moodle, které specificky povolily přístup k mobilním aplikacím.

Nemůžete-li se připojit k vašim stránkám Moodle, musíte se obrátit na správce stránek a požádat ho o přečtení http://docs.moodle.org/en/Mobile_app

Otestujte aplikaci v demo Moodle jako učitel nebo student na poli adresa stránek a klikněte na Připojení.

", "core.login.instructions": "Pokyny", @@ -1707,9 +1703,6 @@ "core.login.invalidurl": "Chybná specifikace URL", "core.login.invalidvaluemax": "Maximální hodnota je {{$a}}", "core.login.invalidvaluemin": "Minimální hodnota je {{$a}}", - "core.login.legacymoodleversion": "Pokoušíte se připojit k nepodporované verzi Moodle. Stáhněte si aplikaci Moodle Classic pro přístup k těmto stránkám Moodle.", - "core.login.legacymoodleversiondesktop": "Snažíte se připojit {{$a}}.

Na těchto stránkách běží zastaralá nepodporovaná verze Moodle, která s touto aplikací Desktop Moodle nebude fungovat.

Pokud se jedná o vaše stránky, obraťte se na místního partnera moodle a obraťte se pro pomoc při jeho aktualizaci.

Prohlédněte kontaktní stránku , kde můžete odeslat žádost o pomoc", - "core.login.legacymoodleversiondesktopdownloadold": "

Případně můžete stále přistupovat k této stránce pomocí nepodporované verze aplikace, kterou lze stáhnout zde.", "core.login.localmobileunexpectedresponse": "Kontrola rozšířených vlastností Moodle Mobile vrátil neočekávanou odezvu. Budete ověřen pomocí standardních služeb mobilu .", "core.login.loggedoutssodescription": "Musíte se znovu autentizovat. Musíte se přihlásit na stránky v okně prohlížeče.", "core.login.login": "Přihlášení", @@ -1999,8 +1992,18 @@ "core.submit": "Odeslat", "core.success": "Úspěch", "core.tablet": "Tablet", + "core.tag.defautltagcoll": "Výchozí kolekce", "core.tag.errorareanotsupported": "Aplikace nepodporuje pole štítků.", + "core.tag.inalltagcoll": "Všude", + "core.tag.itemstaggedwith": "{{$a.tagarea}} označeno štítkem \"{{$a.tag}}\"", + "core.tag.notagsfound": "Hledání výrazu ve štítku \"{{$a}}\" nepřineslo žádné výsledky", + "core.tag.searchtags": "Prohledat štítky", + "core.tag.showingfirsttags": "Zobrazit {{$a}} nejpopulárnějších štítků", "core.tag.tag": "Štítek", + "core.tag.tagarea_course": "Kurzy", + "core.tag.tagarea_course_modules": "Činnosti a materiály", + "core.tag.tagarea_post": "Příspěvky blogu", + "core.tag.tagarea_user": "Zájmy uživatele", "core.tag.tags": "Štítky", "core.tag.warningareasnotsupported": "Některé oblasti štítků se nezobrazí, protože nejsou aplikací podporovány.", "core.teachers": "Učitelé", diff --git a/src/assets/lang/da.json b/src/assets/lang/da.json index 7d94b8006..9ffa04335 100644 --- a/src/assets/lang/da.json +++ b/src/assets/lang/da.json @@ -1323,7 +1323,6 @@ "core.comments.nocomments": "Ingen kommentarer", "core.comments.savecomment": "Gem kommentar", "core.commentscount": "Kommentarer ({{$a}})", - "core.commentsnotworking": "Kommentarer kan ikke hentes", "core.completion-alt-auto-fail": "Gennemført: {{$a}} (opnåede ikke beståelseskarakter)", "core.completion-alt-auto-n": "Ikke gennemført: {{$a}}", "core.completion-alt-auto-n-override": "Ikke fuldført: {{$a.modname}} (set by {{$a.overrideuser}})", @@ -1591,7 +1590,6 @@ "core.login.authenticating": "Godkender", "core.login.cancel": "Annuller", "core.login.changepassword": "Skift adgangskode", - "core.login.checksiteversion": "Tjek at dit site kører Moodle 2.4 eller nyere.", "core.login.confirmdeletesite": "Er du sikker på at du ønsker at slette websiden {{sitename}}?", "core.login.connect": "Tilslut!", "core.login.connecttomoodle": "Tilslut til Moodle", @@ -1604,7 +1602,6 @@ "core.login.emailconfirmsentnoemail": "

Du har fået tilsendt en mail.

Den har enkle retningslinjer for hvordan du bliver registreret.

Kontakt sidens administrator hvis du behøver hjælp.

", "core.login.emailconfirmsentsuccess": "Bekræftelsesmail sendt", "core.login.emailnotmatch": "Mailadresserne matcher ikke", - "core.login.enterthewordsabove": "Skriv ordene herover", "core.login.erroraccesscontrolalloworigin": "Det Cross-Origin opkald du forsøger at udføre er blevet afvist. Kontroller venligst https://docs.moodle.org/dev/Moodle_Mobile_development_using_Chrome_or_Chromium", "core.login.errordeletesite": "En fejl opstod ved sletning af denne webside. Prøv venligst igen.", "core.login.errorupdatesite": "En fejl opstod under opdatering af webstedets token.", @@ -1612,7 +1609,6 @@ "core.login.firsttime": "Er det dit første besøg?", "core.login.forcepasswordchangenotice": "Du skal ændre din kode inden du kan fortsætte", "core.login.forgotten": "Har du glemt dit brugernavn eller din adgangskode?", - "core.login.getanothercaptcha": "Få en anden CAPTCHA", "core.login.help": "Hjælp", "core.login.helpmelogin": "

Der er mange tusinde Moodlesites rundt omkring i verden. Denne app kan kun anvendes på sites, der specifikt har aktiveret app-adgang.

Hvis du ikke kan tilgå dit Moodlesite, kan du kontakte din siteadministrator og gøre opmærksom på siden https://docs.moodle.org/en/Mobile_app

App'en kan testes på et Moodle-demosite ved at skrive teacher elllerstudent i feltet Site address og klikke på knappen Connect.

", "core.login.instructions": "Instruktioner", @@ -1884,7 +1880,17 @@ "core.submit": "Gem", "core.success": "Succes", "core.tablet": "Tablet", + "core.tag.defautltagcoll": "Standard samling", + "core.tag.inalltagcoll": "Overalt", + "core.tag.itemstaggedwith": "{{$a.tagarea}} tagget med \"{{$a.tag}}\"", + "core.tag.notagsfound": "Der blev ikke fundet nogen tag-match på \"{{$a}}\"", + "core.tag.searchtags": "Søg i tags", + "core.tag.showingfirsttags": "Viser de {{$a}} mest populære tags", "core.tag.tag": "Tag", + "core.tag.tagarea_course": "Kurser", + "core.tag.tagarea_course_modules": "Aktiviteter og materialer", + "core.tag.tagarea_post": "Blogindlæg", + "core.tag.tagarea_user": "Brugerinteresser", "core.tag.tags": "Tags", "core.teachers": "Lærere", "core.thereisdatatosync": "Der er {{$a}} offline der skal synkroniseres.", diff --git a/src/assets/lang/de-du.json b/src/assets/lang/de-du.json index 193165d6f..123d27c98 100644 --- a/src/assets/lang/de-du.json +++ b/src/assets/lang/de-du.json @@ -4,64 +4,83 @@ "addon.calendar.calendarevent": "Kalendereintrag", "addon.calendar.calendarevents": "Kalender", "addon.calendar.calendarreminders": "Kalendererinnerungen", - "addon.calendar.confirmeventdelete": "Möchtest du wirklich diesen Termin löschen?", + "addon.calendar.confirmeventdelete": "Möchtest du den Termin '{{$a}}' wirklich löschen?", + "addon.calendar.confirmeventseriesdelete": "Der Termin '{{$a.name}}' ist Teil der Serie. Möchtest du nur diesen Termin entfernen oder alle {{$a.count}} Termine dieser Serie?", "addon.calendar.currentmonth": "Aktueller Monat", "addon.calendar.defaultnotificationtime": "Standardmäßige Benachrichtigungszeit", "addon.calendar.errorloadevent": "Fehler beim Laden des Kalendereintrags", "addon.calendar.errorloadevents": "Fehler beim Laden der Kalendereinträge", + "addon.calendar.invalidtimedurationminutes": "Die angegebene Dauer in Minuten ist ungültig. Gib eine Zahl größer als 0 ein oder wähle 'Ohne Zeitangabe'.", + "addon.calendar.invalidtimedurationuntil": "Der angegebene Wert für Datum und Zeit von 'Dauer bis' liegt vor der Startzeit des Termins. Korrigiere diese Einstellung.", "addon.calendar.noevents": "Keine Kalendereinträge", + "addon.calendar.nopermissiontoupdatecalendar": "Du hast nicht die Rechte, den Termin zu aktualisieren.", "addon.calendar.reminders": "Erinnerungen", "addon.calendar.setnewreminder": "Neue Erinnerung erstellen", "addon.competency.errornocompetenciesfound": "Keine Kompetenzen gefunden", "addon.competency.nocompetencies": "Keine Kompetenzen", "addon.coursecompletion.complete": "Abschließen", "addon.coursecompletion.couldnotloadreport": "Fehler beim Laden des Abschlussberichts. Versuche es später noch einmal.", + "addon.coursecompletion.nottracked": "Aktuell läuft für dich in diesem Kurs keine Abschlussverfolgung", "addon.coursecompletion.required": "Notwendig", "addon.coursecompletion.status": "Status", "addon.files.couldnotloadfiles": "Die Dateiliste konnte nicht geladen werden.", "addon.files.emptyfilelist": "Keine Dateien", "addon.files.erroruploadnotworking": "Im Moment können keine Dateien zur Website hochgeladen werden.", "addon.messageoutput_airnotifier.processorsettingsdesc": "Geräte konfigurieren", + "addon.messages.addcontactconfirm": "Möchtest du wirklich {{$a}} zu deinen Kontakten hinzufügen?", + "addon.messages.blockuserconfirm": "Möchtest du wirklich {{$a}} blockieren?", "addon.messages.contactlistempty": "Die Kontaktliste ist leer.", "addon.messages.contactname": "Name", - "addon.messages.deleteallconfirm": "Möchtest du die Kommunikation wirklich löschen?", + "addon.messages.deleteallconfirm": "Möchtest du wirklich die gesamte Kommunikation löschen? Die Kommunikation bleibt allerdings bei den anderen beteiligten Personen erhalten.", "addon.messages.deleteallselfconfirm": "Möchtest du diese persönliche Kommunikation wirklich löschen?", "addon.messages.deletemessage": "Mitteilung löschen", - "addon.messages.deletemessageconfirmation": "Möchtest du diese Mitteilung wirklich löschen? Die Mitteilung wird nur in deinem Verlauf gelöscht, aber nicht bei der Person, die die Mitteilung gesendet oder empfangen hat..", + "addon.messages.deletemessageconfirmation": "Möchtest du diese Mitteilung wirklich löschen? Die Mitteilung wird nur in deinem Verlauf gelöscht, aber nicht bei der Person, die die Mitteilung gesendet oder empfangen hat.", "addon.messages.errordeletemessage": "Fehler beim Löschen der Mitteilung", "addon.messages.errorwhileretrievingcontacts": "Fehler beim Abrufen der Kontakte vom Server", "addon.messages.errorwhileretrievingdiscussions": "Fehler beim Abrufen der Themen vom Server", "addon.messages.errorwhileretrievingmessages": "Fehler beim Abrufen der Mitteilungen vom Server", "addon.messages.errorwhileretrievingusers": "Fehler beim Abrufen der Personen vom Server", + "addon.messages.isnotinyourcontacts": "{{$a}} gehört nicht zu deinen Kontakten", "addon.messages.messagenotsent": "Die Mitteilung wurde nicht gesendet. Versuche es später noch einmal.", "addon.messages.newmessages": "Neue Mitteilungen", "addon.messages.nousersfound": "Keine Personen gefunden", - "addon.messages.removecontactconfirm": "Der Kontakt wird aus deiner Kontaktliste gelöscht.", + "addon.messages.removecontactconfirm": "Möchtest du wirklich {{$a}} aus deinen Kontakten entfernen?", + "addon.messages.requirecontacttomessage": "Du musst {{$a}} anfragen, Sie als Kontakt hinzuzufügen. Erst dann kannst du eine Mitteilung senden.", + "addon.messages.selfconversationdefaultmessage": "Speichere Entwürfe von Nachrichten, Links, Notizen usw. für einen späteren Zugriff.", "addon.messages.showdeletemessages": "Mitteilungen löschen anzeigen", "addon.messages.type_blocked": "Blockiert", "addon.messages.type_offline": "Offline", "addon.messages.type_online": "Online", "addon.messages.type_search": "Suchergebnisse", "addon.messages.type_strangers": "Weitere Personen", + "addon.messages.unblockuserconfirm": "Möchtest du die Blockierung für {{$a}} wirklich aufheben?", "addon.messages.useentertosenddescdesktop": "Falls deaktiviert, kannst du mit Ctrl+Enter die Mitteilung senden.", "addon.messages.useentertosenddescmac": "Falls deaktiviert, kannst du mit Cmd+Enter die Mitteilung senden.", + "addon.messages.userwouldliketocontactyou": "{{$a}} möchte dich kontaktieren", "addon.messages.warningconversationmessagenotsent": "Mitteilung zum Thema {{conversation}} konnte nicht gesendet werden. {{error}}", "addon.messages.warningmessagenotsent": "Mitteilung an {{user}} konnte nicht gesendet werden. {{error}}", + "addon.messages.wouldliketocontactyou": "Ich möchte dich kontaktieren", "addon.messages.you": "Du:", + "addon.messages.youhaveblockeduser": "Du hast diese Person blockiert.", + "addon.messages.yourcontactrequestpending": "Deine Kontaktanfrage an {{$a}} ist noch offen", "addon.mod_assign.acceptsubmissionstatement": "Bestätige die Erklärung zur Eigenständigkeit.", "addon.mod_assign.cannoteditduetostatementsubmission": "Du kannst in der App keine Abgabe hinzufügen oder bearbeiten, weil keine Erklärung zur Eigenständigkeit von der Website abgerufen werden konnte.", "addon.mod_assign.cannotgradefromapp": "Manche Bewertungsmethoden werden von der App bisher nicht unterstützt und können nicht verändert werden.", "addon.mod_assign.cannotsubmitduetostatementsubmission": "Du kannst in der App keine Abgabe machen, weil von der Website keine Erklärung zur Eigenständigkeit abgerufen werden konnte.", + "addon.mod_assign.confirmsubmission": "Wenn du nun deine Lösung zur Bewertung einreichst, kannst du nichts mehr verändern. Bist du dir sicher?", "addon.mod_assign.erroreditpluginsnotsupported": "Du kannst in der App keine Abgabe hinzufügen oder bearbeiten, weil manche Plugins bisher keine Bearbeitung zulassen:", "addon.mod_assign.errorshowinginformation": "Die Abgabeinformationen können nicht angezeigt werden.", "addon.mod_assign.feedbacknotsupported": "Dieses Feedback wird von der App nicht unterstützt, so dass Informationen fehlen könnten.", "addon.mod_assign.gradenotsynced": "Bewertung nicht synchronisiert", + "addon.mod_assign.multipleteams_desc": "Diese Aufgabe wird in Gruppen abgegeben. Du bist Mitglied in mehr als einer Gruppe. Um deine Einreichung korrekt deiner Gruppe zuordnen zu können, musst du Mitglied in genau einer Gruppe sein. Kontaktiere deine Trainer/innen, um deine Gruppenzugehörigkeit zu aktualisieren.", "addon.mod_assign.notallparticipantsareshown": "Teilnehmer/innen, die nichts abgegeben haben, werden nicht angezeigt.", - "addon.mod_assign.submissionnotsupported": "Diese Abgabe wird von der App nicht unterstützt und es könnten Informationen fehlen.", - "addon.mod_assign.submitassignment_help": "Sobald die Aufgabe abgegeben ist, kannst du keine Änderungen mehr vornehmen.", + "addon.mod_assign.noteam_desc": "Diese Aufgabe wird in Gruppen abgegeben. Du bist kein Mitglied in einer Gruppe und kannst die Aufgabe deswegen nicht einreichen. Kontaktiere deine Trainer/innen, um einer Gruppe hinzugefügt zu werden.", + "addon.mod_assign.submissionnotsupported": "Diese Abgabe wird von der App nicht unterstützt und es könnten Infos fehlen.", + "addon.mod_assign.submitassignment_help": "Sobald diese Aufgabe abgegeben ist, kannst du nichts mehr ändern.", "addon.mod_assign.userwithid": "Nutzer/in mit ID {{id}}", "addon.mod_assign.warningsubmissiongrademodified": "Die Abgabebewertung wurde auf der Website geändert.", "addon.mod_assign.warningsubmissionmodified": "Die Abgabe wurde auf der Website geändert.", + "addon.mod_assign_submission_onlinetext.wordlimitexceeded": "Die maximale Anzahl von Wörtern für diese Aufgabenlösung ist {{$a.limit}}. Deine Antwort hat {{$a.count}} Wörter. Verkürze deinen Text und speichere ihn dann erneut.", "addon.mod_chat.enterchat": "Willst du den Chat starten? Klick hier.", "addon.mod_chat.errorwhileconnecting": "Fehler beim Verbinden zum Chat", "addon.mod_chat.errorwhilegettingchatdata": "Fehler beim Abrufen des Chats", @@ -70,35 +89,46 @@ "addon.mod_chat.errorwhilesendingmessage": "Fehler beim Senden der Mitteilung", "addon.mod_chat.messagebeepsyou": "{{$a}} hat dich angepiepst!", "addon.mod_chat.messageyoubeep": "Du hast {{$a}} angepiepst", - "addon.mod_chat.mustbeonlinetosendmessages": "Du musst online sein, um Mitteilungen zu senden.", + "addon.mod_chat.mustbeonlinetosendmessages": "Du musst online sein, um Mitteilungen senden zu können.", "addon.mod_chat.nosessionsfound": "Keine Sessions", "addon.mod_chat.showincompletesessions": "Unvollständige Sessions anzeigen", + "addon.mod_choice.cannotsubmit": "Deine Auswahl konnte nicht gespeichert werden. Versuche es noch einmal.", "addon.mod_choice.errorgetchoice": "Fehler beim Laden der Abstimmung", + "addon.mod_choice.previewonly": "Diese Vorschau zeigt die verfügbaren Optionen für diese Aktivität. Du kannst deine Wahl nicht vor {{$a}} einreichen.", + "addon.mod_choice.publishinfoanonafter": "Ergebnisse werden nach deiner Antwort ohne Namensnennung veröffentlicht.", + "addon.mod_choice.publishinfofullafter": "Ergebnisse werden nach deiner Antwort vollständig mit Namensnennung veröffentlicht.", + "addon.mod_choice.publishinfonever": "Die Ergebnisse werden nach deiner Antwort nicht veröffentlicht.", "addon.mod_choice.responsesresultgraphdescription": "{{number}}% der Teilnehmer/innen haben die Option gewählt: {{text}}.", "addon.mod_choice.resultsnotsynced": "Deine letzte Antwort ist in den Ergebnissen noch nicht enthalten. Synchronisiere die Daten.", "addon.mod_choice.yourselection": "Deine Auswahl", "addon.mod_data.confirmdeleterecord": "Möchtest du diesen Datensatz in der Datenbank wirklich löschen?", "addon.mod_data.edittagsnotsupported": "Die Bearbeitung von Tags wird in der App nicht unterstützt.", "addon.mod_data.emptyaddform": "Du hast keine Einträge vorgenommen!", - "addon.mod_data.entrieslefttoadd": "Du musst {{$a.entriesleft}} (weitere) Einträge vornehmen, um diese Aktivität zu beenden.", - "addon.mod_data.entrieslefttoaddtoview": "Du musst {{$a.entrieslefttoview}} (weitere) Einträge vornehmen, bevor du andere Teilnehmerbeiträge anschauen darfst.", + "addon.mod_data.entrieslefttoadd": "Du musst {{$a.entriesleft}} weitere Einträge vornehmen, um diese Aktivität zu beenden.", + "addon.mod_data.entrieslefttoaddtoview": "Du musst {{$a.entrieslefttoview}} weitere Einträge vornehmen, bevor du andere Teilnehmerbeiträge anschauen darfst.", "addon.mod_data.errorapproving": "Fehler beim Freigeben bzw. Ablehnen des Eintrags", "addon.mod_data.errordeleting": "Fehler beim Löschen des Eintrags", + "addon.mod_data.errormustsupplyvalue": "Du musst hier einen Wert eintragen.", "addon.mod_data.gettinglocation": "Geodaten holen ...", "addon.mod_data.mylocation": "Meine Geodaten", "addon.mod_data.searchbytagsnotsupported": "Die Suche nach Tags wird in der App nicht unterstützt.", "addon.mod_feedback.captchaofflinewarning": "Ein Feedback mit Captcha kann offline nicht beendet werden. Captcha funktioniert nur, wenn der Server antworten kann.", + "addon.mod_feedback.complete_the_form": "", "addon.mod_feedback.feedback_submitted_offline": "Das Feedback wurde gespeichert, um es später zu übertragen.", + "addon.mod_feedback.this_feedback_is_already_submitted": "Du hast diese Aktivität bereits beendet.", "addon.mod_folder.emptyfilelist": "Keine Dateien", - "addon.mod_forum.cannotadddiscussionall": "Du darfst kein neues Thema für alle Teilnehmer/innen hinzufügen.", + "addon.mod_forum.cannotadddiscussionall": "Du darfst kein neues Thema für alle Teilnehmer/innen anlegen.", "addon.mod_forum.couldnotadd": "Dein Beitrag wurde nicht abgeschickt.", "addon.mod_forum.couldnotupdate": "Der Beitrag wurde nicht geändert.", + "addon.mod_forum.cutoffdatereached": "Der letzte Abgabetermin für dieses Forum ist erreicht. Du kannst keinen Beitrag mehr schreiben.", "addon.mod_forum.deletesure": "Möchtest du diesen Beitrag wirklich löschen?", + "addon.mod_forum.discussionlocked": "Dieses Thema wurde gesperrt. Du kannst hier nicht weiter antworten.", "addon.mod_forum.erroremptymessage": "Du hast keinen Text geschrieben.", "addon.mod_forum.erroremptysubject": "Du hast den Betreff vergessen.", "addon.mod_forum.errorgetforum": "Fehler beim Laden der Forumsdaten", "addon.mod_forum.errorgetgroups": "Fehler beim Laden der Gruppeneinstellungen", "addon.mod_forum.errorposttoallgroups": "Das neue Thema konnte nicht in allen Gruppen angelegt werden.", + "addon.mod_forum.favouriteupdated": "Deine Markierungsoption wurde aktualisiert.", "addon.mod_forum.forumnodiscussionsyet": "Keine Themen im Forum", "addon.mod_forum.group": "Gruppe", "addon.mod_forum.numdiscussions": "{{numdiscussions}} Themen", @@ -123,28 +153,31 @@ "addon.mod_imscp.showmoduledescription": "Beschreibung anzeigen", "addon.mod_lesson.congratulations": "Herzlichen Glückwunsch! Du hast das Ende der Lektion erreicht.", "addon.mod_lesson.defaultessayresponse": "Deine Freitext-Antwort wird später bewertet.", - "addon.mod_lesson.displayscorewithessays": "Du hast bisher {{$a.score}} von {{$a.tempmaxgrade}} Punkten erzielt (ohne Freitext-Aufgaben).
Freitext-Aufgaben ({{$a.essayquestions}}) werden später bewertet und zur Gesamtpunktzahl hinzugefügt .

Deine derzeitige Bewertung: {{$a.score}} von {{$a.grade}} Punkten (mit Freitext-Aufgaben).", - "addon.mod_lesson.displayscorewithoutessays": "Deine Bewertung: {{$a.score}} Punkte (von {{$a.grade}} Punkten).", - "addon.mod_lesson.enterpassword": "Bitte gib das Kennwort ein:", - "addon.mod_lesson.eolstudentoutoftimenoanswers": "Du hast keine Fragen beantwortet. Du erhältst daher 0 Punkte/keine Bewertung für die Lektion.", + "addon.mod_lesson.displayscorewithessays": "

Du hast bisher für die automatisch bewerteten Fragen die Bewertung {{$a.score}} von {{$a.tempmaxgrade}} erzielt.

\n

Deine Freitext-Aufgaben ({{$a.essayquestions}}) werden später bewertet und zur Gesamtbewertung hinzugefügt.

\n

Deine derzeitige Bewertung ohne die Freitextaufgaben ist {{$a.score}} von {{$a.grade}}.

", + "addon.mod_lesson.displayscorewithoutessays": "Deine Bewertung: {{$a.score}} von {{$a.grade}}", + "addon.mod_lesson.enterpassword": "Gib bitte das Kennwort ein:", + "addon.mod_lesson.eolstudentoutoftimenoanswers": "Du hast keine Fragen beantwortet. Du erhältst deswegen 0 Punkte für die Lektion.", "addon.mod_lesson.errorprefetchrandombranch": "Diese Lektion enthält einen Sprung zu einer zufälligen Seite. Die Lektion kann in der App nicht versucht werden, ohne im Webbrowser begonnen worden zu sein.", "addon.mod_lesson.errorreviewretakenotlast": "Dieser Versuch kann nicht mehr angesehen werden, weil ein weiterer Versuch beendet wurde.", "addon.mod_lesson.finishretakeoffline": "Dieser Versuch wurde offline beendet.", - "addon.mod_lesson.firstwrong": "Deine Antwort ist falsch. Möchtest du sie noch einmal (ohne Bewertung) wiederholen?", + "addon.mod_lesson.firstwrong": "Ihre Antwort ist falsch. Möchtest du es noch einmal (ohne Bewertung) versuchen?", "addon.mod_lesson.leftduringtimed": "Du hast die Lektion abgebrochen. Die Bearbeitung der Lektion ist zeitlich begrenzt.
Klicke auf Fortsetzen, um die Lektion erneut zu beginnen.", - "addon.mod_lesson.leftduringtimednoretake": "Du hast die Lektion abgebrochen. Die Bearbeitung der Lektion ist zeitlich begrenzt.
Du kannst diese Lektion nicht mehr bearbeiten.", - "addon.mod_lesson.loginfail": "Der Login ist gescheitert. Bitte versuche es noch einmal.", + "addon.mod_lesson.leftduringtimednoretake": "Du hast die Lektion abgebrochen. Die Bearbeitung der Lektion ist zeitlich begrenzt.
Du kannst diese Lektion nicht weiter bearbeiten.", + "addon.mod_lesson.loginfail": "Der Login ist gescheitert. Versuche es bitte noch einmal.", "addon.mod_lesson.maximumnumberofattemptsreached": "Du hast die Höchstzahl der Versuche erreicht - weiter zur nächsten Seite", - "addon.mod_lesson.noanswer": "Du hast keine Antwort abgegeben. Gehe zurück und gib eine Antwort ein.", - "addon.mod_lesson.retakefinishedinsync": "Ein Offline-Versuch wurde synchronisiert. Möchtest du ihn überprüfen?", + "addon.mod_lesson.noanswer": "Eine oder mehrere Fragen wurden nicht beantwortet. Gehe zurück und gib Antworten ein.", + "addon.mod_lesson.progressbarteacherwarning2": "Die Fortschrittsanzeige wird dir nicht angezeigt, weil du diese Lektion als Trainer/in bearbeiten kannst.", + "addon.mod_lesson.progresscompleted": "Du hast {{$a}}% der Lektion erledigt.", + "addon.mod_lesson.retakefinishedinsync": "Ein Offline-Versuch wurde synchronisiert. Möchtest du ihn erneut überprüfen?", "addon.mod_lesson.retakelabelfull": "{{retake}}: {{grade}} {{timestart}} ({{duration}})", "addon.mod_lesson.retakelabelshort": "{{retake}}: {{grade}} {{timestart}}", "addon.mod_lesson.secondpluswrong": "Nicht ganz. Möchtest du es noch einmal versuchen?", - "addon.mod_lesson.teacherjumpwarning": "In der Lektion werden \"{{$a.cluster}}\"-Sprünge und/oder \"{{$a.unseen}}\"-Sprünge verwendet. Diese Sprünge werden durch \"Nächste Seite\"-Sprünge ersetzt. Melde dich als Teilnehmer/in an, um diese Sprünge zu testen.", + "addon.mod_lesson.teacherjumpwarning": "In der Lektion werden '{{$a.cluster}}'-Sprünge und/oder '{{$a.unseen}}'-Sprünge verwendet. Diese Sprünge werden durch 'Nächste Seite'-Sprünge ersetzt. Melde dich als Teilnehmer/in an, um die Sprünge zu testen.", "addon.mod_lesson.teacherongoingwarning": "Die aktuelle Bewertung wird nur für Teilnehmer/innen angezeigt. Melde dich als Teilnehmer/in an, um diese Funktion zu testen.", - "addon.mod_lesson.teachertimerwarning": "Die Zeitbegrenzung wird nur für Teilnehmer/innen angezeigt. Melde dich als Teilnehmer/in an, um diese Funktion zu testen.", + "addon.mod_lesson.teachertimerwarning": "Die Zeitbegrenzung funktioniert nur für Teilnehmer/innen. Melde dich als Teilnehmer/in an, um diese Funktion zu testen.", "addon.mod_lesson.warningretakefinished": "Der Versuch wurde auf der Website beendet.", - "addon.mod_lesson.youhaveseen": "Du hast einige Seiten der Lektion schon einmal bearbeitet.
Möchtest du sie an der Stelle fortsetzen, an der du damals aufgehört hast?", + "addon.mod_lesson.youhaveseen": "Du hast einige Seiten der Lektion schon einmal bearbeitet.
Möchtest du an der Stelle weitermachen, an der du damals aufgehört hast?", + "addon.mod_lesson.youranswer": "Deine Antwort", "addon.mod_lesson.yourcurrentgradeisoutof": "Deine derzeitige Bewertung ist {{$a.grade}} von {{$a.total}}", "addon.mod_lti.errorgetlti": "Fehler beim Laden der Moduldaten", "addon.mod_lti.errorinvalidlaunchurl": "Die Launch-URL ist ungültig.", @@ -214,7 +247,7 @@ "addon.notifications.therearentnotificationsyet": "Keine Systemnachrichten", "addon.storagemanager.deletecourse": "Alle Kursdaten entladen", "addon.storagemanager.deletedatafrom": "Daten von {{name}} entladen", - "addon.storagemanager.info": "Auf deinem Gerät gespeicherte Daten beschleunigen die Arbeit und ermöglichen die Offline-Verwendung der App. Du kannst Daten sicher entladen, wenn du Speicherplatz freigeben musst.", + "addon.storagemanager.info": "Auf deinem Gerät gespeicherte Daten beschleunigen die Arbeit und ermöglichen die Offline-Verwendung der App. Du kannst Daten entladen, wenn du Speicherplatz freigeben musst.", "addon.storagemanager.managestorage": "Speicher verwalten", "addon.storagemanager.storageused": "Verwendeter Dateispeicher:", "core.android": "Android", @@ -229,7 +262,6 @@ "core.clicktoseefull": "Tippe zum Anzeigen aller Inhalte", "core.comments.commentsnotworking": "Kommentare können nicht abgerufen werden.", "core.comments.warningcommentsnotsent": "Kommentare konnten nicht synchronisiert werden. {{error}}", - "core.commentsnotworking": "Kommentare können nicht abgerufen werden.", "core.confirmcanceledit": "Möchtest du diese Seite wirklich verlassen? Alle Änderungen gehen verloren!", "core.confirmgotabroot": "Möchtest du wirklich zu {{name}} zurückgehen?", "core.confirmgotabrootdefault": "Möchtest du wirklich zur initalen Seite des aktuellen Tabs zurückkehren?", @@ -261,7 +293,7 @@ "core.course.errordownloadingcourse": "Fehler beim Herunterladen des Kurses", "core.course.errordownloadingsection": "Fehler beim Herunterladen des Abschnitts", "core.course.errorgetmodule": "Fehler beim Laden von Aktivitätsdaten", - "core.course.insufficientavailablequota": "Dein Gerät konnte keinen Speicherplatz für diesen Download zuweisen. Möglicherweise ist Speicherplatz für anstehende App- und Systemupdates reserviert. Sorge zuerst für ausreichend freien Speicher.", + "core.course.insufficientavailablequota": "Dein Gerät konnte für diesen Download keinen Speicherplatz zuweisen. Möglicherweise ist Speicherplatz für anstehende App- und Systemupdates reserviert. Sorge zuerst für ausreichend freien Speicher.", "core.course.insufficientavailablespace": "Du versuchst, {{size}} herunterzuladen. Dein Gerät hat danach möglicherweise nicht genügend Platz, um normal zu funktionieren. Sorge zuerst für ausreichend freien Speicher.", "core.course.manualcompletionnotsynced": "Manueller Abschluss wurde nicht synchronisiert", "core.course.nocontentavailable": "Momentan sind keine Inhalte verfügbar", @@ -281,6 +313,7 @@ "core.courses.errorselfenrol": "Fehler bei der Selbsteinschreibung", "core.courses.filtermycourses": "Meine Kurse filtern", "core.courses.nocourses": "Keine Kursinformation", + "core.courses.notenroled": "Du bist nicht in diesen Kurs eingeschrieben", "core.courses.notenrollable": "Du kannst dich nicht selbst in diesen Kurs einschreiben.", "core.courses.password": "Einschreibeschlüssel", "core.courses.paymentrequired": "Dieser Kurs ist gebührenpflichtig. Bitte bezahle die Teilnahmegebühr, um im Kurs eingeschrieben zu werden.", @@ -298,7 +331,7 @@ "core.dflastweekdate": "ddd", "core.dfmediumdate": "LLL", "core.dftimedate": "HH[:]mm", - "core.digitalminor_desc": "Um ein Nutzerkonto für diese Website zu erstellen, müssen sich deine Erziehungsberechtigten an folgende Stelle wenden:", + "core.digitalminor_desc": "Um ein Nutzerkonto zu erstellen, müssen sich deine Erziehungsberechtigten an folgende Stelle wenden:", "core.discard": "Verwerfen", "core.dismiss": "Abbrechen", "core.downloaded": "Heruntergeladen", @@ -310,7 +343,7 @@ "core.errordownloadingsomefiles": "Fehler beim Laden der Dateien. Einige Dateien könnten fehlen.", "core.errorfileexistssamename": "Eine Datei mit gleichem Namen existiert bereits.", "core.errorinvalidform": "Das Formular enthält ungültige Daten. Fülle alle notwendigen Felder aus und prüfe, dass alle Daten richtig sind.", - "core.errorinvalidresponse": "Ungültige Antwort empfangen. Frage den Administrator, wenn der Fehler weiter auftritt.", + "core.errorinvalidresponse": "Ungültige Antwort empfangen. Frage den Administrator, wenn der Fehler wieder auftritt.", "core.errorloadingcontent": "Fehler beim Laden des Inhalts", "core.errorofflinedisabled": "Der Offline-Zugriff auf diese Website ist deaktiviert. Du musst online sein, um die App zu verwenden.", "core.erroropenfilenoapp": "Fehler: Keine App zum Öffnen dieses Dateityps gefunden.", @@ -320,6 +353,7 @@ "core.errorsomedatanotdownloaded": "Beim Herunterladen dieser Aktivität könnten möglicherweise einige Daten aus Performance- und Datennutzungsgründen ausgenommen worden sein.", "core.errorsync": "Fehler beim Synchronisieren. Versuche es noch einmal.", "core.errorsyncblocked": "{{$a}} kann im Moment wegen eines anderen Vorgangs nicht synchronisiert werden. Versuche es später noch einmal. Falls das Problem weiterhin besteht, starte die App neu.", + "core.explanationdigitalminor": "Diese Informationen sind notwendig um festzustellen, ob du dich selber registrieren darfst. Nur wenn du alt genug bist, kannst du selber den Richtlinien und Nutzungsbedingungen zustimmen. Andernfalls müssen dies deine Erziehungsberechtigten tun.", "core.filenameexist": "Der Dateiname existiert bereits: {{$a}}", "core.fileuploader.audio": "Audio", "core.fileuploader.camera": "Kamera", @@ -334,7 +368,7 @@ "core.fileuploader.errorreadingfile": "Fehler beim Lesen der Datei", "core.fileuploader.errorwhileuploading": "Fehler beim Hochladen der Datei", "core.fileuploader.file": "Datei", - "core.fileuploader.fileuploaded": "Datei hochgeladen", + "core.fileuploader.fileuploaded": "Datei erfolgreich hochgeladen", "core.fileuploader.maxbytesfile": "Die Datei {{$a.file}} ist zu groß. Die maximale Größe zum Hochladen ist {{$a.size}}.", "core.fileuploader.photoalbums": "Fotoalbum", "core.fileuploader.readingfile": "Datei lesen", @@ -344,9 +378,13 @@ "core.fileuploader.uploading": "Hochladen ...", "core.fileuploader.uploadingperc": "Hochladen: {{$a}}%", "core.fileuploader.video": "Video", - "core.forcepasswordchangenotice": "Bitte ändere dein Kennwort, bevor du weiterarbeitest.", + "core.forcepasswordchangenotice": "Ändere dein Kennwort, bevor du weiterarbeitest.", "core.fullnameandsitename": "{{fullname}} ({{sitename}})", + "core.h5p.confirmdialogbody": "Möchtest du wirklich fortfahren? Die Aktion kann nicht zurückgenommen werden.", + "core.h5p.contentchanged": "Dieser Inhalt hat sich seit deiner letzten Verwendung geändert.", + "core.h5p.offlineDialogBody": "Infos zum Abschluss dieser Aufgabe konnten nicht gesendet werden. Überprüfe die Internetverbindung.", "core.h5p.play": "H5P abspielen", + "core.h5p.resizescript": "Füge dieses Skript in die Website ein, wenn du eine dynamische Größenanpassung des eingebetteten Inhalts möchtest:", "core.hasdatatosync": "Die Offline-Daten von {{$a}} müssen synchronisiert werden.", "core.humanreadablesize": "{{size}} {{unit}}", "core.image": "Bild", @@ -360,10 +398,9 @@ "core.login.authenticating": "Authentifizieren ...", "core.login.changepasswordbutton": "Seite zur Kennwortänderung aufrufen", "core.login.changepasswordhelp": "Wenn du Probleme beim Ändern deines Kennworts hast, wende dich an den Administrator deiner Website. Administrator/innen sind die Personen, die das Moodle an deiner Schule, Universität, Firma oder Organisation verwalten. Wenn du nicht weißt, wie du mit ihnen Kontakt aufnehmen kannst, wende dich an deine Trainer/innen.", - "core.login.changepasswordinstructions": "Du kannst dein Kennwort nicht in der App ändern. Klicke auf die folgende Taste, um die Website in einem Webbrowser zu öffnen und dein Kennwort zu ändern. Beachte, dass du den Browser nach dem Ändern des Kennworts schließen musst, da du nicht zur App zurückgeleitet wirst.", + "core.login.changepasswordinstructions": "Du kannst dein Kennwort nicht in der App ändern. Klicke auf die folgende Taste, um Moodle in einem Webbrowser zu öffnen und dein Kennwort zu ändern. Beachte, dass du den Browser nach dem Ändern des Kennworts schließen musst, um wieder in die App zurückzukommen.", "core.login.changepasswordlogoutinstructions": "Wenn du die Website wechseln oder dich abmelden möchtest, klicke auf die folgende Taste:", "core.login.changepasswordreconnectinstructions": "Klicke auf die folgende Taste, um die Verbindung zur Website wiederherzustellen. Beachte, dass du zum vorherigen Bildschirm zurückkehrst, wenn du dein Kennwort nicht erfolgreich geändert hast.", - "core.login.checksiteversion": "Prüfe, ob die Website mindestens Moodle 2.4 verwendet.", "core.login.confirmdeletesite": "Möchtest du '{{sitename}}' wirklich aus der Liste löschen?", "core.login.connect": "Verbinden", "core.login.connecttomoodle": "Zu Moodle verbinden", @@ -373,7 +410,7 @@ "core.login.contactyouradministratorissue": "Frage den Administrator, um folgendes Problem prüfen zu lassen: {{$a}}", "core.login.createuserandpass": "Wähle deinen Anmeldenamen und dein Kennwort", "core.login.credentialsdescription": "Gib den Anmeldenamen und das Kennwort ein. ", - "core.login.emailconfirmsent": "

Um sicherzugehen, dass sich niemand unberechtigt über die von dir angegebene E-Mail anmeldet, wird eine automatische Benachrichtigung an diese Adresse {{$a}} gesendet. Je nach Netzlast trifft sie sofort oder auch etwas später bei dir ein.

\n

Die Benachrichtigung enthält eine Anleitung, wie du deine Registrierung bestätigst.\nDanach bist du auf dieser Moodle-Seite registriert und kannst sofort loslegen.

\n

Bei Problemen wende dich bitte an die Administrator/innen der Website.

", + "core.login.emailconfirmsent": "

Um sicherzugehen, dass sich niemand unberechtigt über die von dir angegebene E-Mail anmeldet, wird eine automatische Benachrichtigung an diese Adresse {{$a}} gesendet.

\n

Die Benachrichtigung enthält eine Anleitung, wie du deine Registrierung bestätigst. Danach bist du auf dieser Moodle-Seite registriert und kannst sofort loslegen.

\n

Bei Problemen wende dich bitte an die Administrator/innen der Website.

", "core.login.emailconfirmsentnoemail": "

An deine E-Mail-Adresse wurde eine Mitteilung gesendet.

Die Mitteilung enthält eine einfache Anleitung, um deine Registrierung abzuschließen. Falls du Probleme hast, frage den Administrator der Website.

", "core.login.emailnotmatch": "Die E-Mail-Adressen stimmen nicht überein.", "core.login.erroraccesscontrolalloworigin": "Der Cross-Origin.Aufruf wurde zurückgewiesen. Weitere Infos: https://docs.moodle.org/dev/Moodle_Mobile_development_using_Chrome_or_Chromium", @@ -381,7 +418,7 @@ "core.login.errorupdatesite": "Fehler bei der Authentifizierung", "core.login.findyoursite": "Website suchen", "core.login.firsttime": "Bist du zum ersten Mal auf dieser Webseite?", - "core.login.forcepasswordchangenotice": "Bitte ändere dein Kennwort, bevor du weiterarbeitest.", + "core.login.forcepasswordchangenotice": "Ändere dein Kennwort, bevor du weiterarbeitest.", "core.login.helpmelogin": "

Auf der Welt gibt es viele tausend Websites mit Moodle. Diese App kann aber nur mit Websites verbunden werden, die für den Zugriff mit der mobilen Moodle App freigegeben wurden.

Falls du dich nicht mit deiner Website verbinden kannst, frage den Administrator der Website und bitte ihn, die Anleitung zu lesen. https://docs.moodle.org/de/Moodle_App

Um die App mit einer Demoseite auszuprobieren, schreibe teacher oder student in das Feld Website-Adresse und tippe dann auf Verbinden.

", "core.login.invalidaccount": "Prüfe deine Anmeldedaten oder frage den Administrator der Website.", "core.login.invaliddate": "Ungültiges Datum", @@ -390,25 +427,24 @@ "core.login.invalidtime": "Ungültige Zeitangabe", "core.login.invalidvaluemax": "Der Maximalwert ist {{$a}}.", "core.login.invalidvaluemin": "Der Minimalwert ist {{$a}}.", - "core.login.legacymoodleversion": "Du versuchst, dich mit einer nicht unterstützen Moodle-Version zu verbinden. Verwende die Moodle-Classic-App, um auf diese Website zu gelangen.", - "core.login.legacymoodleversiondesktop": "Du versuchst, dich mit {{$a}} zu verbinden.

Diese Website läuft mit einer alten, nicht unterstützen Moodle-Version, die nicht mit der Moodle-Desktop-App funktioniert.

Falls diese Website dir gehört und du Probleme mit der Aktualisierung hast, frage bitte einen lokalen Moodle-Partner. Deine Anfrage kannst du über unsere Kontaktseite abgeben.", - "core.login.legacymoodleversiondesktopdownloadold": "

Alternativ kannst du auf diese Website zugreifen, wenn du eine ältere Version der App herunterlädt und verwendest.", "core.login.localmobileunexpectedresponse": "Die Verbindung zum Plugin 'Moodle Mobile - Zusatzfeatures' ist fehlgeschlagen. Du wirst über den standardmäßigen mobilen Webservice authentifiziert.", "core.login.loggedoutssodescription": "Du musst dich neu authentifizieren. Melde dich im Browser auf der Website an.", "core.login.loginbutton": "Anmelden", "core.login.logininsiterequired": "Du musst dich für diese Website im Browser anmelden.", - "core.login.loginsteps": "Guten Tag!\n\nUm an den verschiedenen Kursen teilnehmen zu können, musst du dir einen Nutzerzugang für diese Website anlegen. Für einige Kurse könnte zusätzlich ein Zugangsschlüssel notwendig sein.\n\n

Gehe dazu bitte wie folgt vor:\n

    \n
  1. Fülle das Formular Neuer Zugang mit deinen Angaben aus.
  2. \n
  3. Du erhältst umgehend eine Benachrichtigung an die von dir angegebene E-Mail-Adresse.
  4. \n
  5. Lies diese E-Mail genau und klicke den darin enthaltenen Link an.
  6. \n
  7. Dein Zugang wird auf diese Weise bestätigt und du wirst automatisch mit deinen zuvor angegebenen Zugangsdaten auf der Startseite eingeloggt.
  8. \n
  9. Jetzt wählst du bitte den Kurs aus, an dem du teilnehmen möchtest.
  10. \n
  11. Für einige Kurse ist ein Zugangsschlüssel notwendig. Benutze dazu bitte den Zugangsschlüssel, den dir deine Trainer/in mitgeteilt hat. Mit diesem Zugangsschlüssel kannst du dich in den entsprechenden Kurs einschreiben.
  12. \n
  13. Nun hast du einen Nutzerzugang zur Website. Zukünftig musst du jedes Mal den bei deiner Registrierung gewählten Anmeldenamen und das Kennwort (im Login-Block auf dieser Seite) eingeben, um sich einzuloggen und Zugang zu den verschiedenen Kursen zu erhalten.
  14. \n
", + "core.login.loginsteps": "Für den vollen Zugriff auf die Website brauchst du ein Nutzerkonto.", "core.login.mobileservicesnotenabled": "Der mobile Zugriff ist für diese Website nicht aktiviert. Frage den Administrator, wenn du die mobile App aktiviert haben möchtest.", "core.login.mustconfirm": "Du musst deinen Zugang bestätigen", "core.login.newsitedescription": "Gib die URL zu deiner Website ein.", "core.login.notloggedin": "Du musst angemeldet sein.", + "core.login.passwordforgotteninstructions2": "Um dein Kennwort zurückzusetzen, trage bitte entweder deinen Anmeldenamen oder deine E-Mail-Adresse ein. Wenn du in der Datenbank zu finden bist, wird eine Mitteilung an deine E-Mail-Adresse verschickt. Diese Mitteilung enthält eine Anleitung für die weiteren Schritte.", "core.login.passwordrequired": "Kennwort fehlt", - "core.login.policyagree": "Lies diese Zustimmungserklärung sorgfältig. Du musst erst zustimmen, um diese Website weiter nutzen zu können. Stimmst du zu?", + "core.login.policyagree": "Lies diese Datenschutzinfos sorgfältig. Du musst zustimmen, um diese Website weiter nutzen zu können. Stimmst du zu?", "core.login.policyagreementclick": "Klicke hier, um die Zustimmungserklärung zu lesen.", + "core.login.potentialidps": "Verwende dein Nutzerkonto bei:", "core.login.problemconnectingerror": "Probleme beim Verbinden mit", "core.login.problemconnectingerrorcontinue": "Prüfe, ob die URL richtig eingegeben ist. Versuche es noch einmal.", - "core.login.recaptchachallengeimage": "reCaptcha Challenge Image", - "core.login.recaptchaexpired": "Überprüfung notwendig. Beantworte die Sicherheitsfrage noch einmal.", + "core.login.recaptchachallengeimage": "reCaptcha Challenge-Bild", + "core.login.recaptchaexpired": "Überprüfung abgelaufen. Beantworte die Sicherheitsfrage noch einmal.", "core.login.recaptchaincorrect": "Die Antwort auf die Sicherheitsfrage ist falsch.", "core.login.reconnect": "Neu verbinden", "core.login.reconnectdescription": "Die Authentifizierung ist abgelaufen oder ungültig. Du musst dich neu anmelden.", @@ -419,9 +455,9 @@ "core.login.siteaddress": "URL der Website", "core.login.sitehasredirect": "Deine Website enthält mindestens eine HTTP-Weiterleitung. Die App kann keine Weiterleitungen verarbeiten und der Verbindungaufbau könnte fehlschlagen.", "core.login.siteinmaintenance": "Diese Website ist im Wartungsmodus.", - "core.login.sitepolicynotagreederror": "Zustimmungserklärung nicht bestätigt", + "core.login.sitepolicynotagreederror": "Die Zustimmungserklärung wurde nicht bestätigt.", "core.login.siteurl": "URL der Website", - "core.login.siteurlrequired": "Die URL der Website ist notwendig, z.B. https://www.meinmoode.de", + "core.login.siteurlrequired": "Die URL der Website ist notwendig, z.B. https://www.meinmoodle.de", "core.login.stillcantconnect": "Kannst du dich immer noch nicht verbinden?", "core.login.usernamerequired": "Anmeldename fehlt", "core.login.visitchangepassword": "Möchtest du die Website aufrufen, um das Kennwort zu ändern?", @@ -435,7 +471,7 @@ "core.nooptionavailable": "Keine Option verfügbar", "core.nopasswordchangeforced": "Du kannst nicht weitermachen, ohne das Kennwort zu ändern.", "core.nopermissionerror": "Du hast aktuell keine Rechte, um dies zu tun.", - "core.nopermissions": "Entschuldigung, aber du besitzt derzeit keine Rechte, dies zu tun ({{$a}}).", + "core.nopermissions": "Du hast derzeit keine Rechte, dies zu tun ({{$a}}).", "core.notapplicable": "n/a", "core.notingroup": "Diese Aktivität ist nur für Gruppenmitglieder zugänglich. Du bist kein Gruppenmitglied.", "core.notsent": "Nicht gesendet", @@ -443,7 +479,7 @@ "core.openfullimage": "Tippe, um das Bild in voller Größe anzuzeigen", "core.openinbrowser": "Im Browser öffnen", "core.parentlanguage": "de", - "core.paymentinstant": "Klicke auf den Button, um die Teilnahmegebühr zu bezahlen. Sobald dieser Zahlvorgang abgeschlossen ist, wirst du automatisch in den Kurs eingeschrieben.", + "core.paymentinstant": "Klicke auf die Taste, um die Teilnahmegebühr zu bezahlen. Sobald der Zahlvorgang abgeschlossen ist, wirst du automatisch in den Kurs eingeschrieben.", "core.percentagenumber": "{{$a}}%", "core.phone": "Telefon", "core.pulltorefresh": "Zum Aktualisieren runterziehen", @@ -533,6 +569,8 @@ "core.strftimerecent": "%d. %b, %H:%M", "core.strftimerecentfull": "%a, %d. %b %Y, %H:%M", "core.strftimetime": "%H:%M", + "core.strftimetime12": "%I:%M %p", + "core.strftimetime24": "%H:%M", "core.tablet": "Tablet", "core.tag.errorareanotsupported": "Dieser Tag-Bereich wird von der App nicht unterstützt.", "core.tag.warningareasnotsupported": "Einige der Tag-Bereiche werden nicht angezeigt, weil sie von der App nicht unterstützt werden.", @@ -548,6 +586,7 @@ "core.unzipping": "Entpacken ...", "core.updaterequired": "Aktualisierung der App notwendig", "core.updaterequireddesc": "Aktualisieren Sie Ihre App auf die Version {{$a}}", + "core.upgraderunning": "Diese Website wird gerade aktualisiert. Versuche es später nochmal.", "core.user.contact": "Kontakt", "core.user.detailsnotavailable": "Die Nutzerdetails zu dieser Person sind für dich nicht verfügbar.", "core.user.editingteacher": "Trainer/in", diff --git a/src/assets/lang/de.json b/src/assets/lang/de.json index 7dd6f1b60..7d0336939 100644 --- a/src/assets/lang/de.json +++ b/src/assets/lang/de.json @@ -21,7 +21,7 @@ "addon.badges.language": "Sprache", "addon.badges.noalignment": "Für diesen Badge sind keine externen Skills oder Kompetenzen angegeben.", "addon.badges.nobadges": "Keine Badges verfügbar", - "addon.badges.norelated": "Dieser Badge hat keine verwandten Badges.", + "addon.badges.norelated": "Dieser Badge hat keine zugeordneten Badges.", "addon.badges.recipientdetails": "Empfängerdetails", "addon.badges.relatedbages": "Zugeordnete Badges", "addon.badges.version": "Version", @@ -91,7 +91,7 @@ "addon.calendar.calendarreminders": "Kalendererinnerungen", "addon.calendar.categoryevents": "Kursbereichstermine", "addon.calendar.confirmeventdelete": "Möchten Sie den Termin '{{$a}}' wirklich löschen?", - "addon.calendar.confirmeventseriesdelete": "Der Termin '{{$a.name}}' ist Teil der Serie. Wollen Sie nur diesen Termin entfernen oder alle {{$a.count}} Termine dieser Serie?", + "addon.calendar.confirmeventseriesdelete": "Der Termin '{{$a.name}}' ist Teil der Serie. Möchten Sie nur diesen Termin entfernen oder alle {{$a.count}} Termine dieser Serie?", "addon.calendar.courseevents": "Kurstermine", "addon.calendar.currentmonth": "Aktueller Monat", "addon.calendar.daynext": "Nächster Tag", @@ -215,11 +215,11 @@ "addon.coursecompletion.coursecompletion": "Kursabschluss", "addon.coursecompletion.criteria": "Kriterien", "addon.coursecompletion.criteriagroup": "Kriteriengruppe", - "addon.coursecompletion.criteriarequiredall": "Alle nachfolgenden Kriterien sind notwendig", - "addon.coursecompletion.criteriarequiredany": "Eine der nachfolgenden Kriterien ist notwendig", + "addon.coursecompletion.criteriarequiredall": "Alle folgenden Kriterien sind notwendig", + "addon.coursecompletion.criteriarequiredany": "Eines der folgenden Kriterien ist notwendig", "addon.coursecompletion.inprogress": "In Bearbeitung", "addon.coursecompletion.manualselfcompletion": "Manueller eigener Abschluss", - "addon.coursecompletion.nottracked": "Für Sie läuft aktuell in diesem Kurs keine Abschlussverfolgung", + "addon.coursecompletion.nottracked": "Aktuell läuft für Sie in diesem Kurs keine Abschlussverfolgung", "addon.coursecompletion.notyetstarted": "Noch nicht begonnen", "addon.coursecompletion.pending": "Unerledigt", "addon.coursecompletion.required": "Erforderlich", @@ -258,7 +258,7 @@ "addon.messages.deleteconversation": "Kommunikation löschen", "addon.messages.deleteforeveryone": "Für mich und alle anderen löschen", "addon.messages.deletemessage": "Mitteilung löschen", - "addon.messages.deletemessageconfirmation": "Möchten Sie diese Mitteilung wirklich löschen? Die Mitteilung wird nur in Ihrem Verlauf gelöscht, aber nicht bei der Person, die die Mitteilung gesendet oder empfangen hat..", + "addon.messages.deletemessageconfirmation": "Möchten Sie diese Mitteilung wirklich löschen? Die Mitteilung wird nur in Ihrem Verlauf gelöscht, aber nicht bei der Person, die die Mitteilung gesendet oder empfangen hat.", "addon.messages.errordeletemessage": "Fehler beim Löschen der Mitteilung", "addon.messages.errorwhileretrievingcontacts": "Fehler beim Abrufen der Kontakte vom Server", "addon.messages.errorwhileretrievingdiscussions": "Fehler beim Abrufen der Themen vom Server", @@ -291,10 +291,10 @@ "addon.messages.removefromfavourites": "Markierung der Kommunikaton entfernen", "addon.messages.removefromyourcontacts": "Aus Kontakten entfernen", "addon.messages.requests": "Anfragen", - "addon.messages.requirecontacttomessage": "Sie müssen {{$a}} bitten, Sie als Kontakt hinzuzufügen, um eine Mitteilung senden zu können.", + "addon.messages.requirecontacttomessage": "Sie müssen {{$a}} anfragen, Sie als Kontakt hinzuzufügen. Erst dann können Sie eine Mitteilung senden.", "addon.messages.searchcombined": "Personen und Mitteilungen suchen", "addon.messages.selfconversation": "Persönlicher Bereich", - "addon.messages.selfconversationdefaultmessage": "Speichern Sie Entwürfe von Nachrichten, Links, Notizen etc. für einen späteren Zugriff.", + "addon.messages.selfconversationdefaultmessage": "Speichern Sie Entwürfe von Nachrichten, Links, Notizen usw. für einen späteren Zugriff.", "addon.messages.sendcontactrequest": "Kontaktanfrage senden", "addon.messages.showdeletemessages": "Mitteilungen löschen anzeigen", "addon.messages.type_blocked": "Blockiert", @@ -321,10 +321,10 @@ "addon.mod_assign.addnewattempt": "Neuen Versuch hinzufügen", "addon.mod_assign.addnewattemptfromprevious": "Neuen Versuch auf Grundlage der vorherigen Lösung abgeben", "addon.mod_assign.addsubmission": "Abgabe hinzufügen", - "addon.mod_assign.allowsubmissionsanddescriptionfromdatesummary": "Die Aufgabendetails und die Lösungsabgabe stehen zur Verfügung ab {{$a}}", + "addon.mod_assign.allowsubmissionsanddescriptionfromdatesummary": "Die Aufgabendetails und die Lösungsabgabe sind verfügbar ab {{$a}}", "addon.mod_assign.allowsubmissionsfromdate": "Abgabebeginn", "addon.mod_assign.allowsubmissionsfromdatesummary": "Abgabe möglich ab {{$a}}", - "addon.mod_assign.applytoteam": "Bewertungen und Feedback der gesamten Gruppe zuweisen.", + "addon.mod_assign.applytoteam": "Bewertungen und Feedback der gesamten Gruppe zuweisen", "addon.mod_assign.assignmentisdue": "Aufgabe ist fällig", "addon.mod_assign.attemptnumber": "Nummer", "addon.mod_assign.attemptreopenmethod": "Versuche erneut bearbeitbar", @@ -334,7 +334,7 @@ "addon.mod_assign.cannoteditduetostatementsubmission": "Sie können in der App keine Abgabe hinzufügen oder bearbeiten, weil keine Erklärung zur Eigenständigkeit von der Website abgerufen werden konnte.", "addon.mod_assign.cannotgradefromapp": "Manche Bewertungsmethoden werden von der App bisher nicht unterstützt und können nicht verändert werden.", "addon.mod_assign.cannotsubmitduetostatementsubmission": "Sie können in der App keine Abgabe machen, weil von der Website keine Erklärung zur Eigenständigkeit abgerufen werden konnte.", - "addon.mod_assign.confirmsubmission": "Wenn Sie nun Ihre Lösung zur Bewertung einreichen, können Sie keine Änderungen mehr vornehmen. Sind Sie sich sicher?", + "addon.mod_assign.confirmsubmission": "Wenn Sie nun Ihre Lösung zur Bewertung einreichen, können Sie nichts mehr verändern. Sind Sie sich sicher?", "addon.mod_assign.currentattempt": "Dies ist Versuch {{$a}}.", "addon.mod_assign.currentattemptof": "Versuch {{$a.attemptnumber}} (mögliche Versuche {{$a.maxattempts}})", "addon.mod_assign.currentgrade": "Aktuelle Bewertung in Bewertungen", @@ -371,14 +371,14 @@ "addon.mod_assign.markingworkflowstatereleased": "Freigegeben (Teilnehmer/innen informieren)", "addon.mod_assign.modulenameplural": "Aufgaben", "addon.mod_assign.multipleteams": "Mitglied in mehreren Gruppen", - "addon.mod_assign.multipleteams_desc": "Diese Aufgabe wird in Gruppen abgegeben. Sie sind Mitglied in mehr als einer Gruppe. Um die Aufgabe einzureichen müssen Sie Mitglied in genau einer Gruppe sein, damit Ihre Einreichung Ihrer Gruppe korrekt zugeordnet werden kann. Bitte kontaktieren Sie Ihren Trainer um Ihre Gruppenzugehörigkeit zu aktualisieren.", + "addon.mod_assign.multipleteams_desc": "Diese Aufgabe wird in Gruppen abgegeben. Sie sind Mitglied in mehr als einer Gruppe. Um ihre Einreichung korrekt Ihrer Gruppe zuordnen zu können, müssen Sie Mitglied in genau einer Gruppe sein. Kontaktieren Sie Ihre Trainer/innen, um Ihre Gruppenzugehörigkeit zu aktualisieren.", "addon.mod_assign.noattempt": "Kein Versuch", - "addon.mod_assign.nomoresubmissionsaccepted": "Weitere Abgaben sind nur zugelassen, wenn der Abgabezeitraum verlängert wurde.", + "addon.mod_assign.nomoresubmissionsaccepted": "Weitere Abgaben sind nur zugelassen, wenn der Abgabezeitraum verlängert wird.", "addon.mod_assign.noonlinesubmissions": "Diese Aufgabe benötigt keine Online-Abgabe", "addon.mod_assign.nosubmission": "Für diese Aufgabe wurde nichts abgegeben", "addon.mod_assign.notallparticipantsareshown": "Teilnehmer/innen, die nichts abgegeben haben, werden nicht angezeigt.", "addon.mod_assign.noteam": "Nicht Mitglied einer Gruppe", - "addon.mod_assign.noteam_desc": "Diese Aufgabe wird in Gruppen abgegeben. Sie sind kein Mitglied einer Gruppe, deshalb können Sie die Aufgabe derzeit nicht einreichen. Bitte kontaktieren Sie Ihren Trainer um einer Gruppe hinzugefügt zu werden.", + "addon.mod_assign.noteam_desc": "Diese Aufgabe wird in Gruppen abgegeben. Sie sind kein Mitglied in einer Gruppe und können die Aufgabe deswegen nicht einreichen. Kontaktieren Sie Ihre Trainer/innen, um einer Gruppe hinzugefügt zu werden.", "addon.mod_assign.notgraded": "Nicht bewertet", "addon.mod_assign.numberofdraftsubmissions": "Entwürfe", "addon.mod_assign.numberofparticipants": "Teilnehmer/innen", @@ -390,8 +390,8 @@ "addon.mod_assign.overdue": "Abgabeende überschritten seit: {{$a}}", "addon.mod_assign.submission": "Abgabe", "addon.mod_assign.submissioneditable": "Teilnehmer/innen können eingereichte Lösung bearbeiten", - "addon.mod_assign.submissionnoteditable": "Teilnehmer/innen können eingereichte Lösung nicht bearbeiten", - "addon.mod_assign.submissionnotsupported": "Diese Abgabe wird von der App nicht unterstützt und es könnten Informationen fehlen.", + "addon.mod_assign.submissionnoteditable": "Teilnehmer/innen können die eingereichte Lösung nicht bearbeiten", + "addon.mod_assign.submissionnotsupported": "Diese Abgabe wird von der App nicht unterstützt und es könnten Infos fehlen.", "addon.mod_assign.submissionslocked": "Bei dieser Aufgabe können derzeit keine Lösungen abgeben werden.", "addon.mod_assign.submissionstatus": "Abgabestatus", "addon.mod_assign.submissionstatus_": "Keine Abgabe", @@ -403,7 +403,7 @@ "addon.mod_assign.submissionstatusheading": "Abgabestatus", "addon.mod_assign.submissionteam": "Gruppe", "addon.mod_assign.submitassignment": "Aufgabe abgeben", - "addon.mod_assign.submitassignment_help": "Sobald diese Aufgabe abgegeben wird, sind keine weiteren Änderungen mehr möglich", + "addon.mod_assign.submitassignment_help": "Sobald diese Aufgabe abgegeben ist, sind keine Änderungen mehr möglich.", "addon.mod_assign.submittedearly": "Aufgabe wurde {{$a}} vor dem Abgabeende abgegeben", "addon.mod_assign.submittedlate": "Aufgabe wurde {{$a}} verspätet abgegeben", "addon.mod_assign.timemodified": "Zuletzt geändert", @@ -423,7 +423,7 @@ "addon.mod_assign_submission_comments.pluginname": "Abgabekommentare", "addon.mod_assign_submission_file.pluginname": "Dateiabgabe", "addon.mod_assign_submission_onlinetext.pluginname": "Texteingabe online", - "addon.mod_assign_submission_onlinetext.wordlimitexceeded": "Die Höchstgrenze der Wörter für diese Aufgabenlösung ist {{$a.limit}}. Ihre Antwort enthält {{$a.count}} Wörter. Überarbeiten Sie bitte Ihre Lösung und speichern Sie sie dann erneut.", + "addon.mod_assign_submission_onlinetext.wordlimitexceeded": "Die maximale Anzahl von Wörtern für diese Aufgabenlösung ist {{$a.limit}}. Ihre Antwort hat {{$a.count}} Wörter. Verkürzen Sie Ihren Text und speichern Sie ihn dann erneut.", "addon.mod_book.errorchapter": "Fehler beim Lesen des Kapitels", "addon.mod_book.modulenameplural": "Bücher", "addon.mod_book.tagarea_book_chapters": "Buchkapitel", @@ -432,7 +432,7 @@ "addon.mod_chat.chatreport": "Chat-Protokolle", "addon.mod_chat.currentusers": "Aktuelle Nutzer/innen", "addon.mod_chat.enterchat": "Chat betreten", - "addon.mod_chat.entermessage": "Schreiben Sie Ihre Mitteilung", + "addon.mod_chat.entermessage": "Mitteilung schreiben", "addon.mod_chat.errorwhileconnecting": "Fehler beim Verbinden zum Chat", "addon.mod_chat.errorwhilegettingchatdata": "Fehler beim Abrufen des Chats", "addon.mod_chat.errorwhilegettingchatusers": "Fehler beim Anzeigen der Personen im Chat", @@ -440,21 +440,21 @@ "addon.mod_chat.errorwhilesendingmessage": "Fehler beim Senden der Mitteilung", "addon.mod_chat.messagebeepseveryone": "{{$a}} piepst jeden an!", "addon.mod_chat.messagebeepsyou": "{{$a}} hat Sie angepiepst!", - "addon.mod_chat.messageenter": "{{$a}} hat den Chat gerade betreten", + "addon.mod_chat.messageenter": "{{$a}} hat den Chat betreten", "addon.mod_chat.messageexit": "{{$a}} hat den Chat verlassen", "addon.mod_chat.messages": "Mitteilungen", "addon.mod_chat.messageyoubeep": "Sie haben {{$a}} angepiepst", "addon.mod_chat.modulenameplural": "Chats", - "addon.mod_chat.mustbeonlinetosendmessages": "Sie müssen online sein, um Mitteilungen zu senden.", + "addon.mod_chat.mustbeonlinetosendmessages": "Sie müssen online sein, um Mitteilungen senden zu können.", "addon.mod_chat.nomessages": "Keine Mitteilungen", "addon.mod_chat.nosessionsfound": "Keine Sessions", "addon.mod_chat.saidto": "sagte zu", "addon.mod_chat.send": "Senden", - "addon.mod_chat.sessionstart": "Nächster Chat beginnt {{$a.date}}, (also in {{$a.fromnow}})", + "addon.mod_chat.sessionstart": "Der nächste Chat beginnt {{$a.date}}, (also in {{$a.fromnow}})", "addon.mod_chat.showincompletesessions": "Unvollständige Sessions anzeigen", "addon.mod_chat.talk": "Sprechen", "addon.mod_chat.viewreport": "Chat-Protokolle anzeigen", - "addon.mod_choice.cannotsubmit": "Ihre Auswahl konnte nicht gespeichert werden. Versuchen Sie es bitte noch einmal.", + "addon.mod_choice.cannotsubmit": "Ihre Auswahl konnte nicht gespeichert werden. Versuchen Sie es noch einmal.", "addon.mod_choice.choiceoptions": "Abstimmoptionen", "addon.mod_choice.errorgetchoice": "Fehler beim Laden der Abstimmung", "addon.mod_choice.expired": "Diese Abstimmung ist seit {{$a}} beendet.", @@ -465,13 +465,13 @@ "addon.mod_choice.numberofuser": "Anzahl der Antworten", "addon.mod_choice.numberofuserinpercentage": "Prozent der Antworten", "addon.mod_choice.previewonly": "Diese Vorschau zeigt die verfügbaren Optionen für diese Aktivität. Sie können Ihre Wahl nicht vor {{$a}} einreichen.", - "addon.mod_choice.publishinfoanonafter": "Ergebnisse werden ohne Namensnennung nach Ihrer Antwort veröffentlicht.", + "addon.mod_choice.publishinfoanonafter": "Ergebnisse werden nach Ihrer Antwort ohne Namensnennung veröffentlicht.", "addon.mod_choice.publishinfoanonclose": "Ergebnisse werden ohne Namensnennung nach Abschluss der Aktivität veröffentlicht.", - "addon.mod_choice.publishinfofullafter": "Ergebnisse werden vollständig mit Namensnennung nach Ihrer Antwort veröffentlicht.", - "addon.mod_choice.publishinfofullclose": "Ergebnisse werden vollständig mit Namensnennung nach Abschluss der Aktivität veröffentlicht.", + "addon.mod_choice.publishinfofullafter": "Ergebnisse werden nach Ihrer Antwort vollständig mit Namensnennung veröffentlicht.", + "addon.mod_choice.publishinfofullclose": "Ergebnisse werden nach Abschluss der Aktivität vollständig mit Namensnennung veröffentlicht.", "addon.mod_choice.publishinfonever": "Die Ergebnisse werden nach Ihrer Antwort nicht veröffentlicht.", "addon.mod_choice.removemychoice": "Meine Auswahl löschen", - "addon.mod_choice.responses": "Stimmabgaben", + "addon.mod_choice.responses": "Antworten", "addon.mod_choice.responsesresultgraphdescription": "{{number}}% der Teilnehmer/innen haben die Option gewählt: {{text}}.", "addon.mod_choice.responsesresultgraphheader": "Grafische Darstellung", "addon.mod_choice.resultsnotsynced": "Ihre letzte Antwort ist in den Ergebnissen noch nicht enthalten. Synchronisieren Sie die Daten.", @@ -484,19 +484,19 @@ "addon.mod_data.approve": "Zulassen", "addon.mod_data.approved": "Bestätigt", "addon.mod_data.ascending": "Aufsteigend", - "addon.mod_data.authorfirstname": "Vorname (Autor)", - "addon.mod_data.authorlastname": "Nachname (Autor)", - "addon.mod_data.confirmdeleterecord": "Möchten Sie diesen Datensatz wirklich in der Datenbank löschen?", + "addon.mod_data.authorfirstname": "Vorname (Autor/in)", + "addon.mod_data.authorlastname": "Nachname (Autor/in)", + "addon.mod_data.confirmdeleterecord": "Möchten Sie diesen Datensatz in der Datenbank wirklich löschen?", "addon.mod_data.descending": "Absteigend", "addon.mod_data.disapprove": "Eintrag nicht freigegeben", "addon.mod_data.edittagsnotsupported": "Das Bearbeitung von Tags wird in der App nicht unterstützt.", "addon.mod_data.emptyaddform": "Sie haben keine Einträge vorgenommen!", - "addon.mod_data.entrieslefttoadd": "Sie müssen {{$a.entriesleft}} (weitere) Einträge vornehmen, um diese Aktivität zu beenden.", - "addon.mod_data.entrieslefttoaddtoview": "Sie müssen {{$a.entrieslefttoview}} (weitere) Einträge vornehmen, bevor Sie andere Teilnehmerbeiträge betrachten können.", + "addon.mod_data.entrieslefttoadd": "Sie müssen {{$a.entriesleft}} weitere Einträge vornehmen, um diese Aktivität zu beenden.", + "addon.mod_data.entrieslefttoaddtoview": "Sie müssen {{$a.entrieslefttoview}} weitere Einträge vornehmen, bevor Sie andere Teilnehmerbeiträge betrachten können.", "addon.mod_data.errorapproving": "Fehler beim Freigeben bzw. Ablehnen des Eintrags", "addon.mod_data.errordeleting": "Fehler beim Löschen des Eintrags", "addon.mod_data.errormustsupplyvalue": "Sie müssen hier einen Wert eintragen.", - "addon.mod_data.expired": "Die Aktivität wurde am {{$a}} abgeschlossen und ist nicht weiter verfügbar.", + "addon.mod_data.expired": "Die Aktivität wurde am {{$a}} abgeschlossen und ist nicht mehr verfügbar.", "addon.mod_data.fields": "Felder", "addon.mod_data.foundrecords": "Einträge gefunden: {{$a.num}}/{{$a.max}} (Filter zurücksetzen)", "addon.mod_data.gettinglocation": "Geodaten holen ...", @@ -528,18 +528,18 @@ "addon.mod_feedback.anonymous_entries": "Anonyme Einträge ({{$a}})", "addon.mod_feedback.average": "Mittelwert", "addon.mod_feedback.captchaofflinewarning": "Ein Feedback mit Captcha kann offline nicht beendet werden. Captcha funktioniert nur, wenn der Server antworten kann.", - "addon.mod_feedback.complete_the_form": "Beantworten Sie die Fragen", + "addon.mod_feedback.complete_the_form": "Fragebogen ausfüllen", "addon.mod_feedback.completed_feedbacks": "Ausgefüllte Feedbacks", - "addon.mod_feedback.continue_the_form": "Beantworten der Fragen fortsetzen", - "addon.mod_feedback.feedback_is_not_open": "Feedback ist zu diesem Zeitpunkt nicht möglich", + "addon.mod_feedback.continue_the_form": "Ausfüllen des Fragebogens fortsetzen", + "addon.mod_feedback.feedback_is_not_open": "Ein Feedback ist zu diesem Zeitpunkt nicht möglich", "addon.mod_feedback.feedback_submitted_offline": "Das Feedback wurde gespeichert, um es später zu übertragen.", "addon.mod_feedback.feedbackclose": "Antworten erlauben bis", "addon.mod_feedback.feedbackopen": "Antworten erlauben ab", - "addon.mod_feedback.mapcourses": "Diesem Feedback Kurse zuordnen", + "addon.mod_feedback.mapcourses": "Feedback zu Kursen zuordnen", "addon.mod_feedback.maximal": "Maximal", "addon.mod_feedback.minimal": "Minimal", "addon.mod_feedback.mode": "Modus", - "addon.mod_feedback.modulenameplural": "Feedbacks", + "addon.mod_feedback.modulenameplural": "Feedback", "addon.mod_feedback.next_page": "Nächste Seite", "addon.mod_feedback.non_anonymous": "Nicht anonym", "addon.mod_feedback.non_anonymous_entries": "Nicht-anonyme Einträge ({{$a}})", @@ -567,11 +567,11 @@ "addon.mod_forum.addtofavourites": "Dieses Thema markieren", "addon.mod_forum.advanced": "Erweitert", "addon.mod_forum.cannotadddiscussion": "Nur Gruppenmitglieder dürfen Beiträge zum Forum hinzufügen.", - "addon.mod_forum.cannotadddiscussionall": "Sie dürfen kein neues Thema für alle Teilnehmer/innen hinzufügen.", - "addon.mod_forum.cannotcreatediscussion": "Das neue Thema wurde leider nicht gespeichert.", - "addon.mod_forum.couldnotadd": "Der Beitrag wurde wegen eines Fehlers nicht hinzugefügt.", - "addon.mod_forum.couldnotupdate": "Der Beitrag wurde wegen eines Fehlers nicht aktualisiert.", - "addon.mod_forum.cutoffdatereached": "Der Termin der letzten Abgabemöglichkeit für dieses Forum ist erreicht und Sie können nicht mehr hineinschreiben.", + "addon.mod_forum.cannotadddiscussionall": "Sie dürfen kein neues Thema für alle Teilnehmer/innen anlegen.", + "addon.mod_forum.cannotcreatediscussion": "Das neue Thema konnte nicht angelegt werden.", + "addon.mod_forum.couldnotadd": "Fehler. Der Beitrag konnte nicht hinzugefügt werden.", + "addon.mod_forum.couldnotupdate": "Fehler. Der Beitrag konnte nicht geändert werden.", + "addon.mod_forum.cutoffdatereached": "Der letzte Abgabetermin für dieses Forum ist erreicht. Sie können keinen Beitrag mehr schreiben.", "addon.mod_forum.delete": "Löschen", "addon.mod_forum.deletedpost": "Der Beitrag wurde gelöscht.", "addon.mod_forum.deletesure": "Möchten Sie diesen Beitrag wirklich löschen?", @@ -582,7 +582,7 @@ "addon.mod_forum.discussionlistsortbylastpostdesc": "Nach Erstelldatum des letzten Beitrags absteigend sortieren", "addon.mod_forum.discussionlistsortbyrepliesasc": "Nach Anzahl der Antworten aufsteigend sortieren", "addon.mod_forum.discussionlistsortbyrepliesdesc": "Nach Anzahl der Antworten absteigend sortieren", - "addon.mod_forum.discussionlocked": "Dieses Thema wurde gesperrt, so dass Sie hier nicht weiter antworten können.", + "addon.mod_forum.discussionlocked": "Dieses Thema wurde gesperrt. Sie können hier nicht weiter antworten.", "addon.mod_forum.discussionpinned": "Angepinnt", "addon.mod_forum.discussionsubscription": "Themenabonnement", "addon.mod_forum.edit": "Bearbeiten", @@ -605,7 +605,7 @@ "addon.mod_forum.numdiscussions": "{{numdiscussions}} Themen", "addon.mod_forum.numreplies": "{{numreplies}} Antworten", "addon.mod_forum.pindiscussion": "Dieses Thema anpinnen", - "addon.mod_forum.pinupdated": "Die Anpinnoption", + "addon.mod_forum.pinupdated": "Die Anpinnoption wurde aktualisiert.", "addon.mod_forum.postisprivatereply": "Der Beitrag wurde privat verfasst und ist nicht für alle Personen sichtbar.", "addon.mod_forum.posttoforum": "Beitrag absenden", "addon.mod_forum.posttomygroups": "Kopie an alle Gruppen senden", @@ -615,18 +615,18 @@ "addon.mod_forum.refreshposts": "Forenbeiträge aktualisieren", "addon.mod_forum.removefromfavourites": "Themenmarkierung entfernen", "addon.mod_forum.reply": "Antworten", - "addon.mod_forum.replyplaceholder": "Schreiben Sie Ihre Antwort…", + "addon.mod_forum.replyplaceholder": "Schreiben Sie Ihre Antwort …", "addon.mod_forum.subject": "Betreff", "addon.mod_forum.tagarea_forum_posts": "Forenbeiträge", - "addon.mod_forum.thisforumhasduedate": "Das Fälligkeitsdatum, um einen Beitrag in diesem Forum zu verfassen, ist {{$a}}.", - "addon.mod_forum.thisforumisdue": "Das Fälligkeitsdatum, um einen Beitrag in diesem Forum zu verfassen, war {{$a}}.", + "addon.mod_forum.thisforumhasduedate": "Das Fälligkeitsdatum, um diesem Forum zu schreiben, ist {{$a}}.", + "addon.mod_forum.thisforumisdue": "Das Fälligkeitsdatum, um diesem Forum zu schreiben, war {{$a}}.", "addon.mod_forum.unlockdiscussion": "Dieses Thema entsperren", - "addon.mod_forum.unpindiscussion": "Dieses Thema loslösen", + "addon.mod_forum.unpindiscussion": "Dieses Thema abpinnen", "addon.mod_forum.unread": "Ungelesen", "addon.mod_forum.unreadpostsnumber": "{{$a}} ungelesene Beiträge", "addon.mod_forum.yourreply": "Antwort", "addon.mod_glossary.addentry": "Eintrag hinzufügen", - "addon.mod_glossary.aliases": "Alternativbegriff(e)", + "addon.mod_glossary.aliases": "Alternativbegriffe", "addon.mod_glossary.attachment": "Anhang", "addon.mod_glossary.browsemode": "Einträge durchblättern", "addon.mod_glossary.byalphabet": "Alphabetisch", @@ -643,7 +643,7 @@ "addon.mod_glossary.entriestobesynced": "Einträge zum Synchronisieren", "addon.mod_glossary.entrypendingapproval": "Dieser Eintrag wartet auf eine Freigabe.", "addon.mod_glossary.entryusedynalink": "Eintrag automatisch verlinken", - "addon.mod_glossary.errconceptalreadyexists": "Diesen Begriff gibt es bereits. In diesem Glossar sind keine Doppeleinträge möglich.", + "addon.mod_glossary.errconceptalreadyexists": "Diesen Begriff gibt es bereits. In diesem Glossar sind keine Doppeleinträge erlaubt.", "addon.mod_glossary.errorloadingentries": "Fehler beim Laden von Einträgen", "addon.mod_glossary.errorloadingentry": "Fehler beim Laden des Eintrags", "addon.mod_glossary.errorloadingglossary": "Fehler beim Laden des Glossars", @@ -669,39 +669,39 @@ "addon.mod_lesson.cannotfinduser": "Fehler: Nutzer/innen konnten nicht gefunden werden", "addon.mod_lesson.clusterjump": "Ungesehene Frage innerhalb des Clusters", "addon.mod_lesson.completed": "Abgeschlossen", - "addon.mod_lesson.congratulations": "Ende der Lektion.", + "addon.mod_lesson.congratulations": "Das Ende der Lektion ist erreicht!", "addon.mod_lesson.continue": "Fortsetzen", "addon.mod_lesson.continuetonextpage": "Auf der nächsten Seite fortsetzen", "addon.mod_lesson.defaultessayresponse": "Die Freitext-Antwort wird später bewertet.", "addon.mod_lesson.detailedstats": "Ergebnisanalyse", "addon.mod_lesson.didnotanswerquestion": "Hat diese Frage nicht beantwortet.", "addon.mod_lesson.displayofgrade": "Anzeige der Bewertungen (für Teilnehmer/innen)", - "addon.mod_lesson.displayscorewithessays": "

Sie haben bisher die Bewertung {{$a.score}} von {{$a.tempmaxgrade}} für die automatisch bewerteten Fragen erzielt.

\n

Ihre Freitext-Aufgaben ({{$a.essayquestions}}) werden später bewertet und zur Gesamtbewertung hinzugefügt.

\n

Ihre derzeitige Bewertung ohne die Freitextaufgaben ist {{$a.score}} von {{$a.grade}}.

", + "addon.mod_lesson.displayscorewithessays": "

Sie haben bisher für die automatisch bewerteten Fragen die Bewertung {{$a.score}} von {{$a.tempmaxgrade}} erzielt.

\n

Ihre Freitext-Aufgaben ({{$a.essayquestions}}) werden später bewertet und zur Gesamtbewertung hinzugefügt.

\n

Ihre derzeitige Bewertung ohne die Freitextaufgaben ist {{$a.score}} von {{$a.grade}}.

", "addon.mod_lesson.displayscorewithoutessays": "Ihre Bewertung: {{$a.score}} von {{$a.grade}}", - "addon.mod_lesson.emptypassword": "Das Kennwort muss eingegeben werden", - "addon.mod_lesson.enterpassword": "Bitte geben Sie das Kennwort ein:", - "addon.mod_lesson.eolstudentoutoftimenoanswers": "Sie haben keine Fragen beantwortet. Sie erhalten daher 0 Punkte für die Lektion.", + "addon.mod_lesson.emptypassword": "Das Kennwort darf nicht leer sein.", + "addon.mod_lesson.enterpassword": "Geben Sie bitte das Kennwort ein:", + "addon.mod_lesson.eolstudentoutoftimenoanswers": "Sie haben keine Fragen beantwortet. Sie erhalten deswegen 0 Punkte für die Lektion.", "addon.mod_lesson.errorprefetchrandombranch": "Diese Lektion enthält einen Sprung zu einer zufälligen Seite. Die Lektion kann in der App nicht versucht werden, ohne im Webbrowser begonnen worden zu sein.", "addon.mod_lesson.errorreviewretakenotlast": "Dieser Versuch kann nicht mehr angesehen werden, weil ein weiterer Versuch beendet wurde.", "addon.mod_lesson.finish": "Fertigstellen", "addon.mod_lesson.finishretakeoffline": "Dieser Versuch wurde offline beendet.", - "addon.mod_lesson.firstwrong": "Ihre Antwort ist falsch. Wollen Sie noch einmal (ohne Bewertung) wiederholen?", + "addon.mod_lesson.firstwrong": "Ihre Antwort ist falsch. Möchten Sie es noch einmal (ohne Bewertung) versuchen?", "addon.mod_lesson.gotoendoflesson": "Zum Lektionende gehen", "addon.mod_lesson.grade": "Relative Bewertung", "addon.mod_lesson.highscore": "Beste Bewertung", "addon.mod_lesson.hightime": "Beste Zeit", "addon.mod_lesson.leftduringtimed": "Sie haben die Lektion abgebrochen. Die Bearbeitung der Lektion ist zeitlich begrenzt.
Klicken Sie auf Fortsetzen, um die Lektion erneut zu beginnen.", - "addon.mod_lesson.leftduringtimednoretake": "Sie haben die Lektion abgebrochen. Die Bearbeitung der Lektion ist zeitlich begrenzt.
Sie können diese Lektion nicht mehr bearbeiten.", + "addon.mod_lesson.leftduringtimednoretake": "Sie haben die Lektion abgebrochen. Die Bearbeitung der Lektion ist zeitlich begrenzt.
Sie können diese Lektion nicht weiter bearbeiten.", "addon.mod_lesson.lessonmenu": "Inhaltsverzeichnis", "addon.mod_lesson.lessonstats": "Statistik", "addon.mod_lesson.linkedmedia": "Verlinkte Medien", - "addon.mod_lesson.loginfail": "Der Login ist gescheitert. Bitte versuchen Sie es noch einmal.", + "addon.mod_lesson.loginfail": "Der Login ist gescheitert. Versuchen Sie es bitte noch einmal.", "addon.mod_lesson.lowscore": "Schlechteste Bewertung", "addon.mod_lesson.lowtime": "Schlechteste Zeit", "addon.mod_lesson.maximumnumberofattemptsreached": "Sie haben die Höchstzahl der Versuche erreicht - weiter zur nächsten Seite", "addon.mod_lesson.modattemptsnoteacher": "Navigation nur für Teilnehmer/innen möglich", "addon.mod_lesson.modulenameplural": "Lektionen", - "addon.mod_lesson.noanswer": "Eine oder mehrere Fragen wurden nicht beantwortet. Gehen Sie zurück und geben Sie die Antworten ein.", + "addon.mod_lesson.noanswer": "Eine oder mehrere Fragen wurden nicht beantwortet. Gehen Sie zurück und geben Sie Antworten ein.", "addon.mod_lesson.nolessonattempts": "Bisher keine Versuche", "addon.mod_lesson.nolessonattemptsgroup": "Keines der {{$a}} Gruppenmitglieder hat zu dieser Lektion einen Versuch gemacht.", "addon.mod_lesson.notcompleted": "Nicht beendet", @@ -713,20 +713,20 @@ "addon.mod_lesson.or": "ODER", "addon.mod_lesson.overview": "Übersicht", "addon.mod_lesson.preview": "Vorschau", - "addon.mod_lesson.progressbarteacherwarning2": "Die Fortschrittsanzeige wird Ihnen nicht angezeigt, da Sie diese Lektion als Trainer/in bearbeiten können.", - "addon.mod_lesson.progresscompleted": "Sie haben {{$a}}% der Lektion erledigt", + "addon.mod_lesson.progressbarteacherwarning2": "Die Fortschrittsanzeige wird Ihnen nicht angezeigt, weil Sie diese Lektion als Trainer/in bearbeiten können.", + "addon.mod_lesson.progresscompleted": "Sie haben {{$a}}% der Lektion erledigt.", "addon.mod_lesson.question": "Frage", "addon.mod_lesson.rawgrade": "Absolute Bewertung", "addon.mod_lesson.reports": "Ergebnisse", "addon.mod_lesson.response": "Feedback", - "addon.mod_lesson.retakefinishedinsync": "Ein Offline-Versuch wurde synchronisiert. Möchten Sie ihn überprüfen?", + "addon.mod_lesson.retakefinishedinsync": "Ein Offline-Versuch wurde synchronisiert. Möchten Sie ihn erneut prüfen?", "addon.mod_lesson.retakelabelfull": "{{retake}}: {{grade}} {{timestart}} ({{duration}})", "addon.mod_lesson.retakelabelshort": "{{retake}}: {{grade}} {{timestart}}", "addon.mod_lesson.review": "Rückschau", "addon.mod_lesson.reviewlesson": "Zur Lektion", "addon.mod_lesson.reviewquestionback": "Wiederholen", "addon.mod_lesson.reviewquestioncontinue": "Fortsetzen", - "addon.mod_lesson.secondpluswrong": "Nicht ganz. Wollen Sie noch einmal wiederholen?", + "addon.mod_lesson.secondpluswrong": "Nicht ganz. Möchten Sie es noch einmal versuchen?", "addon.mod_lesson.submit": "Einreichen", "addon.mod_lesson.teacherjumpwarning": "In der Lektion werden '{{$a.cluster}}'-Sprünge und/oder '{{$a.unseen}}'-Sprünge verwendet. Diese Sprünge werden durch 'Nächste Seite'-Sprünge ersetzt. Melden Sie sich als Teilnehmer/in an, um die Sprünge zu testen.", "addon.mod_lesson.teacherongoingwarning": "Die aktuelle Bewertung wird nur für Teilnehmer/innen angezeigt. Melden Sie sich als Teilnehmer/in an, um diese Funktion zu testen.", @@ -738,7 +738,7 @@ "addon.mod_lesson.unseenpageinbranch": "Noch nicht angezeigte Frage innerhalb des Zweiges", "addon.mod_lesson.warningretakefinished": "Der Versuch wurde auf der Website beendet.", "addon.mod_lesson.welldone": "Glückwunsch!", - "addon.mod_lesson.youhaveseen": "Sie haben einige Seiten der Lektion schon einmal bearbeitet.
Wollen Sie an der Stelle fortsetzen, an der Sie damals aufgehört haben?", + "addon.mod_lesson.youhaveseen": "Sie haben einige Seiten der Lektion schon einmal bearbeitet.
Möchten Sie an der Stelle weitermachen, an der Sie damals aufgehört haben?", "addon.mod_lesson.youranswer": "Ihre Antwort", "addon.mod_lesson.yourcurrentgradeisoutof": "Ihre derzeitige Bewertung ist {{$a.grade}} von {{$a.total}}", "addon.mod_lesson.youshouldview": "mindestens zu beantworten: {{$a}}", @@ -850,7 +850,7 @@ "addon.mod_scorm.errordownloadscorm": "Fehler beim Laden des Lernpakets '{{name}}'.", "addon.mod_scorm.errorgetscorm": "Fehler beim Laden von Lernpaketdaten", "addon.mod_scorm.errorinvalidversion": "Die App unterstützt ausschließlich Lernpakete mit SCORM 1.2.", - "addon.mod_scorm.errornotdownloadable": "Das Herunterladen von Lernpaketen ist für diese Website deaktiviert. Wenden Sie sich an den Administrator.", + "addon.mod_scorm.errornotdownloadable": "Das Herunterladen von Lernpaketen ist für diese Website deaktiviert. Fragen Sie den Administrator.", "addon.mod_scorm.errornovalidsco": "Dieses Lernpaket hat kein sichtbares SCO zum Laden.", "addon.mod_scorm.errorpackagefile": "Die App unterstützt ausschließlich ZIP-Pakete.", "addon.mod_scorm.errorsyncscorm": "Fehler beim Synchronisieren. Versuchen Sie es noch einmal.", @@ -1010,7 +1010,7 @@ "addon.notifications.therearentnotificationsyet": "Keine Systemnachrichten", "addon.storagemanager.deletecourse": "Alle Kursdaten entladen", "addon.storagemanager.deletedatafrom": "Daten von {{name}} entladen", - "addon.storagemanager.info": "Auf Ihrem Gerät gespeicherte Daten beschleunigen die Arbeit und ermöglichen die Offline-Verwendung der App. Sie können Daten sicher entladen, wenn Sie Speicherplatz freigeben müssen.", + "addon.storagemanager.info": "Auf Ihrem Gerät gespeicherte Daten beschleunigen die Arbeit und ermöglichen die Offline-Verwendung der App. Sie können Daten entladen, wenn Sie Speicherplatz freigeben müssen.", "addon.storagemanager.managestorage": "Speicher verwalten", "addon.storagemanager.storageused": "Verwendeter Dateispeicher:", "assets.countries.AD": "Andorra", @@ -1133,12 +1133,12 @@ "assets.countries.KI": "Kiribati", "assets.countries.KM": "Komoren", "assets.countries.KN": "Saint Kitts und Nevis", - "assets.countries.KP": "Korea (Demokratische Volksrepublik Nordkorea)", - "assets.countries.KR": "Korea (Republik Südkorea)", + "assets.countries.KP": "Korea (Demokratische Volksrepublik)", + "assets.countries.KR": "Korea (Republik)", "assets.countries.KW": "Kuwait", "assets.countries.KY": "Kaiman Inseln", "assets.countries.KZ": "Kasachstan", - "assets.countries.LA": "Demokratische Volksrepublik Laos", + "assets.countries.LA": "Laos (Demokratische Volksrepublik)", "assets.countries.LB": "Libanon", "assets.countries.LC": "Santa Lucia", "assets.countries.LI": "Liechtenstein", @@ -1336,9 +1336,9 @@ "core.capturevideo": "Video aufnehmen", "core.category": "Kursbereich", "core.choose": "Auswahl", - "core.choosedots": "Auswählen...", + "core.choosedots": "Auswählen ...", "core.clearsearch": "Suche löschen", - "core.clicktohideshow": "Zum Erweitern oder Einklappen klicken", + "core.clicktohideshow": "Zum Erweitern oder Einklappen tippen", "core.clicktoseefull": "Tippen zum Anzeigen aller Inhalte", "core.close": "Schließen", "core.comments": "Kommentare", @@ -1353,7 +1353,6 @@ "core.comments.savecomment": "Kommentar speichern", "core.comments.warningcommentsnotsent": "Kommentare konnten nicht synchronisiert werden. {{error}}", "core.commentscount": "Kommentare ({{$a}})", - "core.commentsnotworking": "Kommentare können nicht abgerufen werden.", "core.completion-alt-auto-fail": "Abgeschlossen: {{$a}} (Bestehensgrenze nicht erreicht)", "core.completion-alt-auto-n": "Nicht abgeschlossen: {{$a}}", "core.completion-alt-auto-n-override": "Nicht abgeschlossen: {{$a.modname}} (gesetzt von {{$a.overrideuser}})", @@ -1403,7 +1402,7 @@ "core.course.errorgetmodule": "Fehler beim Laden von Aktivitätsdaten", "core.course.hiddenfromstudents": "Für Teilnehmer/innen verborgen", "core.course.hiddenoncoursepage": "Verfügbar, aber auf der Kursseite verborgen", - "core.course.insufficientavailablequota": "Ihr Gerät konnte keinen Speicherplatz für diesen Download zuweisen. Möglicherweise ist Speicherplatz für anstehende App- und Systemupdates reserviert. Sorgen Sie zuerst für ausreichend freien Speicher.", + "core.course.insufficientavailablequota": "Ihr Gerät konnte für diesen Download keinen Speicherplatz zuweisen. Möglicherweise ist Speicherplatz für anstehende App- und Systemupdates reserviert. Sorgen Sie zuerst für ausreichend freien Speicher.", "core.course.insufficientavailablespace": "Sie versuchen, {{size}} herunterzuladen. Ihr Gerät hat danach möglicherweise nicht genügend Platz, um normal zu funktionieren. Sorgen Sie zuerst für ausreichend freien Speicher.", "core.course.manualcompletionnotsynced": "Manueller Abschluss wurde nicht synchronisiert", "core.course.nocontentavailable": "Momentan sind keine Inhalte verfügbar", @@ -1461,7 +1460,7 @@ "core.defaultvalue": "Standard ({{$a}})", "core.delete": "Löschen", "core.deletedoffline": "Offline gelöscht", - "core.deleteduser": "Gelöschter Nutzer", + "core.deleteduser": "Nutzer/in gelöscht", "core.deleting": "Löschen ...", "core.description": "Beschreibung", "core.dfdaymonthyear": "DD.MM.YYYY", @@ -1487,7 +1486,7 @@ "core.errordownloadingsomefiles": "Fehler beim Laden der Dateien. Einige Dateien könnten fehlen.", "core.errorfileexistssamename": "Eine Datei mit gleichem Namen existiert bereits.", "core.errorinvalidform": "Das Formular enthält ungültige Daten. Füllen Sie alle notwendigen Felder aus und prüfen Sie, dass alle Daten richtig sind.", - "core.errorinvalidresponse": "Ungültige Antwort empfangen. Wenden Sie sich an den Administrator, wenn der Fehler weiter auftritt.", + "core.errorinvalidresponse": "Ungültige Antwort empfangen. Wenden Sie sich an den Administrator, wenn der Fehler wieder auftritt.", "core.errorloadingcontent": "Fehler beim Laden des Inhalts", "core.errorofflinedisabled": "Der Offline-Zugriff auf diese Website ist deaktiviert. Sie müssen online sein, um die App zu verwenden.", "core.erroropenfilenoapp": "Fehler: Keine App zum Öffnen dieses Dateityps gefunden.", @@ -1536,7 +1535,7 @@ "core.fullnameandsitename": "{{fullname}} ({{sitename}})", "core.grades.average": "Durchschnitt", "core.grades.badgrade": "Ungültige Bewertung", - "core.grades.contributiontocoursetotal": "Beiträge zum Kurs insgesamt", + "core.grades.contributiontocoursetotal": "Beiträge zum Kurs gesamt", "core.grades.feedback": "Feedback", "core.grades.grade": "Bewertung", "core.grades.gradeitem": "Bewertungsaspekt", @@ -1554,7 +1553,7 @@ "core.h5p.additionallicenseinfo": "Zusätzliche Infos zur Lizenz", "core.h5p.author": "Autor/in", "core.h5p.authorcomments": "Kommentare", - "core.h5p.authorcommentsdescription": "Kommentare an den Herausgeber des Inhalts. Dieser Text wird nicht als Teil der Copyright-Informationen veröffentlicht.", + "core.h5p.authorcommentsdescription": "Kommentare an den Herausgeber des Inhalts. Dieser Text wird nicht als Teil der Copyright-Infos veröffentlicht.", "core.h5p.authorname": "Autorenname", "core.h5p.authorrole": "Autorenrolle", "core.h5p.by": "von", @@ -1593,7 +1592,7 @@ "core.h5p.embedtitle": "Einbettcode für diesen Inhalt anzeigen", "core.h5p.fullscreen": "Vollbild", "core.h5p.gpl": "General Public License v3", - "core.h5p.h5ptitle": "Besuchen Sie h5p.org, um weitere Informationen zu erhalten.", + "core.h5p.h5ptitle": "Besuchen Sie h5p.org, um weitere Infos zu erhalten.", "core.h5p.hideadvanced": "Erweitert ausblenden", "core.h5p.license": "Lizenz", "core.h5p.licenseCC010": "CC0 1.0 Universal (CC0 1.0) Public Domain", @@ -1611,17 +1610,17 @@ "core.h5p.licenseextras": "Lizenzextras", "core.h5p.licenseversion": "Lizenzversion", "core.h5p.nocopyright": "Für diesen Inhalt sind keine Copyright-Infos verfügbar.", - "core.h5p.offlineDialogBody": "Infos zum Abschluss dieser Aufgabe konnten nicht gesendet werden. Überprüfe Sie die Internetverbindung.", + "core.h5p.offlineDialogBody": "Infos zum Abschluss dieser Aufgabe konnten nicht gesendet werden. Überprüfen Sie die Internetverbindung.", "core.h5p.offlineDialogHeader": "Ihre Verbindung zum Server wurde unterbrochen.", "core.h5p.offlineDialogRetryButtonLabel": "Sofort wiederholen", - "core.h5p.offlineDialogRetryMessage": "Wiederholung in :num ....", + "core.h5p.offlineDialogRetryMessage": "Wiederholung in :num ...", "core.h5p.offlineSuccessfulSubmit": "Ergebnisse erfolgreich übertragen.", "core.h5p.originator": "Urheber/in", "core.h5p.pd": "Public Domain", "core.h5p.pddl": "Public Domain Widmung und Lizenz", "core.h5p.pdm": "Public Domain Mark (PDM)", "core.h5p.play": "H5P abspielen", - "core.h5p.resizescript": "Fügen Sie dieses Skript in Ihre Website ein, wenn Sie eine dynamische Größenanpassung des eingebetteten Inhalts wünschen:", + "core.h5p.resizescript": "Fügen Sie dieses Skript in die Website ein, wenn Sie eine dynamische Größenanpassung des eingebetteten Inhalts möchtest:", "core.h5p.resubmitScores": "Versuch, gespeicherte Ergebnisse zu übermitteln.", "core.h5p.reuse": "Wiederverwenden", "core.h5p.reuseContent": "Inhalt wiederverwenden", @@ -1648,7 +1647,7 @@ "core.humanreadablesize": "{{size}} {{unit}}", "core.image": "Bild", "core.imageviewer": "Bildanzeige", - "core.info": "Informationen", + "core.info": "Infos", "core.invalidformdata": "Falsche Formulardaten!", "core.ios": "iOS", "core.labelsep": ": ", @@ -1668,10 +1667,9 @@ "core.login.changepassword": "Kennwort ändern", "core.login.changepasswordbutton": "Seite zur Kennwortänderung aufrufen", "core.login.changepasswordhelp": "Wenn Sie Probleme beim Ändern Ihres Kennworts haben, wenden Sie sich an den Administrator Ihrer Website. Administrator/innen sind die Personen, die das Moodle an Ihrer Schule, Universität, Firma oder Organisation verwalten. Wenn Sie nicht wissen, wie Sie mit ihnen Kontakt aufnehmen können, wenden Sie sich an Ihre Trainer/innen.", - "core.login.changepasswordinstructions": "Sie können Ihr Kennwort nicht in der App ändern. Klicken Sie auf die folgende Taste, um die Website in einem Webbrowser zu öffnen und Ihr Kennwort zu ändern. Beachten Sie, dass Sie den Browser nach dem Ändern des Kennworts schließen müssen, da Sie nicht zur App zurückgeleitet werden.", + "core.login.changepasswordinstructions": "Sie können Ihr Kennwort nicht in der App ändern. Klicken Sie auf die folgende Taste, um Moodle in einem Webbrowser zu öffnen und Ihr Kennwort zu ändern. Beachten Sie, dass Sie den Browser nach dem Ändern des Kennworts schließen müssen, um wieder in die App zurückzukommen.", "core.login.changepasswordlogoutinstructions": "Wenn Sie die Website wechseln oder sich abmelden möchten, klicken Sie auf die folgende Taste:", "core.login.changepasswordreconnectinstructions": "Klicken Sie auf die folgende Taste, um die Verbindung zur Website wiederherzustellen. Beachten Sie, dass Sie zum vorherigen Bildschirm zurückkehren, wenn Sie Ihr Kennwort nicht erfolgreich geändert haben.", - "core.login.checksiteversion": "Prüfen Sie, ob die Website mindestens Moodle 2.4 verwendet.", "core.login.confirmdeletesite": "Möchten Sie '{{sitename}}' wirklich aus der Liste löschen?", "core.login.connect": "Verbinden", "core.login.connecttomoodle": "Zu Moodle verbinden", @@ -1682,11 +1680,10 @@ "core.login.createaccount": "Neues Nutzerkonto anlegen", "core.login.createuserandpass": "Anmeldedaten wählen", "core.login.credentialsdescription": "Geben Sie den Anmeldenamen und das Kennwort ein. ", - "core.login.emailconfirmsent": "

Um sicherzugehen, dass sich niemand unberechtigt über die von Ihnen angegebene E-Mail anmeldet, wird eine automatische Benachrichtigung an diese Adresse {{$a}} gesendet. Je nach Netzlast trifft sie sofort oder auch etwas später bei Ihnen ein.

\n

Die Benachrichtigung enthält eine Anleitung, wie Sie Ihre Registrierung bestätigen.\nDanach sind Sie auf dieser Moodle-Seite registriert und können sofort loslegen.

\n

Bei Problemen wenden Sie sich bitte an die Administrator/innen der Website.

", + "core.login.emailconfirmsent": "

Um sicherzugehen, dass sich niemand unberechtigt über die von Ihnen angegebene E-Mail anmeldet, wird eine automatische Benachrichtigung an diese Adresse {{$a}} gesendet.

\n

Die Benachrichtigung enthält eine Anleitung, wie Sie Ihre Registrierung bestätigen. Danach sind Sie auf dieser Moodle-Seite registriert und können sofort loslegen.

\n

Bei Problemen wenden Sie sich bitte an die Administrator/innen der Website.

", "core.login.emailconfirmsentnoemail": "

An Ihre E-Mail-Adresse wurde eine Mitteilung gesendet.

Die Mitteilung enthält eine einfache Anleitung, um Ihre Registrierung abzuschließen. Falls Sie Probleme haben, wenden Sie sich an den Administrator der Website.

", "core.login.emailconfirmsentsuccess": "Die Bestätigungsmitteilung wurde erfolgreich versendet.", "core.login.emailnotmatch": "Die E-Mail-Adressen stimmen nicht überein.", - "core.login.enterthewordsabove": "Geben Sie die gezeigten Wörter ein", "core.login.erroraccesscontrolalloworigin": "Der Cross-Origin-Aufruf wurde zurückgewiesen. Weitere Infos: https://docs.moodle.org/dev/Moodle_Mobile_development_using_Chrome_or_Chromium", "core.login.errordeletesite": "Fehler beim Entfernen der Website aus der Liste. Versuchen Sie es noch einmal.", "core.login.errorupdatesite": "Fehler bei der Authentifizierung", @@ -1694,7 +1691,6 @@ "core.login.firsttime": "Sind Sie zum ersten Mal auf dieser Webseite?", "core.login.forcepasswordchangenotice": "Ändern Sie Ihr Kennwort, bevor Sie weiterarbeiten.", "core.login.forgotten": "Anmeldename oder Kennwort vergessen?", - "core.login.getanothercaptcha": "Neues Captcha laden", "core.login.help": "Hilfe", "core.login.helpmelogin": "

Auf der Welt gibt es viele tausend Websites mit Moodle. Diese App kann aber nur mit Websites verbunden werden, die für den Zugriff mit der mobilen Moodle App freigegeben wurden.

Falls Sie sich nicht mit Ihrer Website verbinden können, wenden Sie sich an den Administrator der Website und bitten ihn, die Anleitung zu lesen. https://docs.moodle.org/de/Moodle_App

Um die App mit einer Demoseite auszuprobieren, schreiben Sie teacher oder student in das Feld Website-Adresse und tippen dann auf Verbinden.

", "core.login.instructions": "Anleitung", @@ -1707,9 +1703,6 @@ "core.login.invalidurl": "Ungültige URL angegeben", "core.login.invalidvaluemax": "Der Maximalwert ist {{$a}}.", "core.login.invalidvaluemin": "Der Minimalwert ist {{$a}}.", - "core.login.legacymoodleversion": "Sie versuchen, sich mit einer nicht unterstützen Moodle-Version zu verbinden. Verwenden Sie die Moodle-Classic-App, um auf diese Website zu gelangen.", - "core.login.legacymoodleversiondesktop": "Sie versuchen, sich mit {{$a}} zu verbinden.

Diese Website läuft mit einer alten, nicht unterstützen Moodle-Version, die nicht mit der Moodle-Desktop-App funktioniert..

Falls diese Website Ihnen gehört und Sie Probleme mit der Aktualisierung haben, wenden Sie sich bitte an einen lokalen Moodle-Partner. Ihre Anfrage können Sie über unsere Kontaktseite abgeben.", - "core.login.legacymoodleversiondesktopdownloadold": "

Alternativ können Sie auf diese Website zugreifen, wenn Sie eine ältere Version der App herunterladen und verwenden.", "core.login.localmobileunexpectedresponse": "Die Verbindung zum Plugin 'Moodle Mobile - Zusatzfeatures' ist fehlgeschlagen. Sie werden über den standardmäßigen mobilen Webservice authentifiziert.", "core.login.loggedoutssodescription": "Sie müssen sich neu authentifizieren. Melden Sie sich im Browser auf der Website an.", "core.login.login": "Login", @@ -1729,15 +1722,15 @@ "core.login.passwordforgotteninstructions2": "Um Ihr Kennwort zurückzusetzen, tragen Sie bitte entweder Ihren Anmeldenamen oder Ihre E-Mail-Adresse ein. Wenn Sie in der Datenbank zu finden sind, wird eine Mitteilung an Ihre E-Mail-Adresse verschickt. Diese Mitteilung enthält eine Anleitung für die weiteren Schritte.", "core.login.passwordrequired": "Kennwort fehlt", "core.login.policyaccept": "Ich habe den Text gelesen und stimme ihm zu", - "core.login.policyagree": "Lesen Sie diese Datenschutzinformation sorgfältig. Sie müssen zustimmen, um die Website nutzen zu können. Stimmen Sie zu?", - "core.login.policyagreement": "Datenschutzinformation", - "core.login.policyagreementclick": "URL zur Datenschutzinformation", + "core.login.policyagree": "Lesen Sie diese Datenschutzinfos sorgfältig. Sie müssen zustimmen, um die Website nutzen zu können. Stimmen Sie zu?", + "core.login.policyagreement": "Datenschutzinfos", + "core.login.policyagreementclick": "URL zu den Datenschutzinfos", "core.login.potentialidps": "Verwenden Sie Ihr Nutzerkonto bei:", "core.login.problemconnectingerror": "Probleme beim Verbinden mit", "core.login.problemconnectingerrorcontinue": "Prüfen Sie, ob die URL richtig ist. Versuchen Sie es noch einmal.", "core.login.profileinvaliddata": "Ungültiger Wert", "core.login.recaptchachallengeimage": "reCaptcha Challenge-Bild", - "core.login.recaptchaexpired": "Überprüfung notwendig. Beantworten Sie die Sicherheitsfrage noch einmal.", + "core.login.recaptchaexpired": "Überprüfung abgelaufen. Beantworten Sie die Sicherheitsfrage noch einmal.", "core.login.recaptchaincorrect": "Die Antwort auf die Sicherheitsfrage ist falsch.", "core.login.reconnect": "Neu verbinden", "core.login.reconnectdescription": "Die Authentifizierung ist abgelaufen oder ungültig. Sie müssen sich neu anmelden.", @@ -1760,7 +1753,7 @@ "core.login.username": "Anmeldename", "core.login.usernameoremail": "Geben Sie den Anmeldenamen oder die E-Mail-Adresse ein.", "core.login.usernamerequired": "Anmeldename fehlt", - "core.login.usernotaddederror": "Nutzer wurde nicht hinzugefügt - Fehler", + "core.login.usernotaddederror": "Fehler. Nutzer/in wurde nicht hinzugefügt", "core.login.visitchangepassword": "Möchten Sie die Website aufrufen, um das Kennwort zu ändern?", "core.login.webservicesnotenabled": "Die Webservices sind für diese Website deaktiviert. Wenden Sie sich an den Administrator, wenn Sie die mobile App aktiviert haben möchten.", "core.lostconnection": "Die Authentifizierung ist abgelaufen oder ungültig. Sie müssen sich neu anmelden.", @@ -1818,7 +1811,7 @@ "core.noresults": "Keine Ergebnisse", "core.noselection": "Keine Auswahl", "core.notapplicable": "n/a", - "core.notenrolledprofile": "Dieses Profil ist nicht verfügbar, weil die Person den Kurs bisher nicht betreten hat.", + "core.notenrolledprofile": "Das Profil ist nicht verfügbar, weil diese Person den Kurs bisher nicht betreten hat.", "core.notice": "Hinweis", "core.notingroup": "Die Aktivität ist nur für Gruppenmitglieder zugänglich.", "core.notsent": "Nicht gesendet", @@ -1833,10 +1826,10 @@ "core.othergroups": "Weitere Gruppen", "core.pagea": "Seite {{$a}}", "core.parentlanguage": "", - "core.paymentinstant": "Klicken Sie auf den Button, um das Teilnahmeentgelt zu bezahlen. Sobald der Zahlvorgang abgeschlossen ist, werden Sie automatisch in den Kurs eingeschrieben.", + "core.paymentinstant": "Klicken Sie auf die Taste, um das Teilnahmeentgelt zu bezahlen. Sobald der Zahlvorgang abgeschlossen ist, werden Sie automatisch in den Kurs eingeschrieben.", "core.percentagenumber": "{{$a}}%", "core.phone": "Telefon", - "core.pictureof": "Nutzerbild {{$a}}", + "core.pictureof": "Nutzerbild von {{$a}}", "core.previous": "Zurück", "core.proceed": "Weitermachen", "core.pulltorefresh": "Zum Aktualisieren runterziehen", @@ -1967,8 +1960,8 @@ "core.sharedfiles.sharedfiles": "Geteilte Dateien", "core.sharedfiles.successstorefile": "Die Datei wurde erfolgreich gespeichert. Sie können die Datei in 'Meine Dateien' hochladen oder in einer Aktivität verwenden.", "core.show": "Anzeigen", - "core.showless": "Weniger anzeigen...", - "core.showmore": "Mehr anzeigen...", + "core.showless": "Weniger anzeigen ...", + "core.showmore": "Mehr anzeigen ...", "core.site": "Website", "core.sitehome.sitehome": "Startseite", "core.sitehome.sitenews": "Ankündigungen", @@ -2000,8 +1993,18 @@ "core.submit": "Speichern", "core.success": "erfolgreich", "core.tablet": "Tablet", + "core.tag.defautltagcoll": "Standard-Collection", "core.tag.errorareanotsupported": "Dieser Tag-Bereich wird von der App nicht unterstützt.", + "core.tag.inalltagcoll": "Überall", + "core.tag.itemstaggedwith": "{{$a.tagarea}} mit '{{$a.tag}}' markiert", + "core.tag.notagsfound": "Kein Tag passend zu '{{$a}}' gefunden", + "core.tag.searchtags": "Tags suchen", + "core.tag.showingfirsttags": "Beliebteste Tags anzeigen: {{$a}}", "core.tag.tag": "Tag", + "core.tag.tagarea_course": "Kurse", + "core.tag.tagarea_course_modules": "Aktivitäten und Material", + "core.tag.tagarea_post": "Blogeinträge", + "core.tag.tagarea_user": "Nutzerinteressen", "core.tag.tags": "Tags", "core.tag.warningareasnotsupported": "Einige der Tag-Bereiche werden nicht angezeigt, weil sie von der App nicht unterstützt werden.", "core.teachers": "Trainer/innen", @@ -2021,7 +2024,7 @@ "core.unzipping": "Entpacken ...", "core.updaterequired": "Aktualisierung der App notwendig", "core.updaterequireddesc": "Aktualisieren Sie Ihre App auf die Version {{$a}}", - "core.upgraderunning": "Diese Website wird gerade aktualisiert. Bitte versuchen Sie es später nochmal.", + "core.upgraderunning": "Diese Website wird gerade aktualisiert. Versuchen Sie es später nochmal.", "core.user": "Nutzer", "core.user.address": "Adresse", "core.user.city": "Stadt", diff --git a/src/assets/lang/el.json b/src/assets/lang/el.json index 0605f8dfe..008058665 100644 --- a/src/assets/lang/el.json +++ b/src/assets/lang/el.json @@ -1314,7 +1314,6 @@ "core.comments.nocomments": "Χωρίς σχόλια", "core.comments.savecomment": "Αποθήκευση σχολίου", "core.commentscount": "Σχόλια ({{$a}})", - "core.commentsnotworking": "Τα σχόλια δεν μπορούν να ανακτηθούν", "core.completion-alt-auto-fail": "Ολοκληρωμένο: {{$a}} (με βαθμό κάτω της βάσης)", "core.completion-alt-auto-n": "Μη ολοκληρωμένο: {{$a}}", "core.completion-alt-auto-n-override": "Δεν ολοκληρώθηκε: {{$a.modname}} (καθορίστηκε από τον/την {{$a.overrideuser}})", @@ -1520,7 +1519,6 @@ "core.login.authenticating": "Έλεγχος ταυτότητας", "core.login.cancel": "Άκυρο", "core.login.changepassword": "Αλλαγή του κωδικού πρόσβασης", - "core.login.checksiteversion": "Ελέγξτε ποια έκδοση Moodle χρησιμοποιεί το site, Moodle 2.4 ή μετέπειτα έκδοση.", "core.login.confirmdeletesite": "Είστε σίγουροι ότι θέλετε να διαγράψετε το site {{sitename}};", "core.login.connect": "Σύνδεση!", "core.login.connecttomoodle": "Σύνδεση στο Moodle", @@ -1532,14 +1530,12 @@ "core.login.emailconfirmsent": "

Ένα μήνυμα ηλεκτρονικού ταχυδρομείου θα πρέπει να έχει σταλεί στη διεύθυνσή σας, {{$a}}

\n

Περιέχει απλές οδηγίες για την ολοκλήρωση της εγγραφής σας.

\n

Αν συνεχίζετε να αντιμετωπίζετε δυσκολίες, επικοινωνήστε με το διαχειριστή του δικτυακού τόπου.

", "core.login.emailconfirmsentsuccess": "Η αποστολή μηνύματος επιβεβαίωσης με ηλεκτρονικό ταχυδρομείο έγινε με επιτυχία.", "core.login.emailnotmatch": "Οι διευθύνσεις email δεν ταιριάζουν", - "core.login.enterthewordsabove": "Εισάγετε τις παραπάνω λέξεις", "core.login.erroraccesscontrolalloworigin": "Η κλήση πολλαπλών προελεύσεων (Cross-Origin call) που προσπαθείτε να εκτελέσετε έχει απορριφθεί. Παρακαλώ ελέγξτε https://docs.moodle.org/dev/Moodle_Mobile_development_using_Chrome_or_Chromium", "core.login.errordeletesite": "Παρουσιάστηκε σφάλμα κατά τη διάρκεια της διαγραφής του site. Δοκιμάστε ξανά αργότερα.", "core.login.errorupdatesite": "Παρουσιάστηκε σφάλμα κατά την ενημέρωση του site token.", "core.login.firsttime": "Είναι η πρώτη σας φορά εδώ;", "core.login.forcepasswordchangenotice": "Πρέπει να αλλάξετε τον κωδικό πρόσβασης για να συνεχίσετε.", "core.login.forgotten": "Ξεχάσατε το όνομα χρήστη ή τον κωδικό πρόσβασης;", - "core.login.getanothercaptcha": "Φέρε μια άλλη εικόνα CAPTCHA", "core.login.help": "Βοήθεια", "core.login.helpmelogin": "

Υπάρχουν χιλιάδες Moodle ιστότοποι σε όλο τον κόσμο. Αυτή η εφαρμογή μπορεί να συνδεθεί μόνο σε ιστότοπους Moodle που έχουν ενεργοποιήσει συγκεκριμένα την πρόσβαση σε εφαρμογές για κινητά.

Εάν δεν μπορείτε να συνδεθείτε στον ιστότοπό σας Moodle, πρέπει να επικοινωνήσετε με το διαχειριστή του Moodle ιστότοπου όπου θέλετε να συνδεθείτε και ζητήστε τους να διαβάσουν http://docs.moodle.org/en/Mobile_app

Για να δοκιμάσετε την εφαρμογή σε έναν δοκιμαστικό ιστότοπο Moodle δάσκαλος ή σπουδαστής στο πεδίο Διεύθυνση ιστοτόπου και κάντε κλικ στο κουμπί < .

", "core.login.instructions": "Οδηγίες", @@ -1817,7 +1813,17 @@ "core.submit": "Υποβολή", "core.success": "Επιτυχία", "core.tablet": "Tablet", + "core.tag.defautltagcoll": "Προεπιλεγμένη συλλογή", + "core.tag.inalltagcoll": "Παντού", + "core.tag.itemstaggedwith": "Στην περιοχή «{{$a.tagarea}}» τοποθετήθηκε ετικέτα «{{$a.tag}}»", + "core.tag.notagsfound": "Δεν βρέθηκαν ετικέτες που να ταιριάζουν με το «{{$a}}»", + "core.tag.searchtags": "Αναζήτηση ετικετών", + "core.tag.showingfirsttags": "Προβάλλονται οι {{$a}} περισσότερο δημοφιλείς ετικέτες", "core.tag.tag": "Ετικέτα", + "core.tag.tagarea_course": "Μαθήματα", + "core.tag.tagarea_course_modules": "Δραστηριότητες και πόροι", + "core.tag.tagarea_post": "Αναρτήσεις ιστολογίου", + "core.tag.tagarea_user": "Ενδιαφέροντα χρήστη", "core.tag.tags": "Ετικέτες", "core.teachers": "Διδάσκοντες", "core.thereisdatatosync": "Υπάρχουν εκτός σύνδεσης {{$a}} για συγχρονισμό.", diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index b6433e9fb..51b1c753d 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -426,6 +426,8 @@ "addon.mod_assign_submission_onlinetext.wordlimitexceeded": "The word limit for this assignment is {{$a.limit}} words and you are attempting to submit {{$a.count}} words. Please review your submission and try again.", "addon.mod_book.errorchapter": "Error reading chapter of book.", "addon.mod_book.modulenameplural": "Books", + "addon.mod_book.navnexttitle": "Next: {{$a}}", + "addon.mod_book.navprevtitle": "Previous: {{$a}}", "addon.mod_book.tagarea_book_chapters": "Book chapters", "addon.mod_book.toc": "Table of contents", "addon.mod_chat.beep": "Beep", @@ -1322,12 +1324,12 @@ "core.all": "All", "core.allgroups": "All groups", "core.allparticipants": "All participants", - "core.android": "Android", "core.answer": "Answer", "core.answered": "Answered", "core.areyousure": "Are you sure?", "core.back": "Back", "core.block.blocks": "Blocks", + "core.browser": "Browser", "core.cancel": "Cancel", "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.", @@ -1465,6 +1467,7 @@ "core.deleteduser": "Deleted user", "core.deleting": "Deleting", "core.description": "Description", + "core.desktop": "Desktop", "core.dfdaymonthyear": "MM-DD-YYYY", "core.dfdayweekmonth": "ddd, D MMM", "core.dffulldate": "dddd, D MMMM YYYY h[:]mm A", @@ -1475,11 +1478,27 @@ "core.digitalminor_desc": "Please ask your parent/guardian to contact:", "core.discard": "Discard", "core.dismiss": "Dismiss", + "core.displayoptions": "Display options", "core.done": "Done", "core.download": "Download", "core.downloaded": "Downloaded", "core.downloading": "Downloading", "core.edit": "Edit", + "core.editor.autosavesucceeded": "Draft saved.", + "core.editor.bold": "Bold", + "core.editor.clear": "Clear formatting", + "core.editor.h3": "Heading (large)", + "core.editor.h4": "Heading (medium)", + "core.editor.h5": "Heading (small)", + "core.editor.hidetoolbar": "Hide toolbar", + "core.editor.italic": "Italic", + "core.editor.orderedlist": "Ordered list", + "core.editor.p": "Paragraph", + "core.editor.strike": "Strike through", + "core.editor.textrecovered": "A draft version of this text was automatically restored.", + "core.editor.toggle": "Toggle editor", + "core.editor.underline": "Underline", + "core.editor.unorderedlist": "Unordered list", "core.emptysplit": "This page will appear blank if the left panel is empty or is loading.", "core.error": "Error", "core.errorchangecompletion": "An error occurred while changing the completion status. Please try again.", @@ -1617,6 +1636,7 @@ "core.h5p.offlineDialogRetryButtonLabel": "Retry now", "core.h5p.offlineDialogRetryMessage": "Retrying in :num....", "core.h5p.offlineSuccessfulSubmit": "Successfully submitted results.", + "core.h5p.offlinedisabled": "The site doesn't allow downloading H5P packages.", "core.h5p.originator": "Originator", "core.h5p.pd": "Public Domain", "core.h5p.pddl": "Public Domain Dedication and Licence", @@ -1651,7 +1671,6 @@ "core.imageviewer": "Image viewer", "core.info": "Information", "core.invalidformdata": "Incorrect form data", - "core.ios": "iOS", "core.labelsep": ":", "core.lastaccess": "Last access", "core.lastdownloaded": "Last downloaded", @@ -1686,7 +1705,6 @@ "core.login.emailconfirmsentnoemail": "

An email should have been sent to your address.

It contains easy instructions to complete your registration.

If you continue to have difficulty, contact the site administrator.

", "core.login.emailconfirmsentsuccess": "Confirmation email sent successfully", "core.login.emailnotmatch": "Emails do not match", - "core.login.enterthewordsabove": "Enter the words above", "core.login.erroraccesscontrolalloworigin": "The cross-origin call you're trying to perform has been rejected. Please check https://docs.moodle.org/dev/Moodle_Mobile_development_using_Chrome_or_Chromium", "core.login.errordeletesite": "An error occurred while deleting this site. Please try again.", "core.login.errorupdatesite": "An error occurred while updating the site's token.", @@ -1694,7 +1712,6 @@ "core.login.firsttime": "Is this your first time here?", "core.login.forcepasswordchangenotice": "You must change your password to proceed.", "core.login.forgotten": "Forgotten your username or password?", - "core.login.getanothercaptcha": "Get another CAPTCHA", "core.login.help": "Help", "core.login.helpmelogin": "

There are many thousands of Moodle sites around the world. This app can only connect to Moodle sites that have specifically enabled Mobile app access.

If you can't connect to your Moodle site then you need to contact your site administrator and ask them to read http://docs.moodle.org/en/Mobile_app

To test the app in a Moodle demo site type teacher or student in the Site address field and click the Connect button.

", "core.login.instructions": "Instructions", @@ -1761,7 +1778,6 @@ "core.login.visitchangepassword": "Do you want to visit the site to change the password?", "core.login.webservicesnotenabled": "Web services are not enabled in your site. Please contact your site administrator if you think they should be enabled.", "core.lostconnection": "Your authentication token is invalid or has expired. You will have to reconnect to the site.", - "core.mainmenu.appsettings": "App settings", "core.mainmenu.changesite": "Change site", "core.mainmenu.help": "Help", "core.mainmenu.logout": "Log out", @@ -1892,7 +1908,8 @@ "core.sending": "Sending", "core.serverconnection": "Error connecting to the server", "core.settings.about": "About", - "core.settings.appready": "App ready", + "core.settings.appsettings": "App settings", + "core.settings.appversion": "App version", "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", @@ -1900,6 +1917,7 @@ "core.settings.colorscheme-dark": "Dark", "core.settings.colorscheme-light": "Light", "core.settings.compilationinfo": "Compilation info", + "core.settings.copyinfo": "Copy device info on the clipboard", "core.settings.cordovadevicemodel": "Cordova device model", "core.settings.cordovadeviceosversion": "Cordova device OS version", "core.settings.cordovadeviceplatform": "Cordova device platform", @@ -1912,7 +1930,6 @@ "core.settings.deletesitefilestitle": "Delete site files", "core.settings.deviceinfo": "Device info", "core.settings.deviceos": "Device OS", - "core.settings.devicewebworkers": "Device web workers supported", "core.settings.disableall": "Disable notifications", "core.settings.disabled": "Disabled", "core.settings.displayformat": "Display format", @@ -1929,9 +1946,10 @@ "core.settings.filesystemroot": "File system root", "core.settings.fontsize": "Text size", "core.settings.fontsizecharacter": "A", + "core.settings.forcedsetting": "This setting has been forced by your site configuration.", "core.settings.general": "General", "core.settings.language": "Language", - "core.settings.license": "Licence", + "core.settings.license": "License", "core.settings.localnotifavailable": "Local notifications available", "core.settings.locationhref": "Web view URL", "core.settings.locked": "Locked", @@ -1940,19 +1958,24 @@ "core.settings.navigatorlanguage": "Navigator language", "core.settings.navigatoruseragent": "Navigator userAgent", "core.settings.networkstatus": "Internet connection status", + "core.settings.opensourcelicenses": "Open Source Licences", + "core.settings.preferences": "Preferences", "core.settings.privacypolicy": "Privacy policy", + "core.settings.publisher": "Publisher", "core.settings.pushid": "Push notifications ID", "core.settings.reportinbackground": "Report errors automatically", + "core.settings.screen": "Screen information", "core.settings.settings": "Settings", "core.settings.showdownloadoptions": "Show download options", + "core.settings.siteinfo": "Site info", "core.settings.sites": "Sites", "core.settings.spaceusage": "Space usage", + "core.settings.spaceusagehelp": "Deleting the stored information of the site will remove all the site offline data. This information allows you to use the app when offline. ", "core.settings.synchronization": "Synchronisation", "core.settings.synchronizenow": "Synchronise now", + "core.settings.synchronizenowhelp": "Synchronising a site will send pending changes and all offline activity stored in the device and will synchronise some data like messages and notifications.", "core.settings.syncsettings": "Synchronisation settings", "core.settings.total": "Total", - "core.settings.versioncode": "Version code", - "core.settings.versionname": "Version name", "core.settings.wificonnection": "Wi-Fi connection", "core.sharedfiles.chooseaccountstorefile": "Choose an account to store the file in.", "core.sharedfiles.chooseactionrepeatedfile": "A file with this name already exists. Do you want to replace the existing file or rename it to \"{{$a}}\"?", @@ -2070,7 +2093,6 @@ "core.whoops": "Oops!", "core.whyisthishappening": "Why is this happening?", "core.whyisthisrequired": "Why is this required?", - "core.windowsphone": "Windows Phone", "core.wsfunctionnotavailable": "The web service function is not available.", "core.year": "year", "core.years": "years", diff --git a/src/assets/lang/es-mx.json b/src/assets/lang/es-mx.json index af686f0f4..b8319abbd 100644 --- a/src/assets/lang/es-mx.json +++ b/src/assets/lang/es-mx.json @@ -1353,7 +1353,6 @@ "core.comments.savecomment": "Guardar comentario", "core.comments.warningcommentsnotsent": "No se pudo sincronizar comentarios. {{error}}", "core.commentscount": "Comentarios ({{$a}})", - "core.commentsnotworking": "No pueden recuperarse comentarios", "core.completion-alt-auto-fail": "Finalizado {{$a}} (no obtuvo calificación de aprobado)", "core.completion-alt-auto-n": "Sin finalizar: {{$a}}", "core.completion-alt-auto-n-override": "No completado: {{$a.modname}} (configurado por {{$a.overrideuser}})", @@ -1671,7 +1670,6 @@ "core.login.changepasswordinstructions": "Usted no puede cambiar su contraseña en la App. Por favor elija el botón siguiente para abrir el sitio en un navegador web para cambiar su contraseña. Considere que Usted necesita cerrar el navegador después de cambiar la contraseña, ya que Usted no será redirigido a la App.", "core.login.changepasswordlogoutinstructions": "Si prefiere cambiar de sitio o salirse, por favor elija el botón siguiente:", "core.login.changepasswordreconnectinstructions": "Elija el botón siguiente para reconectarse al sitio. (considere que si no cambia exitosamente su contraseña, Usted regresaría a la pantalla anterior).", - "core.login.checksiteversion": "Revisar que su sitio usa Moodle 2.4 o más reciente.", "core.login.confirmdeletesite": "¿Está seguro de querer eliminar el sitio {{sitename}}?", "core.login.connect": "¡Conectar!", "core.login.connecttomoodle": "Conectar a Moodle", @@ -1686,7 +1684,6 @@ "core.login.emailconfirmsentnoemail": "

Un Email debería de haberse enviado a su dirección.

Contiene instrucciones fáciles para completar su registro.

Si persisten las dificultades, póngase en contacto con el administrador del sitio.

", "core.login.emailconfirmsentsuccess": "Email de confirmación enviado exitosamente", "core.login.emailnotmatch": "No coinciden los Emails", - "core.login.enterthewordsabove": "Escriba las palabras de arriba", "core.login.erroraccesscontrolalloworigin": "La llamada de Orígen Cruzado (''Cross-Origin'') que Usted está tratando de realizar ha sido rechazada. Por favor revise https://docs.moodle.org/dev/Moodle_Mobile_development_using_Chrome_or_Chromium", "core.login.errordeletesite": "Ocurrió un error al eliminar el sitio. Por favor inténtelo nuevamente.", "core.login.errorupdatesite": "Ocurrió un error al actualizar la ficha (''token'') del sitio", @@ -1694,7 +1691,6 @@ "core.login.firsttime": "Registrarse como usuario", "core.login.forcepasswordchangenotice": "Para continuar, deberá cambiar su contraseña.", "core.login.forgotten": "¿Olvidó su nombre_de_usuario o contraseña?", - "core.login.getanothercaptcha": "Obtener otro CAPTCHA", "core.login.help": "Ayuda", "core.login.helpmelogin": "

Existen muchos miles de sitios Moodle en el mundo. Esta App solamente puede conectar a sitios Moodle que tengan específicamente habilitado el acceso por la App Mobile.

Si Usted no puede conectarse a su sitio Moodle, entonces Usted necesita ponerse en contacto con su administrador del sitio y pedirle que lea http://docs.moodle.org/en/Mobile_app

Para probar la App en un sitio Moodle demostrativo, escriba teacher o student en el campo de la Dirección del sitio y haga click en el Botón Conectar.

", "core.login.instructions": "Instrucciones", @@ -1707,9 +1703,6 @@ "core.login.invalidurl": "Se ha especificado una URL no válida", "core.login.invalidvaluemax": "El valor máximo es {{$a}}", "core.login.invalidvaluemin": "El valor mínimo es {{$a}}", - "core.login.legacymoodleversion": "Usted está tratando de conectarse a una versión de Moodle no soportada. Por favor, descargue la App de Moodle Clásico para acceder a este sitio Moodle.", - "core.login.legacymoodleversiondesktop": "Usted está intentando conectarse a {{$a}}.

Este sitio está ejecutando una versión antigua no soportada de Moodle, la cual no funcionará con esta App de Moodle Desktop.

Si este es su sitio, por favor póngase en contacto con su socio Moodle local para obtener asistencia para actualizarlo.

Vea nuestra página de contacto para enviar una solicitud para asistencia.", - "core.login.legacymoodleversiondesktopdownloadold": "

Alternativamente, Usted puede continuar accediendo a este sitio usando una versión no soportada de la App que puede descargarse desde aquí.", "core.login.localmobileunexpectedresponse": "La revisión de las características Adicionales de Moodle Mobile regresó una respuesta inesperada; Usted será autenticado usando el servicio Mobile estándar.", "core.login.loggedoutssodescription": "Usted tiene que autenticarse nuevamente. Usted necesita ingresar al sitio en una ventana del navegador.", "core.login.login": "Ingresar", @@ -2000,8 +1993,18 @@ "core.submit": "Enviar", "core.success": "Éxito", "core.tablet": "Tableta", + "core.tag.defautltagcoll": "Colección por defecto", "core.tag.errorareanotsupported": "Esta área de marca no está soportada por la App.", + "core.tag.inalltagcoll": "En todas partes", + "core.tag.itemstaggedwith": "{{$a.tagarea}} marcadas con \"{{$a.tag}}\"", + "core.tag.notagsfound": "No se encontraron marcas que coincidan con \"{{$a}}\"", + "core.tag.searchtags": "Buscar marcas", + "core.tag.showingfirsttags": "Mostrando las {{$a}} marcas más populares", "core.tag.tag": "Marca", + "core.tag.tagarea_course": "Cursos", + "core.tag.tagarea_course_modules": "Actividades y recursos", + "core.tag.tagarea_post": "Publicacines en blog", + "core.tag.tagarea_user": "Intereses del usuario", "core.tag.tags": "Marcas", "core.tag.warningareasnotsupported": "Algunas de las áreas de marca no son mostradas porque no están soportadas por la App.", "core.teachers": "Profesores", diff --git a/src/assets/lang/es.json b/src/assets/lang/es.json index c27a69f98..a0567b01c 100644 --- a/src/assets/lang/es.json +++ b/src/assets/lang/es.json @@ -1351,7 +1351,6 @@ "core.comments.savecomment": "Guardar comentario", "core.comments.warningcommentsnotsent": "No se han podido sincronizar los comentarios. {{error}}", "core.commentscount": "Comentarios ({{$a}})", - "core.commentsnotworking": "No pueden recuperarse comentarios", "core.completion-alt-auto-fail": "Finalizado {{$a}} (no ha alcanzado la calificación de aprobado)", "core.completion-alt-auto-n": "Sin finalizar: {{$a}}", "core.completion-alt-auto-n-override": "No completado: {{$a.modname}} (configurado por {{$a.overrideuser}})", @@ -1575,7 +1574,6 @@ "core.login.authenticating": "Autenticando", "core.login.cancel": "Cancelar", "core.login.changepassword": "Cambiar contraseña", - "core.login.checksiteversion": "Comprueba que tu sitio utiliza Moodle 2.4 o superior.", "core.login.confirmdeletesite": "¿Está seguro de que quiere eliminar el sitio {{sitename}}?", "core.login.connect": "Conectar", "core.login.connecttomoodle": "Conectar a Moodle", @@ -1590,7 +1588,6 @@ "core.login.emailconfirmsentnoemail": "

Un email debería de haberse enviado a su dirección.

Contiene instrucciones fáciles para completar su registro.

Si persisten las dificultades, póngase en contacto con el administrador del sitio.

", "core.login.emailconfirmsentsuccess": "Correo de confirmación enviado exitosamente", "core.login.emailnotmatch": "Las direcciones de correo no coinciden.", - "core.login.enterthewordsabove": "Escriba las palabras de arriba", "core.login.erroraccesscontrolalloworigin": "La llamada Cross-Origin que está intentando ha sido rechazada. Por favor visite https://docs.moodle.org/dev/Moodle_Mobile_development_using_Chrome_or_Chromium", "core.login.errordeletesite": "Ha ocurrido un error eliminando este sitio. Por favor, inténtelo de nuevo.", "core.login.errorupdatesite": "Se ha producido un error actualizando el token del sitio.", @@ -1598,7 +1595,6 @@ "core.login.firsttime": "Registrarse como usuario", "core.login.forcepasswordchangenotice": "Para continuar, deberá cambiar su contraseña.", "core.login.forgotten": "¿Olvidó su nombre de usuario o contraseña?", - "core.login.getanothercaptcha": "Obtener otro CAPTCHA", "core.login.help": "Ayuda", "core.login.helpmelogin": "

Existen muchos sitios Moodle en el mundo. Esta aplicación solamente puede conectar a sitios Moodle que tengan específicamente habilitado el acceso por la aplicación Moodle.

Si no puede conectarse a su sitio Moodle, póngase en contacto con el administrador del sitio y pídale que lea http://docs.moodle.org/es/Mobile_app

Para probar la aplicación en un sitio Moodle de muestra, escriba teacher o student en el campo de la Dirección del sitio y haga click en el Botón Conectar.

", "core.login.instructions": "Instrucciones", @@ -1611,9 +1607,6 @@ "core.login.invalidurl": "Se ha especificado una URL no válida", "core.login.invalidvaluemax": "El valor máximo es {{$a}}", "core.login.invalidvaluemin": "El valor mínimo es {{$a}}", - "core.login.legacymoodleversion": "Está tratando de conectarse a una versión de Moodle no soportada. Por favor, descargue la aplicación de Moodle Classic para acceder a este sitio Moodle.", - "core.login.legacymoodleversiondesktop": "Está intentando conectarse a {{$a}}.

Este sitio está ejecutando una versión antigua no soportada de Moodle, la cual no funcionará con esta aplicación de Moodle Desktop.

Si este es su sitio, por favor póngase en contacto con su Moodle Partner local para obtener asistencia para actualizarlo.

Vea nuestra página de contacto para solicitar asistencia.", - "core.login.legacymoodleversiondesktopdownloadold": "

Alternativamente, puede continuar accediendo a este sitio usando una versión no soportada de la App que puede descargarse desde aquí.", "core.login.localmobileunexpectedresponse": "Las características adicionales de Moodle Mobile han devuelto una respuesta inesperada, debe autenticarse utilizando el servicio estándar de Mobile.", "core.login.loggedoutssodescription": "Tiene que autenticarse nuevamente. Necesita acceder al sitio en una ventana del navegador.", "core.login.login": "Acceder", @@ -1899,7 +1892,13 @@ "core.success": "Éxito", "core.tablet": "Tablet", "core.tag.errorareanotsupported": "Esta etiqueta de area no está permitida por esta aplicación.", + "core.tag.searchtags": "Buscar marcas", + "core.tag.showingfirsttags": "Mostrando las {{$a}} marcas más populares", "core.tag.tag": "Marca", + "core.tag.tagarea_course": "Cursos", + "core.tag.tagarea_course_modules": "Actividades y recursos", + "core.tag.tagarea_post": "Publicaciones de blog", + "core.tag.tagarea_user": "Intereses del usuario", "core.tag.tags": "Marcas", "core.tag.warningareasnotsupported": "Algunas de las etiquetas de areas no se muestran porque no están permitidas en esta aplicación.", "core.teachers": "Profesores", diff --git a/src/assets/lang/eu.json b/src/assets/lang/eu.json index f576b6d56..6ee25d0de 100644 --- a/src/assets/lang/eu.json +++ b/src/assets/lang/eu.json @@ -1353,7 +1353,6 @@ "core.comments.savecomment": "Gorde iruzkina", "core.comments.warningcommentsnotsent": "Ezin izan dira iruzkinak sinkronizatu. {{error}}", "core.commentscount": "Iruzkinak: ({{$a}})", - "core.commentsnotworking": "Iruzkinak ezin izan dira atzitu", "core.completion-alt-auto-fail": "Osatuta: {{$a}} (ez dute gutxieneko kalifikazioa lortu)", "core.completion-alt-auto-n": "Osatu gabea: {{$a}}", "core.completion-alt-auto-n-override": "Osatu gabea: {{$a.modname}} ({{$a.overrideuser}}-(e)k baliogabetua)", @@ -1671,7 +1670,6 @@ "core.login.changepasswordinstructions": "Ezin duzu pasahitza app-an aldatu. Egin klik mesedez hurrengo botoian gunea nabigatzaile batean ireki eta bertatik pasahitza aldatzeko. Kontuan izan pasahitza aldatu ondoren ez zarela app-ra automatikoki bideratua izango eta beraz app-ra itzultzeko nabigatzailea itxi beharko duzu.", "core.login.changepasswordlogoutinstructions": "Gunea aldatu edo saioa amaitu nahi baduzu, egin klik hurrengo botoian:", "core.login.changepasswordreconnectinstructions": "Egin klik hurrengo botoian gunera berriz konektatzeko. (Kontuan izan pasahitza aldatzea lortu ez baduzu aurreko pantailara itzuliko zarela)", - "core.login.checksiteversion": "Egiaztatu zure Moodle guneak 2.4 bertsioa edo aurreragokoa erabiltzen duela.", "core.login.confirmdeletesite": "Ziur zaude {{sitename}} gunea ezabatu nahi duzula?", "core.login.connect": "Konektatu!", "core.login.connecttomoodle": "Moodle-ra konektatu", @@ -1686,7 +1684,6 @@ "core.login.emailconfirmsentnoemail": "

Zure helbidera e-posta mezu bat bidali da.

Bertan erregistroa amaitzeko argibide errazak aurkituko dituzu.

Arazoekin jarraitzen baduzu jarri zaitez harremanetan zure guneko kudeatzailearekin.

", "core.login.emailconfirmsentsuccess": "Baieztatze e-maila ondo bidali da", "core.login.emailnotmatch": "E-posta helbideak ez datoz bat", - "core.login.enterthewordsabove": "Idatzi goiko hitzak", "core.login.erroraccesscontrolalloworigin": "Saiatzen ari zaren cross-origin deia ez da onartu. Ikusi mesedez https://docs.moodle.org/dev/Moodle_Mobile_development_using_Chrome_or_Chromium", "core.login.errordeletesite": "Errorea gertatu da gunea ezabatzean. Mesedez saiatu beranduago.", "core.login.errorupdatesite": "Errore bat gertatu da guneko token-a eguneratzean.", @@ -1694,7 +1691,6 @@ "core.login.firsttime": "Hau al da zure lehen aldia hemen?", "core.login.forcepasswordchangenotice": "Jarraitzeko zure pasahitza aldatu behar duzu.", "core.login.forgotten": "Zure erabiltzaile-izena edo pasahitza ahaztu duzu?", - "core.login.getanothercaptcha": "Eskuratu beste CAPTCHA bat", "core.login.help": "Laguntza", "core.login.helpmelogin": "

Mundu osoan milaka Moodle gune dago. App honek soilik Mobile sarbidea espresuki gaitu duten guneetara konektatu daiteke.

Zure Moodle gunera ezin bazara konektatu zure guneko kudeatzailearekin harremanetan jarri beharko zara eta eskatu http://docs.moodle.org/en/Mobile_app irakurtzeko

App-a probetarako Moodle ingurunean probatzeko idatzi teacher edo student Gunearen helbidea eremuan eta egin klik Konektatu! botoian.

", "core.login.instructions": "Argibideak", @@ -1707,9 +1703,6 @@ "core.login.invalidurl": "Balio ez duen URLa ezarri da", "core.login.invalidvaluemax": "Gehieneko balioa {{$a}} da.", "core.login.invalidvaluemin": "Gutxieneko balioa {{$a}} da.", - "core.login.legacymoodleversion": "Onartzen ez den Moodle-ko bertsioa duen gune batera konektatzen saiatzen ari zara. Jaitsi ezazu mesedez Moodle Classic app-a Moodle gune horretara sartzeko.", - "core.login.legacymoodleversiondesktop": "{{$a}} helbidera konektatzen saiatzen ari zara.

Gune honek zaharkituta dagoen eta jada onartzen ez den Moodle bertsioa erabiltzen du eta ez du Moodle Desktop App honekin funtzionatuko.

Hau zure gunea bada jarri zaitez harremanetan hurbileko Moodle partner batekin eguneratzen lagundu zaitzan.

Ikusi gure kontaktuen orria laguntza-eskaera bat bidaltzeko.", - "core.login.legacymoodleversiondesktopdownloadold": "

Bestela, zure gunean sartu zaitezke app-aren sostengurik gabeko bertsio bat erabiliz, hemendik jaitsi daitekeena.", "core.login.localmobileunexpectedresponse": "Moodle Mobile-ko Funtzio Aurreratuen kontrolak ezusteko erantzuna eman du. Mobile zerbitzu estandarra erabilita autentifikatuko zaitugu.", "core.login.loggedoutssodescription": "Berriz autentifikatu behar duzua. Gunean nabigatzaile leiho baten bitartez hasi behar duzu saioa.", "core.login.login": "Sartu", @@ -2000,8 +1993,18 @@ "core.submit": "Bidali", "core.success": "Ondo", "core.tablet": "Tablet-a", + "core.tag.defautltagcoll": "Lehenetsitako bilduma", "core.tag.errorareanotsupported": "Etiketa-eremu hau ez da app-an onartzen.", + "core.tag.inalltagcoll": "Edonon", + "core.tag.itemstaggedwith": "{{$a.tagarea}} ondokoarekin etiketatu da: \"{{$a.tag}}\"", + "core.tag.notagsfound": "Ez da aurkitu \"{{$a}}\"-(r)ekin bat datorren etiketarik", + "core.tag.searchtags": "Bilatu etiketak", + "core.tag.showingfirsttags": "{{$a}} etiketa ospetsuenak erakusten", "core.tag.tag": "Etiketa", + "core.tag.tagarea_course": "Ikastaroak", + "core.tag.tagarea_course_modules": "Jarduerak eta baliabideak", + "core.tag.tagarea_post": "Blogetako mezuak", + "core.tag.tagarea_user": "Erabiltzailearen interesak", "core.tag.tags": "Etiketak", "core.tag.warningareasnotsupported": "Etiketa-eremu batzuk ez dira erakusten app-an a", "core.teachers": "Irakasleak", diff --git a/src/assets/lang/fa.json b/src/assets/lang/fa.json index 2f6ded3e2..651d7aeb0 100644 --- a/src/assets/lang/fa.json +++ b/src/assets/lang/fa.json @@ -1207,11 +1207,9 @@ "core.login.createuserandpass": "نام کاربری و رمز عبور خود را انتخاب کنید", "core.login.emailconfirmsent": "

باید نامه‌ای به آدرس شما در {{$a}} فرستاده شده باشد

\n

این نامه شامل دستورالعمل‌های ساده‌ای برای تکمیل عضویت شما است.

\n

در صورت تداوم مواجهه با مشکل، با مدیر سایت تماس بگیرید.

", "core.login.emailconfirmsentsuccess": "ارسال ایمیل تاییدیه موفقیت آمیز بود", - "core.login.enterthewordsabove": "کلمات بالا را وارد نمائید", "core.login.firsttime": "برای اولین بار به این صفحه آمده‌اید؟", "core.login.forcepasswordchangenotice": "برای پیش‌روی باید رمز ورود خود را تغییر دهید.", "core.login.forgotten": "نام کاربری و یا رمز ورود خود را فراموش کرده‌اید؟", - "core.login.getanothercaptcha": "دریافت یک کپچای دیگر", "core.login.help": "راهنمایی", "core.login.helpmelogin": "

چندین هزار سایت مودل در دنیا وجود دارد. این برنامه فقط می‌تواند به آن مودل‌هایی متصل شود که صراحتا دسترسی «برنامه موبایل» را فعال کرده‌باشند.

اگر نمی‌توانید به سایت مودل خود وصل شوید، باید با مدیر مودل آن سایت تماس بگیرید و بخواهید که http://docs.moodle.org/en/Mobile_app را مطالعه کنند.

\n

برای آزمایش برنامه با استفاده از یک سایت آزمایشی، در قسمت مربوط به آدرس سایت عبارت teacher یا student را وارد کرده و بر روی دکمه اتصال کلیک کنید.

", "core.login.instructions": "دستورالعمل", @@ -1414,7 +1412,13 @@ "core.strftimetime": "%I:%M %p", "core.submit": "ارسال", "core.success": "موفق", + "core.tag.defautltagcoll": "مجموعه پیش‌فرض", + "core.tag.searchtags": "جستجوی برچسب‌ها", "core.tag.tag": "برچسب", + "core.tag.tagarea_course": "درس‌ها", + "core.tag.tagarea_course_modules": "فعالیت‌ها و منابع", + "core.tag.tagarea_post": "مطلب‌های وبلاگ", + "core.tag.tagarea_user": "علائق کاربران", "core.tag.tags": "tag ها", "core.teachers": "استاد", "core.thisdirection": "rtl", diff --git a/src/assets/lang/fi.json b/src/assets/lang/fi.json index 3f007ade2..970ced49b 100644 --- a/src/assets/lang/fi.json +++ b/src/assets/lang/fi.json @@ -1290,7 +1290,6 @@ "core.comments.nocomments": "Ei kommentteja", "core.comments.savecomment": "Tallenna kommentti", "core.commentscount": "Kommentit ({{$a}})", - "core.commentsnotworking": "Kommentteja ei pystytä lataamaan", "core.completion-alt-auto-fail": "Suoritettu: {{$a}} (ei saavutettu hyväksyttyä arvosanaa)", "core.completion-alt-auto-n": "Suorittamatta: {{$a}}", "core.completion-alt-auto-n-override": "Suorittamatta: {{$a.modname}} (asettanut {{$a.overrideuser}})", @@ -1485,7 +1484,6 @@ "core.login.authenticating": "Autentikoidaan", "core.login.cancel": "Peruuta", "core.login.changepassword": "Vaihda salasana", - "core.login.checksiteversion": "Tarkista, että Moodlen versio on 2.4 tai uudempi.", "core.login.confirmdeletesite": "Oletko varma, että haluat poistaa sivuston {{sitename}}?", "core.login.connect": "Yhdistä!", "core.login.connecttomoodle": "Yhdistä Moodleen", @@ -1497,13 +1495,11 @@ "core.login.emailconfirmsent": "

Vahvistusviesti on lähetetty osoitteeseesi {{$a}}

\n

Se sisältää ohjeet, kuinka voit vahvistaa käyttäjätunnuksesi.

\n

Jos vahvistuksessa on ongelmia, ota yhteyttä ylläpitäjään.

", "core.login.emailconfirmsentsuccess": "Vahvistusviesti lähetetty onnistuneesti", "core.login.emailnotmatch": "Sähköpostiosoitteet eivät täsmää", - "core.login.enterthewordsabove": "Kirjoita ylläolevat sanat", "core.login.errordeletesite": "Sivustoa poistettaessa tapahtui virhe. Ole hyvä ja yritä uudelleen.", "core.login.errorupdatesite": "Sivuston tokenia päivittäessä tapahtui virhe.", "core.login.firsttime": "Jos sinulla ei ole vielä tunnusta:", "core.login.forcepasswordchangenotice": "Sinun pitää vaihtaa salasanasi ennen kuin voit jatkaa.", "core.login.forgotten": "Unohditko käyttäjätunnuksesi tai salasanasi?", - "core.login.getanothercaptcha": "Hae uusi varmenne", "core.login.help": "Ohje", "core.login.instructions": "Ohjeet", "core.login.invalidaccount": "Ole hyvä ja tarkista kirjautumistietosi, jos ne ovat oikein pyydä järjestelmän pääkäyttäjää tarkistamaan, että palvelu toimii oikein.", @@ -1750,6 +1746,7 @@ "core.submit": "Lähetä", "core.success": "Valmis!", "core.tablet": "Tabletti-tietokone", + "core.tag.searchtags": "Hae tunnisteita", "core.tag.tag": "Tunniste", "core.tag.tags": "Tunnisteet", "core.teachers": "Opettajat", diff --git a/src/assets/lang/fr.json b/src/assets/lang/fr.json index cfb645395..3e538595c 100644 --- a/src/assets/lang/fr.json +++ b/src/assets/lang/fr.json @@ -1353,7 +1353,6 @@ "core.comments.savecomment": "Enregistrer le commentaire", "core.comments.warningcommentsnotsent": "Impossible de synchroniser les commentaires. {{error}}", "core.commentscount": "Commentaires ({{$a}})", - "core.commentsnotworking": "Les commentaires ne peuvent pas être récupérés", "core.completion-alt-auto-fail": "Terminé : {{$a}} (n'a pas atteint la note pour passer)", "core.completion-alt-auto-n": "Non terminé : {{$a}}", "core.completion-alt-auto-n-override": "Non terminé : {{$a.modname}} (défini par {{$a.overrideuser}})", @@ -1494,6 +1493,7 @@ "core.erroropenfilenoextension": "Erreur lors de l'ouverture du fichier : le nom du fichier n'a pas d'extension.", "core.erroropenpopup": "Cette activité essaie d'ouvrir dans une fenêtre surgissante. Ceci n'est pas supporté dans l'app.", "core.errorrenamefile": "Erreur lors du renommage du fichier. Veuillez essayer à nouveau.", + "core.errorsomedatanotdownloaded": "Si vous avez téléchargé cette activité, veuillez prendre note que certaines données ne sont pas téléchargées pour des raisons de performance et d'utilisation de données.", "core.errorsync": "Une erreur est survenue lors de la synchronisation. Veuillez essayer plus tard.", "core.errorsyncblocked": "Ce {{$a}} ne peut pas être synchronisé maintenant en raison d'une tâche en cours. Veuillez essayer plus tard. Si le problème persiste, veuillez relancer l'app.", "core.explanationdigitalminor": "Cette information est requise pour déterminer si vous avez l'âge de la majorité numérique. Cet âge est celui qu'une personne doit avoir pour pouvoir donner son consentement à l'accès et au stockage par un tiers des données personnelles la concernant.", @@ -1666,7 +1666,8 @@ "core.login.cancel": "Annuler", "core.login.changepassword": "Changer le mot de passe", "core.login.changepasswordbutton": "Ouvrir la page de changement de mot de passe", - "core.login.checksiteversion": "Veuillez vérifier que votre site utilise Moodle 2.4 ou une version ultérieure.", + "core.login.changepasswordhelp": "Si vous rencontrez des problèmes lors du changement de mot de passe, veuillez contacter l'administrateur de votre site. Si vous ne savez pas comment le contacter, veuillez contacter votre enseignant.", + "core.login.changepasswordinstructions": "Il n'est pas possible de changer votre mot de passe dans l'app. Veuillez cliquer le bouton ci-dessous pour ouvrir le site dans un navigateur, afin de changer votre mot de passe. N'oubliez pas de fermer votre navigateur après avoir changé de mot de passe, car vous ne serez pas redirigé vers l'app.", "core.login.confirmdeletesite": "Voulez-vous vraiment supprimer la plateforme {{sitename}} ?", "core.login.connect": "Connecter !", "core.login.connecttomoodle": "Connexion à Moodle", @@ -1679,7 +1680,6 @@ "core.login.emailconfirmsentnoemail": "

Un courriel a été envoyé à votre adresse.

Il contient des instructions simples à effectuer pour terminer votre enregistrement.

Si vous rencontrez des difficultés, veuillez contacte l'administrateur du site.", "core.login.emailconfirmsentsuccess": "Courriel de confirmation envoyé", "core.login.emailnotmatch": "Les adresses de courriel ne correspondent pas", - "core.login.enterthewordsabove": "Tapez les mots ci-dessus", "core.login.erroraccesscontrolalloworigin": "La tentative d'appel « Cross-Origin » que vous avez effectuée a été rejetée. Veuillez consulter https://docs.moodle.org/dev/Moodle_Mobile_development_using_Chrome_or_Chromium", "core.login.errordeletesite": "Une erreur est survenue lors de la suppression de la plateforme. Veuillez essayer plus tard.", "core.login.errorupdatesite": "Une erreur est survenue lors de la mise à jour du jeton du site.", @@ -1687,7 +1687,6 @@ "core.login.firsttime": "Première visite sur ce site ?", "core.login.forcepasswordchangenotice": "Vous devez changer votre mot de passe pour continuer.", "core.login.forgotten": "Vous avez oublié votre nom d'utilisateur et/ou votre mot de passe ?", - "core.login.getanothercaptcha": "Obtenir un nouveau CAPTCHA", "core.login.help": "Aide", "core.login.helpmelogin": "Il y a plusieurs milliers de sites Moodle dans le monde. Cette app peut uniquement se connecter aux sites Moodle qui ont activé l'accès via l'app mobile.

Si vous n'arrivez pas à vous connecter, veuillez contacter l'administrateur de votre site Moodle et lui demander de lire https://docs.moodle.org/fr/App_Moodle

Pour tester l'app avec un site Moodle de démonstration, tapez teacher ou student dans le champ Adresse du site et tapotez sur le bouton Connexion.

", "core.login.instructions": "Instructions", @@ -1700,9 +1699,6 @@ "core.login.invalidurl": "URL spécifié non valide", "core.login.invalidvaluemax": "La valeur maximale est {{$a}}", "core.login.invalidvaluemin": "La valeur minimale est {{$a}}", - "core.login.legacymoodleversion": "Vous essayez de vous connecter à une version non supportée de Moodle. Veuillez télécharger l'app Moodle classique pour accéder à ce site.", - "core.login.legacymoodleversiondesktop": "Vous essayez de vous connecter à {{$a}}.

Ce site tourne avec un ancienne version non supportée de Moodle, qui ne fonctionnera pas avec cette app Moodle Desktop.

S'il s'agit de votre site, contactez votre partenaire Moodle pour obtenir de l'assistance afin de le mettre à jour.

Consultez notre page de contact pour envoyer une demande d'assistance.", - "core.login.legacymoodleversiondesktopdownloadold": "

Autrement, vous pouvez accéder à ce site au moyen d'une version non supportée de l'app, téléchargeable ici.", "core.login.localmobileunexpectedresponse": "La vérification des fonctionnalités additionnelles de Moodle Mobile a envoyé une réponse inattendue. Vous allez être connecté au moyen du service mobile standard.", "core.login.loggedoutssodescription": "Veuillez vous ré-authentifier en vous connectant au site au moyen d'un navigateur web.", "core.login.login": "Connexion", @@ -1993,8 +1989,18 @@ "core.submit": "Envoyer", "core.success": "Succès", "core.tablet": "Tablette", + "core.tag.defautltagcoll": "Collection par défaut", "core.tag.errorareanotsupported": "Cette zone de tag n'est pas supportée par l'app.", + "core.tag.inalltagcoll": "Partout", + "core.tag.itemstaggedwith": "{{$a.tagarea}} signalées avec « {{$a.tag}} »", + "core.tag.notagsfound": "Aucun tags avec « {{$a}} » trouvé", + "core.tag.searchtags": "Chercher des tags", + "core.tag.showingfirsttags": "Affichage des {{$a}} tags les plus utilisés", "core.tag.tag": "Tag", + "core.tag.tagarea_course": "Cours", + "core.tag.tagarea_course_modules": "Activités et ressources", + "core.tag.tagarea_post": "Articles de blog", + "core.tag.tagarea_user": "Centres d'intérêt", "core.tag.tags": "Tags", "core.tag.warningareasnotsupported": "Certaines zones de tag ne sont pas affichées, car elles ne sont pas supportées par l'app.", "core.teachers": "Enseignants", diff --git a/src/assets/lang/he.json b/src/assets/lang/he.json index 9ba6ed61d..f03f6eb45 100644 --- a/src/assets/lang/he.json +++ b/src/assets/lang/he.json @@ -1281,13 +1281,11 @@ "core.login.credentialsdescription": "יש להזין את שם המשתמש והסיסמה שלך כדי להתחבר", "core.login.emailconfirmsent": "

לכתובתך ב {{$a}} תשלח בקרוב הודעת דואר אלקטרוני.

הודעה זו מכילה הוראות פשוטות להשלמת הרשמתך.

אם תיתקל בקשיים, אנא צור קשר עם מנהל האתר.

", "core.login.emailconfirmsentsuccess": "אימות דוא\"ל נשלח בהצלחה", - "core.login.enterthewordsabove": "יש להזין את מילים (תווים או מספרים) המוצגים מעלה (בתמונה)", "core.login.errordeletesite": "התרחשה שגיאה בזמן מחיקת אתר זה. אנא נסה שוב.", "core.login.errorupdatesite": "התרחשה שגיאה בזמן עדכון אסימון האתר (token).", "core.login.firsttime": "האם זהו הביקור הראשון שלך באתר זה?", "core.login.forcepasswordchangenotice": "יש לשנות סיסמה כדי להמשיך", "core.login.forgotten": "שכחת את שם המשתמש או את הסיסמה שלך?", - "core.login.getanothercaptcha": "הצגת CAPTCHA אחר", "core.login.help": "עזרה", "core.login.helpmelogin": "

קיימים אלפים רבים של אתרי מוודל ברחבי העולם. אפליקציה זו יכולה להתחבר רק לאתרים בהם .הופעלה גישה לאפליקצית מובייל

אם אין באפרותך להתחבר לאתר המוודל שלך, עליך לפנות למנהל האתר הדרוש ולבקש מהם לקרוא http://docs.moodle.org/en/Mobile_app

בכדי לבדוק את האפליקציה באתר דמו של מוודל, יש להזין teacher או student בשדה כתובת אתר וללחוץ על כפתור הוסף.

", "core.login.instructions": "הוראות", @@ -1509,7 +1507,17 @@ "core.submit": "שמירה", "core.success": "הצלחה", "core.tablet": "טאבלט", + "core.tag.defautltagcoll": "אוסף בררת־המחדל", + "core.tag.inalltagcoll": "בכל מקום", + "core.tag.itemstaggedwith": "התג '{{$a.tag}}' נוסף ל'{{$a.tagarea}}'", + "core.tag.notagsfound": "לא נמצאו תגים בשם \"{{$a}}\"", + "core.tag.searchtags": "חיפוש תגים", + "core.tag.showingfirsttags": "תצוגת {{$a}} התגים השכיחים ביותר", "core.tag.tag": "תג", + "core.tag.tagarea_course": "קורסים", + "core.tag.tagarea_course_modules": "משאבים ופעילויות", + "core.tag.tagarea_post": "פרסומי בלוגים", + "core.tag.tagarea_user": "תחומי עניין של משתמש", "core.tag.tags": "תגים", "core.teachers": "מורים", "core.thisdirection": "rtl", diff --git a/src/assets/lang/hi.json b/src/assets/lang/hi.json index 124a982de..7458e991f 100644 --- a/src/assets/lang/hi.json +++ b/src/assets/lang/hi.json @@ -424,7 +424,6 @@ "core.comments.eventcommentcreated": "टिप्पणी बनाई गई", "core.comments.eventcommentdeleted": "टिप्पणी हटा दी गई", "core.commentscount": "टिप्पणियाँ ({{$a}})", - "core.commentsnotworking": "टिप्पणियों को पुनर्प्राप्त नहीं किया जा सकता है", "core.completion-alt-auto-fail": "समाप्त किया: {{$a}} (पास ग्रेड प्राप्त नहीं हुआ)", "core.completion-alt-auto-n": "समाप्त नहीं हुई: {{$a}}", "core.completion-alt-auto-n-override": "समाप्त नहीं हुई: {{$a.modname}} (set by {{$a.overrideuser}})", @@ -598,7 +597,6 @@ "core.login.authenticating": "प्रमाणित कर रहा है", "core.login.cancel": "कैन्सॅल", "core.login.changepassword": "पासवर्ड बदलिए", - "core.login.checksiteversion": "जांचें कि आपकी साइट Moodle 2.4 या बाद का उपयोग करती है।", "core.login.confirmdeletesite": "क्या आप वाकई {{sitename}} साइट को हटाना चाहते हैं?", "core.login.connect": "जुडिये!", "core.login.connecttomoodle": "मूडल से कनेक्ट करें", @@ -611,7 +609,6 @@ "core.login.emailconfirmsentnoemail": "

आपके पते पर एक ईमेल भेजा जाना चाहिए।

इसमें आपका पंजीकरण पूरा करने के लिए आसान निर्देश शामिल हैं।

यदि आपको कठिनाई जारी है, तो साइट व्यवस्थापक से संपर्क करें। ", "core.login.emailconfirmsentsuccess": "पुष्टिकरण ईमेल सफलतापूर्वक भेजा गया", "core.login.emailnotmatch": "ईमेल एक - दूसरे से मेल नहीं खाते", - "core.login.enterthewordsabove": "ऊपर्युक्त शब्दों को दर्ज करें", "core.login.erroraccesscontrolalloworigin": "जिस क्रॉस-ऑरिजिन कॉल को आप करने की कोशिश कर रहे हैं, उसे अस्वीकार कर दिया गया है। कृपया https://docs.moodle.org/dev/Moodle_Mobile_development_using_Chrome_or_hhhium पर जाएं", "core.login.errordeletesite": "इस साइट को हटाते समय एक त्रुटि हुई। कृपया पुन: प्रयास करें।", "core.login.errorupdatesite": "साइट के टोकन को अपडेट करते समय एक त्रुटि हुई।", @@ -619,7 +616,6 @@ "core.login.firsttime": "क्या आप यहाँ पहली बार आयें हैं?", "core.login.forcepasswordchangenotice": "आगे बढ़ने के लिए आपको अपना पासवर्ड बदलना होगा।", "core.login.forgotten": "क्या आप अपना यूज़रनेम या पासवर्ड भूल गये हैं?", - "core.login.getanothercaptcha": "एक और कैप्चा प्राप्त करें", "core.login.help": "सहायता", "core.login.helpmelogin": "

दुनिया भर में कई हजारों Moodle साइट्स हैं। यह ऐप केवल उन Moodle साइटों से कनेक्ट हो सकता है जिन्होंने विशेष रूप से मोबाइल ऐप एक्सेस सक्षम किया है।

यदि आप अपनी Moodle साइट से कनेक्ट नहीं कर सकते हैं, तो आपको अपने साइट व्यवस्थापक से संपर्क करने और उन्हें पढ़ने के लिए कहने की आवश्यकता है \"http://docs.moodle.org/en/Mobile_app\" लक्ष्य = \"_blank\"> http://docs.moodle.org/en/Mobile_app

में एप्लिकेशन का परीक्षण करने के लिए Moodle डेमो साइट प्रकार शिक्षक या छात्र साइट पते फ़ील्ड में और कनेक्ट बटन ", "core.login.instructions": "निर्देश", @@ -630,9 +626,6 @@ "core.login.invalidtime": "अमान्य समय", "core.login.invalidvaluemax": "अधिकतम मूल्य {{$ a}} है", "core.login.invalidvaluemin": "न्यूनतम मान {{$ a}} है", - "core.login.legacymoodleversion": "आप किसी असमर्थित Moodle संस्करण से कनेक्ट करने का प्रयास कर रहे हैं। कृपया, इस Moodle साइट तक पहुँचने के लिए Moodle क्लासिक ऐप डाउनलोड करें", - "core.login.legacymoodleversiondesktop": "आप {{$a}} से जुड़ने का प्रयास कर रहे हैं।

यह साइट Moodle का पुराना असमर्थित संस्करण चला रही है जो इस Moodle डेस्कटॉप ऐप के साथ काम नहीं करेगा।

br> यदि यह आपकी साइट है तो कृपया इसे अपडेट करने के लिए सहायता प्राप्त करने के लिए अपने स्थानीय मूडी साथी से संपर्क करें।

हमारे संपर्क पृष्ठ देखें सहायता के लिए अनुरोध प्रस्तुत करने के लिए।", - "core.login.legacymoodleversiondesktopdownloadold": "

वैकल्पिक रूप से, आप अभी भी इस साइट को ऐप के एक असमर्थित संस्करण का उपयोग करके एक्सेस कर सकते हैं जिसे यहां से डाउनलोड किया जा सकता है।", "core.login.localmobileunexpectedresponse": "Moodle मोबाइल एडिशनल फीचर्स चेक ने अप्रत्याशित प्रतिक्रिया दी। आपको मानक मोबाइल सेवा का उपयोग करके प्रमाणित किया जाएगा।", "core.login.loggedoutssodescription": "आपको फिर से प्रमाणित करना होगा। आपको ब्राउज़र विंडो में साइट पर लॉग इन करना होगा", "core.login.login": "लॉग-इन", diff --git a/src/assets/lang/hr.json b/src/assets/lang/hr.json index 1a233af92..a74d1404d 100644 --- a/src/assets/lang/hr.json +++ b/src/assets/lang/hr.json @@ -1341,11 +1341,9 @@ "core.login.createuserandpass": "Stvori novo korisničko ime i lozinku s kojom se mogu prijaviti sustavu", "core.login.emailconfirmsent": "

Uskoro biste trebali primiti poruku e-poste na vasu adresu {{$a}}

\n

Poruka sadrzava jednostavne upute o daljnjem postupku registracije.

\n

Ako imate tehnickih problema s registracijom ili pitanja u vezi iste, posaljite poruku e-postom administratoru sustava.

", "core.login.emailconfirmsentsuccess": "Poruka potvrde registracije uspješno poslana", - "core.login.enterthewordsabove": "Unesite riječi ispisane iznad", "core.login.firsttime": "Ovdje ste prvi put?", "core.login.forcepasswordchangenotice": "Morate promijeniti svoju lozinku kako biste mogli nastaviti s radom.", "core.login.forgotten": "Zaboravili ste svoje korisničko ime ili lozinku?", - "core.login.getanothercaptcha": "Koristi drugu CAPTCHA", "core.login.help": "Pomoć", "core.login.instructions": "Upute", "core.login.invaliddate": "Neispravan datum", @@ -1590,7 +1588,14 @@ "core.submit": "Predaj", "core.success": "Uspješno", "core.tablet": "Tablet", + "core.tag.defautltagcoll": "Zadana zbirka", + "core.tag.inalltagcoll": "Svuda", + "core.tag.searchtags": "Pretraži oznake", "core.tag.tag": "Oznaka (tag)", + "core.tag.tagarea_course": "E-kolegiji", + "core.tag.tagarea_course_modules": "Aktivnosti i resursi", + "core.tag.tagarea_post": "Blog članci", + "core.tag.tagarea_user": "Interesi korisnika", "core.tag.tags": "Oznake", "core.teachers": "Nastavnici", "core.thereisdatatosync": "Postoje offline {{$a}} koje treba sinkronizirati.", diff --git a/src/assets/lang/hu.json b/src/assets/lang/hu.json index 22381574e..041c5b204 100644 --- a/src/assets/lang/hu.json +++ b/src/assets/lang/hu.json @@ -1374,11 +1374,9 @@ "core.login.createuserandpass": "Új felhasználónév és jelszó megadása", "core.login.emailconfirmsent": "Egy e-mailt kellett kapnia {{$a}} címére. A levél a regisztráció kitöltéséhez szükséges egyszerű teendőket tartalmazza. Ha továbbra is nehézségekbe ütközik, lépjen kapcsolatba a portál rendszergazdájával.", "core.login.emailconfirmsentsuccess": "A visszaigazoló e-mail elküldése sikerült", - "core.login.enterthewordsabove": "Írja le a fenti szavakat", "core.login.firsttime": "Most van itt először?", "core.login.forcepasswordchangenotice": "Továbblépéshez módosítsa jelszavát.", "core.login.forgotten": "Elfelejtette felhasználónevét vagy jelszavát?", - "core.login.getanothercaptcha": "Másik CAPTCHA beszerzése", "core.login.help": "Súgó", "core.login.helpmelogin": "

Több ezer Moodle-portál üzemel szerte a világban. Ez az alkalmazás csak azokhoz a portálokhoz képes kapcsolódni, amelyek lehetővé teszik ezt a mobil alkalmazás számára.

Ha gondot okoz a kapcsolódás, forduljon a Moodle azon rendszergazdájához, aki az adott portált üzemelteti, és kérje meg, hogy olvassa el a http://docs.moodle.org/en/Mobile_app weboldalon lévő információt

Az alkalmazás demó üzemmódú Moodle-portálon való teszteléséhez a Site URL mezőbe írja be a teacher vagy student szót, és kattintson az Add button gombra.

", "core.login.instructions": "Utasítások", @@ -1575,7 +1573,17 @@ "core.strftimetime24": "%H:%M", "core.submit": "Leadás", "core.success": "Sikerült", + "core.tag.defautltagcoll": "Alapgyűjtemény", + "core.tag.inalltagcoll": "Mindenhol", + "core.tag.itemstaggedwith": "{{$a.tagarea}} \"{{$a.tag}}\" címkével megjelölve", + "core.tag.notagsfound": "Nincs \"{{$a}}\" szerint megfelelő címke", + "core.tag.searchtags": "Címkék közötti keresés", + "core.tag.showingfirsttags": "{{$a}} legnépszerűbb címke megjelenítése", "core.tag.tag": "Címke", + "core.tag.tagarea_course": "Kurzusok", + "core.tag.tagarea_course_modules": "Tevékenységek és tananyagok", + "core.tag.tagarea_post": "Blogüzenetek", + "core.tag.tagarea_user": "Érdeklődési kör", "core.tag.tags": "Címkék", "core.teachers": "Tanárok", "core.thisdirection": "ltr", diff --git a/src/assets/lang/id.json b/src/assets/lang/id.json index cd6e46eab..b26844535 100644 --- a/src/assets/lang/id.json +++ b/src/assets/lang/id.json @@ -646,7 +646,6 @@ "core.comments.nocomments": "Tidak ada komentar", "core.comments.savecomment": "Simpan komentar", "core.commentscount": "Komentar ({{$a}})", - "core.commentsnotworking": "Komentar tidak dapat diambil", "core.confirmcanceledit": "Apakah Anda yakin ingin meninggalkan halaman ini? Semua perubahan akan hilang.", "core.confirmloss": "Apakah Anda yakin? Semua perubahan akan hilang", "core.confirmopeninbrowser": "Apakah Anda ingin membukanya di browse?", @@ -819,7 +818,6 @@ "core.login.authenticating": "Mengotentikasi", "core.login.cancel": "Batal", "core.login.changepassword": "Ubah password", - "core.login.checksiteversion": "Pastikan situs Anda menggunakan Moodle 2.4 atau yang terbaru.", "core.login.confirmdeletesite": "Apakah Anda yakin ingin menghapus situs {{sitename}}?", "core.login.connect": "Terhubung!", "core.login.connecttomoodle": "Terhubung ke Moodle", @@ -831,14 +829,12 @@ "core.login.emailconfirmsent": "

Sebuah email telah dikirimkan ke alamat Anda pada {{$a}}\n

yang berisi petunjuk ringkas untuk melengkapi pendaftaran Anda.\n

Jika Anda menemukan kesulitan, hubungi administrator situs ini.

", "core.login.emailconfirmsentsuccess": "Pengiriman surel konfirmasi sukses", "core.login.emailnotmatch": "Email tidak sesuai", - "core.login.enterthewordsabove": "Masukkan kata-kata di atas", "core.login.erroraccesscontrolalloworigin": "Panggilan Cross-Origin yang coba Anda lakukan telah ditolak. Harap periksa https://docs.moodle.org/dev/Moodle_Mobile_development_using_Chrome_or_Chromium", "core.login.errordeletesite": "Terjadi kesalahan saat menghapus situs. Silahkan coba lagi.", "core.login.errorupdatesite": "Terjadi kesalahan saat memperbaharui situs. Silahkan coba lagi.", "core.login.firsttime": "Ini yang pertama kali Anda kesini?", "core.login.forcepasswordchangenotice": "Anda harus mengubah kata sandi Anda untuk memproses lebih lanjut.", "core.login.forgotten": "Lupa nama pengguna dan password Anda?", - "core.login.getanothercaptcha": "Dapatkan CAPTCHA lainnya", "core.login.help": "Bantuan", "core.login.helpmelogin": "

Ada banyak situs Moodle di seluruh duinia. Aplikasi ini hanya bisa terhubung ke situs Moodle yang akses Aplikasi Mobilenya telah diaktifkan

Jika Anda tidak bisa terhubung ke situs Moodle Anda maka Anda butuh menghubungi administrator di tempat dimana Anda ingin terhubung dan menginginkan mereka untuk membacanya http://docs.moodle.org/en/Mobile_app

To test the app in a Moodle demo site type teacher or student in the Site address field and click the Connect button.

", "core.login.instructions": "Instruksi", diff --git a/src/assets/lang/it.json b/src/assets/lang/it.json index 553efe5b2..90406155a 100644 --- a/src/assets/lang/it.json +++ b/src/assets/lang/it.json @@ -1282,7 +1282,6 @@ "core.comments.nocomments": "Non ci sono commenti", "core.comments.savecomment": "Salva commento", "core.commentscount": "Commenti: ({{$a}})", - "core.commentsnotworking": "Non è possibile scaricare i commenti", "core.completion-alt-auto-fail": "Completato: {{$a}} (senza raggiungere la sufficienza)", "core.completion-alt-auto-n": "Non completata: {{$a}}", "core.completion-alt-auto-n-override": "Non completato: {{$a.modname}} (impostato da {{$a.overrideuser}})", @@ -1541,7 +1540,6 @@ "core.login.authenticating": "Autenticazione in corso", "core.login.cancel": "Annulla", "core.login.changepassword": "Cambia password", - "core.login.checksiteversion": "Verifica che il sito utilizzi Moodle 2.4 o versioni successive.", "core.login.confirmdeletesite": "Sei sicuro di eliminare il sito {{sitename}}?", "core.login.connect": "Collegati!", "core.login.connecttomoodle": "Collegati a Moodle", @@ -1550,14 +1548,12 @@ "core.login.credentialsdescription": "Per favore inserisci username e password per l'autenticazione", "core.login.emailconfirmsent": "

Una email è stata inviata al tuo indirizzo {{$a}}

\n

Contiene semplici istruzioni per completare la tua registrazione.

\n

Se hai qualche difficoltà contatta l'amministratore del sito.

", "core.login.emailconfirmsentsuccess": "L'email di conferma è stata inviata correttamente", - "core.login.enterthewordsabove": "Inserisci le parole sovrastanti", "core.login.erroraccesscontrolalloworigin": "La chiamata cross-origin che stai effettuando è stata rifiutata. Per ulteriori informazioni: https://docs.moodle.org/dev/Moodle_Mobile_development_using_Chrome_or_Chromium", "core.login.errordeletesite": "Si è verificato un errore durante l'eliminazione di questo sito. Per favore riprova.", "core.login.errorupdatesite": "Si è verificato un errore durante l'aggiornamento del token del sito.", "core.login.firsttime": "È la prima volta che accedi qui?", "core.login.forcepasswordchangenotice": "È necessario cambiare la password per proseguire.", "core.login.forgotten": "Hai dimenticato lo username o la password?", - "core.login.getanothercaptcha": "Chiedi un altro CAPTCHA", "core.login.help": "Aiuto", "core.login.helpmelogin": "

Esistono diverse migliaia di siti Moodle al mondo. Queta app può collegarsi solamente in quei siti dove è stato configurato l'accesso Mobile app.

Se non riesci a collegarti al sito Moodle desiderato devi contattare l'amministratore di quel sito e chiedergli di documentarsi su http://docs.moodle.org/en/Mobile_app

Per provare l'applicazione su un sito Moodle demo inserire teacher oppure student nel campo Username e fare click sul pulsante Aggiungi.

", "core.login.instructions": "Istruzioni", @@ -1815,7 +1811,17 @@ "core.submit": "Invia", "core.success": "Operazione eseguita correttamente", "core.tablet": "Tablet", + "core.tag.defautltagcoll": "Raccolta di default", + "core.tag.inalltagcoll": "Ovunque", + "core.tag.itemstaggedwith": "{{$a.tagarea}} è stata associata a \"{{$a.tag}}\"", + "core.tag.notagsfound": "Non sono stati trovati tag corrispondenti a {{$a}}\"", + "core.tag.searchtags": "Cerca tag", + "core.tag.showingfirsttags": "Visualizzazione dei {{$a}} più utilizzati", "core.tag.tag": "Tag", + "core.tag.tagarea_course": "Corsi", + "core.tag.tagarea_course_modules": "Attività e risorse", + "core.tag.tagarea_post": "Intervento blog", + "core.tag.tagarea_user": "Interessi dell'utente", "core.tag.tags": "Tag", "core.teachers": "Docenti", "core.thisdirection": "ltr", diff --git a/src/assets/lang/ja.json b/src/assets/lang/ja.json index 550aa6d19..6825e7959 100644 --- a/src/assets/lang/ja.json +++ b/src/assets/lang/ja.json @@ -1312,7 +1312,6 @@ "core.comments.nocomments": "コメントなし", "core.comments.savecomment": "コメントを保存する", "core.commentscount": "コメント ({{$a}})", - "core.commentsnotworking": "コメントが取得できませんでした", "core.completion-alt-auto-fail": "完了: {{$a}} (合格点未到達)", "core.completion-alt-auto-n": "未完了: {{$a}}", "core.completion-alt-auto-n-override": "未完了: {{$a.modname}} ({{$a.overrideuser}} による設定)", @@ -1567,11 +1566,9 @@ "core.login.createuserandpass": "あなたのユーザ名 およびパスワードを入力してください。", "core.login.emailconfirmsent": "

あなたの {{$a}} のメールアドレス宛にメールが送信されました。

\n

メールには登録を確認するための簡単な説明が記載されています。

\n

分からない場合、サイト管理者にご連絡ください。

", "core.login.emailconfirmsentsuccess": "確認メールが正常に送信されました。", - "core.login.enterthewordsabove": "上記の言葉を入力してください。", "core.login.firsttime": "はじめての方ですか?", "core.login.forcepasswordchangenotice": "続けるにはパスワードを変更してください。", "core.login.forgotten": "あなたのユーザ名またはパスワードを忘れましたか?", - "core.login.getanothercaptcha": "別のCAPTCHAを取得する", "core.login.help": "ヘルプ", "core.login.helpmelogin": "

世界中に無数のMoodleサイトが存在しますが、本アプリはモバイルアプリからのアクセスを有効にした特別なMoodleサイトでなければ接続できません。

あなたのMoodleサイトにアプリから接続できず、それをできるようにしてもらいたいときには、Moodleサイトの管理ユーザにhttp://docs.moodle.org/en/Mobile_appを読むよう依頼してみてください。

Moodleでもサイトでアプリをテストするには、サイトURLの欄にteacherあるいはstudentを入力し、追加ボタンを押してください。

", "core.login.instructions": "説明", @@ -1786,7 +1783,17 @@ "core.submit": "送信", "core.success": "成功", "core.tablet": "タブレット", + "core.tag.defautltagcoll": "デフォルトコレクション", + "core.tag.inalltagcoll": "どこでも", + "core.tag.itemstaggedwith": "「 {{$a.tag}} 」がタグ付けされた {{$a.tagarea}}", + "core.tag.notagsfound": "「 {{$a}} 」に合致するタグは見つかりませんでした。", + "core.tag.searchtags": "タグを検索する", + "core.tag.showingfirsttags": "{{$a}} 件の最も人気のあるタグを表示中", "core.tag.tag": "タグ", + "core.tag.tagarea_course": "コース", + "core.tag.tagarea_course_modules": "活動およびリソース", + "core.tag.tagarea_post": "ブログ記事", + "core.tag.tagarea_user": "ユーザの興味のあること", "core.tag.tags": "タグ", "core.teachers": "教師", "core.thereisdatatosync": "同期が必要なオフライン {{$a}} があります。", diff --git a/src/assets/lang/km.json b/src/assets/lang/km.json index 1fee91a1c..1572d4e10 100644 --- a/src/assets/lang/km.json +++ b/src/assets/lang/km.json @@ -1233,7 +1233,6 @@ "core.comments.comments": "មតិ", "core.comments.commentscount": "មតិ ({{$a}})", "core.commentscount": "មតិ ({{$a}})", - "core.commentsnotworking": "មតិយោបល់មិនអាចទាញយកមកបាន", "core.completion-alt-auto-fail": "បានបញ្ចប់៖ {{$a}} (មិនបានទទួលពិន្ទុជាប់)", "core.completion-alt-auto-n": "មិនបានបញ្ចប់៖ {{$a}}", "core.completion-alt-auto-n-override": "មិនបានបញ្ចប់៖ {{$a.modname}} (បានកំណត់ដោយ {{$a.overrideuser}})", @@ -1434,7 +1433,6 @@ "core.login.authenticating": "កំពុងផ្ទៀងផ្ទាត់", "core.login.cancel": "បោះបង់", "core.login.changepassword": "ផ្លាស់ប្ដូរពាក្យសម្ងាត់", - "core.login.checksiteversion": "ពិនិត្យមើលថាគេហទំព័ររបស់អ្នកប្រើប្រាស់ Moodle 2.4 ឬក៏ជំនាន់ក្រោយៗមកទៀត", "core.login.confirmdeletesite": "តើអ្នកប្រាកដទេថាអ្នកចង់លុបគេហទំព័រគេហទំព័រនេះ {{sitename}}?", "core.login.connect": "ភ្ជាប់!", "core.login.connecttomoodle": "ភ្ជាប់ទៅ Moodle", @@ -1446,7 +1444,6 @@ "core.login.emailconfirmsent": "

អ៊ីមែលមួយត្រូវបានផ្ញើទៅកាន់អាសយដ្ឋានរបស់អ្នកនៅ {{$a}}

\n

វាមានសេចក្ដីណែនាំងាយៗ ដើម្បីបញ្ចប់ការចុះឈ្មោះរបស់អ្នក ។

\n

ប្រសិនបើអ្នកនៅតែមានការលំបាក សូមទាក់ទងអ្នកគ្រប់គ្រងតំបន់បណ្ដាញ ។

", "core.login.emailconfirmsentnoemail": "

មានអ៊ីមែលមួយបានផ្ញើទៅកាន់អសយដ្ឋានរបស់អ្នកគឺ{{$a}}

វាមានការណែនាំងាយៗដើម្បីបញ្ចប់ការចុះឈ្មោះរបស់អ្នក។

បើអ្នកបន្តជួបការលំបាកសូមទាក់ទងអ្នកគ្រប់គ្រងគេហទំព័រ។

'", "core.login.emailnotmatch": "អាសយដ្ឋានអ៊ីមែលមិនត្រូវគ្នា", - "core.login.enterthewordsabove": "បញ្ចូលពាក្យខាងលើ", "core.login.erroraccesscontrolalloworigin": "ការទូរសព្ទហៅឆ្លងដោយផ្ទាល់ពីអ្នកម្នាក់ ត្រូវបានបដិសេធ។ សូមពិនិត្យមើលឯកសារណែនាំ https://docs.moodle.org/dev/Moodle_Mobile_development_using_Chrome_or_Chromium", "core.login.errordeletesite": "កំហុសមួយបានកើតឡើងនៅពេលលុបគេហទំព័រនេះ។ សូមព្យាយាមម្តងទៀត", "core.login.errorupdatesite": "កំហុសមួយបានកើតឡើងខណៈពេលធ្វើបច្ចុប្បន្នភាពនៅលើគេហទំព័រ", @@ -1454,7 +1451,6 @@ "core.login.firsttime": "តើអ្នកមកទីនេះជាលើកដំបូងឬ ?", "core.login.forcepasswordchangenotice": "អ្នកត្រូវតែផ្លាស់ប្ដូរពាក្យសម្ងាត់របស់អ្នកដើម្បីបន្ត ។", "core.login.forgotten": "ភ្លេចពាក្យសម្ងាត់ ឬឈ្មោះអ្នកប្រើរបស់អ្នកឬ ?", - "core.login.getanothercaptcha": "យក CAPTCHA មួយផ្សេងទៀត", "core.login.help": "ជំនួយ", "core.login.helpmelogin": "

មានគេហទំព័រ Moodle រាប់ពាន់នៅជុំវិញពិភពលោក។ កម្មវិធីនេះអាចភ្ជាប់ទៅតែគេហទំព័រ Moodle ដែលបានបើកការចូលដំណើរការកម្មវិធីទូរសព្ទ។

ប្រសិនបើអ្នកមិនអាចភ្ជាប់ទៅគេហទំព័រ Moodle របស់អ្នកបានទេអ្នកចាំបាច់ត្រូវទាក់ទងអ្នកគ្រប់គ្រងតំបន់បណ្ដាញរបស់អ្នកហើយសុំឱ្យពួកគេអានhttp://docs.moodle.org/en/Mobile_app

ដើម្បីសាកប្រើកម្មវិធី Moodle demo គ្រូ or សិស្ស in the អាសយដ្ឋានគេហទំព័រ ចន្លោះ ចុច ភ្ជាប់.

", "core.login.instructions": "សេចក្ដីណែនាំ", @@ -1466,9 +1462,6 @@ "core.login.invalidtime": "ពេលវេលាមិនត្រឹមត្រូវ", "core.login.invalidvaluemax": "តម្លៃអតិបរមាគឺ {{$a}}", "core.login.invalidvaluemin": "តម្លៃអប្បបរមាគឺ {{$a}}", - "core.login.legacymoodleversion": "អ្នកកំពុងព្យាយាមភ្ជាប់ទៅកំណែ Moodle ដែលមិនត្រួវបានគាំទ្រ។ សូមទាញយកកម្មវិធី Moodle Classic ដើម្បីចូលប្រើគេហទំព័រ Moodle នេះ។", - "core.login.legacymoodleversiondesktop": "អ្នកកំពុងព្យាយាមភ្ជាប់ទៅ {{$a}}.

ទំព័រនេះកំពុងស្ថិតនៅកំណែហួសសម័យកាល ហើយមិនអាចប្រើជាមួយកំណែ Moodle Desktopទេ

ប្រសិនបើទំព័រនេះជាទំព័ររបស់អ្នក សូមទាក់ទងដៃគូរ moodleក្នុងតំបន់របស់អ្នកដើម្បីជួយធ្វើបច្ចុប្បន្ននៃទំព័រនេះ

សូមអាន ទាក់ទងមកយើង ដើម្បីសុំជំនួយ", - "core.login.legacymoodleversiondesktopdownloadold": "

អ្នកនៅតែអាចប្រើកម្មវិធីជំនាន់មុនៗបានដោយទាញយកវានៅទីនេះ។", "core.login.localmobileunexpectedresponse": "ការត្រួតពិនិត្យលក្ខណៈពិសេសបន្ថែមរបស់ Moodle Mobile បានត្រឡប់មកវិញនូវការឆ្លើយតបដែលមិនបានរំពឹងទុក។ អ្នកនឹងត្រូវបានផ្ទៀងផ្ទាត់ភាពត្រឹមត្រូវដោយប្រើសេវាទូរសព្ទស្តង់ដារ។", "core.login.loggedoutssodescription": "អ្នកត្រូវតែផ្ទៀងផ្ទាត់ម្តងទៀត។ អ្នកចាំបាច់ត្រូវចូលទៅក្នុងគេហទំព័រនៅក្នុងផ្ទាំងបើកកម្មវិធីអ៊ីនធឺណិត។", "core.login.login": "ចូល", @@ -1728,7 +1721,17 @@ "core.submit": "ដាក់ស្នើ", "core.success": "ជោគជ័យ", "core.tablet": "ថេប្លេត", + "core.tag.defautltagcoll": "កម្រងលំនាំដើម", + "core.tag.inalltagcoll": "គ្រប់កន្លែង", + "core.tag.itemstaggedwith": "{{$a.tagarea}} ដែលបានដាក់ស្លាក \"{{$a.tag}}\"", + "core.tag.notagsfound": "រកមិនឃើញស្លាកដែលផ្គូផ្គងនឹង \"{{$a}}\"", + "core.tag.searchtags": "ស្វែងរកស្លាក", + "core.tag.showingfirsttags": "បង្ហាញស្លាក {{$a}} ដែលពេញនិយមបំផុត", "core.tag.tag": "ស្លាក", + "core.tag.tagarea_course": "វគ្គសិក្សា", + "core.tag.tagarea_course_modules": "សកម្មភាព និងធនធាន", + "core.tag.tagarea_post": "ប្រកាសនៃកំណត់ហេតុបណ្តាញ", + "core.tag.tagarea_user": "ចំណូលចិត្តរបស់អ្នកប្រើ", "core.tag.tags": "ស្លាក", "core.teachers": "គ្រូ", "core.thereisdatatosync": "គ្មានអ៊ីនធឺណិត{{$a}} ដែលត្រូវធ្វើសមកាលកម្ម", diff --git a/src/assets/lang/kn.json b/src/assets/lang/kn.json index 1af71ae50..7b315c538 100644 --- a/src/assets/lang/kn.json +++ b/src/assets/lang/kn.json @@ -211,7 +211,6 @@ "core.comments.commentscount": "ಟಿಪ್ಪಣಿಗಳು({{$a}})", "core.comments.nocomments": "ಟಿಪ್ಪಣಿಗಳಿಲ್ಲ", "core.commentscount": "ಟಿಪ್ಪಣಿಗಳು({{$a}})", - "core.commentsnotworking": "ಅಭಿಪ್ರಾಯಗಳನ್ನು ಮರುಕಳಿಸಲಾಗುವುದಿಲ್ಲ", "core.confirmcanceledit": "ನೀವು ಈ ಪುಟದಿಂದ ಹೊರಹೋಗಲು ಇಚ್ಛಿಸುತ್ತೀರೆ? ಎಲ್ಲಾ ಬದಲಾವಣೆಗಳು ಅಳಿಸಿಹೋಗುತ್ತವೆ.", "core.confirmloss": "ನೀವು ಈ ಪುಟದಿಂದ ಹೊರಹೋಗಲು ಇಚ್ಛಿಸುತ್ತೀರೆ? ಎಲ್ಲಾ ಬದಲಾವಣೆಗಳು ಅಳಿಸಿಹೋಗುತ್ತವೆ.", "core.confirmopeninbrowser": "ನೀವು ಇದನ್ನು ವೆಬ್‌ ಬ್ರೌಸರ್‌ನಲ್ಲಿ ತೆರೆಯಲು ಇಚ್ಛಿಸುತ್ತೀರೆ?", @@ -332,7 +331,6 @@ "core.location": "ಕ್ಷೇತ್ರ", "core.login.authenticating": "ದೃಢೀಕರಿಸಲಾಗುತ್ತಿದೆ", "core.login.changepassword": "ಗುಪ್ತಪದವನ್ನು ಬದಲಾಯಿಸು", - "core.login.checksiteversion": "ನಿಮ್ಮ ತಾಣವು ಮೂಡಲ್‌ 2.4 ಅಥವಾ ನಂತರದ ಆವೃತ್ತಿಯನ್ನು ಬಳಸುತ್ತಿದೆಯೆಂದು ಪರೀಕ್ಷಿಸಿ", "core.login.confirmdeletesite": "ನೀವು {{sitename}} ತಾಣವನ್ನು ಅಳಿಸಲು ಇಚ್ಛಿಸುತ್ತೀರೆ?", "core.login.connect": "ಸಂಪರ್ಕ !", "core.login.connecttomoodle": "ಮೂಡಲ್‌ಗೆ ಸಂಪರ್ಕಿಸಿ", @@ -340,7 +338,6 @@ "core.login.contactyouradministratorissue": "ಈ ಸಮಸ್ಯೆಯನ್ನು ಪರೀಕ್ಷಿಸಲು ನಿಮ್ಮ ತಾಣದ ಆಡಳಿತಗಾರರಿಗೆ ತಿಳಿಸಿ : {{$a}}", "core.login.credentialsdescription": "ಒಳಹೋಗಲು ದಯಮಾಡಿ ನಿಮ್ಮ ಬಳಕೆದಾರರ ಹೆಸರು ಹಾಗು ಗುಪ್ತಪದವನ್ನು ನೀಡಿರಿ", "core.login.emailnotmatch": "ಮಿಂಚಂಚೆಗಳು ಹೊಂದಿಕೆಯಾಗುತ್ತಿಲ್ಲ", - "core.login.enterthewordsabove": "ಮೇಲಿನ ಪದಗಳನ್ನು ನಮೂದಿಸಿ", "core.login.errordeletesite": "ಈ ತಾಣವನ್ನು ಅಳಿಸುವಾಗ ತಪ್ಪಾಯಿತು. ದಯವಿಟ್ಟು ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ.", "core.login.errorupdatesite": "ತಾಣದ ಟೋಕನ್‌ ಅನ್ನು ನವೀಕರಿಸುವಾಗ ತಪ್ಪಾಯಿತು.", "core.login.invalidaccount": "ನಿಮ್ಮ ಲಾಗ್‌ಇನ್‌ ಮಾಹಿತಿಯನ್ನು ಪರೀಕ್ಷಿಸಿ ಅಥವಾ ನಿಮ್ಮ ತಾಣದ ಆಡಳಿತಗಾರರನ್ನು ತಾಣದ ಆಕೃತಿಯನ್ನು ಪರೀಕ್ಷಿಸಲು ಹೇಳಿ.", @@ -445,6 +442,7 @@ "core.strftimerecentfull": "%a, %d %b %Y, %I:%M %p", "core.strftimetime": "%I:%M %p", "core.tablet": "ಟ್ಯಾಬ್ಲೆಟ್", + "core.tag.tagarea_course_modules": "ಚಟುವಟಿಕೆಗಳು ಮತ್ತು ಸಂಪನ್ಮೂಲಗಳು", "core.tag.tags": "ಕುಣಿಕೆಗಳು", "core.teachers": "ಶಿಕ್ಷಕರು", "core.thereisdatatosync": "ಕೆಲವು ಆಫ್‌ಲೈನ್‌ {{$a}} ನವೀಕರಿಸಬೇಕು", diff --git a/src/assets/lang/ko.json b/src/assets/lang/ko.json index ed3788b83..afdd94047 100644 --- a/src/assets/lang/ko.json +++ b/src/assets/lang/ko.json @@ -1043,7 +1043,6 @@ "core.comments.nocomments": "덧글 없음", "core.comments.savecomment": "덧글 저장", "core.commentscount": "댓글 ({{$a}})", - "core.commentsnotworking": "댓글을 검색 할 수 없습니다.", "core.completion-alt-auto-fail": "이수함(통과 성적을 획득하지 못함)", "core.completion-alt-auto-n": "미이수", "core.completion-alt-auto-pass": "이수함(통과 성적 획득)", @@ -1232,7 +1231,6 @@ "core.login.authenticating": "인증 중", "core.login.cancel": "취소", "core.login.changepassword": "비밀번호 변경", - "core.login.checksiteversion": "귀하의 사이트가 Moodle 2.4 이상을 사용하고 있는지 확인하십시오.", "core.login.confirmdeletesite": "{{sitename}} 사이트를 삭제 하시겠습니까?", "core.login.connect": "연결", "core.login.connecttomoodle": "무들 연결", @@ -1243,14 +1241,12 @@ "core.login.credentialsdescription": "로그인하려면 사용자 이름과 비밀번호를 입력하십시오.", "core.login.emailconfirmsent": "

당신의 이메일 주소인 {{$a}}로 메일이 갔습니다.

\n

등록을 마치기 위한 간단한 안내문이 포함되어 있습니다.

", "core.login.emailnotmatch": "이메일이 일치하지 않습니다.", - "core.login.enterthewordsabove": "위의 단어를 입력하시오", "core.login.erroraccesscontrolalloworigin": "수행하려는 교차 원점 호출이 거부되었습니다. https://docs.moodle.org/dev/Moodle_Mobile_development_using_Chrome_or_Chromium을 확인하십시오.", "core.login.errordeletesite": "이 사이트를 삭제하는 중에 오류가 발생했습니다. 다시 시도하십시오.", "core.login.errorupdatesite": "사이트 토큰을 업데이트하는 중 오류가 발생했습니다.", "core.login.firsttime": "이곳에 처음 오셨나요?", "core.login.forcepasswordchangenotice": "계속하려면 비밀번호를 바꿔야만 함", "core.login.forgotten": "사용자 아이디나 비밀번호를 잊으셨습니까?", - "core.login.getanothercaptcha": "다른 CAPTCHA 얻기", "core.login.help": "도움", "core.login.helpmelogin": "

전 세계에는 수천 개의 무들 사이트가 있습니다. 이 앱은 모바일 앱 액세스가 가능한 무들 사이트에만 연결할 수 있습니다.

무들 사이트에 연결할 수 없는 경우 사이트 관리자에게 문의하여 다음을 읽어보도록 해주세요. http://docs.moodle.org/ko/Mobile_app

앱을 테스트하려면 사이트 주소 입력란에 무들 데모 사이트 유형 선생님 또는 학생 을 입력하고 연결 버튼 을 클릭하십시오.

>", "core.login.instructions": "안내문", @@ -1492,6 +1488,7 @@ "core.submit": "제출", "core.success": "성공", "core.tablet": "태블릿", + "core.tag.searchtags": "태그 검색", "core.tag.tag": "태그", "core.tag.tags": "태그", "core.teachers": "선생님", diff --git a/src/assets/lang/lt.json b/src/assets/lang/lt.json index 16eb9a262..a095cbb00 100644 --- a/src/assets/lang/lt.json +++ b/src/assets/lang/lt.json @@ -1262,7 +1262,6 @@ "core.comments.nocomments": "Jokių komentarų", "core.comments.savecomment": "Įrašyti komentarą", "core.commentscount": "Komentarai ({{$a}})", - "core.commentsnotworking": "Komentarų negalima ištaisyti", "core.completion-alt-auto-fail": "Užbaigta: {{$a}} (negautas išlaikymo įvertis)", "core.completion-alt-auto-n": "Nebaigta: {{$a}}", "core.completion-alt-auto-n-override": "Nebaigta: {{$a.modname}} (pažymėjo {{$a.overrideuser}})", @@ -1454,7 +1453,6 @@ "core.login.authenticating": "Autentifikuojama", "core.login.cancel": "Atšaukti", "core.login.changepassword": "Pakeisti slaptažodį", - "core.login.checksiteversion": "Patikrintkite, ar svetainė naudoja Moodle 2.4. arba vėlesnę versiją.", "core.login.confirmdeletesite": "Ar tikrai norite ištrinti svetainę {{sitename}}?", "core.login.connect": "Prisijungta!", "core.login.connecttomoodle": "Prisijungti prie Moodle", @@ -1466,14 +1464,12 @@ "core.login.emailconfirmsent": "

El. laiškas išsiųstas jūsų adresu {{$a}}

.

Jame pateikti paprasti nurodymai, kaip užbaigti registraciją.

Jei iškils kokių sunkumų, kreipkitės į svetainės administratorių.

", "core.login.emailconfirmsentsuccess": "Patvirtinimo el. laiškas išsiųstas sėkmingai", "core.login.emailnotmatch": "El. paštas nesutampa", - "core.login.enterthewordsabove": "Įvesti aukščiau rodomus žodžius", "core.login.erroraccesscontrolalloworigin": "Kryžminis veiksmas buvo atmestas. Patikrinkite: https://docs.moodle.org/dev/Moodle_Mobile_development_using_Chrome_or_Chromium", "core.login.errordeletesite": "Klaida trinant šią svetainę. Prašome mėginti vėl.", "core.login.errorupdatesite": "Klaida atnaujinant svetainės kodą.", "core.login.firsttime": "Ar jūs čia pirmą kartą?", "core.login.forcepasswordchangenotice": "Norėdami tęsti, turite pakeisti slaptažodį.", "core.login.forgotten": "Pamiršote savo naudotojo vardą ar slaptažodį?", - "core.login.getanothercaptcha": "Gauti kitą CAPTCHA", "core.login.help": "Žinynas", "core.login.helpmelogin": "

Visame pasaulyje Moodle svetainių yra labai daug. Ši programėlė gali prisijungti prie Moodle svetainių, turinčių specialią Moodle programėlių prieigą.

Jeigu negalite prisijungti, praneškite apie tai Moodle administratoriui ir paprašykite perskaityti http://docs.moodle.org/en/Mobile_app

Norėdami išbandyti programėlės demo versiją surinkite teacher (dėstytojui) ar student (besimokančiajam) Svetainės adreso lauke ir paspauskite Prisijungti mygtuką.

", "core.login.instructions": "Instrukcijos", @@ -1747,7 +1743,17 @@ "core.submit": "Pateikti", "core.success": "Sėkmingai", "core.tablet": "Planšetė", + "core.tag.defautltagcoll": "Numatytas rinkinys", + "core.tag.inalltagcoll": "Visur", + "core.tag.itemstaggedwith": "{{$a.tagarea}} pažymėta su \"{{$a.tag}}\"", + "core.tag.notagsfound": "Nerasta jokių žymių atitiktimi \"{{$a}}\"", + "core.tag.searchtags": "Ieškoti žymių", + "core.tag.showingfirsttags": "Rodomos {{$a}} populiariausios žymės", "core.tag.tag": "Žymė", + "core.tag.tagarea_course": "Kursai", + "core.tag.tagarea_course_modules": "Veiklos ir ištekliai", + "core.tag.tagarea_post": "Tinklaraščio įrašai", + "core.tag.tagarea_user": "Naudotojo interesai", "core.tag.tags": "Žymės", "core.teachers": "Dėstytojai", "core.thereisdatatosync": "Neprijungtas {{$a}}, kad sinchronizuotų.", diff --git a/src/assets/lang/lv.json b/src/assets/lang/lv.json index 0fc45cfee..8b2a70e1b 100644 --- a/src/assets/lang/lv.json +++ b/src/assets/lang/lv.json @@ -1383,7 +1383,6 @@ "core.login.authenticating": "Autentificēšana", "core.login.cancel": "Atcelt", "core.login.changepassword": "Mainīt paroli", - "core.login.checksiteversion": "Pārbaudiet, vai jūsu vietne izmanto Moodle 2.4 vai jaunāku versiju.", "core.login.confirmdeletesite": "Vai tiešām vēlaties dzēst vietni {{sitename}}?", "core.login.connect": "Savienoties!", "core.login.connecttomoodle": "Savienoties ar Moodle", @@ -1414,9 +1413,6 @@ "core.login.invalidurl": "Norādīta nederīga saite", "core.login.invalidvaluemax": "Maksimālā vērtība ir {{$a}}", "core.login.invalidvaluemin": "Minimālā vērtība ir {{$a}}", - "core.login.legacymoodleversion": "Jūs mēģināt izveidot savienojumu ar neatbalstītu Moodle versiju. Lūdzu, lejupielādējiet Moodle Classic lietotni, lai piekļūtu šai Moodle vietnei.", - "core.login.legacymoodleversiondesktop": "Jūs mēģināt izveidot savienojumu ar {{$ a}} .

Šī vietne darbojas ar novecojušu neatbalstītu Moodle versiju, kas nedarbosies ar šo Moodle darbvirsmas lietotni. << br> Ja šī ir jūsu vietne, lūdzu, sazinieties ar vietējo partneri, lai saņemtu palīdzību, lai to atjauninātu.

Skatiet mūsu kontaktu lapu , lai iesniegt palīdzības pieprasījumu.", - "core.login.legacymoodleversiondesktopdownloadold": "

Alternatīvi, jūs joprojām varat piekļūt šai vietnei, izmantojot neatbalstītu lietotnes versiju, kuru var lejupielādēt no šejienes.", "core.login.localmobileunexpectedresponse": "Moodle Mobile papildu funkciju pārbaude atgriezās negaidīti. Jūs autentificēs, izmantojot standarta mobilo pakalpojumu.", "core.login.loggedoutssodescription": "Jums vēlreiz jāautentificējas. Jums ir jāpiesakās vietnē pārlūkprogrammas logā.", "core.login.login": "Pieslēgties", @@ -1682,7 +1678,17 @@ "core.submit": "Iesniegt", "core.success": "Sekmīgi", "core.tablet": "Planšete", + "core.tag.defautltagcoll": "Noklusējuma kolekcija", + "core.tag.inalltagcoll": "Visur", + "core.tag.itemstaggedwith": "{{$a.tagarea}} tagots ar \"{{$a.tag}}\"", + "core.tag.notagsfound": "Nav atrasti tagi, kas atbilstu \"{{$a}}\"", + "core.tag.searchtags": "Meklēt tagus", + "core.tag.showingfirsttags": "Rāda {{$a}} populārākos tagus", "core.tag.tag": "Tags", + "core.tag.tagarea_course": "Kursi", + "core.tag.tagarea_course_modules": "Aktivitātes un resursi", + "core.tag.tagarea_post": "Blogu ieraksti", + "core.tag.tagarea_user": "Lietotāju intereses", "core.tag.tags": "Tagi", "core.teachers": "Pasniedzēji", "core.thereisdatatosync": "Sinhronizācijai ir bezsaistē {{$a}}.", diff --git a/src/assets/lang/mr.json b/src/assets/lang/mr.json index 019783b95..d08d61275 100644 --- a/src/assets/lang/mr.json +++ b/src/assets/lang/mr.json @@ -654,7 +654,6 @@ "core.choose": "निवडा", "core.clearsearch": "शोध साफ करा", "core.clicktoseefull": "संपूर्ण सामग्री पाहण्यासाठी क्लिक करा.", - "core.commentsnotworking": "टिप्पण्या पुनर्प्राप्त करणे शक्य नाही", "core.confirmcanceledit": "आपली खात्री आहे की आपण हे पृष्ठ सोडू इच्छिता? सर्व बदल गमावले जातील.", "core.confirmloss": "तुम्हाला खात्री आहे? सर्व बदल गमावले जातील.", "core.confirmopeninbrowser": "आपण ते ब्राउझरमध्ये उघडू इच्छिता?", @@ -814,7 +813,6 @@ "core.login.authenticating": "प्रमाणीकरण करीत आहे", "core.login.cancel": "रद्द करा", "core.login.changepassword": "पासवर्ड बदला", - "core.login.checksiteversion": "आपली साइट Moodle 2.4 किंवा नंतर वापरते हे तपासा.", "core.login.confirmdeletesite": "Are you sure you want to delete the site {{sitename}}?", "core.login.connect": "कनेक्ट व्हा!", "core.login.connecttomoodle": "मूडलला जोडणी करा", @@ -825,14 +823,12 @@ "core.login.credentialsdescription": "कृपया लॉग इन करण्यासाठी आपले वापरकर्तानाव आणि संकेतशब्द प्रदान करा.", "core.login.emailconfirmsent": "

आपल्या पत्त्यावर ईमेल {{$a}}

येथे पाठवला गेला पाहीजे कारण त्यात आपली नोंदणी पूर्ण करण्यासाठी सुलभ सूचना आहेत.

जर आपल्याला समस्या येणे चालूच राहते, साइट प्रशासकाशी संपर्क साधा. ", "core.login.emailnotmatch": "ईमेल जुळत नाहीत", - "core.login.enterthewordsabove": "वरील शब्द द्या.", "core.login.erroraccesscontrolalloworigin": "आपण पार करण्याचा प्रयत्न करत असलेला क्रॉस-ओरिजिन कॉल नाकारला गेला आहे. कृपया https://docs.moodle.org/dev/Moodle_Mobile_development_using_Chrome_or_Chromium तपासा", "core.login.errordeletesite": "ही साइट हटविताना त्रुटी आली. कृपया पुन्हा प्रयत्न करा.", "core.login.errorupdatesite": "साइटचे टोकन अद्यतनित करताना एक त्रुटी आली.", "core.login.firsttime": "तुम्ही इथे पहील्यांदा आहात का?", "core.login.forcepasswordchangenotice": "पुढे जाण्यासाठी तुम्ही तुमचा पासवर्ड बदलला पाहीजे", "core.login.forgotten": "युजरनेम किंवा पासवर्ड विसरला आहात?", - "core.login.getanothercaptcha": "दुसरे CAPTCHA मिळवा.", "core.login.help": "मदत", "core.login.helpmelogin": "

जगभरातील हजारो मूडल साइट्स आहेत. हा अॅप केवळ मूडल साइटशी कनेक्ट करु शकतो ज्याने विशेषतः मोबाईल अॅप्स प्रवेश सक्षम केला आहे.

आपण आपल्या मूडल साइटशी कनेक्ट करू शकत नसल्यास आपण जिथे कनेक्ट करू इच्छिता त्या स्थानावर मूडल प्रशासकाशी संपर्क साधण्याची आणि http://docs.moodle.org/en/Mobile_app

वाचण्यासाठी त्यांना विचारा साइट पत्ता फील्डमध्ये मूडलच्या डेमो साइट प्रकार teacher किंवा student मध्ये site address कनेक्ट बटण क्लिक करा . ", "core.login.instructions": "सूचना", @@ -1033,6 +1029,7 @@ "core.submit": "सबमीट", "core.success": "यश", "core.tablet": "टॅब्लेट", + "core.tag.searchtags": "टॅग शोधा", "core.tag.tag": "टॅग", "core.tag.tags": "टॅगस", "core.teachers": "शिक्षक", diff --git a/src/assets/lang/nl.json b/src/assets/lang/nl.json index db758f20f..61fdcf152 100644 --- a/src/assets/lang/nl.json +++ b/src/assets/lang/nl.json @@ -1353,7 +1353,6 @@ "core.comments.savecomment": "Bewaar notitie", "core.comments.warningcommentsnotsent": "Kan reacties niet synchroniseren. {{error}}", "core.commentscount": "Opmerkingen ({{$a}})", - "core.commentsnotworking": "Opmerkingen konden niet opgehaald worden", "core.completion-alt-auto-fail": "Voltooid: {{$a}} (bereikte het cijfer voor geslaagd niet)", "core.completion-alt-auto-n": "Niet voltooid: {{$a}}", "core.completion-alt-auto-n-override": "Niet voltooid: {{$a.modname}} (ingesteld door {{$a.overrideuser}})", @@ -1671,7 +1670,6 @@ "core.login.changepasswordinstructions": "Je kunt je wachtwoord niet wijzigen in de app. Klik op onderstaande knop om de site te openen in een browser om je wachtwoord te wijzigen. Sluit je browser na het wijzigen van je wachtwoord, want je zult niet automatisch terug naar de app gestuurd worden.", "core.login.changepasswordlogoutinstructions": "Als je naar een andere site wil of wil afmelden, klik dan volgende knop:", "core.login.changepasswordreconnectinstructions": "Klik op volgende knop om opnieuw te verbinden met de site. (Je zult terug naar het vorige scherm gebracht worden als je je wachtwoord fout ingeeft.)", - "core.login.checksiteversion": "Controleer of je site minstens Moodle 2.4 of nieuwe gebruikt.", "core.login.confirmdeletesite": "Weet je zeker dat je de site {{sitename}} wil verwijderen?", "core.login.connect": "Verbinden!", "core.login.connecttomoodle": "Verbinden met Moodle", @@ -1686,7 +1684,6 @@ "core.login.emailconfirmsentnoemail": "

Er zou een e-mail naar jouw adres verstuurd moeten zijn.

Die bevat makkelijke instructies om je registratie te voltooien.

Als je blijft problemen ondervinden, neem dan contact op met je site-beheerder.

", "core.login.emailconfirmsentsuccess": "Bevestigingsmail is verstuurd", "core.login.emailnotmatch": "E-mailadressen komen niet overeen", - "core.login.enterthewordsabove": "Vul hier bovenstaande woorden in", "core.login.erroraccesscontrolalloworigin": "De Cross-Origin call die je probeerde uit te voeren, werd geweigerd. Controleer https://docs.moodle.org/dev/Moodle_Mobile_development_using_Chrome_or_Chromium", "core.login.errordeletesite": "Er is een fout opgetreden bij het verwijderen van deze site. Probeer opnieuw.", "core.login.errorupdatesite": "Er is een fout opgetreden bij het updaten van het token van deze site.", @@ -1694,7 +1691,6 @@ "core.login.firsttime": "Is dit de eerste keer dat je hier bent?", "core.login.forcepasswordchangenotice": "Je moet je wachtwoord wijzigen om verder te kunnen gaan", "core.login.forgotten": "Ben je je gebruikersnaam of wachtwoord vergeten?", - "core.login.getanothercaptcha": "Vraag een andere CAPTCHA", "core.login.help": "Help", "core.login.helpmelogin": "

Deze app kan alleen verbinding maken met Moodle sites die mobiele toegang ingeschakeld hebben.

Als je niet kunt verbinden met je Moodle-site, neem dan contact op met je site-beheerder en vraag hem om http://docs.moodle.org/en/Mobile_app te lezen.

Om de app in een Moodle demo site te testen, kun je teacher of student in het Site-adres-veld typen en op de verbind-knop klikken.

e", "core.login.instructions": "Instructies", @@ -1707,9 +1703,6 @@ "core.login.invalidurl": "Ongeldige URL opgegeven", "core.login.invalidvaluemax": "De maximum waarde is {{$a}}", "core.login.invalidvaluemin": "De minimum waard eis {{$a}}", - "core.login.legacymoodleversion": "Je probeert verbinding te maken met een niet-ondersteunde Moodle-versie. Download de Moodle Classic-app om verbinding te maken met deze site.", - "core.login.legacymoodleversiondesktop": "Je probeert verbinding te maken met {{$a}}.

Deze site gebruikt een oude, niet-ondersteunde Moodle-versie die niet werkt met deze Moodle Desktop App.

Als dit jouw site is, neem dan contact op met een lokale Moodle-partner om hulp te krijgen bij het upgraden van de site.

Zie de contactpagina om een aanvraag te sturen of om hulp te vragen.", - "core.login.legacymoodleversiondesktopdownloadold": "

Als alternatief kun je nog steeds toegang krijgen tot de site door het gebruiken van een niet meer ondersteunde versie van de app die je kunt downloaden vanaf hier.", "core.login.localmobileunexpectedresponse": "Moodle Mobile Additional Features Check gaf een onverwacht antwoord. Je zult aanmelden via de standaard Mobile service.", "core.login.loggedoutssodescription": "Je moet opnieuw aanmelden. Je moet aanmelden met een browservenster.", "core.login.login": "Login", @@ -2000,8 +1993,18 @@ "core.submit": "Insturen", "core.success": "Succes", "core.tablet": "Tablet", + "core.tag.defautltagcoll": "Standaard collectie", "core.tag.errorareanotsupported": "Dit taggebied wordt niet ondersteund door de app.", + "core.tag.inalltagcoll": "Overal", + "core.tag.itemstaggedwith": "{{$a.tagarea}} getagged met \"{{$a.tag}}\"", + "core.tag.notagsfound": "Geen tags die overeenkomen met \"{{$a}}\" gevonden", + "core.tag.searchtags": "Zoek tags", + "core.tag.showingfirsttags": "Toon {{$a}} populairste tags", "core.tag.tag": "Tag", + "core.tag.tagarea_course": "Cursussen", + "core.tag.tagarea_course_modules": "Activiteiten en bronnen", + "core.tag.tagarea_post": "Blogberichten", + "core.tag.tagarea_user": "Gebruikersinteresses", "core.tag.tags": "Tags", "core.tag.warningareasnotsupported": "Sommige taggebieden worden niet weergegeven omdat ze niet worden ondersteund door de app.", "core.teachers": "leraren", diff --git a/src/assets/lang/no.json b/src/assets/lang/no.json index 2cff7bb38..29eef28b3 100644 --- a/src/assets/lang/no.json +++ b/src/assets/lang/no.json @@ -1421,11 +1421,9 @@ "core.login.createuserandpass": "Lag et brukernavn og passord for innlogging", "core.login.emailconfirmsent": "

En e-post er nå sendt til adressen {{$a}}

\n

Den inneholder informasjon om hvordan du fullfører registreringen.


\nOBS! Bekreft via URL straks du har fått e-posten. Går det for lang tid vil bekreftelsesprosessen bli automatisk annulert og du må begynne på nytt.\n

Hvis du fortsatt har problemer, vennligst ta kontakt med administrator for portalen.

", "core.login.emailconfirmsentsuccess": "Vellykket sending av bekreftelsesepost", - "core.login.enterthewordsabove": "Skriv inn ordene over", "core.login.firsttime": "Er det første gang du er her?", "core.login.forcepasswordchangenotice": "Du må endre passordet for å fortsette", "core.login.forgotten": "Har du glemt brukernavn eller passord?", - "core.login.getanothercaptcha": "Få en CAPTCHA til", "core.login.help": "Hjelp", "core.login.instructions": "Instruksjoner", "core.login.invaliddate": "Ugyldig dato", @@ -1603,7 +1601,17 @@ "core.strftimetime24": "%H:%M", "core.submit": "Send", "core.success": "Vellykket", + "core.tag.defautltagcoll": "Standardsamling", + "core.tag.inalltagcoll": "Over alt", + "core.tag.itemstaggedwith": "{{$a.tagarea}} tagget med \"{{$a.tag}}\"", + "core.tag.notagsfound": "Fant ingen tagger som lignet på \"{{$a}}\"", + "core.tag.searchtags": "Søk i tagger", + "core.tag.showingfirsttags": "Viser {{$a}} mest populære tagger", "core.tag.tag": "Tagg", + "core.tag.tagarea_course": "Kurs", + "core.tag.tagarea_course_modules": "Aktiviteter og ressurser", + "core.tag.tagarea_post": "Blogginnlegg", + "core.tag.tagarea_user": "Interesser", "core.tag.tags": "Tagger", "core.teachers": "Lærere", "core.thisdirection": "ltr", diff --git a/src/assets/lang/pl.json b/src/assets/lang/pl.json index 7eafaf385..4eb905090 100644 --- a/src/assets/lang/pl.json +++ b/src/assets/lang/pl.json @@ -1321,11 +1321,9 @@ "core.login.createuserandpass": "Wybierz nazwę użytkownika oraz hasło, które będą używane do logowania", "core.login.emailconfirmsent": "

Został do Ciebie wysłany e-mail pod adres {{$a}}.

Zawiera on prostą instrukcję, jak dokończyć rejestrację.

Jeżeli nadal będziesz mieć kłopoty, skontaktuj się z administratorem serwisu.

", "core.login.emailconfirmsentsuccess": "E-mail z potwierdzeniem został pomyślnie wysłany", - "core.login.enterthewordsabove": "Wpisz słowa powyżej", "core.login.firsttime": "Czy jesteś w tym serwisie po raz pierwszy?", "core.login.forcepasswordchangenotice": "W celu kontynuacji musisz zmienić swoje hasło", "core.login.forgotten": "Zapomniałeś(aś) nazwy użytkownika lub hasła?", - "core.login.getanothercaptcha": "Wygeneruj inne CAPTCHA", "core.login.help": "Pomoc", "core.login.instructions": "Instrukcje", "core.login.invaliddate": "Nieprawidłowa data", @@ -1541,7 +1539,14 @@ "core.strftimetime": "%H:%M %p", "core.submit": "Prześlij", "core.success": "Gotowe", + "core.tag.defautltagcoll": "Domyślna kolekcja", + "core.tag.inalltagcoll": "Wszędzie", + "core.tag.itemstaggedwith": "{{$a.tagarea}} otagowano \"{{$a.tag}}\"", + "core.tag.notagsfound": "Nie znaleziono pasujących tagów \"{{$a}}\"", + "core.tag.searchtags": "Szukaj tagów", + "core.tag.showingfirsttags": "Wyświetl {{$a}} najpopularniejszych tagów", "core.tag.tag": "Tag", + "core.tag.tagarea_course": "Kursy", "core.tag.tags": "Tagi", "core.teachers": "Prowadzący", "core.thisdirection": "ltr", diff --git a/src/assets/lang/pt-br.json b/src/assets/lang/pt-br.json index 4b48684c4..90338a819 100644 --- a/src/assets/lang/pt-br.json +++ b/src/assets/lang/pt-br.json @@ -1319,7 +1319,6 @@ "core.comments.nocomments": "Nenhum comentário", "core.comments.savecomment": "Salvar comentário", "core.commentscount": "Comentários ({{$a}})", - "core.commentsnotworking": "Os comentários não podem ser recuperados", "core.completion-alt-auto-fail": "Concluído: {{$a}} (não obteve nota para aprovação)", "core.completion-alt-auto-n": "Não concluído(s): {{$a}}", "core.completion-alt-auto-n-override": "Não concluído: {{$a.modname}} (por {{$a.overrideuser}})", @@ -1627,7 +1626,6 @@ "core.login.authenticating": "Autenticação", "core.login.cancel": "Cancelar", "core.login.changepassword": "Mudar a senha", - "core.login.checksiteversion": "Verifique se seu site usa Moodle 2.4 ou superior.", "core.login.confirmdeletesite": "Você tem certeza que quer excluir o site {{sitename}}?", "core.login.connect": "Conectar!", "core.login.connecttomoodle": "Conectar ao moodle", @@ -1640,7 +1638,6 @@ "core.login.emailconfirmsentnoemail": "

Um e-mail deve ter sido enviado para o seu endereço.

Ele contém instruções simplificadas para concluir seu registro.

Se você continuar com dificuldades, entre em contato com o administrador do site.

", "core.login.emailconfirmsentsuccess": "E-mail de confirmação enviado com sucesso", "core.login.emailnotmatch": "Os e-mail não coincidem", - "core.login.enterthewordsabove": "Digite as palavras acima", "core.login.erroraccesscontrolalloworigin": "A chamada de Cross-Origin que você está tentando executar foi rejeitada. Por favor, verifique https://docs.moodle.org/dev/Moodle_Mobile_development_using_Chrome_or_Chromium", "core.login.errordeletesite": "Um erro ocorreu ao excluir esse site. Por favor tente novamente.", "core.login.errorupdatesite": "Um erro ocorreu quando atualizava o token do site.", @@ -1648,7 +1645,6 @@ "core.login.firsttime": "Esta é a sua primeira vez aqui?", "core.login.forcepasswordchangenotice": "Você tem que mudar a senha antes de continuar", "core.login.forgotten": "Esqueceu o seu usuário ou senha?", - "core.login.getanothercaptcha": "Mudar imagem", "core.login.help": "Ajuda", "core.login.helpmelogin": "

Existem milhares de sites Moodle em todo o mundo. Este aplicativo pode se conectar somente a sites Moodle que habilitaram o acesso para dispositivos móveis.

Se você não conseguir se conectar ao seu site Moodle, entre em contato com o administrador do site e peça a ele para ler http://docs.moodle.org/en/Mobile_app

Para testar o aplicativo em um site de demonstração do Moodle digite teacher ou student no campoEndereço do site e clique no botão Conectar.

", "core.login.instructions": "Instruções", @@ -1661,9 +1657,6 @@ "core.login.invalidurl": "URL inválida especificada", "core.login.invalidvaluemax": "O valor máximo é {{$a}}", "core.login.invalidvaluemin": "O valor minimo é{{$a}}", - "core.login.legacymoodleversion": "Você está tentando se conectar a uma versão do Moodle sem suporte. Por favor, baixe o aplicativo Moodle Classic para acessar este site Moodle.", - "core.login.legacymoodleversiondesktop": "Você está tentando se conectar a {{$a}}.

Este site está executando uma versão desatualizada do Moodle, que não funciona com este aplicativo Moodle Desktop.
Se este for seu site, entre em contato com seu moodle partner para obter suporte para atualizá-lo.

Veja nossa página de contato para enviar uma solicitação de assistência.", - "core.login.legacymoodleversiondesktopdownloadold": "

Como alternativa, você ainda pode acessar este site utilizando uma versão sem suporte do aplicativo que pode ser baixada aqui.", "core.login.localmobileunexpectedresponse": "Verificação do Moodle Mobile Additional Features retornou uma resposta inesperada, você ira se autenticar usando o serviço Mobile padrão", "core.login.loggedoutssodescription": "Você tem que autenticar novamente. Você precisa fazer login no site em uma janela do navegador.", "core.login.login": "Acessar", @@ -1943,7 +1936,17 @@ "core.submit": "Enviar", "core.success": "Sucesso", "core.tablet": "Tablet", + "core.tag.defautltagcoll": "Coleção padrão", + "core.tag.inalltagcoll": "Em toda parte", + "core.tag.itemstaggedwith": "{{$a.tagarea}} marcada com \"{{$a.tag}}\"", + "core.tag.notagsfound": "Nenhuma tag correspondente a \"{{$a}}\" encontrado", + "core.tag.searchtags": "Pesquisar tags", + "core.tag.showingfirsttags": "Exibindo {{$a}} tags mais populares", "core.tag.tag": "Tag", + "core.tag.tagarea_course": "Cursos", + "core.tag.tagarea_course_modules": "Atividades e recursos", + "core.tag.tagarea_post": "Postagens de blog", + "core.tag.tagarea_user": "Interesses do usuário", "core.tag.tags": "Tags", "core.teachers": "Professores", "core.thereisdatatosync": "Existem {{$a}} offline para ser sincronizados.", diff --git a/src/assets/lang/pt.json b/src/assets/lang/pt.json index b7200312d..a193bd4c4 100644 --- a/src/assets/lang/pt.json +++ b/src/assets/lang/pt.json @@ -1351,7 +1351,6 @@ "core.comments.savecomment": "Guardar comentário", "core.comments.warningcommentsnotsent": "Não foi possível sincronizar os comentários. {{error}}", "core.commentscount": "Comentários ({{$a}})", - "core.commentsnotworking": "Não foi possível recuperar os comentários", "core.completion-alt-auto-fail": "Concluída: {{$a}} (não atingiu nota de aprovação)", "core.completion-alt-auto-n": "Não concluída: {{$a}}", "core.completion-alt-auto-n-override": "Não concluída: {{$a.modname}} (marcada por {{$a.overrideuser}})", @@ -1663,7 +1662,6 @@ "core.login.authenticating": "Autenticação", "core.login.cancel": "Cancelar", "core.login.changepassword": "Modificar senha", - "core.login.checksiteversion": "Verifique se o seu site Moodle está na versão 2.4 ou posterior.", "core.login.confirmdeletesite": "Tem a certeza que pretende remover o site {{sitename}}?", "core.login.connect": "Ligar", "core.login.connecttomoodle": "Ligação ao Moodle", @@ -1678,7 +1676,6 @@ "core.login.emailconfirmsentnoemail": "

Foi enviado um e-mail para o seu endereço.

Contém instruções fáceis para concluir o seu registo.

Se continuar com dificuldades, entre em contacto com o administrador do site.

", "core.login.emailconfirmsentsuccess": "E-mail de confirmação enviado com sucesso", "core.login.emailnotmatch": "Os e-mails não coincidem", - "core.login.enterthewordsabove": "Insira as palavras indicadas acima", "core.login.erroraccesscontrolalloworigin": "A acção de Cross-Origin que tentou executar foi rejeitada. Por favor, consulte mais informações em https://docs.moodle.org/dev/Moodle_Mobile_development_using_Chrome_or_Chromium", "core.login.errordeletesite": "Ocorreu um erro ao apagar este site. Por favor, tente novamente.", "core.login.errorupdatesite": "Ocorreu um erro enquanto atualizava o token do site.", @@ -1686,7 +1683,6 @@ "core.login.firsttime": "É a sua primeira visita aqui?", "core.login.forcepasswordchangenotice": "Tem de alterar a sua senha para poder continuar.", "core.login.forgotten": "Esqueceu-se do seu nome de utilizador ou da senha?", - "core.login.getanothercaptcha": "Gerar outro CAPTCHA", "core.login.help": "Ajuda", "core.login.helpmelogin": "

Existem muitos milhares de sites Moodle em todo o mundo. Esta aplicação só pode ligar-se a sites Moodle que ativaram especificamente o acesso a aplicações para dispositivos móveis.

Se não conseguir ligar-se ao seu site Moodle, entre em contacto com o administrador do site e peça-lhe para consultar http://docs.moodle.org/en/Mobile_app.

Para testar a aplicação num site de demonstração do Moodle digite professor ou aluno no campo Endereço do site e depois clique no botão Ligar.

", "core.login.instructions": "Instruções", @@ -1699,9 +1695,6 @@ "core.login.invalidurl": "O URL é inválido", "core.login.invalidvaluemax": "O valor máximo é {{$a}}", "core.login.invalidvaluemin": "O valor mínimo é {{$a}}", - "core.login.legacymoodleversion": "Está a tentar ligar-se a uma versão do Moodle não suportada. Por favor, descarregue a aplicação clássica do Moodle para aceder a este site Moodle.", - "core.login.legacymoodleversiondesktop": "Está a tentar ligar-se a {{$ a}}.

O site está a usar uma versão desatualizada do Moodle que não funcionará com esta aplicação Moodle.

Se for o seu site, entre em contacto com o seu parceiro Moodle local para obter assistência e atualizá-lo.
br>Consulte a nossa página de contacto para enviar um pedido de assistência.", - "core.login.legacymoodleversiondesktopdownloadold": "

Como alternativa, pode aceder a este site usando uma versão não suportada da aplicação que pode ser descarregada aqui.", "core.login.localmobileunexpectedresponse": "Ocorreu um erro inesperado na verificação de Moodle Mobile Additional Features. Será autenticado através do serviço Mobile padrão.", "core.login.loggedoutssodescription": "Tem de autenticar-se novamente. A autenticação no site tem de ser numa janela do navegador.", "core.login.login": "Entrar", @@ -1986,8 +1979,18 @@ "core.submit": "Submeter", "core.success": "Operação realizada com sucesso", "core.tablet": "Tablet", + "core.tag.defautltagcoll": "Coleção predefinida", "core.tag.errorareanotsupported": "Esta área de palavras-chave não é suportada pela aplicação.", + "core.tag.inalltagcoll": "Em qualquer local", + "core.tag.itemstaggedwith": "{{$a.tagarea}} com a palavra-chave \"{{$a.tag}}\"", + "core.tag.notagsfound": "Não foi encontrada a palavra-chave \"{{$a}}\"", + "core.tag.searchtags": "Procurar palavras-chave", + "core.tag.showingfirsttags": "A mostrar as {{$a}} palavras-chave mais populares", "core.tag.tag": "Palavra-chave", + "core.tag.tagarea_course": "Disciplinas", + "core.tag.tagarea_course_modules": "Atividades e recursos", + "core.tag.tagarea_post": "Entradas de blogue", + "core.tag.tagarea_user": "Interesses do utilizador", "core.tag.tags": "Palavras-chave", "core.tag.warningareasnotsupported": "Algumas das áreas de palavras-chave não são mostraas porque não são suportadas pela aplicação.", "core.teachers": "Professores", diff --git a/src/assets/lang/ro.json b/src/assets/lang/ro.json index 49ae78aac..ab8630b68 100644 --- a/src/assets/lang/ro.json +++ b/src/assets/lang/ro.json @@ -1143,14 +1143,12 @@ "core.login.credentialsdescription": "Va rugăm să introduceți userul și parola dumneavoastră pentru a vă conecta", "core.login.emailconfirmsent": "

Un email a fost trimis la adresa Dvs. {{$a}}

\n

Acest mesaj conţine instrucţiuni despre finalizarea înregistrării.

\n

În cazul în care întâmpinaţi dificultăţi contactaţi administratorul site-ului.

", "core.login.emailconfirmsentsuccess": "S-a trimis mail-ul de confirmare", - "core.login.enterthewordsabove": "Introduceţi cuvintele de mai sus", "core.login.erroraccesscontrolalloworigin": "Operațiune Cross-Origin pe care încercați să o efectuați a fost respinsă. Verificați la https://docs.moodle.org/dev/Moodle_Mobile_development_using_Chrome_or_Chromium", "core.login.errordeletesite": "A apărut o eroare în timpul ștergerii acestui site. Încercați din nou!", "core.login.errorupdatesite": "A apărut o eroare în timpul actualizării tokenului pentru acest site.", "core.login.firsttime": "Este prima oară când accesaţi această pagină?", "core.login.forcepasswordchangenotice": "Trebui să vă schimbaţi parola pentru a putea continua.", "core.login.forgotten": "Aţi uitat numele de utilizator sau parola?", - "core.login.getanothercaptcha": "Obţineţi un alt CAPTCHA", "core.login.help": "Ajutor", "core.login.helpmelogin": "

Există mii de siteuri Moodle în întreaga lume. Această aplicație vă poate conecta doar la acele siteuri ce oferă accesul pentru aplicațiile mobile.

Dacă nu vă puteți conecta la siteul Moodle va trebui să contactați un administrator al acelui site și să îi indicați să citească http://docs.moodle.org/en/Mobile_app

Pentru a testa aplicația pe un site Moodle demonstrativ introduceți teacher sau student în câmpurile din siteul URL site și apăsați butonul Adaugă.

", "core.login.instructions": "Instrucţiuni", @@ -1390,7 +1388,15 @@ "core.submit": "Trimite", "core.success": "Succes", "core.tablet": "Tabletă", + "core.tag.defautltagcoll": "Colecție implicită", + "core.tag.inalltagcoll": "Peste tot", + "core.tag.itemstaggedwith": "{{$a.tagarea}} etichetat cu \"{{$a.tag}}\"", + "core.tag.searchtags": "Caută etichete", + "core.tag.showingfirsttags": "Se afișează cele mai populare {{$a}} etichete", "core.tag.tag": "Etichetă", + "core.tag.tagarea_course": "Cursuri", + "core.tag.tagarea_course_modules": "Activități și resurse", + "core.tag.tagarea_post": "Postări pe blog", "core.tag.tags": "Etichete", "core.teachers": "Profesori", "core.thisdirection": "ltr", diff --git a/src/assets/lang/ru.json b/src/assets/lang/ru.json index 9b99f3565..31cbccb0b 100644 --- a/src/assets/lang/ru.json +++ b/src/assets/lang/ru.json @@ -1280,7 +1280,6 @@ "core.comments.nocomments": "Нет комментариев", "core.comments.savecomment": "Сохранить комментарий", "core.commentscount": "Комментарии ({{$a}})", - "core.commentsnotworking": "Комментарии не могут быть найдены", "core.completion-alt-auto-fail": "Выполнено: {{$a}} (оценка ниже проходного балла)", "core.completion-alt-auto-n": "Не выполнено: {{$a}}", "core.completion-alt-auto-n-override": "Не выполнено: {{$a.modname}} (отметил(а) {{$a.overrideuser}})", @@ -1484,7 +1483,6 @@ "core.login.authenticating": "Аутентификация", "core.login.cancel": "Отмена", "core.login.changepassword": "Изменить пароль", - "core.login.checksiteversion": "Убедитесь, что ваш сайт использует Moodle 2.4 или более позднюю версию.", "core.login.confirmdeletesite": "Вы уверены, что хотите удалить сайт {{sitename}}?", "core.login.connect": "Подключено!", "core.login.connecttomoodle": "Подключение к Moodle", @@ -1496,14 +1494,12 @@ "core.login.emailconfirmsent": "На указанный Вами адрес электронной почты ({{$a}}) было отправлено письмо с простыми инструкциями для завершения регистрации.\n Если у вас появятся проблемы с регистрацией, свяжитесь с администратором сайта.", "core.login.emailconfirmsentsuccess": "Письмо подтверждения отправлено успешно", "core.login.emailnotmatch": "Адреса электронной почты не совпадают", - "core.login.enterthewordsabove": "Напишите слова, которые Вы видите выше", "core.login.erroraccesscontrolalloworigin": "Запрос «Cross-Origin», который вы пытаетесь выполнить, отклонён. Пожалуйста, проверьте https://docs.moodle.org/dev/Moodle_Mobile_development_using_Chrome_or_Chromium", "core.login.errordeletesite": "При удалении этой страницы произошла ошибка. Пожалуйста, попробуйте еще раз.", "core.login.errorupdatesite": "При обновлении ключа сайта произошла ошибка.", "core.login.firsttime": "Вы в первый раз на нашем сайте?", "core.login.forcepasswordchangenotice": "Вы должны изменить свой пароль.", "core.login.forgotten": "Забыли логин или пароль?", - "core.login.getanothercaptcha": "Получить другой CAPTCHA (тест для различения людей и компьютеров)", "core.login.help": "Справка", "core.login.helpmelogin": "

Во всем мире есть тысячи сайтов Moodle. Это приложение может подключаться только к тем сайтам Moodle, на которых явно разрешен доступ мобильному приложению.

Если Вы не можете подключиться к вашему сайту Moodle, то необходимо связаться с администратором вашего сайта и попросить его прочитать http://docs.moodle.org/en/Mobile_app

Чтобы проверить приложение на демонстрационном сайте Moodle, введите teacher или student в поле Адрес сайта и нажмите Кнопку подключения.

", "core.login.instructions": "Инструкции", @@ -1783,7 +1779,17 @@ "core.submit": "Отправить", "core.success": "Успешно", "core.tablet": "Планшет", + "core.tag.defautltagcoll": "Коллекция по умолчанию", + "core.tag.inalltagcoll": "Везде", + "core.tag.itemstaggedwith": "Область «{{$a.tagarea}}» помечена тегом «{{$a.tag}}»", + "core.tag.notagsfound": "Не найдено тегов, совпадающих с «{{$a}}»", + "core.tag.searchtags": "Поиск тегов", + "core.tag.showingfirsttags": "Показать {{$a}} наиболее популярных тегов", "core.tag.tag": "Тег", + "core.tag.tagarea_course": "Курсы", + "core.tag.tagarea_course_modules": "Элементы и ресурсы", + "core.tag.tagarea_post": "Записи блогов", + "core.tag.tagarea_user": "Интересы пользователей", "core.tag.tags": "Теги", "core.teachers": "Преподаватели", "core.thereisdatatosync": "Есть {{$a}} для синхронизации, созданные вне сети.", diff --git a/src/assets/lang/sr-cr.json b/src/assets/lang/sr-cr.json index 3c7a9893e..9aaf5acb2 100644 --- a/src/assets/lang/sr-cr.json +++ b/src/assets/lang/sr-cr.json @@ -1328,7 +1328,6 @@ "core.comments.nocomments": "Нема коментара", "core.comments.savecomment": "Сачувај коментар", "core.commentscount": "Коментари ({{$a}})", - "core.commentsnotworking": "Коментари не могу да буду преузети", "core.completion-alt-auto-fail": "Завршено: {{$a}} (није постигао/ла прелазну оцену)", "core.completion-alt-auto-n": "Није завршено: {{$a}}", "core.completion-alt-auto-n-override": "Није завршено: {{$a.modname}} (подесио/ла {{$a.overrideuser}})", @@ -1541,7 +1540,6 @@ "core.login.authenticating": "Провера идентитета", "core.login.cancel": "Одустани", "core.login.changepassword": "Промени лозинку", - "core.login.checksiteversion": "Проверите да ли ваш сајт користи Moodle 2.4 или новију верзију.", "core.login.confirmdeletesite": "Да ли сте сигурни да желите да обришете сајт {{sitename}}?", "core.login.connect": "Повежите се!", "core.login.connecttomoodle": "Повежите се са Moodleom", @@ -1554,7 +1552,6 @@ "core.login.emailconfirmsentnoemail": "

Требало би да је послата е-порука на вашу адресу.

Порука садржи једноставна упутства о даљем поступку регистрације.

Ако и даље имате проблема, контактирајте администратора.

", "core.login.emailconfirmsentsuccess": "Е-порука за потврду успешно је послата", "core.login.emailnotmatch": "Адресе е-поште се не поклапају", - "core.login.enterthewordsabove": "Унесите реч изнад", "core.login.erroraccesscontrolalloworigin": "Cross-Origin позив који покушавате да изведете је одбијен. Молимо, проверите https://docs.moodle.org/dev/Moodle_Mobile_development_using_Chrome_or_Chromium", "core.login.errordeletesite": "Дошло је до грешке приликом брисања овог сајта. Молим, покушајте поново.", "core.login.errorupdatesite": "Дошло је до грешке приликом ажурирању токена сајта.", @@ -1562,7 +1559,6 @@ "core.login.firsttime": "Да ли сте овде први пут?", "core.login.forcepasswordchangenotice": "Морате променити своју лозинку да бисте наставили", "core.login.forgotten": "Заборавили сте своје корисничко име или лозинку?", - "core.login.getanothercaptcha": "Преузмите други CAPTCHА", "core.login.help": "Помоћ", "core.login.helpmelogin": "

Постоји више хиљада Moodle сајтова широм света. Ова апликација може да се повеже само са Moodle сајтовима који су експлицитно омогућили приступ мобилној апликацији.

Ако не можете да се повежете са Moodle сајтом, контактирајте администратора вашег сајта и затражите да прочита http://docs.moodle.org/en/Mobile_app

Да бисте тестирали апликацију на Moodle демо сајту упишите teacher или student у поље Адреса сајта и кликните дугме Повежите се!.

", "core.login.instructions": "Упутства", @@ -1575,9 +1571,6 @@ "core.login.invalidurl": "Наведена је неисправна URL адреса", "core.login.invalidvaluemax": "Максимална вредност је {{$a}}", "core.login.invalidvaluemin": "Минимална вредност је {{$a}}", - "core.login.legacymoodleversion": "Покушавате да се повежете са неподржаном Moodle верзијом. Молимо, преузмите Moodle Classic апликацију да бисте приступили овом Moodle сајту.", - "core.login.legacymoodleversiondesktop": "Покушавате да се повежете са сајтом{{$a}}.

Овај сајт користи застарелу неподржану верзију Moodlea која неће радити са овом Moodle Desktop апликацијом.

Ако је ово ваш сајт, обратите се локалном Moodle партнеру да вам помогне да га ажурирате.

Погледајте нашу страницу за контакт како бисте послали захтев за помоћ.", - "core.login.legacymoodleversiondesktopdownloadold": "

Алтернативно, и даље можете приступити овом сајту помоћу неподржане верзије апликације коју можете преузети овде.", "core.login.localmobileunexpectedresponse": "Провера Moodle Mobile додатних функционалности вратила је неочекиван одговор. Ваш идентитет биће проверен помоћу стандардног мобилнog сервиса.", "core.login.loggedoutssodescription": "Морате поново да потврдите свој идентитет. Потребно је да се пријавите на сајт у прозору веб читача.", "core.login.login": "Пријава", @@ -1855,7 +1848,17 @@ "core.submit": "Проследи", "core.success": "Успешно", "core.tablet": "Таблет", + "core.tag.defautltagcoll": "Подразумевана колекција", + "core.tag.inalltagcoll": "Свугде", + "core.tag.itemstaggedwith": "{{$a.tagarea}} означена са \"{{$a.tag}}\"", + "core.tag.notagsfound": "Нису пронађене ознаке које се поклапају са \"{{$a}}\"", + "core.tag.searchtags": "Претражи ознаке", + "core.tag.showingfirsttags": "Број најпопуларнијих ознака које се приказују: {{$a}}", "core.tag.tag": "Ознака", + "core.tag.tagarea_course": "Курсеви", + "core.tag.tagarea_course_modules": "Активности и ресурси", + "core.tag.tagarea_post": "Објаве на блогу", + "core.tag.tagarea_user": "Корисничка интересовања", "core.tag.tags": "Ознаке", "core.teachers": "Предавачи", "core.thereisdatatosync": "Број офлајн податак које треба синхронизовати: {{$a}}", diff --git a/src/assets/lang/sr-lt.json b/src/assets/lang/sr-lt.json index 75b37e347..6f08390ab 100644 --- a/src/assets/lang/sr-lt.json +++ b/src/assets/lang/sr-lt.json @@ -1328,7 +1328,6 @@ "core.comments.nocomments": "Nema komentara", "core.comments.savecomment": "Sačuvaj komentar", "core.commentscount": "Komentari ({{$a}})", - "core.commentsnotworking": "Komentari ne mogu da budu preuzeti", "core.completion-alt-auto-fail": "Završeno: {{$a}} (nije postigao/la prelaznu ocenu)", "core.completion-alt-auto-n": "Nije završeno: {{$a}}", "core.completion-alt-auto-n-override": "Nije završeno: {{$a.modname}} (podesio/la {{$a.overrideuser}})", @@ -1541,7 +1540,6 @@ "core.login.authenticating": "Provera identiteta", "core.login.cancel": "Odustani", "core.login.changepassword": "Promeni lozinku", - "core.login.checksiteversion": "Proverite da li vaš sajt koristi Moodle 2.4 ili noviju verziju.", "core.login.confirmdeletesite": "Da li ste sigurni da želite da obrišete sajt {{sitename}}?", "core.login.connect": "Povežite se!", "core.login.connecttomoodle": "Povežite se sa Moodleom", @@ -1554,7 +1552,6 @@ "core.login.emailconfirmsentnoemail": "

Trebalo bi da je poslata e-poruka na vašu adresu.

Poruka sadrži jednostavna uputstva o daljem postupku registracije.

Ako i dalje imate problema, kontaktirajte administratora.

", "core.login.emailconfirmsentsuccess": "E-poruka za potvrdu uspešno je poslata", "core.login.emailnotmatch": "Adrese e-pošte se ne poklapaju", - "core.login.enterthewordsabove": "Unesite reč iznad", "core.login.erroraccesscontrolalloworigin": "Cross-Origin poziv koji pokušavate da izvedete je odbijen. Molimo, proverite https://docs.moodle.org/dev/Moodle_Mobile_development_using_Chrome_or_Chromium", "core.login.errordeletesite": "Došlo je do greške prilikom brisanja ovog sajta. Molim, pokušajte ponovo.", "core.login.errorupdatesite": "Došlo je do greške prilikom ažuriranju tokena sajta.", @@ -1562,7 +1559,6 @@ "core.login.firsttime": "Da li ste ovde prvi put?", "core.login.forcepasswordchangenotice": "Morate promeniti svoju lozinku da biste nastavili", "core.login.forgotten": "Zaboravili ste svoje korisničko ime ili lozinku?", - "core.login.getanothercaptcha": "Preuzmite drugi CAPTCHA", "core.login.help": "Pomoć", "core.login.helpmelogin": "

Postoji više hiljada Moodle sajtova širom sveta. Ova aplikacija može da se poveže samo sa Moodle sajtovima koji su eksplicitno omogućili pristup mobilnoj aplikaciji.

Ako ne možete da se povežete sa Moodle sajtom, kontaktirajte administratora vašeg sajta i zatražite da pročita http://docs.moodle.org/en/Mobile_app

Da biste testirali aplikaciju na Moodle demo sajtu upišite teacher ili student u polje Adresa sajta i kliknite dugme Povežite se!.

", "core.login.instructions": "Uputstva", @@ -1575,9 +1571,6 @@ "core.login.invalidurl": "Navedena je neispravna URL adresa", "core.login.invalidvaluemax": "Maksimalna vrednost je {{$a}}", "core.login.invalidvaluemin": "Minimalna vrednost je {{$a}}", - "core.login.legacymoodleversion": "Pokušavate da se povežete sa nepodržanom Moodle verzijom. Molimo, preuzmite Moodle Classic aplikaciju da biste pristupili ovom Moodle sajtu.", - "core.login.legacymoodleversiondesktop": "Pokušavate da se povežete sa sajtom{{$a}}.

Ovaj sajt koristi zastarelu nepodržanu verziju Moodlea koja neće raditi sa ovom Moodle Desktop aplikacijom.

Ako je ovo vaš sajt, obratite se lokalnom Moodle partneru da vam pomogne da ga ažurirate.

Pogledajte našu stranicu za kontakt kako biste poslali zahtev za pomoć.", - "core.login.legacymoodleversiondesktopdownloadold": "

Alternativno, i dalje možete pristupiti ovom sajtu pomoću nepodržane verzije aplikacije koju možete preuzeti ovde.", "core.login.localmobileunexpectedresponse": "Provera Moodle Mobile dodatnih funkcionalnosti vratila je neočekivan odgovor. Vaš identitet biće proveren pomoću standardnog mobilnog servisa.", "core.login.loggedoutssodescription": "Morate ponovo da potvrdite svoj identitet. Potrebno je da se prijavite na sajt u prozoru veb čitača.", "core.login.login": "Prijava", @@ -1855,7 +1848,17 @@ "core.submit": "Prosledi", "core.success": "Uspešno", "core.tablet": "Tablet", + "core.tag.defautltagcoll": "Podrazumevana kolekcija", + "core.tag.inalltagcoll": "Svugde", + "core.tag.itemstaggedwith": "{{$a.tagarea}} označena sa \"{{$a.tag}}\"", + "core.tag.notagsfound": "Nisu pronađene oznake koje se poklapaju sa \"{{$a}}\"", + "core.tag.searchtags": "Pretraži oznake", + "core.tag.showingfirsttags": "Broj najpopularnijih oznaka koje se prikazuju: {{$a}}", "core.tag.tag": "Oznaka", + "core.tag.tagarea_course": "Kursevi", + "core.tag.tagarea_course_modules": "Aktivnosti i resursi", + "core.tag.tagarea_post": "Objave na blogu", + "core.tag.tagarea_user": "Korisnička interesovanja", "core.tag.tags": "Oznake", "core.teachers": "Predavači", "core.thereisdatatosync": "Broj oflajn podatak koje treba sinhronizovati: {{$a}}", diff --git a/src/assets/lang/sv.json b/src/assets/lang/sv.json index 60b7202e8..6231cfd94 100644 --- a/src/assets/lang/sv.json +++ b/src/assets/lang/sv.json @@ -1316,13 +1316,11 @@ "core.login.credentialsdescription": "Ange ditt användarnamn och lösenord för att logga på", "core.login.emailconfirmsent": "

Vi har skickat ett e-postbrev som Du bör ha fått
till Din adress på {{$a}}

\n

Det innehåller enkla instruktioner som hjälper Dig
att fullfölja Din registrering.

\n

Om Du stöter på problem, är Du välkommen att
kontakta den som ansvarar för webbplatsen.

", "core.login.emailconfirmsentsuccess": "Bekräftelsemejlet har skickats.", - "core.login.enterthewordsabove": "Mata in de ovanstående orden", "core.login.errordeletesite": "Ett fel inträffade vid borttagning av denna webbsida. Var god försök igen.", "core.login.errorupdatesite": "Ett fel inträffade vid uppdatering av webbplatsens token.", "core.login.firsttime": "Är det första gången Du är här?", "core.login.forcepasswordchangenotice": "Du måste använda Ditt lösenord för att kunna fortsätta.", "core.login.forgotten": "Har Du glömt Ditt användarnamn och lösenord?", - "core.login.getanothercaptcha": "Skaffa en CAPTCHA till", "core.login.help": "Hjälp", "core.login.helpmelogin": "

För att logga in måste du kontrollera att: 1. Moodle webbplats är version 2.4 eller högre
2. Moodle administratören har aktiverat Mobil åtkomst

För att testa appen på en Moodle demo site skriv lärare eller elev i i fält för Användarnamn och klicka på Lägg till knapp . Besök Moodle webbplats för mer detaljerad Moodle information och hjälp. ", "core.login.instructions": "Instruktioner", @@ -1556,6 +1554,7 @@ "core.submit": "Skicka", "core.success": "Framgång", "core.tablet": "Tablet", + "core.tag.searchtags": "Sökrubriker", "core.tag.tag": "Etikett", "core.tag.tags": "etiketter", "core.teachers": "Distanslärare/
handledare/
coacher", diff --git a/src/assets/lang/tg.json b/src/assets/lang/tg.json index 467850934..6259ed2cf 100644 --- a/src/assets/lang/tg.json +++ b/src/assets/lang/tg.json @@ -976,11 +976,9 @@ "core.login.createuserandpass": "Номи истифодабаранда ва калимаи раҳкушоро интихоб намоед", "core.login.emailconfirmsent": "Ба адреси почтаи электронии ({{$a}}) зикркардаи Шумо нома бо дастурҳои оддӣ барои анҷом додани бақайдгирӣ фиристода шудааст. Агар дар мавриди бақайдгирӣ мушкилот ба миён оянд, бо администратори сомона робита намоед.", "core.login.emailnotmatch": "Нишониҳои почтаи электронӣ мувофиқат намекунанд.", - "core.login.enterthewordsabove": "Каломаҳоеро, ки дар боло мебинед, нависед", "core.login.firsttime": "Шумо бори аввал дар сомонаи мо ҳастед?", "core.login.forcepasswordchangenotice": "Шумо бояд калимаи раҳкушои худро иваз намоед.", "core.login.forgotten": "Номи корбар ё ниҳонвожаро фаромӯш кардед?", - "core.login.getanothercaptcha": "Ба даст овардани CAPTCHA -и дигар (матн барои фарқ кунондани одамон ва компютерҳо)", "core.login.help": "Маълумотнома", "core.login.instructions": "Дастурҳо", "core.login.invaliddate": "Таърихи нодурусти рӯз", @@ -1166,6 +1164,7 @@ "core.strftimetime": "%H:%M", "core.submit": "Равона карда шавад", "core.success": "Бо муваффақият", + "core.tag.searchtags": "Ҷустуҷӯи тегҳо", "core.tag.tag": "Тег", "core.tag.tags": "Тегҳо", "core.teachers": "Муаллимон", diff --git a/src/assets/lang/tr.json b/src/assets/lang/tr.json index b785861b7..5d90431ee 100644 --- a/src/assets/lang/tr.json +++ b/src/assets/lang/tr.json @@ -1160,11 +1160,9 @@ "core.login.emailconfirmsent": "

Bu e-posta adresinize ({{$a}}) bir mesaj gönderildi.

\n

Bu mesaj, kaydınızı tamamlamanız için basit yönergeleri içermektedir.

\n

Bu işlemleri yaparken bir zorlukla karşılaşırsanız site yöneticiyle iletişim kurabilirsiniz.

", "core.login.emailconfirmsentsuccess": "Onaylama e-postası başarıyla gönderildi", "core.login.emailnotmatch": "E-postalar uyuymuyor", - "core.login.enterthewordsabove": "Yukarıdaki sözcükleri yazın", "core.login.firsttime": "Buraya ilk defa mı geliyorsunuz?", "core.login.forcepasswordchangenotice": "Devam etmek için şifrenizi değiştirmelisiniz.", "core.login.forgotten": "Kullanıcı adı veya şifrenizi mi unuttunuz?", - "core.login.getanothercaptcha": "Anlaşılmıyor, yeniden yükle", "core.login.help": "Yardım", "core.login.helpmelogin": "

Dünyada binlerce Moodle Sitesi var. Bu uygulama sadece Moodle App girişine izin verilmiş olan Moodle Sitelerinde çalışır

Eğer moodle sitenizle bağlantı kuramıyorsanız, Moodle yöneticinize başvurun nereyle bağlantı kuramıyorsanız, yöneticizle iletişime geçin ve şu linkteki açıklamaları okumasını isteyin http://docs.moodle.org/en/Mobile_app

Deneme yapmak için Moodle demo sitesinde ilgili alanlara öğretmen veya öğrenciiçinde Site URL seçeneklerini girin ve ekle butonuna tıklayın", "core.login.instructions": "Yönergeler", @@ -1379,7 +1377,17 @@ "core.submit": "Gönder", "core.success": "Başarı", "core.tablet": "Tablet", + "core.tag.defautltagcoll": "Varsayılan koleksiyon", + "core.tag.inalltagcoll": "Her yer", + "core.tag.itemstaggedwith": "{{$a.tagarea}} etiketlenen ile birlikte \"{{$a.tag}}\"", + "core.tag.notagsfound": "\"{{$a}}\" ile eşleşen etiket bulunamadı", + "core.tag.searchtags": "Etiketleri ara", + "core.tag.showingfirsttags": "En popüler etiketler {{$a}} gösteriliyor", "core.tag.tag": "Etiket", + "core.tag.tagarea_course": "Dersler", + "core.tag.tagarea_course_modules": "Faaliyetler ve kaynaklar", + "core.tag.tagarea_post": "Blog girdileri", + "core.tag.tagarea_user": "Kullanıcı ilgi alanları", "core.tag.tags": "Etiketler", "core.teachers": "Eğitimciler", "core.thisdirection": "ltr", diff --git a/src/assets/lang/uk.json b/src/assets/lang/uk.json index 19f21e2f4..761e07ac5 100644 --- a/src/assets/lang/uk.json +++ b/src/assets/lang/uk.json @@ -1145,7 +1145,6 @@ "core.comments.nocomments": "Немає коментарів", "core.comments.savecomment": "Зберегти коментар", "core.commentscount": "Коментарі ({{$a}})", - "core.commentsnotworking": "Коментар не може бути відновлений", "core.completion-alt-auto-fail": "Виконано: {{$a}} (не вдалося досягти прохідного балу)", "core.completion-alt-auto-n": "Не виконано: {{$a}}", "core.completion-alt-auto-pass": "Виконано: {{$a}} (перевищено прохідний бал)", @@ -1332,7 +1331,6 @@ "core.login.authenticating": "Аутентифікація", "core.login.cancel": "Скасувати", "core.login.changepassword": "Змінити пароль", - "core.login.checksiteversion": "Переконайтеся, що ваш сайт використовує Moodle 2.4 або більш пізньої версії.", "core.login.confirmdeletesite": "Видалити сайт {{sitename}}?", "core.login.connect": "З'єднано!", "core.login.connecttomoodle": "Підключитись до Moodle", @@ -1343,14 +1341,12 @@ "core.login.credentialsdescription": "Будь ласка, введіть Ваше ім'я користувача та пароль, щоб увійти в систему.", "core.login.emailconfirmsent": "На зазначену Вами адресу електронної пошти ({{$a}}) було відправлено листа з інструкціями із завершення реєстрації. Якщо у Вас з'являться проблеми з реєстрацією, зв'яжіться з адміністратором сайту.", "core.login.emailnotmatch": "Email не співпадають", - "core.login.enterthewordsabove": "Введіть символи, які бачите вище", "core.login.erroraccesscontrolalloworigin": "Cross-Origin дзвінок був відхилений. Будь ласка, перевірте https://docs.moodle.org/dev/Moodle_Mobile_development_using_Chrome_or_Chromium", "core.login.errordeletesite": "Під час видалення цього сайту сталася помилка. Будь ласка спробуйте ще раз.", "core.login.errorupdatesite": "При оновленні токена сайту сталася помилка.", "core.login.firsttime": "Ви вперше на нашому сайті?", "core.login.forcepasswordchangenotice": "Щоб продовжити, ви повинні змінити свій пароль.", "core.login.forgotten": "Забули ім'я або пароль?", - "core.login.getanothercaptcha": "Отримати інший варант", "core.login.help": "Допомога", "core.login.helpmelogin": "

Є багато тисяч сайтів Moodle по всьому світу. Ця програма може підключатися тільки до сайтів Moodle, які є з підтримкою мобільного доступу додатків.

Якщо ви не можете підключитися до вашого сайту Moodle, то вам необхідно звернутися до адміністратора Moodle в тому місці, де планується отримати доступ і попросіть їх прочитати http://docs.moodle.org/en/Mobile_app

Щоб перевірити додаток на Moodle демо сайті в ролі вчителя або студента в полі адреса сайту і натисніть кнопку Підключення .

", "core.login.instructions": "Інструкції", @@ -1622,7 +1618,17 @@ "core.submit": "Прийняти", "core.success": "Успішно", "core.tablet": "Планшет", + "core.tag.defautltagcoll": "Типова колекція", + "core.tag.inalltagcoll": "Скрізь", + "core.tag.itemstaggedwith": "Область {{$a.tagarea}} відмічена як \"{{$a.tag}}\"", + "core.tag.notagsfound": "Для \"{{$a}}\" відповідних міток не знайдено", + "core.tag.searchtags": "Пошук міток", + "core.tag.showingfirsttags": "Показати {{$a}} найпопулярніших міток", "core.tag.tag": "Мітка", + "core.tag.tagarea_course": "Курси", + "core.tag.tagarea_course_modules": "Діяльності та ресурси", + "core.tag.tagarea_post": "Записи блогу", + "core.tag.tagarea_user": "Інтереси користувачів", "core.tag.tags": "Мітки", "core.teachers": "Викладачі", "core.thereisdatatosync": "Офлайн {{$a}} повинні бути синхронізовані.", diff --git a/src/assets/lang/zh-cn.json b/src/assets/lang/zh-cn.json index 1df461a91..9cec061cb 100644 --- a/src/assets/lang/zh-cn.json +++ b/src/assets/lang/zh-cn.json @@ -1225,7 +1225,6 @@ "core.comments.nocomments": "无评论", "core.comments.savecomment": "保存评论", "core.commentscount": "评论({{$a}})", - "core.commentsnotworking": "评论不能检索", "core.completion-alt-auto-fail": "已完成:{{$a}}(未及格)", "core.completion-alt-auto-n": "未完成:{{$a}}", "core.completion-alt-auto-pass": "已完成:{{$a}}(及格)", @@ -1420,7 +1419,6 @@ "core.login.authenticating": "验证中", "core.login.cancel": "取消", "core.login.changepassword": "更改密码", - "core.login.checksiteversion": "检查您的站点是否使用了Moodle 2.4或更高的版本。", "core.login.confirmdeletesite": "你确定你想删除{{sitename}}网站吗?", "core.login.connect": "连接!", "core.login.connecttomoodle": "连接到Moodle", @@ -1431,14 +1429,12 @@ "core.login.credentialsdescription": "请提供您的用户名和密码来登录。", "core.login.emailconfirmsent": "

一封邮件已经发送到您的地址 {{$a}}

\n

这封邮件简要说明了您如何完成注册。

\n

如果您还遇到什么困难,请和网站管理员联系。

", "core.login.emailnotmatch": "电子邮件不匹配", - "core.login.enterthewordsabove": "输入上面的字母", "core.login.erroraccesscontrolalloworigin": "你想要执行的Cross-Origin调用被拒绝了。\n请检查https://docs.moodle.org/dev/Moodle_Mobile_development_using_Chrome_or_Chromium", "core.login.errordeletesite": "在删除这个站点时发生了错误。请再试一次。", "core.login.errorupdatesite": "在更新站点令牌时发生了一个错误。", "core.login.firsttime": "第一次来访吗?", "core.login.forcepasswordchangenotice": "继续下去之前,您必须修改您的密码。", "core.login.forgotten": "忘记用户名或密码了?", - "core.login.getanothercaptcha": "获取另一个 CAPTCHA", "core.login.help": "帮助", "core.login.helpmelogin": "

全世界有成千上万的Moodle站点。本APP只能连接那些启用移动APP接入功能的Moodle站点。

如果您无法连接该站点,请与相关站点管理员联系并请他们阅读以下文档http://docs.moodle.org/en/Mobile_app

想要在Moodle演示站点中测试本APP,请在站点URL栏中输入teacher或者student,并点击添加按钮

", "core.login.instructions": "使用说明", @@ -1709,7 +1705,17 @@ "core.submit": "提交", "core.success": "成功", "core.tablet": "平板电脑", + "core.tag.defautltagcoll": "默认收藏夹", + "core.tag.inalltagcoll": "到处", + "core.tag.itemstaggedwith": "{{$a.tagarea}}由\"{{$a.tag}}\"标签标记", + "core.tag.notagsfound": "未发现匹配\"{{$a}}\"的标签", + "core.tag.searchtags": "搜索标签", + "core.tag.showingfirsttags": "显示最受欢迎标签{{$a}}", "core.tag.tag": "标签", + "core.tag.tagarea_course": "课程", + "core.tag.tagarea_course_modules": "活动和资源", + "core.tag.tagarea_post": "博客帖子", + "core.tag.tagarea_user": "用户兴趣", "core.tag.tags": "标签", "core.teachers": "教师", "core.thereisdatatosync": "离线 {{$a}}需要同步。", diff --git a/src/assets/lang/zh-tw.json b/src/assets/lang/zh-tw.json index 23bad0e01..8ebf26de8 100644 --- a/src/assets/lang/zh-tw.json +++ b/src/assets/lang/zh-tw.json @@ -1244,7 +1244,6 @@ "core.comments.nocomments": "沒有評論", "core.comments.savecomment": "儲存評論", "core.commentscount": "評論數({{$a}})", - "core.commentsnotworking": "無法取得評論資料", "core.completion-alt-auto-fail": "已完成:{{$a}} (不及格)", "core.completion-alt-auto-n": "尚未完成:{{$a}}", "core.completion-alt-auto-pass": "已經完成:{{$a}} (及格)", @@ -1463,7 +1462,6 @@ "core.login.authenticating": "認證中", "core.login.cancel": "取消", "core.login.changepassword": "更改密碼", - "core.login.checksiteversion": "請檢查您的網站是否使用Moodle 2.4或更高版本.", "core.login.confirmdeletesite": "您確定要刪除網站{{sitename}}嗎?", "core.login.connect": "連線!", "core.login.connecttomoodle": "連上Moodle平台", @@ -1475,14 +1473,12 @@ "core.login.emailconfirmsent": "

本系統已經送出電子郵件到 {{$a}}

\n

訊息內容包含如何完成註冊手續。

\n

請閱覽您的私人郵件 按下確認連結後 便可登入本系統,若是有問題請和系統管理員連絡。

", "core.login.emailconfirmsentsuccess": "成功寄出確認電郵", "core.login.emailnotmatch": "電子郵件內容不符", - "core.login.enterthewordsabove": "輸入您在上圖中看到的字元", "core.login.erroraccesscontrolalloworigin": "您嘗試執行的Cross-Origin呼叫已被拒絕. 請檢查https://docs.moodle.org/dev/Moodle_Mobile_development_using_Chrome_or_Chromium", "core.login.errordeletesite": "刪除此網站時出錯. 請再試一次.", "core.login.errorupdatesite": "更新網站權杖時出錯.", "core.login.firsttime": "您第一次來訪嗎?", "core.login.forcepasswordchangenotice": "您必須修改您的密碼才能繼續進行。", "core.login.forgotten": "忘記帳號或密碼嗎?", - "core.login.getanothercaptcha": "換一個字詞", "core.login.help": "輔助說明", "core.login.helpmelogin": "

要登入前請確認:

1. Moodle 入口網站版本要是2.4或更高
2. Moodle 入口網站管理員有啟用手機存取.

想測試Moodle示範入口網站型態須輸入teacherstudentUsername 欄位並點選Add button.

Moodle文件網站有更詳細的資料及協助

", "core.login.instructions": "使用說明", @@ -1754,7 +1750,17 @@ "core.submit": "送出", "core.success": "成功", "core.tablet": "平板", + "core.tag.defautltagcoll": "預設的蒐藏", + "core.tag.inalltagcoll": "每個地方", + "core.tag.itemstaggedwith": "在{{$a.tagarea}} 被標籤為\"{{$a.tag}}\"", + "core.tag.notagsfound": "沒有找到符合\"{{$a}}\"的標籤", + "core.tag.searchtags": "搜尋標籤", + "core.tag.showingfirsttags": "顯示 {{$a}} 個最普遍的標籤", "core.tag.tag": "標籤", + "core.tag.tagarea_course": "課程", + "core.tag.tagarea_course_modules": "課程模組", + "core.tag.tagarea_post": "部落格貼文", + "core.tag.tagarea_user": "用戶興趣", "core.tag.tags": "標籤", "core.teachers": "教師", "core.thereisdatatosync": "有離線的{{$ a}}要同步", diff --git a/src/assets/ydn.db-dev.js b/src/assets/ydn.db-dev.js deleted file mode 100755 index d9a1b3958..000000000 --- a/src/assets/ydn.db-dev.js +++ /dev/null @@ -1,365 +0,0 @@ -(function(){var l,s=this;function t(a){return void 0!==a}function ba(a){a=a.split(".");for(var b=s,c;c=a.shift();)if(null!=b[c])b=b[c];else return null;return b} -function ca(a){var b=typeof a;if("object"==b)if(a){if(a instanceof Array)return"array";if(a instanceof Object)return b;var c=Object.prototype.toString.call(a);if("[object Window]"==c)return"object";if("[object Array]"==c||"number"==typeof a.length&&"undefined"!=typeof a.splice&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("splice"))return"array";if("[object Function]"==c||"undefined"!=typeof a.call&&"undefined"!=typeof a.propertyIsEnumerable&&!a.propertyIsEnumerable("call"))return"function"}else return"null"; -else if("function"==b&&"undefined"==typeof a.call)return"object";return b}function u(a){return"array"==ca(a)}function v(a){var b=ca(a);return"array"==b||"object"==b&&"number"==typeof a.length}function w(a){return"string"==typeof a}function da(a){return"boolean"==typeof a}function ea(a){return"number"==typeof a}function fa(a){return"function"==ca(a)}function y(a){var b=typeof a;return"object"==b&&null!=a||"function"==b}var ga="closure_uid_"+(1E9*Math.random()>>>0),ha=0; -function ia(a,b,c){return a.call.apply(a.bind,arguments)}function ja(a,b,c){if(!a)throw Error();if(2":"
")} -function va(a){if(!wa.test(a))return a;-1!=a.indexOf("&")&&(a=a.replace(xa,"&"));-1!=a.indexOf("<")&&(a=a.replace(ya,"<"));-1!=a.indexOf(">")&&(a=a.replace(za,">"));-1!=a.indexOf('"')&&(a=a.replace(Aa,"""));-1!=a.indexOf("'")&&(a=a.replace(Ba,"'"));-1!=a.indexOf("\x00")&&(a=a.replace(Ca,"�"));return a}var xa=/&/g,ya=//g,Aa=/"/g,Ba=/'/g,Ca=/\x00/g,wa=/[\x00&<>"']/;function Da(a){return ua(a.replace(/ /g,"  "),void 0)} -function Ea(a,b){for(var c=b.length,d=0;de))if(d in Ga)d=Ga[d];else if(d in Fa)d=Ga[d]=Fa[d];else{e=d;g=d.charCodeAt(0);if(31g)e=d;else{if(256>g){if(e="\\x",16>g||256g&&(e+="0");e+=g.toString(16).toUpperCase()}d=Ga[d]=e}g=d}b[f]=g}b.push('"');return b.join("")}function Ia(a,b){return ab?1:0};function Ja(a,b){b.unshift(a);qa.call(this,sa.apply(null,b));b.shift()}z(Ja,qa);Ja.prototype.name="AssertionError";function Ka(a,b){throw new Ja("Failure"+(a?": "+a:""),Array.prototype.slice.call(arguments,1));};function La(a){s.setTimeout(function(){throw a;},0)}var Ma; -function Na(){if(s.Promise&&s.Promise.resolve){var a=s.Promise.resolve();return function(b){a.then(function(){try{b()}catch(a){La(a)}})}}var b=s.MessageChannel;"undefined"===typeof b&&"undefined"!==typeof window&&window.postMessage&&window.addEventListener&&(b=function(){var a=document.createElement("iframe");a.style.display="none";a.src="";document.documentElement.appendChild(a);var b=a.contentWindow,a=b.document;a.open();a.write("");a.close();var c="callImmediate"+Math.random(),d=b.location.protocol+ -"//"+b.location.host,a=ka(function(a){if(a.origin==d||a.data==c)this.port1.onmessage()},this);b.addEventListener("message",a,!1);this.port1={};this.port2={postMessage:function(){b.postMessage(c,d)}}});if("undefined"!==typeof b){var c=new b,d={},e=d;c.port1.onmessage=function(){d=d.next;var a=d.Eb;d.Eb=null;a()};return function(a){e.next={Eb:a};e=e.next;c.port2.postMessage(0)}}return"undefined"!==typeof document&&"onreadystatechange"in document.createElement("script")?function(a){var b=document.createElement("script"); -b.onreadystatechange=function(){b.onreadystatechange=null;b.parentNode.removeChild(b);b=null;a();a=null};document.documentElement.appendChild(b)}:function(a){s.setTimeout(a,0)}};function Oa(a,b){if(!Pa){var c=Qa;fa(s.setImmediate)?s.setImmediate(c):(Ma||(Ma=Na()),Ma(c));Pa=!0}Ra.push(new Sa(a,b))}var Pa=!1,Ra=[];[].push(function(){Pa=!1;Ra=[]});function Qa(){for(;Ra.length;){var a=Ra;Ra=[];for(var b=0;bc?Math.max(0,a.length+c):c;if(w(a))return w(b)&&1==b.length?a.indexOf(b,c):-1;for(;cc?null:w(a)?a.charAt(c):a[c]}function ob(a,b,c){for(var d=a.length,e=w(a)?a.split(""):a,f=0;f=a.length)throw Qb;if(b in a)return a[b++];b++}};return c}throw Error("Not implemented");} -function Tb(a,b){if(v(a))try{kb(a,b,void 0)}catch(c){if(c!==Qb)throw c;}else{a=Sb(a);try{for(;;)b.call(void 0,a.next(),void 0,a)}catch(d){if(d!==Qb)throw d;}}};function Ub(a,b){this.b={};this.a=[];this.d=this.c=0;var c=arguments.length;if(1=c.length)throw Qb;var g=c[b++];return a?g:d[g]}};return g};var Xb;a:{var Yb=s.navigator;if(Yb){var Zb=Yb.userAgent;if(Zb){Xb=Zb;break a}}Xb=""}function $b(a){return-1!=Xb.indexOf(a)};function ac(){return s.navigator||null}var bc=$b("Opera")||$b("OPR"),cc=$b("Trident")||$b("MSIE"),dc=$b("Gecko")&&-1==Xb.toLowerCase().indexOf("webkit")&&!($b("Trident")||$b("MSIE")),ec=-1!=Xb.toLowerCase().indexOf("webkit");ec&&$b("Mobile");var fc,gc=ac();fc=gc&&gc.platform||"";fc.indexOf("Mac");fc.indexOf("Win");fc.indexOf("Linux");ac()&&(ac().appVersion||"").indexOf("X11");var hc=Xb;hc&&hc.indexOf("Android");hc&&hc.indexOf("iPhone");hc&&hc.indexOf("iPad"); -function ic(){var a=s.document;return a?a.documentMode:void 0}var jc=function(){var a="",b;if(bc&&s.opera)return a=s.opera.version,fa(a)?a():a;dc?b=/rv\:([^\);]+)(\)|;)/:cc?b=/\b(?:MSIE|rv)[: ]([^\);]+)(\)|;)/:ec&&(b=/WebKit\/(\S+)/);b&&(a=(a=b.exec(Xb))?a[1]:"");return cc&&(b=ic(),b>parseFloat(a))?String(b):a}(),kc={}; -function lc(a){var b;if(!(b=kc[a])){b=0;for(var c=String(jc).replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split("."),d=String(a).replace(/^[\s\xa0]+|[\s\xa0]+$/g,"").split("."),e=Math.max(c.length,d.length),f=0;0==b&&fb.length){c.push(qc(a)+"(");for(var d=a.arguments,e=0;d&&e=Gc(this).value)for(fa(b)&&(b=b()),a=this.f(a,b,c,uc.prototype.log),b="log:"+a.d,s.console&&(s.console.timeStamp?s.console.timeStamp(b):s.console.markTimeline&&s.console.markTimeline(b)),s.msWriteProfilerMark&&s.msWriteProfilerMark(b),b=this;b;){c=b;var d=a;if(c.a)for(var e=0,f=void 0;f=c.a[e];e++)f(d);b=b.c}}; -uc.prototype.f=function(a,b,c,d){a=new sc(a,String(b),this.e);if(c){a.b=c;var e;d=d||uc.prototype.f;try{var f;var g=ba("window.location.href");if(w(c))f={message:c,name:"Unknown error",lineNumber:"Not available",fileName:g,stack:"Not available"};else{var h,k;b=!1;try{h=c.lineNumber||c.sc||"Not available"}catch(m){h="Not available",b=!0}try{k=c.fileName||c.filename||c.sourceURL||s.$googDebugFname||g}catch(n){k="Not available",b=!0}f=!b&&c.lineNumber&&c.fileName&&c.stack&&c.message&&c.name?c:{message:c.message|| -"Not available",name:c.name||"UnknownError",lineNumber:h,fileName:k,stack:c.stack||"Not available"}}e="Message: "+va(f.message)+'\nUrl: '+f.fileName+"\nLine: "+f.lineNumber+"\n\nBrowser stack:\n"+va(f.stack+"-> ")+"[end]\n\nJS stack traversal:\n"+va(oc(d)+"-> ")}catch(q){e="Exception trying to expose exception! You win, we lose. "+q}a.a=e}return a};var Hc={},Ic=null;function Jc(){Ic||(Ic=new uc(""),Hc[""]=Ic,Ic.b=Ac)} -function Kc(){Jc();return Ic}function G(a){Jc();var b;if(!(b=Hc[a])){b=new uc(a);var c=a.lastIndexOf("."),d=a.substr(c+1),c=G(a.substr(0,c));c.d||(c.d={});c.d[d]=b;b.c=c;Hc[a]=b}return b};function Lc(a,b,c){a&&a.log(b,c,void 0)}function Mc(a,b){a&&a.log(xc,b,void 0)}function H(a,b){a&&a.log(yc,b,void 0)}function Nc(a,b){a&&a.log(Bc,b,void 0)};function I(a,b){a&&a.log(Dc,b,void 0)}function J(a,b){a&&a.log(Cc,b,void 0)};function Oc(a,b){B.call(this,0,b);this.e=[]}z(Oc,B);Oc.prototype.Va=function(a,b){this.e.push([a,b]);return this};Oc.prototype.callback=function(a){this.e.length=0;Oc.r.callback.call(this,a)};Oc.prototype.k=function(a){this.e.length=0;Oc.r.k.call(this,a)};B.prototype.done=B.prototype.C;B.prototype.fail=B.prototype.Db;B.prototype.always=B.prototype.Cb;Oc.prototype.then=Oc.prototype.then;function K(a,b,c,d){this.lower=a;this.upper=b;this.lowerOpen=!!c;this.upperOpen=!!d;fa(Object.freeze)&&Object.freeze(this)}K.prototype.lower=void 0;K.prototype.upper=void 0;K.prototype.toJSON=function(){return Pc(this)};function Qc(a){return Rc(a)}K.only=function(a){return new K(a,a,!1,!1)};K.bound=function(a,b,c,d){return new K(a,b,c,d)};K.upperBound=function(a,b){return new K(void 0,a,void 0,!!b)};K.lowerBound=function(a,b){return new K(a,void 0,!!b,void 0)}; -function Sc(a){var b;if(u(a))b=sb(a),b.push("\uffff");else if(w(a))b=a+"\uffff";else if(ea(a))b=a+2.220460492503131E-16,a-=2.220460492503131E-16;else return K.only(a);return K.bound(a,b,!1,!0)}function Pc(a){a=a||{};return{lower:a.lower,upper:a.upper,lowerOpen:a.lowerOpen,upperOpen:a.upperOpen}} -function Rc(a){return null!=a?null!=a.upper&&null!=a.lower?Tc.bound(a.lower,a.upper,!!a.lowerOpen,!!a.upperOpen):null!=a.upper?Tc.upperBound(a.upper,a.upperOpen):null!=a.lower?Tc.lowerBound(a.lower,a.lowerOpen):null:null}function Uc(a){if(a instanceof K)return"";if(null!=a){if(y(a)){for(var b in a){var c;if(c=a.hasOwnProperty(b))c=!(0<=jb(["lower","upper","lowerOpen","upperOpen"],b));if(c)return'invalid attribute "'+b+'" in key range object'}return""}return"key range must be an object"}return""} -K.prototype.ba=function(a){var b=this.lower,c=this.upper,d=this.lowerOpen,e=this.upperOpen;null!=a.lower&&(null==this.lower||a.lower>=this.lower)&&(b=a.lower,d=a.lowerOpen||this.lowerOpen);null!=a.upper&&(null==this.upper||a.upper<=this.upper)&&(c=a.upper,e=a.upperOpen||this.upperOpen);return K.bound(b,c,d,e)};function Vc(a){if(!a)return"";var b=a.lowerOpen?"(":"[";null!=a.lower&&(b+=a.lower+", ");null!=a.upper&&(b+=a.upper);return b+=a.upperOpen?")":"]"} -function Wc(a,b,c,d,e){if(c)if(c.lowerOpen||c.upperOpen||null==c.lower||null==c.upper||0!==L(c.lower,c.upper)){if(null!=c.lower){var f=c.lowerOpen?" > ":" >= ";d.push(a+f+"?");e.push(Xc(c.lower,b))}null!=c.upper&&(f=c.upperOpen?" < ":" <= ",d.push(a+f+"?"),e.push(Xc(c.upper,b)))}else d.push(a+" = ?"),e.push(Xc(c.lower,b))} -function Yc(a,b,c,d){var e,f,g,h;if("starts"==a||"^"==a)return Sc(b);if("<"==a||"<="==a)e=b,g="<"==a;else if(">"==a||">="==a)f=b,h=">"==a;else if("="==a||"=="==a)e=f=b;else throw new C("invalid op: "+a);if("<"==c||"<="==c)e=d,g="<"==c;else if(">"==c||">="==c)f=d,h=">"==c;else if(t(c))throw new C("invalid op2: "+c);return K.bound(f,e,h,g)}var Tc=s.IDBKeyRange||s.webkitIDBKeyRange||K;function Zc(a,b){var c,d;2==arguments.length&&w(arguments[1])?(c=!0,d=arguments[1].split(".")):d=(c=v(b))?b:arguments;for(c=c?0:1;c=f?c.write(f+1):16510>=f?(f-=127,c.write(128|f>>8,f&255)):c.write(192|f>>10, -f>>2|255,(f|3)<<6)}c.write(0)}else if(e===ad)a.write(0);else return"";c=0}for(b=a.a.length;"00"===a.a[--b];);a.a.length=++b;return a.toString()}function ed(a){for(var b=[],c=b,d=[],e,f,g=new fd(a);null!=gd(g);)if(0===g.a)c=d.pop();else{if(null===g.a)break;do{e=g.a/4|0;a=g.a%4;for(var h=0;hc&&(d=1,c=-c);f=0;if(2.2250738585072014E-308<=c){for(e=c;1>e;)f--,e*=2;for(;2<=e;)f++,e/=2;e=f+1023}f=e?Math.floor(4503599627370496*(c/Math.pow(2,f)-1)):Math.floor(c/4.9E-324)}else e=2047,isNaN(c)?f=0x8000000000000:-Infinity===c&&(d=1);c=d;d=e;e=f;c&&(e=0xfffffffffffff-e,d=2047-d);a.write((c?0:128)|d>>4);a.write((d&15)<<4|0|e/281474976710656);e%=281474976710656;c=0|e/4294967296;a.write(c>>8,c&255);e%=4294967296;c=0|e/65536;a.write(c>> -8,c&255);c=e%65536;a.write(c>>8,c&255)}function hd(a){var b=gd(a)|0,c=b>>7?!1:!0,d=c?-1:1,e=(b&127)<<4,b=gd(a)|0,e=e+(b>>4);c&&(e=2047-e);for(var b=[c?15-(b&15):b&15],f=6;f--;)b.push(c?255-(gd(a)|0):gd(a)|0);a=0;for(f=7;f--;)a=a/256+b[f];a/=16;return 0===a&&0===e?0:(a+1)*Math.pow(2,e-1023)*d} -function id(a){for(var b=[],c=0,d=0,e=0,f,g;;){f=gd(a);if(0===f||null==f)break;0===c?(g=f>>6,2>g&&!isNaN(f)?b.push(String.fromCharCode(f-1)):(c=g,d=f<<10,e++)):2===c?(b.push(String.fromCharCode(d+f+127)),c=d=e=0):2===e?(d+=f<<2,e++):(b.push(String.fromCharCode(d|f>>6)),c=d=e=0)}return b.join("")}function fd(a){this.a=null;this.b=a;this.c=this.b.length-1;this.index=-1}function gd(a){return a.a=a.indexd?1:c==d?0:-1};function kd(a,b,c,d,e){if(!(b instanceof K))if(w(b)&&t(c))b=Yc(b,c,d,e);else if(null!=b){if(!(b instanceof K))if(y(b))b=new K(b.lower,b.upper,b.lowerOpen,b.upperOpen);else throw new C("Invalid key range: "+b+" of type "+typeof b);}else b=null;this.a=b;this.ea=a}kd.prototype.ea="";kd.prototype.ba=function(a){if(this.ea!=a.ea)return null;a=null!=this.a&&null!=a.a?this.a.ba(a.a):this.a||a.a;return new kd(this.ea,a)};var ld={READ_ONLY:"readonly",READ_WRITE:"readwrite",VERSION_CHANGE:"versionchange"},md=s.IDBRequest&&"LOADING"in s.IDBRequest?s.IDBTransaction:s.webkitIDBRequest&&"LOADING"in s.webkitIDBRequest&&1===s.webkitIDBTransaction.READ_WRITE?s.webkitIDBTransaction:ld,nd=md.READ_ONLY,M=md.READ_WRITE,od=md.VERSION_CHANGE,pd=s.indexedDB||s.mozIndexedDB||s.webkitIndexedDB||s.moz_indexedDB||s.msIndexedDB;function qd(){0!=rd&&(sd[this[ga]||(this[ga]=++ha)]=this)}var rd=0,sd={};qd.prototype.xa=!1;qd.prototype.kb=function(){if(!this.xa&&(this.xa=!0,this.$(),0!=rd)){var a=this[ga]||(this[ga]=++ha);delete sd[a]}};qd.prototype.$=function(){if(this.Va)for(;this.Va.length;)this.Va.shift()()};function td(a,b,c,d){qd.call(this);this.b=c;this.m=c.getName();this.I=void 0;this.f=!1;this.p=null;this.d=a;this.Mb=b;this.ma=0;this.N=this.v=!1;this.u=d||4;this.h=this.c=this.a=void 0;this.i=function(){throw new Jb;};this.l=function(){throw new Jb;};this.Ya=function(){}}z(td,qd);l=td.prototype; -l.Bb=function(a,b,c,d,e){if(t(b)){a=this.b;var f,g=b;u(b)?(f=ud(a,b),g=b.join(", ")):f=vd(a,b);if(!f)throw new C('require index "'+g+'" not found in store "'+a.getName()+'"');this.I=f.getName()}this.f=w(this.I);this.p=c||null;this.ma=0;this.N=this.v=!1;this.reverse="prev"==d||"prevunique"==d;this.unique="nextunique"==d||"prevunique"==d;this.Q=d;this.wa=e;this.h=this.c=this.a=void 0};l.Q="";l.p=null;l.unique=!1;l.reverse=!1;l.wa=!0;l.logger=G("ydn.db.core.req.AbstractCursor"); -function wd(a,b){a.l(b);xd(a);a.v=!0}l.O=function(a,b,c){null==a&&(J(this.logger,this+" finished."),this.v=!0);this.a=a;this.c=b;this.h=c;this.ma++;this.v?(I(this.logger,this+" DONE."),this.i(),xd(this)):(I(this.logger,this+" new cursor position {"+(this.f?this.a+", "+this.c:this.a)+"}"),this.i(this.a))};l.$=function(){this.d=null};l.toString=function(){return"Cursor:"+this.m+(t(this.I)?":"+this.I:"")+"["+(this.d?"":"~")+this.Mb+"]"}; -function xd(a){null!=a.c?a.c=yd(a.c):a.c=void 0;null!=a.a?a.a=yd(a.a):a.a=void 0;a.Ya(a.N,a.a,a.c)}l.open=function(a,b,c,d){this.d=a;this.Mb=b;this.v=this.N=!1;this.a=c;this.c=d;this.openCursor(this.a,this.c)};function zd(a){a.N=!0;I(a.logger,a+": exit");xd(a)}l.mb=function(){return this.ma};l.nc=function(){return this.a};l.B=function(){return this.f?this.c:this.a};l.aa=function(){return this.wa?this.B():this.h};l.Da=function(){};l.ja=function(){}; -function Ad(a,b,c){I(a.logger,a+" restarting");a.v=!1;a.N=!1;a.openCursor(c,b)};function Bd(){};function N(a,b,c,d,e,f,g){if(!w(a))throw new TypeError("store name must be a string, but "+a+" found.");this.c=a;this.d=b;this.e=g;this.i=!!this.d;if(t(d)&&!da(d))throw new C("reverse value must be a boolean, but "+typeof d+" found");if(t(e)&&!da(e))throw new C("unique value must be a boolean, but "+typeof e+" found");if(t(f)&&!da(f))throw new C("key_only value must be a boolean, but "+typeof f+" found");this.a=t(f)?f:!!w(this.d);a="next";d&&e?a="prevunique":d?a="prev":e&&(a="nextunique");this.l= -a;if(d=Uc(c))throw new C("Invalid key range: "+d);this.b=Rc(c);this.f=Dd;this.o=NaN}z(N,Bd);N.prototype.a=!0;function Ed(a,b,c){if(3=a.g)?[]:b}$d.prototype.c=function(){return[]};function be(a){Error.captureStackTrace?Error.captureStackTrace(this,be):this.stack=Error().stack||"";a&&(this.message=String(a));this.name="ConstraintError"}z(be,Error);be.prototype.name="ConstraintError";be.prototype.toString=function(){return this.name+": "+this.message};function ce(a){Error.captureStackTrace?Error.captureStackTrace(this,ce):this.stack=Error().stack||"";a&&(this.message=String(a));this.name="ydn.db.InvalidKeyException"}z(ce,Error); -ce.prototype.toString=function(){return this.name+": "+this.message};function de(a){Error.captureStackTrace?Error.captureStackTrace(this,de):this.stack=Error().stack||"";a&&(this.message=String(a));this.name="ydn.db.VersionError"}z(de,Error);de.prototype.name="ydn.db.VersionError";de.prototype.toString=function(){return this.name+": "+this.message};function ee(a){Error.captureStackTrace?Error.captureStackTrace(this,ee):this.stack=Error().stack||"";a&&(this.message=String(a))}z(ee,Error); -ee.prototype.name="ydn.db.InternalError";function Ld(a){Error.captureStackTrace?Error.captureStackTrace(this,Ld):this.stack=Error().stack||"";a&&(this.message=String(a));this.name="InvalidStateError"}z(Ld,Error);function fe(a){Error.captureStackTrace?Error.captureStackTrace(this,fe):this.stack=Error().stack||"";a&&(this.message=String(a));this.name="InvalidAccessError"}z(fe,Error); -function ge(a){Error.captureStackTrace?Error.captureStackTrace(this,ge):this.stack=Error().stack||"";a&&(this.message=String(a));this.name="NotFoundError"}z(ge,Error);ge.prototype.name="NotFoundError";ge.prototype.toString=function(){return this.name+": "+this.message};function he(a,b){Error.captureStackTrace?Error.captureStackTrace(this,he):this.stack=Error().stack||"";b&&(this.message=String(b));this.message+=" :"+a.message+" ["+a.code+"]";this.name="SQLError"}z(he,Error); -he.prototype.toString=function(){return this.name+": "+this.message};function ie(a,b){Error.captureStackTrace?Error.captureStackTrace(this,ie):this.stack=Error().stack||"";b&&(this.message=String(b));this.message+=" :"+a.message;this.name="SecurityError"}z(ie,Error);ie.prototype.toString=function(){return this.name+": "+this.message};function je(a){Error.captureStackTrace?Error.captureStackTrace(this,je):this.stack=Error().stack||"";a&&(this.message=String(a));this.name="ydn.db.SqlParseError"} -z(je,Error);function ke(a){Error.captureStackTrace?Error.captureStackTrace(this,ke):this.stack=Error().stack||"";a&&(this.message=String(a));this.name="ydn.db.TimeoutError"}z(ke,Error);function le(a,b,c){var d;if(y(a))d=a.store,b=a.id,null!=a.parent&&(c=new le(a.parent));else if(t(b))d=a;else if(d=a.lastIndexOf("^|"),b=a,0a.J.length)b=!1;else{for(c=0;c=this.i)throw new Ib("Exceed maximum number of transactions of "+this.i);this.l=!0;this.j().transaction(function(c){var d=h.b;d.a=c;d.d=!1;d.J=b;d.mode=g;d.b++;d.F=null;k=h.A();Nc(h.logger,k+" BEGIN "+U(b)+" "+g);a(h);for(a=null;ve(h);)c=h.c.shift(),c.F&&h.e.push(c.F),I(h.logger,"pop tx queue"+(h.c.length+1)+" reusing T"+h.d),c.Ma()},e,g,function(a,b){Nc(h.logger,k+" "+a);var c=h.b;c.a?(c.a=null,c.J=null,c.mode=null,fa(c.F)&&c.F(a,b),c.F=null):H(c.logger,c+" has no TX to be unlocked for "+ -a);for(c=0;cc.length)throw new C('at least one valid key required in key list "'+ -Ke(a)+'"');d=this.a.request("rm:keys",c,M);Q(d,function(){W(this).vb(d,a)},this)}else throw new C('first argument requires store name, key (ydn.db.Key) or list of keys (array) , but "'+Ke(a)+'" ('+ca(a)+") found.");return d};l.toString=function(){return"DbOperator:"+this.j().getName()};function Ze(a,b,c){ze.call(this,a,b,c)}z(Ze,Ie);l=Ze.prototype;l.logger=G("ydn.db.core.DbOperator");l.get=function(a,b){if(a instanceof N){var c=a.c,d=X(this.b,c);if(!d)throw new C('store "'+c+'" not found.');var e=a.d;if(t(e)&&!Pe(d,e))throw new C('index "'+e+'" not found in store "'+c+'".');J(this.logger,"getByIterator:"+a);var f=this.a.request("get:iter",[c]);Q(f,function(){$e(this,5,f,a,1)},this);return f}return Ze.r.get.call(this,a,b)}; -l.keys=function(a,b,c,d,e,f,g){if(a instanceof N){var h=100;if(ea(b)){if(h=b,1>h)throw new C("limit must be a positive value, but "+b);}else if(t(b))throw new C("limit must be a number, but "+b);if(t(c))throw new C("offset must not be specified");J(this.logger,"keysByIterator:"+a);var k=this.a.request("keys:iter",[a.c]);Q(k,function(){a.i?$e(this,1,k,a,h):$e(this,2,k,a,h)},this);return k}return Ze.r.keys.call(this,a,b,c,d,e,f,g)}; -l.count=function(a,b,c,d){if(a instanceof N){if(t(b)||t(c))throw new C("too many arguments.");J(this.logger,"countIterator:"+a);var e=this.a.request("count",[a.c]);Q(e,function(){$e(this,6,e,a)},this);return e}return Ze.r.count.call(this,a,b,c,d)}; -l.w=function(a,b,c,d,e,f){if(a instanceof N){var g;if(ea(b)){if(g=b,1>g)throw new C("limit must be a positive value, but "+g);}else if(t(b))throw new C("limit must be a number, but "+b);if(t(c))throw new C("offset must not be specified");J(this.logger,"listByIterator:"+a);var h=this.a.request("values:iter",[a.c]);Q(h,function(){a.a?$e(this,2,h,a,g):$e(this,4,h,a,g)},this);return h}return Ze.r.w.call(this,a,b,c,d,e,f)}; -l.Ab=function(a,b,c){if(!u(b))throw new C("iterators argument must be an array, but "+b+" of type "+typeof b+" found");for(var d=0;d",r=g?r+" ":r+"= ";g=Xc(f,n.type);h=Xc(h,a.type);n="";e?(a=tf(a,c,b,q,e,k,m),a.n+=" AND ",n=d+r+"?",c.push(g)):(e=k?K.upperBound(f,!0):K.lowerBound(f,!0),a=tf(a,c,b,q,e,k,m),n=a.n,a.n="");a.n+="("+n+" OR ("+d+" = ? AND "+p+r+"?))";c.push(g);c.push(h);return"SELECT "+a.select+" FROM "+a.M+" WHERE "+a.n+(a.group?" GROUP BY "+a.group:"")+" ORDER BY "+a.P}l.clone=function(){return Ce(this.toJSON())}; -l.index=function(a){return this.a[a]||null};function vd(a,b){return nb(a.a,function(a){return a.getName()==b})}function ud(a,b){for(var c=0;c -c)throw new C("Invalid version: "+c+" ("+a+")");isNaN(c)&&(c=void 0)}if(t(b)&&(!u(b)||0c.a.length)c=!1;else{for(var d=0;d=this.h)throw new Ib("Exceed maximum number of transactions of "+this.h);this.j().transaction(e,b,h,f)}}; -l.request=function(a,b,c,d){var e=new O(a),f=this;this.ha(function(a){f.a++;Id(e,a,f.A()+"R"+f.a)},b,c||nd,function(a,b){Kd(e);d&&d(a,b)});return e};l.W=function(a,b,c,d,e){var f=this,g;this.ha(function(c){f.a++;g=f.A()+"R"+f.a;J(f.logger,g+" BEGIN");b(c,g,function(b,d){f.c=c;g=f.A()+"R"+f.a;d?(J(f.logger,g+" ERROR"),a.k(b)):(J(f.logger,g+" SUCCESS"),a.callback(b));f.c=null});b=null;J(f.logger,g+" END")},c,d,e)};l.toString=function(){return"Parallel:"+this.f+":"+this.A()+(this.c?"*":"")};function Of(a,b){Nf.call(this,a,b,re)}z(Of,Nf);l=Of.prototype;l.logger=G("ydn.db.tr.AtomicParallel");l.Qb=function(){return!1};l.request=function(a,b,c){var d,e,f,g=this,h=Of.r.request.call(this,a,b,c,function(a,b){Kd(h);J(g.logger,"transaction "+a);if(d)"complete"!=a&&(f=!0,e=b),d(e,f);else{var c=new ke;P(h,c,!0)}});Md(h,function(a,b,c){f=b;e=a;d=c});return h}; -l.W=function(a,b,c,d,e){var f,g,h=new B;yb(h,function(a){g=!1;f=a},function(a){g=!0;f=a});Of.r.W.call(this,h,b,c,d,function(b,c){if("complete"!=b)a.k(c);else if(!0===g)a.k(f);else if(!1===g)a.callback(f);else{var d=new ke;a.k(d)}e&&(e(b,c),e=void 0)})};l.toString=function(){return"Atomic"+Of.r.toString.call(this)};function Pf(a,b,c){T.call(this,a,b,c);this.na=0;a=!0;b=re;c&&(t(c.isSerial)&&(a=!!c.isSerial),c.policy&&(b=c.policy));c=Qf(this,b,a);this.i=Qf(this,"atomic",!1);this.c=this.la(c,this.i)}z(Pf,T);l=Pf.prototype;l.na=0;l.Rb=function(a,b,c,d,e,f){a=a||re;var g;"readonly"==d?g=nd:"readwrite"==d&&(g=M);a=Qf(this,a,b,c,g,e);return this.la(a,f?null:this.i)};l.la=function(a){return new ze(this,this.a,a)}; -function Qf(a,b,c,d,e,f){if(c){if("multi"==b||"repeat"==b||"all"==b||b==re)return new te(a,a.na++,b,d,e,f);if("atomic"==b)return new ye(a,a.na++);throw new C('Invalid requestType "'+b+'"');}if("multi"==b||"repeat"==b||"all"==b||b==re)return new Nf(a,a.na++,b,d,e,f);if("atomic"==b)return new Of(a,a.na++);throw new C('Invalid requestType "'+b+'"');} -l.Zb=function(a,b,c){if(3r&&rk&&k=b.length)0b&&bf&&f=a)return Yf(c,b)}var c=this,d=this.e?-1:0;this.reverse?Zf(this.ca,b,this.e):$f(this.ca,b,this.e)}; -l.ja=function(a){if(null!=a){var b=this,c=new Z(a),d=function(c){b.e=c;if(!c)return Yf(b,c);var d=L(c.value.key,a);if(b.reverse){if(1!=d)return Yf(b,c)}else if(-1!=d)return Yf(b,c)};this.reverse?Zf(this.ca,d,c):$f(this.ca,d,c)}else this.advance(1)};function ag(a){setTimeout(function(){a.o.a?(a.o.a=!1,a.O(a.a,a.c,a.h),ag(a)):(a.g(),a.g=null)},4)} -function Yf(a,b){if(a.e=b){var c=b.value;if(a.p)if(a.reverse||null==a.p.upper)a.reverse&&null!=a.p.lower&&(d=L(c.key,a.p.lower),-1==d||0==d&&a.p.lowerOpen)&&(a.e=null);else{var d=L(c.key,a.p.upper);if(1==d||0==d&&a.p.upperOpen)a.e=null}if(a.e){if(a.unique&&null!=a.a&&null!=c.key&&0==L(a.a,c.key))return;a.a=c.key;a.c=a.f?c.a:a.a;4==a.u&&(a.wa?a.h=a.c:a.h=bg(a.Za,a.c))}}a.e||(a.a=void 0,a.c=void 0,a.h=void 0);return a.o.a=!0} -l.openCursor=function(a,b){var c=null;if(this.p)if(this.reverse){var d=this.f?"\uffff":void 0;null!=this.p.upper&&(c=new Z(this.p.upper,d))}else null!=this.p.lower&&(c=new Z(this.p.lower));null!=a&&(c=this.f?new Z(a,b):new Z(a));this.g=this.d.j(function(b){function d(b){var e=b.value,f=e.key;if(b&&null!=f)if(null!=a){if(0==cg(c,e))return}else if(this.p&&(!this.reverse&&this.p.lowerOpen&&null!=this.p.lower&&(e=L(f,this.p.lower),0==e)||this.reverse&&this.p.upperOpen&&null!=this.p.upper&&(e=L(f,this.p.upper), -0==e)))return;return Yf(this,b)}this.Za=dg(b,this.m);this.ca=eg(this.Za,this.I);this.reverse?Zf(this.ca,ka(d,this),c):$f(this.ca,ka(d,this),c);ag(this)},this)};l.clear=function(){throw new Hb;};l.Da=function(){throw new Hb;};l.toString=function(){return"Simple"+Wf.r.toString.call(this)};function fg(a,b,c){a=["ydn.db",a];t(b)&&(a.push(b),t(c)&&(a.push(c),t(void 0)&&a.push(bd(void 0))));return a.join("^|")};function Z(a,b){this.key=a;this.a=b}Z.prototype.toString=function(){return"ydn.db.con.simple.Node("+this.key+(null!=this.a?", "+this.a+")":")")};function cg(a,b){var c=L(a.key,b.key);return 0===c?null!=a.a?null!=b.a?L(a.a,b.a):1:null!=b.a?-1:0:c};function gg(a){this.L=a||hg}function hg(a,b){return String(a)String(b)?1:0}l=gg.prototype;l.s=null;l.L=null;l.U=null;l.T=null; -l.add=function(a){if(null==this.s)return this.T=this.U=this.s=new ig(a),!0;var b=null;jg(this,function(c){var d=null,e=this.L(c.value,a);0e&&(d=c.right,null==c.right&&(b=new ig(a,c),c.right=b,c==this.T&&(this.T=b)));return d});b&&(jg(this,function(a){a.count++;return a.parent},b.parent),kg(this,b.parent));return!!b}; -function lg(a,b){jg(a,function(a){var d=null,e=this.L(a.value,b);0e?d=a.right:mg(this,a);return d})}l.clear=function(){this.T=this.U=this.s=null};l.contains=function(a){var b=!1;jg(this,function(c){var d=null,e=this.L(c.value,a);0e?d=c.right:b=!0;return d});return b};l.indexOf=function(a){var b=-1,c=0;jg(this,function(d){var e=this.L(d.value,a);if(0e)return c++,d.right;b=c;return null});return b}; -l.mb=function(){return this.s?this.s.count:0};l.Oa=function(){var a=[];ng(this,function(b){a.push(b)});return a};function ng(a,b){if(a.s){var c,d=c=og(a);for(c=c.left?c.left:c;null!=d;)if(null!=d.left&&d.left!=c&&d.right!=c)d=d.left;else{if(d.right!=c&&b(d.value))break;var e=d,d=null!=d.right&&d.right!=c?d.right:d.parent;c=e}}}function jg(a,b,c){for(c=c?c:a.s;c&&null!=c;)c=b.call(a,c)} -function kg(a,b){jg(a,function(a){var b=a.left?a.left.height:0,e=a.right?a.right.height:0;1this.L(a.value,c)?b=a.right:d=a;return b}),!d)return}else d=og(a);a=d;for(var e=d.left?d.left:d;null!=a;)if(null!=a.left&&a.left!=e&&a.right!=e)a=a.left;else{if(a.right!=e&&b(a))return;var f=a;a=null!=a.right&&a.right!=e?a.right:a.parent;e=f}b(null)}} -function Zf(a,b,c){if(a.s){var d;if(c instanceof ig)d=c;else if(c){if(jg(a,ka(function(a){var b=null;0this.L(a.value,c)&&(b=a.right),d=a);return b},a)),!d)return}else d=tg(a);a=d;for(var e=d.right?d.right:d;null!=a;)if(null!=a.right&&a.right!=e&&a.left!=e)a=a.right;else{if(a.left!=e&&b(a))return;var f=a;a=null!=a.left&&a.left!=e?a.left:a.parent;e=f}b(null)}};function vg(a,b,c){this.f=a;this.storage=b;this.b=c;this.a={};a=this.b.keyPath;this.c=u(a)?a.join(","):a||"_ROWID_";this.a[this.c]=null;this.d=fg(this.f,this.b.getName(),this.c)+"^|"} -function eg(a,b){var c=b||a.c;if(!a.a[c]){a.a[c]=new ug(cg);for(var d=a.storage.length,e=0;eg.length&&(g.push(b.key),h.push(bf(p))))}},d);if(10>g.length)for(c=0;c=f)return!0}}var n=[],q,p=!!k&&null!=k[0];c=c||a.c;var r=c!=a.c;c=eg(a,c);var x=null,A=null;t(g)||(g=0);var E=-1,D=!1,F=!1;null!=d&&(null!=d.lower&&(x=r&&e?new Z(d.lower,"\uffff"):new Z(d.lower)),null!=d.upper&&(A=r&&!e?new Z(d.upper,"\uffff"):new Z(d.upper)),D=!!d.lowerOpen,F=!!d.upperOpen);if(p){e?F=!0:D=!0;d=k[0];var R=t(k[1])?k[1]:"\uffff";e?A=r?new Z(d,R):new Z(d):x=r?new Z(d,R):new Z(d)}e? -Zf(c,m,A):$f(c,m,x);return n}vg.prototype.fa=function(a,b,c,d,e){return Bg(this,2,a,b,c,d,e)};vg.prototype.toString=function(){return"ydn.db.con.simple.Store:"+this.f+":"+this.b.getName()};function Cg(){this.clear()}l=Cg.prototype;l.ib=function(){return this};l.setItem=function(a,b){t(this.a[a])||(this.keys.push(a.toString()),this.length=this.keys.length);this.a[a]=b};l.getItem=function(a){return t(this.a[a])?this.a[a]:null};l.removeItem=function(a){delete this.a[a];qb(this.keys,a.toString());this.length=this.keys.length};l.length=0;l.key=function(a){a=this.keys[a];return t(a)?this.a[a]:null};l.clear=function(){this.a={};this.keys=[];this.length=0};function Dg(a){this.e=a||new Cg;this.f={}}function dg(a,b){var c=X(a.a,b);if(c)a.f[b]||(a.f[b]=new vg(a.d,a.c,c));else throw new Jb('store name "'+b+'" not found.');return a.f[b]}Dg.prototype.K=function(a){var b=this;setTimeout(function(){var c=fg(b.d),c=b.c.getItem(c),c=new Df(c);a(c)},10)};function Eg(a,b){this.b=a;this.a=b}Eg.prototype.j=function(a,b){var c=this.b;setTimeout(function(){a.call(b,c)},4);var d=this;return function(){d.a("complete",null);d.a=null;d.b=null}};function Fg(a,b){this.a=b}z(Fg,ne);l=Fg.prototype;l.logger=G("ydn.db.crud.req.SimpleStore");l.sb=function(a,b,c){this.S(a,!0,!1,null,b,c)}; -l.S=function(a,b,c,d,e,f){I(this.logger,S(a)+" "+(b?"put":"add")+"Object"+(c?"":"s "+e.length+" objects"));var g=a.a.j(function(h){var k;if(c)k=dg(h,d),h=f?f[0]:void 0,h=Xf(k,h,e[0],!b),null!=h?P(a,h):(k=Ke(h),k=new be(k),P(a,k,!0));else{for(var m=d,n=[],q=!1,p=f||{},r=0;rp.rowsAffected&&(r=!0,aa=new be(aa+" no-op"));for(var ib=0,Cd=h.a.length;ibb&&b -b&&bh&&h=b.length)P(a,f,g);else{var k=X(e.a,b[h].m),m=Xc(b[h].id,k.type),n=" WHERE "+k.d+" = ?",q="DELETE FROM "+uf(k)+n,p=S(a)+" SQL: "+q+" PARAMS: "+[m];d.executeSql(q,[m],function(){f++;c(h)},function(a,b){H(e.logger,"error: "+p+b.message);g=!0;c(h);return!1});h++;for(var q=function(b){b="ydn.db.me:"+k.getName()+":"+b.getName();b="DELETE FROM "+Ha(b)+n;I(e.logger,S(a)+NaN+b);d.executeSql(b,[m])},r=0,x=k.a.length;r=|=|>|<)(.+)/i;if(0d?c:d:d}};function $g(a,b){this.a=b}z($g,Uf);$g.prototype.logger=G("ydn.db.sql.req.IndexedDb");$g.prototype.executeSql=function(a,b,c){if(c=Rg(b,c))throw new je(c);c=sb(b.a);if(1==c.length){var d=X(this.a,c[0]);if(!d)throw new ge(c[0]);var e=Tg(b);if(e)for(var f=0;fh.version){var k=h.setVersion(b.version);k.a=function(a){H(e.logger,"migrating from "+h.version+" to "+b.version+" failed.");d(null,a)};k.onsuccess=function(){c(h,k.transaction,!0)}}else b.version==h.version?Lc(e.logger,Cc,"database version "+h.version+" ready to go"):H(e.logger,"connected database version "+h.version+" is higher than requested version."),e.K(function(a){a=Ef(b,a,!1,!0);0g.version)c(NaN,new de("existing version "+g.version+" is larger than "+this.a.version));else{var h= -this.a.version;this.b=t(h)?h:g.version+1;for(h=0;hthis.d)throw Error("[goog.structs.SimplePool] Initial cannot be greater than max");for(var c=0;ca&&(b=" ");100>a&&(b=" ");10>a&&(b=" ");return b+a}function vh(a){a=Math.round(a);return String(100+a/1E3%60).substring(1,3)+"."+String(1E3+a%1E3).substring(1,4)}new qh;var wh=!cc||cc&&9<=nc,xh=cc&&!lc("9");!ec||lc("528");dc&&lc("1.9b")||cc&&lc("8")||bc&&lc("9.5")||ec&&lc("528");dc&&!lc("8")||cc&&lc("9");function yh(a){yh[" "](a);return a}yh[" "]=function(){};function zh(a,b){df.call(this,a?a.type:"");this.a=this.target=null;this.clientY=this.clientX=0;this.b=null;if(a){this.type=a.type;this.target=a.target||a.srcElement;this.a=b;var c=a.relatedTarget;if(c&&dc)try{yh(c.nodeName)}catch(d){}this.clientX=void 0!==a.clientX?a.clientX:a.pageX;this.clientY=void 0!==a.clientY?a.clientY:a.pageY;this.b=a;a.defaultPrevented&&this.preventDefault()}}z(zh,df); -zh.prototype.preventDefault=function(){zh.r.preventDefault.call(this);var a=this.b;if(a.preventDefault)a.preventDefault();else if(a.returnValue=!1,xh)try{if(a.ctrlKey||112<=a.keyCode&&123>=a.keyCode)a.keyCode=-1}catch(b){}};var Ah="closure_listenable_"+(1E6*Math.random()|0),Bh=0;function Ch(a,b,c,d,e){this.ga=a;this.a=null;this.src=b;this.type=c;this.Fa=!!d;this.Pa=e;this.key=++Bh;this.oa=this.Ea=!1}function Dh(a){a.oa=!0;a.ga=null;a.a=null;a.src=null;a.Pa=null};function Eh(a){this.src=a;this.a={};this.b=0}Eh.prototype.add=function(a,b,c,d,e){var f=a.toString();a=this.a[f];a||(a=this.a[f]=[],this.b++);var g=Fh(a,b,d,e);-1c.keyCode||void 0!=c.returnValue)){a:{var f=!1;if(0==c.keyCode)try{c.keyCode=-1;break a}catch(g){f=!0}if(f||void 0==c.returnValue)c.returnValue=!0}c=[];for(f=d.a;f;f=f.parentNode)c.push(f);for(var f=a.type,h=c.length-1;0<=h;h--)d.a=c[h],e&=Uh(c[h],f,!0,d);for(h=0;h>>0);function Lh(a){return fa(a)?a:a[Wh]||(a[Wh]=function(b){return a.handleEvent(b)})};function Xh(){qd.call(this);this.Z=new Eh(this);this.b=this;this.a=null}z(Xh,qd);Xh.prototype[Ah]=!0;Xh.prototype.addEventListener=function(a,b,c,d){Kh(this,a,b,c,d)};Xh.prototype.removeEventListener=function(a,b,c,d){Sh(this,a,b,c,d)};Xh.prototype.$=function(){Xh.r.$.call(this);if(this.Z){var a=this.Z,b=0,c;for(c in a.a){for(var d=a.a[c],e=0;ee;e++){var f;if(f="_ROWID_"!=c[e]){f=b;var g=c[e];f=!(t(f.keyPath)&&(1== -f.e.length?f.keyPath===g:v(g)&&tb(f.e,g)))}if(f&&c[e]!=d)throw new C('select field name must be "'+d+'", but "'+c[e]+'" found.');}d=3}else throw new C("Selecting more than 2 field names is not supported, but "+c.length+" fields selected.");return new $(this.db,this.b,d,a)};l.jc=function(a){a=a||100;var b=4,c=oi(this);this.c&&this.c[0]&&(c=c.Ob(this.c[0],this.c[1]));if(2==this.type||3==this.type||1==this.type)b=this.type;a=cf(this.db,b,c,a);a.C(function(){"rest"==c.f&&(this.c=[c.g,c.h])},this);return a}; -l.nb=function(){return[this.a.clone()]};function oi(a){return hi(a.a,!(2==a.type||3==a.type||1==a.type))} -l.Yb=function(a,b){var c=oi(this);c.a&&(c=new N(c.c,c.d,c.b,c.V(),c.qa(),!1,c.e));if(1>arguments.length)throw new C("too few arguments");if(2==arguments.length){if(!w(a)&&u(a)){if(!u(b))throw new C("an array is expected for second argument but, "+Ke(b)+" of type "+typeof b+" found");if(a.length!=b.length)throw new C("length of two input arguments must be equal but, "+a.length+" and "+b.length+" found");}}else if(1==arguments.length){if(!y(a))throw new C("an object is expected but, "+Ke(a)+" of type "+ -typeof a+" found");}else throw new C("too many arguments");var d=this.db.open(function(c){var f=c.aa();if(w(a))$c(f,a,b);else if(u(a))for(var g=0;ga?"0"+a:String(a)}function ui(a,b){var c=(a.e-b)/1E3,d=c.toFixed(3),e=0;if(1>c)e=2;else for(;100>c;)e++,c*=10;for(;0',ua(Da(va(a.d))));this.b&&a.b&&c.push("
",ua(Da(a.a||"")));c.push("");c.push("
");return c.join("")};function wi(a){ri.call(this,a)}z(wi,ri); -wi.prototype.c=function(a){var b=[];b.push(this.d," ");this.a&&b.push("[",si(a),"] ");b.push("[",ui(a,this.f.get()),"s] ");b.push("[",a.c,"] ");b.push(a.d);this.b&&a.b&&b.push("\n",a.a);b.push("\n");return b.join("")};function xi(){this.f=ka(this.e,this);this.a=new wi;this.a.a=!1;this.b=this.a.b=!1;this.c="";this.d={}}xi.prototype.e=function(a){if(!this.d[a.c]){var b=this.a.c(a),c=yi;if(c)switch(a.f){case wc:zi(c,"info",b);break;case xc:zi(c,"error",b);break;case yc:zi(c,"warn",b);break;default:zi(c,"debug",b)}else window.opera?window.opera.postError(b):this.c+=b}};var yi=window.console;function zi(a,b,c){if(a[b])a[b](c);else a.log(c)};var Ai=!cc||cc&&9<=nc;!dc&&!cc||cc&&cc&&9<=nc||dc&&lc("1.9.1");cc&&lc("9");function Bi(a,b){Mb(b,function(b,d){"style"==d?a.style.cssText=b:"class"==d?a.className=b:"for"==d?a.htmlFor=b:d in Ci?a.setAttribute(Ci[d],b):0==d.lastIndexOf("aria-",0)||0==d.lastIndexOf("data-",0)?a.setAttribute(d,b):a[d]=b})}var Ci={cellpadding:"cellPadding",cellspacing:"cellSpacing",colspan:"colSpan",frameborder:"frameBorder",height:"height",maxlength:"maxLength",role:"role",rowspan:"rowSpan",type:"type",usemap:"useMap",valign:"vAlign",width:"width"}; -function Di(a,b,c){function d(c){c&&b.appendChild(w(c)?a.createTextNode(c):c)}for(var e=2;e");f=f.join("")}f=d.createElement(f);g&&(w(g)?f.className=g:u(g)?f.className=g.join(" "):Bi(f,g));2=this.a.scrollHeight-this.a.scrollTop-this.a.clientHeight,c=this.d.createElement("div");c.className="logmsg";c.innerHTML=this.b.c(a);this.a.appendChild(c);b&&(this.a.scrollTop=this.a.scrollHeight)};Hi.prototype.clear=function(){this.a.innerHTML=""};var Ii=null,Ji=null;G("ydn.debug");na("ydn.debug.log",function(a,b,c){if(ea(b))b=new vc("log",b);else if(w(b)){b=b.toUpperCase();if(!Fc){Fc={};for(var d=0,e;e=Ec[d];d++)Fc[e.value]=e,Fc[e.name]=e}b=Fc[b]||null}else b=Bc;G(a||"ydn").b=b;t(c)?Ji||(a=Ji=new Hi(c),!0!=a.c&&(c=Kc(),b=a.f,c.a||(c.a=[]),c.a.push(b),a.c=!0),Kc().b=yc):Ii||Ji||(a=Ii=new xi,!0!=a.b&&(c=Kc(),b=a.f,c.a||(c.a=[]),c.a.push(b),a.b=!0),Kc().b=yc)});na("ydn.db.Storage",$h);})(); - - //# sourceMappingURL=ydn.db-dev.js.map - \ No newline at end of file diff --git a/src/classes/site.ts b/src/classes/site.ts index dcf06ef29..54b2dd294 100644 --- a/src/classes/site.ts +++ b/src/classes/site.ts @@ -170,7 +170,7 @@ interface RequestQueueItem { /** * Class that represents a site (combination of site + user). * It will have all the site data and provide utility functions regarding a site. - * To add tables to the site's database, please use CoreSitesProvider.createTablesFromSchema. This will make sure that + * To add tables to the site's database, please use CoreSitesProvider.registerSiteSchema. This will make sure that * the tables are created in all the sites, not just the current one. */ export class CoreSite { @@ -233,6 +233,7 @@ export class CoreSite { protected requestQueueTimeout = null; protected tokenPluginFileWorks: boolean; protected tokenPluginFileWorksPromise: Promise; + protected oauthId: number; /** * Create a site. @@ -404,6 +405,15 @@ export class CoreSite { return !!this.loggedOut; } + /** + * Get OAuth ID. + * + * @return OAuth ID. + */ + getOAuthId(): number { + return this.oauthId; + } + /** * Set site info. * @@ -444,6 +454,24 @@ export class CoreSite { this.loggedOut = !!loggedOut; } + /** + * Set OAuth ID. + * + * @param oauth OAuth ID. + */ + setOAuthId(oauthId: number): void { + this.oauthId = oauthId; + } + + /** + * Check if the user authenticated in the site using an OAuth method. + * + * @return {boolean} Whether the user authenticated in the site using an OAuth method. + */ + isOAuth(): boolean { + return this.oauthId != null && typeof this.oauthId != 'undefined'; + } + /** * Can the user access their private files? * diff --git a/src/classes/utils/url.ts b/src/classes/utils/url.ts new file mode 100644 index 000000000..2539d19c6 --- /dev/null +++ b/src/classes/utils/url.ts @@ -0,0 +1,122 @@ +// (C) Copyright 2015 Moodle 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. + +/** + * Parts contained within a url. + */ +interface UrlParts { + + /** + * Url protocol. + */ + protocol?: string; + + /** + * Url domain. + */ + domain?: string; + + /** + * Url port. + */ + port?: string; + + /** + * Url path. + */ + path?: string; + + /** + * Url query. + */ + query?: string; + + /** + * Url fragment. + */ + fragment?: string; + +} + +/** + * Singleton with helper functions for urls. + */ +export class CoreUrl { + + // Avoid creating singleton instances. + private constructor() {} + + /** + * Parse parts of a url, using an implicit protocol if it is missing from the url. + * + * @param url Url. + * @return Url parts. + */ + static parse(url: string): UrlParts | null { + // Parse url with regular expression taken from RFC 3986: https://tools.ietf.org/html/rfc3986#appendix-B. + const match = url.trim().match(/^(([^:/?#]+):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/); + + if (!match) { + return null; + } + + // Split host into domain and port. + const host = match[4] || ''; + const [domain, port]: string[] = host.indexOf(':') === -1 ? [host] : host.split(':'); + + // Prepare parts replacing empty strings with undefined. + return { + protocol: match[2] || undefined, + domain: domain || undefined, + port: port || undefined, + path: match[5] || undefined, + query: match[7] || undefined, + fragment: match[9] || undefined, + }; + } + + /** + * Guess the Moodle domain from a site url. + * + * @param url Site url. + * @return Guessed Moodle domain. + */ + static guessMoodleDomain(url: string): string | null { + // Add protocol if it was missing. Moodle can only be served through http or https, so this is a fair assumption to make. + if (!url.match(/^https?:\/\//)) { + url = `https://${url}`; + } + + // Match using common suffixes. + const knownSuffixes = [ + '\/my\/?', + '\/\\\?redirect=0', + '\/index\\\.php', + '\/course\/view\\\.php', + '\/login\/index\\\.php', + '\/mod\/page\/view\\\.php', + ]; + const match = url.match(new RegExp(`^https?:\/\/(.*?)(${knownSuffixes.join('|')})`)); + + if (match) { + return match[1]; + } + + // If nothing else worked, parse the domain. + const urlParts = CoreUrl.parse(url); + + return urlParts && urlParts.domain ? urlParts.domain : null; + } + +} diff --git a/src/components/components.module.ts b/src/components/components.module.ts index fe663a255..e48f29dd6 100644 --- a/src/components/components.module.ts +++ b/src/components/components.module.ts @@ -25,7 +25,6 @@ import { CoreSplitViewComponent } from './split-view/split-view'; import { CoreIframeComponent } from './iframe/iframe'; import { CoreProgressBarComponent } from './progress-bar/progress-bar'; import { CoreEmptyBoxComponent } from './empty-box/empty-box'; -import { CoreSearchBoxComponent } from './search-box/search-box'; import { CoreFileComponent } from './file/file'; import { CoreFilesComponent } from './files/files'; import { CoreIconComponent } from './icon/icon'; @@ -40,7 +39,6 @@ import { CoreLocalFileComponent } from './local-file/local-file'; import { CoreSitePickerComponent } from './site-picker/site-picker'; import { CoreTabsComponent } from './tabs/tabs'; import { CoreTabComponent } from './tabs/tab'; -import { CoreRichTextEditorComponent } from './rich-text-editor/rich-text-editor'; import { CoreNavBarButtonsComponent } from './navbar-buttons/navbar-buttons'; import { CoreDynamicComponent } from './dynamic-component/dynamic-component'; import { CoreSendMessageFormComponent } from './send-message-form/send-message-form'; @@ -66,7 +64,6 @@ import { CoreBSTooltipComponent } from './bs-tooltip/bs-tooltip'; CoreIframeComponent, CoreProgressBarComponent, CoreEmptyBoxComponent, - CoreSearchBoxComponent, CoreFileComponent, CoreFilesComponent, CoreIconComponent, @@ -81,7 +78,6 @@ import { CoreBSTooltipComponent } from './bs-tooltip/bs-tooltip'; CoreSitePickerComponent, CoreTabsComponent, CoreTabComponent, - CoreRichTextEditorComponent, CoreNavBarButtonsComponent, CoreDynamicComponent, CoreSendMessageFormComponent, @@ -118,7 +114,6 @@ import { CoreBSTooltipComponent } from './bs-tooltip/bs-tooltip'; CoreIframeComponent, CoreProgressBarComponent, CoreEmptyBoxComponent, - CoreSearchBoxComponent, CoreFileComponent, CoreFilesComponent, CoreIconComponent, @@ -131,7 +126,6 @@ import { CoreBSTooltipComponent } from './bs-tooltip/bs-tooltip'; CoreSitePickerComponent, CoreTabsComponent, CoreTabComponent, - CoreRichTextEditorComponent, CoreNavBarButtonsComponent, CoreDynamicComponent, CoreSendMessageFormComponent, diff --git a/src/components/context-menu/context-menu.ts b/src/components/context-menu/context-menu.ts index 49f7a1e43..7dd28b31a 100644 --- a/src/components/context-menu/context-menu.ts +++ b/src/components/context-menu/context-menu.ts @@ -70,7 +70,7 @@ export class CoreContextMenuComponent implements OnInit, OnDestroy { */ ngOnInit(): void { this.icon = this.icon || 'more'; - this.ariaLabel = this.ariaLabel || this.title || this.translate.instant('core.info'); + this.ariaLabel = this.ariaLabel || this.title || this.translate.instant('core.displayoptions'); } /** diff --git a/src/components/file/file.scss b/src/components/file/file.scss index 9fa957c53..9671b89e7 100644 --- a/src/components/file/file.scss +++ b/src/components/file/file.scss @@ -13,11 +13,6 @@ ion-app.app-root { } } - .card-wp core-file + core-file > .item-wp.item-block > .item-inner, - core-file + core-file > .item-wp.item-block > .item-inner { - border-top: 1px solid $list-wp-border-color; - } - core-file > .item.item-block > .item-inner { border-bottom: 0; @include safe-area-padding(null, 0px, null, null); diff --git a/src/components/file/file.ts b/src/components/file/file.ts index 57c509107..22534fe16 100644 --- a/src/components/file/file.ts +++ b/src/components/file/file.ts @@ -20,6 +20,7 @@ import { CoreFileHelperProvider } from '@providers/file-helper'; import { CoreSitesProvider } from '@providers/sites'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype'; +import { CoreUrlUtilsProvider } from '@providers/utils/url'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreConstants } from '@core/constants'; @@ -57,16 +58,17 @@ export class CoreFileComponent implements OnInit, OnDestroy { protected timemodified: number; protected observer; - constructor(private sitesProvider: CoreSitesProvider, - private utils: CoreUtilsProvider, - private domUtils: CoreDomUtilsProvider, - private filepoolProvider: CoreFilepoolProvider, - private appProvider: CoreAppProvider, - private fileHelper: CoreFileHelperProvider, - private mimeUtils: CoreMimetypeUtilsProvider, - private eventsProvider: CoreEventsProvider, - private textUtils: CoreTextUtilsProvider, - private pluginFileDelegate: CorePluginFileDelegate) { + constructor(protected sitesProvider: CoreSitesProvider, + protected utils: CoreUtilsProvider, + protected domUtils: CoreDomUtilsProvider, + protected filepoolProvider: CoreFilepoolProvider, + protected appProvider: CoreAppProvider, + protected fileHelper: CoreFileHelperProvider, + protected mimeUtils: CoreMimetypeUtilsProvider, + protected eventsProvider: CoreEventsProvider, + protected textUtils: CoreTextUtilsProvider, + protected pluginFileDelegate: CorePluginFileDelegate, + protected urlUtils: CoreUrlUtilsProvider) { this.onDelete = new EventEmitter(); } @@ -104,6 +106,8 @@ export class CoreFileComponent implements OnInit, OnDestroy { this.observer = this.eventsProvider.on(eventName, () => { this.calculateState(); }); + }).catch(() => { + // File not downloadable. }); } } @@ -152,14 +156,14 @@ export class CoreFileComponent implements OnInit, OnDestroy { return; } - if (!this.canDownload) { + if (!this.canDownload || !this.state || this.state == CoreConstants.NOT_DOWNLOADABLE) { // File cannot be downloaded, just open it. if (this.file.toURL) { // Local file. this.utils.openFile(this.file.toURL()); } else if (this.fileUrl) { if (this.fileUrl.indexOf('http') === 0) { - this.utils.openOnlineFile(this.fileUrl); + this.utils.openOnlineFile(this.urlUtils.unfixPluginfileURL(this.fileUrl)); } else { this.utils.openFile(this.fileUrl); } diff --git a/src/components/ion-tabs/ion-tab.ts b/src/components/ion-tabs/ion-tab.ts index 0d656594f..313f55db7 100644 --- a/src/components/ion-tabs/ion-tab.ts +++ b/src/components/ion-tabs/ion-tab.ts @@ -16,9 +16,10 @@ import { Component, Optional, ElementRef, NgZone, Renderer, ComponentFactoryResolver, ChangeDetectorRef, ErrorHandler, OnInit, OnDestroy, ViewEncapsulation } from '@angular/core'; -import { Tab, App, Config, Platform, GestureController, DeepLinker, DomController } from 'ionic-angular'; +import { Tab, App, Config, Platform, GestureController, DeepLinker, DomController, NavOptions } from 'ionic-angular'; import { TransitionController } from 'ionic-angular/transitions/transition-controller'; import { CoreIonTabsComponent } from './ion-tabs'; +import { TransitionDoneFn } from 'ionic-angular/navigation/nav-util'; /** * Equivalent to ion-tab, but to be used inside core-ion-tabs. @@ -58,4 +59,35 @@ export class CoreIonTabComponent extends Tab implements OnInit, OnDestroy { this.parent.remove(this); } + + /** + * Push a page to the navigation stack. this similar to parent NavController, but perform some check to make + * sure one page won't open multiple time. + */ + push(page: any, params?: any, opts?: NavOptions, done?: TransitionDoneFn): Promise { + if (this.isTransitioning()) { + // Try again later, the app is transitioning, this also happen when the page is first loaded. + return new Promise((resolve, reject): void => { + setTimeout(() => { + + return this.push(page, params, opts, done).then(resolve, reject); + }, 250); + }); + } else { + const previousViews = this.getViews(); + if (previousViews.length > 0) { + const previousView = previousViews[previousViews.length - 1]; + const previousParam = previousView.getNavParams().data; + + // If the view we pushing in have same page's name and identical params, then we won't do anything. + // This is Ionic issue when user clicking too fast on old device or slow internet connection. + if (previousView.name === page && JSON.stringify(previousParam) === JSON.stringify(params)) { + + return Promise.resolve(); + } + } + + return super.push(page, params, opts, done); + } + } } diff --git a/src/components/ion-tabs/ion-tabs.scss b/src/components/ion-tabs/ion-tabs.scss index 5088c5969..523e1dd53 100644 --- a/src/components/ion-tabs/ion-tabs.scss +++ b/src/components/ion-tabs/ion-tabs.scss @@ -76,10 +76,6 @@ ion-app.app-root.md core-ion-tabs .core-ion-tabs-loading { min-height: $tabs-md-tab-min-height; } -ion-app.app-root.wp core-ion-tabs .core-ion-tabs-loading { - min-height: $tabs-wp-tab-min-height; -} - // Copy some styles from ion-tabs and ion-tab. core-ion-tabs, core-ion-tab { @include position(0, null, null, 0); @@ -134,7 +130,3 @@ ion-app.app-root.ios { ion-app.app-root.md { @include core-ion-tabs-statusbar-padding($toolbar-md-height, $toolbar-md-padding, $content-md-padding, $cordova-md-statusbar-padding, $cordova-md-statusbar-padding-modal-max-width); } - -ion-app.app-root.wp { - @include core-ion-tabs-statusbar-padding($toolbar-wp-height, $toolbar-wp-padding, $content-wp-padding, $cordova-wp-statusbar-padding, $cordova-wp-statusbar-padding-modal-max-width); -} diff --git a/src/components/local-file/core-local-file.html b/src/components/local-file/core-local-file.html index a988397a9..ffec36361 100644 --- a/src/components/local-file/core-local-file.html +++ b/src/components/local-file/core-local-file.html @@ -1,4 +1,4 @@ - + {{fileExtension}} diff --git a/src/components/local-file/local-file.ts b/src/components/local-file/local-file.ts index e1c46b1c2..c9f63ed17 100644 --- a/src/components/local-file/local-file.ts +++ b/src/components/local-file/local-file.ts @@ -12,8 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, Input, Output, OnInit, EventEmitter } from '@angular/core'; +import { Component, Input, Output, OnInit, EventEmitter, ViewChild, ElementRef } from '@angular/core'; +import { CoreEventsProvider } from '@providers/events'; import { CoreFileProvider } from '@providers/file'; +import { CoreSitesProvider } from '@providers/sites'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype'; import { CoreTextUtilsProvider } from '@providers/utils/text'; @@ -38,6 +40,8 @@ export class CoreLocalFileComponent implements OnInit { @Output() onRename?: EventEmitter; // Will notify when the file is renamed. Receives the FileEntry as the param. @Output() onClick?: EventEmitter; // Will notify when the file is clicked. Only if overrideClick is true. + @ViewChild('nameForm') formElement: ElementRef; + fileName: string; fileIcon: string; fileExtension: string; @@ -47,12 +51,14 @@ export class CoreLocalFileComponent implements OnInit { editMode: boolean; relativePath: string; - constructor(private mimeUtils: CoreMimetypeUtilsProvider, - private utils: CoreUtilsProvider, - private textUtils: CoreTextUtilsProvider, - private fileProvider: CoreFileProvider, - private domUtils: CoreDomUtilsProvider, - private timeUtils: CoreTimeUtilsProvider) { + constructor(protected mimeUtils: CoreMimetypeUtilsProvider, + protected utils: CoreUtilsProvider, + protected textUtils: CoreTextUtilsProvider, + protected fileProvider: CoreFileProvider, + protected domUtils: CoreDomUtilsProvider, + protected timeUtils: CoreTimeUtilsProvider, + protected sitesProvider: CoreSitesProvider, + protected eventsProvider: CoreEventsProvider) { this.onDelete = new EventEmitter(); this.onRename = new EventEmitter(); this.onClick = new EventEmitter(); @@ -137,6 +143,7 @@ export class CoreLocalFileComponent implements OnInit { if (newName == this.file.name) { // Name hasn't changed, stop. this.editMode = false; + this.domUtils.triggerFormCancelledEvent(this.formElement, this.sitesProvider.getCurrentSiteId()); return; } @@ -152,6 +159,9 @@ export class CoreLocalFileComponent implements OnInit { }).catch(() => { // File doesn't exist, move it. return this.fileProvider.moveFile(this.relativePath, newPath).then((fileEntry) => { + + this.domUtils.triggerFormSubmittedEvent(this.formElement, false, this.sitesProvider.getCurrentSiteId()); + this.editMode = false; this.file = fileEntry; this.loadFileBasicData(); diff --git a/src/components/navigation-bar/core-navigation-bar.html b/src/components/navigation-bar/core-navigation-bar.html index 4a7c211d3..47bc813a0 100644 --- a/src/components/navigation-bar/core-navigation-bar.html +++ b/src/components/navigation-bar/core-navigation-bar.html @@ -1,17 +1,19 @@ - + - - + + + - + - - + + + diff --git a/src/components/navigation-bar/navigation-bar.scss b/src/components/navigation-bar/navigation-bar.scss new file mode 100644 index 000000000..4a6590802 --- /dev/null +++ b/src/components/navigation-bar/navigation-bar.scss @@ -0,0 +1,11 @@ +ion-app.app-root core-navigation-bar { + .core-navigation-bar-arrow { + text-transform: none; + max-width: 100%; + + core-format-text { + overflow: hidden; + text-overflow: ellipsis; + } + } +} diff --git a/src/components/navigation-bar/navigation-bar.ts b/src/components/navigation-bar/navigation-bar.ts index 2942e5c2d..c19aad5ac 100644 --- a/src/components/navigation-bar/navigation-bar.ts +++ b/src/components/navigation-bar/navigation-bar.ts @@ -30,7 +30,9 @@ import { CoreTextUtilsProvider } from '@providers/utils/text'; }) export class CoreNavigationBarComponent { @Input() previous?: any; // Previous item. If not defined, the previous arrow won't be shown. + @Input() previousTitle?: string; // Previous item title. If not defined, only the arrow will be shown. @Input() next?: any; // Next item. If not defined, the next arrow won't be shown. + @Input() nextTitle?: string; // Next item title. If not defined, only the arrow will be shown. @Input() info?: string; // Info to show when clicking the info button. If not defined, the info button won't be shown. @Input() title?: string; // Title to show when seeing the info (new page). @Input() component?: string; // Component the bar belongs to. diff --git a/src/components/search-box/search-box.scss b/src/components/search-box/search-box.scss deleted file mode 100644 index 1e80c19f2..000000000 --- a/src/components/search-box/search-box.scss +++ /dev/null @@ -1,21 +0,0 @@ -ion-app.app-root core-search-box { - .button.item-button[icon-only] { - margin: 0; - padding: ($content-padding / 2) $content-padding; - } - .item.item-input.item-block .item-inner, - .item.item-input.item-block .item-inner ion-input { - border-bottom: 0 !important; - box-shadow: none; - } - - .item-inner { - padding-right: 0 !important; - padding-left: 0 !important; - } - - .item-wp .text-input { - border: 0; - margin: 0; - } -} diff --git a/src/components/send-message-form/core-send-message-form.html b/src/components/send-message-form/core-send-message-form.html index 8caa886c3..bed8756ce 100644 --- a/src/components/send-message-form/core-send-message-form.html +++ b/src/components/send-message-form/core-send-message-form.html @@ -1,4 +1,4 @@ - + diff --git a/src/core/course/components/module-completion/module-completion.scss b/src/core/course/components/module-completion/module-completion.scss index 23a677924..7bd3501c5 100644 --- a/src/core/course/components/module-completion/module-completion.scss +++ b/src/core/course/components/module-completion/module-completion.scss @@ -1,5 +1,6 @@ -ion-app.app-root core-course-module-completion a { +ion-app.app-root core-course-module-completion button { display: block; + background-color: transparent; img { padding: 5px; @@ -7,4 +8,4 @@ ion-app.app-root core-course-module-completion a { vertical-align: middle; max-width: none; } -} \ No newline at end of file +} diff --git a/src/core/course/components/module/module.scss b/src/core/course/components/module/module.scss index 1f1af0389..b63bb332b 100644 --- a/src/core/course/components/module/module.scss +++ b/src/core/course/components/module/module.scss @@ -25,10 +25,6 @@ ion-app.app-root core-course-module { &.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 { @@ -103,9 +99,7 @@ ion-app.app-root core-course-module { &.item-ios:active, &.item-ios.activated, &.item-md:active, - &.item-md.activated, - &.item-wp:active, - &.item-wp.activated { + &.item-md.activated { background-color: $core-dark-background-color; } } @@ -174,33 +168,6 @@ ion-app.app-root.ios core-course-module { } } -ion-app.app-root.wp core-course-module { - .core-module-description { - @include padding(null, $item-wp-padding-end / 2, null, null); - margin-bottom: $label-md-margin-bottom; - - .core-show-more { - @include padding(null, $item-wp-padding-end / 2, null, null); - } - } - - .item.core-course-module-handler .core-module-icon { - margin-top: $item-wp-padding-top; - margin-bottom: $item-wp-padding-bottom; - width: 24px; - height: 24px; - } - - .core-module-title core-format-text { - padding-top: $item-wp-padding-top + 3; - } - - .button-wp { - margin-top: 8px; - margin-bottom: 8px; - } -} - ion-app.app-root .core-course-module-handler.item [item-start] + .item-inner { @include margin-horizontal(4px, null); } \ No newline at end of file diff --git a/src/core/course/providers/helper.ts b/src/core/course/providers/helper.ts index d386f1a58..7fbc8b740 100644 --- a/src/core/course/providers/helper.ts +++ b/src/core/course/providers/helper.ts @@ -815,6 +815,11 @@ export class CoreCourseHelperProvider { } } + if (moduleInfo.status == CoreConstants.DOWNLOADING) { + // Set this to 0 to prevent "remove file" option showing up while downloading. + instance.size = 0; + } + if (typeof instance.contextMenuStatusObserver == 'undefined' && component) { instance.contextMenuStatusObserver = this.eventsProvider.on(CoreEventsProvider.PACKAGE_STATUS_CHANGED, (data) => { if (data.componentId == module.id && data.component == component) { @@ -1253,7 +1258,7 @@ export class CoreCourseHelperProvider { } else { modal.dismiss(); - return this.getAndOpenCourse(undefined, courseId, params, siteId); + return this.getAndOpenCourse(navCtrl, courseId, params, siteId); } }).catch((error) => { modal.dismiss(); diff --git a/src/core/courses/components/course-progress/core-courses-course-progress.html b/src/core/courses/components/course-progress/core-courses-course-progress.html index 646d9edcb..6818d7be4 100644 --- a/src/core/courses/components/course-progress/core-courses-course-progress.html +++ b/src/core/courses/components/course-progress/core-courses-course-progress.html @@ -33,7 +33,7 @@ -
diff --git a/src/core/courses/components/course-progress/course-progress.scss b/src/core/courses/components/course-progress/course-progress.scss index a5f0f750a..643740b73 100644 --- a/src/core/courses/components/course-progress/course-progress.scss +++ b/src/core/courses/components/course-progress/course-progress.scss @@ -90,9 +90,6 @@ ion-app.app-root core-courses-course-progress { ion-item-divider .label-md { @extend .label-md; } - ion-item-divider .label-wp { - @extend .label-wp; - } ion-item-divider .label-ios { @extend .label-ios; } diff --git a/src/core/courses/pages/dashboard/dashboard.scss b/src/core/courses/pages/dashboard/dashboard.scss index c21def799..004768c6c 100644 --- a/src/core/courses/pages/dashboard/dashboard.scss +++ b/src/core/courses/pages/dashboard/dashboard.scss @@ -10,9 +10,6 @@ ion-app.app-root page-core-courses-dashboard { max-height: $toolbar-ios-height - 24; } - .toolbar-title-wp .core-header-logo { - max-height: $toolbar-wp-height - 24; - } .toolbar-title core-format-text { display: none; } diff --git a/src/core/courses/pages/search/search.html b/src/core/courses/pages/search/search.html index 20e55c6c4..00475b3f9 100644 --- a/src/core/courses/pages/search/search.html +++ b/src/core/courses/pages/search/search.html @@ -4,7 +4,7 @@ - +
{{ 'core.courses.totalcoursesearchresults' | translate:{$a: total} }} diff --git a/src/core/courses/pages/search/search.module.ts b/src/core/courses/pages/search/search.module.ts index 57a51cb46..56d61d396 100644 --- a/src/core/courses/pages/search/search.module.ts +++ b/src/core/courses/pages/search/search.module.ts @@ -18,6 +18,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { CoreCoursesSearchPage } from './search'; import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreSearchComponentsModule } from '@core/search/components/components.module'; import { CoreCoursesComponentsModule } from '../../components/components.module'; @NgModule({ @@ -28,8 +29,9 @@ import { CoreCoursesComponentsModule } from '../../components/components.module' CoreComponentsModule, CoreDirectivesModule, CoreCoursesComponentsModule, + CoreSearchComponentsModule, IonicPageModule.forChild(CoreCoursesSearchPage), - TranslateModule.forChild() + TranslateModule.forChild(), ], }) export class CoreCoursesSearchPageModule {} diff --git a/src/core/courses/pages/search/search.ts b/src/core/courses/pages/search/search.ts index e3d56dcfb..e05a4a703 100644 --- a/src/core/courses/pages/search/search.ts +++ b/src/core/courses/pages/search/search.ts @@ -52,6 +52,18 @@ export class CoreCoursesSearchPage { }); } + /** + * Clear search box. + * + * @param e The event object. + */ + clearSearch(e: Event): void { + this.currentSearch = ''; + this.courses = undefined; + this.page = 0; + this.total = 0; + } + /** * Load more results. * diff --git a/src/core/courses/pages/self-enrol-password/self-enrol-password.html b/src/core/courses/pages/self-enrol-password/self-enrol-password.html index 3617c7dc3..1ca665964 100644 --- a/src/core/courses/pages/self-enrol-password/self-enrol-password.html +++ b/src/core/courses/pages/self-enrol-password/self-enrol-password.html @@ -10,7 +10,7 @@ - + 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 a646dacb4..17da104ae 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 @@ -12,8 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component } from '@angular/core'; +import { Component, ViewChild, ElementRef } from '@angular/core'; import { IonicPage, ViewController } from 'ionic-angular'; +import { CoreEventsProvider } from '@providers/events'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; /** * Page that displays a form to enter a password to self enrol in a course. @@ -24,12 +27,19 @@ import { IonicPage, ViewController } from 'ionic-angular'; templateUrl: 'self-enrol-password.html', }) export class CoreCoursesSelfEnrolPasswordPage { - constructor(private viewCtrl: ViewController) { } + + @ViewChild('enrolPasswordForm') formElement: ElementRef; + + constructor(protected viewCtrl: ViewController, + protected eventsProvider: CoreEventsProvider, + protected sitesProvider: CoreSitesProvider, + protected domUtils: CoreDomUtilsProvider) { } /** * Close help modal. */ close(): void { + this.domUtils.triggerFormCancelledEvent(this.formElement, this.sitesProvider.getCurrentSiteId()); this.viewCtrl.dismiss(); } @@ -43,6 +53,8 @@ export class CoreCoursesSelfEnrolPasswordPage { e.preventDefault(); e.stopPropagation(); + this.domUtils.triggerFormSubmittedEvent(this.formElement, false, this.sitesProvider.getCurrentSiteId()); + this.viewCtrl.dismiss(password); } } diff --git a/src/core/courses/providers/course-link-handler.ts b/src/core/courses/providers/course-link-handler.ts index b94813f4c..c7fdd7df4 100644 --- a/src/core/courses/providers/course-link-handler.ts +++ b/src/core/courses/providers/course-link-handler.ts @@ -178,8 +178,9 @@ export class CoreCoursesCourseLinkHandler extends CoreContentLinksHandlerBase { error = this.translate.instant('core.courses.notenroled'); } - const body = this.translate.instant('core.twoparagraphs', - { p1: error, p2: this.translate.instant('core.confirmopeninbrowser') }); + const body = this.textUtils.buildSeveralParagraphsMessage( + [error, this.translate.instant('core.confirmopeninbrowser')]); + this.domUtils.showConfirm(body).then(() => { this.sitesProvider.getCurrentSite().openInBrowserWithAutoLogin(url); }).catch(() => { diff --git a/src/core/editor/components/components.module.ts b/src/core/editor/components/components.module.ts new file mode 100644 index 000000000..5d8edf3e7 --- /dev/null +++ b/src/core/editor/components/components.module.ts @@ -0,0 +1,41 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { IonicModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreEditorRichTextEditorComponent } from './rich-text-editor/rich-text-editor'; +import { CoreComponentsModule } from '@components/components.module'; + +@NgModule({ + declarations: [ + CoreEditorRichTextEditorComponent + ], + imports: [ + CommonModule, + IonicModule, + TranslateModule.forChild(), + CoreComponentsModule, + ], + providers: [ + ], + exports: [ + CoreEditorRichTextEditorComponent + ], + entryComponents: [ + CoreEditorRichTextEditorComponent + ] +}) +export class CoreEditorComponentsModule {} diff --git a/src/components/rich-text-editor/core-rich-text-editor.html b/src/core/editor/components/rich-text-editor/core-editor-rich-text-editor.html similarity index 51% rename from src/components/rich-text-editor/core-rich-text-editor.html rename to src/core/editor/components/rich-text-editor/core-editor-rich-text-editor.html index ba2bd897f..20faba16e 100644 --- a/src/components/rich-text-editor/core-rich-text-editor.html +++ b/src/core/editor/components/rich-text-editor/core-editor-rich-text-editor.html @@ -1,81 +1,88 @@ -
+
+
+
+ + + +
+ + {{ infoMessage | translate }} +
- -
- - - - - - - - - - - - - - + + + + + + - - - - -
diff --git a/src/components/rich-text-editor/rich-text-editor.scss b/src/core/editor/components/rich-text-editor/rich-text-editor.scss similarity index 88% rename from src/components/rich-text-editor/rich-text-editor.scss rename to src/core/editor/components/rich-text-editor/rich-text-editor.scss index e2dd2ac07..ae01ced74 100644 --- a/src/components/rich-text-editor/rich-text-editor.scss +++ b/src/core/editor/components/rich-text-editor/rich-text-editor.scss @@ -10,12 +10,35 @@ ion-app.app-root core-rich-text-editor { background-color: $gray-darker; } + .core-rte-editor-container { + max-height: calc(100% - 46px); + display: flex; + flex-direction: column; + flex-grow: 1; + &.toolbar-hidden { + max-height: 100%; + } + + .core-rte-info-message { + padding: 5px; + border-top: 1px solid $info; + background: white; + flex-shrink: 1; + font-size: 1.4rem; + + .icon { + color: $info; + } + } + } + .core-rte-editor, .core-textarea { padding: 2px; margin: 2px; width: 100%; resize: none; background-color: $white; + flex-grow: 1; @include darkmode() { background-color: $gray-darker; color: $white; @@ -147,7 +170,6 @@ ion-app.app-root core-rich-text-editor { border: none; } } - } body.keyboard-is-open ion-app.app-root core-rich-text-editor { diff --git a/src/components/rich-text-editor/rich-text-editor.ts b/src/core/editor/components/rich-text-editor/rich-text-editor.ts similarity index 71% rename from src/components/rich-text-editor/rich-text-editor.ts rename to src/core/editor/components/rich-text-editor/rich-text-editor.ts index d4367e509..a99a15e0c 100644 --- a/src/components/rich-text-editor/rich-text-editor.ts +++ b/src/core/editor/components/rich-text-editor/rich-text-editor.ts @@ -21,28 +21,23 @@ import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreUrlUtilsProvider } from '@providers/utils/url'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreEventsProvider } from '@providers/events'; +import { CoreEditorOfflineProvider } from '../../providers/editor-offline'; import { FormControl } from '@angular/forms'; import { Subscription } from 'rxjs'; /** - * Directive to display a rich text editor if enabled. + * Component to display a rich text editor if enabled. * - * If enabled, this directive will show a rich text editor. Otherwise it'll show a regular textarea. - * - * This directive requires an OBJECT model. The text written in the editor or textarea will be stored inside - * a "text" property in that object. This is to ensure 2-way data-binding, since using a string as a model - * could be easily broken. + * If enabled, this component will show a rich text editor. Otherwise it'll show a regular textarea. * * Example: * - * - * In the example above, the text written in the editor will be stored in newpost.text. */ @Component({ selector: 'core-rich-text-editor', - templateUrl: 'core-rich-text-editor.html' + templateUrl: 'core-editor-rich-text-editor.html' }) -export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy { +export class CoreEditorRichTextEditorComponent implements AfterContentInit, OnDestroy { // Based on: https://github.com/judgewest2000/Ionic3RichText/ // @todo: Anchor button, fullscreen... // @todo: Textarea height is not being updated when editor is resized. Height is calculated if any css is changed. @@ -52,11 +47,19 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy @Input() name = 'core-rich-text-editor'; // Name to set to the textarea. @Input() component?: string; // The component to link the files to. @Input() componentId?: number; // An ID to use in conjunction with the component. + @Input() autoSave?: boolean | string; // Whether to auto-save the contents in a draft. Defaults to true. + @Input() contextLevel?: string; // The context level of the text. + @Input() contextInstanceId?: number; // The instance ID related to the context. + @Input() elementId?: string; // An ID to set to the element. + @Input() draftExtraParams: {[name: string]: any}; // Extra params to identify the draft. @Output() contentChanged: EventEmitter; @ViewChild('editor') editor: ElementRef; // WYSIWYG editor. @ViewChild('textarea') textarea: TextInput; // Textarea editor. + protected DRAFT_AUTOSAVE_FREQUENCY = 30000; + protected RESTORE_MESSAGE_CLEAR_TIME = 6000; + protected SAVE_MESSAGE_CLEAR_TIME = 2000; protected element: HTMLDivElement; protected editorElement: HTMLDivElement; protected kbHeight = 0; // Last known keyboard height. @@ -64,7 +67,8 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy protected valueChangeSubscription: Subscription; protected keyboardObs: any; - protected initHeightInterval; + protected resetObs: any; + protected initHeightInterval: NodeJS.Timer; rteEnabled = false; editorSupported = true; @@ -84,22 +88,37 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy u: 'false', strike: 'false', p: 'false', - h1: 'false', - h2: 'false', h3: 'false', + h4: 'false', + h5: 'false', ul: 'false', ol: 'false', }; + infoMessage: string; protected isCurrentView = true; protected toolbarButtonWidth = 40; protected toolbarArrowWidth = 28; + protected pageInstance: string; + protected autoSaveInterval: NodeJS.Timer; + protected hideMessageTimeout: NodeJS.Timer; + protected lastDraft = ''; + protected draftWasRestored = false; + protected originalContent: string; - constructor(private domUtils: CoreDomUtilsProvider, private urlUtils: CoreUrlUtilsProvider, - private sitesProvider: CoreSitesProvider, private filepoolProvider: CoreFilepoolProvider, - @Optional() private content: Content, elementRef: ElementRef, private events: CoreEventsProvider, - private utils: CoreUtilsProvider, private platform: Platform) { + constructor( + protected domUtils: CoreDomUtilsProvider, + protected urlUtils: CoreUrlUtilsProvider, + protected sitesProvider: CoreSitesProvider, + protected filepoolProvider: CoreFilepoolProvider, + @Optional() protected content: Content, + elementRef: ElementRef, + protected events: CoreEventsProvider, + protected utils: CoreUtilsProvider, + protected platform: Platform, + protected editorOffline: CoreEditorOfflineProvider) { this.contentChanged = new EventEmitter(); this.element = elementRef.nativeElement as HTMLDivElement; + this.pageInstance = 'app_' + Date.now(); // Generate a "unique" ID based on timestamp. } /** @@ -115,6 +134,8 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy // Setup the editor. this.editorElement = this.editor.nativeElement as HTMLDivElement; this.setContent(this.control.value); + this.originalContent = this.control.value; + this.lastDraft = this.control.value; this.editorElement.onchange = this.onChange.bind(this); this.editorElement.onkeyup = this.onChange.bind(this); this.editorElement.onpaste = this.onChange.bind(this); @@ -123,7 +144,20 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy // Listen for changes on the control to update the editor (if it is updated from outside of this component). this.valueChangeSubscription = this.control.valueChanges.subscribe((param) => { - this.setContent(param); + if (!this.draftWasRestored || this.originalContent != param) { + // Apply the new content. + this.setContent(param); + this.originalContent = param; + this.infoMessage = null; + + // Save a draft so the original content is saved. + this.lastDraft = param; + this.editorOffline.saveDraft(this.contextLevel, this.contextInstanceId, this.elementId, + this.draftExtraParams, this.pageInstance, param, param); + } else { + // A draft was restored and the content hasn't changed in the site. Use the draft value instead of this one. + this.control.setValue(this.lastDraft, {emitEvent: false}); + } }); // Use paragraph on enter. @@ -148,6 +182,20 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy }); this.updateToolbarButtons(); + + if (this.elementId) { + // Prepend elementId with 'id_' like in web. Don't use a setter for this because the value shouldn't change. + this.elementId = 'id_' + this.elementId; + this.element.setAttribute('id', this.elementId); + } + + if (this.shouldAutoSaveDrafts()) { + this.restoreDraft(); + + this.autoSaveDrafts(); + + this.deleteDraftOnSubmitOrCancel(); + } } /** @@ -196,7 +244,7 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy } if (height > this.minHeight) { - this.element.style.height = this.domUtils.formatPixelsSize(height); + this.element.style.height = this.domUtils.formatPixelsSize(height - 1); } else { this.element.style.height = ''; } @@ -291,8 +339,7 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy return; } - $event.preventDefault(); - $event.stopPropagation(); + this.stopBubble($event); const move = $event['key'] == 'ArrowLeft' ? -1 : +1, cursor = this.getCurrentCursorPosition(this.editorElement); @@ -408,8 +455,7 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy * @param $event The event. */ toggleEditor($event: Event): void { - $event.preventDefault(); - $event.stopPropagation(); + this.stopBubble($event); this.setContent(this.control.value); @@ -533,26 +579,42 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy * * @param $event Event data * @param command Command to execute. + * @param parameters If parameters is set to block, a formatBlock command will be performed. Otherwise it will switch the + * toolbar styles button when set. */ - buttonAction($event: any, command: string): void { + buttonAction($event: any, command: string, parameters: string): void { this.stopBubble($event); if (command) { - if (command.includes('|')) { - const parameters = command.split('|')[1]; - command = command.split('|')[0]; - - document.execCommand(command, false, parameters); + if (parameters == 'block') { + document.execCommand('formatBlock', false, '<' + command + '>'); } else { + if (parameters) { + this.toolbarStyles[parameters] = this.toolbarStyles[parameters] == 'true' ? 'false' : 'true'; + } + document.execCommand(command, false); } } } + /** + * Focus editor when click the area. + * + * @param e Event + */ + focusRTE(e?: Event): void { + if (this.rteEnabled) { + this.editorElement.focus(); + } else { + this.textarea.setFocus(); + } + } + /** * Hide the toolbar in phone mode. */ - hideToolbar($event: any): void { + hideToolbar($event: Event): void { this.stopBubble($event); if (this.isPhone) { @@ -563,7 +625,9 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy /** * Show the toolbar. */ - showToolbar(): void { + showToolbar($event: Event): void { + this.stopBubble($event); + this.editorElement.focus(); this.toolbarHidden = false; } @@ -578,6 +642,19 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy event.stopPropagation(); } + /** + * When a button is clicked first we should stop event propagation, but it has some cases to not. + * + * @param event Event. + */ + mouseDownAction(event: Event): void { + const selection = window.getSelection().toString(); + // When RTE is focused with a range selection the stopBubble will not fire click. + if (!this.rteEnabled || document.activeElement != this.editorElement || selection == '') { + this.stopBubble(event); + } + } + /** * Method that shows the next toolbar buttons. */ @@ -648,7 +725,7 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy * Update highlighted toolbar styles. */ updateToolbarStyles = (): void => { - const node = document.getSelection().focusNode; + const node = window.getSelection().focusNode; if (!node) { return; } @@ -673,6 +750,121 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy } } + /** + * Check if should auto save drafts. + * + * @return {boolean} Whether it should auto save drafts. + */ + protected shouldAutoSaveDrafts(): boolean { + return !!this.sitesProvider.getCurrentSite() && + (typeof this.autoSave == 'undefined' || this.utils.isTrueOrOne(this.autoSave)) && + typeof this.contextLevel != 'undefined' && + typeof this.contextInstanceId != 'undefined' && + typeof this.elementId != 'undefined'; + } + + /** + * Restore a draft if there is any. + * + * @return Promise resolved when done. + */ + protected async restoreDraft(): Promise { + try { + const entry = await this.editorOffline.resumeDraft(this.contextLevel, this.contextInstanceId, this.elementId, + this.draftExtraParams, this.pageInstance, this.originalContent); + + if (typeof entry == 'undefined') { + // No draft found. + return; + } + + let draftText = entry.drafttext; + + // Revert untouched editor contents to an empty string. + if (draftText == '

' || draftText == '


' || draftText == '
' || + draftText == '

 

' || draftText == '


 

') { + draftText = ''; + } + + if (draftText !== '' && draftText != this.control.value) { + // Restore the draft. + this.control.setValue(draftText, {emitEvent: false}); + this.setContent(draftText); + this.lastDraft = draftText; + this.draftWasRestored = true; + this.originalContent = entry.originalcontent; + + if (entry.drafttext != entry.originalcontent) { + // Notify the user. + this.showMessage('core.editor.textrecovered', this.RESTORE_MESSAGE_CLEAR_TIME); + } + } + } catch (error) { + // Ignore errors, shouldn't happen. + } + } + + /** + * Automatically save drafts every certain time. + */ + protected autoSaveDrafts(): void { + this.autoSaveInterval = setInterval(async () => { + const newText = this.control.value; + + if (this.lastDraft == newText) { + // Text hasn't changed, nothing to save. + return; + } + + try { + await this.editorOffline.saveDraft(this.contextLevel, this.contextInstanceId, this.elementId, + this.draftExtraParams, this.pageInstance, newText, this.originalContent); + + // Draft saved, notify the user. + this.lastDraft = newText; + this.showMessage('core.editor.autosavesucceeded', this.SAVE_MESSAGE_CLEAR_TIME); + } catch (error) { + // Error saving draft. + } + }, this.DRAFT_AUTOSAVE_FREQUENCY); + } + + /** + * Delete the draft when the form is submitted or cancelled. + */ + protected deleteDraftOnSubmitOrCancel(): void { + + this.resetObs = this.events.on(CoreEventsProvider.FORM_ACTION, async (data) => { + const form = this.element.closest('form'); + + if (data.form && form && data.form == form) { + try { + await this.editorOffline.deleteDraft(this.contextLevel, this.contextInstanceId, this.elementId, + this.draftExtraParams); + } catch (error) { + // Error deleting draft. Shouldn't happen. + } + } + }, this.sitesProvider.getCurrentSiteId()); + } + + /** + * Show a message. + * + * @param message Identifier of the message to display. + * @param timeout Number of milliseconds when to remove the message. + */ + protected showMessage(message: string, timeout: number): void { + clearTimeout(this.hideMessageTimeout); + + this.infoMessage = message; + + this.hideMessageTimeout = setTimeout(() => { + this.hideMessageTimeout = null; + this.infoMessage = null; + }, timeout); + } + /** * User entered the page that contains the component. */ @@ -698,5 +890,8 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy document.removeEventListener('selectionchange', this.updateToolbarStyles); clearInterval(this.initHeightInterval); this.keyboardObs && this.keyboardObs.off(); + clearInterval(this.autoSaveInterval); + clearTimeout(this.hideMessageTimeout); + this.resetObs && this.resetObs.off(); } } diff --git a/src/core/editor/editor.module.ts b/src/core/editor/editor.module.ts new file mode 100644 index 000000000..e55c88a49 --- /dev/null +++ b/src/core/editor/editor.module.ts @@ -0,0 +1,38 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { CoreEditorComponentsModule } from './components/components.module'; +import { CoreEditorOfflineProvider } from './providers/editor-offline'; + +// List of providers (without handlers). +export const CORE_GRADES_PROVIDERS: any[] = [ + CoreEditorOfflineProvider, +]; + +@NgModule({ + declarations: [ + ], + imports: [ + CoreEditorComponentsModule, + ], + providers: [ + CoreEditorOfflineProvider, + ], +}) +export class CoreEditorModule { + constructor(editorOffline: CoreEditorOfflineProvider) { + // Inject the helper even if it isn't used here it's instantiated. + } +} diff --git a/src/core/editor/lang/en.json b/src/core/editor/lang/en.json new file mode 100644 index 000000000..508c6ddb0 --- /dev/null +++ b/src/core/editor/lang/en.json @@ -0,0 +1,17 @@ +{ + "autosavesucceeded": "Draft saved.", + "bold": "Bold", + "clear": "Clear formatting", + "h3": "Heading (large)", + "h4": "Heading (medium)", + "h5": "Heading (small)", + "hidetoolbar": "Hide toolbar", + "italic": "Italic", + "orderedlist": "Ordered list", + "p": "Paragraph", + "strike": "Strike through", + "textrecovered": "A draft version of this text was automatically restored.", + "toggle": "Toggle editor", + "underline": "Underline", + "unorderedlist": "Unordered list" +} \ No newline at end of file diff --git a/src/core/editor/providers/editor-offline.ts b/src/core/editor/providers/editor-offline.ts new file mode 100644 index 000000000..17d8b44ce --- /dev/null +++ b/src/core/editor/providers/editor-offline.ts @@ -0,0 +1,277 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { CoreLoggerProvider } from '@providers/logger'; +import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; +import { CoreTextUtilsProvider } from '@providers/utils/text'; +import { CoreUtilsProvider } from '@providers/utils/utils'; + +/** + * Service with features regarding rich text editor in offline. + */ +@Injectable() +export class CoreEditorOfflineProvider { + + protected DRAFT_TABLE = 'editor_draft'; + + protected logger; + protected siteSchema: CoreSiteSchema = { + name: 'CoreEditorProvider', + version: 1, + tables: [ + { + name: this.DRAFT_TABLE, + columns: [ + { + name: 'contextlevel', + type: 'TEXT', + }, + { + name: 'contextinstanceid', + type: 'INTEGER', + }, + { + name: 'elementid', + type: 'TEXT', + }, + { + name: 'extraparams', // Moodle web uses a page hash built with URL. App will use some params stringified. + type: 'TEXT', + }, + { + name: 'drafttext', + type: 'TEXT', + notNull: true, + }, + { + name: 'pageinstance', + type: 'TEXT', + notNull: true, + }, + { + name: 'timecreated', + type: 'INTEGER', + notNull: true, + }, + { + name: 'timemodified', + type: 'INTEGER', + notNull: true, + }, + { + name: 'originalcontent', + type: 'TEXT', + }, + ], + primaryKeys: ['contextlevel', 'contextinstanceid', 'elementid', 'extraparams'], + }, + ], + }; + + constructor( + logger: CoreLoggerProvider, + protected sitesProvider: CoreSitesProvider, + protected textUtils: CoreTextUtilsProvider, + protected utils: CoreUtilsProvider) { + this.logger = logger.getInstance('CoreEditorProvider'); + + this.sitesProvider.registerSiteSchema(this.siteSchema); + } + + /** + * Delete a draft from DB. + * + * @param contextLevel Context level. + * @param contextInstanceId The instance ID related to the context. + * @param elementId Element ID. + * @param extraParams Object with extra params to identify the draft. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. + */ + async deleteDraft(contextLevel: string, contextInstanceId: number, elementId: string, extraParams: {[name: string]: any}, + siteId?: string): Promise { + + try { + const db = await this.sitesProvider.getSiteDb(siteId); + + const params = this.fixDraftPrimaryData(contextLevel, contextInstanceId, elementId, extraParams); + + return db.deleteRecords(this.DRAFT_TABLE, params); + } catch (error) { + // Ignore errors, probably no draft stored. + } + } + + /** + * Return an object with the draft primary data converted to the right format. + * + * @param contextLevel Context level. + * @param contextInstanceId The instance ID related to the context. + * @param elementId Element ID. + * @param extraParams Object with extra params to identify the draft. + * @return Object with the fixed primary data. + */ + protected fixDraftPrimaryData(contextLevel: string, contextInstanceId: number, elementId: string, + extraParams: {[name: string]: any}): CoreEditorDraftPrimaryData { + + return { + contextlevel: contextLevel, + contextinstanceid: contextInstanceId, + elementid: elementId, + extraparams: this.utils.sortAndStringify(extraParams || {}), + }; + } + + /** + * Get a draft from DB. + * + * @param contextLevel Context level. + * @param contextInstanceId The instance ID related to the context. + * @param elementId Element ID. + * @param extraParams Object with extra params to identify the draft. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the draft data. Undefined if no draft stored. + */ + async getDraft(contextLevel: string, contextInstanceId: number, elementId: string, extraParams: {[name: string]: any}, + siteId?: string): Promise { + + const db = await this.sitesProvider.getSiteDb(siteId); + + const params = this.fixDraftPrimaryData(contextLevel, contextInstanceId, elementId, extraParams); + + return db.getRecord(this.DRAFT_TABLE, params); + } + + /** + * Get draft to resume it. + * + * @param contextLevel Context level. + * @param contextInstanceId The instance ID related to the context. + * @param elementId Element ID. + * @param extraParams Object with extra params to identify the draft. + * @param pageInstance Unique identifier to prevent storing data from several sources at the same time. + * @param originalContent Original content of the editor. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the draft data. Undefined if no draft stored. + */ + async resumeDraft(contextLevel: string, contextInstanceId: number, elementId: string, extraParams: {[name: string]: any}, + pageInstance: string, originalContent?: string, siteId?: string): Promise { + + try { + // Check if there is a draft stored. + const entry = await this.getDraft(contextLevel, contextInstanceId, elementId, extraParams, siteId); + + // There is a draft stored. Update its page instance. + try { + const db = await this.sitesProvider.getSiteDb(siteId); + + entry.pageinstance = pageInstance; + entry.timemodified = Date.now(); + + if (originalContent && entry.originalcontent != originalContent) { + entry.originalcontent = originalContent; + entry.drafttext = ''; // "Discard" the draft. + } + + await db.insertRecord(this.DRAFT_TABLE, entry); + } catch (error) { + // Ignore errors saving the draft. It shouldn't happen. + } + + return entry; + } catch (error) { + // No draft stored. Store an empty draft to save the pageinstance. + await this.saveDraft(contextLevel, contextInstanceId, elementId, extraParams, pageInstance, '', originalContent, + siteId); + } + } + + /** + * Save a draft in DB. + * + * @param contextLevel Context level. + * @param contextInstanceId The instance ID related to the context. + * @param elementId Element ID. + * @param extraParams Object with extra params to identify the draft. + * @param pageInstance Unique identifier to prevent storing data from several sources at the same time. + * @param draftText The text to store. + * @param originalContent Original content of the editor. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. + */ + async saveDraft(contextLevel: string, contextInstanceId: number, elementId: string, extraParams: {[name: string]: any}, + pageInstance: string, draftText: string, originalContent?: string, siteId?: string): Promise { + + let timecreated = Date.now(); + let entry: CoreEditorDraft; + + // Check if there is a draft already stored. + try { + entry = await this.getDraft(contextLevel, contextInstanceId, elementId, extraParams, siteId); + + timecreated = entry.timecreated; + } catch (error) { + // No draft already stored. + } + + if (entry) { + if (entry.pageinstance != pageInstance) { + this.logger.warning(`Discarding draft because of pageinstance. Context '${contextLevel}' '${contextInstanceId}', ` + + `element '${elementId}'`); + throw null; + } + + if (!originalContent) { + // Original content not set, use the one in the entry. + originalContent = entry.originalcontent; + } + } + + const db = await this.sitesProvider.getSiteDb(siteId); + + const data: CoreEditorDraft = this.fixDraftPrimaryData(contextLevel, contextInstanceId, elementId, extraParams); + + data.drafttext = (draftText || '').trim(); + data.pageinstance = pageInstance; + data.timecreated = timecreated; + data.timemodified = Date.now(); + if (originalContent) { + data.originalcontent = originalContent; + } + + await db.insertRecord(this.DRAFT_TABLE, data); + } +} + +/** + * Primary data to identify a stored draft. + */ +type CoreEditorDraftPrimaryData = { + contextlevel: string; // Context level. + contextinstanceid: number; // The instance ID related to the context. + elementid: string; // Element ID. + extraparams: string; // Extra params stringified. +}; + +/** + * Draft data stored. + */ +type CoreEditorDraft = CoreEditorDraftPrimaryData & { + drafttext?: string; // Draft text stored. + pageinstance?: string; // Unique identifier to prevent storing data from several sources at the same time. + timecreated?: number; // Time created. + timemodified?: number; // Time modified. + originalcontent?: string; // Original content of the editor. +}; diff --git a/src/core/emulator/providers/local-notifications.ts b/src/core/emulator/providers/local-notifications.ts index d3234bcd8..affff2e19 100644 --- a/src/core/emulator/providers/local-notifications.ts +++ b/src/core/emulator/providers/local-notifications.ts @@ -14,10 +14,10 @@ import { Injectable } from '@angular/core'; import { LocalNotifications, ILocalNotification, ILocalNotificationAction } from '@ionic-native/local-notifications'; -import { CoreAppProvider } from '@providers/app'; +import { CoreAppProvider, CoreAppSchema } from '@providers/app'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreUtilsProvider } from '@providers/utils/utils'; -import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb'; +import { SQLiteDB } from '@classes/sqlitedb'; import { CoreConstants } from '@core/constants'; import { CoreConfigConstants } from '../../../configconstants'; import * as moment from 'moment'; @@ -43,41 +43,48 @@ export class LocalNotificationsMock extends LocalNotifications { // Variables for database. protected DESKTOP_NOTIFS_TABLE = 'desktop_local_notifications'; - protected tableSchema: SQLiteDBTableSchema = { - name: this.DESKTOP_NOTIFS_TABLE, - columns: [ + protected tableSchema: CoreAppSchema = { + name: 'LocalNotificationsMock', + version: 1, + tables: [ { - name: 'id', - type: 'INTEGER', - primaryKey: true + name: this.DESKTOP_NOTIFS_TABLE, + columns: [ + { + name: 'id', + type: 'INTEGER', + primaryKey: true + }, + { + name: 'title', + type: 'TEXT' + }, + { + name: 'text', + type: 'TEXT' + }, + { + name: 'at', + type: 'INTEGER' + }, + { + name: 'data', + type: 'TEXT' + }, + { + name: 'triggered', + type: 'INTEGER' + } + ], }, - { - name: 'title', - type: 'TEXT' - }, - { - name: 'text', - type: 'TEXT' - }, - { - name: 'at', - type: 'INTEGER' - }, - { - name: 'data', - type: 'TEXT' - }, - { - name: 'triggered', - type: 'INTEGER' - } - ] + ], }; protected appDB: SQLiteDB; protected scheduled: { [i: number]: any } = {}; protected triggered: { [i: number]: any } = {}; protected observers: {[event: string]: Subject}; + protected dbReady: Promise; // Promise resolved when the app DB is initialized. protected defaults = { actions : [], attachments : [], @@ -117,7 +124,9 @@ export class LocalNotificationsMock extends LocalNotifications { super(); this.appDB = appProvider.getDB(); - this.appDB.createTableFromSchema(this.tableSchema); + this.dbReady = appProvider.createTablesFromSchema(this.tableSchema).catch(() => { + // Ignore errors. + }); // Initialize observers. this.observers = { @@ -550,20 +559,21 @@ export class LocalNotificationsMock extends LocalNotifications { * * @return Promise resolved with the notifications. */ - protected getAllNotifications(): Promise { - return this.appDB.getAllRecords(this.DESKTOP_NOTIFS_TABLE).then((notifications) => { - notifications.forEach((notification) => { - notification.trigger = { - at: new Date(notification.at) - }; - notification.data = this.textUtils.parseJSON(notification.data); - notification.triggered = !!notification.triggered; + protected async getAllNotifications(): Promise { + await this.dbReady; - this.mergeWithDefaults(notification); - }); + const notifications = await this.appDB.getAllRecords(this.DESKTOP_NOTIFS_TABLE); + notifications.forEach((notification) => { + notification.trigger = { + at: new Date(notification.at), + }; + notification.data = this.textUtils.parseJSON(notification.data); + notification.triggered = !!notification.triggered; - return notifications; + this.mergeWithDefaults(notification); }); + + return notifications; } /** @@ -889,7 +899,9 @@ export class LocalNotificationsMock extends LocalNotifications { * @param id ID of the notification. * @return Promise resolved when done. */ - protected removeNotification(id: number): Promise { + protected async removeNotification(id: number): Promise { + await this.dbReady; + return this.appDB.deleteRecords(this.DESKTOP_NOTIFS_TABLE, { id: id }); } @@ -979,7 +991,9 @@ export class LocalNotificationsMock extends LocalNotifications { * @param triggered Whether the notification has been triggered. * @return Promise resolved when stored. */ - protected storeNotification(notification: ILocalNotification, triggered: boolean): Promise { + protected async storeNotification(notification: ILocalNotification, triggered: boolean): Promise { + await this.dbReady; + // Only store some of the properties. const entry = { id : notification.id, diff --git a/src/core/emulator/providers/zip.ts b/src/core/emulator/providers/zip.ts index 1c3af04a7..0dc848da7 100644 --- a/src/core/emulator/providers/zip.ts +++ b/src/core/emulator/providers/zip.ts @@ -85,8 +85,8 @@ export class ZipMock extends Zip { } // First of all, create the directory where the files will be unzipped. - const destParent = destination.substring(0, source.lastIndexOf('/')), - destFolderName = destination.substr(source.lastIndexOf('/') + 1); + const destParent = destination.substring(0, destination.lastIndexOf('/')), + destFolderName = destination.substr(destination.lastIndexOf('/') + 1); return this.file.createDir(destParent, destFolderName, true); }).then(() => { diff --git a/src/core/fileuploader/providers/helper.ts b/src/core/fileuploader/providers/helper.ts index fb1ac27a0..afaa6f909 100644 --- a/src/core/fileuploader/providers/helper.ts +++ b/src/core/fileuploader/providers/helper.ts @@ -399,7 +399,10 @@ export class CoreFileUploaderHelperProvider { // Error, not cancelled. this.logger.error('Error while recording audio/video', error); - return Promise.reject(this.translate.instant(defaultMessage)); + const message = error.code == 20 ? this.translate.instant('core.fileuploader.errornoapp') : + (error.message || this.translate.instant(defaultMessage)); + + return Promise.reject(message); } else { this.logger.debug('Cancelled'); } diff --git a/src/core/filter/providers/helper.ts b/src/core/filter/providers/helper.ts index 82c89e8ac..ed0c13460 100644 --- a/src/core/filter/providers/helper.ts +++ b/src/core/filter/providers/helper.ts @@ -38,10 +38,12 @@ export class CoreFilterHelperProvider { protected moduleContextsCache: { [siteId: string]: { [courseId: number]: { - contexts: CoreFilterClassifiedFilters, - time: number - } - } + [contextLevel: string]: { + contexts: CoreFilterClassifiedFilters, + time: number, + }, + }, + }, } = {}; constructor(logger: CoreLoggerProvider, @@ -110,7 +112,7 @@ export class CoreFilterHelperProvider { return getFilters().then((contexts) => { return this.filterProvider.getAvailableInContexts(contexts, siteId).then((filters) => { - this.storeInMemoryCache(options.courseId, filters, siteId); + this.storeInMemoryCache(options.courseId, contextLevel, filters, siteId); return filters[contextLevel][instanceId] || []; }); @@ -278,14 +280,16 @@ export class CoreFilterHelperProvider { const siteId = site.getId(); // Check if we have the context in the memory cache. - if (this.moduleContextsCache[siteId] && this.moduleContextsCache[siteId][courseId]) { - const cachedCourse = this.moduleContextsCache[siteId][courseId]; + if (this.moduleContextsCache[siteId] && this.moduleContextsCache[siteId][courseId] && + this.moduleContextsCache[siteId][courseId][contextLevel]) { + + const cachedData = this.moduleContextsCache[siteId][courseId][contextLevel]; if (!this.appProvider.isOnline() || - Date.now() <= cachedCourse.time + site.getExpirationDelay(CoreSite.FREQUENCY_RARELY)) { + Date.now() <= cachedData.time + site.getExpirationDelay(CoreSite.FREQUENCY_RARELY)) { // We can use cache, return the filters if found. - return cachedCourse.contexts[contextLevel] && cachedCourse.contexts[contextLevel][instanceId]; + return cachedData.contexts[contextLevel] && cachedData.contexts[contextLevel][instanceId]; } } } @@ -316,11 +320,14 @@ export class CoreFilterHelperProvider { * @param contexts Filters to store, classified by contextlevel and instanceid * @param siteId Site ID. */ - protected storeInMemoryCache(courseId: number, contexts: CoreFilterClassifiedFilters, siteId: string): void { + protected storeInMemoryCache(courseId: number, contextLevel: string, contexts: CoreFilterClassifiedFilters, siteId: string) + : void { + this.moduleContextsCache[siteId] = this.moduleContextsCache[siteId] || {}; - this.moduleContextsCache[siteId][courseId] = { + this.moduleContextsCache[siteId][courseId] = this.moduleContextsCache[siteId][courseId] || {}; + this.moduleContextsCache[siteId][courseId][contextLevel] = { contexts: contexts, - time: Date.now() + time: Date.now(), }; } } diff --git a/src/core/grades/components/course/core-grades-course.html b/src/core/grades/components/course/core-grades-course.html index 9832467c9..9a6ee1845 100644 --- a/src/core/grades/components/course/core-grades-course.html +++ b/src/core/grades/components/course/core-grades-course.html @@ -16,7 +16,7 @@ - + diff --git a/src/core/grades/components/course/course.scss b/src/core/grades/components/course/course.scss index 5a3a9eb4e..c5ccb244d 100644 --- a/src/core/grades/components/course/course.scss +++ b/src/core/grades/components/course/course.scss @@ -85,6 +85,10 @@ ion-app.app-root core-grades-course { font-size: 0.85em; } } + + .core-grades-grade-clickable { + cursor: pointer; + } } } diff --git a/src/core/h5p/components/h5p-player/h5p-player.ts b/src/core/h5p/components/h5p-player/h5p-player.ts index 4804c4dbf..25153b69c 100644 --- a/src/core/h5p/components/h5p-player/h5p-player.ts +++ b/src/core/h5p/components/h5p-player/h5p-player.ts @@ -73,7 +73,8 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy { this.logger = loggerProvider.getInstance('CoreH5PPlayerComponent'); this.site = sitesProvider.getCurrentSite(); this.siteId = this.site.getId(); - this.siteCanDownload = this.sitesProvider.getCurrentSite().canDownloadFiles(); + this.siteCanDownload = this.sitesProvider.getCurrentSite().canDownloadFiles() && + !this.h5pProvider.isOfflineDisabledInSite(); } /** diff --git a/src/core/h5p/lang/en.json b/src/core/h5p/lang/en.json index 923542f14..d661721f0 100644 --- a/src/core/h5p/lang/en.json +++ b/src/core/h5p/lang/en.json @@ -64,6 +64,7 @@ "offlineDialogRetryButtonLabel": "Retry now", "offlineDialogRetryMessage": "Retrying in :num....", "offlineSuccessfulSubmit": "Successfully submitted results.", + "offlinedisabled": "The site doesn't allow downloading H5P packages.", "originator": "Originator", "pd": "Public Domain", "pddl": "Public Domain Dedication and Licence", diff --git a/src/core/h5p/providers/h5p.ts b/src/core/h5p/providers/h5p.ts index 6750e226c..75fe4b2b9 100644 --- a/src/core/h5p/providers/h5p.ts +++ b/src/core/h5p/providers/h5p.ts @@ -1876,6 +1876,30 @@ export class CoreH5PProvider { }); } + /** + * Check whether H5P offline is disabled. + * + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: whether is disabled. + */ + async isOfflineDisabled(siteId?: string): Promise { + const site = await this.sitesProvider.getSite(siteId); + + return this.isOfflineDisabledInSite(site); + } + + /** + * Check whether H5P offline is disabled. + * + * @param site Site instance. If not defined, current site. + * @return Whether is disabled. + */ + isOfflineDisabledInSite(site?: CoreSite): boolean { + site = site || this.sitesProvider.getCurrentSite(); + + return site.isFeatureDisabled('NoDelegate_H5POffline'); + } + /** * Performs actions required when a library has been installed. * @@ -2578,7 +2602,7 @@ export class CoreH5PProvider { url = url.replace('/webservice/pluginfile', '/pluginfile'); } - return url; + return this.urlUtils.removeUrlParams(url); } /** diff --git a/src/core/h5p/providers/pluginfile-handler.ts b/src/core/h5p/providers/pluginfile-handler.ts index 3a38e6381..8f0ca98d7 100644 --- a/src/core/h5p/providers/pluginfile-handler.ts +++ b/src/core/h5p/providers/pluginfile-handler.ts @@ -22,6 +22,7 @@ import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreH5PProvider } from './h5p'; import { CoreWSExternalFile } from '@providers/ws'; import { FileEntry } from '@ionic-native/file'; +import { TranslateService } from '@ngx-translate/core'; /** * Handler to treat H5P files. @@ -35,7 +36,8 @@ export class CoreH5PPluginFileHandler implements CorePluginFileHandler { protected textUtils: CoreTextUtilsProvider, protected utils: CoreUtilsProvider, protected fileProvider: CoreFileProvider, - protected h5pProvider: CoreH5PProvider) { } + protected h5pProvider: CoreH5PProvider, + protected translate: TranslateService) { } /** * React to a file being deleted. @@ -112,6 +114,28 @@ export class CoreH5PPluginFileHandler implements CorePluginFileHandler { return this.h5pProvider.canGetTrustedH5PFileInSite(); } + /** + * Check if a file is downloadable. + * + * @param file The file data. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with a boolean and a reason why it isn't downloadable if needed. + */ + async isFileDownloadable(file: CoreWSExternalFile, siteId?: string): Promise<{downloadable: boolean, reason?: string}> { + const offlineDisabled = await this.h5pProvider.isOfflineDisabled(siteId); + + if (offlineDisabled) { + return { + downloadable: false, + reason: this.translate.instant('core.h5p.offlinedisabled'), + }; + } else { + return { + downloadable: true, + }; + } + } + /** * 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/login/lang/en.json b/src/core/login/lang/en.json index fe816b435..054a51168 100644 --- a/src/core/login/lang/en.json +++ b/src/core/login/lang/en.json @@ -22,7 +22,6 @@ "emailconfirmsentnoemail": "

An email should have been sent to your address.

It contains easy instructions to complete your registration.

If you continue to have difficulty, contact the site administrator.

", "emailconfirmsentsuccess": "Confirmation email sent successfully", "emailnotmatch": "Emails do not match", - "enterthewordsabove": "Enter the words above", "erroraccesscontrolalloworigin": "The cross-origin call you're trying to perform has been rejected. Please check https://docs.moodle.org/dev/Moodle_Mobile_development_using_Chrome_or_Chromium", "errordeletesite": "An error occurred while deleting this site. Please try again.", "errorupdatesite": "An error occurred while updating the site's token.", @@ -30,7 +29,6 @@ "firsttime": "Is this your first time here?", "forcepasswordchangenotice": "You must change your password to proceed.", "forgotten": "Forgotten your username or password?", - "getanothercaptcha": "Get another CAPTCHA", "help": "Help", "helpmelogin": "

There are many thousands of Moodle sites around the world. This app can only connect to Moodle sites that have specifically enabled Mobile app access.

If you can't connect to your Moodle site then you need to contact your site administrator and ask them to read http://docs.moodle.org/en/Mobile_app

To test the app in a Moodle demo site type teacher or student in the Site address field and click the Connect button.

", "instructions": "Instructions", diff --git a/src/core/login/pages/credentials/credentials.html b/src/core/login/pages/credentials/credentials.html index 467bb49dd..cff98e8b5 100644 --- a/src/core/login/pages/credentials/credentials.html +++ b/src/core/login/pages/credentials/credentials.html @@ -3,8 +3,8 @@ {{ 'core.login.login' | translate }} - @@ -23,7 +23,7 @@

{{siteUrl}}

- + diff --git a/src/core/login/pages/credentials/credentials.ts b/src/core/login/pages/credentials/credentials.ts index ed0b43d0e..5c2d5909e 100644 --- a/src/core/login/pages/credentials/credentials.ts +++ b/src/core/login/pages/credentials/credentials.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component } from '@angular/core'; +import { Component, ViewChild, ElementRef } from '@angular/core'; import { IonicPage, NavController, NavParams } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; import { CoreAppProvider } from '@providers/app'; @@ -32,6 +32,9 @@ import { CoreConfigConstants } from '../../../../configconstants'; templateUrl: 'credentials.html', }) export class CoreLoginCredentialsPage { + + @ViewChild('credentialsForm') formElement: ElementRef; + credForm: FormGroup; siteUrl: string; siteChecked = false; @@ -153,11 +156,11 @@ export class CoreLoginCredentialsPage { protected treatSiteConfig(): void { if (this.siteConfig) { this.siteName = CoreConfigConstants.sitename ? CoreConfigConstants.sitename : this.siteConfig.sitename; - this.logoUrl = this.siteConfig.logourl || this.siteConfig.compactlogourl; + this.logoUrl = this.loginHelper.getLogoUrl(this.siteConfig); this.authInstructions = this.siteConfig.authinstructions || this.translate.instant('core.login.loginsteps'); - this.identityProviders = this.loginHelper.getValidIdentityProviders(this.siteConfig); const disabledFeatures = this.loginHelper.getDisabledFeatures(this.siteConfig); + this.identityProviders = this.loginHelper.getValidIdentityProviders(this.siteConfig, disabledFeatures); this.canSignup = this.siteConfig.registerauth == 'email' && !this.loginHelper.isEmailSignupDisabled(this.siteConfig, disabledFeatures); this.showForgottenPassword = !this.loginHelper.isForgottenPasswordDisabled(this.siteConfig, disabledFeatures); @@ -242,6 +245,8 @@ export class CoreLoginCredentialsPage { } }).finally(() => { modal.dismiss(); + + this.domUtils.triggerFormSubmittedEvent(this.formElement, true); }); } diff --git a/src/core/login/pages/email-signup/email-signup.html b/src/core/login/pages/email-signup/email-signup.html index 2e49ea822..3e52b4b6c 100644 --- a/src/core/login/pages/email-signup/email-signup.html +++ b/src/core/login/pages/email-signup/email-signup.html @@ -17,7 +17,7 @@ - +

{{ 'core.agelocationverification' | translate }}

@@ -47,7 +47,7 @@ -
+

{{siteUrl}}

diff --git a/src/core/login/pages/email-signup/email-signup.ts b/src/core/login/pages/email-signup/email-signup.ts index ffb8a6f42..62121f94b 100644 --- a/src/core/login/pages/email-signup/email-signup.ts +++ b/src/core/login/pages/email-signup/email-signup.ts @@ -12,9 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, ViewChild } from '@angular/core'; +import { Component, ViewChild, ElementRef } from '@angular/core'; import { IonicPage, NavController, NavParams, Content } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; +import { CoreEventsProvider } from '@providers/events'; import { CoreSitesProvider } from '@providers/sites'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreTextUtilsProvider } from '@providers/utils/text'; @@ -35,6 +36,8 @@ import { CoreConfigConstants } from '../../../../configconstants'; }) export class CoreLoginEmailSignupPage { @ViewChild(Content) content: Content; + @ViewChild('ageForm') ageFormElement: ElementRef; + @ViewChild('signupFormEl') signupFormElement: ElementRef; signupForm: FormGroup; siteUrl: string; @@ -66,10 +69,18 @@ export class CoreLoginEmailSignupPage { policyErrors: any; namefieldsErrors: any; - constructor(private navCtrl: NavController, navParams: NavParams, private fb: FormBuilder, private wsProvider: CoreWSProvider, - private sitesProvider: CoreSitesProvider, private loginHelper: CoreLoginHelperProvider, - private domUtils: CoreDomUtilsProvider, private translate: TranslateService, private utils: CoreUtilsProvider, - private textUtils: CoreTextUtilsProvider, private userProfileFieldDelegate: CoreUserProfileFieldDelegate) { + constructor(protected navCtrl: NavController, + navParams: NavParams, + protected fb: FormBuilder, + protected wsProvider: CoreWSProvider, + protected sitesProvider: CoreSitesProvider, + protected loginHelper: CoreLoginHelperProvider, + protected domUtils: CoreDomUtilsProvider, + protected translate: TranslateService, + protected utils: CoreUtilsProvider, + protected textUtils: CoreTextUtilsProvider, + protected userProfileFieldDelegate: CoreUserProfileFieldDelegate, + protected eventsProvider: CoreEventsProvider) { this.siteUrl = navParams.get('siteUrl'); @@ -265,6 +276,9 @@ export class CoreLoginEmailSignupPage { return this.wsProvider.callAjax('auth_email_signup_user', params, { siteUrl: this.siteUrl }); }).then((result) => { if (result.success) { + + this.domUtils.triggerFormSubmittedEvent(this.signupFormElement, true); + // Show alert and ho back. const message = this.translate.instant('core.login.emailconfirmsent', { $a: params.email }); this.domUtils.showAlert(this.translate.instant('core.success'), message); @@ -334,6 +348,9 @@ export class CoreLoginEmailSignupPage { params.age = parseInt(params.age, 10); // Use just the integer part. this.wsProvider.callAjax('core_auth_is_minor', params, {siteUrl: this.siteUrl}).then((result) => { + + this.domUtils.triggerFormSubmittedEvent(this.ageFormElement, true); + if (!result.status) { if (this.countryControl.value) { this.signUpCountryControl.setValue(this.countryControl.value); diff --git a/src/core/login/pages/forgotten-password/forgotten-password.html b/src/core/login/pages/forgotten-password/forgotten-password.html index 182ef2312..81905e4d4 100644 --- a/src/core/login/pages/forgotten-password/forgotten-password.html +++ b/src/core/login/pages/forgotten-password/forgotten-password.html @@ -10,7 +10,7 @@
- + {{ 'core.login.searchby' | translate }} diff --git a/src/core/login/pages/forgotten-password/forgotten-password.ts b/src/core/login/pages/forgotten-password/forgotten-password.ts index b1d5d6785..212840455 100644 --- a/src/core/login/pages/forgotten-password/forgotten-password.ts +++ b/src/core/login/pages/forgotten-password/forgotten-password.ts @@ -12,9 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component } from '@angular/core'; +import { Component, ViewChild, ElementRef } from '@angular/core'; import { IonicPage, NavController, NavParams } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; +import { CoreEventsProvider } from '@providers/events'; +import { CoreSitesProvider } from '@providers/sites'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreLoginHelperProvider } from '../../providers/helper'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; @@ -28,11 +30,20 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; templateUrl: 'forgotten-password.html', }) export class CoreLoginForgottenPasswordPage { + + @ViewChild('resetPasswordForm') formElement: ElementRef; + myForm: FormGroup; siteUrl: string; - constructor(private navCtrl: NavController, navParams: NavParams, fb: FormBuilder, private translate: TranslateService, - private loginHelper: CoreLoginHelperProvider, private domUtils: CoreDomUtilsProvider) { + constructor(protected navCtrl: NavController, + navParams: NavParams, + fb: FormBuilder, + protected translate: TranslateService, + protected loginHelper: CoreLoginHelperProvider, + protected domUtils: CoreDomUtilsProvider, + protected eventsProvider: CoreEventsProvider, + protected sitesProvider: CoreSitesProvider) { this.siteUrl = navParams.get('siteUrl'); this.myForm = fb.group({ @@ -71,6 +82,8 @@ export class CoreLoginForgottenPasswordPage { this.domUtils.showErrorModal(response.notice); } else { // Success. + this.domUtils.triggerFormSubmittedEvent(this.formElement, true); + this.domUtils.showAlert(this.translate.instant('core.success'), response.notice); this.navCtrl.pop(); } diff --git a/src/core/login/pages/reconnect/reconnect.html b/src/core/login/pages/reconnect/reconnect.html index 3876f7e76..1b87186ee 100644 --- a/src/core/login/pages/reconnect/reconnect.html +++ b/src/core/login/pages/reconnect/reconnect.html @@ -29,7 +29,7 @@ {{ 'core.login.reconnectdescription' | translate }}

- +

{{username}}

@@ -51,7 +51,7 @@ -
diff --git a/src/core/login/pages/reconnect/reconnect.ts b/src/core/login/pages/reconnect/reconnect.ts index afa776e21..f36a21975 100644 --- a/src/core/login/pages/reconnect/reconnect.ts +++ b/src/core/login/pages/reconnect/reconnect.ts @@ -12,9 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component } from '@angular/core'; +import { Component, ViewChild, ElementRef } from '@angular/core'; import { IonicPage, NavController, NavParams } from 'ionic-angular'; import { CoreAppProvider } from '@providers/app'; +import { CoreEventsProvider } from '@providers/events'; import { CoreSitesProvider } from '@providers/sites'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreLoginHelperProvider } from '../../providers/helper'; @@ -29,6 +30,9 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; templateUrl: 'reconnect.html', }) export class CoreLoginReconnectPage { + + @ViewChild('reconnectForm') formElement: ElementRef; + credForm: FormGroup; siteUrl: string; username: string; @@ -38,6 +42,7 @@ export class CoreLoginReconnectPage { site: any; showForgottenPassword = true; showSiteAvatar = false; + isOAuth = false; protected infoSiteUrl: string; protected pageName: string; @@ -46,13 +51,14 @@ export class CoreLoginReconnectPage { protected isLoggedOut: boolean; protected siteId: string; - constructor(private navCtrl: NavController, + constructor(protected navCtrl: NavController, navParams: NavParams, fb: FormBuilder, - private appProvider: CoreAppProvider, - private sitesProvider: CoreSitesProvider, - private loginHelper: CoreLoginHelperProvider, - private domUtils: CoreDomUtilsProvider) { + protected appProvider: CoreAppProvider, + protected sitesProvider: CoreSitesProvider, + protected loginHelper: CoreLoginHelperProvider, + protected domUtils: CoreDomUtilsProvider, + protected eventsProvider: CoreEventsProvider) { const currentSite = this.sitesProvider.getCurrentSite(); @@ -74,8 +80,7 @@ export class CoreLoginReconnectPage { */ ionViewDidLoad(): void { if (this.siteConfig) { - this.identityProviders = this.loginHelper.getValidIdentityProviders(this.siteConfig); - this.showForgottenPassword = !this.loginHelper.isForgottenPasswordDisabled(this.siteConfig); + this.getDataFromConfig(this.siteConfig); } this.sitesProvider.getSite(this.siteId).then((site) => { @@ -89,6 +94,9 @@ export class CoreLoginReconnectPage { this.siteUrl = site.infos.siteurl; this.siteName = site.getSiteName(); + // If login was OAuth we should only reach this page if the OAuth method ID has changed. + this.isOAuth = site.isOAuth(); + // Show logo instead of avatar if it's a fixed site. this.showSiteAvatar = this.site.avatar && !this.loginHelper.getFixedSites(); @@ -97,10 +105,10 @@ export class CoreLoginReconnectPage { // Check logoURL if user avatar is not set. if (this.site.avatar.startsWith(site.infos.siteurl + '/theme/image.php')) { this.showSiteAvatar = false; - this.logoUrl = config.logourl || config.compactlogourl; + this.logoUrl = this.loginHelper.getLogoUrl(config); } - this.showForgottenPassword = !this.loginHelper.isForgottenPasswordDisabled(config); + this.getDataFromConfig(this.siteConfig); }).catch(() => { this.cancel(); }); @@ -111,7 +119,18 @@ export class CoreLoginReconnectPage { // Shouldn't happen. Just leave the view. this.cancel(); }); + } + /** + * Get some data (like identity providers) from the site config. + * + * @param config Config to use. + */ + protected getDataFromConfig(config: any): void { + const disabledFeatures = this.loginHelper.getDisabledFeatures(config); + + this.identityProviders = this.loginHelper.getValidIdentityProviders(config, disabledFeatures); + this.showForgottenPassword = !this.loginHelper.isForgottenPasswordDisabled(config); } /** @@ -161,6 +180,9 @@ export class CoreLoginReconnectPage { // Start the authentication process. this.sitesProvider.getUserToken(siteUrl, username, password).then((data) => { return this.sitesProvider.updateSiteToken(this.infoSiteUrl, username, data.token, data.privateToken).then(() => { + + this.domUtils.triggerFormSubmittedEvent(this.formElement, true); + // Update site info too because functions might have changed (e.g. unisntall local_mobile). return this.sitesProvider.updateSiteInfoByUrl(this.infoSiteUrl, username).then(() => { // Reset fields so the data is not in the view anymore. diff --git a/src/core/login/pages/site/site.html b/src/core/login/pages/site/site.html index 9503d5335..8da75c237 100644 --- a/src/core/login/pages/site/site.html +++ b/src/core/login/pages/site/site.html @@ -3,8 +3,8 @@ {{ 'core.login.connecttomoodle' | translate }} - - + + + + {{item.searchedtext}} + + diff --git a/src/core/search/components/search-box/search-box.scss b/src/core/search/components/search-box/search-box.scss new file mode 100644 index 000000000..a800669ef --- /dev/null +++ b/src/core/search/components/search-box/search-box.scss @@ -0,0 +1,44 @@ +ion-app.app-root core-search-box { + height: 65px; + display: block; + + ion-card { + position: absolute; + z-index: 4; + } + + .button.item-button[icon-only] { + margin: 0; + padding: ($content-padding / 2) $content-padding; + } + .item.item-input.item-block .item-inner, + .item.item-input.item-block .item-inner ion-input { + border-bottom: 0 !important; + box-shadow: none; + } + + .item-inner { + padding-right: 0 !important; + padding-left: 0 !important; + } + + .core-search-history { + display: none; + max-height: calc(-120px + 80vh); + overflow-y: auto; + + .item:hover { + background-color: $gray-lighter; + } + + .list .item.item-block:last-child > .item-inner { + border-bottom: 0; + } + } + + form:focus-within { + .core-search-history { + display: block; + } + } +} diff --git a/src/components/search-box/search-box.ts b/src/core/search/components/search-box/search-box.ts similarity index 52% rename from src/components/search-box/search-box.ts rename to src/core/search/components/search-box/search-box.ts index 3846e2f77..8a3116c14 100644 --- a/src/components/search-box/search-box.ts +++ b/src/core/search/components/search-box/search-box.ts @@ -12,9 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core'; +import { Component, Input, Output, EventEmitter, OnInit, ViewChild, ElementRef } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; +import { CoreEventsProvider } from '@providers/events'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreUtilsProvider } from '@providers/utils/utils'; +import { CoreSearchHistoryProvider, CoreSearchHistoryItem } from '../../providers/search-history'; /** * Component to display a "search box". @@ -39,14 +43,27 @@ export class CoreSearchBoxComponent implements OnInit { @Input() lengthCheck = 3; // Check value length before submit. If 0, any string will be submitted. @Input() showClear = true; // Show/hide clear button. @Input() disabled = false; // Disables the input text. - @Input() initialSearch: string; // Initial search text. + @Input() protected initialSearch: string; // Initial search text. + @Input() protected searchArea?: string; // If provided. It will save and display a history of searches for this particular Id. + // To use different history lists, place different Id. + // I.e. AddonMessagesContacts or CoreUserParticipants-6 (using the course Id). @Output() onSubmit: EventEmitter; // Send data when submitting the search form. @Output() onClear: EventEmitter; // Send event when clearing the search form. - searched = false; - searchText = ''; + @ViewChild('searchForm') formElement: ElementRef; - constructor(private translate: TranslateService, private utils: CoreUtilsProvider) { + searched = ''; // Last search emitted. + searchText = ''; + history: CoreSearchHistoryItem[] = []; + historyShown = false; + + constructor(protected translate: TranslateService, + protected utils: CoreUtilsProvider, + protected searchHistoryProvider: CoreSearchHistoryProvider, + protected eventsProvider: CoreEventsProvider, + protected sitesProvider: CoreSitesProvider, + protected domUtils: CoreDomUtilsProvider, + ) { this.onSubmit = new EventEmitter(); this.onClear = new EventEmitter(); } @@ -57,6 +74,10 @@ export class CoreSearchBoxComponent implements OnInit { this.spellcheck = this.utils.isTrueOrOne(this.spellcheck); this.showClear = this.utils.isTrueOrOne(this.showClear); this.searchText = this.initialSearch || ''; + + if (this.searchArea) { + this.loadHistory(); + } } /** @@ -64,24 +85,66 @@ export class CoreSearchBoxComponent implements OnInit { * * @param e Event. */ - submitForm(e: Event): void { - e.preventDefault(); - e.stopPropagation(); + submitForm(e?: Event): void { + e && e.preventDefault(); + e && e.stopPropagation(); if (this.searchText.length < this.lengthCheck) { // The view should handle this case, but we check it here too just in case. return; } - this.searched = true; + if (this.searchArea) { + this.saveSearchToHistory(this.searchText); + } + + this.domUtils.triggerFormSubmittedEvent(this.formElement, false, this.sitesProvider.getCurrentSiteId()); + + this.searched = this.searchText; this.onSubmit.emit(this.searchText); } + /** + * Saves the search term onto the history. + * + * @param text Text to save. + * @return Promise resolved when done. + */ + protected async saveSearchToHistory(text: string): Promise { + try { + await this.searchHistoryProvider.insertOrUpdateSearchText(this.searchArea, text.toLowerCase()); + } finally { + this.loadHistory(); + } + } + + /** + * Loads search history. + * + * @return Promise resolved when done. + */ + protected async loadHistory(): Promise { + this.history = await this.searchHistoryProvider.getSearchHistory(this.searchArea); + } + + /** + * Select an item and use it for search text. + * + * @param e Event. + * @param text Selected text. + */ + historyClicked(e: Event, text: string): void { + if (this.searched != text) { + this.searchText = text; + this.submitForm(e); + } + } + /** * Form submitted. */ clearForm(): void { - this.searched = false; + this.searched = ''; this.searchText = ''; this.onClear.emit(); } diff --git a/src/core/search/providers/search-history.ts b/src/core/search/providers/search-history.ts new file mode 100644 index 000000000..b70e91cb7 --- /dev/null +++ b/src/core/search/providers/search-history.ts @@ -0,0 +1,176 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; +import { SQLiteDB } from '@classes/sqlitedb'; + +/** + * Search history item definition. + */ +export interface CoreSearchHistoryItem { + searcharea: string; // Search area where the search has been performed. + lastused?: number; // Timestamp of the last search. + searchedtext: string; // Text of the performed search. + times?: number; // Times search has been performed (if previously in history). +} + +/** + * Service that enables adding a history to a search box. + */ +@Injectable() +export class CoreSearchHistoryProvider { + + protected static HISTORY_TABLE = 'seach_history'; + protected static HISTORY_LIMIT = 10; + + protected siteSchema: CoreSiteSchema = { + name: 'CoreSearchHistoryProvider', + version: 1, + tables: [ + { + name: CoreSearchHistoryProvider.HISTORY_TABLE, + columns: [ + { + name: 'searcharea', + type: 'TEXT', + notNull: true, + }, + { + name: 'lastused', + type: 'INTEGER', + notNull: true, + }, + { + name: 'times', + type: 'INTEGER', + notNull: true, + }, + { + name: 'searchedtext', + type: 'TEXT', + notNull: true, + }, + ], + primaryKeys: ['searcharea', 'searchedtext'], + }, + ], + }; + + constructor(protected sitesProvider: CoreSitesProvider) { + + this.sitesProvider.registerSiteSchema(this.siteSchema); + } + + /** + * Get a search area history sorted by use. + * + * @param searchArea Search Area Name. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the list of items when done. + */ + async getSearchHistory(searchArea: string, siteId?: string): Promise { + const site = await this.sitesProvider.getSite(siteId); + const conditions: any = { + searcharea: searchArea, + }; + + const history = await site.getDb().getRecords(CoreSearchHistoryProvider.HISTORY_TABLE, conditions); + + // Sorting by last used DESC. + return history.sort((a, b) => { + return b.lastused - a.lastused; + }); + } + + /** + * Controls search limit and removes the last item if overflows. + * + * @param searchArea Search area to control + * @param db SQLite DB where to perform the search. + * @return Resolved when done. + */ + protected async controlSearchLimit(searchArea: string, db: SQLiteDB): Promise { + const items = await this.getSearchHistory(searchArea); + if (items.length > CoreSearchHistoryProvider.HISTORY_LIMIT) { + // Over the limit. Remove the last. + const lastItem = items.pop(); + + const searchItem: CoreSearchHistoryItem = { + searcharea: lastItem.searcharea, + searchedtext: lastItem.searchedtext, + }; + + await db.deleteRecords(CoreSearchHistoryProvider.HISTORY_TABLE, searchItem); + } + } + + /** + * Updates the search history item if exists. + * + * @param searchArea Area where the search has been performed. + * @param text Text of the performed text. + * @param db SQLite DB where to perform the search. + * @return True if exists, false otherwise. + */ + protected async updateExistingItem(searchArea: string, text: string, db: SQLiteDB): Promise { + const searchItem: CoreSearchHistoryItem = { + searcharea: searchArea, + searchedtext: text, + }; + + try { + const existingItem = await db.getRecord(CoreSearchHistoryProvider.HISTORY_TABLE, searchItem); + + // If item exist, update time and number of times searched. + existingItem.lastused = Date.now(); + existingItem.times++; + + await db.updateRecords(CoreSearchHistoryProvider.HISTORY_TABLE, existingItem, searchItem); + + return true; + } catch (e) { + return false; + } + } + + /** + * Inserts a searched term on the history. + * + * @param searchArea Area where the search has been performed. + * @param text Text of the performed text. + * @param siteId Site ID. If not defined, current site. + * @return Resolved when done. + */ + async insertOrUpdateSearchText(searchArea: string, text: string, siteId?: string): Promise { + const site = await this.sitesProvider.getSite(siteId); + const db = site.getDb(); + + const exists = await this.updateExistingItem(searchArea, text, db); + + if (!exists) { + // If item is new, control the history does not goes over the limit. + const searchItem: CoreSearchHistoryItem = { + searcharea: searchArea, + searchedtext: text, + lastused: Date.now(), + times: 1, + }; + + await site.getDb().insertRecord(CoreSearchHistoryProvider.HISTORY_TABLE, searchItem); + + await this.controlSearchLimit(searchArea, db); + } + } +} diff --git a/src/core/search/search.module.ts b/src/core/search/search.module.ts new file mode 100644 index 000000000..4026fb898 --- /dev/null +++ b/src/core/search/search.module.ts @@ -0,0 +1,30 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { CoreSearchComponentsModule } from './components/components.module'; +import { CoreSearchHistoryProvider } from './providers/search-history'; + +@NgModule({ + declarations: [ + ], + imports: [ + CoreSearchComponentsModule, + ], + providers: [ + CoreSearchComponentsModule, + CoreSearchHistoryProvider, + ], +}) +export class CoreSearchModule {} diff --git a/src/core/settings/lang/en.json b/src/core/settings/lang/en.json index 32a82c6cc..a91251cb9 100644 --- a/src/core/settings/lang/en.json +++ b/src/core/settings/lang/en.json @@ -1,6 +1,7 @@ { "about": "About", - "appready": "App ready", + "appsettings": "App settings", + "appversion": "App version", "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", @@ -8,6 +9,7 @@ "colorscheme-dark": "Dark", "colorscheme-light": "Light", "compilationinfo": "Compilation info", + "copyinfo": "Copy device info on the clipboard", "cordovadevicemodel": "Cordova device model", "cordovadeviceosversion": "Cordova device OS version", "cordovadeviceplatform": "Cordova device platform", @@ -20,7 +22,6 @@ "deletesitefilestitle": "Delete site files", "deviceinfo": "Device info", "deviceos": "Device OS", - "devicewebworkers": "Device web workers supported", "disableall": "Disable notifications", "disabled": "Disabled", "displayformat": "Display format", @@ -36,9 +37,11 @@ "filesystemroot": "File system root", "fontsize": "Text size", "fontsizecharacter": "A", + "forcedsetting": "This setting has been forced by your site configuration.", "general": "General", "language": "Language", - "license": "Licence", + "license": "License", + "opensourcelicenses": "Open Source Licences", "localnotifavailable": "Local notifications available", "locationhref": "Web view URL", "locked": "Locked", @@ -48,18 +51,22 @@ "navigatoruseragent": "Navigator userAgent", "networkstatus": "Internet connection status", "privacypolicy": "Privacy policy", + "publisher": "Publisher", "pushid": "Push notifications ID", "reportinbackground": "Report errors automatically", "entriesincache": "{{$a}} entries in cache", + "screen": "Screen information", "settings": "Settings", + "preferences": "Preferences", "showdownloadoptions": "Show download options", + "siteinfo": "Site info", "sites": "Sites", "spaceusage": "Space usage", + "spaceusagehelp": "Deleting the stored information of the site will remove all the site offline data. This information allows you to use the app when offline. ", "synchronization": "Synchronisation", "synchronizenow": "Synchronise now", + "synchronizenowhelp": "Synchronising a site will send pending changes and all offline activity stored in the device and will synchronise some data like messages and notifications.", "syncsettings": "Synchronisation settings", "total": "Total", - "versioncode": "Version code", - "versionname": "Version name", "wificonnection": "Wi-Fi connection" } diff --git a/src/core/settings/pages/about/about.html b/src/core/settings/pages/about/about.html index 01ec8e930..0c051bbda 100644 --- a/src/core/settings/pages/about/about.html +++ b/src/core/settings/pages/about/about.html @@ -7,120 +7,16 @@

{{ appName }} {{ versionName }}

- - - {{ 'core.settings.license' | translate }} - - -

Apache 2.0

-

http://www.apache.org/licenses/LICENSE-2.0

-
-
- - - {{ 'core.settings.privacypolicy' | translate }} - - -

{{ privacyPolicy }}

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

{{ siteUrl }}

-
-
- - - {{ 'core.settings.deviceinfo' | translate }} - - -

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

-

{{ versionName }}

-
- -

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

-

{{ versionCode }}

-
- -

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

-

{{ compilationTime | coreFormatDate: "LLL Z": false }}

-

{{ lastCommit }}

-
- -

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

-

{{ fileSystemRoot }}

-

{{ fileSystemRoot }}

-
- -

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

-

{{ navigator.userAgent }}

-
- -

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

-

{{ navigator.language }}

-
- -

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

-

{{ locationHref }}

-
- -

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

-

{{ appReady | translate }}

-
- -

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

-

{{ deviceType | translate }}

-
- -

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

-

{{ deviceOs | translate }}

-
- -

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

-

{{ currentLanguage }}

-
- -

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

-

{{ networkStatus | translate }}

-
- -

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

-

{{ wifiConnection | translate }}

-
- -

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

-

{{ deviceWebWorkers | translate }}

-
- -

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

-

{{ device.cordova }}

-
- -

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

-

{{ device.platform }}

-
- -

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

-

{{ device.version }}

-
- -

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

-

{{ device.model }}

-
- -

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

-

{{ device.uuid }}

-
- -

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

-

{{ pushId }}

-
- -

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

-

{{ localNotifAvailable | translate }}

-
-
+ + + {{ 'core.settings.opensourcelicenses' | translate }} + + + + {{ 'core.settings.privacypolicy' | translate }} + + + + {{ 'core.settings.deviceinfo' | translate }} +
diff --git a/src/core/settings/pages/about/about.ts b/src/core/settings/pages/about/about.ts index 4f4474d0a..4c6241663 100644 --- a/src/core/settings/pages/about/about.ts +++ b/src/core/settings/pages/about/about.ts @@ -12,17 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, } from '@angular/core'; -import { IonicPage, Platform } from 'ionic-angular'; -import { Device } from '@ionic-native/device'; +import { Component, Optional } from '@angular/core'; +import { IonicPage, NavController } from 'ionic-angular'; import { CoreAppProvider } from '@providers/app'; -import { CoreFileProvider } from '@providers/file'; -import { CoreInitDelegate } from '@providers/init'; -import { CoreLangProvider } from '@providers/lang'; -import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; import { CoreSitesProvider } from '@providers/sites'; +import { CoreSplitViewComponent } from '@components/split-view/split-view'; import { CoreConfigConstants } from '../../../../configconstants'; -import { CorePushNotificationsProvider } from '@core/pushnotifications/providers/pushnotifications'; /** * Page that displays the about settings. @@ -36,89 +31,30 @@ export class CoreSettingsAboutPage { appName: string; versionName: string; - versionCode: number; - compilationTime: number; - lastCommit: string; privacyPolicy: string; - navigator: Navigator; - locationHref: string; - appReady: string; - deviceType: string; - deviceOs: string; - currentLanguage: string; - networkStatus: string; - wifiConnection: string; - deviceWebWorkers: string; - device: Device; - fileSystemRoot: string; - fsClickable: boolean; - 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, - localNotificationsProvider: CoreLocalNotificationsProvider, pushNotificationsProvider: CorePushNotificationsProvider) { + constructor(appProvider: CoreAppProvider, + sitesProvider: CoreSitesProvider, + @Optional() private svComponent: CoreSplitViewComponent, + protected navCtrl: NavController) { const currentSite = sitesProvider.getCurrentSite(); this.appName = appProvider.isDesktop() ? CoreConfigConstants.desktopappname : CoreConfigConstants.appname; this.versionName = CoreConfigConstants.versionname; - this.versionCode = CoreConfigConstants.versioncode; - this.compilationTime = CoreConfigConstants.compilationtime; - this.lastCommit = CoreConfigConstants.lastcommit; // Calculate the privacy policy to use. this.privacyPolicy = (currentSite && (currentSite.getStoredConfig('tool_mobile_apppolicy') || currentSite.getStoredConfig('sitepolicy'))) || CoreConfigConstants.privacypolicy; + } - this.navigator = window.navigator; - if (window.location && window.location.href) { - const url = window.location.href; - this.locationHref = url.substr(0, url.indexOf('#')); - } - - this.appReady = initDelegate.isReady() ? 'core.yes' : 'core.no'; - this.deviceType = platform.is('tablet') ? 'core.tablet' : 'core.phone'; - - if (platform.is('android')) { - this.deviceOs = 'core.android'; - } else if (platform.is('ios')) { - this.deviceOs = 'core.ios'; - } else if (platform.is('windows')) { - this.deviceOs = 'core.windowsphone'; - } else { - const matches = navigator.userAgent.match(/\(([^\)]*)\)/); - if (matches && matches.length > 1) { - this.deviceOs = matches[1]; - } else { - this.deviceOs = 'core.unknown'; - } - } - - langProvider.getCurrentLanguage().then((lang) => { - this.currentLanguage = lang; - }); - - this.networkStatus = appProvider.isOnline() ? 'core.online' : 'core.offline'; - this.wifiConnection = appProvider.isWifi() ? 'core.yes' : 'core.no'; - this.deviceWebWorkers = !!window['Worker'] && !!window['URL'] ? 'core.yes' : 'core.no'; - this.device = device; - - if (fileProvider.isAvailable()) { - fileProvider.getBasePath().then((basepath) => { - this.fileSystemRoot = basepath; - this.fsClickable = fileProvider.usesHTMLAPI(); - }); - } - - 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; + /** + * Opens a page. + * + * @param page The component deeplink name you want to push onto the navigation stack. + */ + openPage(page: string): void { + const navCtrl = this.svComponent ? this.svComponent.getMasterNav() : this.navCtrl; + navCtrl.push(page); } } diff --git a/src/core/settings/pages/list/list.html b/src/core/settings/pages/app/app.html similarity index 67% rename from src/core/settings/pages/list/list.html rename to src/core/settings/pages/app/app.html index 4cdf4958f..4d79ded29 100644 --- a/src/core/settings/pages/list/list.html +++ b/src/core/settings/pages/app/app.html @@ -1,6 +1,6 @@ - {{ 'core.settings.settings' | translate}} + {{ 'core.settings.appsettings' | translate}} @@ -10,29 +10,23 @@ -

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

+

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

-

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

+

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

-

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

+

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

-

{{ 'core.sharedfiles.sharedfiles' | translate }}

+

{{ 'core.sharedfiles.sharedfiles' | translate }}

- - - -

{{ handler.title | translate}}

-
- -

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

+

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

diff --git a/src/core/settings/pages/list/list.module.ts b/src/core/settings/pages/app/app.module.ts similarity index 85% rename from src/core/settings/pages/list/list.module.ts rename to src/core/settings/pages/app/app.module.ts index bec88bfb9..47516a42e 100644 --- a/src/core/settings/pages/list/list.module.ts +++ b/src/core/settings/pages/app/app.module.ts @@ -15,19 +15,19 @@ import { NgModule } from '@angular/core'; import { IonicPageModule } from 'ionic-angular'; import { TranslateModule } from '@ngx-translate/core'; -import { CoreSettingsListPage } from './list'; +import { CoreAppSettingsPage } from './app'; import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; @NgModule({ declarations: [ - CoreSettingsListPage + CoreAppSettingsPage ], imports: [ CoreComponentsModule, CoreDirectivesModule, - IonicPageModule.forChild(CoreSettingsListPage), + IonicPageModule.forChild(CoreAppSettingsPage), TranslateModule.forChild() ], }) -export class CoreSettingsListPageModule {} +export class CoreAppSettingsPageModule {} diff --git a/src/core/settings/pages/list/list.ts b/src/core/settings/pages/app/app.ts similarity index 76% rename from src/core/settings/pages/list/list.ts rename to src/core/settings/pages/app/app.ts index 604ef9069..e459cd241 100644 --- a/src/core/settings/pages/list/list.ts +++ b/src/core/settings/pages/app/app.ts @@ -14,25 +14,24 @@ import { Component, ViewChild } from '@angular/core'; import { IonicPage, NavParams, Platform } from 'ionic-angular'; -import { CoreSettingsDelegate, CoreSettingsHandlerData } from '../../providers/delegate'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; /** - * Page that displays the list of settings pages. + * Page that displays the list of app settings pages. */ -@IonicPage({segment: 'core-settings-list'}) +@IonicPage({segment: 'core-settings-app'}) @Component({ - selector: 'page-core-settings-list', - templateUrl: 'list.html', + selector: 'page-core-settings-app', + templateUrl: 'app.html', }) -export class CoreSettingsListPage { +export class CoreAppSettingsPage { @ViewChild(CoreSplitViewComponent) splitviewCtrl: CoreSplitViewComponent; - handlers: CoreSettingsHandlerData[]; isIOS: boolean; selectedPage: string; - constructor(private settingsDelegate: CoreSettingsDelegate, platorm: Platform, navParams: NavParams) { + constructor(platorm: Platform, + navParams: NavParams) { this.isIOS = platorm.is('ios'); this.selectedPage = navParams.get('page') || false; @@ -42,7 +41,6 @@ export class CoreSettingsListPage { * View loaded. */ ionViewDidLoad(): void { - this.handlers = this.settingsDelegate.getHandlers(); if (this.selectedPage) { this.openHandler(this.selectedPage); } else if (this.splitviewCtrl.isOn()) { diff --git a/src/core/settings/pages/deviceinfo/deviceinfo.html b/src/core/settings/pages/deviceinfo/deviceinfo.html new file mode 100644 index 000000000..042b7a00c --- /dev/null +++ b/src/core/settings/pages/deviceinfo/deviceinfo.html @@ -0,0 +1,96 @@ + + + {{ 'core.settings.deviceinfo' | translate }} + + + + + + + +

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

+

{{ deviceInfo.versionName }} ({{ deviceInfo.versionCode }})

+
+ +

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

+

{{ deviceInfo.compilationTime | coreFormatDate: "LLL Z": false }}

+

{{ deviceInfo.lastCommit }}

+
+ +

{{ 'core.settings.siteinfo' | translate }} *

+

{{ deviceInfo.siteUrl }}

+

{{ deviceInfo.siteVersion }}

+

{{ deviceInfo.siteId }}

+
+ +

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

+

{{ deviceInfo.fileSystemRoot }}

+

{{ deviceInfo.fileSystemRoot }}

+
+ +

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

+

{{ deviceInfo.userAgent }}

+
+ +

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

+

{{ deviceInfo.browserLanguage }}

+
+ +

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

+

{{ currentLangName }} ({{ deviceInfo.currentLanguage }})

+
+ +

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

+

{{ deviceInfo.locationHref }}

+
+ +

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

+

{{ 'core.' + deviceInfo.deviceType | translate }}

+
+ +

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

+

{{ deviceOsTranslated }}

+
+ +

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

+

{{ deviceInfo.screen | translate }}

+
+ +

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

+

{{ 'core.' + deviceInfo.networkStatus | translate }}

+
+ +

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

+

{{ 'core.' + deviceInfo.wifiConnection | translate }}

+
+ +

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

+

{{ deviceInfo.cordovaVersion }}

+
+ +

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

+

{{ deviceInfo.platform }}

+
+ +

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

+

{{ deviceInfo.osVersion }}

+
+ +

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

+

{{ deviceInfo.model }}

+
+ +

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

+

{{ deviceInfo.uuid }}

+
+ +

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

+

{{ deviceInfo.pushId }}

+
+ +

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

+

{{ 'core.' + deviceInfo.localNotifAvailable | translate }}

+
+
diff --git a/src/core/settings/pages/deviceinfo/deviceinfo.module.ts b/src/core/settings/pages/deviceinfo/deviceinfo.module.ts new file mode 100644 index 000000000..7c192b47a --- /dev/null +++ b/src/core/settings/pages/deviceinfo/deviceinfo.module.ts @@ -0,0 +1,35 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreSettingsDeviceInfoPage } from './deviceinfo'; +import { CoreComponentsModule } from '@components/components.module'; +import { CoreDirectivesModule } from '@directives/directives.module'; +import { CorePipesModule } from '@pipes/pipes.module'; + +@NgModule({ + declarations: [ + CoreSettingsDeviceInfoPage + ], + imports: [ + CoreComponentsModule, + CoreDirectivesModule, + CorePipesModule, + IonicPageModule.forChild(CoreSettingsDeviceInfoPage), + TranslateModule.forChild() + ], +}) +export class CoreSettingsDeviceInfoPageModule {} diff --git a/src/core/settings/pages/deviceinfo/deviceinfo.scss b/src/core/settings/pages/deviceinfo/deviceinfo.scss new file mode 100644 index 000000000..79511592c --- /dev/null +++ b/src/core/settings/pages/deviceinfo/deviceinfo.scss @@ -0,0 +1,5 @@ +page-core-settings-deviceinfo { + .item { + user-select: text; + } +} \ No newline at end of file diff --git a/src/core/settings/pages/deviceinfo/deviceinfo.ts b/src/core/settings/pages/deviceinfo/deviceinfo.ts new file mode 100644 index 000000000..266b6cdc7 --- /dev/null +++ b/src/core/settings/pages/deviceinfo/deviceinfo.ts @@ -0,0 +1,237 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, NgZone } from '@angular/core'; +import { IonicPage, Platform } from 'ionic-angular'; +import { Device } from '@ionic-native/device'; +import { Network } from '@ionic-native/network'; +import { TranslateService } from '@ngx-translate/core'; +import { CoreAppProvider } from '@providers/app'; +import { CoreFileProvider } from '@providers/file'; +import { CoreInitDelegate } from '@providers/init'; +import { CoreLangProvider } from '@providers/lang'; +import { CoreUtilsProvider } from '@providers/utils/utils'; +import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreConfigConstants } from '../../../../configconstants'; +import { CorePushNotificationsProvider } from '@core/pushnotifications/providers/pushnotifications'; + +/** + * Device Info to be shown and copied to clipboard. + */ +interface CoreSettingsDeviceInfo { + versionName: string; + versionCode: number; + compilationTime: number; + lastCommit: string; + siteUrl?: string; + isPrefixedUrl?: boolean; + siteId?: string; + siteVersion?: number; + fileSystemRoot?: string; + userAgent?: string; + deviceOs?: string; + browserLanguage?: string; + currentLanguage?: string; + locationHref?: string; + deviceType?: string; + screen?: string; + networkStatus: string; + wifiConnection: string; + cordovaVersion?: string; + platform?: string; + osVersion?: string; + model?: string; + uuid?: string; + pushId: string; + localNotifAvailable: string; +} + +/** + * Page that displays the device information. + */ +@IonicPage({segment: 'core-settings-deviceinfo'}) +@Component({ + selector: 'page-core-settings-deviceinfo', + templateUrl: 'deviceinfo.html', +}) +export class CoreSettingsDeviceInfoPage { + + deviceInfo: CoreSettingsDeviceInfo; + deviceOsTranslated: string; + currentLangName: string; + fsClickable: boolean; + + protected onlineObserver: any; + + constructor(platform: Platform, + device: Device, + network: Network, + zone: NgZone, + appProvider: CoreAppProvider, + fileProvider: CoreFileProvider, + initDelegate: CoreInitDelegate, + langProvider: CoreLangProvider, + sitesProvider: CoreSitesProvider, + localNotificationsProvider: CoreLocalNotificationsProvider, + pushNotificationsProvider: CorePushNotificationsProvider, + protected utils: CoreUtilsProvider, + protected translate: TranslateService) { + + this.deviceInfo = { + versionName: CoreConfigConstants.versionname, + versionCode: CoreConfigConstants.versioncode, + compilationTime: CoreConfigConstants.compilationtime, + lastCommit: CoreConfigConstants.lastcommit, + networkStatus: appProvider.isOnline() ? 'online' : 'offline', + wifiConnection: appProvider.isWifi() ? 'yes' : 'no', + localNotifAvailable: localNotificationsProvider.isAvailable() ? 'yes' : 'no', + pushId: pushNotificationsProvider.getPushId(), + }; + + if (window.location && window.location.href) { + const url = window.location.href; + this.deviceInfo.locationHref = url.substr(0, url.indexOf('#')); + } + + const navigator = window.navigator; + if (navigator) { + if (navigator.userAgent) { + this.deviceInfo.userAgent = navigator.userAgent; + } + + if (navigator.language) { + this.deviceInfo.browserLanguage = navigator.language; + } + } + + if (device) { + if (device.cordova) { + this.deviceInfo.cordovaVersion = device.cordova; + } + if (device.platform) { + this.deviceInfo.platform = device.platform; + } + if (device.version) { + this.deviceInfo.osVersion = device.version; + } + if (device.model) { + this.deviceInfo.model = device.model; + } + if (device.uuid) { + this.deviceInfo.uuid = device.uuid; + } + } + + if (appProvider.isMobile()) { + this.deviceInfo.deviceType = platform.is('tablet') ? 'tablet' : 'phone'; + if (appProvider.isAndroid()) { + this.deviceInfo.deviceOs = 'android'; + this.deviceOsTranslated = 'Android'; + } else if (appProvider.isIOS()) { + this.deviceInfo.deviceOs = 'ios'; + this.deviceOsTranslated = 'iOS'; + } else { + const matches = navigator.userAgent.match(/\(([^\)]*)\)/); + if (matches && matches.length > 1) { + this.deviceInfo.deviceOs = matches[1]; + this.deviceOsTranslated = matches[1]; + } else { + this.deviceInfo.deviceOs = 'unknown'; + this.deviceOsTranslated = this.translate.instant('core.unknown'); + } + } + } else { + this.deviceInfo.deviceType = appProvider.isDesktop() ? 'desktop' : 'browser'; + if (appProvider.isLinux()) { + this.deviceInfo.deviceOs = 'linux'; + this.deviceOsTranslated = 'Linux'; + } else if (appProvider.isMac()) { + this.deviceInfo.deviceOs = 'mac'; + this.deviceOsTranslated = 'MacOS'; + } else if (appProvider.isWindows()) { + this.deviceInfo.deviceOs = 'windows'; + this.deviceOsTranslated = 'Windows'; + } else { + const matches = navigator.userAgent.match(/\(([^\)]*)\)/); + if (matches && matches.length > 1) { + this.deviceInfo.deviceOs = matches[1]; + this.deviceOsTranslated = matches[1]; + } else { + this.deviceInfo.deviceOs = 'unknown'; + this.deviceOsTranslated = this.translate.instant('core.unknown'); + } + } + } + + langProvider.getCurrentLanguage().then((lang) => { + this.deviceInfo.currentLanguage = lang; + this.currentLangName = CoreConfigConstants.languages[lang]; + }); + + if (fileProvider.isAvailable()) { + fileProvider.getBasePath().then((basepath) => { + this.deviceInfo.fileSystemRoot = basepath; + this.fsClickable = fileProvider.usesHTMLAPI(); + }); + } + + if (window.screen) { + this.deviceInfo.screen = window.innerWidth + 'x' + window.innerHeight + + ' (' + window.screen.width + 'x' + window.screen.height + ')'; + } + + const currentSite = sitesProvider.getCurrentSite(); + + this.deviceInfo.siteUrl = (currentSite && currentSite.getURL()) || + (typeof CoreConfigConstants.siteurl == 'string' && CoreConfigConstants.siteurl); + this.deviceInfo.isPrefixedUrl = !!CoreConfigConstants.siteurl; + this.deviceInfo.siteId = currentSite && currentSite.getId(); + this.deviceInfo.siteVersion = currentSite && currentSite.getInfo().release; + + // Refresh online status when changes. + this.onlineObserver = network.onchange().subscribe(() => { + // Execute the callback in the Angular zone, so change detection doesn't stop working. + zone.run(() => { + this.deviceInfo.networkStatus = appProvider.isOnline() ? 'online' : 'offline'; + }); + }); + } + + /** + * Copies device info into the clipboard. + */ + copyInfo(): void { + this.utils.copyToClipboard(JSON.stringify(this.deviceInfo)); + } + + /** + * Copies device info item into the clipboard. + * + * @param e Event. + */ + copyItemInfo(e: Event): void { + e.preventDefault(); + + const el = e.target; + this.utils.copyToClipboard(el.closest('ion-item').textContent.trim()); + } + + /** + * Page destroyed. + */ + ngOnDestroy(): void { + this.onlineObserver && this.onlineObserver.unsubscribe(); + } +} diff --git a/src/core/settings/pages/general/general.html b/src/core/settings/pages/general/general.html index 34f0d2376..2cd97f99f 100644 --- a/src/core/settings/pages/general/general.html +++ b/src/core/settings/pages/general/general.html @@ -21,8 +21,11 @@ -

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

- + +

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

+

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

+
+ {{ 'core.settings.colorscheme-' + scheme | translate }}
diff --git a/src/core/settings/pages/general/general.ts b/src/core/settings/pages/general/general.ts index 2880ef3c8..76b680088 100644 --- a/src/core/settings/pages/general/general.ts +++ b/src/core/settings/pages/general/general.ts @@ -47,6 +47,7 @@ export class CoreSettingsGeneralPage { analyticsEnabled: boolean; colorSchemes = []; selectedScheme: string; + colorSchemeDisabled: boolean; constructor(private appProvider: CoreAppProvider, private configProvider: CoreConfigProvider, fileProvider: CoreFileProvider, private eventsProvider: CoreEventsProvider, private langProvider: CoreLangProvider, @@ -63,20 +64,27 @@ export class CoreSettingsGeneralPage { } if (!CoreConfigConstants.forceColorScheme) { - let defaultColorScheme = 'light'; + this.colorSchemeDisabled = this.settingsHelper.isColorSchemeDisabledInSite(); - // Auto is not working on iOS right now until we update Webkit. - if (!this.appProvider.isIOS() && (window.matchMedia('(prefers-color-scheme: dark)').matches || - window.matchMedia('(prefers-color-scheme: light)').matches)) { - this.colorSchemes.push('auto'); - defaultColorScheme = 'auto'; + if (this.colorSchemeDisabled) { + this.colorSchemes.push('light'); + this.selectedScheme = this.colorSchemes[0]; + } else { + let defaultColorScheme = 'light'; + + // Auto is not working on iOS right now until we update Webkit. + if (!this.appProvider.isIOS() && (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; + }); } - 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. diff --git a/src/core/settings/pages/licenses/licenses.html b/src/core/settings/pages/licenses/licenses.html new file mode 100644 index 000000000..abbbeee99 --- /dev/null +++ b/src/core/settings/pages/licenses/licenses.html @@ -0,0 +1,26 @@ + + + {{ 'core.settings.opensourcelicenses' | translate }} + + + + + + {{ 'core.settings.opensourcelicenses' | translate }} + + + + +

+ {{ license.name }} + {{ license.name }} - {{ license.version }} +

+

{{ 'core.settings.publisher' | translate }}{{ 'core.labelsep' | translate }} {{ license.publisher }}

+

{{ 'core.settings.license' | translate }}{{ 'core.labelsep' | translate }} {{ license.licenses }}

+

{{ license.url }}

+

{{ license.email }}

+ {{ 'core.view' | translate }} +
+
+
+
diff --git a/src/core/settings/pages/licenses/licenses.module.ts b/src/core/settings/pages/licenses/licenses.module.ts new file mode 100644 index 000000000..08c21a53a --- /dev/null +++ b/src/core/settings/pages/licenses/licenses.module.ts @@ -0,0 +1,35 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreSettingsLicensesPage } from './licenses'; +import { CoreComponentsModule } from '@components/components.module'; +import { CoreDirectivesModule } from '@directives/directives.module'; +import { CorePipesModule } from '@pipes/pipes.module'; + +@NgModule({ + declarations: [ + CoreSettingsLicensesPage + ], + imports: [ + CoreComponentsModule, + CoreDirectivesModule, + CorePipesModule, + IonicPageModule.forChild(CoreSettingsLicensesPage), + TranslateModule.forChild() + ], +}) +export class CoreSettingsLicensesPageModule {} diff --git a/src/core/settings/pages/licenses/licenses.ts b/src/core/settings/pages/licenses/licenses.ts new file mode 100644 index 000000000..37595c6e8 --- /dev/null +++ b/src/core/settings/pages/licenses/licenses.ts @@ -0,0 +1,88 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component } from '@angular/core'; +import { IonicPage } from 'ionic-angular'; +import { HttpClient } from '@angular/common/http'; +import { CoreConfigConstants } from '../../../../configconstants'; + +/** + * Defines license info + */ +interface CoreSettingsLicense { + name: string; + version: string; + licenses: string; + repository?: string; + publisher?: string; + url?: string; + email?: string; + licenseUrl?: string; + licenseFile?: string; +} + +/** + * Page that displays the open source licenses information. + */ +@IonicPage({segment: 'core-settings-licenses'}) +@Component({ + selector: 'page-core-settings-licenses', + templateUrl: 'licenses.html', +}) +export class CoreSettingsLicensesPage { + + licensesUrl: string; + loaded = false; + licenses: CoreSettingsLicense[]; + error = false; + + constructor(protected http: HttpClient) { + let version = 'v' + CoreConfigConstants.versionname; + if (version.indexOf('-') > 0) { + version = 'integration'; + } + + this.licensesUrl = 'https://raw.githubusercontent.com/moodlehq/moodleapp/' + version + '/licenses.json'; + } + + /** + * View loaded. + */ + ionViewDidLoad(): void { + this.http.get(this.licensesUrl).toPromise().then((licenses) => { + this.licenses = Object.keys(licenses).map((name) => { + const license = licenses[name]; + + const nameSplit = name.lastIndexOf('@'); + license.name = name.substring(0, nameSplit); + license.version = name.substring(nameSplit + 1); + + if (license.repository) { + license.repository = license.repository.replace('git://', 'https://'); + if (license.repository.indexOf('github.com') > 0) { + license.licenseUrl = license.repository + '/blob/' + license.version + '/' + license.licenseFile; + } + } + + return license; + }); + + this.error = false; + }).catch(() => { + this.error = true; + }).finally(() => { + this.loaded = true; + }); + } +} diff --git a/src/core/settings/pages/site/site.html b/src/core/settings/pages/site/site.html new file mode 100644 index 000000000..6c932d246 --- /dev/null +++ b/src/core/settings/pages/site/site.html @@ -0,0 +1,52 @@ + + + {{ 'core.settings.preferences' | translate}} + + + + + + + + + + + + +

{{siteInfo.fullname}}

+ + {{ siteUrl }} +
+ + + +

{{ 'core.sharedfiles.sharedfiles' | translate }}

+ {{ iosSharedFiles }} +
+ + + +

{{ handler.title | translate}}

+
+ + + +

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

+

{{ spaceUsage.spaceUsage | coreBytesToSize }}

+

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

+ +
+ +

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

+ + +
+
+
+
+
+
diff --git a/src/core/settings/pages/site/site.module.ts b/src/core/settings/pages/site/site.module.ts new file mode 100644 index 000000000..d7e2a43c3 --- /dev/null +++ b/src/core/settings/pages/site/site.module.ts @@ -0,0 +1,35 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreSitePreferencesPage } from './site'; +import { CoreComponentsModule } from '@components/components.module'; +import { CoreDirectivesModule } from '@directives/directives.module'; +import { CorePipesModule } from '@pipes/pipes.module'; + +@NgModule({ + declarations: [ + CoreSitePreferencesPage, + ], + imports: [ + CoreComponentsModule, + CoreDirectivesModule, + CorePipesModule, + IonicPageModule.forChild(CoreSitePreferencesPage), + TranslateModule.forChild(), + ], +}) +export class CoreSitePreferencesPageModule {} diff --git a/src/core/settings/pages/site/site.ts b/src/core/settings/pages/site/site.ts new file mode 100644 index 000000000..4f35c7feb --- /dev/null +++ b/src/core/settings/pages/site/site.ts @@ -0,0 +1,198 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, ViewChild } from '@angular/core'; +import { IonicPage, NavParams, Platform } from 'ionic-angular'; +import { TranslateService } from '@ngx-translate/core'; +import { CoreSettingsDelegate, CoreSettingsHandlerData } from '../../providers/delegate'; +import { CoreEventsProvider } from '@providers/events'; +import { CoreSitesProvider, CoreSiteBasicInfo } from '@providers/sites'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { CoreSplitViewComponent } from '@components/split-view/split-view'; +import { CoreSharedFilesProvider } from '@core/sharedfiles/providers/sharedfiles'; +import { CoreSettingsHelper, CoreSiteSpaceUsage } from '../../providers/helper'; + +/** + * Page that displays the list of site settings pages. + */ +@IonicPage({segment: 'core-site-preferences'}) +@Component({ + selector: 'page-core-site-preferences', + templateUrl: 'site.html', +}) +export class CoreSitePreferencesPage { + @ViewChild(CoreSplitViewComponent) splitviewCtrl: CoreSplitViewComponent; + + handlers: CoreSettingsHandlerData[]; + isIOS: boolean; + selectedPage: string; + siteId: string; + siteInfo: CoreSiteBasicInfo[] = []; + siteName: string; + siteUrl: string; + spaceUsage: CoreSiteSpaceUsage = { + cacheEntries: 0, + spaceUsage: 0 + }; + loaded = false; + iosSharedFiles: number; + protected sitesObserver: any; + protected isDestroyed = false; + + constructor(protected settingsDelegate: CoreSettingsDelegate, + protected settingsHelper: CoreSettingsHelper, + protected sitesProvider: CoreSitesProvider, + protected domUtils: CoreDomUtilsProvider, + protected eventsProvider: CoreEventsProvider, + protected sharedFilesProvider: CoreSharedFilesProvider, + protected translate: TranslateService, + platorm: Platform, + navParams: NavParams, + ) { + + this.isIOS = platorm.is('ios'); + + this.selectedPage = navParams.get('page') || false; + + this.sitesObserver = this.eventsProvider.on(CoreEventsProvider.SITE_UPDATED, (data) => { + if (data.siteId == this.siteId) { + this.refreshData(); + } + }); + } + + /** + * View loaded. + */ + ionViewDidLoad(): void { + this.fetchData().finally(() => { + this.loaded = true; + + if (this.selectedPage) { + this.openHandler(this.selectedPage); + } else if (this.splitviewCtrl.isOn()) { + if (this.isIOS) { + this.openHandler('CoreSharedFilesListPage', {manage: true, siteId: this.siteId, hideSitePicker: true}); + } else if (this.handlers.length > 0) { + this.openHandler(this.handlers[0].page, this.handlers[0].params); + } + } + }); + } + + /** + * Fetch Data. + */ + protected async fetchData(): Promise { + this.handlers = this.settingsDelegate.getHandlers(); + const currentSite = this.sitesProvider.getCurrentSite(); + this.siteId = currentSite.id; + this.siteInfo = currentSite.getInfo(); + this.siteName = currentSite.getSiteName(); + this.siteUrl = currentSite.getURL(); + + const promises = []; + + promises.push(this.settingsHelper.getSiteSpaceUsage(this.siteId).then((spaceUsage) => this.spaceUsage = spaceUsage)); + + if (this.isIOS) { + promises.push(this.sharedFilesProvider.getSiteSharedFiles(this.siteId).then((files) => + this.iosSharedFiles = files.length + )); + } + + await Promise.all(promises); + } + + /** + * Syncrhonizes the site. + */ + synchronize(siteId: string): void { + // Using syncOnlyOnWifi false to force manual sync. + this.settingsHelper.synchronizeSite(false, this.siteId).catch((error) => { + if (this.isDestroyed) { + return; + } + this.domUtils.showErrorModalDefault(error, 'core.settings.errorsyncsite', true); + }); + } + + /** + * Returns true if site is beeing synchronized. + * + * @return True if site is beeing synchronized, false otherwise. + */ + isSynchronizing(): boolean { + return this.siteId && !!this.settingsHelper.getSiteSyncPromise(this.siteId); + } + + /** + * Refresh the data. + * + * @param refresher Refresher. + */ + refreshData(refresher?: any): void { + this.fetchData().finally(() => { + refresher && refresher.complete(); + }); + } + + /** + * Deletes files of a site and the tables that can be cleared. + * + * @param siteData Site object with space usage. + */ + deleteSiteStorage(): void { + this.settingsHelper.deleteSiteStorage(this.siteName, this.siteId).then((newInfo) => { + this.spaceUsage = newInfo; + }).catch(() => { + // Ignore cancelled confirmation modal. + }); + } + + /** + * Open a handler. + * + * @param page Page to open. + * @param params Params of the page to open. + */ + openHandler(page: string, params?: any): void { + this.selectedPage = page; + this.splitviewCtrl.push(page, params); + } + + /** + * Show information about space usage actions. + */ + showSpaceInfo(): void { + this.domUtils.showAlert(this.translate.instant('core.help'), + this.translate.instant('core.settings.spaceusagehelp')); + } + + /** + * Show information about sync actions. + */ + showSyncInfo(): void { + this.domUtils.showAlert(this.translate.instant('core.help'), + this.translate.instant('core.settings.synchronizenowhelp')); + } + + /** + * Page destroyed. + */ + ngOnDestroy(): void { + this.isDestroyed = true; + this.sitesObserver && this.sitesObserver.off(); + } +} diff --git a/src/core/settings/pages/space-usage/space-usage.html b/src/core/settings/pages/space-usage/space-usage.html index f4d247a89..29dac4705 100644 --- a/src/core/settings/pages/space-usage/space-usage.html +++ b/src/core/settings/pages/space-usage/space-usage.html @@ -1,13 +1,20 @@ {{ 'core.settings.spaceusage' | translate }} + + + + + - + - +

{{ site.fullName }}

@@ -19,11 +26,11 @@
- -

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

+ +

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

-

{{ totalUsage | coreBytesToSize }}

-

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

+

{{ totals.spaceUsage | coreBytesToSize }}

+

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

diff --git a/src/core/settings/pages/space-usage/space-usage.ts b/src/core/settings/pages/space-usage/space-usage.ts index 185f5a196..35a49c323 100644 --- a/src/core/settings/pages/space-usage/space-usage.ts +++ b/src/core/settings/pages/space-usage/space-usage.ts @@ -15,13 +15,9 @@ import { Component, } from '@angular/core'; import { IonicPage } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; -import { CoreAppProvider } from '@providers/app'; -import { CoreEventsProvider } from '@providers/events'; -import { CoreFilepoolProvider } from '@providers/filepool'; -import { CoreSitesProvider } from '@providers/sites'; +import { CoreSitesProvider, CoreSiteBasicInfo } from '@providers/sites'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; -import { CoreCourseProvider } from '@core/course/providers/course'; -import { CoreFilterProvider } from '@core/filter/providers/filter'; +import { CoreSettingsHelper, CoreSiteSpaceUsage } from '../../providers/helper'; /** * Page that displays the space usage settings. @@ -33,20 +29,19 @@ import { CoreFilterProvider } from '@core/filter/providers/filter'; }) export class CoreSettingsSpaceUsagePage { - usageLoaded = false; + loaded = false; sites = []; currentSiteId = ''; - totalUsage = 0; - totalEntries = 0; + totals: CoreSiteSpaceUsage = { + cacheEntries: 0, + spaceUsage: 0 + }; - constructor(private filePoolProvider: CoreFilepoolProvider, - private eventsProvider: CoreEventsProvider, - private sitesProvider: CoreSitesProvider, - private filterProvider: CoreFilterProvider, - private translate: TranslateService, - private domUtils: CoreDomUtilsProvider, - appProvider: CoreAppProvider, - private courseProvider: CoreCourseProvider) { + constructor(protected sitesProvider: CoreSitesProvider, + protected settingsHelper: CoreSettingsHelper, + protected domUtils: CoreDomUtilsProvider, + protected translate: TranslateService, + ) { this.currentSiteId = this.sitesProvider.getCurrentSiteId(); } @@ -54,66 +49,36 @@ export class CoreSettingsSpaceUsagePage { * View loaded. */ ionViewDidLoad(): void { - this.fetchData().finally(() => { - this.usageLoaded = true; + this.loadSiteData().finally(() => { + this.loaded = true; }); } /** - * Convenience function to calculate each site's usage, and the total usage. + * Convenience function to load site data/usage and calculate the totals. * * @return Resolved when done. */ - protected calculateSizeUsage(): Promise { - return this.sitesProvider.getSortedSites().then((sites) => { - this.sites = sites; + protected async loadSiteData(): Promise { + // Calculate total usage. + let totalSize = 0; + let totalEntries = 0; - // Get space usage. - const promises = this.sites.map((siteEntry) => { - return this.sitesProvider.getSite(siteEntry.id).then((site) => { - const proms2 = []; + this.sites = await this.sitesProvider.getSortedSites(); - proms2.push(this.calcSiteClearRows(site).then((rows) => { - siteEntry.cacheEntries = rows; - })); + // Get space usage. + await Promise.all(this.sites.map(async (site) => { + const siteInfo = await this.settingsHelper.getSiteSpaceUsage(site.id); - proms2.push(site.getSpaceUsage().then((size) => { - siteEntry.spaceUsage = size; - })); + site.cacheEntries = siteInfo.cacheEntries; + site.spaceUsage = siteInfo.spaceUsage; - return Promise.all(proms2); - }); - }); - - return Promise.all(promises); - }); - } - - /** - * Convenience function to calculate total usage. - */ - protected calculateTotalUsage(): void { - let totalSize = 0, - totalEntries = 0; - this.sites.forEach((site) => { totalSize += (site.spaceUsage ? parseInt(site.spaceUsage, 10) : 0); totalEntries += (site.cacheEntries ? parseInt(site.cacheEntries, 10) : 0); - }); - this.totalUsage = totalSize; - this.totalEntries = totalEntries; - } + })); - /** - * Convenience function to calculate space usage. - * - * @return Resolved when done. - */ - protected fetchData(): Promise { - const promises = [ - this.calculateSizeUsage().then(() => this.calculateTotalUsage()), - ]; - - return Promise.all(promises); + this.totals.spaceUsage = totalSize; + this.totals.cacheEntries = totalEntries; } /** @@ -122,96 +87,40 @@ export class CoreSettingsSpaceUsagePage { * @param refresher Refresher. */ refreshData(refresher: any): void { - this.fetchData().finally(() => { + this.loadSiteData().finally(() => { refresher.complete(); }); } - /** - * Convenience function to update site size, along with total usage. - * - * @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; - site.spaceUsage = newUsage; - this.totalUsage -= oldUsage - newUsage; - } - - /** - * Calculate the number of rows to be deleted on a site. - * - * @param site Site object. - * @return If there are rows to delete or not. - */ - protected calcSiteClearRows(site: any): Promise { - const clearTables = this.sitesProvider.getSiteTableSchemasToClear(); - - let totalEntries = 0; - - const promises = clearTables.map((name) => { - return site.getDb().countRecords(name).then((rows) => { - totalEntries += rows; - }); - }); - - return Promise.all(promises).then(() => { - return totalEntries; - }); - } - /** * Deletes files of a site and the tables that can be cleared. * * @param siteData Site object with space usage. */ - deleteSiteStorage(siteData: any): void { - this.filterProvider.formatText(siteData.siteName, {clean: true, singleLine: true, filter: false}, [], siteData.id) - .then((siteName) => { - - const title = this.translate.instant('core.settings.deletesitefilestitle'); - const message = this.translate.instant('core.settings.deletesitefiles', {sitename: siteName}); - - this.domUtils.showConfirm(message, title).then(() => { - return this.sitesProvider.getSite(siteData.id); - }).then((site) => { - - // Clear cache tables. - const cleanSchemas = this.sitesProvider.getSiteTableSchemasToClear(); - const promises = cleanSchemas.map((name) => { - return site.getDb().deleteRecords(name); - }); - - promises.push(site.deleteFolder().then(() => { - this.filePoolProvider.clearAllPackagesStatus(site.id); - this.filePoolProvider.clearFilepool(site.id); - this.updateSiteUsage(siteData, 0); - this.courseProvider.clearAllCoursesStatus(site.id); - }).catch((error) => { - if (error && error.code === FileError.NOT_FOUND_ERR) { - // Not found, set size 0. - this.filePoolProvider.clearAllPackagesStatus(site.id); - this.updateSiteUsage(siteData, 0); - } else { - // Error, recalculate the site usage. - this.domUtils.showErrorModal('core.settings.errordeletesitefiles', true); - site.getSpaceUsage().then((size) => { - this.updateSiteUsage(siteData, size); - }); - } - }).finally(() => { - this.eventsProvider.trigger(CoreEventsProvider.SITE_STORAGE_DELETED, {}, site.getId()); - - this.calcSiteClearRows(site).then((rows) => { - siteData.cacheEntries = rows; - }); - })); - - return Promise.all(promises); - }).catch(() => { - // Ignore cancelled confirmation modal. - }); + deleteSiteStorage(siteData: CoreSiteBasicInfoWithUsage): void { + this.settingsHelper.deleteSiteStorage(siteData.siteName, siteData.id).then((newInfo) => { + this.totals.spaceUsage -= siteData.spaceUsage - newInfo.spaceUsage; + this.totals.spaceUsage -= siteData.cacheEntries - newInfo.cacheEntries; + siteData.spaceUsage = newInfo.spaceUsage; + siteData.cacheEntries = newInfo.cacheEntries; + }).catch(() => { + // Ignore cancelled confirmation modal. }); } + + /** + * Show information about space usage actions. + */ + showInfo(): void { + this.domUtils.showAlert(this.translate.instant('core.help'), + this.translate.instant('core.settings.spaceusagehelp')); + } +} + +/** + * Basic site info with space usage and cache entries that can be erased. + */ +export interface CoreSiteBasicInfoWithUsage extends CoreSiteBasicInfo { + cacheEntries?: number; // Number of cached entries that can be cleared. + spaceUsage?: number; // Space used in this site. } diff --git a/src/core/settings/pages/synchronization/synchronization.html b/src/core/settings/pages/synchronization/synchronization.html index 3f7a6a2a1..bb5b62e0f 100644 --- a/src/core/settings/pages/synchronization/synchronization.html +++ b/src/core/settings/pages/synchronization/synchronization.html @@ -1,12 +1,19 @@ {{ 'core.settings.synchronization' | translate }} + + + + + -

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

+

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

{{ 'core.settings.enablesyncwifi' | translate }} @@ -14,7 +21,7 @@ -

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

+

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

diff --git a/src/core/settings/pages/synchronization/synchronization.ts b/src/core/settings/pages/synchronization/synchronization.ts index c478c4ad8..77a7eddf5 100644 --- a/src/core/settings/pages/synchronization/synchronization.ts +++ b/src/core/settings/pages/synchronization/synchronization.ts @@ -14,6 +14,7 @@ import { Component, OnDestroy } from '@angular/core'; import { IonicPage } from 'ionic-angular'; +import { TranslateService } from '@ngx-translate/core'; import { CoreConstants } from '@core/constants'; import { CoreEventsProvider } from '@providers/events'; import { CoreSitesProvider, CoreSiteBasicInfo } from '@providers/sites'; @@ -36,11 +37,15 @@ export class CoreSettingsSynchronizationPage implements OnDestroy { sitesObserver: any; currentSiteId = ''; syncOnlyOnWifi = false; - isDestroyed = false; + protected isDestroyed = false; - constructor(private configProvider: CoreConfigProvider, private eventsProvider: CoreEventsProvider, - private sitesProvider: CoreSitesProvider, private domUtils: CoreDomUtilsProvider, - private settingsHelper: CoreSettingsHelper) { + constructor(protected configProvider: CoreConfigProvider, + protected eventsProvider: CoreEventsProvider, + protected sitesProvider: CoreSitesProvider, + protected domUtils: CoreDomUtilsProvider, + protected settingsHelper: CoreSettingsHelper, + protected translate: TranslateService, + ) { this.currentSiteId = this.sitesProvider.getCurrentSiteId(); @@ -85,7 +90,8 @@ export class CoreSettingsSynchronizationPage implements OnDestroy { * @param siteId Site ID. */ synchronize(siteId: string): void { - this.settingsHelper.synchronizeSite(this.syncOnlyOnWifi, siteId).catch((error) => { + // Using syncOnlyOnWifi false to force manual sync. + this.settingsHelper.synchronizeSite(false, siteId).catch((error) => { if (this.isDestroyed) { return; } @@ -103,6 +109,14 @@ export class CoreSettingsSynchronizationPage implements OnDestroy { return !!this.settingsHelper.getSiteSyncPromise(siteId); } + /** + * Show information about sync actions. + */ + showInfo(): void { + this.domUtils.showAlert(this.translate.instant('core.help'), + this.translate.instant('core.settings.synchronizenowhelp')); + } + /** * Page destroyed. */ diff --git a/src/core/settings/providers/helper.ts b/src/core/settings/providers/helper.ts index b5b48ab05..9d4769bfe 100644 --- a/src/core/settings/providers/helper.ts +++ b/src/core/settings/providers/helper.ts @@ -18,13 +18,25 @@ import { CoreCronDelegate } from '@providers/cron'; import { CoreEventsProvider } from '@providers/events'; import { CoreFilepoolProvider } from '@providers/filepool'; import { CoreLoggerProvider } from '@providers/logger'; +import { CoreSite } from '@classes/site'; import { CoreSitesProvider } from '@providers/sites'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreConstants } from '@core/constants'; import { CoreConfigProvider } from '@providers/config'; +import { CoreFilterProvider } from '@core/filter/providers/filter'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreConfigConstants } from '../../../configconstants'; import { TranslateService } from '@ngx-translate/core'; +/** + * Object with space usage and cache entries that can be erased. + */ +export interface CoreSiteSpaceUsage { + cacheEntries?: number; // Number of cached entries that can be cleared. + spaceUsage?: number; // Space used in this site. +} + /** * Settings helper service. */ @@ -33,11 +45,137 @@ export class CoreSettingsHelper { protected logger; protected syncPromises = {}; - 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 configProvider: CoreConfigProvider) { + constructor(loggerProvider: CoreLoggerProvider, + protected appProvider: CoreAppProvider, + protected cronDelegate: CoreCronDelegate, + protected domUtils: CoreDomUtilsProvider, + protected eventsProvider: CoreEventsProvider, + protected filePoolProvider: CoreFilepoolProvider, + protected sitesProvider: CoreSitesProvider, + protected utils: CoreUtilsProvider, + protected translate: TranslateService, + protected configProvider: CoreConfigProvider, + protected filterProvider: CoreFilterProvider, + protected courseProvider: CoreCourseProvider, + ) { this.logger = loggerProvider.getInstance('CoreSettingsHelper'); + + if (!CoreConfigConstants.forceColorScheme) { + // Update color scheme when a user enters or leaves a site, or when the site info is updated. + const applySiteScheme = (): void => { + if (this.isColorSchemeDisabledInSite()) { + // Dark mode is disabled, force light mode. + this.setColorScheme('light'); + } else { + // Reset color scheme settings. + this.initColorScheme(); + } + }; + + eventsProvider.on(CoreEventsProvider.LOGIN, applySiteScheme.bind(this)); + + eventsProvider.on(CoreEventsProvider.SITE_UPDATED, applySiteScheme.bind(this)); + + eventsProvider.on(CoreEventsProvider.LOGOUT, () => { + // Reset color scheme settings. + this.initColorScheme(); + }); + } + } + + /** + * Deletes files of a site and the tables that can be cleared. + * + * @param siteName Site Name. + * @param siteId: Site ID. + * @return Resolved with detailed new info when done. + */ + async deleteSiteStorage(siteName: string, siteId: string): Promise { + const siteInfo: CoreSiteSpaceUsage = { + cacheEntries: 0, + spaceUsage: 0 + }; + + siteName = await this.filterProvider.formatText(siteName, {clean: true, singleLine: true, filter: false}, [], siteId); + + const title = this.translate.instant('core.settings.deletesitefilestitle'); + const message = this.translate.instant('core.settings.deletesitefiles', {sitename: siteName}); + + await this.domUtils.showConfirm(message, title); + + const site = await this.sitesProvider.getSite(siteId); + + // Clear cache tables. + const cleanSchemas = this.sitesProvider.getSiteTableSchemasToClear(site); + const promises = cleanSchemas.map((name) => site.getDb().deleteRecords(name)); + + promises.push(site.deleteFolder().then(() => { + this.filePoolProvider.clearAllPackagesStatus(site.id); + this.filePoolProvider.clearFilepool(site.id); + this.courseProvider.clearAllCoursesStatus(site.id); + + siteInfo.spaceUsage = 0; + }).catch(async (error) => { + if (error && error.code === FileError.NOT_FOUND_ERR) { + // Not found, set size 0. + this.filePoolProvider.clearAllPackagesStatus(site.id); + siteInfo.spaceUsage = 0; + } else { + // Error, recalculate the site usage. + this.domUtils.showErrorModal('core.settings.errordeletesitefiles', true); + + siteInfo.spaceUsage = await site.getSpaceUsage(); + } + }).then(async () => { + this.eventsProvider.trigger(CoreEventsProvider.SITE_STORAGE_DELETED, {}, site.getId()); + + siteInfo.cacheEntries = await this.calcSiteClearRows(site); + })); + + await Promise.all(promises); + + return siteInfo; + } + + /** + * Calculates each site's usage, and the total usage. + * + * @param siteId ID of the site. Current site if undefined. + * @return Resolved with detailed info when done. + */ + async getSiteSpaceUsage(siteId?: string): Promise { + const site = await this.sitesProvider.getSite(siteId); + + // Get space usage. + const siteInfo: CoreSiteSpaceUsage = { + cacheEntries: 0, + spaceUsage: 0, + }; + + await Promise.all([ + this.calcSiteClearRows(site).then((rows) => siteInfo.cacheEntries = rows), + site.getSpaceUsage().then((size) => siteInfo.spaceUsage = size), + ]); + + return siteInfo; + } + + /** + * Calculate the number of rows to be deleted on a site. + * + * @param site Site object. + * @return If there are rows to delete or not. + */ + protected async calcSiteClearRows(site: CoreSite): Promise { + const clearTables = this.sitesProvider.getSiteTableSchemasToClear(site); + + let totalEntries = 0; + + await Promise.all(clearTables.map(async (name) => + totalEntries = await site.getDb().countRecords(name) + totalEntries + )); + + return totalEntries; } /** @@ -189,6 +327,13 @@ export class CoreSettingsHelper { this.setFontSize(fontSize); }); + this.initColorScheme(); + } + + /** + * Init the color scheme. + */ + initColorScheme(): void { if (!!CoreConfigConstants.forceColorScheme) { this.setColorScheme(CoreConfigConstants.forceColorScheme); } else { @@ -205,6 +350,30 @@ export class CoreSettingsHelper { } } + /** + * Check if color scheme is disabled in a site. + * + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with whether color scheme is disabled. + */ + async isColorSchemeDisabled(siteId?: string): Promise { + const site = await this.sitesProvider.getSite(siteId); + + return this.isColorSchemeDisabledInSite(site); + } + + /** + * Check if color scheme is disabled in a site. + * + * @param site Site instance. If not defined, current site. + * @return Whether color scheme is disabled. + */ + isColorSchemeDisabledInSite(site?: CoreSite): boolean { + site = site || this.sitesProvider.getCurrentSite(); + + return site ? site.isFeatureDisabled('NoDelegate_DarkMode') : false; + } + /** * Set document default font size. * diff --git a/src/core/sharedfiles/pages/list/list.html b/src/core/sharedfiles/pages/list/list.html index 5f210c99a..06bbf5228 100644 --- a/src/core/sharedfiles/pages/list/list.html +++ b/src/core/sharedfiles/pages/list/list.html @@ -14,7 +14,7 @@
- + diff --git a/src/core/sharedfiles/pages/list/list.ts b/src/core/sharedfiles/pages/list/list.ts index ec408963c..66c8b199c 100644 --- a/src/core/sharedfiles/pages/list/list.ts +++ b/src/core/sharedfiles/pages/list/list.ts @@ -35,6 +35,7 @@ export class CoreSharedFilesListPage implements OnInit, OnDestroy { isModal: boolean; manage: boolean; pick: boolean; // To pick a file you MUST use a modal. + showSitePicker: boolean; path = ''; title: string; filesLoaded: boolean; @@ -52,6 +53,7 @@ export class CoreSharedFilesListPage implements OnInit, OnDestroy { this.manage = !!navParams.get('manage'); this.pick = !!navParams.get('pick'); this.path = navParams.get('path') || ''; + this.showSitePicker = !navParams.get('hideSitePicker'); } /** diff --git a/src/core/sharedfiles/providers/sharedfiles.ts b/src/core/sharedfiles/providers/sharedfiles.ts index f95c1dc29..91f971436 100644 --- a/src/core/sharedfiles/providers/sharedfiles.ts +++ b/src/core/sharedfiles/providers/sharedfiles.ts @@ -13,7 +13,7 @@ // limitations under the License. import { Injectable } from '@angular/core'; -import { CoreAppProvider } from '@providers/app'; +import { CoreAppProvider, CoreAppSchema } from '@providers/app'; import { CoreEventsProvider } from '@providers/events'; import { CoreFileProvider } from '@providers/file'; import { CoreLoggerProvider } from '@providers/logger'; @@ -21,7 +21,7 @@ import { CoreSitesProvider } from '@providers/sites'; import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { Md5 } from 'ts-md5/dist/md5'; -import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb'; +import { SQLiteDB } from '@classes/sqlitedb'; /** * Service to share files with the app. @@ -32,19 +32,26 @@ export class CoreSharedFilesProvider { // Variables for the database. protected SHARED_FILES_TABLE = 'shared_files'; - protected tableSchema: SQLiteDBTableSchema = { - name: this.SHARED_FILES_TABLE, - columns: [ + protected tableSchema: CoreAppSchema = { + name: 'CoreSharedFilesProvider', + version: 1, + tables: [ { - name: 'id', - type: 'TEXT', - primaryKey: true - } - ] + name: this.SHARED_FILES_TABLE, + columns: [ + { + name: 'id', + type: 'TEXT', + primaryKey: true + }, + ], + }, + ], }; protected logger; protected appDB: SQLiteDB; + protected dbReady: Promise; // Promise resolved when the app DB is initialized. constructor(logger: CoreLoggerProvider, private fileProvider: CoreFileProvider, appProvider: CoreAppProvider, private textUtils: CoreTextUtilsProvider, private mimeUtils: CoreMimetypeUtilsProvider, @@ -52,7 +59,9 @@ export class CoreSharedFilesProvider { this.logger = logger.getInstance('CoreSharedFilesProvider'); this.appDB = appProvider.getDB(); - this.appDB.createTableFromSchema(this.tableSchema); + this.dbReady = appProvider.createTablesFromSchema(this.tableSchema).catch(() => { + // Ignore errors. + }); } /** @@ -189,7 +198,9 @@ export class CoreSharedFilesProvider { * @param fileId File ID. * @return Resolved if treated, rejected otherwise. */ - protected isFileTreated(fileId: string): Promise { + protected async isFileTreated(fileId: string): Promise { + await this.dbReady; + return this.appDB.getRecord(this.SHARED_FILES_TABLE, { id: fileId }); } @@ -199,12 +210,16 @@ export class CoreSharedFilesProvider { * @param fileId File ID. * @return Promise resolved when marked. */ - protected markAsTreated(fileId: string): Promise { - // Check if it's already marked. - return this.isFileTreated(fileId).catch(() => { + protected async markAsTreated(fileId: string): Promise { + await this.dbReady; + + try { + // Check if it's already marked. + await this.isFileTreated(fileId); + } catch (err) { // Doesn't exist, insert it. - return this.appDB.insertRecord(this.SHARED_FILES_TABLE, { id: fileId }); - }); + await this.appDB.insertRecord(this.SHARED_FILES_TABLE, { id: fileId }); + } } /** @@ -243,7 +258,9 @@ export class CoreSharedFilesProvider { * @param fileId File ID. * @return Resolved when unmarked. */ - protected unmarkAsTreated(fileId: string): Promise { + protected async unmarkAsTreated(fileId: string): Promise { + await this.dbReady; + return this.appDB.deleteRecords(this.SHARED_FILES_TABLE, { id: fileId }); } } diff --git a/src/core/sitehome/components/all-course-list/core-sitehome-all-course-list.html b/src/core/sitehome/components/all-course-list/core-sitehome-all-course-list.html index 4ae3a0095..615f2251c 100644 --- a/src/core/sitehome/components/all-course-list/core-sitehome-all-course-list.html +++ b/src/core/sitehome/components/all-course-list/core-sitehome-all-course-list.html @@ -1,4 +1,4 @@ - +

{{ 'core.courses.availablecourses' | translate}}

diff --git a/src/core/sitehome/components/categories/core-sitehome-categories.html b/src/core/sitehome/components/categories/core-sitehome-categories.html index 9ecbba6da..24f9365a8 100644 --- a/src/core/sitehome/components/categories/core-sitehome-categories.html +++ b/src/core/sitehome/components/categories/core-sitehome-categories.html @@ -1,4 +1,4 @@ - +

{{ 'core.courses.categories' | translate}}

diff --git a/src/core/sitehome/components/course-search/core-sitehome-course-search.html b/src/core/sitehome/components/course-search/core-sitehome-course-search.html index e67ae19ee..c420ae151 100644 --- a/src/core/sitehome/components/course-search/core-sitehome-course-search.html +++ b/src/core/sitehome/components/course-search/core-sitehome-course-search.html @@ -1,4 +1,4 @@ - +

{{ 'core.courses.searchcourses' | translate}}

diff --git a/src/core/sitehome/components/enrolled-course-list/core-sitehome-enrolled-course-list.html b/src/core/sitehome/components/enrolled-course-list/core-sitehome-enrolled-course-list.html index 9de854ec5..0d917350a 100644 --- a/src/core/sitehome/components/enrolled-course-list/core-sitehome-enrolled-course-list.html +++ b/src/core/sitehome/components/enrolled-course-list/core-sitehome-enrolled-course-list.html @@ -1,4 +1,4 @@ - +

{{ 'core.courses.mycourses' | translate}}

diff --git a/src/core/siteplugins/components/block/block.ts b/src/core/siteplugins/components/block/block.ts index 7a817fb88..7f034a041 100644 --- a/src/core/siteplugins/components/block/block.ts +++ b/src/core/siteplugins/components/block/block.ts @@ -63,9 +63,11 @@ export class CoreSitePluginsBlockComponent extends CoreBlockBaseComponent implem } /** - * Pass on content invalidation by refreshing content in the plugin content component. + * Invalidate block data. + * + * @return Promise resolved when done. */ protected invalidateContent(): Promise { - return Promise.resolve(this.content.refreshContent()); + return this.sitePluginsProvider.invalidateContent(this.component, this.method, this.args); } } diff --git a/src/core/siteplugins/components/plugin-content/plugin-content.ts b/src/core/siteplugins/components/plugin-content/plugin-content.ts index aebf88e59..3a0b491c7 100644 --- a/src/core/siteplugins/components/plugin-content/plugin-content.ts +++ b/src/core/siteplugins/components/plugin-content/plugin-content.ts @@ -102,6 +102,7 @@ export class CoreSitePluginsPluginContentComponent implements OnInit, DoCheck { this.jsData = Object.assign(this.data, this.sitePluginsProvider.createDataForJS(this.initResult, result)); // Pass some methods as jsData so they can be called from the template too. + this.jsData.fetchContent = this.fetchContent.bind(this); this.jsData.openContent = this.openContent.bind(this); this.jsData.refreshContent = this.refreshContent.bind(this); this.jsData.updateContent = this.updateContent.bind(this); @@ -169,12 +170,14 @@ export class CoreSitePluginsPluginContentComponent implements OnInit, DoCheck { * @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. + * @param preSets New preSets to use. If not provided, use current preSets. */ - updateContent(args: any, component?: string, method?: string, jsData?: any): void { + updateContent(args: any, component?: string, method?: string, jsData?: any, preSets?: any): void { this.component = component || this.component; this.method = method || this.method; this.args = args; this.dataLoaded = false; + this.preSets = preSets || this.preSets; if (jsData) { Object.assign(this.data, jsData); } diff --git a/src/core/siteplugins/directives/call-ws-new-content.ts b/src/core/siteplugins/directives/call-ws-new-content.ts index fc65d5573..b6c1bdea0 100644 --- a/src/core/siteplugins/directives/call-ws-new-content.ts +++ b/src/core/siteplugins/directives/call-ws-new-content.ts @@ -87,7 +87,7 @@ export class CoreSitePluginsCallWSNewContentDirective extends CoreSitePluginsCal if (this.utils.isTrueOrOne(this.samePage)) { // Update the parent content (if it exists). if (this.parentContent) { - this.parentContent.updateContent(args, this.component, this.method, this.jsData); + this.parentContent.updateContent(args, this.component, this.method, this.jsData, this.newContentPreSets); } } else { let jsData = this.jsData; diff --git a/src/core/siteplugins/directives/new-content.ts b/src/core/siteplugins/directives/new-content.ts index d95b34bae..a373a55ee 100644 --- a/src/core/siteplugins/directives/new-content.ts +++ b/src/core/siteplugins/directives/new-content.ts @@ -84,7 +84,7 @@ export class CoreSitePluginsNewContentDirective implements OnInit { if (this.utils.isTrueOrOne(this.samePage)) { // Update the parent content (if it exists). if (this.parentContent) { - this.parentContent.updateContent(args, this.component, this.method, this.jsData); + this.parentContent.updateContent(args, this.component, this.method, this.jsData, this.preSets); } } else { let jsData = this.jsData; diff --git a/src/core/siteplugins/providers/helper.ts b/src/core/siteplugins/providers/helper.ts index 170ccf2aa..caa617a9e 100644 --- a/src/core/siteplugins/providers/helper.ts +++ b/src/core/siteplugins/providers/helper.ts @@ -13,7 +13,6 @@ // limitations under the License. import { Injectable } from '@angular/core'; -import { Http } from '@angular/http'; import { TranslateService } from '@ngx-translate/core'; import { CoreAppProvider } from '@providers/app'; import { CoreEventsProvider } from '@providers/events'; @@ -33,6 +32,7 @@ import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCoursesProvider } from '@core/courses/providers/courses'; import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; +import { CoreWSProvider } from '@providers/ws'; // Delegates import { CoreMainMenuDelegate } from '@core/mainmenu/providers/delegate'; @@ -93,7 +93,7 @@ export class CoreSitePluginsHelperProvider { private moduleDelegate: CoreCourseModuleDelegate, private userDelegate: CoreUserDelegate, private langProvider: CoreLangProvider, - private http: Http, + private wsProvider: CoreWSProvider, private sitePluginsProvider: CoreSitePluginsProvider, private prefetchDelegate: CoreCourseModulePrefetchDelegate, private compileProvider: CoreCompileProvider, @@ -208,15 +208,7 @@ export class CoreSitePluginsHelperProvider { undefined, undefined, undefined, handlerSchema.styles.version).then((url) => { // File is downloaded, get the contents. - return this.http.get(url).toPromise(); - }).then((response): any => { - const text = response && response.text(); - - if (typeof text == 'string') { - return text; - } else { - return Promise.reject(null); - } + return this.wsProvider.getText(url); }); }); } diff --git a/src/core/siteplugins/providers/siteplugins.ts b/src/core/siteplugins/providers/siteplugins.ts index 7b3b9c03b..b70686dd5 100644 --- a/src/core/siteplugins/providers/siteplugins.ts +++ b/src/core/siteplugins/providers/siteplugins.ts @@ -164,7 +164,7 @@ export class CoreSitePluginsProvider { if (initResult) { // First of all, add the data returned by the init JS (if any). - data = this.utils.clone(initResult.jsResult || {}); + data = Object.assign({}, initResult.jsResult || {}); if (typeof data == 'boolean') { data = {}; } diff --git a/src/core/tag/pages/search/search.html b/src/core/tag/pages/search/search.html index 78747f2a2..0ae40733d 100644 --- a/src/core/tag/pages/search/search.html +++ b/src/core/tag/pages/search/search.html @@ -10,7 +10,7 @@ - + diff --git a/src/core/tag/pages/search/search.module.ts b/src/core/tag/pages/search/search.module.ts index 084c9605c..f199aa6c6 100644 --- a/src/core/tag/pages/search/search.module.ts +++ b/src/core/tag/pages/search/search.module.ts @@ -18,6 +18,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { CoreTagSearchPage } from './search'; import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreSearchComponentsModule } from '@core/search/components/components.module'; @NgModule({ declarations: [ @@ -26,6 +27,7 @@ import { CoreDirectivesModule } from '@directives/directives.module'; imports: [ CoreComponentsModule, CoreDirectivesModule, + CoreSearchComponentsModule, IonicPageModule.forChild(CoreTagSearchPage), TranslateModule.forChild() ], diff --git a/src/core/user/components/components.module.ts b/src/core/user/components/components.module.ts index a2f772ed8..8c3029a51 100644 --- a/src/core/user/components/components.module.ts +++ b/src/core/user/components/components.module.ts @@ -22,6 +22,7 @@ import { CoreUserTagAreaComponent } from './tag-area/tag-area'; import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; import { CorePipesModule } from '@pipes/pipes.module'; +import { CoreSearchComponentsModule } from '@core/search/components/components.module'; @NgModule({ declarations: [ @@ -35,7 +36,8 @@ import { CorePipesModule } from '@pipes/pipes.module'; TranslateModule.forChild(), CoreComponentsModule, CoreDirectivesModule, - CorePipesModule + CorePipesModule, + CoreSearchComponentsModule, ], providers: [ ], diff --git a/src/core/user/components/participants/core-user-participants.html b/src/core/user/components/participants/core-user-participants.html index 78b894138..5b84592eb 100644 --- a/src/core/user/components/participants/core-user-participants.html +++ b/src/core/user/components/participants/core-user-participants.html @@ -10,7 +10,7 @@ - + diff --git a/src/core/user/components/participants/participants.ts b/src/core/user/components/participants/participants.ts index bcb4edce8..23e8fb2e5 100644 --- a/src/core/user/components/participants/participants.ts +++ b/src/core/user/components/participants/participants.ts @@ -161,16 +161,10 @@ export class CoreUserParticipantsComponent implements OnInit { this.displaySearchResults = false; this.participants = []; this.searchPage = 0; - this.splitviewCtrl.emptyDetails(); // Remove search results and display all participants. this.participantsLoaded = false; - this.fetchData(true).then(() => { - if (this.splitviewCtrl.isOn() && this.participants.length > 0) { - // Take first and load it. - this.gotoParticipant(this.participants[0].id); - } - }); + this.fetchData(true); } /** @@ -201,9 +195,19 @@ export class CoreUserParticipantsComponent implements OnInit { this.canLoadMore = result.canLoadMore; this.searchPage++; - if (!loadMore && this.splitviewCtrl.isOn() && this.participants.length > 0) { - // Take first and load it. - this.gotoParticipant(this.participants[0].id); + if (!loadMore && this.splitviewCtrl.isOn()) { + // Load the first entry. + if (this.participants.length > 0) { + const found = this.participantId && this.participants.some((user) => user.id == this.participantId); + + // The current selected user is not found in the current list, open first item. + if (!found) { + this.gotoParticipant(this.participants[0].id); + } + } else { + this.participantId = null; + this.splitviewCtrl.emptyDetails(); + } } }).catch((error) => { diff --git a/src/core/user/providers/user.ts b/src/core/user/providers/user.ts index fc685dadb..1bbb23c80 100644 --- a/src/core/user/providers/user.ts +++ b/src/core/user/providers/user.ts @@ -522,8 +522,10 @@ export class CoreUserProvider { promises.push(this.getProfile(userId, courseId, false, siteId).then((profile) => { if (profile.profileimageurl) { - this.filepoolProvider.addToQueueByUrl(siteId, profile.profileimageurl); + return this.filepoolProvider.addToQueueByUrl(siteId, profile.profileimageurl); } + }).catch((error) => { + this.logger.warn(`Ignore error when prefetching user ${userId}`, error); })); } }); @@ -531,6 +533,39 @@ export class CoreUserProvider { return Promise.all(promises); } + /** + * Prefetch user avatars. It prevents duplicates. + * + * @param entries List of entries that have the images. + * @param propertyName The name of the property that contains the image. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when prefetched. + */ + async prefetchUserAvatars(entries: any[], propertyName: string, siteId?: string): Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + const treated = {}; + + const promises = entries.map(async (entry) => { + const imageUrl = entry[propertyName]; + + if (!imageUrl || treated[imageUrl]) { + // It doesn't have an image or it has already been treated. + return; + } + + treated[imageUrl] = true; + + try { + await this.filepoolProvider.addToQueueByUrl(siteId, imageUrl); + } catch (ex) { + this.logger.warn(`Ignore error when prefetching user avatar ${imageUrl}`, entry, ex); + } + }); + + await Promise.all(promises); + } + /** * Search participants in a certain course. * diff --git a/src/directives/format-text.ts b/src/directives/format-text.ts index 9c4a8c314..dc178bedc 100644 --- a/src/directives/format-text.ts +++ b/src/directives/format-text.ts @@ -106,7 +106,7 @@ export class CoreFormatTextDirective implements OnChanges { * Detect changes on input properties. */ ngOnChanges(changes: { [name: string]: SimpleChange }): void { - if (changes.text) { + if (changes.text || changes.filter || changes.contextLevel || changes.contextInstanceId) { this.hideShowMore(); this.formatAndRenderContents(); } @@ -302,7 +302,7 @@ export class CoreFormatTextDirective implements OnChanges { return; } else { // Open a new state with the contents. - const filter = this.utils.isTrueOrOne(this.filter); + const filter = typeof this.filter != 'undefined' ? this.utils.isTrueOrOne(this.filter) : undefined; this.textUtils.expandText(this.fullTitle || this.translate.instant('core.description'), this.text, this.component, this.componentId, undefined, filter, this.contextLevel, this.contextInstanceId, this.courseId); @@ -413,7 +413,8 @@ export class CoreFormatTextDirective implements OnChanges { this.contextInstanceId = site.getSiteHomeId(); } - this.filter = typeof this.filter == 'undefined' ? !!(this.contextLevel && this.contextInstanceId) : !!this.filter; + const filter = typeof this.filter == 'undefined' ? + !!(this.contextLevel && typeof this.contextInstanceId != 'undefined') : this.utils.isTrueOrOne(this.filter); result.options = { clean: this.utils.isTrueOrOne(this.clean), @@ -423,7 +424,7 @@ export class CoreFormatTextDirective implements OnChanges { wsNotFiltered: this.utils.isTrueOrOne(this.wsNotFiltered) }; - if (this.filter) { + if (filter) { return this.filterHelper.getFiltersAndFormatText(this.text, this.contextLevel, this.contextInstanceId, result.options, result.siteId).then((res) => { diff --git a/src/index.html b/src/index.html index bd4bf7fb1..fa07d2622 100644 --- a/src/index.html +++ b/src/index.html @@ -34,8 +34,6 @@ })(); - - diff --git a/src/lang/en.json b/src/lang/en.json index 34a7890d1..876b80259 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -6,11 +6,11 @@ "all": "All", "allgroups": "All groups", "allparticipants": "All participants", - "android": "Android", "answer": "Answer", "answered": "Answered", "areyousure": "Are you sure?", "back": "Back", + "browser": "Browser", "cancel": "Cancel", "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.", @@ -63,6 +63,7 @@ "deleteduser": "Deleted user", "deleting": "Deleting", "description": "Description", + "desktop": "Desktop", "dfdaymonthyear": "MM-DD-YYYY", "dfdayweekmonth": "ddd, D MMM", "dffulldate": "dddd, D MMMM YYYY h[:]mm A", @@ -73,6 +74,7 @@ "digitalminor_desc": "Please ask your parent/guardian to contact:", "discard": "Discard", "dismiss": "Dismiss", + "displayoptions": "Display options", "done": "Done", "download": "Download", "downloaded": "Downloaded", @@ -118,7 +120,6 @@ "imageviewer": "Image viewer", "info": "Information", "invalidformdata": "Incorrect form data", - "ios": "iOS", "labelsep": ":", "filter": "Filter", "lastaccess": "Last access", @@ -295,7 +296,6 @@ "whoops": "Oops!", "whyisthishappening": "Why is this happening?", "whyisthisrequired": "Why is this required?", - "windowsphone": "Windows Phone", "wsfunctionnotavailable": "The web service function is not available.", "year": "year", "years": "years", diff --git a/src/providers/app.ts b/src/providers/app.ts index f2071b076..7a50ff553 100644 --- a/src/providers/app.ts +++ b/src/providers/app.ts @@ -21,7 +21,7 @@ import { StatusBar } from '@ionic-native/status-bar'; import { CoreDbProvider } from './db'; import { CoreLoggerProvider } from './logger'; import { CoreEventsProvider } from './events'; -import { SQLiteDB } from '@classes/sqlitedb'; +import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb'; import { CoreConfigConstants } from '../configconstants'; /** @@ -49,6 +49,37 @@ export interface CoreRedirectData { timemodified?: number; } +/** + * App DB schema and migration function. + */ +export interface CoreAppSchema { + /** + * Name of the schema. + */ + name: string; + + /** + * Latest version of the schema (integer greater than 0). + */ + version: number; + + /** + * Tables to create when installing or upgrading the schema. + */ + tables?: SQLiteDBTableSchema[]; + + /** + * Migrates the schema to the latest version. + * + * Called when installing and upgrading the schema, after creating the defined tables. + * + * @param db The affected DB. + * @param oldVersion Old version of the schema or 0 if not installed. + * @return Promise resolved when done. + */ + migrate?(db: SQLiteDB, oldVersion: number): Promise; +} + /** * Factory to provide some global functionalities, like access to the global app database. * @description @@ -71,12 +102,33 @@ export class CoreAppProvider { protected mainMenuOpen: number; protected forceOffline = false; + // Variables for DB. + protected createVersionsTableReady: Promise; + protected SCHEMA_VERSIONS_TABLE = 'schema_versions'; + protected versionsTableSchema: SQLiteDBTableSchema = { + name: this.SCHEMA_VERSIONS_TABLE, + columns: [ + { + name: 'name', + type: 'TEXT', + primaryKey: true, + }, + { + name: 'version', + type: 'INTEGER', + }, + ], + }; + constructor(dbProvider: CoreDbProvider, private platform: Platform, private keyboard: Keyboard, private appCtrl: App, private network: Network, logger: CoreLoggerProvider, private events: CoreEventsProvider, zone: NgZone, private menuCtrl: MenuController, private statusBar: StatusBar) { this.logger = logger.getInstance('CoreAppProvider'); this.db = dbProvider.getDB(this.DBNAME); + // Create the schema versions table. + this.createVersionsTableReady = this.db.createTableFromSchema(this.versionsTableSchema); + this.keyboard.onKeyboardShow().subscribe((data) => { // Execute the callback in the Angular zone, so change detection doesn't stop working. zone.run(() => { @@ -133,6 +185,47 @@ export class CoreAppProvider { } } + /** + * Install and upgrade a certain schema. + * + * @param schema The schema to create. + * @return Promise resolved when done. + */ + async createTablesFromSchema(schema: CoreAppSchema): Promise { + this.logger.debug(`Apply schema to app DB: ${schema.name}`); + + let oldVersion; + + try { + // Wait for the schema versions table to be created. + await this.createVersionsTableReady; + + // Fetch installed version of the schema. + const entry = await this.db.getRecord(this.SCHEMA_VERSIONS_TABLE, {name: schema.name}); + oldVersion = entry.version; + } catch (error) { + // No installed version yet. + oldVersion = 0; + } + + if (oldVersion >= schema.version) { + // Version already installed, nothing else to do. + return; + } + + this.logger.debug(`Migrating schema '${schema.name}' of app DB from version ${oldVersion} to ${schema.version}`); + + if (schema.tables) { + await this.db.createTablesFromSchema(schema.tables); + } + if (schema.migrate) { + await schema.migrate(this.db, oldVersion); + } + + // Set installed version. + await this.db.insertRecord(this.SCHEMA_VERSIONS_TABLE, {name: schema.name, version: schema.version}); + } + /** * Get the application global database. * diff --git a/src/providers/config.ts b/src/providers/config.ts index 38039bff6..709b5ce8e 100644 --- a/src/providers/config.ts +++ b/src/providers/config.ts @@ -13,8 +13,8 @@ // limitations under the License. import { Injectable } from '@angular/core'; -import { CoreAppProvider } from './app'; -import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb'; +import { CoreAppProvider, CoreAppSchema } from './app'; +import { SQLiteDB } from '@classes/sqlitedb'; /** * Factory to provide access to dynamic and permanent config and settings. @@ -24,24 +24,34 @@ import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb'; export class CoreConfigProvider { protected appDB: SQLiteDB; protected TABLE_NAME = 'core_config'; - protected tableSchema: SQLiteDBTableSchema = { - name: this.TABLE_NAME, - columns: [ + protected tableSchema: CoreAppSchema = { + name: 'CoreConfigProvider', + version: 1, + tables: [ { - name: 'name', - type: 'TEXT', - unique: true, - notNull: true + name: this.TABLE_NAME, + columns: [ + { + name: 'name', + type: 'TEXT', + unique: true, + notNull: true + }, + { + name: 'value' + }, + ], }, - { - name: 'value' - } - ] + ], }; + protected dbReady: Promise; // Promise resolved when the app DB is initialized. + constructor(appProvider: CoreAppProvider) { this.appDB = appProvider.getDB(); - this.appDB.createTableFromSchema(this.tableSchema); + this.dbReady = appProvider.createTablesFromSchema(this.tableSchema).catch(() => { + // Ignore errors. + }); } /** @@ -50,7 +60,9 @@ export class CoreConfigProvider { * @param name The config name. * @return Promise resolved when done. */ - delete(name: string): Promise { + async delete(name: string): Promise { + await this.dbReady; + return this.appDB.deleteRecords(this.TABLE_NAME, { name: name }); } @@ -61,16 +73,20 @@ export class CoreConfigProvider { * @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) => { + async get(name: string, defaultValue?: any): Promise { + await this.dbReady; + + try { + const entry = await this.appDB.getRecord(this.TABLE_NAME, { name: name }); + return entry.value; - }).catch((error) => { + } catch (error) { if (typeof defaultValue != 'undefined') { return defaultValue; } - return Promise.reject(error); - }); + throw error; + } } /** @@ -80,7 +96,9 @@ export class CoreConfigProvider { * @param value The config value. Can only store number or strings. * @return Promise resolved when done. */ - set(name: string, value: number | string): Promise { + async set(name: string, value: number | string): Promise { + await this.dbReady; + return this.appDB.insertRecord(this.TABLE_NAME, { name: name, value: value }); } } diff --git a/src/providers/cron.ts b/src/providers/cron.ts index dbf9ce3e1..f0b130e9f 100644 --- a/src/providers/cron.ts +++ b/src/providers/cron.ts @@ -14,12 +14,12 @@ import { Injectable, NgZone } from '@angular/core'; import { Network } from '@ionic-native/network'; -import { CoreAppProvider } from './app'; +import { CoreAppProvider, CoreAppSchema } from './app'; import { CoreConfigProvider } from './config'; import { CoreLoggerProvider } from './logger'; import { CoreUtilsProvider } from './utils/utils'; import { CoreConstants } from '@core/constants'; -import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb'; +import { SQLiteDB } from '@classes/sqlitedb'; /** * Interface that all cron handlers must implement. @@ -92,23 +92,30 @@ export class CoreCronDelegate { // Variables for database. protected CRON_TABLE = 'cron'; - protected tableSchema: SQLiteDBTableSchema = { - name: this.CRON_TABLE, - columns: [ + protected tableSchema: CoreAppSchema = { + name: 'CoreCronDelegate', + version: 1, + tables: [ { - name: 'id', - type: 'TEXT', - primaryKey: true + name: this.CRON_TABLE, + columns: [ + { + name: 'id', + type: 'TEXT', + primaryKey: true + }, + { + name: 'value', + type: 'INTEGER' + }, + ], }, - { - name: 'value', - type: 'INTEGER' - } - ] + ], }; protected logger; protected appDB: SQLiteDB; + protected dbReady: Promise; // Promise resolved when the app DB is initialized. protected handlers: { [s: string]: CoreCronHandler } = {}; protected queuePromise = Promise.resolve(); @@ -117,7 +124,9 @@ export class CoreCronDelegate { this.logger = logger.getInstance('CoreCronDelegate'); this.appDB = this.appProvider.getDB(); - this.appDB.createTableFromSchema(this.tableSchema); + this.dbReady = appProvider.createTablesFromSchema(this.tableSchema).catch(() => { + // Ignore errors. + }); // When the app is re-connected, start network handlers that were stopped. network.onConnect().subscribe(() => { @@ -306,16 +315,19 @@ export class CoreCronDelegate { * @param name Handler's name. * @return Promise resolved with the handler's last execution time. */ - protected getHandlerLastExecutionTime(name: string): Promise { + protected async getHandlerLastExecutionTime(name: string): Promise { + await this.dbReady; + const id = this.getHandlerLastExecutionId(name); - return this.appDB.getRecord(this.CRON_TABLE, { id: id }).then((entry) => { + try { + const entry = await this.appDB.getRecord(this.CRON_TABLE, { id: id }); const time = parseInt(entry.value, 10); return isNaN(time) ? 0 : time; - }).catch(() => { + } catch (err) { return 0; // Not set, return 0. - }); + } } /** @@ -471,7 +483,9 @@ export class CoreCronDelegate { * @param time Time to set. * @return Promise resolved when the execution time is saved. */ - protected setHandlerLastExecutionTime(name: string, time: number): Promise { + protected async setHandlerLastExecutionTime(name: string, time: number): Promise { + await this.dbReady; + const id = this.getHandlerLastExecutionId(name), entry = { id: id, diff --git a/src/providers/events.ts b/src/providers/events.ts index 5d1557ed8..abed87a95 100644 --- a/src/providers/events.ts +++ b/src/providers/events.ts @@ -64,6 +64,7 @@ export class CoreEventsProvider { static SELECT_COURSE_TAB = 'select_course_tab'; static WS_CACHE_INVALIDATED = 'ws_cache_invalidated'; static SITE_STORAGE_DELETED = 'site_storage_deleted'; + static FORM_ACTION = 'form_action'; protected logger; protected observables: { [s: string]: Subject } = {}; diff --git a/src/providers/filepool.ts b/src/providers/filepool.ts index 9e8fa5790..a4faa08f3 100644 --- a/src/providers/filepool.ts +++ b/src/providers/filepool.ts @@ -14,7 +14,7 @@ import { Injectable, NgZone } from '@angular/core'; import { Network } from '@ionic-native/network'; -import { CoreAppProvider } from './app'; +import { CoreAppProvider, CoreAppSchema } from './app'; import { CoreEventsProvider } from './events'; import { CoreFileProvider } from './file'; import { CoreInitDelegate } from './init'; @@ -28,7 +28,7 @@ import { CoreTextUtilsProvider } from './utils/text'; import { CoreTimeUtilsProvider } from './utils/time'; import { CoreUrlUtilsProvider } from './utils/url'; import { CoreUtilsProvider } from './utils/utils'; -import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb'; +import { SQLiteDB } from '@classes/sqlitedb'; import { CoreConstants } from '@core/constants'; import { Md5 } from 'ts-md5/dist/md5'; @@ -224,58 +224,62 @@ export class CoreFilepoolProvider { protected FILES_TABLE = 'filepool_files'; // Downloaded files. protected LINKS_TABLE = 'filepool_files_links'; // Links between downloaded files and components. protected PACKAGES_TABLE = 'filepool_packages'; // Downloaded packages (sets of files). - protected appTablesSchema: SQLiteDBTableSchema[] = [ - { - name: this.QUEUE_TABLE, - columns: [ - { - name: 'siteId', - type: 'TEXT' - }, - { - name: 'fileId', - type: 'TEXT' - }, - { - name: 'added', - type: 'INTEGER' - }, - { - name: 'priority', - type: 'INTEGER' - }, - { - name: 'url', - type: 'TEXT' - }, - { - name: 'revision', - type: 'INTEGER' - }, - { - name: 'timemodified', - type: 'INTEGER' - }, - { - name: 'isexternalfile', - type: 'INTEGER' - }, - { - name: 'repositorytype', - type: 'TEXT' - }, - { - name: 'path', - type: 'TEXT' - }, - { - name: 'links', - type: 'TEXT' - } - ], - primaryKeys: ['siteId', 'fileId'] - } - ]; + protected appTablesSchema: CoreAppSchema = { + name: 'CoreFilepoolProvider', + version: 1, + tables: [ + { + name: this.QUEUE_TABLE, + columns: [ + { + name: 'siteId', + type: 'TEXT' + }, + { + name: 'fileId', + type: 'TEXT' + }, + { + name: 'added', + type: 'INTEGER' + }, + { + name: 'priority', + type: 'INTEGER' + }, + { + name: 'url', + type: 'TEXT' + }, + { + name: 'revision', + type: 'INTEGER' + }, + { + name: 'timemodified', + type: 'INTEGER' + }, + { + name: 'isexternalfile', + type: 'INTEGER' + }, + { + name: 'repositorytype', + type: 'TEXT' + }, + { + name: 'path', + type: 'TEXT' + }, + { + name: 'links', + type: 'TEXT' + }, + ], + primaryKeys: ['siteId', 'fileId'], + }, + ], + }; protected siteSchema: CoreSiteSchema = { name: 'CoreFilepoolProvider', version: 1, @@ -392,6 +396,7 @@ export class CoreFilepoolProvider { protected logger; protected appDB: SQLiteDB; + protected dbReady: Promise; // Promise resolved when the app DB is initialized. protected tokenRegex = new RegExp('(\\?|&)token=([A-Za-z0-9]*)'); protected queueState: string; protected urlAttributes = [ @@ -415,7 +420,9 @@ export class CoreFilepoolProvider { this.logger = logger.getInstance('CoreFilepoolProvider'); this.appDB = this.appProvider.getDB(); - this.appDB.createTablesFromSchema(this.appTablesSchema); + this.dbReady = appProvider.createTablesFromSchema(this.appTablesSchema).catch(() => { + // Ignore errors. + }); this.sitesProvider.registerSiteSchema(this.siteSchema); @@ -567,11 +574,13 @@ export class CoreFilepoolProvider { * @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 { + protected async addToQueue(siteId: string, fileId: string, url: string, priority: number, revision: number, + timemodified: number, filePath: string, onProgress?: (event: any) => any, options: any = {}, link?: any): Promise { + await this.dbReady; + this.logger.debug(`Adding ${fileId} to the queue`); - return this.appDB.insertRecord(this.QUEUE_TABLE, { + await this.appDB.insertRecord(this.QUEUE_TABLE, { siteId: siteId, fileId: fileId, url: url, @@ -583,13 +592,13 @@ export class CoreFilepoolProvider { repositorytype: options.repositorytype, links: JSON.stringify(link ? [link] : []), added: Date.now() - }).then(() => { - // Check if the queue is running. - this.checkQueueProcessing(); - this.notifyFileDownloading(siteId, fileId); - - return this.getQueuePromise(siteId, fileId, true, onProgress); }); + + // Check if the queue is running. + this.checkQueueProcessing(); + this.notifyFileDownloading(siteId, fileId); + + return this.getQueuePromise(siteId, fileId, true, onProgress); } /** @@ -608,9 +617,11 @@ export class CoreFilepoolProvider { * @param alreadyFixed Whether the URL has already been fixed. * @return Resolved on success. */ - addToQueueByUrl(siteId: string, fileUrl: string, component?: string, componentId?: string | number, timemodified: number = 0, - filePath?: string, onProgress?: (event: any) => any, priority: number = 0, options: any = {}, revision?: number, - alreadyFixed?: boolean): Promise { + async 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, alreadyFixed?: boolean): Promise { + await this.dbReady; + let fileId, link, queueDeferred; @@ -1335,28 +1346,6 @@ export class CoreFilepoolProvider { }); } - /** - * Fill Missing Extension In Files, used to migrate from previous file handling. - * Reserved for core use, please do not call. - * - * @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); - - return this.sitesProvider.getSiteDb(siteId).then((db) => { - return db.getAllRecords(this.FILES_TABLE).then((entries) => { - const promises = []; - entries.forEach((entry) => { - promises.push(this.fillExtensionInFile(entry, siteId)); - }); - - return Promise.all(promises); - }); - }); - } - /** * Fix a component ID to always be a Number if possible. * @@ -2309,16 +2298,17 @@ export class CoreFilepoolProvider { * @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) => { - if (typeof entry === 'undefined') { - return Promise.reject(null); - } - // Convert the links to an object. - entry.links = this.textUtils.parseJSON(entry.links, []); + protected async hasFileInQueue(siteId: string, fileId: string): Promise { + await this.dbReady; - return entry; - }); + const entry = await this.appDB.getRecord(this.QUEUE_TABLE, { siteId: siteId, fileId: fileId }); + if (typeof entry === 'undefined') { + throw null; + } + // Convert the links to an object. + entry.links = this.textUtils.parseJSON(entry.links, []); + + return entry; } /** @@ -2395,6 +2385,23 @@ export class CoreFilepoolProvider { }); } + /** + * Check whether a file is downloadable. + * + * @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 a boolean: whether a file is downloadable. + */ + async isFileDownloadable(siteId: string, fileUrl: string, timemodified: number = 0, filePath?: string, revision?: number) + : Promise { + const state = await this.getFileStateByUrl(siteId, fileUrl, timemodified, filePath, revision); + + return state != CoreConstants.NOT_DOWNLOADABLE; + } + /** * Check if a file is downloading. * @@ -2546,19 +2553,25 @@ export class CoreFilepoolProvider { * * @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) => { - const item = items.pop(); - if (!item) { - return Promise.reject(this.ERR_QUEUE_IS_EMPTY); - } - // Convert the links to an object. - item.links = this.textUtils.parseJSON(item.links, []); + protected async processImportantQueueItem(): Promise { + await this.dbReady; - return this.processQueueItem(item); - }, () => { - return Promise.reject(this.ERR_QUEUE_IS_EMPTY); - }); + let items; + + try { + items = await this.appDB.getRecords(this.QUEUE_TABLE, undefined, 'priority DESC, added ASC', undefined, 0, 1); + } catch (err) { + throw this.ERR_QUEUE_IS_EMPTY; + } + + const item = items.pop(); + if (!item) { + throw this.ERR_QUEUE_IS_EMPTY; + } + // Convert the links to an object. + item.links = this.textUtils.parseJSON(item.links, []); + + return this.processQueueItem(item); } /** @@ -2685,7 +2698,9 @@ export class CoreFilepoolProvider { * @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 { + protected async removeFromQueue(siteId: string, fileId: string): Promise { + await this.dbReady; + return this.appDB.deleteRecords(this.QUEUE_TABLE, { siteId: siteId, fileId: fileId }); } @@ -2998,34 +3013,6 @@ export class CoreFilepoolProvider { }); } - /** - * Remove extension from fileId in queue, used to migrate from previous file handling. - * - * @return Promise resolved when done. - */ - treatExtensionInQueue(): Promise { - this.logger.debug('Treat extensions in queue'); - - return this.appDB.getAllRecords(this.QUEUE_TABLE).then((entries) => { - const promises = []; - entries.forEach((entry) => { - - // For files in the queue, we only need to remove the extension from the fileId. - // After downloading, additional info will be added. - const fileId = entry.fileId; - entry.fileId = this.mimeUtils.removeExtension(fileId); - - if (fileId == entry.fileId) { - return; - } - - promises.push(this.appDB.updateRecords(this.QUEUE_TABLE, { fileId: entry.fileId }, { fileId: fileId })); - }); - - return Promise.all(promises); - }); - } - /** * Resolves or rejects a queue deferred and removes it from the list. * diff --git a/src/providers/handlers/site-info-cron-handler.ts b/src/providers/handlers/site-info-cron-handler.ts new file mode 100644 index 000000000..c8e39cd58 --- /dev/null +++ b/src/providers/handlers/site-info-cron-handler.ts @@ -0,0 +1,62 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { CoreCronHandler } from '@providers/cron'; +import { CoreSitesProvider } from '@providers/sites'; + +/** + * Cron handler to update site info every certain time. + */ +@Injectable() +export class CoreSiteInfoCronHandler implements CoreCronHandler { + name = 'CoreSiteInfoCronHandler'; + + constructor(protected sitesProvider: CoreSitesProvider) {} + + /** + * Execute the process. + * Receives the ID of the site affected, undefined for all sites. + * + * @param siteId ID of the site affected, undefined for all sites. + * @return Promise resolved when done, rejected if failure. + */ + async execute(siteId?: string): Promise { + if (!siteId) { + const siteIds = await this.sitesProvider.getSitesIds(); + + return Promise.all(siteIds.map((siteId) => this.sitesProvider.updateSiteInfo(siteId))); + } else { + return this.sitesProvider.updateSiteInfo(siteId); + } + } + + /** + * Returns handler's interval in milliseconds. Defaults to CoreCronDelegate.DEFAULT_INTERVAL. + * + * @return Interval time (in milliseconds). + */ + getInterval(): number { + return 3600000; // 1 hour. + } + + /** + * Check whether it's a synchronization process or not. True if not defined. + * + * @return Whether it's a synchronization process or not. + */ + isSync(): boolean { + return false; + } +} diff --git a/src/providers/local-notifications.ts b/src/providers/local-notifications.ts index 3967f8cfe..78f2ccb08 100644 --- a/src/providers/local-notifications.ts +++ b/src/providers/local-notifications.ts @@ -17,13 +17,13 @@ import { Platform, Alert, AlertController } from 'ionic-angular'; import { LocalNotifications, ILocalNotification } from '@ionic-native/local-notifications'; import { Push } from '@ionic-native/push'; import { TranslateService } from '@ngx-translate/core'; -import { CoreAppProvider } from './app'; +import { CoreAppProvider, CoreAppSchema } from './app'; import { CoreConfigProvider } from './config'; import { CoreEventsProvider } from './events'; import { CoreLoggerProvider } from './logger'; import { CoreTextUtilsProvider } from './utils/text'; import { CoreUtilsProvider } from './utils/utils'; -import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb'; +import { SQLiteDB } from '@classes/sqlitedb'; import { CoreConstants } from '@core/constants'; import { CoreConfigConstants } from '../configconstants'; import { Subject, Subscription } from 'rxjs'; @@ -40,56 +40,61 @@ export class CoreLocalNotificationsProvider { protected SITES_TABLE = 'notification_sites'; // Store to asigne unique codes to each site. protected COMPONENTS_TABLE = 'notification_components'; // Store to asigne unique codes to each component. protected TRIGGERED_TABLE = 'notifications_triggered'; // Store to prevent re-triggering notifications. - protected tablesSchema: SQLiteDBTableSchema[] = [ - { - name: this.SITES_TABLE, - columns: [ - { - name: 'id', - type: 'TEXT', - primaryKey: true - }, - { - name: 'code', - type: 'INTEGER', - notNull: true - } - ] - }, - { - name: this.COMPONENTS_TABLE, - columns: [ - { - name: 'id', - type: 'TEXT', - primaryKey: true - }, - { - name: 'code', - type: 'INTEGER', - notNull: true - } - ] - }, - { - name: this.TRIGGERED_TABLE, - columns: [ - { - name: 'id', - type: 'INTEGER', - primaryKey: true - }, - { - name: 'at', - type: 'INTEGER', - notNull: true - } - ] - } - ]; + protected tablesSchema: CoreAppSchema = { + name: 'CoreLocalNotificationsProvider', + version: 1, + tables: [ + { + name: this.SITES_TABLE, + columns: [ + { + name: 'id', + type: 'TEXT', + primaryKey: true + }, + { + name: 'code', + type: 'INTEGER', + notNull: true + }, + ], + }, + { + name: this.COMPONENTS_TABLE, + columns: [ + { + name: 'id', + type: 'TEXT', + primaryKey: true + }, + { + name: 'code', + type: 'INTEGER', + notNull: true + }, + ], + }, + { + name: this.TRIGGERED_TABLE, + columns: [ + { + name: 'id', + type: 'INTEGER', + primaryKey: true + }, + { + name: 'at', + type: 'INTEGER', + notNull: true + }, + ], + }, + ], + }; protected logger; protected appDB: SQLiteDB; + protected dbReady: Promise; // Promise resolved when the app DB is initialized. protected codes: { [s: string]: number } = {}; protected codeRequestsQueue = {}; protected observables = {}; @@ -114,7 +119,9 @@ export class CoreLocalNotificationsProvider { this.logger = logger.getInstance('CoreLocalNotificationsProvider'); this.appDB = appProvider.getDB(); - this.appDB.createTablesFromSchema(this.tablesSchema); + this.dbReady = appProvider.createTablesFromSchema(this.tablesSchema).catch(() => { + // Ignore errors. + }); platform.ready().then(() => { // Listen to events. @@ -242,34 +249,35 @@ export class CoreLocalNotificationsProvider { * @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 { + protected async getCode(table: string, id: string): Promise { + await this.dbReady; + const key = table + '#' + id; // Check if the code is already in memory. if (typeof this.codes[key] != 'undefined') { - return Promise.resolve(this.codes[key]); + return this.codes[key]; } - // Check if we already have a code stored for that ID. - return this.appDB.getRecord(table, { id: id }).then((entry) => { + try { + // Check if we already have a code stored for that ID. + const entry = await this.appDB.getRecord(table, { id: id }); this.codes[key] = entry.code; return entry.code; - }).catch(() => { + } catch (err) { // No code stored for that ID. Create a new code for it. - return this.appDB.getRecords(table, undefined, 'code DESC').then((entries) => { - let newCode = 0; - if (entries.length > 0) { - newCode = entries[0].code + 1; - } + const entries = await this.appDB.getRecords(table, undefined, 'code DESC'); + let newCode = 0; + if (entries.length > 0) { + newCode = entries[0].code + 1; + } - return this.appDB.insertRecord(table, { id: id, code: newCode }).then(() => { - this.codes[key] = newCode; + await this.appDB.insertRecord(table, { id: id, code: newCode }); + this.codes[key] = newCode; - return newCode; - }); - }); - }); + return newCode; + } } /** @@ -352,8 +360,11 @@ export class CoreLocalNotificationsProvider { * @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) => { + async isTriggered(notification: ILocalNotification): Promise { + await this.dbReady; + + try { + const stored = await this.appDB.getRecord(this.TRIGGERED_TABLE, { id: notification.id }); let triggered = (notification.trigger && notification.trigger.at) || 0; if (typeof triggered != 'number') { @@ -361,9 +372,9 @@ export class CoreLocalNotificationsProvider { } return stored.at === triggered; - }).catch(() => { + } catch (err) { return this.localNotifications.isTriggered(notification.id); - }); + } } /** @@ -477,7 +488,9 @@ export class CoreLocalNotificationsProvider { * @param id Notification ID. * @return Promise resolved when it is removed. */ - removeTriggered(id: number): Promise { + async removeTriggered(id: number): Promise { + await this.dbReady; + return this.appDB.deleteRecords(this.TRIGGERED_TABLE, { id: id }); } @@ -714,7 +727,9 @@ export class CoreLocalNotificationsProvider { * @param notification Triggered notification. * @return Promise resolved when stored, rejected otherwise. */ - trigger(notification: ILocalNotification): Promise { + async trigger(notification: ILocalNotification): Promise { + await this.dbReady; + const entry = { id: notification.id, at: notification.trigger && notification.trigger.at ? notification.trigger.at : Date.now() @@ -730,7 +745,9 @@ export class CoreLocalNotificationsProvider { * @param newName The new name. * @return Promise resolved when done. */ - updateComponentName(oldName: string, newName: string): Promise { + async updateComponentName(oldName: string, newName: string): Promise { + await this.dbReady; + const oldId = this.COMPONENTS_TABLE + '#' + oldName, newId = this.COMPONENTS_TABLE + '#' + newName; diff --git a/src/providers/plugin-file-delegate.ts b/src/providers/plugin-file-delegate.ts index 469518698..42ee781a7 100644 --- a/src/providers/plugin-file-delegate.ts +++ b/src/providers/plugin-file-delegate.ts @@ -84,6 +84,15 @@ export interface CorePluginFileHandler extends CoreDelegateHandler { */ getFileSize?(file: CoreWSExternalFile, siteId?: string): Promise; + /** + * Check if a file is downloadable. + * + * @param file The file data. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with a boolean and a reason why it isn't downloadable if needed. + */ + isFileDownloadable?(file: CoreWSExternalFile, siteId?: string): Promise; + /** * Check whether the file should be treated by this handler. It is used in functions where the component isn't used. * @@ -103,6 +112,21 @@ export interface CorePluginFileHandler extends CoreDelegateHandler { treatDownloadedFile?(fileUrl: string, file: FileEntry, siteId?: string): Promise; } +/** + * Data about if a file is downloadable. + */ +export type CorePluginFileDownloadableResult = { + /** + * Whether it's downloadable. + */ + downloadable: boolean; + + /** + * If not downloadable, the reason why it isn't. + */ + reason?: string; +}; + /** * Delegate to register pluginfile information handlers. */ @@ -155,16 +179,22 @@ export class CorePluginFileDelegate extends CoreDelegate { * @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) + protected async getHandlerDownloadableFile(file: CoreWSExternalFile, handler: CorePluginFileHandler, siteId?: string) : Promise { - if (handler && handler.getDownloadableFile) { - return handler.getDownloadableFile(file, siteId).then((newFile) => { - return newFile || file; - }); + const isDownloadable = await this.isFileDownloadable(file, siteId); + + if (!isDownloadable.downloadable) { + throw isDownloadable.reason; } - return Promise.resolve(file); + if (handler && handler.getDownloadableFile) { + const newFile = await handler.getDownloadableFile(file, siteId); + + return newFile || file; + } + + return file; } /** @@ -240,23 +270,32 @@ export class CorePluginFileDelegate extends CoreDelegate { * @param siteId Site ID. If not defined, current site. * @return Promise resolved with the size. */ - getFileSize(file: CoreWSExternalFile, siteId?: string): Promise { + async getFileSize(file: CoreWSExternalFile, siteId?: string): Promise { + const isDownloadable = await this.isFileDownloadable(file, siteId); + + if (!isDownloadable.downloadable) { + return 0; + } + const handler = this.getHandlerForFile(file); // First of all check if file can be downloaded. - return this.getHandlerDownloadableFile(file, handler, siteId).then((file) => { - if (!file) { - return 0; - } + const downloadableFile = await this.getHandlerDownloadableFile(file, handler, siteId); + if (!downloadableFile) { + return 0; + } - if (handler && handler.getFileSize) { - return handler.getFileSize(file, siteId).catch(() => { - return file.filesize; - }); - } + if (handler && handler.getFileSize) { + try { + const size = handler.getFileSize(downloadableFile, siteId); - return Promise.resolve(file.filesize); - }); + return size; + } catch (error) { + // Ignore errors. + } + } + + return downloadableFile.filesize; } /** @@ -275,6 +314,24 @@ export class CorePluginFileDelegate extends CoreDelegate { } } + /** + * Check if a file is downloadable. + * + * @param file The file data. + * @param siteId Site ID. If not defined, current site. + * @return Promise with the data. + */ + isFileDownloadable(file: CoreWSExternalFile, siteId?: string): Promise { + const handler = this.getHandlerForFile(file); + + if (handler && handler.isFileDownloadable) { + return handler.isFileDownloadable(file, siteId); + } + + // Default to true. + return Promise.resolve({downloadable: true}); + } + /** * Removes the revision number from a file URL. * diff --git a/src/providers/sites.ts b/src/providers/sites.ts index 53230e7d2..dcf33d0c4 100644 --- a/src/providers/sites.ts +++ b/src/providers/sites.ts @@ -15,7 +15,7 @@ import { Injectable, Injector } from '@angular/core'; import { HttpClient } from '@angular/common/http'; import { TranslateService } from '@ngx-translate/core'; -import { CoreAppProvider } from './app'; +import { CoreAppProvider, CoreAppSchema } from './app'; import { CoreEventsProvider } from './events'; import { CoreLoggerProvider } from './logger'; import { CoreSitesFactoryProvider } from './sites-factory'; @@ -140,6 +140,12 @@ export interface CoreSiteSchema { */ canBeCleared?: string[]; + /** + * If true, the schema will only be applied to the current site. Otherwise it will be applied to all sites. + * If you're implementing a site plugin, please set it to true. + */ + onlyCurrentSite?: boolean; + /** * Tables to create when installing or upgrading the schema. */ @@ -158,6 +164,16 @@ export interface CoreSiteSchema { migrate?(db: SQLiteDB, oldVersion: number, siteId: string): Promise | void; } +/** + * Registered site schema. + */ +export interface CoreRegisteredSiteSchema extends CoreSiteSchema { + /** + * Site ID to apply the schema to. If not defined, all sites. + */ + siteId?: string; +} + export const enum CoreSitesReadingStrategy { OnlyCache, PreferCache, @@ -170,7 +186,7 @@ export const enum CoreSitesReadingStrategy { * their own database tables. Example: * * constructor(sitesProvider: CoreSitesProvider) { - * this.sitesProvider.createTableFromSchema(this.tableSchema); + * this.sitesProvider.registerSiteSchema(this.tableSchema); * * This provider will automatically create the tables in the databases of all the instantiated sites, and also to the * databases of sites instantiated from now on. @@ -178,62 +194,96 @@ export const enum CoreSitesReadingStrategy { @Injectable() export class CoreSitesProvider { // Variables for the database. - protected SITES_TABLE = 'sites'; - protected CURRENT_SITE_TABLE = 'current_site'; - protected SCHEMA_VERSIONS_TABLE = 'schema_versions'; - protected appTablesSchema: SQLiteDBTableSchema[] = [ - { - name: this.SITES_TABLE, - columns: [ - { - name: 'id', - type: 'TEXT', - primaryKey: true - }, - { - name: 'siteUrl', - type: 'TEXT', - notNull: true - }, - { - name: 'token', - type: 'TEXT' - }, - { - name: 'info', - type: 'TEXT' - }, - { - name: 'privateToken', - type: 'TEXT' - }, - { - name: 'config', - type: 'TEXT' - }, - { - name: 'loggedOut', - type: 'INTEGER' + static SITES_TABLE = 'sites_2'; + static CURRENT_SITE_TABLE = 'current_site'; + static SCHEMA_VERSIONS_TABLE = 'schema_versions'; + protected appTablesSchema: CoreAppSchema = { + name: 'CoreSitesProvider', + version: 2, + tables: [ + { + name: CoreSitesProvider.SITES_TABLE, + columns: [ + { + name: 'id', + type: 'TEXT', + primaryKey: true + }, + { + name: 'siteUrl', + type: 'TEXT', + notNull: true + }, + { + name: 'token', + type: 'TEXT' + }, + { + name: 'info', + type: 'TEXT' + }, + { + name: 'privateToken', + type: 'TEXT' + }, + { + name: 'config', + type: 'TEXT' + }, + { + name: 'loggedOut', + type: 'INTEGER' + }, + { + name: 'oauthId', + type: 'INTEGER' + }, + ], + }, + { + name: CoreSitesProvider.CURRENT_SITE_TABLE, + columns: [ + { + name: 'id', + type: 'INTEGER', + primaryKey: true + }, + { + name: 'siteId', + type: 'TEXT', + notNull: true, + unique: true + }, + ], + }, + ], + async migrate(db: SQLiteDB, oldVersion: number): Promise { + if (oldVersion < 2) { + const newTable = CoreSitesProvider.SITES_TABLE; + const oldTable = 'sites'; + + try { + // Check if V1 table exists. + await db.tableExists(oldTable); + + // Move the records from the old table. + const sites = await db.getAllRecords(oldTable); + const promises = []; + + sites.forEach((site) => { + promises.push(db.insertRecord(newTable, site)); + }); + + await Promise.all(promises); + + // Data moved, drop the old table. + await db.dropTable(oldTable); + } catch (error) { + // Old table does not exist, ignore. } - ] + } }, - { - name: this.CURRENT_SITE_TABLE, - columns: [ - { - name: 'id', - type: 'INTEGER', - primaryKey: true - }, - { - name: 'siteId', - type: 'TEXT', - notNull: true, - unique: true - } - ] - } - ]; + }; // Constants to validate a site version. protected WORKPLACE_APP = 3; @@ -249,13 +299,14 @@ export class CoreSitesProvider { protected currentSite: CoreSite; protected sites: { [s: string]: CoreSite } = {}; protected appDB: SQLiteDB; + protected dbReady: Promise; // Promise resolved when the app DB is initialized. protected siteSchemasMigration: { [siteId: string]: Promise } = {}; // Schemas for site tables. Other providers can add schemas in here. - protected siteSchemas: { [name: string]: CoreSiteSchema } = {}; + protected siteSchemas: { [name: string]: CoreRegisteredSiteSchema } = {}; protected siteTablesSchemas: SQLiteDBTableSchema[] = [ { - name: this.SCHEMA_VERSIONS_TABLE, + name: CoreSitesProvider.SCHEMA_VERSIONS_TABLE, columns: [ { name: 'name', @@ -323,7 +374,9 @@ export class CoreSitesProvider { this.logger = logger.getInstance('CoreSitesProvider'); this.appDB = appProvider.getDB(); - this.appDB.createTablesFromSchema(this.appTablesSchema); + this.dbReady = appProvider.createTablesFromSchema(this.appTablesSchema).catch(() => { + // Ignore errors. + }); this.registerSiteSchema(this.siteSchema); } @@ -600,16 +653,17 @@ export class CoreSitesProvider { * @param token User's token. * @param privateToken User's private token. * @param login Whether to login the user in the site. Defaults to true. + * @param oauthId OAuth ID. Only if the authentication was using an OAuth method. * @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 { + newSite(siteUrl: string, token: string, privateToken: string = '', login: boolean = true, oauthId?: number): Promise { if (typeof login != 'boolean') { login = true; } // Create a "candidate" site to fetch the site info. - let candidateSite = this.sitesFactory.makeSite(undefined, siteUrl, token, undefined, privateToken), - isNewSite = true; + let candidateSite = this.sitesFactory.makeSite(undefined, siteUrl, token, undefined, privateToken, undefined, undefined); + let isNewSite = true; return candidateSite.fetchSiteInfo().then((info) => { const result = this.isValidMoodleVersion(info); @@ -627,12 +681,14 @@ export class CoreSitesProvider { candidateSite.setToken(token); candidateSite.setPrivateToken(privateToken); candidateSite.setInfo(info); + candidateSite.setOAuthId(oauthId); } else { // New site, set site ID and info. isNewSite = true; candidateSite.setId(siteId); candidateSite.setInfo(info); + candidateSite.setOAuthId(oauthId); // Create database tables before login and before any WS call. return this.migrateSiteSchemas(candidateSite); @@ -652,7 +708,7 @@ export class CoreSitesProvider { } // Add site to sites list. - this.addSite(siteId, siteUrl, token, info, privateToken, config); + this.addSite(siteId, siteUrl, token, info, privateToken, config, oauthId); this.sites[siteId] = candidateSite; if (login) { @@ -670,6 +726,13 @@ export class CoreSitesProvider { } return this.treatInvalidAppVersion(result, siteUrl); + }).catch((error) => { + // Error invaliddevice is returned by Workplace server meaning the same as connecttoworkplaceapp. + if (error && error.errorcode == 'invaliddevice') { + return this.treatInvalidAppVersion(this.WORKPLACE_APP, siteUrl); + } + + return Promise.reject(error); }); } @@ -857,9 +920,13 @@ export class CoreSitesProvider { * @param info Site's info. * @param privateToken User's private token. * @param config Site config (from tool_mobile_get_config). + * @param oauthId OAuth ID. Only if the authentication was using an OAuth method. * @return Promise resolved when done. */ - addSite(id: string, siteUrl: string, token: string, info: any, privateToken: string = '', config?: any): Promise { + async addSite(id: string, siteUrl: string, token: string, info: any, privateToken: string = '', config?: any, + oauthId?: number): Promise { + await this.dbReady; + const entry = { id: id, siteUrl: siteUrl, @@ -867,10 +934,11 @@ export class CoreSitesProvider { info: info ? JSON.stringify(info) : info, privateToken: privateToken, config: config ? JSON.stringify(config) : config, - loggedOut: 0 + loggedOut: 0, + oauthId: oauthId, }; - return this.appDB.insertRecord(this.SITES_TABLE, entry); + return this.appDB.insertRecord(CoreSitesProvider.SITES_TABLE, entry); } /** @@ -1070,29 +1138,32 @@ export class CoreSitesProvider { * @param siteId ID of the site to delete. * @return Promise to be resolved when the site is deleted. */ - deleteSite(siteId: string): Promise { + async deleteSite(siteId: string): Promise { + await this.dbReady; + this.logger.debug(`Delete site ${siteId}`); if (typeof this.currentSite != 'undefined' && this.currentSite.id == siteId) { this.logout(); } - return this.getSite(siteId).then((site: CoreSite) => { - return site.deleteDB().then(() => { - // Site DB deleted, now delete the app from the list of sites. - delete this.sites[siteId]; + const site = await this.getSite(siteId); - return this.appDB.deleteRecords(this.SITES_TABLE, { id: siteId }).then(() => { - // Site deleted from sites list, now delete the folder. - return site.deleteFolder(); - }, () => { - // DB remove shouldn't fail, but we'll go ahead even if it does. - return site.deleteFolder(); - }).then(() => { - this.eventsProvider.trigger(CoreEventsProvider.SITE_DELETED, site, siteId); - }); - }); - }); + await site.deleteDB(); + + // Site DB deleted, now delete the app from the list of sites. + delete this.sites[siteId]; + + try { + await this.appDB.deleteRecords(CoreSitesProvider.SITES_TABLE, { id: siteId }); + } catch (err) { + // DB remove shouldn't fail, but we'll go ahead even if it does. + } + + // Site deleted from sites list, now delete the folder. + await site.deleteFolder(); + + this.eventsProvider.trigger(CoreEventsProvider.SITE_DELETED, site, siteId); } /** @@ -1100,10 +1171,12 @@ export class CoreSitesProvider { * * @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) => { - return count > 0; - }); + async hasSites(): Promise { + await this.dbReady; + + const count = await this.appDB.countRecords(CoreSitesProvider.SITES_TABLE); + + return count > 0; } /** @@ -1112,18 +1185,24 @@ export class CoreSitesProvider { * @param siteId The site ID. If not defined, current site (if available). * @return Promise resolved with the site. */ - getSite(siteId?: string): Promise { + async getSite(siteId?: string): Promise { + await this.dbReady; + if (!siteId) { - return this.currentSite ? Promise.resolve(this.currentSite) : Promise.reject(null); + if (this.currentSite) { + return this.currentSite; + } + + throw null; } else if (this.currentSite && this.currentSite.getId() == siteId) { - return Promise.resolve(this.currentSite); + return this.currentSite; } else if (typeof this.sites[siteId] != 'undefined') { - return Promise.resolve(this.sites[siteId]); + return this.sites[siteId]; } else { // Retrieve and create the site. - return this.appDB.getRecord(this.SITES_TABLE, { id: siteId }).then((data) => { - return this.makeSiteFromSiteListEntry(data); - }); + const data = await this.appDB.getRecord(CoreSitesProvider.SITES_TABLE, { id: siteId }); + + return this.makeSiteFromSiteListEntry(data); } } @@ -1144,6 +1223,7 @@ export class CoreSitesProvider { site = this.sitesFactory.makeSite(entry.id, entry.siteUrl, entry.token, info, entry.privateToken, config, entry.loggedOut == 1); + site.setOAuthId(entry.oauthId); return this.migrateSiteSchemas(site).then(() => { // Set site after migrating schemas, or a call to getSite could get the site while tables are being created. @@ -1199,27 +1279,29 @@ export class CoreSitesProvider { * @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) => { - const formattedSites = []; - sites.forEach((site) => { - if (!ids || ids.indexOf(site.id) > -1) { - // Parse info. - const siteInfo = site.info ? this.textUtils.parseJSON(site.info) : site.info, - basicInfo: CoreSiteBasicInfo = { - id: site.id, - siteUrl: site.siteUrl, - fullName: siteInfo && siteInfo.fullname, - siteName: CoreConfigConstants.sitename ? CoreConfigConstants.sitename : siteInfo && siteInfo.sitename, - avatar: siteInfo && siteInfo.userpictureurl, - siteHomeId: siteInfo && siteInfo.siteid || 1 - }; - formattedSites.push(basicInfo); - } - }); + async getSites(ids?: string[]): Promise { + await this.dbReady; - return formattedSites; + const sites = await this.appDB.getAllRecords(CoreSitesProvider.SITES_TABLE); + + const formattedSites = []; + sites.forEach((site) => { + if (!ids || ids.indexOf(site.id) > -1) { + // Parse info. + const siteInfo = site.info ? this.textUtils.parseJSON(site.info) : site.info; + const basicInfo: CoreSiteBasicInfo = { + id: site.id, + siteUrl: site.siteUrl, + fullName: siteInfo && siteInfo.fullname, + siteName: CoreConfigConstants.sitename ? CoreConfigConstants.sitename : siteInfo && siteInfo.sitename, + avatar: siteInfo && siteInfo.userpictureurl, + siteHomeId: siteInfo && siteInfo.siteid || 1, + }; + formattedSites.push(basicInfo); + } }); + + return formattedSites; } /** @@ -1257,11 +1339,13 @@ export class CoreSitesProvider { * * @return Promise resolved when the sites IDs are retrieved. */ - getLoggedInSitesIds(): Promise { - return this.appDB.getRecords(this.SITES_TABLE, {loggedOut : 0}).then((sites) => { - return sites.map((site) => { - return site.id; - }); + async getLoggedInSitesIds(): Promise { + await this.dbReady; + + const sites = await this.appDB.getRecords(CoreSitesProvider.SITES_TABLE, {loggedOut : 0}); + + return sites.map((site) => { + return site.id; }); } @@ -1270,11 +1354,13 @@ export class CoreSitesProvider { * * @return Promise resolved when the sites IDs are retrieved. */ - getSitesIds(): Promise { - return this.appDB.getAllRecords(this.SITES_TABLE).then((sites) => { - return sites.map((site) => { - return site.id; - }); + async getSitesIds(): Promise { + await this.dbReady; + + const sites = await this.appDB.getAllRecords(CoreSitesProvider.SITES_TABLE); + + return sites.map((site) => { + return site.id; }); } @@ -1284,15 +1370,17 @@ export class CoreSitesProvider { * @param siteid ID of the site the user is accessing. * @return Promise resolved when current site is stored. */ - login(siteId: string): Promise { + async login(siteId: string): Promise { + await this.dbReady; + const entry = { id: 1, siteId: siteId }; - return this.appDB.insertRecord(this.CURRENT_SITE_TABLE, entry).then(() => { - this.eventsProvider.trigger(CoreEventsProvider.LOGIN, {}, siteId); - }); + await this.appDB.insertRecord(CoreSitesProvider.CURRENT_SITE_TABLE, entry); + + this.eventsProvider.trigger(CoreEventsProvider.LOGIN, {}, siteId); } /** @@ -1300,7 +1388,9 @@ export class CoreSitesProvider { * * @return Promise resolved when the user is logged out. */ - logout(): Promise { + async logout(): Promise { + await this.dbReady; + let siteId; const promises = []; @@ -1314,12 +1404,14 @@ export class CoreSitesProvider { promises.push(this.setSiteLoggedOut(siteId, true)); } - promises.push(this.appDB.deleteRecords(this.CURRENT_SITE_TABLE, { id: 1 })); + promises.push(this.appDB.deleteRecords(CoreSitesProvider.CURRENT_SITE_TABLE, { id: 1 })); } - return Promise.all(promises).finally(() => { + try { + await Promise.all(promises); + } finally { this.eventsProvider.trigger(CoreEventsProvider.LOGOUT, {}, siteId); - }); + } } /** @@ -1327,21 +1419,24 @@ export class CoreSitesProvider { * * @return Promise resolved if a session is restored. */ - restoreSession(): Promise { + async restoreSession(): Promise { if (this.sessionRestored) { return Promise.reject(null); } + await this.dbReady; + this.sessionRestored = true; - return this.appDB.getRecord(this.CURRENT_SITE_TABLE, { id: 1 }).then((currentSite) => { + try { + const currentSite = await this.appDB.getRecord(CoreSitesProvider.CURRENT_SITE_TABLE, { id: 1 }); const siteId = currentSite.siteId; this.logger.debug(`Restore session in site ${siteId}`); return this.loadSite(siteId); - }).catch(() => { + } catch (err) { // No current session. - }); + } } /** @@ -1351,17 +1446,18 @@ export class CoreSitesProvider { * @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) => { - const newValues = { - token: '', // Erase the token for security. - loggedOut: loggedOut ? 1 : 0 - }; + async setSiteLoggedOut(siteId: string, loggedOut: boolean): Promise { + await this.dbReady; - site.setLoggedOut(loggedOut); + const site = await this.getSite(siteId); + const newValues = { + token: '', // Erase the token for security. + loggedOut: loggedOut ? 1 : 0 + }; - return this.appDB.updateRecords(this.SITES_TABLE, newValues, { id: siteId }); - }); + site.setLoggedOut(loggedOut); + + return this.appDB.updateRecords(CoreSitesProvider.SITES_TABLE, newValues, { id: siteId }); } /** @@ -1396,20 +1492,21 @@ export class CoreSitesProvider { * @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) => { - const newValues = { - token: token, - privateToken: privateToken, - loggedOut: 0 - }; + async updateSiteTokenBySiteId(siteId: string, token: string, privateToken: string = ''): Promise { + await this.dbReady; - site.token = token; - site.privateToken = privateToken; - site.setLoggedOut(false); // Token updated means the user authenticated again, not logged out anymore. + const site = await this.getSite(siteId); + const newValues = { + token: token, + privateToken: privateToken, + loggedOut: 0, + }; - return this.appDB.updateRecords(this.SITES_TABLE, newValues, { id: siteId }); - }); + site.token = token; + site.privateToken = privateToken; + site.setLoggedOut(false); // Token updated means the user authenticated again, not logged out anymore. + + return this.appDB.updateRecords(CoreSitesProvider.SITES_TABLE, newValues, { id: siteId }); } /** @@ -1418,39 +1515,49 @@ export class CoreSitesProvider { * @param siteid Site's ID. * @return A promise resolved when the site is updated. */ - updateSiteInfo(siteId: string): Promise { - return this.getSite(siteId).then((site) => { - return site.fetchSiteInfo().then((info) => { - site.setInfo(info); + async updateSiteInfo(siteId: string): Promise { + await this.dbReady; - const versionCheck = this.isValidMoodleVersion(info); - if (versionCheck != this.VALID_VERSION) { - // The Moodle version is not supported, reject. - return this.treatInvalidAppVersion(versionCheck, site.getURL(), site.getId()); - } + const site = await this.getSite(siteId); - // Try to get the site config. - return this.getSiteConfig(site).catch(() => { - // Error getting config, keep the current one. - }).then((config) => { - const newValues: any = { - info: JSON.stringify(info), - loggedOut: site.isLoggedOut() ? 1 : 0 - }; + try { - if (typeof config != 'undefined') { - site.setConfig(config); - newValues.config = JSON.stringify(config); - } + const info = await site.fetchSiteInfo(); + site.setInfo(info); - return this.appDB.updateRecords(this.SITES_TABLE, newValues, { id: siteId }).finally(() => { - this.eventsProvider.trigger(CoreEventsProvider.SITE_UPDATED, info, siteId); - }); - }); - }).catch((error) => { - // Ignore that we cannot fetch site info. Probably the auth token is invalid. - }); - }); + const versionCheck = this.isValidMoodleVersion(info); + if (versionCheck != this.VALID_VERSION) { + // The Moodle version is not supported, reject. + return this.treatInvalidAppVersion(versionCheck, site.getURL(), site.getId()); + } + + // Try to get the site config. + let config; + + try { + config = await this.getSiteConfig(site); + } catch (error) { + // Error getting config, keep the current one. + } + + const newValues: any = { + info: JSON.stringify(info), + loggedOut: site.isLoggedOut() ? 1 : 0, + }; + + if (typeof config != 'undefined') { + site.setConfig(config); + newValues.config = JSON.stringify(config); + } + + try { + await this.appDB.updateRecords(CoreSitesProvider.SITES_TABLE, newValues, { id: siteId }); + } finally { + this.eventsProvider.trigger(CoreEventsProvider.SITE_UPDATED, info, siteId); + } + } catch (error) { + // Ignore that we cannot fetch site info. Probably the auth token is invalid. + } } /** @@ -1476,11 +1583,13 @@ export class CoreSitesProvider { * @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 { + async getSiteIdsFromUrl(url: string, prioritize?: boolean, username?: string): Promise { + await this.dbReady; + // If prioritize is true, check current site first. if (prioritize && this.currentSite && this.currentSite.containsUrl(url)) { if (!username || this.currentSite.getInfo().username == username) { - return Promise.resolve([this.currentSite.getId()]); + return [this.currentSite.getId()]; } } @@ -1489,18 +1598,19 @@ export class CoreSitesProvider { // URL doesn't have http(s) protocol. Check if it has any protocol. if (this.urlUtils.isAbsoluteURL(url)) { // It has some protocol. Return empty array. - return Promise.resolve([]); + return []; } else { // No protocol, probably a relative URL. Return current site. if (this.currentSite) { - return Promise.resolve([this.currentSite.getId()]); + return [this.currentSite.getId()]; } else { - return Promise.resolve([]); + return []; } } } - return this.appDB.getAllRecords(this.SITES_TABLE).then((siteEntries) => { + try { + const siteEntries = await this.appDB.getAllRecords(CoreSitesProvider.SITES_TABLE); const ids = []; const promises = []; @@ -1516,13 +1626,13 @@ export class CoreSitesProvider { } }); - return Promise.all(promises).then(() => { - return ids; - }); - }).catch(() => { + await Promise.all(promises); + + return ids; + } catch (error) { // Shouldn't happen. return []; - }); + } } /** @@ -1530,10 +1640,12 @@ export class CoreSitesProvider { * * @return Promise resolved with the site ID. */ - getStoredCurrentSiteId(): Promise { - return this.appDB.getRecord(this.CURRENT_SITE_TABLE, { id: 1 }).then((currentSite) => { - return currentSite.siteId; - }); + async getStoredCurrentSiteId(): Promise { + await this.dbReady; + + const currentSite = await this.appDB.getRecord(CoreSitesProvider.CURRENT_SITE_TABLE, { id: 1 }); + + return currentSite.siteId; } /** @@ -1580,6 +1692,7 @@ export class CoreSitesProvider { * Create a table in all the sites databases. * * @param table Table schema. + * @deprecated. Please use registerSiteSchema instead. */ createTableFromSchema(table: SQLiteDBTableSchema): void { this.createTablesFromSchema([table]); @@ -1589,6 +1702,7 @@ export class CoreSitesProvider { * Create several tables in all the sites databases. * * @param tables List of tables schema. + * @deprecated. Please use registerSiteSchema instead. */ createTablesFromSchema(tables: SQLiteDBTableSchema[]): void { // Add the tables to the list of schemas. This list is to create all the tables in new sites. @@ -1626,9 +1740,41 @@ export class CoreSitesProvider { /** * Register a site schema. + * + * @param schema The schema to register. + * @return Promise resolved when done. */ - registerSiteSchema(schema: CoreSiteSchema): void { - this.siteSchemas[schema.name] = schema; + async registerSiteSchema(schema: CoreSiteSchema): Promise { + if (this.currentSite) { + try { + // Site has already been created, apply the schema directly. + const schemas: {[name: string]: CoreRegisteredSiteSchema} = {}; + schemas[schema.name] = schema; + + if (!schema.onlyCurrentSite) { + // Apply it to all sites. + const siteIds = await this.getSitesIds(); + + await Promise.all(siteIds.map(async (siteId) => { + const site = await this.getSite(siteId); + + return this.applySiteSchemas(site, schemas); + })); + } else { + // Apply it to the specified site only. + (schema as CoreRegisteredSiteSchema).siteId = this.currentSite.getId(); + + await this.applySiteSchemas(this.currentSite, schemas); + } + } finally { + // Add the schema to the list. It's done in the end to prevent a schema being applied twice. + this.siteSchemas[schema.name] = schema; + } + + } else if (!schema.onlyCurrentSite) { + // Add the schema to the list, it will be applied when the sites are created. + this.siteSchemas[schema.name] = schema; + } } /** @@ -1638,7 +1784,6 @@ export class CoreSitesProvider { * @return Promise resolved when done. */ migrateSiteSchemas(site: CoreSite): Promise { - const db = site.getDb(); if (this.siteSchemasMigration[site.id]) { return this.siteSchemasMigration[site.id]; @@ -1647,40 +1792,8 @@ export class CoreSitesProvider { this.logger.debug(`Migrating all schemas of ${site.id}`); // First create tables not registerd with name/version. - const promise = db.createTablesFromSchema(this.siteTablesSchemas).then(() => { - // Fetch installed versions of the schema. - return db.getAllRecords(this.SCHEMA_VERSIONS_TABLE).then((records) => { - const versions = {}; - records.forEach((record) => { - versions[record.name] = record.version; - }); - - const promises = []; - for (const name in this.siteSchemas) { - const schema = this.siteSchemas[name]; - const oldVersion = versions[name] || 0; - if (oldVersion >= schema.version) { - continue; - } - - this.logger.debug(`Migrating schema '${name}' of ${site.id} from version ${oldVersion} to ${schema.version}`); - - let promise: Promise = Promise.resolve(); - if (schema.tables) { - promise = promise.then(() => db.createTablesFromSchema(schema.tables)); - } - if (schema.migrate) { - promise = promise.then(() => schema.migrate(db, oldVersion, site.id)); - } - - // Set installed version. - promise = promise.then(() => db.insertRecord(this.SCHEMA_VERSIONS_TABLE, {name, version: schema.version})); - - promises.push(promise); - } - - return Promise.all(promises); - }); + const promise = site.getDb().createTablesFromSchema(this.siteTablesSchemas).then(() => { + return this.applySiteSchemas(site, this.siteSchemas); }); this.siteSchemasMigration[site.id] = promise; @@ -1690,6 +1803,53 @@ export class CoreSitesProvider { }); } + /** + * Install and upgrade the supplied schemas for a certain site. + * + * @param site Site. + * @param schemas Schemas to migrate. + * @return Promise resolved when done. + */ + protected applySiteSchemas(site: CoreSite, schemas: {[name: string]: CoreRegisteredSiteSchema}): Promise { + const db = site.getDb(); + + // Fetch installed versions of the schema. + return db.getAllRecords(CoreSitesProvider.SCHEMA_VERSIONS_TABLE).then((records) => { + const versions = {}; + records.forEach((record) => { + versions[record.name] = record.version; + }); + + const promises = []; + for (const name in schemas) { + const schema = schemas[name]; + const oldVersion = versions[name] || 0; + if (oldVersion >= schema.version || (schema.siteId && site.getId() != schema.siteId)) { + // Version already applied or the schema shouldn't be registered to this site. + continue; + } + + this.logger.debug(`Migrating schema '${name}' of ${site.id} from version ${oldVersion} to ${schema.version}`); + + let promise: Promise = Promise.resolve(); + if (schema.tables) { + promise = promise.then(() => db.createTablesFromSchema(schema.tables)); + } + if (schema.migrate) { + promise = promise.then(() => schema.migrate(db, oldVersion, site.id)); + } + + // Set installed version. + promise = promise.then(() => db.insertRecord(CoreSitesProvider.SCHEMA_VERSIONS_TABLE, + {name, version: schema.version})); + + promises.push(promise); + } + + return Promise.all(promises); + }); + } + /** * Check if a URL is the root URL of any of the stored sites. * @@ -1727,12 +1887,15 @@ export class CoreSitesProvider { /** * Returns the Site Schema names that can be cleared on space storage. * + * @param site The site that will be cleared. * @return Name of the site schemas. */ - getSiteTableSchemasToClear(): string[] { + getSiteTableSchemasToClear(site: CoreSite): string[] { let reset = []; for (const name in this.siteSchemas) { - if (this.siteSchemas[name].canBeCleared) { + const schema = this.siteSchemas[name]; + + if (schema.canBeCleared && (!schema.siteId || site.getId() == schema.siteId)) { reset = reset.concat(this.siteSchemas[name].canBeCleared); } } diff --git a/src/providers/update-manager.ts b/src/providers/update-manager.ts index 24ddb9f79..b32ef3499 100644 --- a/src/providers/update-manager.ts +++ b/src/providers/update-manager.ts @@ -13,48 +13,10 @@ // limitations under the License. import { Injectable } from '@angular/core'; -import { CoreAppProvider } from './app'; import { CoreConfigProvider } from './config'; -import { CoreFilepoolProvider } from './filepool'; import { CoreInitHandler, CoreInitDelegate } from './init'; -import { CoreLocalNotificationsProvider } from './local-notifications'; import { CoreLoggerProvider } from './logger'; -import { CoreSitesProvider } from './sites'; -import { CoreUtilsProvider } from './utils/utils'; -import { CoreTimeUtilsProvider } from './utils/time'; import { CoreConfigConstants } from '../configconstants'; -import { AddonCalendarProvider } from '@addon/calendar/providers/calendar'; -import { SQLiteDB } from '@classes/sqlitedb'; - -/** - * Data to migrate a store of Ionic 1 app to the SQLite DB. - */ -export interface CoreUpdateManagerMigrateTable { - /** - * Current name of the store/table. - */ - name: string; - - /** - * New name of the table. If not defined, "name" will be used. - */ - newName?: string; - - /** - * An object to rename and convert some fields of the table/store. - */ - fields?: { - name: string, // Field name in the old app. - newName?: string, // New field name. If not provided, keep the same name. - type?: string, // Type of the field if it needs to be treated: 'any', 'object', 'date', 'boolean'. - delete?: boolean // Whether the field must be deleted because it isn't needed in the new schema. - }[]; - - /** - * If set, all the fields that aren't in this array will be deleted. The names in this list should be the new names. - */ - filterFields?: string[]; -} /** * Factory to handle app updates. This factory shouldn't be used outside of core. @@ -70,253 +32,8 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { protected VERSION_APPLIED = 'version_applied'; protected logger; - protected localNotificationsComponentsMigrate: {[old: string]: string} = {}; - /** - * Tables to migrate from app DB ('MoodleMobile'). Include all the core ones to decrease the dependencies. - */ - protected appDBTables: CoreUpdateManagerMigrateTable[] = [ - { - name: 'config', - newName: 'core_config', - fields: [ - { - name: 'value', - type: 'any' - } - ] - }, - { - name: 'cron' - }, - { - name: 'current_site', - fields: [ - { - name: 'siteid', - newName: 'siteId' - } - ] - }, - { - name: 'desktop_local_notifications', - fields: [ - { - name: 'data', - type: 'object' - }, - { - name: 'triggered', - type: 'boolean' - } - ], - filterFields: ['id', 'title', 'text', 'at', 'data', 'triggered'] - }, - { - name: 'files_queue', - newName: 'filepool_files_queue', - fields: [ - { - name: 'isexternalfile', - type: 'boolean' - }, - { - name: 'links', - type: 'object' - }, - { - name: 'sortorder', - delete: true - } - ] - }, - { - name: 'notification_components' - }, - { - name: 'notification_sites' - }, - { - name: 'notifications_triggered' - }, - { - name: 'shared_files' - }, - { - name: 'sites', - fields: [ - { - name: 'siteurl', - newName: 'siteUrl' - }, - { - name: 'infos', - newName: 'info', - type: 'object' - }, - { - name: 'privatetoken', - newName: 'privateToken' - }, - { - name: 'config', - type: 'object' - }, - { - name: 'loggedout', - newName: 'loggedOut' - } - ] - }, - ]; - - /** - * Tables to migrate from each site DB. Include all the core ones to decrease the dependencies. - */ - protected siteDBTables: CoreUpdateManagerMigrateTable[] = [ - { - name: 'check_updates_times', - fields: [ - { - name: 'courseid', - newName: 'courseId' - } - ] - }, - { - name: 'course_status', - fields: [ - { - name: 'previous', - newName: 'previousStatus' - }, - { - name: 'downloadtime', - newName: 'downloadTime' - }, - { - name: 'previousdownloadtime', - newName: 'previousDownloadTime' - } - ] - }, - { - name: 'filepool', - newName: 'filepool_files', - fields: [ - { - name: 'stale', - type: 'boolean' - }, - { - name: 'downloaded', - newName: 'downloadTime' - }, - { - name: 'isexternalfile', - type: 'boolean' - } - ] - }, - { - name: 'files_links', - newName: 'filepool_files_links', - fields: [ - { - name: 'componentAndId', - delete: true - } - ] - }, - { - name: 'filepool_packages', - fields: [ - { - name: 'downloadtime', - newName: 'downloadTime' - }, - { - name: 'previousdownloadtime', - newName: 'previousDownloadTime' - }, - { - name: 'revision', // Move the value of 'revision' to 'extra' so SCORMs keep working. - newName: 'extra' - }, - { - name: 'timemodified', - delete: true - } - ] - }, - { - name: 'mm_emulator_last_received_notification', - newName: 'core_emulator_last_received_notification', - filterFields: ['component', 'id', 'timecreated'] - }, - { - name: 'questions', - fields: [ - { - name: 'componentId', - newName: 'componentid' - }, - { - name: 'componentAndAttempt', - delete: true - }, - { - name: 'componentAndComponentId', - delete: true - } - ] - }, - { - name: 'question_answers', - fields: [ - { - name: 'componentId', - newName: 'componentid' - }, - { - name: 'componentAndAttempt', - delete: true - }, - { - name: 'componentAndComponentId', - delete: true - }, - { - name: 'componentAndAttemptAndQuestion', - delete: true - } - ] - }, - { - name: 'sync' - }, - { - name: 'users' - }, - { - name: 'wscache', - fields: [ - { - name: 'data', - type: 'object' - }, - { - name: 'expirationtime', - newName: 'expirationTime' - } - ] - } - ]; - - constructor(logger: CoreLoggerProvider, private configProvider: CoreConfigProvider, private sitesProvider: CoreSitesProvider, - private filepoolProvider: CoreFilepoolProvider, private notifProvider: CoreLocalNotificationsProvider, - private utils: CoreUtilsProvider, private appProvider: CoreAppProvider, private timeUtils: CoreTimeUtilsProvider, - private calendarProvider: AddonCalendarProvider) { + constructor(logger: CoreLoggerProvider, private configProvider: CoreConfigProvider) { this.logger = logger.getInstance('CoreUpdateManagerProvider'); } @@ -330,36 +47,9 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { const promises = [], versionCode = CoreConfigConstants.versioncode; - return this.configProvider.get(this.VERSION_APPLIED, 0).then((versionApplied) => { - if (!versionApplied) { - // No version applied, either the app was just installed or it's being updated from Ionic 1. - return this.migrateAllDBs().then(() => { - // Now that the DBs have been migrated, migrate the local notification components names. - return this.migrateLocalNotificationsComponents(); - }).then(() => { - // DBs migrated, get the version applied again. - return this.configProvider.get(this.VERSION_APPLIED, 0); - }); - } else { - return versionApplied; - } - }).then((versionApplied: number) => { + return this.configProvider.get(this.VERSION_APPLIED, 0).then((versionApplied: number) => { - if (versionCode >= 2013 && versionApplied < 2013 && versionApplied > 0) { - promises.push(this.migrateFileExtensions()); - } - - if (versionCode >= 2017 && versionApplied < 2017 && versionApplied > 0) { - promises.push(this.setCalendarDefaultNotifTime()); - promises.push(this.setSitesConfig()); - } - - // In version 2018 we adapted the forum offline stores to match a new schema. - // However, due to the migration of data to SQLite we can no longer do that. - - if (versionCode >= 3500 && versionApplied < 3500 && versionApplied > 0) { - promises.push(this.logoutLegacySites()); - } + // Put here the code to treat app updates. return Promise.all(promises).then(() => { return this.configProvider.set(this.VERSION_APPLIED, versionCode); @@ -368,345 +58,4 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { }); }); } - - /** - * Register several app tables to be migrated to the new schema. - * - * @param tables The tables to migrate. - */ - registerAppTablesMigration(tables: CoreUpdateManagerMigrateTable[]): void { - tables.forEach((table) => { - this.registerAppTableMigration(table); - }); - } - - /** - * Register an app table to be migrated to the new schema. - * - * @param table The table to migrate. - */ - registerAppTableMigration(table: CoreUpdateManagerMigrateTable): void { - this.appDBTables.push(table); - } - - /** - * Register several site tables to be migrated to the new schema. - * - * @param tables The tables to migrate. - */ - registerSiteTablesMigration(tables: CoreUpdateManagerMigrateTable[]): void { - tables.forEach((table) => { - this.registerSiteTableMigration(table); - }); - } - - /** - * Register a site table to be migrated to the new schema. - * - * @param table The table to migrate. - */ - registerSiteTableMigration(table: CoreUpdateManagerMigrateTable): void { - this.siteDBTables.push(table); - } - - /** - * Register a migration of component name for local notifications. - * - * @param oldName The old name. - * @param newName The new name. - */ - registerLocalNotifComponentMigration(oldName: string, newName: string): void { - this.localNotificationsComponentsMigrate[oldName] = newName; - } - - /** - * Migrate all DBs and tables from the old format to SQLite. - * - * @return Promise resolved when done. - */ - protected migrateAllDBs(): Promise { - if (!( window).ydn) { - // The ydn-db library is not loaded, stop. - return Promise.resolve(); - } - - // First migrate the app DB. - return this.migrateAppDB().then(() => { - // Now migrate all site DBs. - return this.sitesProvider.getSitesIds(); - }).then((ids) => { - const promises = []; - - ids.forEach((id) => { - promises.push(this.migrateSiteDB(id)); - }); - - return this.utils.allPromises(promises); - }); - } - - /** - * Migrate the app DB. - * - * @return Promise resolved when done. - */ - protected migrateAppDB(): Promise { - const oldDb = new ( window).ydn.db.Storage('MoodleMobile'), - newDb = this.appProvider.getDB(); - - return this.migrateDB(oldDb, newDb, this.appDBTables); - } - - /** - * Migrate the DB of a certain site. - * - * @param siteId The site ID. - * @return Promise resolved when done. - */ - protected migrateSiteDB(siteId: string): Promise { - // Get the site DB. - return this.sitesProvider.getSiteDb(siteId).then((newDb) => { - const oldDb = new ( window).ydn.db.Storage('Site-' + siteId); - - return this.migrateDB(oldDb, newDb, this.siteDBTables); - }); - } - - /** - * Migrate all the tables of a certain DB to the SQLite DB. - * - * @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) { - // Some of the DBs doesn't exist, stop. - return Promise.resolve(); - } - - const promises = []; - - tables.forEach((table) => { - - // Get current values. - promises.push(Promise.resolve(oldDb.values(table.name, undefined, 99999999)).then((entries) => { - const fields = table.fields || [], - filterFields = table.filterFields || []; - - // Treat the entries. - for (let i = 0; i < entries.length; i++) { - const entry = entries[i]; - - // Convert and rename the fields to match the new schema. - fields.forEach((field) => { - const value = entry[field.name]; - - // Convert the field to the right format. - if (field.type == 'object' || (field.type == 'any' && typeof value == 'object')) { - entry[field.name] = JSON.stringify(value); - } else if (field.type == 'date' && value) { - entry[field.name] = value.getTime(); - } else if (field.type == 'boolean' || (field.type == 'any' && typeof value == 'boolean')) { - entry[field.name] = value ? 1 : 0; - } - - if (field.newName) { - // Rename the field. - entry[field.newName] = entry[field.name]; - delete entry[field.name]; - } - - if (field.delete) { - // Delete the field. - delete entry[field.name]; - } - }); - - // Remove invalid and unneeded properties. - for (const name in entry) { - if (name.indexOf('$') === 0) { - // Property not valid, remove. - delete entry[name]; - - } else if (filterFields.length && filterFields.indexOf(name) == -1) { - // The property isn't present in filterFields, remove it. - delete entry[name]; - } - } - } - - // Now store the entries in the new DB. - return newDb.insertRecords(table.newName || table.name, entries); - }).catch((error) => { - this.logger.error('Error migrating table ' + table.name + ' to ' + (table.newName || table.name) + ': ', error); - })); - }); - - return this.utils.allPromises(promises); - } - - /** - * Migrates files filling extensions. - * - * @return Promise resolved when the site migration is finished. - */ - protected migrateFileExtensions(): Promise { - return this.sitesProvider.getSitesIds().then((sites) => { - const promises = []; - sites.forEach((siteId) => { - promises.push(this.filepoolProvider.fillMissingExtensionInFiles(siteId)); - }); - promises.push(this.filepoolProvider.treatExtensionInQueue()); - - return Promise.all(promises); - }); - } - - /** - * Migrate local notifications components from the old nomenclature to the new one. - * - * @return Promise resolved when done. - */ - protected migrateLocalNotificationsComponents(): Promise { - if (!this.notifProvider.isAvailable()) { - // Local notifications not available, nothing to do. - return Promise.resolve(); - } - - const promises = []; - - for (const oldName in this.localNotificationsComponentsMigrate) { - const newName = this.localNotificationsComponentsMigrate[oldName]; - - promises.push(this.notifProvider.updateComponentName(oldName, newName).catch((error) => { - this.logger.error('Error migrating local notif component from ' + oldName + ' to ' + newName + ': ', error); - })); - } - - return Promise.all(promises); - } - - /** - * 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 resolved when the events are configured. - */ - protected setCalendarDefaultNotifTime(): Promise { - if (!this.notifProvider.isAvailable()) { - // Local notifications not available, nothing to do. - return Promise.resolve(); - } - - const now = this.timeUtils.timestamp(); - - return this.sitesProvider.getSitesIds().then((siteIds) => { - - const promises = []; - siteIds.forEach((siteId) => { - // Get stored events. - promises.push(this.calendarProvider.getAllEventsFromLocalDb(siteId).then((events) => { - const eventPromises = []; - - events.forEach((event) => { - if (event.notificationtime && event.notificationtime == AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME) { - eventPromises.push(this.calendarProvider.addEventReminder(event, -1, siteId)); - } else if (event.notificationtime && event.notificationtime > 0) { - const time = event.timestart - event.notificationtime * 60; - - if (time < now) { - // Old reminder, just not add this. - return; - } - eventPromises.push(this.calendarProvider.addEventReminder(event, time, siteId)); - } - }); - - return Promise.all(eventPromises); - })); - }); - - return Promise.all(promises); - }); - } - - /** - * 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 resolved when the config is loaded for the current site (if any). - */ - protected setSitesConfig(): Promise { - return this.sitesProvider.getSitesIds().then((siteIds) => { - - return this.sitesProvider.getStoredCurrentSiteId().catch(() => { - // Error getting current site. - }).then((currentSiteId) => { - let promise; - - // Load the config of current site first. - if (currentSiteId) { - promise = this.setSiteConfig(currentSiteId); - } else { - promise = Promise.resolve(); - } - - // Load the config of rest of sites in background. - siteIds.forEach((siteId) => { - if (siteId != currentSiteId) { - this.setSiteConfig(siteId); - } - }); - - return promise; - }); - }); - } - - /** - * Store the config of a 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) => { - if (site.getStoredConfig() || !site.wsAvailable('tool_mobile_get_config')) { - // Site already has the config or it cannot be retrieved. Stop. - return; - } - - // Get the site config. - return site.getConfig().then((config) => { - return this.sitesProvider.addSite( - site.getId(), site.getURL(), site.getToken(), site.getInfo(), site.getPrivateToken(), config); - }).catch(() => { - // Ignore errors. - }); - }); - } - - /** - * Logout from legacy sites. - * - * @return Promise resolved when done. - */ - protected logoutLegacySites(): Promise { - return this.sitesProvider.getSitesIds().then((siteIds) => { - const promises = []; - - siteIds.forEach((siteId) => { - promises.push(this.sitesProvider.getSite(siteId).then((site) => { - // If the site is a legacy site, mark it as logged out so the user has to authenticate again. - if (this.sitesProvider.isLegacyMoodleByInfo(site.getInfo())) { - return this.sitesProvider.setSiteLoggedOut(site.getId(), true); - } - })); - }); - - return this.utils.allPromises(promises); - }); - } } diff --git a/src/providers/urlschemes.ts b/src/providers/urlschemes.ts index 9b8fe5e76..b7ed851c3 100644 --- a/src/providers/urlschemes.ts +++ b/src/providers/urlschemes.ts @@ -22,7 +22,7 @@ import { CoreDomUtilsProvider } from './utils/dom'; import { CoreTextUtilsProvider } from './utils/text'; import { CoreUrlUtilsProvider } from './utils/url'; import { CoreUtilsProvider } from './utils/utils'; -import { CoreLoginHelperProvider } from '@core/login/providers/helper'; +import { CoreLoginHelperProvider, CoreLoginSSOData } from '@core/login/providers/helper'; import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate'; import { CoreSitePluginsProvider } from '@core/siteplugins/providers/siteplugins'; @@ -32,21 +32,7 @@ import { CoreConstants } from '@core/constants'; /** * All params that can be in a custom URL scheme. */ -export interface CoreCustomURLSchemesParams { - /** - * The site's URL. - */ - siteUrl: string; - - /** - * User's token. If set, user will be authenticated. - */ - token?: string; - - /** - * User's private token. - */ - privateToken?: string; +export interface CoreCustomURLSchemesParams extends CoreLoginSSOData { /** * Username. @@ -57,16 +43,6 @@ export interface CoreCustomURLSchemesParams { * URL to open once authenticated. */ redirect?: any; - - /** - * Name of the page to go once authenticated. - */ - pageName?: string; - - /** - * Params to pass to the page. - */ - pageParams?: any; } /* @@ -162,7 +138,8 @@ export class CoreCustomURLSchemesProvider { } return promise.then(() => { - return this.sitesProvider.newSite(data.siteUrl, data.token, data.privateToken, isSSOToken); + return this.sitesProvider.newSite(data.siteUrl, data.token, data.privateToken, isSSOToken, + this.loginHelper.getOAuthIdFromParams(data.ssoUrlParams)); }); } else { // Token belongs to current site, no need to create it. diff --git a/src/providers/utils/dom.ts b/src/providers/utils/dom.ts index 91e4b7ad1..35055cbb1 100644 --- a/src/providers/utils/dom.ts +++ b/src/providers/utils/dom.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Injectable, SimpleChange } from '@angular/core'; +import { Injectable, SimpleChange, ElementRef } from '@angular/core'; import { LoadingController, Loading, ToastController, Toast, AlertController, Alert, Platform, Content, PopoverController, ModalController, @@ -22,6 +22,7 @@ import { TranslateService } from '@ngx-translate/core'; import { CoreTextUtilsProvider } from './text'; import { CoreAppProvider } from '../app'; import { CoreConfigProvider } from '../config'; +import { CoreEventsProvider } from '../events'; import { CoreLoggerProvider } from '../logger'; import { CoreUrlUtilsProvider } from './url'; import { CoreFileProvider } from '@providers/file'; @@ -64,20 +65,21 @@ export class CoreDomUtilsProvider { protected displayedAlerts = {}; // To prevent duplicated alerts. protected logger; - constructor(private translate: TranslateService, - private loadingCtrl: LoadingController, - private toastCtrl: ToastController, - private alertCtrl: AlertController, - private textUtils: CoreTextUtilsProvider, - private appProvider: CoreAppProvider, - private platform: Platform, - private configProvider: CoreConfigProvider, - private urlUtils: CoreUrlUtilsProvider, - private modalCtrl: ModalController, - private sanitizer: DomSanitizer, - private popoverCtrl: PopoverController, - private fileProvider: CoreFileProvider, - loggerProvider: CoreLoggerProvider) { + constructor(protected translate: TranslateService, + protected loadingCtrl: LoadingController, + protected toastCtrl: ToastController, + protected alertCtrl: AlertController, + protected textUtils: CoreTextUtilsProvider, + protected appProvider: CoreAppProvider, + protected platform: Platform, + protected configProvider: CoreConfigProvider, + protected urlUtils: CoreUrlUtilsProvider, + protected modalCtrl: ModalController, + protected sanitizer: DomSanitizer, + protected popoverCtrl: PopoverController, + protected fileProvider: CoreFileProvider, + loggerProvider: CoreLoggerProvider, + protected eventsProvider: CoreEventsProvider) { this.logger = loggerProvider.getInstance('CoreDomUtilsProvider'); @@ -1054,13 +1056,14 @@ export class CoreDomUtilsProvider { /** * Returns scrollTop of the content. - * Checks hidden property _scroll to avoid errors if view is not active. + * Checks hidden property _scrollContent to avoid errors if view is not active. + * Using navite value of scroll to avoid having non updated values. * * @param content Content where to execute the function. * @return Content scrollTop or 0. */ getScrollTop(content: Content): number { - return (content && content._scroll && content.scrollTop) || 0; + return (content && content._scrollContent && content._scrollContent.nativeElement.scrollTop) || 0; } /** @@ -1625,4 +1628,40 @@ export class CoreDomUtilsProvider { // Now move the element into the wrapper. wrapper.appendChild(el); } + + /** + * Trigger form cancelled event. + * + * @param form Form element. + * @param siteId The site affected. If not provided, no site affected. + */ + triggerFormCancelledEvent(formRef: ElementRef, siteId?: string): void { + if (!formRef) { + return; + } + + this.eventsProvider.trigger(CoreEventsProvider.FORM_ACTION, { + action: 'cancel', + form: formRef.nativeElement, + }, siteId); + } + + /** + * Trigger form submitted event. + * + * @param form Form element. + * @param online Whether the action was done in offline or not. + * @param siteId The site affected. If not provided, no site affected. + */ + triggerFormSubmittedEvent(formRef: ElementRef, online?: boolean, siteId?: string): void { + if (!formRef) { + return; + } + + this.eventsProvider.trigger(CoreEventsProvider.FORM_ACTION, { + action: 'submit', + form: formRef.nativeElement, + online: !!online, + }, siteId); + } } diff --git a/src/providers/utils/text.ts b/src/providers/utils/text.ts index 0f1202161..8565b6dfc 100644 --- a/src/providers/utils/text.ts +++ b/src/providers/utils/text.ts @@ -18,6 +18,16 @@ import { ModalController, Platform } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; import { CoreLangProvider } from '../lang'; +/** + * Different type of errors the app can treat. + */ +export type CoreTextErrorObject = { + message?: string; + error?: string; + content?: string; + body?: string; +}; + /* * "Utils" service with helper functions for text. */ @@ -122,6 +132,38 @@ export class CoreTextUtilsProvider { return result; } + /** + * Build a message with several paragraphs. + * + * @param paragraphs List of paragraphs. + * @return Built message. + */ + buildSeveralParagraphsMessage(paragraphs: (string | CoreTextErrorObject)[]): string { + // Filter invalid messages, and convert them to messages in case they're errors. + const messages: string[] = []; + + paragraphs.forEach((paragraph) => { + // If it's an error, get its message. + const message = this.getErrorMessageFromError(paragraph); + + if (paragraph) { + messages.push(message); + } + }); + + if (messages.length < 2) { + return messages[0] || ''; + } + + let builtMessage = messages[0]; + + for (let i = 1; i < messages.length; i++) { + builtMessage = this.translate.instant('core.twoparagraphs', { p1: builtMessage, p2: messages[i] }); + } + + return builtMessage; + } + /** * Convert size in bytes into human readable format * @@ -449,7 +491,7 @@ export class CoreTextUtilsProvider { * @param error Error object. * @return Error message, undefined if not found. */ - getErrorMessageFromError(error: any): string { + getErrorMessageFromError(error: string | CoreTextErrorObject): string { if (typeof error == 'string') { return error; } diff --git a/src/providers/utils/url.ts b/src/providers/utils/url.ts index ec233b1c0..5c8c0e35a 100644 --- a/src/providers/utils/url.ts +++ b/src/providers/utils/url.ts @@ -469,4 +469,32 @@ export class CoreUrlUtilsProvider { return matches && matches[0]; } + + /** + * Modifies a pluginfile URL to use the default pluginfile script instead of the webservice one. + * + * @param url The url to be fixed. + * @param siteUrl The URL of the site the URL belongs to. + * @return Modified URL. + */ + unfixPluginfileURL(url: string, siteUrl?: string): string { + if (!url) { + return ''; + } + + url = url.replace(/&/g, '&'); + + // It site URL is supplied, check if the URL belongs to the site. + if (siteUrl && url.indexOf(this.textUtils.addEndingSlash(siteUrl)) !== 0) { + return url; + } + + // Not a pluginfile URL. Treat webservice/pluginfile case. + url = url.replace(/\/webservice\/pluginfile\.php\//, '/pluginfile.php/'); + + // Make sure the URL doesn't contain the token. + url.replace(/([?&])token=[^&]*&?/, '$1'); + + return url; + } } diff --git a/src/providers/utils/utils.ts b/src/providers/utils/utils.ts index 624371ac1..bedd0e4b0 100644 --- a/src/providers/utils/utils.ts +++ b/src/providers/utils/utils.ts @@ -1105,7 +1105,7 @@ export class CoreUtilsProvider { * @param keyPrefix Key prefix if neededs to delete it. * @return Object. */ - objectToKeyValueMap(objects: object[], keyName: string, valueName: string, keyPrefix?: string): object { + objectToKeyValueMap(objects: object[], keyName: string, valueName: string, keyPrefix?: string): {[name: string]: any} { if (!objects) { return; } diff --git a/src/providers/ws.ts b/src/providers/ws.ts index cf8aa1ef8..c13af220f 100644 --- a/src/providers/ws.ts +++ b/src/providers/ws.ts @@ -13,8 +13,7 @@ // limitations under the License. import { Injectable } from '@angular/core'; -import { Http } from '@angular/http'; -import { HttpClient } from '@angular/common/http'; +import { HttpClient, HttpResponse } from '@angular/common/http'; import { TranslateService } from '@ngx-translate/core'; import { FileTransfer, FileUploadOptions } from '@ionic-native/file-transfer'; import { CoreAppProvider } from './app'; @@ -92,10 +91,14 @@ export class CoreWSProvider { protected retryCalls = []; protected retryTimeout = 0; - constructor(private http: HttpClient, private translate: TranslateService, private appProvider: CoreAppProvider, - private textUtils: CoreTextUtilsProvider, logger: CoreLoggerProvider, - private fileProvider: CoreFileProvider, private fileTransfer: FileTransfer, private commonHttp: Http, - private mimeUtils: CoreMimetypeUtilsProvider) { + constructor(protected http: HttpClient, + protected translate: TranslateService, + protected appProvider: CoreAppProvider, + protected textUtils: CoreTextUtilsProvider, + protected fileProvider: CoreFileProvider, + protected fileTransfer: FileTransfer, + protected mimeUtils: CoreMimetypeUtilsProvider, + logger: CoreLoggerProvider) { this.logger = logger.getInstance('CoreWSProvider'); } @@ -499,11 +502,12 @@ export class CoreWSProvider { * @param url URL to perform the request. * @return Promise resolved with the response. */ - performHead(url: string): Promise { + performHead(url: string): Promise> { let promise = this.getPromiseHttp('head', url); if (!promise) { - promise = this.commonHttp.head(url).timeout(this.getRequestTimeout()).toPromise(); + promise = this.http.head(url, {observe: 'response', responseType: 'blob'}).timeout(this.getRequestTimeout()) + .toPromise(); promise = this.setPromiseHttp(promise, 'head', url); } @@ -828,6 +832,22 @@ export class CoreWSProvider { return Promise.reject(this.translate.instant('core.errorinvalidresponse')); }); } + + /** + * Perform an HTTP request requesting for a text response. + * + * @param url Url to get. + * @return Resolved with the text when done. + */ + async getText(url: string): Promise { + // Fetch the URL content. + const content = await this.http.get(url, { responseType: 'text' }).toPromise(); + if (typeof content !== 'string') { + return Promise.reject(null); + } + + return content; + } } /** diff --git a/src/theme/dark.scss b/src/theme/dark.scss index 01c373d06..7e065f077 100644 --- a/src/theme/dark.scss +++ b/src/theme/dark.scss @@ -16,109 +16,123 @@ $core-dark-login-item-background-color: $core-dark-login-box-background-color !d $core-dark-login-button-outline: $core-login-button-outline !default; $core-dark-login-loading-color: $core-dark-text-color !default; -ion-app.app-root .ion-page { +ion-app.app-root { @include darkmode() { - color: $core-dark-text-color; - background-color: $core-dark-item-bg-color; + ion-action-sheet .action-sheet-container .action-sheet-group .action-sheet-button { + color: $core-dark-text-color; - a:not(.button) { - color: $core-dark-link-color; + &.action-sheet-selected { + color: $core-color; + } + &.activated { + background-color: $black; + } } - .core-tabs-bar, - .core-tabs-bar *, - .core-tabs-bar .tab-slide, .ion-page, - .item, - .item-select, - ion-card, - .card-header, - .card-content { - color: $core-dark-text-color; - background-color: $core-dark-item-bg-color; + ion-popover .popover-content, + ion-alert .alert-wrapper, + ion-action-sheet .action-sheet-container .action-sheet-group, + ion-loading .loading-wrapper { + color: $core-dark-text-color; + background-color: $core-dark-item-bg-color; - h1, h2, h3, h4, h5, h6, - ion-icon, - .label { + a:not(.button) { + color: $core-dark-link-color; + } + + .alert-message { + color: $core-dark-text-color; + } + + .core-tabs-bar, + .core-tabs-bar *, + .core-tabs-bar .tab-slide, + .ion-page, + .item, + .item-select, + ion-card, + .card-header, + .card-content { + color: $core-dark-text-color; + background-color: $core-dark-item-bg-color; + + h1, h2, h3, h4, h5, h6, + ion-icon, + .label { + color: $core-dark-text-color; + } + + @each $color-name, $color-base, $color-contrast in get-colors($colors-dark) { + .icon-md-#{$color-name}, + .icon-ios-#{$color-name} { + color: $color-base; + } + } + p { + color: $core-dark-text-secondary-color; + } + } + + .item-divider, + .item-divider .item-inner { + color: $core-dark-text-color; + background-color: $core-dark-item-divider-bg-color; + } + + .item.activated { + &.item-ios, + &.item-md { + background-color: $core-dark-background-color; + } + } + + .content, + .content-md, + .content-ios { + color: $core-dark-text-color; + background-color: $core-dark-background-color; + } + + .button, + .button-md-light, + .button-ios-light, + .button-outline { + ion-icon { + color: inherit; + } + } + + .toolbar-md-light .toolbar-background, + .toolbar-ios-light .toolbar-background { + background-color: $core-dark-item-divider-bg-color; + color: $core-dark-text-color; + } + + .button.button-clear-md-dark, + .button.button-clear-ios-dark { + .button-inner ion-icon { color: $core-dark-text-color; } + } - @each $color-name, $color-base, $color-contrast in get-colors($colors-dark) { - .icon-md-#{$color-name}, - .icon-ios-#{$color-name}, - .icon-wp-#{$color-name} { - color: $color-base; - } + .button-outline { + background-color: $core-dark-item-bg-color; + } + + ion-refresher { + .refresher-pulling-icon, + .refresher-refreshing-icon, + .refresher-pulling-icon ion-icon, + .refresher-refreshing-icon ion-icon, + ion-icon { + color: $refresher-icon-color; } - p { - color: $core-dark-text-secondary-color; + + .refresher-pulling-text, + .refresher-refreshing-text { + color: $core-dark-text-color; } - } - - .item-divider, - .item-divider .item-inner { - color: $core-dark-text-color; - background-color: $core-dark-item-divider-bg-color; - } - - .item.item-ios:active, - .item.item-ios.activated, - .item.item-md:active, - .item.item-md.activated, - .item.item-wp:active, - .item.item-wp.activated { - background-color: $core-dark-background-color; - } - - .content, - .content-md, - .content-ios, - .content-wp { - color: $core-dark-text-color; - background-color: $core-dark-background-color; - } - - .button, - .button-md-light, - .button-ios-light, - .button-wp-light, - .button-outline { - ion-icon { - color: inherit; - } - } - - .toolbar-md-light .toolbar-background, - .toolbar-ios-light .toolbar-background, - .toolbar-wp-light .toolbar-background { - background-color: $core-dark-item-divider-bg-color; - color: $core-dark-text-color; - } - - .button.button-clear-md-dark, - .button.button-clear-ios-dark, - .button.button-clear-wp-dark { - .button-inner ion-icon { - color: $core-dark-text-color; - } - } - - .button-outline { - background-color: $core-dark-item-bg-color; - } - - ion-refresher { - .refresher-pulling-icon, - .refresher-refreshing-icon, - .refresher-pulling-icon ion-icon, - .refresher-refreshing-icon ion-icon, - ion-icon { - color: $refresher-icon-color; - } - - .refresher-pulling-text, - .refresher-refreshing-text { - color: $core-dark-text-color; } } } diff --git a/src/theme/format-text.scss b/src/theme/format-text.scss index 8365e7363..6bd43740c 100644 --- a/src/theme/format-text.scss +++ b/src/theme/format-text.scss @@ -1,4 +1,12 @@ /** Styles to match web platform */ +ion-app.app-root core-format-text { + ul { + padding-left: 1rem; + } + ul, ol { + -webkit-padding-start: 15px; + } +} ion-app.app-root core-format-text, ion-app.app-root .item core-format-text, ion-app.app-root core-rich-text-editor .core-rte-editor { @@ -14,14 +22,10 @@ ion-app.app-root core-rich-text-editor .core-rte-editor { } // Fix lists styles in core-format-text. - ul { - padding-left: 1rem; - } ol { list-style-type: decimal; } ul, ol { - -webkit-padding-start: 15px; ol { list-style-type: lower-latin; } @@ -173,6 +177,32 @@ ion-app.app-root core-rich-text-editor .core-rte-editor { color: $color-base; } } + + .label { + display: inline-block; + padding: .25em .4em; + font-size: 75%; + font-weight: 700; + line-height: 1; + text-align: center; + white-space: nowrap; + vertical-align: baseline; + transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out; + color: $white; + background-color: $gray-darker; + } + + .label-important { + color: $white; + background-color: $red; + } + + @each $color-name, $color-base, $color-contrast in get-colors($colors) { + .label-#{$color-name} { + color: $color-contrast; + background-color: $color-base; + } + } } // Those styles are omitted on RTE. diff --git a/src/theme/variables.scss b/src/theme/variables.scss index 8688e1339..15002db6f 100644 --- a/src/theme/variables.scss +++ b/src/theme/variables.scss @@ -134,11 +134,6 @@ $link-color: $blue !default; $background-color: $gray-light !default; $subdued-text-color: $gray-darker !default; -$core-warning-color: colors($colors, warning) !default; // yellow. -$core-success-color: colors($colors, success) !default; // green. -$core-info-color: colors($colors, info) !default; // / blue. -$core-error-color: colors($colors, alert) !default; // Red. - $list-background-color: $white !default; $tabs-background: $gray-darker !default; @@ -162,7 +157,6 @@ $alert-input-placeholder-color: $core-placeholder-color !default; $core-datetime-ios-placeholder-color: $core-placeholder-color !default; $searchbar-ios-input-placeholder-color: $core-placeholder-color !default; $searchbar-md-input-placeholder-color: $core-placeholder-color !default; -$searchbar-wp-input-placeholder-color: $core-placeholder-color !default; $text-input-placeholder-color: $core-placeholder-color !default; $core-select-color: $core-color !default; @@ -202,6 +196,7 @@ $core-login-item-background-color: $white !default; $core-action-sheet-color: $core-color !default; $core-action-sheet-cancel-color: $danger !default; +$core-dark-action-sheet-cancel-color: $danger-dark !default; // App iOS Variables // -------------------------------------------------- @@ -253,29 +248,6 @@ $action-sheet-md-button-text-color: $black !default; $item-md-divider-background: $item-divider-background; $item-md-divider-color: $item-divider-color; -// App Windows Variables -// -------------------------------------------------- -// Windows only Sass variables can go here - -$text-input-wp-show-focus-highlight: true; -$item-wp-detail-push-show: true; -$item-wp-avatar-size: $item-avatar-size; -$loading-wp-spinner-color: $core-loading-spinner-color; -$spinner-wp-circles-color: $core-spinner-color; -$tabs-wp-tab-color-inactive: $tabs-tab-color-inactive; -$button-wp-outline-background-color: $core-button-outline-background-color; -$select-wp-placeholder-color: $core-select-color !default; -$datetime-wp-placeholder-color: $core-datetime-ios-placeholder-color !default; -$label-wp-text-color: $text-color !default; -$radio-wp-disabled-opacity: $input-select-opacity !default; -$checkbox-wp-disabled-opacity: $input-select-opacity !default; -$toggle-wp-disabled-opacity: $input-select-opacity !default; -$note-wp-color: $note-color; -$popover-wp-width: $popover-width; -$action-sheet-wp-title-color: $core-action-sheet-color; -$action-sheet-wp-button-text-color: $black !default; -$item-wp-divider-background: $item-divider-background; -$item-wp-divider-color: $item-divider-color; // Font sizes // --------------------------------------------------- @@ -495,10 +467,6 @@ $core-dd-question-colors: $white, $blue-light, #DCDCDC, #D8BFD8, #87CEFA, #DAA52 height: calc(100% - #{($card-ios-margin-bottom + $card-ios-margin-top)}); width: calc(100% - #{($card-md-margin-end + $card-md-margin-start)}); } - &.card-wp, .card-wp { - height: calc(100% - #{($card-wp-margin-bottom + $card-wp-margin-top)}); - width: calc(100% - #{($card-md-margin-end + $card-md-margin-start)}); - } &.card-md { margin-top: $card-md-margin-top; margin-bottom: $card-md-margin-bottom; @@ -507,10 +475,6 @@ $core-dd-question-colors: $white, $blue-light, #DCDCDC, #D8BFD8, #87CEFA, #DAA52 margin-top: $card-ios-margin-top; margin-bottom: $card-ios-margin-bottom; } - &.card-wp { - margin-top: $card-wp-margin-top; - margin-bottom: $card-wp-margin-bottom; - } &.card, .card { @media (max-width: 360px) { @@ -559,10 +523,6 @@ $core-dd-question-colors: $white, $blue-light, #DCDCDC, #D8BFD8, #87CEFA, #DAA52 border-bottom: $hairlines-width solid $list-ios-border-color; } - .item-wp.item-block > .item-inner { - border-bottom: 1px solid $list-wp-border-color; - } - &:last-child .item > .item-inner { border-bottom: 0; } @@ -577,10 +537,6 @@ $core-dd-question-colors: $white, $blue-light, #DCDCDC, #D8BFD8, #87CEFA, #DAA52 border-bottom: $hairlines-width solid $list-ios-border-color; } - &.item-wp.item-block > .item-inner { - border-bottom: 1px solid $list-wp-border-color; - } - &:last-child > .item-inner { border-bottom: 0; } @@ -595,9 +551,6 @@ $core-dd-question-colors: $white, $blue-light, #DCDCDC, #D8BFD8, #87CEFA, #DAA52 &.item-ios { @include padding-horizontal($item-ios-padding-start - 5px, null); } - &.item-wp { - @include padding-horizontal($item-wp-padding-start - 5px, null); - } }