From ac37a875de4a81743151ca1fde21bfa2e6a1051a Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 15 Jan 2019 14:45:30 +0100 Subject: [PATCH 001/191] MOBILE-2830 desktop: Fix add platform class name --- src/app/app.component.ts | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/app/app.component.ts b/src/app/app.component.ts index f368628ec..dec433beb 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -51,11 +51,17 @@ export class MoodleMobileApp implements OnInit { keyboard.hideFormAccessoryBar(false); - let desktopClass = this.appProvider.isDesktop() ? 'platform-desktop' : ''; - desktopClass += this.appProvider.isMac() ? ' platform-mac' : ''; - desktopClass += this.appProvider.isLinux() ? ' platform-linux' : ''; - desktopClass += this.appProvider.isWindows() ? ' platform-windows' : ''; - desktopClass != '' ? app.setElementClass(desktopClass, true) : false; + if (this.appProvider.isDesktop()) { + app.setElementClass('platform-desktop', true); + + if (this.appProvider.isMac()) { + app.setElementClass('platform-mac', true); + } else if (this.appProvider.isLinux()) { + app.setElementClass('platform-linux', true); + } else if (this.appProvider.isWindows()) { + app.setElementClass('platform-windows', true); + } + } }); } From 6b32c79cd67e671d4848d16efd83c984be311e8c Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 15 Jan 2019 14:55:31 +0100 Subject: [PATCH 002/191] MOBILE-2830 windows: Fix video subtitles in Windows desktop --- src/app/app.scss | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/app/app.scss b/src/app/app.scss index fa548765c..508268db3 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -968,3 +968,16 @@ ion-modal, .split-pane { contain: size layout style; } + +// Styles for desktop apps only. +ion-app.platform-desktop { + video::-webkit-media-text-track-display { + white-space: normal !important; + } + + &.platform-windows { + video::-webkit-media-text-track-display { + font-size: 0.6em; + } + } +} From 569b3aed5bb1513d329971e409e4f39f563566e1 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 16 Jan 2019 10:49:09 +0100 Subject: [PATCH 003/191] MOBILE-2829 electron: Use requestSingleInstanceLock function --- desktop/electron.js | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/desktop/electron.js b/desktop/electron.js index d4a7bdbfa..c7f1359a9 100644 --- a/desktop/electron.js +++ b/desktop/electron.js @@ -70,6 +70,24 @@ function createWindow() { mainWindow.webContents.setUserAgent(mainWindow.webContents.getUserAgent() + ' ' + userAgent); } +// Make sure that only a single instance of the app is running. +var gotTheLock = app.requestSingleInstanceLock(); + +if (!gotTheLock) { + // It's not the main instance of the app, kill it. + app.exit(); + return; +} + +app.on('second-instance', (event, commandLine, workingDirectory) => { + // Another instance was launched. If it was launched with a URL, it should be in the second param. + if (commandLine && commandLine[1]) { + appLaunched(commandLine[1]); + } else { + focusApp(); + } +}); + // This method will be called when Electron has finished initialization and is ready to create browser windows. // Some APIs can only be used after this event occurs. app.on('ready', function() { @@ -122,23 +140,6 @@ fs.readFile(path.join(__dirname, 'config.json'), 'utf8', (err, data) => { } }); -// Make sure that only a single instance of the app is running. -var shouldQuit = app.makeSingleInstance((argv, workingDirectory) => { - // Another instance was launched. If it was launched with a URL, it should be in the second param. - if (argv && argv[1]) { - appLaunched(argv[1]); - } else { - focusApp(); - } -}); - -// For some reason, shouldQuit is always true in signed Mac apps so we should ingore it. -if (shouldQuit && os.platform().indexOf('darwin') == -1) { - // It's not the main instance of the app, kill it. - app.exit(); - return; -} - // Listen for open-url events (Mac OS only). app.on('open-url', (event, url) => { event.preventDefault(); From 4cbb7d417bdf37d526fef3a84a8f2c8933affde5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 16 Jan 2019 11:02:29 +0100 Subject: [PATCH 004/191] MOBILE-2795 more: Fix more item color css --- src/core/mainmenu/pages/more/more.scss | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/core/mainmenu/pages/more/more.scss b/src/core/mainmenu/pages/more/more.scss index 8bb2930d7..24797f641 100644 --- a/src/core/mainmenu/pages/more/more.scss +++ b/src/core/mainmenu/pages/more/more.scss @@ -20,6 +20,9 @@ ion-app.app-root page-core-mainmenu-more { &.item-ios { background-color: $core-more-background-ios; color: $core-more-color-ios; + p { + color: $core-more-color-ios; + } .item-inner { border-bottom: $hairlines-width solid $core-more-border-ios; @@ -28,6 +31,9 @@ ion-app.app-root page-core-mainmenu-more { &.item-md { background-color: $core-more-background-md; color: $core-more-color-md; + p { + color: $core-more-color-md; + } .item-inner { border-bottom: 1px solid $core-more-border-md; @@ -36,6 +42,9 @@ ion-app.app-root page-core-mainmenu-more { &.item-wp { background-color: $core-more-background-wp; color: $core-more-color-wp; + p { + color: $core-more-color-wp; + } .item-inner { border-bottom: 1px solid $core-more-border-wp; From cef8cfabd30a7b110ca49f28486eedae9d9f7d4e Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 16 Jan 2019 12:31:18 +0100 Subject: [PATCH 005/191] MOBILE-2829 desktop: Update electron version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c5361461b..6116e057b 100644 --- a/package.json +++ b/package.json @@ -206,7 +206,7 @@ } ], "compression": "maximum", - "electronVersion": "2.0.4", + "electronVersion": "4.0.1", "mac": { "category": "public.app-category.education", "icon": "resources/desktop/icon.icns", From 7a79d56843206101cb038f7ee9b94f9340fa275a Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 17 Jan 2019 09:58:27 +0100 Subject: [PATCH 006/191] MOBILE-2829 mac: Ignore the single instance lock in Mac --- desktop/electron.js | 4 +++- src/assets/lang/en.json | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/desktop/electron.js b/desktop/electron.js index c7f1359a9..343f81a5b 100644 --- a/desktop/electron.js +++ b/desktop/electron.js @@ -71,9 +71,11 @@ function createWindow() { } // Make sure that only a single instance of the app is running. +// For some reason, gotTheLock is always false in signed Mac apps so we should ingore it. +// See https://github.com/electron/electron/issues/15958 var gotTheLock = app.requestSingleInstanceLock(); -if (!gotTheLock) { +if (!gotTheLock && os.platform().indexOf('darwin') == -1) { // It's not the main instance of the app, kill it. app.exit(); return; diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index 4029982e6..d44abe8ad 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -1246,6 +1246,7 @@ "core.courses.enrolme": "Enrol me", "core.courses.errorloadcategories": "An error occurred while loading categories.", "core.courses.errorloadcourses": "An error occurred while loading courses.", + "core.courses.errorloadplugins": "The plugins required by this course could not be loaded correctly. Please restart the app to try again.", "core.courses.errorsearching": "An error occurred while searching.", "core.courses.errorselfenrol": "An error occurred while self enrolling.", "core.courses.filtermycourses": "Filter my courses", From 0c738e3aaa43a6f548c2ca208765431636b06545 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Mon, 21 Jan 2019 11:37:59 +0100 Subject: [PATCH 007/191] MOBILE-2836 gulp: Fix lang build in Windows --- gulpfile.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 17f687ba2..c4deb448f 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -47,7 +47,13 @@ function treatFile(file, data) { return; // ignore } try { - var path = file.path.substr(file.path.lastIndexOf('/src/') + 5); + var srcPos = file.path.lastIndexOf('/src/'); + if (srcPos == -1) { + // It's probably a Windows environment. + srcPos = file.path.lastIndexOf('\\src\\'); + } + + var path = file.path.substr(srcPos + 5); data[path] = JSON.parse(file.contents.toString()); } catch (err) { console.log('Error parsing JSON: ' + err); @@ -65,7 +71,7 @@ function treatMergedData(data) { var mergedOrdered = {}; for (var filepath in data) { - var pathSplit = filepath.split('/'), + var pathSplit = filepath.split(/[\/\\]/), prefix; pathSplit.pop(); From dde19b17a60134041da323867b2cba982ddb2811 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 22 Jan 2019 12:20:04 +0100 Subject: [PATCH 008/191] MOBILE-2824 course: Allow passing params when navigate to module --- src/addon/mod/assign/providers/module-handler.ts | 8 ++++++-- src/addon/mod/book/providers/module-handler.ts | 8 ++++++-- src/addon/mod/chat/providers/module-handler.ts | 8 ++++++-- src/addon/mod/choice/providers/module-handler.ts | 8 ++++++-- src/addon/mod/data/providers/module-handler.ts | 8 ++++++-- src/addon/mod/feedback/providers/module-handler.ts | 8 ++++++-- src/addon/mod/folder/providers/module-handler.ts | 8 ++++++-- src/addon/mod/forum/providers/module-handler.ts | 8 ++++++-- src/addon/mod/glossary/providers/module-handler.ts | 8 ++++++-- src/addon/mod/imscp/providers/module-handler.ts | 8 ++++++-- src/addon/mod/lesson/providers/module-handler.ts | 8 ++++++-- src/addon/mod/lti/providers/module-handler.ts | 8 ++++++-- src/addon/mod/page/providers/module-handler.ts | 8 ++++++-- src/addon/mod/quiz/providers/module-handler.ts | 8 ++++++-- src/addon/mod/resource/providers/module-handler.ts | 8 ++++++-- src/addon/mod/scorm/providers/module-handler.ts | 8 ++++++-- src/addon/mod/survey/providers/module-handler.ts | 8 ++++++-- src/addon/mod/url/providers/module-handler.ts | 8 ++++++-- src/addon/mod/wiki/providers/module-handler.ts | 8 ++++++-- src/addon/mod/workshop/providers/module-handler.ts | 8 ++++++-- src/core/course/pages/section/section.ts | 4 +++- src/core/course/providers/helper.ts | 12 ++++++++---- src/core/course/providers/module-delegate.ts | 3 ++- src/core/sitehome/pages/index/index.ts | 6 ++++-- 24 files changed, 137 insertions(+), 48 deletions(-) diff --git a/src/addon/mod/assign/providers/module-handler.ts b/src/addon/mod/assign/providers/module-handler.ts index 937aeeb1d..1aa0cda0d 100644 --- a/src/addon/mod/assign/providers/module-handler.ts +++ b/src/addon/mod/assign/providers/module-handler.ts @@ -68,8 +68,12 @@ export class AddonModAssignModuleHandler implements CoreCourseModuleHandler { title: module.name, class: 'addon-mod_assign-handler', showDownloadButton: true, - action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions): void { - navCtrl.push('AddonModAssignIndexPage', {module: module, courseId: courseId}, options); + action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions, params?: any): void { + const pageParams = {module: module, courseId: courseId}; + if (params) { + Object.assign(pageParams, params); + } + navCtrl.push('AddonModAssignIndexPage', pageParams, options); } }; } diff --git a/src/addon/mod/book/providers/module-handler.ts b/src/addon/mod/book/providers/module-handler.ts index a1b4c9af4..6f92d6f32 100644 --- a/src/addon/mod/book/providers/module-handler.ts +++ b/src/addon/mod/book/providers/module-handler.ts @@ -65,8 +65,12 @@ export class AddonModBookModuleHandler implements CoreCourseModuleHandler { title: module.name, class: 'addon-mod_book-handler', showDownloadButton: true, - action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions): void { - navCtrl.push('AddonModBookIndexPage', {module: module, courseId: courseId}, options); + action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions, params?: any): void { + const pageParams = {module: module, courseId: courseId}; + if (params) { + Object.assign(pageParams, params); + } + navCtrl.push('AddonModBookIndexPage', pageParams, options); } }; } diff --git a/src/addon/mod/chat/providers/module-handler.ts b/src/addon/mod/chat/providers/module-handler.ts index e54d0fda2..4ef57c7b3 100644 --- a/src/addon/mod/chat/providers/module-handler.ts +++ b/src/addon/mod/chat/providers/module-handler.ts @@ -62,8 +62,12 @@ export class AddonModChatModuleHandler implements CoreCourseModuleHandler { icon: this.courseProvider.getModuleIconSrc(this.modName, module.modicon), title: module.name, class: 'addon-mod_chat-handler', - action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions): void { - navCtrl.push('AddonModChatIndexPage', {module: module, courseId: courseId}, options); + action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions, params?: any): void { + const pageParams = {module: module, courseId: courseId}; + if (params) { + Object.assign(pageParams, params); + } + navCtrl.push('AddonModChatIndexPage', pageParams, options); } }; } diff --git a/src/addon/mod/choice/providers/module-handler.ts b/src/addon/mod/choice/providers/module-handler.ts index 163b595a5..cc2448da9 100644 --- a/src/addon/mod/choice/providers/module-handler.ts +++ b/src/addon/mod/choice/providers/module-handler.ts @@ -64,8 +64,12 @@ export class AddonModChoiceModuleHandler implements CoreCourseModuleHandler { title: module.name, class: 'addon-mod_choice-handler', showDownloadButton: true, - action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions): void { - navCtrl.push('AddonModChoiceIndexPage', {module: module, courseId: courseId}, options); + action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions, params?: any): void { + const pageParams = {module: module, courseId: courseId}; + if (params) { + Object.assign(pageParams, params); + } + navCtrl.push('AddonModChoiceIndexPage', pageParams, options); } }; } diff --git a/src/addon/mod/data/providers/module-handler.ts b/src/addon/mod/data/providers/module-handler.ts index 78cecd43b..162a9923b 100644 --- a/src/addon/mod/data/providers/module-handler.ts +++ b/src/addon/mod/data/providers/module-handler.ts @@ -67,8 +67,12 @@ export class AddonModDataModuleHandler implements CoreCourseModuleHandler { title: module.name, class: 'addon-mod_data-handler', showDownloadButton: true, - action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions): void { - navCtrl.push('AddonModDataIndexPage', {module: module, courseId: courseId}, options); + action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions, params?: any): void { + const pageParams = {module: module, courseId: courseId}; + if (params) { + Object.assign(pageParams, params); + } + navCtrl.push('AddonModDataIndexPage', pageParams, options); } }; } diff --git a/src/addon/mod/feedback/providers/module-handler.ts b/src/addon/mod/feedback/providers/module-handler.ts index 8da7e41d9..7744ce1b3 100644 --- a/src/addon/mod/feedback/providers/module-handler.ts +++ b/src/addon/mod/feedback/providers/module-handler.ts @@ -65,8 +65,12 @@ export class AddonModFeedbackModuleHandler implements CoreCourseModuleHandler { title: module.name, class: 'addon-mod_feedback-handler', showDownloadButton: true, - action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions): void { - navCtrl.push('AddonModFeedbackIndexPage', {module: module, courseId: courseId}, options); + action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions, params?: any): void { + const pageParams = {module: module, courseId: courseId}; + if (params) { + Object.assign(pageParams, params); + } + navCtrl.push('AddonModFeedbackIndexPage', pageParams, options); } }; } diff --git a/src/addon/mod/folder/providers/module-handler.ts b/src/addon/mod/folder/providers/module-handler.ts index c17f6f0a2..5d372a12c 100644 --- a/src/addon/mod/folder/providers/module-handler.ts +++ b/src/addon/mod/folder/providers/module-handler.ts @@ -64,8 +64,12 @@ export class AddonModFolderModuleHandler implements CoreCourseModuleHandler { title: module.name, class: 'addon-mod_folder-handler', showDownloadButton: true, - action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions): void { - navCtrl.push('AddonModFolderIndexPage', {module: module, courseId: courseId}, options); + action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions, params?: any): void { + const pageParams = {module: module, courseId: courseId}; + if (params) { + Object.assign(pageParams, params); + } + navCtrl.push('AddonModFolderIndexPage', pageParams, options); } }; } diff --git a/src/addon/mod/forum/providers/module-handler.ts b/src/addon/mod/forum/providers/module-handler.ts index 478ed249d..f87f93d03 100644 --- a/src/addon/mod/forum/providers/module-handler.ts +++ b/src/addon/mod/forum/providers/module-handler.ts @@ -72,8 +72,12 @@ export class AddonModForumModuleHandler implements CoreCourseModuleHandler { title: module.name, class: 'addon-mod_forum-handler', showDownloadButton: true, - action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions): void { - navCtrl.push('AddonModForumIndexPage', {module: module, courseId: courseId}, options); + action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions, params?: any): void { + const pageParams = {module: module, courseId: courseId}; + if (params) { + Object.assign(pageParams, params); + } + navCtrl.push('AddonModForumIndexPage', pageParams, options); } }; diff --git a/src/addon/mod/glossary/providers/module-handler.ts b/src/addon/mod/glossary/providers/module-handler.ts index 5cfc21bd4..88b952b13 100644 --- a/src/addon/mod/glossary/providers/module-handler.ts +++ b/src/addon/mod/glossary/providers/module-handler.ts @@ -66,8 +66,12 @@ export class AddonModGlossaryModuleHandler implements CoreCourseModuleHandler { title: module.name, class: 'addon-mod_glossary-handler', showDownloadButton: true, - action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions): void { - navCtrl.push('AddonModGlossaryIndexPage', {module: module, courseId: courseId}, options); + action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions, params?: any): void { + const pageParams = {module: module, courseId: courseId}; + if (params) { + Object.assign(pageParams, params); + } + navCtrl.push('AddonModGlossaryIndexPage', pageParams, options); } }; } diff --git a/src/addon/mod/imscp/providers/module-handler.ts b/src/addon/mod/imscp/providers/module-handler.ts index d99d7ba76..94f5e48ea 100644 --- a/src/addon/mod/imscp/providers/module-handler.ts +++ b/src/addon/mod/imscp/providers/module-handler.ts @@ -65,8 +65,12 @@ export class AddonModImscpModuleHandler implements CoreCourseModuleHandler { title: module.name, class: 'addon-mod_imscp-handler', showDownloadButton: true, - action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions): void { - navCtrl.push('AddonModImscpIndexPage', {module: module, courseId: courseId}, options); + action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions, params?: any): void { + const pageParams = {module: module, courseId: courseId}; + if (params) { + Object.assign(pageParams, params); + } + navCtrl.push('AddonModImscpIndexPage', pageParams, options); } }; } diff --git a/src/addon/mod/lesson/providers/module-handler.ts b/src/addon/mod/lesson/providers/module-handler.ts index 710578202..ec28dc40c 100644 --- a/src/addon/mod/lesson/providers/module-handler.ts +++ b/src/addon/mod/lesson/providers/module-handler.ts @@ -65,8 +65,12 @@ export class AddonModLessonModuleHandler implements CoreCourseModuleHandler { title: module.name, class: 'addon-mod_lesson-handler', showDownloadButton: true, - action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions): void { - navCtrl.push('AddonModLessonIndexPage', {module: module, courseId: courseId}, options); + action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions, params?: any): void { + const pageParams = {module: module, courseId: courseId}; + if (params) { + Object.assign(pageParams, params); + } + navCtrl.push('AddonModLessonIndexPage', pageParams, options); } }; } diff --git a/src/addon/mod/lti/providers/module-handler.ts b/src/addon/mod/lti/providers/module-handler.ts index 85204e90e..72ca2f2ba 100644 --- a/src/addon/mod/lti/providers/module-handler.ts +++ b/src/addon/mod/lti/providers/module-handler.ts @@ -74,8 +74,12 @@ export class AddonModLtiModuleHandler implements CoreCourseModuleHandler { icon: this.courseProvider.getModuleIconSrc(this.modName, module.modicon), title: module.name, class: 'addon-mod_lti-handler', - action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions): void { - navCtrl.push('AddonModLtiIndexPage', {module: module, courseId: courseId}, options); + action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions, params?: any): void { + const pageParams = {module: module, courseId: courseId}; + if (params) { + Object.assign(pageParams, params); + } + navCtrl.push('AddonModLtiIndexPage', pageParams, options); }, buttons: [{ icon: 'link', diff --git a/src/addon/mod/page/providers/module-handler.ts b/src/addon/mod/page/providers/module-handler.ts index 4a75ce269..34b827807 100644 --- a/src/addon/mod/page/providers/module-handler.ts +++ b/src/addon/mod/page/providers/module-handler.ts @@ -65,8 +65,12 @@ export class AddonModPageModuleHandler implements CoreCourseModuleHandler { title: module.name, class: 'addon-mod_page-handler', showDownloadButton: true, - action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions): void { - navCtrl.push('AddonModPageIndexPage', {module: module, courseId: courseId}, options); + action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions, params?: any): void { + const pageParams = {module: module, courseId: courseId}; + if (params) { + Object.assign(pageParams, params); + } + navCtrl.push('AddonModPageIndexPage', pageParams, options); } }; } diff --git a/src/addon/mod/quiz/providers/module-handler.ts b/src/addon/mod/quiz/providers/module-handler.ts index 9c821ec38..f3b484ce9 100644 --- a/src/addon/mod/quiz/providers/module-handler.ts +++ b/src/addon/mod/quiz/providers/module-handler.ts @@ -66,8 +66,12 @@ export class AddonModQuizModuleHandler implements CoreCourseModuleHandler { title: module.name, class: 'addon-mod_quiz-handler', showDownloadButton: true, - action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions): void { - navCtrl.push('AddonModQuizIndexPage', {module: module, courseId: courseId}, options); + action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions, params?: any): void { + const pageParams = {module: module, courseId: courseId}; + if (params) { + Object.assign(pageParams, params); + } + navCtrl.push('AddonModQuizIndexPage', pageParams, options); } }; } diff --git a/src/addon/mod/resource/providers/module-handler.ts b/src/addon/mod/resource/providers/module-handler.ts index 94267ed38..5dc520d7b 100644 --- a/src/addon/mod/resource/providers/module-handler.ts +++ b/src/addon/mod/resource/providers/module-handler.ts @@ -82,8 +82,12 @@ export class AddonModResourceModuleHandler implements CoreCourseModuleHandler { title: module.name, class: 'addon-mod_resource-handler', showDownloadButton: true, - action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions): void { - navCtrl.push('AddonModResourceIndexPage', {module: module, courseId: courseId}, options); + action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions, params?: any): void { + const pageParams = {module: module, courseId: courseId}; + if (params) { + Object.assign(pageParams, params); + } + navCtrl.push('AddonModResourceIndexPage', pageParams, options); }, updateStatus: updateStatus.bind(this), buttons: [ { diff --git a/src/addon/mod/scorm/providers/module-handler.ts b/src/addon/mod/scorm/providers/module-handler.ts index b096f7463..1f1df4c8d 100644 --- a/src/addon/mod/scorm/providers/module-handler.ts +++ b/src/addon/mod/scorm/providers/module-handler.ts @@ -64,8 +64,12 @@ export class AddonModScormModuleHandler implements CoreCourseModuleHandler { title: module.name, class: 'addon-mod_scorm-handler', showDownloadButton: true, - action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions): void { - navCtrl.push('AddonModScormIndexPage', {module: module, courseId: courseId}, options); + action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions, params?: any): void { + const pageParams = {module: module, courseId: courseId}; + if (params) { + Object.assign(pageParams, params); + } + navCtrl.push('AddonModScormIndexPage', pageParams, options); } }; } diff --git a/src/addon/mod/survey/providers/module-handler.ts b/src/addon/mod/survey/providers/module-handler.ts index 4725b6ea3..ed3368ccb 100644 --- a/src/addon/mod/survey/providers/module-handler.ts +++ b/src/addon/mod/survey/providers/module-handler.ts @@ -64,8 +64,12 @@ export class AddonModSurveyModuleHandler implements CoreCourseModuleHandler { title: module.name, class: 'addon-mod_survey-handler', showDownloadButton: true, - action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions): void { - navCtrl.push('AddonModSurveyIndexPage', {module: module, courseId: courseId}, options); + action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions, params?: any): void { + const pageParams = {module: module, courseId: courseId}; + if (params) { + Object.assign(pageParams, params); + } + navCtrl.push('AddonModSurveyIndexPage', pageParams, options); } }; } diff --git a/src/addon/mod/url/providers/module-handler.ts b/src/addon/mod/url/providers/module-handler.ts index 79c8c54f4..6c205ae51 100644 --- a/src/addon/mod/url/providers/module-handler.ts +++ b/src/addon/mod/url/providers/module-handler.ts @@ -72,7 +72,7 @@ export class AddonModUrlModuleHandler implements CoreCourseModuleHandler { title: module.name, class: 'addon-mod_url-handler', showDownloadButton: false, - action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions): void { + action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions, params?: any): void { const modal = handler.domUtils.showModalLoading(); // First of all, check if the URL can be handled by the app. If so, always open it directly. @@ -100,7 +100,11 @@ export class AddonModUrlModuleHandler implements CoreCourseModuleHandler { if (shouldOpen) { handler.openUrl(module, courseId); } else { - navCtrl.push('AddonModUrlIndexPage', {module: module, courseId: courseId}, options); + const pageParams = {module: module, courseId: courseId}; + if (params) { + Object.assign(pageParams, params); + } + navCtrl.push('AddonModUrlIndexPage', pageParams, options); } }).finally(() => { modal.dismiss(); diff --git a/src/addon/mod/wiki/providers/module-handler.ts b/src/addon/mod/wiki/providers/module-handler.ts index 72056f85b..20a94af39 100644 --- a/src/addon/mod/wiki/providers/module-handler.ts +++ b/src/addon/mod/wiki/providers/module-handler.ts @@ -65,8 +65,12 @@ export class AddonModWikiModuleHandler implements CoreCourseModuleHandler { title: module.name, class: 'addon-mod_wiki-handler', showDownloadButton: true, - action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions): void { - navCtrl.push('AddonModWikiIndexPage', {module: module, courseId: courseId}, options); + action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions, params?: any): void { + const pageParams = {module: module, courseId: courseId}; + if (params) { + Object.assign(pageParams, params); + } + navCtrl.push('AddonModWikiIndexPage', pageParams, options); } }; } diff --git a/src/addon/mod/workshop/providers/module-handler.ts b/src/addon/mod/workshop/providers/module-handler.ts index 11914daa9..addfe132d 100644 --- a/src/addon/mod/workshop/providers/module-handler.ts +++ b/src/addon/mod/workshop/providers/module-handler.ts @@ -64,8 +64,12 @@ export class AddonModWorkshopModuleHandler implements CoreCourseModuleHandler { title: module.name, class: 'addon-mod_workshop-handler', showDownloadButton: true, - action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions): void { - navCtrl.push('AddonModWorkshopIndexPage', {module: module, courseId: courseId}, options); + action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions, params?: any): void { + const pageParams = {module: module, courseId: courseId}; + if (params) { + Object.assign(pageParams, params); + } + navCtrl.push('AddonModWorkshopIndexPage', pageParams, options); } }; } diff --git a/src/core/course/pages/section/section.ts b/src/core/course/pages/section/section.ts index 4a8bcf145..e711e1e08 100644 --- a/src/core/course/pages/section/section.ts +++ b/src/core/course/pages/section/section.ts @@ -62,6 +62,7 @@ export class CoreCourseSectionPage implements OnDestroy { displayRefresher: boolean; protected module: any; + protected modParams: any; protected completionObserver; protected courseStatusObserver; protected syncObserver; @@ -80,6 +81,7 @@ export class CoreCourseSectionPage implements OnDestroy { this.sectionNumber = navParams.get('sectionNumber'); this.module = navParams.get('module'); this.firstTabName = navParams.get('selectedTab'); + this.modParams = navParams.get('modParams'); // Get the title to display. We dont't have sections yet. this.title = courseFormatDelegate.getCourseTitle(this.course); @@ -124,7 +126,7 @@ export class CoreCourseSectionPage implements OnDestroy { if (this.module) { this.moduleId = this.module.id; - this.courseHelper.openModule(this.navCtrl, this.module, this.course.id, this.sectionId); + this.courseHelper.openModule(this.navCtrl, this.module, this.course.id, this.sectionId, this.modParams); } this.loadData(false, true).finally(() => { diff --git a/src/core/course/providers/helper.ts b/src/core/course/providers/helper.ts index 3c1dcdb48..a6970a565 100644 --- a/src/core/course/providers/helper.ts +++ b/src/core/course/providers/helper.ts @@ -1007,9 +1007,11 @@ export class CoreCourseHelperProvider { * @param {number} [sectionId] Section the module belongs to. If not defined we'll try to retrieve it from the site. * @param {string} [modName] If set, the app will retrieve all modules of this type with a single WS call. This reduces the * number of WS calls, but it isn't recommended for modules that can return a lot of contents. + * @param {any} [modParams] Params to pass to the module * @return {Promise} Promise resolved when done. */ - navigateToModule(moduleId: number, siteId?: string, courseId?: number, sectionId?: number, modName?: string): Promise { + navigateToModule(moduleId: number, siteId?: string, courseId?: number, sectionId?: number, modName?: string, modParams?: any) + : Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); const modal = this.domUtils.showModalLoading(); @@ -1048,7 +1050,8 @@ export class CoreCourseHelperProvider { const params = { course: { id: courseId }, module: module, - sectionId: sectionId + sectionId: sectionId, + modParams: modParams }; module.handlerData = this.moduleDelegate.getModuleDataFor(module.modname, module, courseId, sectionId); @@ -1075,15 +1078,16 @@ export class CoreCourseHelperProvider { * @param {any} module The module to open. * @param {number} courseId The course ID of the module. * @param {number} [sectionId] The section ID of the module. + * @param {any} [modParams] Params to pass to the module * @param {boolean} True if module can be opened, false otherwise. */ - openModule(navCtrl: NavController, module: any, courseId: number, sectionId?: number): boolean { + openModule(navCtrl: NavController, module: any, courseId: number, sectionId?: number, modParams?: any): boolean { if (!module.handlerData) { module.handlerData = this.moduleDelegate.getModuleDataFor(module.modname, module, courseId, sectionId); } if (module.handlerData && module.handlerData.action) { - module.handlerData.action(new Event('click'), navCtrl, module, courseId, { animate: false }); + module.handlerData.action(new Event('click'), navCtrl, module, courseId, { animate: false }, modParams); return true; } diff --git a/src/core/course/providers/module-delegate.ts b/src/core/course/providers/module-delegate.ts index 3dbf06c42..4a3824784 100644 --- a/src/core/course/providers/module-delegate.ts +++ b/src/core/course/providers/module-delegate.ts @@ -156,8 +156,9 @@ export interface CoreCourseModuleHandlerData { * @param {any} module The module object. * @param {number} courseId The course ID. * @param {NavOptions} [options] Options for the navigation. + * @param {any} [params] Params for the new page. */ - action?(event: Event, navCtrl: NavController, module: any, courseId: number, options?: NavOptions): void; + action?(event: Event, navCtrl: NavController, module: any, courseId: number, options?: NavOptions, params?: any): void; /** * Updates the status of the module. diff --git a/src/core/sitehome/pages/index/index.ts b/src/core/sitehome/pages/index/index.ts index 23247963b..a66018fec 100644 --- a/src/core/sitehome/pages/index/index.ts +++ b/src/core/sitehome/pages/index/index.ts @@ -31,9 +31,11 @@ export class CoreSiteHomeIndexPage { constructor(navParams: NavParams, navCtrl: NavController, courseHelper: CoreCourseHelperProvider, sitesProvider: CoreSitesProvider) { - const module = navParams.get('module'); + const module = navParams.get('module'), + modParams = navParams.get('modParams'); + if (module) { - courseHelper.openModule(navCtrl, module, sitesProvider.getCurrentSite().getSiteHomeId()); + courseHelper.openModule(navCtrl, module, sitesProvider.getCurrentSite().getSiteHomeId(), undefined, modParams); } } } From a3dda152f91f35c7f0ab43b092be5cbe201f14bc Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 22 Jan 2019 12:21:54 +0100 Subject: [PATCH 009/191] MOBILE-2824 book: Support chapterid param in links --- src/addon/mod/book/components/index/index.ts | 16 ++++++++++++- src/addon/mod/book/pages/index/index.html | 2 +- src/addon/mod/book/pages/index/index.ts | 2 ++ src/addon/mod/book/providers/link-handler.ts | 24 ++++++++++++++++++++ 4 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/addon/mod/book/components/index/index.ts b/src/addon/mod/book/components/index/index.ts index 573a53778..bcb78e821 100644 --- a/src/addon/mod/book/components/index/index.ts +++ b/src/addon/mod/book/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 } from '@angular/core'; +import { Component, Optional, Injector, Input } from '@angular/core'; import { Content, PopoverController } from 'ionic-angular'; import { CoreAppProvider } from '@providers/app'; import { CoreCourseProvider } from '@core/course/providers/course'; @@ -29,6 +29,8 @@ import { AddonModBookTocPopoverComponent } from '../../components/toc-popover/to templateUrl: 'addon-mod-book-index.html', }) export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComponent { + @Input() initialChapterId: string; // The initial chapter ID to load. + component = AddonModBookProvider.COMPONENT; chapterContent: string; previousChapter: string; @@ -128,7 +130,19 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp this.contentsMap = this.bookProvider.getContentsMap(this.module.contents); this.chapters = this.bookProvider.getTocList(this.module.contents); + if (typeof this.currentChapter == 'undefined' && typeof this.initialChapterId != 'undefined' && this.chapters) { + // Initial chapter set. Validate that the chapter exists. + const chapter = this.chapters.find((chapter) => { + return chapter.id == this.initialChapterId; + }); + + if (chapter) { + this.currentChapter = this.initialChapterId; + } + } + if (typeof this.currentChapter == 'undefined') { + // Load the first chapter. this.currentChapter = this.bookProvider.getFirstChapter(this.chapters); } diff --git a/src/addon/mod/book/pages/index/index.html b/src/addon/mod/book/pages/index/index.html index 14f0a66bd..78c828426 100644 --- a/src/addon/mod/book/pages/index/index.html +++ b/src/addon/mod/book/pages/index/index.html @@ -12,5 +12,5 @@ - + diff --git a/src/addon/mod/book/pages/index/index.ts b/src/addon/mod/book/pages/index/index.ts index a45a34e1d..7bb9e1f0f 100644 --- a/src/addon/mod/book/pages/index/index.ts +++ b/src/addon/mod/book/pages/index/index.ts @@ -30,10 +30,12 @@ export class AddonModBookIndexPage { title: string; module: any; courseId: number; + chapterId: number; constructor(navParams: NavParams) { this.module = navParams.get('module') || {}; this.courseId = navParams.get('courseId'); + this.chapterId = navParams.get('chapterId'); this.title = this.module.name; } diff --git a/src/addon/mod/book/providers/link-handler.ts b/src/addon/mod/book/providers/link-handler.ts index 1035ef3f9..899978d4b 100644 --- a/src/addon/mod/book/providers/link-handler.ts +++ b/src/addon/mod/book/providers/link-handler.ts @@ -15,6 +15,7 @@ import { Injectable } from '@angular/core'; import { CoreContentLinksModuleIndexHandler } from '@core/contentlinks/classes/module-index-handler'; import { CoreCourseHelperProvider } from '@core/course/providers/helper'; +import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate'; /** * Handler to treat links to book. @@ -26,4 +27,27 @@ export class AddonModBookLinkHandler extends CoreContentLinksModuleIndexHandler constructor(courseHelper: CoreCourseHelperProvider) { super(courseHelper, 'AddonModBook', 'book'); } + + /** + * Get the list of actions for a link (url). + * + * @param {string[]} siteIds List of sites the URL belongs to. + * @param {string} url The URL to treat. + * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param {number} [courseId] Course ID related to the URL. Optional but recommended. + * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. + */ + getActions(siteIds: string[], url: string, params: any, courseId?: number): + CoreContentLinksAction[] | Promise { + + const modParams = params.chapterid ? {chapterId: params.chapterid} : undefined; + courseId = courseId || params.courseid || params.cid; + + return [{ + action: (siteId, navCtrl?): void => { + this.courseHelper.navigateToModule(parseInt(params.id, 10), siteId, courseId, undefined, + this.useModNameToGetModule ? this.modName : undefined, modParams); + } + }]; + } } From 8436162fb6b21ae8a6cb2f282faf418a6e2b5daf Mon Sep 17 00:00:00 2001 From: Travis CI User Date: Fri, 11 Jan 2019 12:29:58 +0000 Subject: [PATCH 010/191] Update lang files [ci skip] --- src/addon/block/myoverview/lang/en.json | 6 +- src/addon/block/timeline/lang/en.json | 2 +- src/addon/messages/lang/en.json | 12 +- src/addon/mod/workshop/lang/en.json | 6 +- src/assets/lang/ar.json | 72 +++- src/assets/lang/bg.json | 89 ++++- src/assets/lang/ca.json | 210 ++++++++-- src/assets/lang/cs.json | 201 ++++++++-- src/assets/lang/da.json | 164 ++++++-- src/assets/lang/de-du.json | 107 +++-- src/assets/lang/de.json | 264 ++++++++++--- src/assets/lang/el.json | 410 ++++++++++++++++---- src/assets/lang/en-us.json | 14 + src/assets/lang/en.json | 28 +- src/assets/lang/es-mx.json | 215 ++++++++-- src/assets/lang/es.json | 148 +++++-- src/assets/lang/eu.json | 279 ++++++++++--- src/assets/lang/fa.json | 179 ++++++++- src/assets/lang/fi.json | 104 +++-- src/assets/lang/fr.json | 220 +++++++++-- src/assets/lang/he.json | 119 ++++-- src/assets/lang/hr.json | 109 +++++- src/assets/lang/hu.json | 192 +++++++-- src/assets/lang/id.json | 57 +-- src/assets/lang/it.json | 143 +++++-- src/assets/lang/ja.json | 200 ++++++++-- src/assets/lang/km.json | 495 +++++++++++++++++++++++- src/assets/lang/kn.json | 30 +- src/assets/lang/ko.json | 80 +++- src/assets/lang/lt.json | 146 +++++-- src/assets/lang/mr.json | 57 +-- src/assets/lang/nl.json | 222 +++++++++-- src/assets/lang/no.json | 197 ++++++++-- src/assets/lang/pl.json | 206 +++++++++- src/assets/lang/pt-br.json | 181 +++++++-- src/assets/lang/pt.json | 220 +++++++++-- src/assets/lang/ro.json | 124 ++++-- src/assets/lang/ru.json | 180 +++++++-- src/assets/lang/sr-cr.json | 93 +++-- src/assets/lang/sr-lt.json | 93 +++-- src/assets/lang/sv.json | 107 +++-- src/assets/lang/tg.json | 95 ++++- src/assets/lang/tr.json | 98 +++-- src/assets/lang/uk.json | 98 +++-- src/assets/lang/zh-cn.json | 100 +++-- src/assets/lang/zh-tw.json | 113 ++++-- src/core/login/lang/en.json | 2 +- src/core/settings/lang/en.json | 2 +- src/lang/en.json | 14 +- 49 files changed, 5300 insertions(+), 1203 deletions(-) diff --git a/src/addon/block/myoverview/lang/en.json b/src/addon/block/myoverview/lang/en.json index e914935f0..f6d14b055 100644 --- a/src/addon/block/myoverview/lang/en.json +++ b/src/addon/block/myoverview/lang/en.json @@ -1,13 +1,13 @@ { "all": "All", + "favourites": "Starred", "future": "Future", - "inprogress": "In progress", - "favourites" : "Starred", "hiddencourses": "Hidden", + "inprogress": "In progress", "lastaccessed": "Last accessed", "morecourses": "More courses", "nocourses": "No courses", "past": "Past", "pluginname": "Course overview", - "title": "Title" + "title": "Course name" } \ No newline at end of file diff --git a/src/addon/block/timeline/lang/en.json b/src/addon/block/timeline/lang/en.json index a9460e4d7..fbd482f47 100644 --- a/src/addon/block/timeline/lang/en.json +++ b/src/addon/block/timeline/lang/en.json @@ -4,7 +4,7 @@ "next3months": "Next 3 months", "next6months": "Next 6 months", "next7days": "Next 7 days", - "nocoursesinprogress": "No in progress courses", + "nocoursesinprogress": "No in-progress courses", "noevents": "No upcoming activities due", "overdue": "Overdue", "pluginname": "Timeline", diff --git a/src/addon/messages/lang/en.json b/src/addon/messages/lang/en.json index f11b07ab6..2ec162f66 100644 --- a/src/addon/messages/lang/en.json +++ b/src/addon/messages/lang/en.json @@ -26,30 +26,30 @@ "errorwhileretrievingdiscussions": "Error while retrieving discussions from the server.", "errorwhileretrievingmessages": "Error while retrieving messages from the server.", "errorwhileretrievingusers": "Error while retrieving users from the server.", - "groupinfo": "Group info", "groupconversations": "Group", + "groupinfo": "Group info", "individualconversations": "Private", "info": "Info", "isnotinyourcontacts": "{{$a}} is not in your contacts", - "messagenotsent": "The message was not sent. Please try again later.", "message": "Message", + "messagenotsent": "The message was not sent. Please try again later.", "messagepreferences": "Message preferences", "messages": "Messages", "newmessage": "New message", "newmessages": "New messages", "nocontactrequests": "No contact requests", - "noncontacts": "Non-contacts", - "nocontactsgetstarted": "Try searching for someone to add them as a contact", + "nocontactsgetstarted": "No contacts", "nofavourites": "No starred conversations", "nogroupconversations": "No group conversations", "noindividualconversations": "No private conversations", "nomessagesfound": "No messages were found", + "noncontacts": "Non-contacts", "nousersfound": "No users found", "numparticipants": "{{$a}} participants", "removecontact": "Remove contact", "removecontactconfirm": "Are you sure you want to remove {{$a}} from your contacts?", - "removefromyourcontacts": "Remove from contacts", "removefromfavourites": "Unstar", + "removefromyourcontacts": "Remove from contacts", "requests": "Requests", "requirecontacttomessage": "You need to request {{$a}} to add you as a contact to be able to message them.", "searchcombined": "Search people and messages", @@ -57,7 +57,7 @@ "searchnomessagesfound": "No messages found", "searchnononcontactsfound": "No non contacts found", "sendcontactrequest": "Send contact request", - "showdeletemessages": "Show delete options", + "showdeletemessages": "Show delete messages", "type_blocked": "Blocked", "type_offline": "Offline", "type_online": "Online", diff --git a/src/addon/mod/workshop/lang/en.json b/src/addon/mod/workshop/lang/en.json index 3f994a544..739b7fd7f 100644 --- a/src/addon/mod/workshop/lang/en.json +++ b/src/addon/mod/workshop/lang/en.json @@ -53,11 +53,11 @@ "switchphase50": "Close workshop", "userplan": "Workshop planner", "userplancurrentphase": "Current phase", - "warningassessmentmodified": "The assessment was modified on the site.", - "warningsubmissionmodified": "The submission was modified on the site.", + "warningassessmentmodified": "The submission was modified on the site.", + "warningsubmissionmodified": "The assessment was modified on the site.", "weightinfo": "Weight: {{$a}}", "yourassessment": "Your assessment", "yourassessmentfor": "Your assessment for {{$a}}", "yourgrades": "Your grades", "yoursubmission": "Your submission" -} +} \ No newline at end of file diff --git a/src/assets/lang/ar.json b/src/assets/lang/ar.json index 1fb73fa29..614bb97d7 100644 --- a/src/assets/lang/ar.json +++ b/src/assets/lang/ar.json @@ -1,4 +1,6 @@ { + "addon.block_activitymodules.pluginname": "أنشطة", + "addon.block_sitemainmenu.pluginname": "القائمة الرئيسية", "addon.calendar.calendar": "تقويم", "addon.calendar.calendarevents": "أحداث التقويم", "addon.calendar.errorloadevent": "خطأ في تحميل الحدث", @@ -16,21 +18,21 @@ "addon.coursecompletion.complete": "مكتمل", "addon.coursecompletion.completecourse": "قم بإتمام المقرر الدراسي", "addon.coursecompletion.completed": "تم إكمال المقرر", - "addon.coursecompletion.completiondate": "تاريخ إكمال المقرر", + "addon.coursecompletion.completiondate": "تاريخ الإتمام", "addon.coursecompletion.completionmenuitem": "الإكمال", "addon.coursecompletion.couldnotloadreport": "لا يمكن تحميل تقرير إكمال المقرر، الرجاء المحاولة في وقت آخر", "addon.coursecompletion.coursecompletion": "إكمال المقرر", - "addon.coursecompletion.criteria": "المعايير", - "addon.coursecompletion.criteriagroup": "معايير المجموعة", + "addon.coursecompletion.criteria": "معايير", + "addon.coursecompletion.criteriagroup": "مجموعة المعايير", "addon.coursecompletion.criteriarequiredall": "كل المعايير في الأسفل مطلوبة", - "addon.coursecompletion.criteriarequiredany": "أي معايير في الأسفل مطلوبة", + "addon.coursecompletion.criteriarequiredany": "أي معيار في الأسفل مطلوب", "addon.coursecompletion.inprogress": "قيد التنفيذ", - "addon.coursecompletion.manualselfcompletion": "إكمال ذاتي يدوي", + "addon.coursecompletion.manualselfcompletion": "إكمال يدوي ذاتي", "addon.coursecompletion.notyetstarted": "لم يبدأ بعد", - "addon.coursecompletion.pending": "قيد الانتظار", - "addon.coursecompletion.required": "مطلوب", + "addon.coursecompletion.pending": "معلق", + "addon.coursecompletion.required": "مفروض", "addon.coursecompletion.requiredcriteria": "المعايير المطلوبة", - "addon.coursecompletion.requirement": "المتطلبات", + "addon.coursecompletion.requirement": "متطلبات", "addon.coursecompletion.status": "الحالة", "addon.coursecompletion.viewcoursereport": "عرض تقرير المقرر", "addon.files.couldnotloadfiles": "لا يمكن تحميل قائمة الملفات", @@ -39,24 +41,27 @@ "addon.files.privatefiles": "ملفات خاصة", "addon.files.sitefiles": "ملفات الموقع", "addon.messages.addcontact": "أضف جهة أتصال", - "addon.messages.blockcontact": "أحجب جهة الأتصال", + "addon.messages.addtoyourcontacts": "أضف جهات اتصاللك", "addon.messages.blocknoncontacts": "امنع المستخدمين الذين ليسوا في قائمة اتصالاتي من مراسلتي", + "addon.messages.contactblocked": "تم حظر جهة الاتصال", "addon.messages.contactlistempty": "قائمة الاتصال فارغة", "addon.messages.contacts": "جهات اتصال", + "addon.messages.deleteallconfirm": "هل أنت متأكد من رغبتك بحذف كامل الحوار؟", "addon.messages.errordeletemessage": "خطأ عند حذف الرسالة", "addon.messages.message": "رسالة", "addon.messages.messagenotsent": "لم يتم إرسال الرسالة، يرجي المحاولة لاحقا", "addon.messages.messagepreferences": "مراجع الرسالة", "addon.messages.messages": "رسائل", "addon.messages.newmessage": "رسالة جديدة", - "addon.messages.nomessages": "لا يوجد رساله/رسائل جديدة", + "addon.messages.nomessagesfound": "لم يتم العثور على إي رسالة", "addon.messages.nousersfound": "لا يوجد مستخدمين", "addon.messages.removecontact": "ازل جهة الاتصال", + "addon.messages.removefromyourcontacts": "حذف من قائمة جهات اتصالك", + "addon.messages.searchcombined": "البحث عن الأشخاص والرسائل", "addon.messages.type_offline": "غير متصل", "addon.messages.type_online": "متصل", "addon.messages.type_search": "نتائج البحث", "addon.messages.type_strangers": "رسائل أخرى", - "addon.messages.unblockcontact": "أزل الحجب عن عنوان الاتصال", "addon.mod_assign.addattempt": "اسمح بمحاولة أخرى", "addon.mod_assign.addnewattempt": "إضافة محاولة جديدة", "addon.mod_assign.addnewattemptfromprevious": "إضافة محاولة جديدة بناء على التسليم السابق", @@ -82,6 +87,7 @@ "addon.mod_assign.graded": "تم رصد درجة", "addon.mod_assign.gradeoutof": "الدرجة من {{$a}}", "addon.mod_assign.gradingstatus": "حالة التقييم", + "addon.mod_assign.modulenameplural": "مهام", "addon.mod_assign.noattempt": "لا توجد محاولات", "addon.mod_assign.notgraded": "لم يتم التقييم", "addon.mod_assign.numberofdraftsubmissions": "مسودات", @@ -106,6 +112,7 @@ "addon.mod_assign_feedback_file.pluginname": "ملف التغذية الراجعة (الملاحظات)", "addon.mod_assign_submission_file.pluginname": "الملف المسلم", "addon.mod_book.errorchapter": "خطأ في قراءة فصل من فصول الكتاب.", + "addon.mod_book.modulenameplural": "كتب", "addon.mod_chat.beep": "نغمة", "addon.mod_chat.currentusers": "المستخدمين الحاليين", "addon.mod_chat.enterchat": "أضغط هناء للدخول إلى المحادثة", @@ -118,6 +125,7 @@ "addon.mod_chat.messagebeepsyou": "{{$a}} أرسل نغمة لك!", "addon.mod_chat.messageenter": "{{$a}} دخل غرفة محادثة", "addon.mod_chat.messageexit": "{{$a}} خرج من غرفة محادثة", + "addon.mod_chat.modulenameplural": "محادثات", "addon.mod_chat.nomessages": "لا توجد رسائل بعد", "addon.mod_chat.send": "إرسل", "addon.mod_chat.sessionstart": "ي {{$a.date}}, ({{$a.fromnow}} وسوف تبدأ جلسة الدردشة القادمة في من الآن", @@ -125,6 +133,7 @@ "addon.mod_choice.choiceoptions": "خيارات الاختيار", "addon.mod_choice.expired": "عذراً، تم إغلاق هذا النشاط في {{$a}} وهو غير متوفر الآن.", "addon.mod_choice.full": "(كامل)", + "addon.mod_choice.modulenameplural": "الاختيارات", "addon.mod_choice.noresultsviewable": "حالياً لا يمكن معاينة النتائج", "addon.mod_choice.notopenyet": "عذرا، هذا النشاط سيكون متوفر في {{$a}}", "addon.mod_choice.numberofuser": "عدد المستخدمين", @@ -149,6 +158,7 @@ "addon.mod_data.expired": "عذراً، تم إغلاق هذا النشاط في {{$a}} وهو غير متوفر الآن.", "addon.mod_data.fields": "حقول", "addon.mod_data.menuchoose": "اختار", + "addon.mod_data.modulenameplural": "قواعد بيانات", "addon.mod_data.more": "المزيد", "addon.mod_data.nomatch": "لم يتم العثور على مدخلات مطابقة!", "addon.mod_data.norecords": "لا يوجد مدخلات في قاعدة البيانات", @@ -175,6 +185,7 @@ "addon.mod_feedback.feedbackclose": "إغلاق الأفادة", "addon.mod_feedback.feedbackopen": "فتح الإفادة", "addon.mod_feedback.mode": "نمط", + "addon.mod_feedback.modulenameplural": "إفادة", "addon.mod_feedback.next_page": "الصفحة التالية", "addon.mod_feedback.non_anonymous": "سيتم تسجيل اسم المستخدم وعرضه مع الإجابات", "addon.mod_feedback.non_respondents_students": "غير مستجيبين", @@ -192,6 +203,7 @@ "addon.mod_feedback.started": "بداء", "addon.mod_feedback.this_feedback_is_already_submitted": "لقد قمت مسبقاً بإكمال هذا النشاط.", "addon.mod_folder.emptyfilelist": "لا يوجد أي ملفات ليتم إظهارها", + "addon.mod_folder.modulenameplural": "مجلدات", "addon.mod_forum.addanewdiscussion": "أضف موضوعا جديدا للنقاش", "addon.mod_forum.addanewquestion": "أضف سؤال جديد", "addon.mod_forum.addanewtopic": "أضف موضوع جديد", @@ -205,6 +217,7 @@ "addon.mod_forum.modeflatnewestfirst": "عرض الردود حسب الأحدث", "addon.mod_forum.modeflatoldestfirst": "عرض الردود حسب الأقدم", "addon.mod_forum.modenested": "عرض الردود حسب المداخلات", + "addon.mod_forum.modulenameplural": "المنتديات", "addon.mod_forum.posttoforum": "أضف المشاركة للمنتدى", "addon.mod_forum.re": "إعادة:", "addon.mod_forum.reply": "رد", @@ -227,6 +240,7 @@ "addon.mod_glossary.fillfields": "حقول المفهوم والتعريف اجبارية", "addon.mod_glossary.fullmatch": "قارن كل الكلمات فقط", "addon.mod_glossary.linking": "ربط آلي", + "addon.mod_glossary.modulenameplural": "مسردات", "addon.mod_imscp.deploymenterror": "خطاء في محتوى الحزمة", "addon.mod_imscp.showmoduledescription": "اظهر الوصف", "addon.mod_lesson.answer": "أجب", @@ -252,6 +266,7 @@ "addon.mod_lesson.lowscore": "أقل درجة", "addon.mod_lesson.lowtime": "أقل وقت", "addon.mod_lesson.modattemptsnoteacher": "معاينة الطالب تعمل فقط للطلاب", + "addon.mod_lesson.modulenameplural": "دروس", "addon.mod_lesson.noanswer": "لم تعطى إجابة", "addon.mod_lesson.nolessonattempts": "لم يتم إجراء محاولات مسبقة في هذا الدرس", "addon.mod_lesson.notcompleted": "لم يتم انهائه", @@ -273,6 +288,7 @@ "addon.mod_lesson.welldone": "أحسنت!", "addon.mod_lesson.youranswer": "إجابتك", "addon.mod_lesson.youshouldview": "يجب أن تجب على الأقل: {{$a}}", + "addon.mod_page.modulenameplural": "صفحات", "addon.mod_quiz.attemptfirst": "المحاولة الأولى", "addon.mod_quiz.attemptlast": "المحاولة الأخيرة", "addon.mod_quiz.attemptnumber": "محاولة", @@ -294,6 +310,7 @@ "addon.mod_quiz.gradehighest": "أعلى درجة", "addon.mod_quiz.grademethod": "أسلوب التقييم", "addon.mod_quiz.marks": "الدرجات", + "addon.mod_quiz.modulenameplural": "اختبارات", "addon.mod_quiz.noquestions": "لم يتم إضافة أية أسئلة بعد", "addon.mod_quiz.overdue": "متأخر جدا", "addon.mod_quiz.preview": "معاينة", @@ -317,6 +334,7 @@ "addon.mod_quiz.timetaken": "الوقت المستغرق", "addon.mod_quiz.yourfinalgradeis": "درجتك النهائية عن هذا الاختبار هي {{$a}}", "addon.mod_resource.modifieddate": "المُحدَّثة {{$a}}", + "addon.mod_resource.modulenameplural": "ملفات", "addon.mod_resource.openthefile": "افتح الملف", "addon.mod_resource.uploadeddate": "تم تحميلها {{$a}}", "addon.mod_scorm.asset": "قييم", @@ -341,6 +359,7 @@ "addon.mod_scorm.incomplete": "غير مكتمل", "addon.mod_scorm.lastattempt": "المحاولاة الأخيرة", "addon.mod_scorm.mode": "نمط", + "addon.mod_scorm.modulenameplural": "حزم إسكورم", "addon.mod_scorm.newattempt": "ابداء محاولة جديدة", "addon.mod_scorm.noattemptsallowed": "عدد المحاولات المسموح بها", "addon.mod_scorm.noattemptsmade": "عدد المحاولات التي قمت بها", @@ -352,12 +371,15 @@ "addon.mod_scorm.suspended": "معلّق", "addon.mod_survey.ifoundthat": "وجدت أن", "addon.mod_survey.ipreferthat": "أفضل أن", + "addon.mod_survey.modulenameplural": "استبيانات", "addon.mod_survey.responses": "إجابات", "addon.mod_survey.results": "النتائج", + "addon.mod_url.modulenameplural": "الروابط الإلكترونية", "addon.mod_wiki.cannoteditpage": "لا يمكنك تحرير هذه الصفحة.", "addon.mod_wiki.createpage": "إنشاء صفحة", "addon.mod_wiki.editingpage": "تحرير الصفحخة: {{$a}}", "addon.mod_wiki.map": "خريطة", + "addon.mod_wiki.modulenameplural": "ويكيات", "addon.mod_wiki.newpagetitle": "عنواو صفحة جديد", "addon.mod_wiki.nocontent": "لا يوجد محتوى في هذه الصفحة", "addon.mod_wiki.notingroup": "لا ينتمي إلى مجموعة", @@ -366,6 +388,7 @@ "addon.mod_workshop.assess": "قييم", "addon.mod_workshop.editsubmission": "حرر التسليم", "addon.mod_workshop.gradinggrade": "تقدير الدرجة", + "addon.mod_workshop.modulenameplural": "ورش عمل", "addon.mod_workshop.reassess": "اعد التقييم", "addon.mod_workshop_assessment_accumulative.dimensionnumber": "اوجه الدولار {{$a}}", "addon.mod_workshop_assessment_accumulative.mustchoosegrade": "يجب انتقاء درجة لهذا الوجه", @@ -642,6 +665,8 @@ "core.accounts": "حسابات", "core.add": "أضف", "core.agelocationverification": "التحقق من العمر والمكان", + "core.ago": "{{$a}} مضى", + "core.all": "الكل", "core.allparticipants": "كل المشاركين", "core.answer": "إجابة", "core.answered": "تم الاجابة", @@ -679,12 +704,11 @@ "core.courses.allowguests": "يسمح للمستخدمين الضيوف بالدخول إلى هذا المقرر الدراسي", "core.courses.availablecourses": "المقررات الدراسية المتاحة", "core.courses.categories": "تصنيفات المقررات الدراسية", - "core.courses.courseoverview": "معاينة عامة لمقرر دراسي", "core.courses.courses": "تصنيف المقررات الدراسية", "core.courses.frontpage": "الصفحة الرئيسية", "core.courses.mycourses": "مقرراتي الدراسية", + "core.courses.mymoodle": "مودل الخاص بي", "core.courses.nocourses": "لا يوجد معلومات لمقرر دراسي ليتم اظهرها", - "core.courses.nocoursesoverview": "لا يوجد مقررات", "core.courses.nocoursesyet": "لا توجد مقررات دراسية لهذه الفئة", "core.courses.nosearchresults": "لا يوجد نتائج", "core.courses.notenroled": "أنت لست مسجلاً كطالب في هذا المقرر", @@ -761,6 +785,7 @@ "core.login.createaccount": "إنشاء حساب مشترك الجديد", "core.login.createuserandpass": "من فضلك اختر اسم المستخدم وكلمة المرور للدخول بهما فيما بعد", "core.login.credentialsdescription": "من فضلك قم بإدخال اسم المستخدم وكلمة المرور للدخول إلى", + "core.login.emailconfirmsent": "

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

\n

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

\n

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

", "core.login.enterthewordsabove": "أدخل الكلمات أعلاه", "core.login.firsttime": "هل هذه هي المرة الأولى لك؟ الاشتراك من هنا", "core.login.forgotten": "هل نسيت اسم الدخول أو كلمة المرور؟", @@ -769,6 +794,7 @@ "core.login.helpmelogin": "

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

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

\n

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

\nلاختبار التطبيق باستخدام الموقع التجريبي لمودل ضع student أو teacher في حقل عنوان الموقع وانقر على زر دخول", "core.login.instructions": "تعليمات", "core.login.invalidaccount": "يرجى مراجعة تفاصيل تسجيل الدخول الخاصة بك أو الطلب من مسؤول موقع الويب الخاص بك للتحقق من تكوين موقع.", + "core.login.invaliddate": "تاريخ غير صحيح", "core.login.invalidemail": "عنوان البريد الإلكتروني غير صحيح", "core.login.invalidmoodleversion": "نسخة موودل غير صالحة. الحد الأدنى للنسخة المطلوبة هو:", "core.login.invalidsite": "رابط عنوان الموقع غير صالح.", @@ -778,6 +804,7 @@ "core.login.missingemail": "العنوان البريدي لم يتم تحدده", "core.login.missingfirstname": "الاسم الأول لم يتم تحدده", "core.login.missinglastname": "الاسم الأخير لم يتم تحدده", + "core.login.mustconfirm": "أنت تحتاج إلى تأكيد الدخول", "core.login.newaccount": "حساب مشترك جديد", "core.login.newsitedescription": "من فضلك أدخل رابط موقع نظام التعلم الإلكتروني مودل الخاص بك. لاحظ انه من الممكن أن يكون لم يتم إعداده للعمل مع هذا التطبيق.", "core.login.password": "كلمة المرور", @@ -806,7 +833,6 @@ "core.mainmenu.appsettings": "إعدادات التطبيق", "core.mainmenu.help": "مساعدة", "core.mainmenu.logout": "خروج", - "core.mainmenu.mycourses": "مقرراتي الدراسية", "core.mainmenu.website": "الموقع", "core.maxsizeandattachments": "الحجم الأقصى للملفات الجديدة: {{$a.size}}, أقصى عدد للمرفقات: {{$a.attachments}}", "core.min": "الحد الأدنى", @@ -873,7 +899,9 @@ "core.question.requiresgrading": "يتطلب التصحيح", "core.quotausage": "حتى الآن قد استخدمت {{$a.used}} من ال {{$a.total}} المسموحه", "core.refresh": "تحديث", + "core.remove": "ازيح", "core.required": "مفروض", + "core.resources": "المصادر", "core.restore": "إسترجاع", "core.save": "احفظ", "core.search": "بحث", @@ -922,6 +950,19 @@ "core.sizekb": "كيلو بايت", "core.sizemb": "ميغا بايب", "core.sortby": "إفرز بـ", + "core.strftimedate": "%d %B %Y", + "core.strftimedatefullshort": "%d/%m/%y", + "core.strftimedateshort": "%d %B", + "core.strftimedatetime": "%d %B %Y, %I:%M %p", + "core.strftimedatetimeshort": "%d/%m/%y، %H:%M", + "core.strftimedaydate": "%A, %d %B %Y", + "core.strftimedaydatetime": "%A, %d %B %Y, %I:%M %p", + "core.strftimedayshort": "%A, %d %B", + "core.strftimedaytime": "%a, %H:%M", + "core.strftimemonthyear": "%B %Y", + "core.strftimerecent": "%d %b, %H:%M", + "core.strftimerecentfull": "%a, %d %b %Y, %I:%M %p", + "core.strftimetime": "%I:%M %p", "core.submit": "سلم", "core.success": "نجاح", "core.teachers": "معلمون", @@ -951,7 +992,8 @@ "core.user.phone1": "هاتف", "core.user.phone2": "رقم الهاتف المحمول", "core.user.roles": "أدوار", - "core.user.student": "طالب", + "core.user.student": "الطالب", + "core.user.teacher": "معلم بدون صلاحية التحرير", "core.user.webpage": "صفحة إنترنت", "core.userdeleted": "تم حذف اشتراك هذا المستخدم", "core.userdetails": "تفاصيل المستخدم", diff --git a/src/assets/lang/bg.json b/src/assets/lang/bg.json index 3493d3747..d97b797ca 100644 --- a/src/assets/lang/bg.json +++ b/src/assets/lang/bg.json @@ -7,6 +7,14 @@ "addon.badges.issuerdetails": "Данни за връчващия", "addon.badges.issuername": "Име на връчващия", "addon.badges.nobadges": "Няма налични значки.", + "addon.block_activitymodules.pluginname": "Дейности", + "addon.block_myoverview.future": "Бъдещи", + "addon.block_myoverview.inprogress": "В прогрес", + "addon.block_myoverview.morecourses": "Още курсове", + "addon.block_myoverview.nocourses": "Няма курсове", + "addon.block_myoverview.past": "Минали", + "addon.block_myoverview.pluginname": "Преглед на курсовете", + "addon.block_sitemainmenu.pluginname": "Главно меню", "addon.calendar.calendar": "Календар", "addon.calendar.calendarevents": "Събития от календара", "addon.calendar.errorloadevent": "Грешка при зареждането на събитие.", @@ -29,12 +37,24 @@ "addon.competency.planstatusdraft": "Чернова", "addon.competency.userplans": "Учебни планове", "addon.coursecompletion.completecourse": "Завършване на курса", + "addon.coursecompletion.completed": "Завършено", + "addon.coursecompletion.completiondate": "Дата на завършване", + "addon.coursecompletion.coursecompletion": "Напредване в курса", + "addon.coursecompletion.criteria": "Критерии", + "addon.coursecompletion.criteriagroup": "Група критерии", + "addon.coursecompletion.criteriarequiredall": "Всички критерии по-долу са задължителни", + "addon.coursecompletion.criteriarequiredany": "Някои критерии по-долу са задължителни", + "addon.coursecompletion.manualselfcompletion": "Ръчно самоотбелязване на завършването", + "addon.coursecompletion.required": "Задължително", + "addon.coursecompletion.requirement": "Изискване", + "addon.coursecompletion.status": "Състояние", + "addon.coursecompletion.viewcoursereport": "Вижте отчет за курса", "addon.files.couldnotloadfiles": "Списъкът с файлове не можа да бъде зареден.", "addon.files.emptyfilelist": "Няма файлове, които да бъдат показани.", "addon.files.files": "Файлове", "addon.files.privatefiles": "Лични файлове", "addon.messages.addcontact": "Добавяне на контакт", - "addon.messages.blockcontact": "Блокиране на контакт", + "addon.messages.addtoyourcontacts": "Добави към Вашите контакти", "addon.messages.blocknoncontacts": "Блокирай съобщения от потребители извън списъка с контакти ", "addon.messages.contactlistempty": "Списъка с контакти е празен", "addon.messages.contactname": "Име на контакта", @@ -47,18 +67,18 @@ "addon.messages.messagepreferences": "Предпочитания за съобщенията", "addon.messages.messages": "Съобщения", "addon.messages.newmessage": "Ново съобщение", - "addon.messages.nomessages": "Няма чакащи съобщения", + "addon.messages.nomessagesfound": "Не бяха открити съобщения", "addon.messages.removecontact": "Премахване на контакт", + "addon.messages.searchcombined": "Търсене на хора и съобщения", "addon.messages.type_blocked": "Блокиран", "addon.messages.type_offline": "Офлайн", "addon.messages.type_online": "Онлайн", "addon.messages.type_search": "Резултати от търсенето", "addon.messages.type_strangers": "Други", - "addon.messages.unblockcontact": "Разблокиране на контакт", "addon.mod_assign.addattempt": "Позволяване на друг опит", "addon.mod_assign.addnewattempt": "Позволяване на нов опит", "addon.mod_assign.addnewattemptfromprevious": "Добавяне на нов опит, основан на предишно предаване", - "addon.mod_assign.addsubmission": "Добавяне на задание", + "addon.mod_assign.addsubmission": "Добавяне на изпълнение", "addon.mod_assign.allowsubmissionsanddescriptionfromdatesummary": "Данните за заданието и формулярът за предаване ще бъдат достъпни от {{$a}}", "addon.mod_assign.allowsubmissionsfromdate": "Позволено предаване от", "addon.mod_assign.allowsubmissionsfromdatesummary": "По това задание се приемат работи от {{$a}}", @@ -85,6 +105,7 @@ "addon.mod_assign.graded": "Оценена", "addon.mod_assign.gradedby": "Оценено от", "addon.mod_assign.gradedon": "Оценено на", + "addon.mod_assign.gradelocked": "Тази оценка е заключена или е коригирана в дневника за оценки.", "addon.mod_assign.gradeoutof": "Оценка до {{$a}}", "addon.mod_assign.gradingstatus": "Състояние на оценяването", "addon.mod_assign.groupsubmissionsettings": "Настройки за групово предаване", @@ -98,6 +119,7 @@ "addon.mod_assign.markingworkflowstatereadyforrelease": "Готово за обявяване", "addon.mod_assign.markingworkflowstatereadyforreview": "Оценяването е завършено", "addon.mod_assign.markingworkflowstatereleased": "Обявено", + "addon.mod_assign.modulenameplural": "Задания", "addon.mod_assign.multipleteams": "Член на повече от една група", "addon.mod_assign.multipleteams_desc": "Това задание изисква предаване по групи. Вие сте член на повече от една група. За да може да предадете, трябва да сте член на само една група. Моля, свържете се с Вашия преподавател за да промените членството в групата.", "addon.mod_assign.noattempt": "Няма опити", @@ -142,6 +164,7 @@ "addon.mod_assign_submission_comments.pluginname": "Коментари към заданието", "addon.mod_assign_submission_file.pluginname": "Качване на файлове", "addon.mod_book.errorchapter": "Грешка при четене на глава от книга.", + "addon.mod_book.modulenameplural": "Книги", "addon.mod_chat.beep": "Биип", "addon.mod_chat.currentusers": "Текущи потребители", "addon.mod_chat.enterchat": "Влизане в чата сега", @@ -149,6 +172,7 @@ "addon.mod_chat.messagebeepsyou": "{{$a}} току що Ви бибитна!", "addon.mod_chat.messageenter": "{{$a}} току що влезе в този чат", "addon.mod_chat.messageexit": "{{$a}} напусна този чат", + "addon.mod_chat.modulenameplural": "Чатове", "addon.mod_chat.nomessages": "Още няма съобщения", "addon.mod_chat.send": "Изпращане", "addon.mod_chat.sessionstart": "", @@ -156,6 +180,7 @@ "addon.mod_choice.choiceoptions": "Възможности за избиране", "addon.mod_choice.expired": "За съжаление тази дейност е затворена от {{$a}} и вече не е достъпна", "addon.mod_choice.full": "(Пълен)", + "addon.mod_choice.modulenameplural": "Възможности", "addon.mod_choice.noresultsviewable": "В момента резултатите не са за показване.", "addon.mod_choice.notopenyet": "За съжаление тази дейност не е достъпна до {{$a}}", "addon.mod_choice.numberofuser": "Брой участници", @@ -182,7 +207,9 @@ "addon.mod_data.entrieslefttoaddtoview": "Трябва да добавите още {{$a.entrieslefttoview}} записи преди да можете да видите записите на другите участници.", "addon.mod_data.expired": "За съжаление тази дейност е затворена в {{$a}} и вече не е достъпна", "addon.mod_data.fields": "Полета", + "addon.mod_data.foundrecords": "Намерени записи: {{$a.num}}/{{$a.max}} (Анулиране на филтрите)", "addon.mod_data.menuchoose": "Изберете...", + "addon.mod_data.modulenameplural": "Бази данни", "addon.mod_data.more": "Още", "addon.mod_data.nomatch": "Не са намерени съответстващи записи!", "addon.mod_data.norecords": "Няма записи в базата данни", @@ -212,6 +239,7 @@ "addon.mod_feedback.feedbackopen": "Позволено е отговаряне от", "addon.mod_feedback.mapcourses": "Свързване на анкетата с курсове", "addon.mod_feedback.mode": "Режим", + "addon.mod_feedback.modulenameplural": "Обратни връзки", "addon.mod_feedback.next_page": "Следваща страница", "addon.mod_feedback.non_anonymous": "Да - ще се показват с отговорите", "addon.mod_feedback.non_anonymous_entries": "няма анонимни отговори", @@ -230,6 +258,7 @@ "addon.mod_feedback.show_nonrespondents": "Показване на не отговорилите", "addon.mod_feedback.started": "започната", "addon.mod_feedback.this_feedback_is_already_submitted": "Вие вече сте изпълнили тази дейност.", + "addon.mod_folder.modulenameplural": "Папки", "addon.mod_forum.addanewdiscussion": "Добавяне на нова тема за обсъждане", "addon.mod_forum.addanewquestion": "Добавяне на нов въпрос", "addon.mod_forum.addanewtopic": "Добавяне на нова тема", @@ -248,6 +277,7 @@ "addon.mod_forum.modeflatnewestfirst": "Плоско показване на отговорите. Най-новият е първи.", "addon.mod_forum.modeflatoldestfirst": "Плоско показване на отговорите. Най-старият е първи.", "addon.mod_forum.modenested": "Показване на отговорите във вложена форма.", + "addon.mod_forum.modulenameplural": "Форуми", "addon.mod_forum.posttoforum": "Изпрати във форума", "addon.mod_forum.re": "Re:", "addon.mod_forum.reply": "Отговаряне", @@ -265,7 +295,9 @@ "addon.mod_glossary.errconceptalreadyexists": "Този термин вече съществува. В този речник не е разрешено повтаряне на дефиниции.", "addon.mod_glossary.fullmatch": "Свързване само на цели думи", "addon.mod_glossary.linking": "Автоматично свързване", + "addon.mod_glossary.modulenameplural": "Речници", "addon.mod_imscp.deploymenterror": "Грешка в съдържанието на пакета!", + "addon.mod_imscp.modulenameplural": "IMS пакети", "addon.mod_lesson.answer": "Отговор", "addon.mod_lesson.attempt": "{{$a}} опит", "addon.mod_lesson.attemptsremaining": "Имате оставащ(и) {{$a}} опит(a)", @@ -302,6 +334,7 @@ "addon.mod_lesson.lowtime": "Low Time", "addon.mod_lesson.maximumnumberofattemptsreached": "Достигнат е максималния брой опити - преминаване на следваща страница", "addon.mod_lesson.modattemptsnoteacher": "\"Преглеждане от студента\" се отнася само за студент.", + "addon.mod_lesson.modulenameplural": "Уроци", "addon.mod_lesson.noanswer": "Няма даден отговор", "addon.mod_lesson.nolessonattempts": "Не са правени опити за изпълнение на урока.", "addon.mod_lesson.notcompleted": "Не е завършен", @@ -336,6 +369,7 @@ "addon.mod_lesson.youhaveseen": "Вече сте видели повече от една страница от този урок.
Искате ли да продължите от последната, която сте чели?", "addon.mod_lesson.youranswer": "Вашият отговор", "addon.mod_lesson.youshouldview": "Трябва да прегледате поне {{$a}}", + "addon.mod_page.modulenameplural": "Страници", "addon.mod_quiz.attemptfirst": "Първи опит", "addon.mod_quiz.attemptlast": "Последен опит", "addon.mod_quiz.attemptnumber": "Опит", @@ -356,6 +390,7 @@ "addon.mod_quiz.grademethod": "Метод за оценяване", "addon.mod_quiz.gradesofar": "{{$a.method}}: {{$a.mygrade}} / {{$a.quizgrade}}.", "addon.mod_quiz.marks": "Точки", + "addon.mod_quiz.modulenameplural": "Тестове", "addon.mod_quiz.mustbesubmittedby": "Това изпълнение трябва да бъде предадено до {{$a}}.", "addon.mod_quiz.noquestions": "Още не са добавени въпроси", "addon.mod_quiz.noreviewattempt": "Вие нямате право да прегледате този опит.", @@ -394,6 +429,7 @@ "addon.mod_quiz.timeleft": "Оставащо време", "addon.mod_quiz.timetaken": "Изминало време", "addon.mod_quiz.yourfinalgradeis": "Финалната Ви оценка за този тест е {{$a}}.", + "addon.mod_resource.modulenameplural": "Файлове", "addon.mod_resource.openthefile": "Отвори файла", "addon.mod_scorm.attempts": "Опити", "addon.mod_scorm.averageattempt": "Средна оценка", @@ -418,12 +454,15 @@ "addon.mod_scorm.reviewmode": "Режим на преглеждане", "addon.mod_survey.ifoundthat": "Аз открих, че", "addon.mod_survey.ipreferthat": "Аз предпочинтам", + "addon.mod_survey.modulenameplural": "Анкети", "addon.mod_survey.responses": "Отговори", + "addon.mod_url.modulenameplural": "URL-и", "addon.mod_url.pointingtourl": "URL адрес към който този сочи този ресурс", "addon.mod_wiki.cannoteditpage": "Вие не може да редактирате тази страница.", "addon.mod_wiki.createpage": "Създаване на страница", "addon.mod_wiki.editingpage": "Редактиране на страница \"{{$a}}\"", "addon.mod_wiki.map": "Карта", + "addon.mod_wiki.modulenameplural": "Уикита", "addon.mod_wiki.newpagetitle": "Заглавие на новата страница", "addon.mod_wiki.pagename": "Име на страница", "addon.mod_wiki.wrongversionlock": "Друг потребител редактира тази страница докато Вие редактирахте и Вашата редакция вече е остаряла.", @@ -433,6 +472,7 @@ "addon.mod_workshop.assessmentsettings": "Настройки на оценяването", "addon.mod_workshop.assessmentweight": "Тегло на иценката", "addon.mod_workshop.gradinggrade": "Оценка за оценяването", + "addon.mod_workshop.modulenameplural": "Работилници", "addon.mod_workshop.receivedgrades": "Получени оценки", "addon.mod_workshop.submissionattachment": "Прикачен файл", "addon.mod_workshop.submissiongrade": "Оценка за заданието", @@ -703,6 +743,9 @@ "assets.mimetypes.text/rtf": "RTF документ", "core.accounts": "Потребителски регистрации", "core.add": "Добавяне", + "core.agelocationverification": "Установяване на възраст и страна", + "core.ago": "Преди {{$a}} ", + "core.all": "Всички", "core.allparticipants": "Всички участници", "core.answer": "Отговор", "core.answered": "Отговорен", @@ -720,6 +763,7 @@ "core.completion-alt-manual-n": "Не е завършена дейността: {{$a}}. Изберете я за да я отбележите за завършена.", "core.completion-alt-manual-y": "Завършена е дейността: {{$a}}. Изберете я за да я отбележите за незавършена.", "core.confirmdeletefile": "Сигурни ли сте, че искате да изтриете този файл?", + "core.considereddigitalminor": "Много сте малки за да се регистрирате на този сайт.", "core.content": "Съдържание", "core.continue": "Продължаване", "core.course": "Курс", @@ -735,23 +779,15 @@ "core.courses.allowguests": "В този курс могат да влизат гости", "core.courses.availablecourses": "Налични курсове", "core.courses.categories": "Категории курсове", - "core.courses.courseoverview": "Преглед на курс", "core.courses.courses": "Курсове", "core.courses.errorloadcourses": "Грешка при зареждането на курсовете.", "core.courses.frontpage": "Начална страница", - "core.courses.future": "Бъдещи", - "core.courses.inprogress": "В прогрес", - "core.courses.morecourses": "Още курсове", "core.courses.mycourses": "Моите курсове", + "core.courses.mymoodle": "Моето табло", "core.courses.nocourses": "Няма информация за курса, която да бъде показана.", - "core.courses.nocoursesfuture": "Няма бъдещи курсове", - "core.courses.nocoursesinprogress": "Няма курсове в прогрес", - "core.courses.nocoursesoverview": "Няма курсове", - "core.courses.nocoursespast": "Няма минали курсове", "core.courses.nocoursesyet": "Няма курсове в тази категория", "core.courses.nosearchresults": "Няма резултати", "core.courses.notenroled": "Вие не сте записани в този курс", - "core.courses.past": "Минали", "core.courses.search": "Търсене", "core.courses.searchcourses": "Търсене на курсове", "core.date": "Дата", @@ -760,12 +796,14 @@ "core.decsep": ",", "core.delete": "Изтриване", "core.description": "Описание", + "core.digitalminor_desc": "Моля, поискайте Ваш родител да се свърже с:", "core.done": "Извършено", "core.download": "Изтегляне", "core.downloading": "Изтегляне", "core.edit": "Редактиране", "core.error": "Грешка", "core.errordownloading": "Грешка при теглене на файл", + "core.explanationdigitalminor": "Тази информация е необходима за да определим дали сте в пълнолетна възраст. Това е възрастта на която хората могат да се съгласяват с изискванията и условията, при които техните лични данни да бъдат съхраняване и обработвани.", "core.filename": "Име на файл", "core.fileuploader.audio": "Аудио", "core.fileuploader.camera": "Камера", @@ -818,6 +856,7 @@ "core.login.connecttomoodle": "Свързване към Moodle", "core.login.createaccount": "Създаване на моята регистрация", "core.login.createuserandpass": "Изберете Вашето име и парола", + "core.login.emailconfirmsent": "

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

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

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

", "core.login.enterthewordsabove": "Въведете думите, изобразени по-горе", "core.login.firsttime": "За първи път ли сте тук?", "core.login.forgotten": "Забравени потребителско име или парола?", @@ -826,6 +865,7 @@ "core.login.helpmelogin": "

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

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

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

", "core.login.instructions": "Инструкции", "core.login.invalidaccount": "Моля проверете своите данни за влизане или помолете администратор да провери конфигурацията на сайта.", + "core.login.invaliddate": "Невалидна дата", "core.login.invalidemail": "Невалиден имейл адрес", "core.login.invalidmoodleversion": "Невалидна версия на Moodle. Най-старата изискване версия е 2.4.", "core.login.invalidsite": "Интернет адресът на сайта е невалиден.", @@ -837,6 +877,7 @@ "core.login.missingfirstname": "Даденото име липсва", "core.login.missinglastname": "Липсва фамилия", "core.login.mobileservicesnotenabled": "Услугите от Moodle не са активирани на Вашия сайт. Моля, свържете се с администратор на Moodle, ако мислите, че мобилния достъп трябва да бъде разрешен.", + "core.login.mustconfirm": "Трябва да потвърдите своето влизане", "core.login.newaccount": "Нова потребителска регистрация", "core.login.notloggedin": "Трябва са сте влезли в сайта.", "core.login.password": "Парола", @@ -867,7 +908,6 @@ "core.mainmenu.appsettings": "Настройки на приложението", "core.mainmenu.help": "Помощ", "core.mainmenu.logout": "Изход", - "core.mainmenu.mycourses": "Моите курсове", "core.mainmenu.website": "Уебсайт", "core.maxsizeandattachments": "Максимален размер за нови файлове: {{$a.size}}, максимален брой файлове: {{$a.attachments}}", "core.min": "мин", @@ -918,6 +958,7 @@ "core.phone": "Телефон", "core.pictureof": "Снимка на {{$a}}", "core.previous": "Обратно", + "core.proceed": "Продължаване", "core.pulltorefresh": "", "core.question.answer": "Отговор", "core.question.answersaved": "Отговорът съхранен", @@ -932,7 +973,9 @@ "core.question.partiallycorrect": "Отчасти верен", "core.question.questionno": "Въпрос {{$a}}", "core.question.requiresgrading": "Изисква оценяване", + "core.remove": "Премахване", "core.required": "Задължително", + "core.resources": "Ресурси", "core.restore": "Възстановяване", "core.search": "Търсене", "core.searchresults": "Резултати от търсенето", @@ -971,6 +1014,17 @@ "core.sizemb": "MB", "core.sizetb": "ТБ", "core.sortby": "Нареждане по", + "core.strftimedate": "%d %B %Y", + "core.strftimedateshort": "%d %B", + "core.strftimedatetime": "%d %B %Y, %H:%M", + "core.strftimedaydate": "%A, %d %B %Y", + "core.strftimedaydatetime": "%A, %d %B %Y, %H:%M", + "core.strftimedayshort": "%A, %d %B", + "core.strftimedaytime": "%a, %H:%M", + "core.strftimemonthyear": "%B %Y", + "core.strftimerecent": "%d %b, %H:%M", + "core.strftimerecentfull": "%a, %d %b %Y, %H:%M", + "core.strftimetime": "%H:%M", "core.submit": "Изпълняване", "core.success": "Успешно", "core.thisdirection": "ltr", @@ -985,13 +1039,13 @@ "core.user.country": "Държава", "core.user.description": "Описание", "core.user.detailsnotavailable": "Детайлите за този потребител не са достъпни за Вас.", - "core.user.editingteacher": "Учител", + "core.user.editingteacher": "Преподавател", "core.user.email": "Имейл адрес", "core.user.emailagain": "Имейл адрес (отново)", "core.user.firstname": "Име", "core.user.interests": "Интереси", "core.user.lastname": "Фамилия", - "core.user.manager": "Мениждър", + "core.user.manager": "Мениджър", "core.user.newpicture": "Нова снимка", "core.user.noparticipants": "Няма намерени участници за този курс", "core.user.participants": "Участници", @@ -1006,6 +1060,9 @@ "core.users": "Потребители", "core.view": "Преглед", "core.viewprofile": "Разглеждане на профила", + "core.whatisyourage": "Каква е Вашата възраст?", + "core.wheredoyoulive": "В коя страна живеете?", + "core.whyisthisrequired": "Защо е нужно това?", "core.years": "години", "core.yes": "Да" } \ No newline at end of file diff --git a/src/assets/lang/ca.json b/src/assets/lang/ca.json index 8e877c9b7..1d39831d2 100644 --- a/src/assets/lang/ca.json +++ b/src/assets/lang/ca.json @@ -1,15 +1,61 @@ { + "addon.badges.alignment": "Competència", "addon.badges.badgedetails": "Detalls de la insígnia", "addon.badges.badges": "Insígnies", + "addon.badges.bendorsement": "Suport", + "addon.badges.claimcomment": "Comentari de suport", + "addon.badges.claimid": "URL de reclamació", "addon.badges.contact": "Contacte", "addon.badges.dateawarded": "Data publicada", "addon.badges.expired": "Ha caducat", "addon.badges.expirydate": "Data d'expiració", + "addon.badges.imageauthoremail": "Correu de l'autor de la imatge", + "addon.badges.imageauthorname": "Nom de l'autor de la imatge", + "addon.badges.imageauthorurl": "URL de l'autor de la imatge", + "addon.badges.imagecaption": "Títol de la imatge", "addon.badges.issuancedetails": "Expiració de la insígnia", "addon.badges.issuerdetails": "Detalls de l'atorgador", + "addon.badges.issueremail": "Correu de l'atorgador", "addon.badges.issuername": "Nom de l'atorgador", + "addon.badges.issuerurl": "URL de l'atorgador", + "addon.badges.language": "Idioma", + "addon.badges.noalignment": "Aquesta insígnia no té cap competència associada.", "addon.badges.nobadges": "No hi ha insígnies disponibles.", + "addon.badges.norelated": "Aquesta insígnia no en té cap altra de relacionada.", "addon.badges.recipientdetails": "Detalls del receptor", + "addon.badges.relatedbages": "Insígnies relacionades", + "addon.badges.version": "Versió", + "addon.badges.warnexpired": "(Aquesta insígnia ha expirat!)", + "addon.block_activitymodules.pluginname": "Activitats", + "addon.block_myoverview.all": "Tots", + "addon.block_myoverview.favourites": "Destacats", + "addon.block_myoverview.future": "Futurs", + "addon.block_myoverview.hiddencourses": "Amagats", + "addon.block_myoverview.inprogress": "Actuals", + "addon.block_myoverview.lastaccessed": "Accedits recentment", + "addon.block_myoverview.morecourses": "Més cursos", + "addon.block_myoverview.nocourses": "Cap curs", + "addon.block_myoverview.past": "Passats", + "addon.block_myoverview.pluginname": "Resum del curs", + "addon.block_myoverview.title": "Nom del curs", + "addon.block_recentlyaccessedcourses.nocourses": "No hi ha cursos recents", + "addon.block_recentlyaccessedcourses.pluginname": "Cursos accedits recentment", + "addon.block_recentlyaccesseditems.noitems": "No hi ha ítems recents", + "addon.block_recentlyaccesseditems.pluginname": "Ítems accedits recentment", + "addon.block_sitemainmenu.pluginname": "Menú principal", + "addon.block_starredcourses.nocourses": "No hi ha cursos destacats", + "addon.block_starredcourses.pluginname": "Cursos destacats", + "addon.block_timeline.duedate": "Data de venciment", + "addon.block_timeline.next30days": "Propers 30 dies", + "addon.block_timeline.next3months": "Propers 3 mesos", + "addon.block_timeline.next6months": "Propers 6 mesos", + "addon.block_timeline.next7days": "Propers 7 dies", + "addon.block_timeline.nocoursesinprogress": "No hi ha cursos en curs", + "addon.block_timeline.noevents": "No hi ha activitats properes vençudes", + "addon.block_timeline.overdue": "Vençut", + "addon.block_timeline.pluginname": "Cronologia", + "addon.block_timeline.sortbycourses": "Ordena per cursos", + "addon.block_timeline.sortbydates": "Ordena per data", "addon.calendar.calendar": "Calendari", "addon.calendar.calendarevents": "Esdeveniments del calendari", "addon.calendar.defaultnotificationtime": "Hora de notificació per defecte", @@ -33,6 +79,8 @@ "addon.competency.competencies": "Competències", "addon.competency.competenciesmostoftennotproficientincourse": "Competències que més sovint no s'assoleixen en aquest curs", "addon.competency.coursecompetencies": "Competències del curs", + "addon.competency.coursecompetencyratingsarenotpushedtouserplans": "Les qualificacions per competències d'aquest curs no afecten els plans d'aprenentatge.", + "addon.competency.coursecompetencyratingsarepushedtouserplans": "Les qualificacions per competències d'aquest curs s'actualitzem als plans d'aprenentatge al moment.", "addon.competency.crossreferencedcompetencies": "Competències referenciades", "addon.competency.duedate": "Data límit", "addon.competency.errornocompetenciesfound": "No s'han trobat competències", @@ -55,7 +103,7 @@ "addon.competency.noevidence": "Cap evidència", "addon.competency.noplanswerecreated": "No s'ha creat cap pla d'aprenentatge.", "addon.competency.path": "Ruta:", - "addon.competency.planstatusactive": "Activa", + "addon.competency.planstatusactive": "Actiu", "addon.competency.planstatuscomplete": "Completat", "addon.competency.planstatusdraft": "Esborrany", "addon.competency.planstatusinreview": "En revisió", @@ -81,8 +129,8 @@ "addon.coursecompletion.coursecompletion": "Compleció del curs", "addon.coursecompletion.criteria": "Criteris", "addon.coursecompletion.criteriagroup": "Grup de criteris", - "addon.coursecompletion.criteriarequiredall": "Calen tots els criteris del dessota", - "addon.coursecompletion.criteriarequiredany": "Cal qualsevol dels criteris del dessota", + "addon.coursecompletion.criteriarequiredall": "Cal que es compleixin tots els criteris que es mostren a continuació", + "addon.coursecompletion.criteriarequiredany": "Cal que es compleixi algun dels criteris que es mostren a continuació", "addon.coursecompletion.inprogress": "En curs", "addon.coursecompletion.manualselfcompletion": "Auto-compleció manual", "addon.coursecompletion.notyetstarted": "No s'ha començat encara", @@ -99,36 +147,76 @@ "addon.files.privatefiles": "Fitxers privats", "addon.files.sitefiles": "Fitxers del lloc", "addon.messageoutput_airnotifier.processorsettingsdesc": "Configura els dispositius", + "addon.messages.acceptandaddcontact": "Accepta i afegeix a contactes", "addon.messages.addcontact": "Afegeix contacte", - "addon.messages.blockcontact": "Bloca contacte", - "addon.messages.blockcontactconfirm": "Ja no rebreu més missatges d'aquest contacte.", + "addon.messages.addcontactconfirm": "Esteu segur que voleu afegir {{$a}} als vostres contactes?", + "addon.messages.addtofavourites": "Destaca", + "addon.messages.addtoyourcontacts": "Afegiu aquest usuari als vostres contactes", "addon.messages.blocknoncontacts": "Impedeix que m'enviïn missatges els usuaris que no siguin a la meva llista de contactes", + "addon.messages.blockuser": "Bloca l'usuari", + "addon.messages.blockuserconfirm": "Esteu segur que voleu blocar {{$a}}?", + "addon.messages.contactableprivacy": "Accepta missatges de:", + "addon.messages.contactableprivacy_coursemember": "Els meus contactes i qualsevol persona dels meus cursos", + "addon.messages.contactableprivacy_onlycontacts": "Només els meus contactes", + "addon.messages.contactableprivacy_site": "Qualsevol al lloc", + "addon.messages.contactblocked": "Contacte blocat", "addon.messages.contactlistempty": "La llista de contactes és buida", "addon.messages.contactname": "Nom del contacte", + "addon.messages.contactrequestsent": "Sol·licitud de contacte enviada", "addon.messages.contacts": "Contactes", + "addon.messages.decline": "Rebutja", + "addon.messages.deleteallconfirm": "Confirmeu que voleu suprimir aquesta conversa per complet?", + "addon.messages.deleteconversation": "Elimina la conversa", "addon.messages.deletemessage": "Elimina el missatge", "addon.messages.deletemessageconfirmation": "Segur que voleu eliminar aquest missatge? Només s'eliminarà de la vostra història de missatges però encara serà visible per l'usuari que va eliminar o rebre el missatge.", "addon.messages.errordeletemessage": "S'ha produït un error mentre s'esborrava el missatge.", "addon.messages.errorwhileretrievingcontacts": "S'ha produït un error en recuperar els contactes del servidor.", "addon.messages.errorwhileretrievingdiscussions": "S'ha produït un error mentre es recuperaven els debats del servidor.", "addon.messages.errorwhileretrievingmessages": "S'ha produït un error descarregant els missatges.", + "addon.messages.errorwhileretrievingusers": "S'ha produït un error descarregant els usuaris del servidor.", + "addon.messages.groupinfo": "Informació del grup", + "addon.messages.info": "Informació", + "addon.messages.isnotinyourcontacts": "{{$a}} no és a la vostra llista de contactes", "addon.messages.message": "Missatge", "addon.messages.messagenotsent": "El missatge no s'ha enviat. Si us plau, intenteu-ho més tard", "addon.messages.messagepreferences": "Preferències dels missatges", "addon.messages.messages": "Missatges", "addon.messages.newmessage": "Missatge nou", "addon.messages.newmessages": "Nous missatges", - "addon.messages.nomessages": "No teniu missatges pendents", + "addon.messages.nocontactrequests": "No teniu cap sol·licitud de contacte", + "addon.messages.nocontactsgetstarted": "No teniu cap contacte destacat", + "addon.messages.nofavourites": "No teniu converses destacades", + "addon.messages.nomessagesfound": "No s'han trobat missatges", + "addon.messages.noncontacts": "No contactes", "addon.messages.nousersfound": "No s'han trobat usuaris", + "addon.messages.numparticipants": "{{$a}} participants", "addon.messages.removecontact": "Suprimeix contacte", "addon.messages.removecontactconfirm": "El contacte s'eliminarà de la vostra llista de contactes.", + "addon.messages.removefromfavourites": "Deixa de destacar", + "addon.messages.removefromyourcontacts": "Suprimeix dels vostres contactes", + "addon.messages.requests": "Sol·licituds", + "addon.messages.requirecontacttomessage": "Cal que sol·liciteu a {{$a}} que us afegeixi com a contacte per poder enviar-li un missatge.", + "addon.messages.searchcombined": "Cerca gent i missatges", + "addon.messages.searchnocontactsfound": "No s'ha trobat cap contacte", + "addon.messages.searchnomessagesfound": "No s'ha trobat cap missatge", + "addon.messages.searchnononcontactsfound": "No s'ha trobat cap usuari no contacte", + "addon.messages.sendcontactrequest": "Envia una sol·licitud de contacte", + "addon.messages.showdeletemessages": "Mostra els missatges eliminats", "addon.messages.type_blocked": "Bloquejat", "addon.messages.type_offline": "Fora de línia", "addon.messages.type_online": "En línia", "addon.messages.type_search": "Resultats de la cerca", "addon.messages.type_strangers": "Altres", - "addon.messages.unblockcontact": "Desbloca contacte", + "addon.messages.unabletomessage": "No podeu enviar missatges a aquest usuari", + "addon.messages.unblockuser": "Desbloca l'usuari", + "addon.messages.unblockuserconfirm": "Esteu segur que voleu desblocar {{$a}}?", + "addon.messages.userwouldliketocontactyou": "{{$a}} vol contactar-vos", + "addon.messages.warningconversationmessagenotsent": "No s'han pogut enviar missatges a la conversa {{user}}. {{error}}", "addon.messages.warningmessagenotsent": "No s'han pogut enviar missatges a l'usuari {{user}}. {{error}}", + "addon.messages.wouldliketocontactyou": "Vol contactar-vos", + "addon.messages.you": "Vós:", + "addon.messages.youhaveblockeduser": "Heu bloquejat aquest usuari en el passat", + "addon.messages.yourcontactrequestpending": "La vostra sol·licitud de contacte amb {{$a}} està pendent", "addon.mod_assign.acceptsubmissionstatement": "Accepteu el compromís de tramesa.", "addon.mod_assign.addattempt": "Permet un altre intent", "addon.mod_assign.addnewattempt": "Afegeix un altre intent", @@ -155,7 +243,7 @@ "addon.mod_assign.defaultteam": "Grup per defecte", "addon.mod_assign.duedate": "Data de venciment", "addon.mod_assign.duedateno": "Sense data de venciment", - "addon.mod_assign.duedatereached": "La data de venciment de la tasca ja ha passat", + "addon.mod_assign.duedatereached": "La data de venciment per a aquesta tasca ja ha passat", "addon.mod_assign.editingstatus": "S'està editant l'estat", "addon.mod_assign.editsubmission": "Edita la tramesa", "addon.mod_assign.erroreditpluginsnotsupported": "No podeu afegir o editar una tramesa en l'aplicació perquè alguns connectors no s'admeten per editar:", @@ -165,7 +253,9 @@ "addon.mod_assign.grade": "Qualificació", "addon.mod_assign.graded": "Qualificada", "addon.mod_assign.gradedby": "Qualificat per", + "addon.mod_assign.gradedfollowupsubmit": "Qualificada: seguiment de la tramesa rebuda", "addon.mod_assign.gradedon": "Qualificat el", + "addon.mod_assign.gradelocked": "Aquesta qualificació està bloquejada o rectificada al llibre de qualificacions.", "addon.mod_assign.gradenotsynced": "La puntuació no està sincronitzada", "addon.mod_assign.gradeoutof": "Qualificació sobre {{$a}}", "addon.mod_assign.gradingstatus": "Estat de la qualificació", @@ -180,13 +270,14 @@ "addon.mod_assign.markingworkflowstatereadyforrelease": "A punt per publicar", "addon.mod_assign.markingworkflowstatereadyforreview": "Avaluació completada", "addon.mod_assign.markingworkflowstatereleased": "Publicada", + "addon.mod_assign.modulenameplural": "Tasques", "addon.mod_assign.multipleteams": "Membre de més d'un grup", "addon.mod_assign.multipleteams_desc": "La tasca requereix la tramesa en grups. Sou membre de més d'un grup. Per poder trametre heu de ser membre de només un grup. Poseu-vos en contacte amb el professor per canviar la vostra adhesió a un grup.", "addon.mod_assign.noattempt": "Cap intent", "addon.mod_assign.nomoresubmissionsaccepted": "Sols és permès per als participants que tenen concedida una pròrroga.", "addon.mod_assign.noonlinesubmissions": "Aquesta tasca no requereix que trameteu res en línia.", "addon.mod_assign.nosubmission": "No s'ha tramès res per a aquesta tasca", - "addon.mod_assign.notallparticipantsareshown": "Els participants que no tinguin cap tramesa no es mostren", + "addon.mod_assign.notallparticipantsareshown": "Els participants que no tinguin cap tramesa no es mostren.", "addon.mod_assign.noteam": "No és membre de cap grup", "addon.mod_assign.noteam_desc": "La tasca requereix la tramesa en grups. No sou membre de cap grup; per tant, no podeu fer una tramesa. Poseu-vos en contacte amb el professor per afegir-vos a un grup.", "addon.mod_assign.notgraded": "Sense qualificació", @@ -234,6 +325,7 @@ "addon.mod_assign_submission_file.pluginname": "Fitxers de la tramesa", "addon.mod_assign_submission_onlinetext.pluginname": "Trameses de text en línia", "addon.mod_book.errorchapter": "S'ha produït un error en llegir el capítol del llibre.", + "addon.mod_book.modulenameplural": "Llibres", "addon.mod_chat.beep": "bip", "addon.mod_chat.currentusers": "Usuaris actuals", "addon.mod_chat.enterchat": "Feu clic aquí per entrar al xat", @@ -246,6 +338,7 @@ "addon.mod_chat.messagebeepsyou": "{{$a}} t'acaba de fer bip!", "addon.mod_chat.messageenter": "{{$a}} acaba d'entrar en aquest xat", "addon.mod_chat.messageexit": "{{$a}} ha abandonat aquest xat", + "addon.mod_chat.modulenameplural": "Xats", "addon.mod_chat.mustbeonlinetosendmessages": "Heu d'estar connectat per poder enviar missatges", "addon.mod_chat.nomessages": "No hi ha missatges encara", "addon.mod_chat.send": "Envia", @@ -256,6 +349,7 @@ "addon.mod_choice.errorgetchoice": "S'ha produït un error en recuperar les dades de la consulta.", "addon.mod_choice.expired": "Aquesta activitat es va tancar el dia {{$a}} i ja no està disponible.", "addon.mod_choice.full": "(Complet)", + "addon.mod_choice.modulenameplural": "Consultes", "addon.mod_choice.noresultsviewable": "A hores d'ara no es poden veure els resultats", "addon.mod_choice.notopenyet": "Aquesta activitat no estarà disponible fins al dia {{$a}}", "addon.mod_choice.numberofuser": "Nombre de respostes", @@ -293,8 +387,10 @@ "addon.mod_data.errormustsupplyvalue": "Cal que poseu un valor aquí.", "addon.mod_data.expired": "Aquesta activitat es va tancar el dia {{$a}} i ja no està disponible", "addon.mod_data.fields": "Camps", + "addon.mod_data.foundrecords": "S'han trobat els registres:{{$a.num}}/{{$a.max}} (Reinicialitza filtres)", "addon.mod_data.latlongboth": "Cal posar tant la latitud com la longitud.", "addon.mod_data.menuchoose": "Trieu...", + "addon.mod_data.modulenameplural": "Bases de dades", "addon.mod_data.more": "Més", "addon.mod_data.nomatch": "No s'han trobat entrades que coincideixin", "addon.mod_data.norecords": "No hi ha entrades en la base de dades", @@ -326,6 +422,7 @@ "addon.mod_feedback.feedbackopen": "Permet les respostes de", "addon.mod_feedback.mapcourses": "Associa una retroacció a cursos.", "addon.mod_feedback.mode": "Mode", + "addon.mod_feedback.modulenameplural": "Retroacció", "addon.mod_feedback.next_page": "Pàgina següent", "addon.mod_feedback.non_anonymous": "El nom de l'usuari es registrarà i es mostrarà amb les respostes", "addon.mod_feedback.non_anonymous_entries": "Entrades no anònimes ({{$a}})", @@ -346,6 +443,7 @@ "addon.mod_feedback.started": "S'ha iniciat", "addon.mod_feedback.this_feedback_is_already_submitted": "Heu completat aquesta activitat", "addon.mod_folder.emptyfilelist": "No hi ha fitxers per mostrar.", + "addon.mod_folder.modulenameplural": "Carpetes", "addon.mod_forum.addanewdiscussion": "Afegeix un tema de debat nou", "addon.mod_forum.addanewquestion": "Afegeix una pregunta nova", "addon.mod_forum.addanewtopic": "Afegeix un tema nou", @@ -368,6 +466,7 @@ "addon.mod_forum.modeflatnewestfirst": "Visualitza les respostes, començant per la més recent", "addon.mod_forum.modeflatoldestfirst": "Visualitza les respostes, començant per la més antiga", "addon.mod_forum.modenested": "Visualitza les respostes escalonades", + "addon.mod_forum.modulenameplural": "Fòrums", "addon.mod_forum.numdiscussions": "{{numdiscussions}} discussions", "addon.mod_forum.numreplies": "{{numreplies}} respostes", "addon.mod_forum.posttoforum": "Envia al fòrum", @@ -403,9 +502,11 @@ "addon.mod_glossary.fillfields": "El concepte i la definició són camps obligatoris.", "addon.mod_glossary.fullmatch": "Enllaça només paraules completes", "addon.mod_glossary.linking": "Enllaços automàtics", + "addon.mod_glossary.modulenameplural": "Glossaris", "addon.mod_glossary.noentriesfound": "No s'han trobat entrades.", "addon.mod_glossary.searchquery": "La vostra cerca", "addon.mod_imscp.deploymenterror": "Error en el contingut del paquet!", + "addon.mod_imscp.modulenameplural": "Paquets de contingut IMS", "addon.mod_imscp.showmoduledescription": "Mostra la descripció", "addon.mod_lesson.answer": "Resposta", "addon.mod_lesson.attempt": "Intent: {{$a}}", @@ -444,11 +545,12 @@ "addon.mod_lesson.lessonmenu": "Menú de la lliçó", "addon.mod_lesson.lessonstats": "Estadístiques de la lliçó", "addon.mod_lesson.linkedmedia": "Fitxer enllaçat", - "addon.mod_lesson.loginfail": "Ha fallat l'inici de sessió, torneu-ho a provar...", + "addon.mod_lesson.loginfail": "Ha fallat l'inici de sessió, torneu a provar-ho...", "addon.mod_lesson.lowscore": "Puntuació més baixa", "addon.mod_lesson.lowtime": "Temps més baix", "addon.mod_lesson.maximumnumberofattemptsreached": "Heu arribat al nombre màxim d'intents. Ara passareu a la pàgina següent.", "addon.mod_lesson.modattemptsnoteacher": "No podeu fer la revisió dels estudiants.", + "addon.mod_lesson.modulenameplural": "Lliçons", "addon.mod_lesson.noanswer": "Una o més preguntes no tenen resposta. Torneu enrere i responeu-les.", "addon.mod_lesson.nolessonattempts": "Encara no s'ha registrat cap intent en aquesta lliçó", "addon.mod_lesson.nolessonattemptsgroup": "Ningú del grup {{$a}} ha fet encara cap intent d'aquesta lliçó.", @@ -472,9 +574,9 @@ "addon.mod_lesson.retakelabelshort": "{{retake}}: {{grade}} {{timestart}}", "addon.mod_lesson.review": "Revisa", "addon.mod_lesson.reviewlesson": "Revisa la lliçó", - "addon.mod_lesson.reviewquestionback": "Sí, vull tornar-ho a provar", + "addon.mod_lesson.reviewquestionback": "Sí, vull tornar a provar-ho", "addon.mod_lesson.reviewquestioncontinue": "No, vull anar a la pregunta següent", - "addon.mod_lesson.secondpluswrong": "Incorrecte de nou. Voleu tornar-ho a provar?", + "addon.mod_lesson.secondpluswrong": "Incorrecte de nou. Voleu tornar a provar-ho?", "addon.mod_lesson.submit": "Envia", "addon.mod_lesson.teacherjumpwarning": "En aquesta lliçó s'utilitza un salt {{$a.cluster}} o un salt {{$a.unseen}}. En lloc d'això, s'utilitzarà un salt a la pàgina següent. Inicieu sessió com a estudiant per comprovar aquests salts.", "addon.mod_lesson.teacherongoingwarning": "La puntuació acumulada només es mostra a l'estudiant. Inicieu sessió com a estudiant per comprovar la puntuació acumulada.", @@ -493,7 +595,9 @@ "addon.mod_lti.errorgetlti": "S'ha produït un error en recuperar les dades del mòdul.", "addon.mod_lti.errorinvalidlaunchurl": "L'adreça URL no és vàlida.", "addon.mod_lti.launchactivity": "Executa l'activitat", + "addon.mod_lti.modulenameplural": "Eines externes", "addon.mod_page.errorwhileloadingthepage": "S'ha produït un error carregant el contingut de la pàgina.", + "addon.mod_page.modulenameplural": "Pàgines", "addon.mod_quiz.attemptfirst": "Primer intent", "addon.mod_quiz.attemptlast": "Darrer intent", "addon.mod_quiz.attemptnumber": "Intent", @@ -528,6 +632,7 @@ "addon.mod_quiz.grademethod": "Mètode de qualificació", "addon.mod_quiz.gradesofar": "{{$a.method}}: {{$a.mygrade}} / {{$a.quizgrade}}.", "addon.mod_quiz.marks": "Punts", + "addon.mod_quiz.modulenameplural": "Qüestionaris", "addon.mod_quiz.mustbesubmittedby": "Aquest intent s'ha d'enviar abans de {{$a}}.", "addon.mod_quiz.noquestions": "Encara no s'han afegit preguntes", "addon.mod_quiz.noreviewattempt": "No teniu permís per revisar aquest intent.", @@ -572,6 +677,7 @@ "addon.mod_quiz.yourfinalgradeis": "La vostra qualificació final en aquest qüestionari és {{$a}}.", "addon.mod_resource.errorwhileloadingthecontent": "S'ha produït un error carregant el contingut.", "addon.mod_resource.modifieddate": "Modificat {{$a}}", + "addon.mod_resource.modulenameplural": "Fitxers", "addon.mod_resource.openthefile": "Obre el fitxer", "addon.mod_resource.uploadeddate": "Penjat dia {{$a}}", "addon.mod_scorm.asset": "Recurs", @@ -608,6 +714,7 @@ "addon.mod_scorm.incomplete": "Incomplet", "addon.mod_scorm.lastattempt": "Darrer intent completat", "addon.mod_scorm.mode": "Mode", + "addon.mod_scorm.modulenameplural": "Paquets SCORM", "addon.mod_scorm.newattempt": "Comença un nou intent", "addon.mod_scorm.noattemptsallowed": "Nombre d'intents permesos", "addon.mod_scorm.noattemptsmade": "Nombre d'intents realitzats", @@ -627,10 +734,12 @@ "addon.mod_survey.errorgetsurvey": "S'ha produït un error recuperant les dades de l'enquesta.", "addon.mod_survey.ifoundthat": "Trobat:", "addon.mod_survey.ipreferthat": "Prefereixo això", + "addon.mod_survey.modulenameplural": "Enquestes", "addon.mod_survey.responses": "Respostes", "addon.mod_survey.results": "Resultats", "addon.mod_survey.surveycompletednograph": "Heu completat aquesta enquesta.", "addon.mod_url.accessurl": "Vés a la URL", + "addon.mod_url.modulenameplural": "URL", "addon.mod_url.pointingtourl": "La URL on apunta aquest recurs.", "addon.mod_wiki.cannoteditpage": "No podeu editar aquesta pàgina.", "addon.mod_wiki.createpage": "Crea una pàgina", @@ -639,6 +748,7 @@ "addon.mod_wiki.errornowikiavailable": "Aquest wiki no té cap contingut encara.", "addon.mod_wiki.gowikihome": "Vés a l'inici del wiki", "addon.mod_wiki.map": "Mapa", + "addon.mod_wiki.modulenameplural": "Wikis", "addon.mod_wiki.newpagehdr": "Pàgina nova", "addon.mod_wiki.newpagetitle": "Títol de la pàgina nova", "addon.mod_wiki.nocontent": "No hi ha contingut per a aquesta pàgina", @@ -677,6 +787,7 @@ "addon.mod_workshop.gradinggradecalculated": "Qualificació calculada per l'avaluació", "addon.mod_workshop.gradinggradeof": "Qualificació de la tasca d'avaluació (de {{$a}})", "addon.mod_workshop.gradinggradeover": "Rectifica la qualificació de l'avaluació", + "addon.mod_workshop.modulenameplural": "Tallers", "addon.mod_workshop.nogradeyet": "Encara sense qualificar", "addon.mod_workshop.notassessed": "No avaluat encara", "addon.mod_workshop.notoverridden": "No rectificada", @@ -692,6 +803,7 @@ "addon.mod_workshop.submissiondeleteconfirm": "Segur que voleu eliminar la tramesa?", "addon.mod_workshop.submissiongrade": "Qualificació de la tramesa", "addon.mod_workshop.submissiongradeof": "Qualificació de la tramesa (de {{$a}})", + "addon.mod_workshop.submissionrequiredcontent": "Cal que escriviu text o afegiu un fitxer.", "addon.mod_workshop.submissionrequiredtitle": "Cal que introduïu un títol.", "addon.mod_workshop.submissionsreport": "Informe de les tasques del taller", "addon.mod_workshop.submissiontitle": "Títol", @@ -702,7 +814,8 @@ "addon.mod_workshop.switchphase50": "Tanca el taller", "addon.mod_workshop.userplan": "Planificador del taller", "addon.mod_workshop.userplancurrentphase": "Fase actual", - "addon.mod_workshop.warningassessmentmodified": "La tramesa s'ha modificat al lloc web.", + "addon.mod_workshop.warningassessmentmodified": "L'avaluació s'ha modificat al lloc web.", + "addon.mod_workshop.warningsubmissionmodified": "La tramesa s'ha modificat al lloc web.", "addon.mod_workshop.weightinfo": "Pes: {{$a}}", "addon.mod_workshop.yourassessment": "L'avaluació que heu fet", "addon.mod_workshop.yourassessmentfor": "La vostra avaluació per a: {{$a}}", @@ -990,6 +1103,7 @@ "assets.mimetypes.application/pdf": "Document PDF", "assets.mimetypes.application/vnd.moodle.backup": "Còpia de seguretat Moodle", "assets.mimetypes.application/vnd.ms-excel": "Full de càlcul Excel", + "assets.mimetypes.application/vnd.ms-excel.sheet.macroEnabled.12": "Llibre de treball d'Excel 2007 amb macros habilitades", "assets.mimetypes.application/vnd.ms-powerpoint": "Presentació PowerPoint", "assets.mimetypes.application/vnd.oasis.opendocument.spreadsheet": "Full de càlcul d'OpenDocument", "assets.mimetypes.application/vnd.oasis.opendocument.spreadsheet-template": "Plantilla de full de càlcul d'OpenDocument", @@ -1014,7 +1128,9 @@ "assets.mimetypes.document/unknown": "Fitxer", "assets.mimetypes.group:archive": "Fitxers d'arxiu", "assets.mimetypes.group:audio": "Fitxers d'àudio", + "assets.mimetypes.group:document": "Documents", "assets.mimetypes.group:html_audio": "Fitxers d'àudio admesos nativament pels navegadors", + "assets.mimetypes.group:html_track": "Fitxers de seguiment HTML", "assets.mimetypes.group:html_video": "Fitxers de vídeo admesos nativament pels navegadors", "assets.mimetypes.group:image": "Fitxers d'imatge", "assets.mimetypes.group:presentation": "Fitxers de presentació de diapositives", @@ -1026,13 +1142,19 @@ "assets.mimetypes.group:web_image": "Fitxers d'imatge utilitzats a la web", "assets.mimetypes.group:web_video": "Fitxers de vídeo utilitzats a la web", "assets.mimetypes.image": "Imatge ({{$a.MIMETYPE2}})", + "assets.mimetypes.image/vnd.microsoft.icon": "Icona de Windows", + "assets.mimetypes.text/css": "Full d'estil en cascada", + "assets.mimetypes.text/csv": "Valors separats per comes", "assets.mimetypes.text/html": "Document HTML", "assets.mimetypes.text/plain": "Fitxer de text", "assets.mimetypes.text/rtf": "Document RTF", + "assets.mimetypes.text/vtt": "Pista de text de vídeo web", "assets.mimetypes.video": "Fitxer de vídeo ({{$a.EXT}})", "core.accounts": "Comptes", "core.add": "Afegeix", "core.agelocationverification": "Verificació de l'edat i procedència", + "core.ago": "Fa {{$a}}", + "core.all": "Tots", "core.allparticipants": "Tots els participants", "core.android": "Android", "core.answer": "Resposta", @@ -1100,18 +1222,21 @@ "core.course.errorgetmodule": "S'ha produït un error en recuperar les dades del mòdul.", "core.course.hiddenfromstudents": "Ocult per a l'estudiantat", "core.course.hiddenoncoursepage": "Disponible, però no es mostra a la pàgina principal del curs", + "core.course.manualcompletionnotsynced": "La compleció manual no està sincronitzada.", "core.course.nocontentavailable": "No hi ha contingut disponible en aquest moment.", "core.course.overriddennotice": "La vostra qualificació final en aquesta activitat s'ha modificat manualment.", "core.course.refreshcourse": "Actualitza el curs", "core.course.sections": "Seccions", "core.course.useactivityonbrowser": "Tot i així, podeu fer-la servir al navegador del dispositiu.", + "core.course.warningmanualcompletionmodified": "S'ha modificat la compleció manual d'una activitat a la web.", + "core.course.warningofflinemanualcompletiondeleted": "S'han eliminat algunes dades fora de línia de compleció manual del curs «{{name}}».{{error}}", "core.coursedetails": "Detalls del curs", + "core.courses.addtofavourites": "Destaca el curs", "core.courses.allowguests": "Aquest curs permet entrar als usuaris visitants", "core.courses.availablecourses": "Cursos disponibles", "core.courses.cannotretrievemorecategories": "No es poden recuperar categories més enllà del nivell {{$a}}.", "core.courses.categories": "Categories de cursos", "core.courses.confirmselfenrol": "Segur que voleu autoinscriure-us en aquest curs?", - "core.courses.courseoverview": "Resum de cursos", "core.courses.courses": "Cursos", "core.courses.downloadcourses": "Descarrega els cursos", "core.courses.enrolme": "Inscriu-me", @@ -1121,29 +1246,24 @@ "core.courses.errorselfenrol": "S'ha produït un error durant l'autoinscripció.", "core.courses.filtermycourses": "Filtrar els meus cursos", "core.courses.frontpage": "Primera plana", - "core.courses.future": "Futurs", - "core.courses.inprogress": "Actuals", - "core.courses.morecourses": "Més cursos", + "core.courses.hidecourse": "Amaga el curs", "core.courses.mycourses": "Els meus cursos", + "core.courses.mymoodle": "Tauler", "core.courses.nocourses": "No hi ha informació de cursos per mostrar.", - "core.courses.nocoursesfuture": "Cap curs futur", - "core.courses.nocoursesinprogress": "Actualment no s'està fent cap curs", - "core.courses.nocoursesoverview": "No hi ha cap curs", - "core.courses.nocoursespast": "Cap curs passat", "core.courses.nocoursesyet": "No hi ha cursos en aquesta categoria", "core.courses.nosearchresults": "Cap resultat", "core.courses.notenroled": "No us heu inscrit en aquest curs", "core.courses.notenrollable": "No podeu autoinscriure-us en aquest curs.", "core.courses.password": "Clau d'inscripció", - "core.courses.past": "Passats", "core.courses.paymentrequired": "Aquest curs requereix pagament.", "core.courses.paypalaccepted": "S'accepten pagaments via PayPal", - "core.courses.recentlyoverdue": "Recentment vençut", + "core.courses.removefromfavourites": "Deixa de destacar el curs", "core.courses.search": "Cerca", "core.courses.searchcourses": "Cerca cursos", "core.courses.searchcoursesadvice": "Podeu fer servir el botó de cercar cursos per accedir als cursos com a convidat o autoinscriure-us en cursos que ho permetin.", "core.courses.selfenrolment": "Autoinscripció", "core.courses.sendpaymentbutton": "Envia pagament via Paypal", + "core.courses.show": "Mostra el curs", "core.courses.totalcoursesearchresults": "Total de cursos: {{$a}}", "core.currentdevice": "Dispositiu actual", "core.datastoredoffline": "S'han desat les dades al dispositiu perquè no s'han pogut enviar. S'enviaran de manera automàtica més tard.", @@ -1180,6 +1300,7 @@ "core.errorinvalidform": "El formulari conté errors. Assegureu-vos que tots els camps requerits estan emplenats i que les dades introduïdes són vàlides.", "core.errorinvalidresponse": "S'ha rebut una resposta no vàlida. Contacteu amb l'administrador de Moodle si l'error persisteix.", "core.errorloadingcontent": "S'ha produït un error en carregar el contingut.", + "core.errorofflinedisabled": "La navegació fora de línia està deshabilitada al seu lloc web. Necessita tornar-se a connectar a Internet per fer servir l'aplicació.", "core.erroropenfilenoapp": "S'ha produït un error en obrir el fitxer: no s'ha trobat cap aplicació per obrir aquest tipus de fitxer.", "core.erroropenfilenoextension": "S'ha produït un error obrint el fitxer: el fitxer no té extensió.", "core.erroropenpopup": "Aquesta activitat està intentant obrir una finestra emergent. Aquesta aplicació no ho suporta.", @@ -1187,6 +1308,7 @@ "core.errorsync": "S'ha produït un error durant la sincronització. Torneu-ho a provar més tard.", "core.errorsyncblocked": "Aquest/a {{$a}} no es pot sincronitzar ara mateix perquè ja hi ha un procés en marxa. Torneu-ho a provar més tard. Si el problema persisteix, reinicieu l'aplicació.", "core.explanationdigitalminor": "Aquesta informació es demana per determinar si la vostra edat està per sobre de l'edat mínima de consentiment digital. Només amb aquesta edat una persona pot acceptar els termes i les condicions de privadesa, perquè les seves dades siguin legalment desades i processades pel sistema.", + "core.favourites": "Destacats", "core.filename": "Nom de fitxer", "core.filenameexist": "Ja existeix un fitxer amb el nom: {{$a}}", "core.fileuploader.addfiletext": "Afegeix un fitxer", @@ -1210,6 +1332,7 @@ "core.fileuploader.more": "Més", "core.fileuploader.photoalbums": "Àlbums de fotos", "core.fileuploader.readingfile": "S'està llegint un fitxer", + "core.fileuploader.readingfileperc": "S'està llegint el fitxer: {{$a}}%", "core.fileuploader.selectafile": "Selecciona un fitxer", "core.fileuploader.uploadafile": "Penja un fitxer", "core.fileuploader.uploading": "S'està penjant", @@ -1269,16 +1392,19 @@ "core.login.createuserandpass": "Trieu un nom d'usuari i una contrasenya", "core.login.credentialsdescription": "Introduïu el vostre nom d'usuari i contrasenya per iniciar sessió.", "core.login.emailconfirmsent": "

S'ha enviat un correu a la vostra adreça {{$a}}

\n

Conté instruccions senzilles per completar el registre.

\n

Si seguiu tenint problemes, contacteu amb l'administrador del lloc.

", + "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.", + "core.login.findyoursite": "Trobeu el vostre lloc web", "core.login.firsttime": "És la primera vegada que veniu aquí?", "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 milers de llocs Moodle al món. Aquesta aplicació només funciona amb els llocs Moodle que tenen habilitat els serveis web per als dispositius mòbils.

Si no podeu connectar-vos al vostre Moodle, contacteu amb l'administrador del lloc per tal que revisi la documentació http://docs.moodle.org/en/Mobile_app

Per provar l'aplicació a un Moodle de demostració accediu amb teacher o student a URL del lloc i feu clic al botó Connecta.

", + "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", "core.login.invalidaccount": "Verifiqueu el vostre nom d'usuari/ària i contrasenya o demaneu a l'administració de Moodle que comprovi la configuració del lloc.", "core.login.invaliddate": "La data no és vàlida", @@ -1290,6 +1416,8 @@ "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ó", @@ -1300,6 +1428,7 @@ "core.login.missingfirstname": "Falta el nom", "core.login.missinglastname": "Falten els cognoms", "core.login.mobileservicesnotenabled": "El lloc Moodle no té habilitats els serveis mòbils. Contacteu amb l'administració del vostre Moodle si penseu que haurien d'estar habilitats.", + "core.login.mustconfirm": "Necessiteu confirmar el vostre inici de sessió", "core.login.newaccount": "Nou compte", "core.login.newsitedescription": "Introduïu l'URL del vostre lloc Moodle. Tingueu en compte que podria no estar configurat per treballar amb aquesta aplicació.", "core.login.notloggedin": "Heu d'entrar al lloc.", @@ -1316,15 +1445,19 @@ "core.login.problemconnectingerrorcontinue": "Torneu a comprovar que l'adreça sigui correcta i proveu-ho de bell nou.", "core.login.profileinvaliddata": "El valor no és vàlid", "core.login.recaptchachallengeimage": "Imatge del repte de reCAPTCHA", + "core.login.recaptchaexpired": "La verificació ha expirat. Contesta la pregunta de seguretat de nou.", + "core.login.recaptchaincorrect": "La resposta a la pregunta de seguretat no és correcta.", "core.login.reconnect": "Torna a connectar", "core.login.reconnectdescription": "El testimoni d'autenticació és invàlid o està caducat, heu de tornar a connectar a la plataforma.", "core.login.reconnectssodescription": "El testimoni d'autenticació és invàlid o està caducat, heu de tornar a connectar a la plataforma. Heu d'entrar des d'un navegador web.", + "core.login.resendemail": "Reenvia el correu", "core.login.searchby": "Cerca per:", "core.login.security_question": "Pregunta de seguretat", "core.login.selectacountry": "Selecciona un país", "core.login.selectsite": "Seleccioneu un lloc:", "core.login.signupplugindisabled": "{{$a}} no està habilitat.", "core.login.siteaddress": "Adreça del lloc", + "core.login.sitehasredirect": "El vostre lloc conté una redirecció HTTP com a mínim. L'aplicació no pot seguir les redireccions, això pot ser un problema que fa que l'aplicació no pugui connectar-se al vostre lloc.", "core.login.siteinmaintenance": "El vostre lloc està en mode de manteniment", "core.login.sitepolicynotagreederror": "No s'ha acceptat la política del lloc.", "core.login.siteurl": "URL del lloc", @@ -1343,8 +1476,6 @@ "core.mainmenu.changesite": "Canvia de lloc", "core.mainmenu.help": "Ajuda", "core.mainmenu.logout": "Surt", - "core.mainmenu.mycourses": "Els meus cursos", - "core.mainmenu.togglemenu": "Canvia menú", "core.mainmenu.website": "Lloc web", "core.maxsizeandattachments": "Mida màxima dels fitxers nous: {{$a.size}}, màxim d'adjuncions: {{$a.attachments}}", "core.min": "minut", @@ -1379,6 +1510,7 @@ "core.more": "més", "core.mygroups": "Els meus grups", "core.name": "Nom", + "core.networkerroriframemsg": "Aquest contingut no està disponible fora de línia. Comproveu la vostra connexió a internet i torneu-ho provar.", "core.networkerrormsg": "La connexió a la xarxa no està habilitada o no funciona.", "core.never": "Mai", "core.next": "Següent", @@ -1387,6 +1519,7 @@ "core.nograde": "Sense qualificació", "core.none": "Cap", "core.nopasswordchangeforced": "No podeu continuar sense canviar la contrasenya.", + "core.nopermissionerror": "Actualment no teniu permisos per a fer això", "core.nopermissions": "Actualment no teniu permisos per a fer això ({{$a}})", "core.noresults": "Sense resultats", "core.notapplicable": "n/d", @@ -1411,6 +1544,7 @@ "core.pulltorefresh": "Estira per actualitzar", "core.question.answer": "Resposta", "core.question.answersaved": "Resposta desada", + "core.question.cannotdeterminestatus": "No es pot determinar l'estat", "core.question.certainty": "Certesa", "core.question.complete": "Completa", "core.question.correct": "Correcte", @@ -1431,8 +1565,10 @@ "core.quotausage": "Actualment heu utilitzat {{$a.used}} del vostre total de {{$a.total}}", "core.redirectingtosite": "Sereu redireccionats al lloc web.", "core.refresh": "Refresca", + "core.remove": "Suprimeix", "core.required": "Necessari", "core.requireduserdatamissing": "En aquest perfil d'usuari hi manquen dades requerides. Si us plau ompliu aquestes dades i torneu a intentar-ho.
{{$a}}", + "core.resources": "Recursos", "core.restore": "Restaura", "core.retry": "Reintenta", "core.save": "Desa", @@ -1457,6 +1593,7 @@ "core.settings.cordovaversion": "Versió Cordova", "core.settings.currentlanguage": "Idioma actual", "core.settings.debugdisplay": "Visualitza missatges de depuració", + "core.settings.debugdisplaydescription": "Si està habilitat, els missatges d'error mostraran tota la informació possible de l'error.", "core.settings.deletesitefiles": "Esteu segur que voleu esborrar els fitxers baixats d'aquest lloc?", "core.settings.deletesitefilestitle": "Elimina els fitxers del lloc", "core.settings.deviceinfo": "Informació del dispositiu", @@ -1487,6 +1624,7 @@ "core.settings.privacypolicy": "Política de privadesa", "core.settings.reportinbackground": "Informa dels errors automàticament", "core.settings.settings": "Paràmetres", + "core.settings.showdownloadoptions": "Mostra les opcions de descàrrega", "core.settings.sites": "Llocs", "core.settings.spaceusage": "Utilització de l'espai", "core.settings.synchronization": "Sincronització", @@ -1518,6 +1656,22 @@ "core.sizetb": "TB", "core.sorry": "Ho sentim...", "core.sortby": "Ordena per", + "core.start": "Comença", + "core.strftimedate": "%d %B %Y", + "core.strftimedatefullshort": "%d/%m/%y", + "core.strftimedateshort": "%d %B", + "core.strftimedatetime": "%d %B %Y, %H:%M", + "core.strftimedatetimeshort": "%d/%m/%Y %H:%M", + "core.strftimedaydate": "%A, %d de %B de %Y", + "core.strftimedaydatetime": "%A, %d %B %Y, %H:%M", + "core.strftimedayshort": "%A, %d de %B", + "core.strftimedaytime": "%a, %H:%M", + "core.strftimemonthyear": "%B %Y", + "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.submit": "Tramet", "core.success": "Èxit", "core.tablet": "Tablet", @@ -1569,10 +1723,14 @@ "core.view": "Visualització", "core.viewcode": "Mostra el codi", "core.vieweditor": "Mostra l'editor", + "core.viewembeddedcontent": "Visualitza el contingut encastat", "core.viewprofile": "Mostra el perfil", "core.warningofflinedatadeleted": "Les dades fora de línia de {{component}} «{{name}}» s'han eliminat. {{error}}", + "core.whatisyourage": "Quants anys teniu?", + "core.wheredoyoulive": "A quin país viviu?", "core.whoops": "Ui!", "core.whyisthishappening": "I això per què passa?", + "core.whyisthisrequired": "Per què això és necessari?", "core.windowsphone": "Windows Phone", "core.wsfunctionnotavailable": "La funció de webservice no està disponible.", "core.year": "any", diff --git a/src/assets/lang/cs.json b/src/assets/lang/cs.json index b55b51c12..857d4e24f 100644 --- a/src/assets/lang/cs.json +++ b/src/assets/lang/cs.json @@ -1,15 +1,61 @@ { + "addon.badges.alignment": "Kompetence", "addon.badges.badgedetails": "Detaily odznaku", "addon.badges.badges": "Odznaky", + "addon.badges.bendorsement": "Schválení", + "addon.badges.claimcomment": "Komentář ke schválení", + "addon.badges.claimid": "URL prohlášení", "addon.badges.contact": "Kontakt", "addon.badges.dateawarded": "Datum udělení", "addon.badges.expired": "Platnost vypršela", "addon.badges.expirydate": "Datum vypršení platnosti", + "addon.badges.imageauthoremail": "E-mail autora obrázku", + "addon.badges.imageauthorname": "Jméno autora obrázku", + "addon.badges.imageauthorurl": "Adresa URL autora obrázku", + "addon.badges.imagecaption": "Titulek obrázku", "addon.badges.issuancedetails": "Vypršení platnosti odznaku", "addon.badges.issuerdetails": "Podrobnosti o vydavateli", + "addon.badges.issueremail": "E-mail", "addon.badges.issuername": "Jméno vydavatele", + "addon.badges.issuerurl": "URL schvalovatele", + "addon.badges.language": "Jazyk", + "addon.badges.noalignment": "Tento odznak nemá žádné uvedené kompetence.", "addon.badges.nobadges": "Žádné odznaky nejsou k dispozici.", + "addon.badges.norelated": "Tento odznak nemá žádné související odznaky.", "addon.badges.recipientdetails": "Podrobnosti o příjemci", + "addon.badges.relatedbages": "Související odznaky", + "addon.badges.version": "Verze", + "addon.badges.warnexpired": "(Platnost odznaku vypršela!)", + "addon.block_activitymodules.pluginname": "Činnosti", + "addon.block_myoverview.all": "Vše", + "addon.block_myoverview.favourites": "Označené hvězdičkou", + "addon.block_myoverview.future": "Budoucí", + "addon.block_myoverview.hiddencourses": "Skryté", + "addon.block_myoverview.inprogress": "Probíhající", + "addon.block_myoverview.lastaccessed": "Poslední přístup", + "addon.block_myoverview.morecourses": "Další kurzy", + "addon.block_myoverview.nocourses": "Žádné kurzy", + "addon.block_myoverview.past": "Minulé", + "addon.block_myoverview.pluginname": "Přehled kurzů", + "addon.block_myoverview.title": "Název kurzu", + "addon.block_recentlyaccessedcourses.nocourses": "Žádné kurzy", + "addon.block_recentlyaccessedcourses.pluginname": "Nedávno navštívené kurzy", + "addon.block_recentlyaccesseditems.noitems": "Žádné nedávné položky", + "addon.block_recentlyaccesseditems.pluginname": "Nedávno navštívené položky", + "addon.block_sitemainmenu.pluginname": "Hlavní nabídka", + "addon.block_starredcourses.nocourses": "Žádné kurzy s hvězdičkou", + "addon.block_starredcourses.pluginname": "Kurzy s hvězdičkou", + "addon.block_timeline.duedate": "Termín ukončení", + "addon.block_timeline.next30days": "Dalších 30 dní", + "addon.block_timeline.next3months": "Další 3 měsíce", + "addon.block_timeline.next6months": "Dalších 6 měsíců", + "addon.block_timeline.next7days": "Dalších 7 dní", + "addon.block_timeline.nocoursesinprogress": "Žádné aktivní kurzy", + "addon.block_timeline.noevents": "Žádné činnosti s termínem ukončení", + "addon.block_timeline.overdue": "Zpožděné", + "addon.block_timeline.pluginname": "Časová osa", + "addon.block_timeline.sortbycourses": "Seřadit podle kurzů", + "addon.block_timeline.sortbydates": "Seřadit podle data", "addon.calendar.calendar": "Kalendář", "addon.calendar.calendarevents": "Kalendář událostí", "addon.calendar.defaultnotificationtime": "Výchozí čas oznámení", @@ -81,19 +127,19 @@ "addon.coursecompletion.completionmenuitem": "Absolvování", "addon.coursecompletion.couldnotloadreport": "Nelze načíst zprávu o absolvování kurzu. Zkuste to prosím později.", "addon.coursecompletion.coursecompletion": "Absolvování kurzu", - "addon.coursecompletion.criteria": "Kritéria", + "addon.coursecompletion.criteria": "Podmínky", "addon.coursecompletion.criteriagroup": "Skupina podmínek", - "addon.coursecompletion.criteriarequiredall": "Jsou požadovány všechny podmínky", - "addon.coursecompletion.criteriarequiredany": "Je požadována libovolná podmínka", + "addon.coursecompletion.criteriarequiredall": "Všechny podmínky musí být splněny", + "addon.coursecompletion.criteriarequiredany": "Jakákoli z podmínek musí být splněna", "addon.coursecompletion.inprogress": "Probíhá", - "addon.coursecompletion.manualselfcompletion": "Ručně nastavené absolvování kurzu samotným studentem", + "addon.coursecompletion.manualselfcompletion": "Označení absolvování kurzu samotným studentem", "addon.coursecompletion.notyetstarted": "Zatím nezačalo", "addon.coursecompletion.pending": "Čeká", - "addon.coursecompletion.required": "Požadováno", - "addon.coursecompletion.requiredcriteria": "Požadovaná kriteria", + "addon.coursecompletion.required": "Vyžadováno", + "addon.coursecompletion.requiredcriteria": "Vyžadované podmínky", "addon.coursecompletion.requirement": "Požadavek", - "addon.coursecompletion.status": "Status", - "addon.coursecompletion.viewcoursereport": "Zobrazit sestavu kurzu", + "addon.coursecompletion.status": "Stav", + "addon.coursecompletion.viewcoursereport": "Zobrazit přehled kurzu", "addon.files.couldnotloadfiles": "Seznam souborů, které nelze načíst .", "addon.files.emptyfilelist": "Žádný soubor k zobrazení.", "addon.files.erroruploadnotworking": "Bohužel v současné době není možné nahrávat na stránky vašeho Moodle.", @@ -101,36 +147,80 @@ "addon.files.privatefiles": "Osobní soubory", "addon.files.sitefiles": "Soubory stránek", "addon.messageoutput_airnotifier.processorsettingsdesc": "Konfigurovat zařízení", + "addon.messages.acceptandaddcontact": "Schválit a přidat do kontaktů", "addon.messages.addcontact": "Přidat kontakt", - "addon.messages.blockcontact": "Blokovat kontakt", - "addon.messages.blockcontactconfirm": "Od tohoto kontaktu již nebudete přijímat zprávy.", + "addon.messages.addcontactconfirm": "Opravdu chcete do svých kontaktů přidat {{$a}}?", + "addon.messages.addtofavourites": "Označit hvězdičkou", + "addon.messages.addtoyourcontacts": "Přidat do kontaktů", "addon.messages.blocknoncontacts": "Blokuj všechny nové zprávy od uživatelů, které nemám v seznamu kontaktů", + "addon.messages.blockuser": "Blokovat uživatele", + "addon.messages.blockuserconfirm": "Opravdu chcete blokovat {{$a}}?", + "addon.messages.contactableprivacy": "Přijmout zprávy od:", + "addon.messages.contactableprivacy_coursemember": "Moje kontakty a kdokoliv v mých kurzech", + "addon.messages.contactableprivacy_onlycontacts": "Pouze mé kontakty", + "addon.messages.contactableprivacy_site": "Kdokoliv na webu", + "addon.messages.contactblocked": "Kontakt blokován", "addon.messages.contactlistempty": "Seznam kontaktů je prázdný", "addon.messages.contactname": "Jméno kontaktu", + "addon.messages.contactrequestsent": "Požadavek na kontakt byl odeslán", "addon.messages.contacts": "Kontakty", + "addon.messages.decline": "Sestupně", + "addon.messages.deleteallconfirm": "Opravdu chcete vymazat celou tuto konverzaci? Ostatním účastníkům konverzace nebude smazána.", + "addon.messages.deleteconversation": "Odstranit konverzaci", "addon.messages.deletemessage": "Odstranit zprávu", "addon.messages.deletemessageconfirmation": "Opravdu chcete tuto zprávu odstranit? Bude smazána pouze z historie zpráv a bude nadále viditelná uživatelem, který poslal nebo přijal zprávu.", "addon.messages.errordeletemessage": "Chyba při odstraňování zprávy.", "addon.messages.errorwhileretrievingcontacts": "Chyba při načítání kontaktů ze serveru.", "addon.messages.errorwhileretrievingdiscussions": "Chyba při načítání diskusí ze serveru.", "addon.messages.errorwhileretrievingmessages": "Chyba při načítání zpráv ze serveru.", + "addon.messages.errorwhileretrievingusers": "Chyba při načítání uživatelů ze serveru.", + "addon.messages.groupconversations": "Skupina", + "addon.messages.groupinfo": "Skupinové informace", + "addon.messages.individualconversations": "Soukromé", + "addon.messages.info": "Informace", + "addon.messages.isnotinyourcontacts": "{{$a}} není ve vašich kontaktech", "addon.messages.message": "Zpráva", "addon.messages.messagenotsent": "Zpráva nebyla odeslána. Zkuste to prosím později.", "addon.messages.messagepreferences": "Nastavení zpráv", "addon.messages.messages": "Zprávy", "addon.messages.newmessage": "Nová zpráva", "addon.messages.newmessages": "Nové zprávy", - "addon.messages.nomessages": "Žádné zprávy", + "addon.messages.nocontactrequests": "Žádné žádosti o kontakt", + "addon.messages.nocontactsgetstarted": "Žádné kontakty", + "addon.messages.nofavourites": "Žádné konverzace označené hvězdičkou", + "addon.messages.nogroupconversations": "Žádné skupinové konverzace", + "addon.messages.noindividualconversations": "Žádné soukromé konverzace", + "addon.messages.nomessagesfound": "Nebyly nalezeny žádné zprávy", + "addon.messages.noncontacts": "Zablokovat zprávy", "addon.messages.nousersfound": "Nebyl nalezen žádný uživatel", + "addon.messages.numparticipants": "{{$a}} účastníků", "addon.messages.removecontact": "Odebrat kontakt", - "addon.messages.removecontactconfirm": "Kontakt bude odstraněn ze seznamu kontaktů.", + "addon.messages.removecontactconfirm": "Opravdu chcete z kontaktů odstranit {{$a}}?", + "addon.messages.removefromfavourites": "Odstranit hvězdičku", + "addon.messages.removefromyourcontacts": "Odebrat z kontaktů", + "addon.messages.requests": "Žádosti", + "addon.messages.requirecontacttomessage": "Musíte požádat {{$a}}, aby vás přidal jako kontakt pro zasílání zpráv.", + "addon.messages.searchcombined": "Hledání osob a zpráv", + "addon.messages.searchnocontactsfound": "Nebyly nalezeny žádné kontakty", + "addon.messages.searchnomessagesfound": "Nebyly nalezeny žádné zprávy", + "addon.messages.searchnononcontactsfound": "Nebyl nalezen nikdo bez kontaktů", + "addon.messages.sendcontactrequest": "Odeslat žádost o kontakt", + "addon.messages.showdeletemessages": "Zobrazit odstraněné zprávy", "addon.messages.type_blocked": "Blokováno", "addon.messages.type_offline": "Offline", "addon.messages.type_online": "Online", "addon.messages.type_search": "Výsledky hledání", "addon.messages.type_strangers": "Další", - "addon.messages.unblockcontact": "Odblokovat kontakt", + "addon.messages.unabletomessage": "Tomuto uživateli nemůžete odeslat zprávu", + "addon.messages.unblockuser": "Odblokovat uživatele", + "addon.messages.unblockuserconfirm": "Opravdu chcete odblokovat {{$a}}?", + "addon.messages.userwouldliketocontactyou": "{{$a}} by vás chtěl kontaktovat", + "addon.messages.warningconversationmessagenotsent": "Nelze odeslat zprávu(y) do konverzace {{conversation}}. {{error}}", "addon.messages.warningmessagenotsent": "Nelze odeslat zprávu (y) uživateli {{user}}. {{error}}", + "addon.messages.wouldliketocontactyou": "Rád by vás kontaktoval", + "addon.messages.you": "Vy:", + "addon.messages.youhaveblockeduser": "Tohoto uživatele jste v minulosti blokovali", + "addon.messages.yourcontactrequestpending": "Váš požadavek na kontakt čeká na {{$a}}", "addon.mod_assign.acceptsubmissionstatement": "Přijměte prohlášení prosím.", "addon.mod_assign.addattempt": "Povolit další pokus", "addon.mod_assign.addnewattempt": "Přidat nový pokus", @@ -140,7 +230,7 @@ "addon.mod_assign.allowsubmissionsfromdate": "Povolit odevzdání úkolů od", "addon.mod_assign.allowsubmissionsfromdatesummary": "Odevzdat úkol bude možné od {{$a}}", "addon.mod_assign.applytoteam": "Použít hodnocení pro celou skupinu", - "addon.mod_assign.assignmentisdue": "Úkol je zpožděn", + "addon.mod_assign.assignmentisdue": "Úkol má být hotov do", "addon.mod_assign.attemptnumber": "Číslo pokusu", "addon.mod_assign.attemptreopenmethod": "Opětovné otevření pokusů", "addon.mod_assign.attemptreopenmethod_manual": "Ručně", @@ -167,7 +257,9 @@ "addon.mod_assign.grade": "Známka", "addon.mod_assign.graded": "Udělena známka", "addon.mod_assign.gradedby": "Hodnoceno", + "addon.mod_assign.gradedfollowupsubmit": "Oznámkováno - další odevzdaný úkol", "addon.mod_assign.gradedon": "Hodnoceno na", + "addon.mod_assign.gradelocked": "Známka je v klasifikaci zamčena nebo přepsána.", "addon.mod_assign.gradenotsynced": "Známky nejsou synchronizovány", "addon.mod_assign.gradeoutof": "Hodnoceno z {{$a}}", "addon.mod_assign.gradingstatus": "Stav hodnocení", @@ -182,6 +274,7 @@ "addon.mod_assign.markingworkflowstatereadyforrelease": "Připraveno ke zveřejnění", "addon.mod_assign.markingworkflowstatereadyforreview": "Známkování dokončeno", "addon.mod_assign.markingworkflowstatereleased": "Zveřejněno", + "addon.mod_assign.modulenameplural": "Úkoly", "addon.mod_assign.multipleteams": "Člen více než jedné skupiny", "addon.mod_assign.multipleteams_desc": "Tento úkol vyžaduje řešení úkolu ve skupinách. Jste členem více než jedné skupiny. Aby bylo možné odeslat řešení úkolu, musíte být členem pouze jedné skupiny. Obraťte se na svého učitele, aby aktualizoval vaše členství ve skupině.", "addon.mod_assign.noattempt": "Neodevzdáno", @@ -236,6 +329,7 @@ "addon.mod_assign_submission_file.pluginname": "Odevzdat soubor(y)", "addon.mod_assign_submission_onlinetext.pluginname": "Odevzdání online textů", "addon.mod_book.errorchapter": "Chyba při čtení kapitoly knihy.", + "addon.mod_book.modulenameplural": "Knihy", "addon.mod_chat.beep": "Prozvonit", "addon.mod_chat.currentusers": "Stávající uživatelé", "addon.mod_chat.enterchat": "Klikněte zde pro vstup do chatu", @@ -248,6 +342,7 @@ "addon.mod_chat.messagebeepsyou": "{{$a}} vás prozvání!", "addon.mod_chat.messageenter": "{{$a}} právě vstoupil(a) do tohoto chatu", "addon.mod_chat.messageexit": "{{$a}} opustil(a) tento chat", + "addon.mod_chat.modulenameplural": "Chatování", "addon.mod_chat.mustbeonlinetosendmessages": "Pro odesílání zpráv musíte být online", "addon.mod_chat.nomessages": "Zatím žádné zprávy", "addon.mod_chat.send": "Odeslat", @@ -258,6 +353,7 @@ "addon.mod_choice.errorgetchoice": "Chyba při načítání dat ankety.", "addon.mod_choice.expired": "Je nám líto, tato činnost byla uzavřena {{$a}} a není nadále dostupná", "addon.mod_choice.full": "(Obsazeno)", + "addon.mod_choice.modulenameplural": "Ankety", "addon.mod_choice.noresultsviewable": "Výsledky nejsou momentálně k dispozici", "addon.mod_choice.notopenyet": "Je nám líto, tato činnost není až do {{$a}} dostupná.", "addon.mod_choice.numberofuser": "Počet odpovědí", @@ -295,8 +391,10 @@ "addon.mod_data.errormustsupplyvalue": "Musí obsahovat nějakou hodnotu.", "addon.mod_data.expired": "Tato činnost byla ukončena {{$a}} a není již nadále dostupná.", "addon.mod_data.fields": "Pole", + "addon.mod_data.foundrecords": "Počet nalezených záznamů: {{$a.num}}/{{$a.max}} (resetovat filtry)", "addon.mod_data.latlongboth": "Je požadována zeměpisná šířka i délka.", "addon.mod_data.menuchoose": "Vybrat...", + "addon.mod_data.modulenameplural": "Databáze", "addon.mod_data.more": "Podrobněji", "addon.mod_data.nomatch": "Nenalezeny žádné záznamy!", "addon.mod_data.norecords": "Nejsou k dispozici žádné záznamy", @@ -328,6 +426,7 @@ "addon.mod_feedback.feedbackopen": "Dostupné od", "addon.mod_feedback.mapcourses": "Mapování kurzů", "addon.mod_feedback.mode": "Režim", + "addon.mod_feedback.modulenameplural": "Dotazníky", "addon.mod_feedback.next_page": "Další stránka", "addon.mod_feedback.non_anonymous": "Jména respondentů budou zaznamenána a ukázána s odpověďmi", "addon.mod_feedback.non_anonymous_entries": "Neanonymní záznamy ({{$a}})", @@ -348,6 +447,7 @@ "addon.mod_feedback.started": "Spuštěno", "addon.mod_feedback.this_feedback_is_already_submitted": "Tento dotazník jste již vyplnili.", "addon.mod_folder.emptyfilelist": "Žádný soubor k zobrazení.", + "addon.mod_folder.modulenameplural": "Složky", "addon.mod_forum.addanewdiscussion": "Přidat nové téma diskuse", "addon.mod_forum.addanewquestion": "Přidat novou otázku", "addon.mod_forum.addanewtopic": "Přidat nové téma", @@ -370,6 +470,7 @@ "addon.mod_forum.modeflatnewestfirst": "Zobrazit odpovědi za sebou (nejnovější nahoře)", "addon.mod_forum.modeflatoldestfirst": "Zobrazit odpovědi za sebou (nejstarší nahoře)", "addon.mod_forum.modenested": "Zobrazit hierarchii odpovědí (včetně textu)", + "addon.mod_forum.modulenameplural": "Fóra", "addon.mod_forum.numdiscussions": "{{numdiscussions}} diskusí", "addon.mod_forum.numreplies": "{{numreplies}} odpovědí", "addon.mod_forum.posttoforum": "Poslat do fóra", @@ -405,9 +506,11 @@ "addon.mod_glossary.fillfields": "Pojem a definice jsou povinná pole.", "addon.mod_glossary.fullmatch": "Srovnávat pouze celá slova
(při automatickém propojování)", "addon.mod_glossary.linking": "Automatické propojování", + "addon.mod_glossary.modulenameplural": "Slovníky", "addon.mod_glossary.noentriesfound": "Nebyly nalezeny žádné záznamy.", "addon.mod_glossary.searchquery": "Vyhledávací dotaz", "addon.mod_imscp.deploymenterror": "Chyba v balíčku!", + "addon.mod_imscp.modulenameplural": "Balíček IMS", "addon.mod_imscp.showmoduledescription": "Zobrazit popis", "addon.mod_lesson.answer": "Odpověď", "addon.mod_lesson.attempt": "Pokus: {{$a}}", @@ -451,6 +554,7 @@ "addon.mod_lesson.lowtime": "Nejkratší čas", "addon.mod_lesson.maximumnumberofattemptsreached": "Vyčerpali jste maximální počet pokusů – následuje další stránka přednášky", "addon.mod_lesson.modattemptsnoteacher": "Revize funguje pouze studentům", + "addon.mod_lesson.modulenameplural": "Přednášky", "addon.mod_lesson.noanswer": "Neodpověděli jste jednu nebo více otázek. Prosím, vraťte se zpět a zadejte odpověď.", "addon.mod_lesson.nolessonattempts": "O zodpovězení otázek v této přednášce se ještě nikdo nepokusil.", "addon.mod_lesson.nolessonattemptsgroup": "Někteří ({{$a}}) členové skupiny se dosud nepokusili o absolvování této přednášky.", @@ -495,7 +599,9 @@ "addon.mod_lti.errorgetlti": "Chyba při načítání", "addon.mod_lti.errorinvalidlaunchurl": "Spuštěná URL není platná.", "addon.mod_lti.launchactivity": "Zahájit aktivitu", + "addon.mod_lti.modulenameplural": "Externí nástroje", "addon.mod_page.errorwhileloadingthepage": "Chyba při načítání", + "addon.mod_page.modulenameplural": "Stránky", "addon.mod_quiz.attemptfirst": "První pokus", "addon.mod_quiz.attemptlast": "Poslední pokus", "addon.mod_quiz.attemptnumber": "Pokus", @@ -530,6 +636,7 @@ "addon.mod_quiz.grademethod": "Metoda známkování", "addon.mod_quiz.gradesofar": "{{$a.method}}: {{$a.mygrade}} / {{$a.quizgrade}}.", "addon.mod_quiz.marks": "Body", + "addon.mod_quiz.modulenameplural": "Testy", "addon.mod_quiz.mustbesubmittedby": "Tento pokus musí být odeslán nejpozději {{$a}}.", "addon.mod_quiz.noquestions": "Dosud nebyly vloženy žádné úlohy.", "addon.mod_quiz.noreviewattempt": "Nemůžete revidovat tento pokus.", @@ -574,6 +681,7 @@ "addon.mod_quiz.yourfinalgradeis": "Vaše konečná známka za tento test je {{$a}}", "addon.mod_resource.errorwhileloadingthecontent": "Chyba při načítání obsahu.", "addon.mod_resource.modifieddate": "Změněno {{$a}}", + "addon.mod_resource.modulenameplural": "Soubory", "addon.mod_resource.openthefile": "Otevřít soubor", "addon.mod_resource.uploadeddate": "Nahráno {{$a}}", "addon.mod_scorm.asset": "Komponenta", @@ -610,6 +718,7 @@ "addon.mod_scorm.incomplete": "Nedokončeno", "addon.mod_scorm.lastattempt": "Poslední pokus", "addon.mod_scorm.mode": "Režim", + "addon.mod_scorm.modulenameplural": "SCORM balíčky", "addon.mod_scorm.newattempt": "Začít nový pokus", "addon.mod_scorm.noattemptsallowed": "Počet povolených pokusů", "addon.mod_scorm.noattemptsmade": "Počet pokusů, které jste vyčerpali", @@ -629,10 +738,12 @@ "addon.mod_survey.errorgetsurvey": "Chyba při načítání dat průzkumu.", "addon.mod_survey.ifoundthat": "Zjistil jsem, že", "addon.mod_survey.ipreferthat": "Představoval bych si, že", + "addon.mod_survey.modulenameplural": "Průzkumy", "addon.mod_survey.responses": "Odpovědi", "addon.mod_survey.results": "Výsledky", "addon.mod_survey.surveycompletednograph": "Tento dotazník jste již vyplnili.", "addon.mod_url.accessurl": "Přístup k URL", + "addon.mod_url.modulenameplural": "URL odkazy", "addon.mod_url.pointingtourl": "Adresa URL, na kterou zdroj odkazuje.", "addon.mod_wiki.cannoteditpage": "Nemůžete editovat tuto stránku.", "addon.mod_wiki.createpage": "Vytvořit stránku", @@ -641,6 +752,7 @@ "addon.mod_wiki.errornowikiavailable": "Tato wiki ještě nemá žádný obsah.", "addon.mod_wiki.gowikihome": "Přejí na výchozí stránku Wiki", "addon.mod_wiki.map": "Mapa", + "addon.mod_wiki.modulenameplural": "Wiki", "addon.mod_wiki.newpagehdr": "Nová stránka", "addon.mod_wiki.newpagetitle": "Nový název stránky", "addon.mod_wiki.nocontent": "Pro tuto stránku není obsah", @@ -679,6 +791,7 @@ "addon.mod_workshop.gradinggradecalculated": "Vypočítaná známka za hodnocení", "addon.mod_workshop.gradinggradeof": "Známka za hodnocení (z {{$a}})", "addon.mod_workshop.gradinggradeover": "Přepsat známku za hodnocení", + "addon.mod_workshop.modulenameplural": "Workshopy", "addon.mod_workshop.nogradeyet": "Zatím bez známky", "addon.mod_workshop.notassessed": "Zatím nehodnoceno", "addon.mod_workshop.notoverridden": "Nepřepisovat", @@ -1044,6 +1157,8 @@ "core.accounts": "Účty", "core.add": "Přidat", "core.agelocationverification": "Ověření věku a polohy", + "core.ago": "před: {{$a}}", + "core.all": "Vše", "core.allparticipants": "Všichni účastníci", "core.android": "Android", "core.answer": "Odpověď", @@ -1081,7 +1196,7 @@ "core.confirmdeletefile": "Jste si jisti, že chcete odstranit tento soubor?", "core.confirmloss": "Jsi si jistý? Všechny změny budou ztraceny.", "core.confirmopeninbrowser": "Chcete jej otevřít v prohlížeči?", - "core.considereddigitalminor": "Pro digitální souhlas jste považováni za nezletilé.", + "core.considereddigitalminor": "Jste příliš mladý/á, abyste na těchto stránkách vytvořili účet.", "core.content": "Obsah", "core.contenteditingsynced": "Obsah, který upravujete byl synchronizován.", "core.contentlinks.chooseaccount": "Vyberte si účet", @@ -1111,18 +1226,21 @@ "core.course.errorgetmodule": "Chyba při načítání", "core.course.hiddenfromstudents": "Skryté před studenty", "core.course.hiddenoncoursepage": "Je dostupná, ale není zobrazena na stránce kurzu", + "core.course.manualcompletionnotsynced": "Ruční splnění není synchronizováno.", "core.course.nocontentavailable": "V tuto chvíli není k dispozici žádný obsah.", "core.course.overriddennotice": "Vaše výsledná známka za tuto činnost byla ručně upravena.", "core.course.refreshcourse": "Obnovit kurz", "core.course.sections": "Sekce", "core.course.useactivityonbrowser": "Můžete i nadále používat jej pomocí prohlížeče zařízení.", + "core.course.warningmanualcompletionmodified": "Ruční splnění aktivity bylo na webu změněno.", + "core.course.warningofflinemanualcompletiondeleted": "Některá offline ruční splnění kurzu \"{{name}}\" byla odstraněna. {{error}", "core.coursedetails": "Podrobnosti kurzu", + "core.courses.addtofavourites": "Kurz označit hvězdičkou", "core.courses.allowguests": "Tento kurz je otevřen i pro hosty", "core.courses.availablecourses": "Dostupné kurzy", "core.courses.cannotretrievemorecategories": "Kategorie hlubší než úroveň {{$a}} nelze načíst.", "core.courses.categories": "Kategorie kurzů", "core.courses.confirmselfenrol": "Jste si jisti, že chcete zapsat se do tohoto kurzu?", - "core.courses.courseoverview": "Přehled kurzů", "core.courses.courses": "Kurzy", "core.courses.downloadcourses": "Stáhnout kurzy", "core.courses.enrolme": "Zapsat se", @@ -1132,29 +1250,24 @@ "core.courses.errorselfenrol": "Při zápisu sebe sama došlo k chybě.", "core.courses.filtermycourses": "Filtrovat mé kurzy", "core.courses.frontpage": "Titulní stránka", - "core.courses.future": "Budoucí", - "core.courses.inprogress": "Probíhající", - "core.courses.morecourses": "Další kurzy", + "core.courses.hidecourse": "Skrýt z pohledu", "core.courses.mycourses": "Moje kurzy", + "core.courses.mymoodle": "Nástěnka", "core.courses.nocourses": "Žádné dostupné informace o kurzech", - "core.courses.nocoursesfuture": "Žádné budoucí kurzy", - "core.courses.nocoursesinprogress": "Žádné probíhající kurzy", - "core.courses.nocoursesoverview": "Žádné kurzy", - "core.courses.nocoursespast": "Žádné minulé kurzy", "core.courses.nocoursesyet": "Žádný kurz v této kategorii", "core.courses.nosearchresults": "Žádné výsledky", "core.courses.notenroled": "Nejste zapsáni v tomto kurzu", "core.courses.notenrollable": "Do tohoto kurzu se nemůžete sami zapsat.", "core.courses.password": "Klíč zápisu", - "core.courses.past": "Minulé", "core.courses.paymentrequired": "Tento kurz je placený", "core.courses.paypalaccepted": "Platby přes PayPal přijímány", - "core.courses.recentlyoverdue": "Nedávno vypršel termín", + "core.courses.removefromfavourites": "Tomuto kurzu odstranit hvězdičku", "core.courses.search": "Hledat", "core.courses.searchcourses": "Vyhledat kurzy", "core.courses.searchcoursesadvice": "Můžete použít tlačítko Vyhledat kurzy, pracovat jako host nebo se zapsat do kurzů, které to umožňují.", "core.courses.selfenrolment": "Zápis sebe sama", "core.courses.sendpaymentbutton": "Poslat platbu přes službu PayPal", + "core.courses.show": "Zobrazit tento kurz", "core.courses.totalcoursesearchresults": "Celkem kurzů: {{$a}}", "core.currentdevice": "Aktuální zařízení", "core.datastoredoffline": "Data byla uložena na zařízení, protože nemohla být odeslána. Budou odeslána automaticky později.", @@ -1174,7 +1287,7 @@ "core.dfmediumdate": "LLL", "core.dftimedate": "h[:]mm A", "core.digitalminor": "Nezletilý pro digitální souhlas", - "core.digitalminor_desc": "Chcete-li vytvořit účet na tomto webu, ať vaše rodiče/zákonný zástupce kontaktují následující osobou.", + "core.digitalminor_desc": "Požádejte svého rodiče/opatrovníka, aby kontaktoval:", "core.discard": "Odstranit", "core.dismiss": "Odmítnout", "core.done": "Hotovo", @@ -1199,6 +1312,7 @@ "core.errorsync": "Při synchronizaci došlo k chybě. Zkuste to prosím znovu.", "core.errorsyncblocked": "{{$a}} nelze nyní synchronizovat z důvodu probíhajícího procesu. Zkuste to prosím znovu později. Pokud problém přetrvává, zkuste restartovat aplikaci.", "core.explanationdigitalminor": "Tyto informace jsou nutné k určení, zda je váš věk nad věkovou hranicí pro digitální souhlas. Jedná se o věk, kdy může jednotlivec souhlasit s podmínkami a a jejich údaje jsou legálně uchovávány a zpracovávány.", + "core.favourites": "Označeno hvězdičkou", "core.filename": "Název souboru", "core.filenameexist": "Jméno souboru již existuje: {{$a}}", "core.fileuploader.addfiletext": "Přidat soubor", @@ -1222,6 +1336,7 @@ "core.fileuploader.more": "Podrobněji", "core.fileuploader.photoalbums": "Fotoalba", "core.fileuploader.readingfile": "Čtení souboru", + "core.fileuploader.readingfileperc": "Načítání souboru: {{$a}}%", "core.fileuploader.selectafile": "Vyberte soubor", "core.fileuploader.uploadafile": "Nahrát soubor", "core.fileuploader.uploading": "Nahrávání", @@ -1280,12 +1395,15 @@ "core.login.createaccount": "Vytvořit můj nový účet", "core.login.createuserandpass": "Vytvořit nové uživatelské jméno a heslo pro přihlášení", "core.login.credentialsdescription": "Pro přihlášení uveďte své uživatelské jméno a heslo.", - "core.login.emailconfirmsent": "

Na vaši adresu{{$a}} měl být zaslán email

Obsahuje jednoduché pokyny pro dokončení registrace.

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

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

Na vaši adresu {{$a}} byl odeslán e-mail s jednoduchými pokyny k dokončení vaší registrace.

Narazíte-li na nějaké obtíže, spojte se se správcem těchto stránek.

", + "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.", + "core.login.findyoursite": "Najít stránky", "core.login.firsttime": "Jste tady poprvé?", "core.login.forgotten": "Zapomněli jste své uživatelské jméno či heslo?", "core.login.getanothercaptcha": "Získat jiné CAPTCHA", @@ -1303,6 +1421,7 @@ "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í", @@ -1313,6 +1432,7 @@ "core.login.missingfirstname": "Chybí křestní jméno", "core.login.missinglastname": "Chybí příjmení", "core.login.mobileservicesnotenabled": "Na vašem webu nejsou povoleny mobilní služby. Pokud si myslíte, že by měl být povolen mobilní přístup, obraťte se prosím na správce Moodlu.", + "core.login.mustconfirm": "Musíte potvrdit svůj účet", "core.login.newaccount": "Nový účet", "core.login.newsitedescription": "Zadejte prosím adresu URL vašeho webu Moodle. Všimněte si, že nemusí být nakonfigurován pro práci s touto aplikací.", "core.login.notloggedin": "Musíte být přihlášeni.", @@ -1334,12 +1454,14 @@ "core.login.reconnect": "Znovu připojit", "core.login.reconnectdescription": "Váš token autentizace je neplatný nebo vypršel. Musíte se znovu připojit k serveru.", "core.login.reconnectssodescription": "Váš token autentizace je neplatný nebo vypršel. Musíte se znovu připojit k serveru. Musíte se přihlásit na stránky v okně prohlížeče.", + "core.login.resendemail": "Přeposlat email", "core.login.searchby": "Hledat pomocí:", "core.login.security_question": "Bezpečnostní otázka", "core.login.selectacountry": "Vyberte zemi", "core.login.selectsite": "Vyberte prosím vaše stránky:", "core.login.signupplugindisabled": "{{$a}} není povoleno.", "core.login.siteaddress": "Adresa stránky", + "core.login.sitehasredirect": "Váš web obsahuje alespoň jedno přesměrování HTTP. Aplikace nemůže sledovat přesměrování, může to být problém, který brání připojení aplikace k vašim stránkám.", "core.login.siteinmaintenance": "Váš web je v režimu údržby", "core.login.sitepolicynotagreederror": "Zásady bezpečnosti nebyly odsouhlaseny.", "core.login.siteurl": "URL adresa stránky", @@ -1358,8 +1480,6 @@ "core.mainmenu.changesite": "Změnit stránky", "core.mainmenu.help": "Nápověda", "core.mainmenu.logout": "Odhlásit se", - "core.mainmenu.mycourses": "Moje kurzy", - "core.mainmenu.togglemenu": "Přepnout nabídku", "core.mainmenu.website": "Webová stránka", "core.maxsizeandattachments": "Maximální velikost nových souborů: {{$a.size}}, maximální přílohy: {{$a.attachments}}", "core.min": "min.", @@ -1394,6 +1514,7 @@ "core.more": "více", "core.mygroups": "Moje skupiny", "core.name": "Název", + "core.networkerroriframemsg": "Tento obsah není k dispozici v režimu offline. Připojte se prosím k internetu a zkuste to znovu.", "core.networkerrormsg": "Při připojování k webu došlo k problému. Zkontrolujte připojení a zkuste to znovu.", "core.never": "Nikdy", "core.next": "Další", @@ -1402,6 +1523,7 @@ "core.nograde": "Bez známky", "core.none": "Žádný", "core.nopasswordchangeforced": "Nelze pokračovat beze změny hesla.", + "core.nopermissionerror": "Je nám líto, ale momentálně k tomu nemáte oprávnění", "core.nopermissions": "Je mi líto, ale momentálně nemáte oprávnění vykonat tuto operaci ({{$a}})", "core.noresults": "Bez výsledků", "core.notapplicable": "n/a", @@ -1426,6 +1548,7 @@ "core.pulltorefresh": "Stáhněte pro obnovu", "core.question.answer": "Odpověď", "core.question.answersaved": "Odpověď uložena", + "core.question.cannotdeterminestatus": "Stav nelze určit", "core.question.certainty": "Jistota", "core.question.complete": "Hotovo", "core.question.correct": "Správně", @@ -1446,8 +1569,10 @@ "core.quotausage": "Právě jste použili {{$a.used}} z vašeho {{$a.total}} limitu.", "core.redirectingtosite": "Budete přesměrováni na web.", "core.refresh": "Obnovit", + "core.remove": "Odstranit", "core.required": "Vyžadováno", "core.requireduserdatamissing": "Tento uživatel nemá některá požadovaná data v profilu. Prosím, vyplňte tato data v systému Moodle a zkuste to znovu.
{{$a}}", + "core.resources": "Studijní materiály", "core.restore": "Obnovit", "core.retry": "Opakovat", "core.save": "Uložit", @@ -1472,6 +1597,7 @@ "core.settings.cordovaversion": "Cordova verze", "core.settings.currentlanguage": "Současný jazyk", "core.settings.debugdisplay": "Zobrazení ladících informací", + "core.settings.debugdisplaydescription": "Je-li tato možnost povolena, budou chybové výpisy zobrazovat více informací o chybě, pokud je to možné.", "core.settings.deletesitefiles": "Jste si jisti, že chcete odstranit stažené soubory z tohoto webu?", "core.settings.deletesitefilestitle": "Odstranit soubory webu", "core.settings.deviceinfo": "Informace o zařízení", @@ -1502,6 +1628,7 @@ "core.settings.privacypolicy": "Zásady ochrany osobních údajů", "core.settings.reportinbackground": "Zobrazovat chyby automaticky", "core.settings.settings": "Nastavení", + "core.settings.showdownloadoptions": "Zobrazit možnosti stahování", "core.settings.sites": "Stránky", "core.settings.spaceusage": "Použitý prostor", "core.settings.synchronization": "Synchronizace", @@ -1533,6 +1660,21 @@ "core.sizetb": "TB", "core.sorry": "Promiňte...", "core.sortby": "Třídit podle", + "core.strftimedate": "%d. %B %Y", + "core.strftimedatefullshort": "%d.%m.%y", + "core.strftimedateshort": "%d. %B", + "core.strftimedatetime": "%d. %B %Y, %H.%M", + "core.strftimedatetimeshort": "%d.%m.%Y %H:%M", + "core.strftimedaydate": "%A, %d. %B %Y", + "core.strftimedaydatetime": "%A, %d. %B %Y, %H.%M", + "core.strftimedayshort": "%A, %d. %B", + "core.strftimedaytime": "%a, %H.%M", + "core.strftimemonthyear": "%B %Y", + "core.strftimerecent": "%d. %b., %H.%M", + "core.strftimerecentfull": "%A, %d. %B %Y, %H.%M", + "core.strftimetime": "%H.%M", + "core.strftimetime12": "%H.%M", + "core.strftimetime24": "%H.%M", "core.submit": "Odeslat", "core.success": "Úspěch", "core.tablet": "Tablet", @@ -1584,6 +1726,7 @@ "core.view": "Zobrazit", "core.viewcode": "Zobrazit kód", "core.vieweditor": "Zobrazit editor", + "core.viewembeddedcontent": "Zobrazení vloženého obsahu", "core.viewprofile": "Zobrazit profil", "core.warningofflinedatadeleted": "Offline data z {{component}} \"{{name}}\" byla odstraněna. {{error}}", "core.whatisyourage": "Jaký je váš věk?", diff --git a/src/assets/lang/da.json b/src/assets/lang/da.json index a86e62357..457df912c 100644 --- a/src/assets/lang/da.json +++ b/src/assets/lang/da.json @@ -8,8 +8,35 @@ "addon.badges.issuancedetails": "Badge-udløb", "addon.badges.issuerdetails": "Udstederdata", "addon.badges.issuername": "Udsteders navn", + "addon.badges.issuerurl": "Udsteders URL", "addon.badges.nobadges": "Der er ingen tilgængelige badges.", "addon.badges.recipientdetails": "Modtagerdata", + "addon.badges.warnexpired": "(Badgen er udgået!)", + "addon.block_activitymodules.pluginname": "Aktiviteter", + "addon.block_myoverview.all": "Alle", + "addon.block_myoverview.favourites": "Fremhævede", + "addon.block_myoverview.future": "Fremtidige", + "addon.block_myoverview.hiddencourses": "Skjulte", + "addon.block_myoverview.inprogress": "I gang", + "addon.block_myoverview.lastaccessed": "Sidst besøgt", + "addon.block_myoverview.morecourses": "Flere kurser", + "addon.block_myoverview.nocourses": "Ingen kurser", + "addon.block_myoverview.past": "Tidligere", + "addon.block_myoverview.pluginname": "Kursusoversigt", + "addon.block_myoverview.title": "Navn", + "addon.block_recentlyaccessedcourses.pluginname": "Senest besøgte kurser", + "addon.block_sitemainmenu.pluginname": "Hovedmenu", + "addon.block_timeline.duedate": "Forfaldsdato", + "addon.block_timeline.next30days": "Næste 30 dage", + "addon.block_timeline.next3months": "Næste 3 måneder", + "addon.block_timeline.next6months": "Næste 6 måneder", + "addon.block_timeline.next7days": "Næste 7 dage", + "addon.block_timeline.nocoursesinprogress": "Ikke igangværende kurser", + "addon.block_timeline.noevents": "Ingen forestående aktiviteter", + "addon.block_timeline.overdue": "Forfaldne", + "addon.block_timeline.pluginname": "Tidslinje", + "addon.block_timeline.sortbycourses": "Sorter efter kurser", + "addon.block_timeline.sortbydates": "Sorter efter dato", "addon.calendar.calendar": "Kalender", "addon.calendar.calendarevents": "Kalenderbegivenheder", "addon.calendar.errorloadevent": "Fejl ved indlæsning af begivenhed.", @@ -72,24 +99,24 @@ "addon.competency.xcompetenciesproficientoutofyincourse": "Du har opnået færdighedsniveau {{$a.x}} ud af {{$a.y}} kompetencer på dette kursus.", "addon.coursecompletion.complete": "Fuldfør", "addon.coursecompletion.completecourse": "Kursus gennemført", - "addon.coursecompletion.completed": "Fuldført", + "addon.coursecompletion.completed": "Gennemført", "addon.coursecompletion.completiondate": "Afslutningsdato", "addon.coursecompletion.completionmenuitem": "Gennemførelse", "addon.coursecompletion.couldnotloadreport": "Kunne ikke indlæse rapporten vedrørende kursusfuldførelse, prøv igen senere.", - "addon.coursecompletion.coursecompletion": "Kursusfuldførelse", - "addon.coursecompletion.criteria": "Kriterier", - "addon.coursecompletion.criteriagroup": "Gruppe af kriterier", - "addon.coursecompletion.criteriarequiredall": "Alle nedenstående kriterier er påkrævet", - "addon.coursecompletion.criteriarequiredany": "Hvilken som helst af nedenstående kriterier er påkrævet", - "addon.coursecompletion.inprogress": "I gang", - "addon.coursecompletion.manualselfcompletion": "Manuel markering af færdiggørelse", - "addon.coursecompletion.notyetstarted": "Endnu ikke startet", - "addon.coursecompletion.pending": "Afventer", - "addon.coursecompletion.required": "Krævet", - "addon.coursecompletion.requiredcriteria": "Krævet kriterie", + "addon.coursecompletion.coursecompletion": "Kursusgennemførelse", + "addon.coursecompletion.criteria": "Kriterie", + "addon.coursecompletion.criteriagroup": "Kriteriegruppe", + "addon.coursecompletion.criteriarequiredall": "Alle kriterier herunder er påkrævet", + "addon.coursecompletion.criteriarequiredany": "Et af kriterierne herunder er påkrævet", + "addon.coursecompletion.inprogress": "Igangværende", + "addon.coursecompletion.manualselfcompletion": "Manuel selvregistrering af gennemførelse", + "addon.coursecompletion.notyetstarted": "Ikke begyndt endnu", + "addon.coursecompletion.pending": "Behandles", + "addon.coursecompletion.required": "Påkrævet", + "addon.coursecompletion.requiredcriteria": "Påkrævede kriterier", "addon.coursecompletion.requirement": "Krav", "addon.coursecompletion.status": "Status", - "addon.coursecompletion.viewcoursereport": "Se kursusrapport", + "addon.coursecompletion.viewcoursereport": "Vis kursusrapport", "addon.files.couldnotloadfiles": "Fillisten kunne ikke hentes", "addon.files.emptyfilelist": "Der er ingen filer at vise.", "addon.files.erroruploadnotworking": "Desværre er det p.t. ikke muligt at uploade filer til dit site.", @@ -97,34 +124,71 @@ "addon.files.privatefiles": "Private filer", "addon.files.sitefiles": "Site filer", "addon.messageoutput_airnotifier.processorsettingsdesc": "Konfigurer enheder", + "addon.messages.acceptandaddcontact": "Accepter og tilføj kontakt", "addon.messages.addcontact": "Tilføj kontakt", - "addon.messages.blockcontact": "Bloker kontakt", - "addon.messages.blockcontactconfirm": "Du modtager ikke længere beskeder fra denne kontakt.", + "addon.messages.addcontactconfirm": "Bekræft at du vil føje {{$a}} til dine kontakter", + "addon.messages.addtofavourites": "Stjernemarker", + "addon.messages.addtoyourcontacts": "Føj til kontakter", "addon.messages.blocknoncontacts": "Bloker alle beskeder fra personer der ikke er i min kontaktliste.", + "addon.messages.blockuser": "Bloker bruger", + "addon.messages.blockuserconfirm": "Bekræft at du vil blokere {{$a}}", + "addon.messages.contactableprivacy": "Accepter besked fra:", + "addon.messages.contactableprivacy_coursemember": "Mine kontakter og alle på mine kurser", + "addon.messages.contactableprivacy_onlycontacts": "Kun mine kontakter", + "addon.messages.contactableprivacy_site": "Alle på sitet", + "addon.messages.contactblocked": "Kontakt blokeret", "addon.messages.contactlistempty": "Kontaktlisten er tom", "addon.messages.contactname": "Navn", + "addon.messages.contactrequestsent": "Anmodning om kontakt sendt", "addon.messages.contacts": "Kontakter", + "addon.messages.decline": "Afvis", + "addon.messages.deleteallconfirm": "Er du sikker på at du vil slette hele denne samtale? Det vil ikke slette den for andre deltagere i konversationen.", + "addon.messages.deleteconversation": "Slet samtale", "addon.messages.errordeletemessage": "Fejl under sletning af filen", "addon.messages.errorwhileretrievingcontacts": "Fejl ved hentning af kontakter fra serveren", "addon.messages.errorwhileretrievingdiscussions": "Fejl ved hentning af diskussioner fra serveren", "addon.messages.errorwhileretrievingmessages": "Fejl ved hentning af beskeder fra serveren.", + "addon.messages.groupinfo": "Gruppe-info", + "addon.messages.info": "Info", + "addon.messages.isnotinyourcontacts": "{{$a}} står ikke på din kontaktliste", "addon.messages.message": "Besked", "addon.messages.messagenotsent": "Beskeden blev ikke sendt, prøv igen senere.", "addon.messages.messagepreferences": "Indstillinger for beskeder", "addon.messages.messages": "Beskeder", "addon.messages.newmessage": "Ny besked", "addon.messages.newmessages": "Nye beskeder", - "addon.messages.nomessages": "Ingen beskeder", + "addon.messages.nocontactrequests": "Ingen kontaktanmodninger", + "addon.messages.nocontactsgetstarted": "Ingen kontakter", + "addon.messages.nofavourites": "Ingen stjernemarkerede beskeder", + "addon.messages.nomessagesfound": "Ingen beskeder fundet", + "addon.messages.noncontacts": "Ikke-kontakter", "addon.messages.nousersfound": "Ingen brugere fundet", + "addon.messages.numparticipants": "{{$a}} deltagere", "addon.messages.removecontact": "Fjern kontakt", - "addon.messages.removecontactconfirm": "Kontakten vil blive fjernet fra listen", + "addon.messages.removecontactconfirm": "Bekræft at du vil fjerne {{$a}} fra kontaktlisten.", + "addon.messages.removefromfavourites": "Fjern stjerne", + "addon.messages.removefromyourcontacts": "Fjern fra kontakter", + "addon.messages.requests": "Anmodninger", + "addon.messages.requirecontacttomessage": "Du skal anmode {{$a}} om at føje dig til sin kontaktliste for at kunne skrive til hende eller til ham.", + "addon.messages.searchcombined": "Søg efter personer og beskeder", + "addon.messages.searchnocontactsfound": "Ingen kontakter fundet", + "addon.messages.searchnomessagesfound": "Ingen beskeder fundet", + "addon.messages.searchnononcontactsfound": "Ingen ikke-kontakter fundet", + "addon.messages.sendcontactrequest": "Send kontaktanmodning", "addon.messages.type_blocked": "Blokeret", "addon.messages.type_offline": "Offline", "addon.messages.type_online": "Online", "addon.messages.type_search": "Søgeresultat", "addon.messages.type_strangers": "Andre", - "addon.messages.unblockcontact": "Fjern blokering af kontakt", + "addon.messages.unabletomessage": "Du kan ikke skrive til denne bruger", + "addon.messages.unblockuser": "Fjern blokering af bruger", + "addon.messages.unblockuserconfirm": "Bekræft at du vil fjerne blokeringen af {{$a}}", + "addon.messages.userwouldliketocontactyou": "{{$a}} vil gerne kontakte dig", "addon.messages.warningmessagenotsent": "Kunne ikke sende besked(er) til brugeren {{user}}. {{error}}", + "addon.messages.wouldliketocontactyou": "Vil gerne kontakte dig", + "addon.messages.you": "Dig:", + "addon.messages.youhaveblockeduser": "Du har tidligere blokeret denne bruger", + "addon.messages.yourcontactrequestpending": "Din kontaktanmodning afventer med {{$a}}", "addon.mod_assign.acceptsubmissionstatement": "Du skal acceptere afleveringserklæringen.", "addon.mod_assign.addattempt": "Tillad endnu et forsøg på besvarelse", "addon.mod_assign.addnewattempt": "Tillad et nyt forsøg på besvarelse", @@ -162,6 +226,7 @@ "addon.mod_assign.graded": "Bedømt", "addon.mod_assign.gradedby": "Bedømt af", "addon.mod_assign.gradedon": "Bedømt", + "addon.mod_assign.gradelocked": "Denne bedømmelse er låst eller overskrevet i karakterbogen.", "addon.mod_assign.gradenotsynced": "Karakterer ikke synkroniseret", "addon.mod_assign.gradeoutof": "Bedømmelse af {{$a}}", "addon.mod_assign.gradingstatus": "Vurderingsstatus", @@ -176,6 +241,7 @@ "addon.mod_assign.markingworkflowstatereadyforrelease": "Klar til udgivelse", "addon.mod_assign.markingworkflowstatereadyforreview": "Bedømmelse fuldført", "addon.mod_assign.markingworkflowstatereleased": "Udgivet", + "addon.mod_assign.modulenameplural": "Opgaver", "addon.mod_assign.multipleteams": "Medlem af mere end en gruppe", "addon.mod_assign.multipleteams_desc": "Opgaven kræver gruppeaflevering. Du er medlem af flere grupper. For at kunne aflevere må du kun være med i en gruppe. Kontakt din lærer og få ændret dit gruppemedlemskab.", "addon.mod_assign.noattempt": "Intet besvarelsesforsøg", @@ -230,6 +296,7 @@ "addon.mod_assign_submission_file.pluginname": "Filafleveringer", "addon.mod_assign_submission_onlinetext.pluginname": "Online tekstafleveringer", "addon.mod_book.errorchapter": "Fejl under læsning af kapitel.", + "addon.mod_book.modulenameplural": "Bøger", "addon.mod_chat.beep": "Bip", "addon.mod_chat.currentusers": "Er i chatten nu:", "addon.mod_chat.enterchat": "Tryk her for at gå ind i chatten nu.", @@ -242,6 +309,7 @@ "addon.mod_chat.messagebeepsyou": "{{$a}} har lige bippet dig!", "addon.mod_chat.messageenter": "{{$a}} er lige kommet til", "addon.mod_chat.messageexit": "{{$a}} har forladt chatten", + "addon.mod_chat.modulenameplural": "Chat", "addon.mod_chat.mustbeonlinetosendmessages": "Du skal være online for at sende beskeder", "addon.mod_chat.nomessages": "Ingen beskeder endnu", "addon.mod_chat.send": "Send", @@ -252,6 +320,7 @@ "addon.mod_choice.errorgetchoice": "Fejl ved hentning af valgte data", "addon.mod_choice.expired": "Beklager, denne aktivitet er lukket d. {{$a}} og er ikke længere tilgængelig", "addon.mod_choice.full": "(Fuld)", + "addon.mod_choice.modulenameplural": "Afstemninger", "addon.mod_choice.noresultsviewable": "Resultaterne er ikke tilgængelige på nuværende tidspunkt.", "addon.mod_choice.notopenyet": "Denne aktivitet er tilgængelig fra {{$a}}", "addon.mod_choice.numberofuser": "Antal svar", @@ -287,8 +356,10 @@ "addon.mod_data.errormustsupplyvalue": "Du skal indsætte en værdi her.", "addon.mod_data.expired": "Beklager, denne aktivitet lukkede {{$a}} og er ikke længere tilgængelig", "addon.mod_data.fields": "Felter", + "addon.mod_data.foundrecords": "Din søgning matcher {{$a.num}}/{{$a.max}} poster(Nulstil filter)", "addon.mod_data.latlongboth": "Skriv både bredde- og længdegrad", "addon.mod_data.menuchoose": "Vælg...", + "addon.mod_data.modulenameplural": "Databaser", "addon.mod_data.more": "Flere", "addon.mod_data.nomatch": "Ingen matchende poster fundet", "addon.mod_data.norecords": "Ingen indlæg i databasen", @@ -320,6 +391,7 @@ "addon.mod_feedback.feedbackopen": "Tillad svar fra", "addon.mod_feedback.mapcourses": "Tilknyt feedback til kurser", "addon.mod_feedback.mode": "Tilstand", + "addon.mod_feedback.modulenameplural": "Feedback", "addon.mod_feedback.next_page": "Næste side", "addon.mod_feedback.non_anonymous": "Brugerens navn vil blive registreret og vist sammen med svarene", "addon.mod_feedback.non_anonymous_entries": "Ikke-anonyme bidrag ({{$a}})", @@ -340,6 +412,7 @@ "addon.mod_feedback.started": "Startet", "addon.mod_feedback.this_feedback_is_already_submitted": "Du har allerede gennemført denne aktivitet.", "addon.mod_folder.emptyfilelist": "Der er ingen filer at vise.", + "addon.mod_folder.modulenameplural": "Mapper", "addon.mod_forum.addanewdiscussion": "Tilføj en ny tråd", "addon.mod_forum.addanewquestion": "Tilføj et nyt spørgsmål", "addon.mod_forum.addanewtopic": "Tilføj nyt emne", @@ -362,6 +435,7 @@ "addon.mod_forum.modeflatnewestfirst": "Kronologisk visning med seneste indlæg først", "addon.mod_forum.modeflatoldestfirst": "Kronologisk visning med første indlæg først", "addon.mod_forum.modenested": "Trådet visning, udfoldet", + "addon.mod_forum.modulenameplural": "Fora", "addon.mod_forum.numdiscussions": "{{numdiscussions}} diskussioner", "addon.mod_forum.numreplies": "{{numreplies}} svar", "addon.mod_forum.posttoforum": "Send til forum", @@ -397,9 +471,11 @@ "addon.mod_glossary.fillfields": "Opslagsord og definition skal udfyldes!", "addon.mod_glossary.fullmatch": "Autolink kun til hele ord.", "addon.mod_glossary.linking": "Autolinkning", + "addon.mod_glossary.modulenameplural": "Opslagsværker", "addon.mod_glossary.noentriesfound": "Ingen opslag fundet", "addon.mod_glossary.searchquery": "Søgeord", "addon.mod_imscp.deploymenterror": "Fejl på indholdspakke!", + "addon.mod_imscp.modulenameplural": "IMS-pakker", "addon.mod_imscp.showmoduledescription": "Vis beskrivelse", "addon.mod_lesson.answer": "Svar", "addon.mod_lesson.attempt": "Forsøg: {{$a}}", @@ -443,6 +519,7 @@ "addon.mod_lesson.lowtime": "Kort tid", "addon.mod_lesson.maximumnumberofattemptsreached": "Det maksimale antal forsøg er nået - du føres til næste side", "addon.mod_lesson.modattemptsnoteacher": "Det er kun studerende der kan gennemse", + "addon.mod_lesson.modulenameplural": "Lektioner", "addon.mod_lesson.noanswer": "Et eller flere spørgsmål er ikke besvaret. Gå tilbage og aflever en besvarelse.", "addon.mod_lesson.nolessonattempts": "Ingen har prøvet denne lektion", "addon.mod_lesson.nolessonattemptsgroup": "Ingen forsøg er udført af {{$a}} gruppemedlemmer i denne lektion", @@ -485,7 +562,9 @@ "addon.mod_lti.errorgetlti": "Fejl ved hentning af moduldata.", "addon.mod_lti.errorinvalidlaunchurl": "Din start-URL er ikke gyldig.", "addon.mod_lti.launchactivity": "Start aktiviteten", + "addon.mod_lti.modulenameplural": "Eksterne værktøjer", "addon.mod_page.errorwhileloadingthepage": "Fejl ved indlæsning af siden.", + "addon.mod_page.modulenameplural": "Sider", "addon.mod_quiz.attemptfirst": "Første besvarelse", "addon.mod_quiz.attemptlast": "Sidste besvarelse", "addon.mod_quiz.attemptnumber": "Besvarelse", @@ -520,6 +599,7 @@ "addon.mod_quiz.grademethod": "Vurdering ved gentagne forsøg", "addon.mod_quiz.gradesofar": "{{$a.method}}: {{$a.mygrade}}/{{$a.quizgrade}}.", "addon.mod_quiz.marks": "Point", + "addon.mod_quiz.modulenameplural": "Quizzer", "addon.mod_quiz.mustbesubmittedby": "Svaret skal indsendes senest {{$a}}", "addon.mod_quiz.noquestions": "Der er ikke tilføjet nogen spørgsmål endnu", "addon.mod_quiz.noreviewattempt": "Du har ikke tilladelse til at gennemse denne besvarelse.", @@ -562,6 +642,7 @@ "addon.mod_quiz.yourfinalgradeis": "Den endelige vurdering af denne quiz er {{$a}}", "addon.mod_resource.errorwhileloadingthecontent": "Fejl under indlæsning af indhold.", "addon.mod_resource.modifieddate": "Ændret {{$a}}", + "addon.mod_resource.modulenameplural": "Filer", "addon.mod_resource.openthefile": "Aben filen", "addon.mod_resource.uploadeddate": "Uploadet {{$a}}", "addon.mod_scorm.asset": "Materiale", @@ -598,6 +679,7 @@ "addon.mod_scorm.incomplete": "Ukomplet", "addon.mod_scorm.lastattempt": "Sidste fuldførte forsøg", "addon.mod_scorm.mode": "Tilstand", + "addon.mod_scorm.modulenameplural": "Scorm-pakker", "addon.mod_scorm.newattempt": "Prøv et nyt forsøg", "addon.mod_scorm.noattemptsallowed": "Antal tilladte forsøg", "addon.mod_scorm.noattemptsmade": "Antal forsøg, du har gjort", @@ -617,16 +699,19 @@ "addon.mod_survey.errorgetsurvey": "Fejl ved hentning af undersøgelsesdata.", "addon.mod_survey.ifoundthat": "Jeg finder at", "addon.mod_survey.ipreferthat": "Jeg foretrækker at", + "addon.mod_survey.modulenameplural": "Undersøgelser", "addon.mod_survey.responses": "Responser", "addon.mod_survey.results": "Resultater", "addon.mod_survey.surveycompletednograph": "Du er færdig med denne undersøgelse", "addon.mod_url.accessurl": "Gå til webadressen", + "addon.mod_url.modulenameplural": "URL'er", "addon.mod_url.pointingtourl": "Ressourcen henviser til denne webadresse", "addon.mod_wiki.cannoteditpage": "Du kan ikke redigere denne side.", "addon.mod_wiki.createpage": "Opret side", "addon.mod_wiki.editingpage": "Redigering af denne side \"{{$a}}\"", "addon.mod_wiki.errornowikiavailable": "Denne wiki har endnu intet indhold.", "addon.mod_wiki.map": "Kort", + "addon.mod_wiki.modulenameplural": "Wiki'er", "addon.mod_wiki.newpagehdr": "Ny side", "addon.mod_wiki.newpagetitle": "Ny sidetitel", "addon.mod_wiki.nocontent": "Der er intet indhold til denne side", @@ -664,6 +749,7 @@ "addon.mod_workshop.gradinggradecalculated": "Beregnet karakter for vurdering", "addon.mod_workshop.gradinggradeof": "Karakter for vurdering (af {{$a}})", "addon.mod_workshop.gradinggradeover": "Tilsidesæt karakter for vurdering", + "addon.mod_workshop.modulenameplural": "Workshopper", "addon.mod_workshop.nogradeyet": "Ingen vurderinger endnu", "addon.mod_workshop.notassessed": "Endnu ikke vurderet", "addon.mod_workshop.notoverridden": "Ikke tilsidesat", @@ -1023,6 +1109,8 @@ "core.accounts": "Konti", "core.add": "Tilføj", "core.agelocationverification": "Alders- og lokaliseringskontrol", + "core.ago": "{{$a}} siden", + "core.all": "Alle", "core.allparticipants": "Alle deltagere", "core.android": "Android", "core.answer": "Svar", @@ -1059,7 +1147,7 @@ "core.confirmdeletefile": "Er du sikker på at du vil slette denne fil?", "core.confirmloss": "Er du sikker? Alle ændringer vil gå tabt.", "core.confirmopeninbrowser": "Vil du åbne den i en browser?", - "core.considereddigitalminor": "Du betragtes som en \"digital mindreårig\".", + "core.considereddigitalminor": "Du er ikke gammel nok til selv at oprette en konto her.", "core.content": "Indhold", "core.contenteditingsynced": "Det indhold du redigerer er blevet synkroniseret.", "core.contentlinks.chooseaccount": "Vælg konto", @@ -1085,11 +1173,11 @@ "core.course.overriddennotice": "Din endelige karakter fra denne aktivitet blev justeret manuelt.", "core.course.sections": "Sektioner", "core.coursedetails": "Kursusdetaljer", + "core.courses.addtofavourites": "Fremhæv dette kursus", "core.courses.allowguests": "Dette kursus tillader gæster", "core.courses.availablecourses": "Tilgængelige kurser", "core.courses.categories": "Kursuskategorier", "core.courses.confirmselfenrol": "Er du sikker på at du ønsker at tilmelde dig dette kursus?", - "core.courses.courseoverview": "Kursusoversigt", "core.courses.courses": "Kurser", "core.courses.enrolme": "Tilmeld mig", "core.courses.errorloadcourses": "En fejl opstod ved indlæsning af kurset.", @@ -1097,28 +1185,24 @@ "core.courses.errorselfenrol": "En fejl opstod under selvtilmelding.", "core.courses.filtermycourses": "Filtrer mit kursus", "core.courses.frontpage": "Forside", - "core.courses.future": "Fremtidige", - "core.courses.inprogress": "I gang", - "core.courses.morecourses": "Flere kurser", + "core.courses.hidecourse": "Vis ikke", "core.courses.mycourses": "Mine kurser", + "core.courses.mymoodle": "Betjeningspanel", "core.courses.nocourses": "Du er ikke tilmeldt nogen kurser.", - "core.courses.nocoursesfuture": "Ingen fremtidige kurser", - "core.courses.nocoursesinprogress": "Ingen igangværende kurser", - "core.courses.nocoursesoverview": "Ingen kurser", - "core.courses.nocoursespast": "Ingen tidligere kurser", "core.courses.nocoursesyet": "Der er ingen kurser i denne kategori", "core.courses.nosearchresults": "Ingen resultater", "core.courses.notenroled": "Du er ikke tilmeldt dette kursus", "core.courses.notenrollable": "Du kan ikke selv tilmelde dig dette kursus.", "core.courses.password": "Tilmeldingsnøgle", - "core.courses.past": "Tidligere", "core.courses.paymentrequired": "Dette kursus kræver betaling for tilmelding.", "core.courses.paypalaccepted": "PayPal-betalinger er velkomne", + "core.courses.removefromfavourites": "Fjern stjerne", "core.courses.search": "Søg", "core.courses.searchcourses": "Søg efter kurser", "core.courses.searchcoursesadvice": "Du kan bruge knappen kursussøgning for at få adgang som gæst eller tilmelde dig kurser der tillader det.", "core.courses.selfenrolment": "Selvtilmelding", "core.courses.sendpaymentbutton": "Send betaling via PayPal", + "core.courses.show": "Vis dette kursus", "core.courses.totalcoursesearchresults": "Kurser i alt: {{$a}}", "core.datastoredoffline": "Der blev gemt data på enheden da det ikke kunne sendes. Det vil blive sendt senere.", "core.date": "Dato", @@ -1150,6 +1234,7 @@ "core.erroropenfilenoextension": "Fejl ved åbning af fil: filen har ingen filendelse.", "core.erroropenpopup": "Denne aktivitet forsøger at åbne en popup. Det understøttes ikke i denne app.", "core.explanationdigitalminor": "Denne information kræves fordi man kun må oprette en profil hvis man er over den \"digitale lavalder\". Det er den alder hvor man må give sit samtykke til betingelser og vilkår, og til at ens data lovligt må gemmes og behandles.", + "core.favourites": "Foretrukne", "core.filename": "Filnavn", "core.filenameexist": "Filnavnet eksisterer allerede: {{$a}}", "core.fileuploader.addfiletext": "Tilføj fil", @@ -1228,6 +1313,7 @@ "core.login.createaccount": "Opret ny profil", "core.login.createuserandpass": "Vælg brugernavn og adgangskode", "core.login.credentialsdescription": "Skriv dit brugernavn og adgangskode for at logge på", + "core.login.emailconfirmsent": "

En e-mail skulle være sendt til din adresse {{$a}}

Den indeholder anvisninger på, hvordan du fuldfører din registrering.

Hvis du stadig har problemer, så kontakt webadministratoren.

", "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", @@ -1257,6 +1343,7 @@ "core.login.missingfirstname": "Mangler fornavn", "core.login.missinglastname": "Mangler efternavn", "core.login.mobileservicesnotenabled": "Mobiladgang er ikke aktiveret på din side. Kontakt venligst din Moodles administrator, hvis du ønsker mobiladgang aktiveret.", + "core.login.mustconfirm": "Du skal bekræfte din konto", "core.login.newaccount": "Ny konto", "core.login.newsitedescription": "Skriv din Moodles webadresse. Bemærk at Moodle kan være konfigureret, så den ikke virker med denne app.", "core.login.notloggedin": "Du skal være logget på.", @@ -1296,8 +1383,6 @@ "core.mainmenu.changesite": "Skift side", "core.mainmenu.help": "Hjælp", "core.mainmenu.logout": "Log ud", - "core.mainmenu.mycourses": "Mine kurser", - "core.mainmenu.togglemenu": "Skift menu", "core.mainmenu.website": "Websted", "core.maxsizeandattachments": "Maksimal størrelse på nye filer: {{$a.size}}, højeste antal bilag: {{$a.attachments}}", "core.min": "min.", @@ -1383,8 +1468,10 @@ "core.quotausage": "Du har nu brugt {{$a.used}} af din grænse på {{$a.total}}.", "core.redirectingtosite": "Du bliver videresendt til siden", "core.refresh": "Genindlæs", + "core.remove": "Fjern", "core.required": "Påkrævet", "core.requireduserdatamissing": "Denne bruger mangler nogle krævede profildata. Udfyld venligst de manglende data i din Moodle og prøv igen.
{{$a}}", + "core.resources": "Materialer", "core.restore": "Gendan", "core.retry": "Prøv igen", "core.save": "Gem", @@ -1466,6 +1553,19 @@ "core.sizetb": "TB", "core.sorry": "Beklager...", "core.sortby": "Sorter efter", + "core.strftimedate": "%d. %B %Y", + "core.strftimedatefullshort": "%j/%n-%y", + "core.strftimedateshort": "%d. %B", + "core.strftimedatetime": "%d. %B %Y, %H:%M", + "core.strftimedatetimeshort": "%d/%m-%y %H:%M", + "core.strftimedaydate": "%A, %d. %B %Y", + "core.strftimedaydatetime": "%A, %d. %B %Y, %H:%M", + "core.strftimedayshort": "%A, %d. %B", + "core.strftimedaytime": "%a, %H:%M", + "core.strftimemonthyear": "%B %Y", + "core.strftimerecent": "%d. %b, %H:%M", + "core.strftimerecentfull": "%a, %d. %b %Y, %H:%M", + "core.strftimetime": "%H:%M", "core.submit": "Gem", "core.success": "Succes", "core.tablet": "Tablet", @@ -1496,7 +1596,7 @@ "core.user.firstname": "Fornavn", "core.user.interests": "Interesser", "core.user.lastname": "Efternavn", - "core.user.manager": "Manager", + "core.user.manager": "Administrator", "core.user.newpicture": "Nyt billede", "core.user.noparticipants": "Ingen deltagere fundet på dette kursus", "core.user.participants": "Deltagere", @@ -1505,7 +1605,7 @@ "core.user.roles": "Roller", "core.user.sendemail": "E-mail", "core.user.student": "Studerende", - "core.user.teacher": "Medlærer", + "core.user.teacher": "Medunderviser", "core.user.webpage": "Webside", "core.userdeleted": "Denne brugerkonto er blevet slettet", "core.userdetails": "Brugeroplysninger", diff --git a/src/assets/lang/de-du.json b/src/assets/lang/de-du.json index 5dc8f8c6e..e3ea2f93c 100644 --- a/src/assets/lang/de-du.json +++ b/src/assets/lang/de-du.json @@ -8,44 +8,36 @@ "addon.competency.errornocompetenciesfound": "Keine Kompetenzen gefunden", "addon.competency.nocompetencies": "Keine Kompetenzen", "addon.coursecompletion.complete": "Abschließen", - "addon.coursecompletion.completed": "Abgeschlossen", - "addon.coursecompletion.completiondate": "Abschlussdatum", "addon.coursecompletion.couldnotloadreport": "Fehler beim Laden des Abschlussberichts. Versuche es später noch einmal.", - "addon.coursecompletion.coursecompletion": "Kursabschluss", - "addon.coursecompletion.criteria": "Kriterien", - "addon.coursecompletion.criteriagroup": "Kriteriengruppe", - "addon.coursecompletion.criteriarequiredall": "Alle nachfolgenden Kriterien sind notwendig.", - "addon.coursecompletion.criteriarequiredany": "Ein nachfolgendes Kriterium ist notwendig.", - "addon.coursecompletion.inprogress": "In Arbeit", - "addon.coursecompletion.manualselfcompletion": "Manueller Selbstabschluss", - "addon.coursecompletion.notyetstarted": "Nicht begonnen", - "addon.coursecompletion.pending": "Nicht erledigt", "addon.coursecompletion.required": "Notwendig", - "addon.coursecompletion.requiredcriteria": "Notwendige Kriterien", - "addon.coursecompletion.requirement": "Anforderung", "addon.coursecompletion.status": "Status", - "addon.coursecompletion.viewcoursereport": "Kursbericht anzeigen", - "addon.files.couldnotloadfiles": "Die Liste der Dateien konnte nicht geladen werden.", + "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.blockcontactconfirm": "Möchtest du von dieser Person keine Mitteilungen mehr empfangen?", "addon.messages.contactlistempty": "Die Kontaktliste ist leer.", "addon.messages.contactname": "Name", + "addon.messages.deleteallconfirm": "Möchtest du die 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.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.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.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.warningconversationmessagenotsent": "Mitteilung zum Thema {{conversation}} konnte nicht gesendet werden. {{error}}", "addon.messages.warningmessagenotsent": "Die Mitteilung an {{user}} konnte nicht gesendet werden. {{error}}", + "addon.messages.you": "Du:", "addon.mod_assign.acceptsubmissionstatement": "Bestätige das Abgabestatement zur Eigenständigkeit.", "addon.mod_assign.cannoteditduetostatementsubmission": "Du kannst in der App keine Abgabe hinzufügen oder bearbeiten, weil kein Abgabestatement 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.", @@ -55,7 +47,7 @@ "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.notallparticipantsareshown": "Teilnehmer/innen ohne Abgaben werden nicht angezeigt.", - "addon.mod_assign.submissionnotsupported": "Diese Abgabe wird von der App nicht unterstützt, so dass Informationen fehlen könnten.", + "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.userwithid": "Nutzer/in mit ID {{id}}", "addon.mod_assign.warningsubmissiongrademodified": "Die Abgabebewertung wurde auf der Website geändert.", @@ -142,7 +134,7 @@ "addon.mod_quiz.confirmclose": "Sobald du diesen Versuch beendest, kannst du deine Antworten nicht mehr bearbeiten.", "addon.mod_quiz.confirmcontinueoffline": "Dieser Versuch wurde seit {{$a}} nicht synchronisiert. Falls du inzwischen mit einem anderen Gerät weiter gearbeitet hast, könnten Daten verloren gehen.", "addon.mod_quiz.confirmleavequizonerror": "Fehler beim Speichern der Antworten. Möchtest du den Test wirklich verlassen?", - "addon.mod_quiz.errorbehaviournotsupported": "Der Test kann in der App nicht durchgeführt werden, weil das Frageverhalten nicht unterstützt wird:", + "addon.mod_quiz.errorbehaviournotsupported": "Der Test kann nicht in der App durchgeführt werden, weil das Frageverhalten nicht unterstützt wird:", "addon.mod_quiz.errordownloading": "Fehler beim Laden notwendiger Daten", "addon.mod_quiz.errorgetattempt": "Fehler beim Laden der Versuche", "addon.mod_quiz.errorgetquestions": "Fehler beim Laden der Fragen", @@ -185,10 +177,11 @@ "addon.mod_wiki.errornowikiavailable": "Dieses Wiki hat noch keinen Inhalt.", "addon.mod_wiki.gowikihome": "Zur Wiki-Startseite", "addon.mod_wiki.subwiki": "Teilwiki", - "addon.mod_wiki.titleshouldnotbeempty": "Der Titel kann nicht leer sein.", + "addon.mod_wiki.titleshouldnotbeempty": "Der Titel darf nicht leer sein.", "addon.mod_wiki.viewpage": "Seite anzeigen", "addon.mod_wiki.wikipage": "Wiki-Seite", "addon.mod_workshop.assessmentstrategynotsupported": "Die Beurteilungsstrategie {{$a}} wird nicht unterstützt.", + "addon.mod_workshop.submissionrequiredtitle": "Du musst einen Titel eingeben.", "addon.mod_workshop.warningassessmentmodified": "Die Abgabe wurde auf der Website geändert.", "addon.mod_workshop.warningsubmissionmodified": "Die Beurteilung wurde auf der Website geändert.", "addon.notes.userwithid": "Nutzer/in mit ID {{id}}", @@ -198,6 +191,7 @@ "addon.notifications.playsound": "Signalton abspielen", "addon.notifications.therearentnotificationsyet": "Keine Systemmitteilungen", "core.android": "Android", + "core.areyousure": "Bist du sicher?", "core.cannotconnect": "Die Verbindung ist nicht möglich. Prüfe, ob die URL eingegeben richtig ist und dass mindestens Moodle 2.4 verwendet wird.", "core.cannotdownloadfiles": "Das Herunterladen von Dateien ist deaktiviert. Frage den Administrator.", "core.captureaudio": "Audio aufnehmen", @@ -210,7 +204,7 @@ "core.confirmcanceledit": "Möchtest du diese Seite wirklich verlassen? Alle Änderungen gehen verloren!", "core.confirmloss": "Möchtest du wirklich alle Änderungen verlieren?", "core.confirmopeninbrowser": "Möchtest du dies im Webbrowser öffnen?", - "core.considereddigitalminor": "Du benötigst eine Bestätigung deiner Eltern zur Registrierung.", + "core.considereddigitalminor": "Du bist zu jung, um ein Nutzerkonto für diese Website zu erstellen.", "core.contenteditingsynced": "Der Inhalt, den du gerade bearbeitest, wurde synchronisiert.", "core.contentlinks.chooseaccount": "Nutzerkonto wählen", "core.contentlinks.chooseaccounttoopenlink": "Nutzerkonto wählen, mit dem der Link geöffnet werden soll.", @@ -230,12 +224,18 @@ "core.course.contents": "Inhalte", "core.course.couldnotloadsectioncontent": "Die Abschnittsinhalte konnten nicht geladen werden. Versuche es später noch einmal.", "core.course.couldnotloadsections": "Die Abschnitte konnten nicht geladen werden. Versuche es später noch einmal.", + "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.manualcompletionnotsynced": "Manueller Abschluss wurde nicht synchronisiert", "core.course.nocontentavailable": "Momentan sind keine Inhalte verfügbar", + "core.course.refreshcourse": "Kurs neu laden", "core.course.useactivityonbrowser": "Du kannst dies im Webbrowser verwenden.", + "core.course.warningmanualcompletionmodified": "Manueller Abschluss einer Aktivität wurde auf der Website geändert.", + "core.course.warningofflinemanualcompletiondeleted": "Manueller Offline-Abschluss des Kurses '{{name}}' wurde gelöscht. {{error}}", "core.courses.cannotretrievemorecategories": "Kursbereiche tiefer als Level {{$a}} können nicht abgerufen werden.", "core.courses.confirmselfenrol": "Möchtest du dich selbst in diesen Kurs einschreiben?", + "core.courses.downloadcourses": "Kurse herunterladen", "core.courses.enrolme": "Selbst einschreiben", "core.courses.errorloadcategories": "Fehler beim Laden von Kursbereichen", "core.courses.errorloadcourses": "Fehler beim Laden von Kursen", @@ -269,10 +269,11 @@ "core.errordeletefile": "Fehler beim Löschen der Datei. Versuche es noch einmal.", "core.errordownloading": "Fehler beim Laden der Datei", "core.errordownloadingsomefiles": "Fehler beim Herunterladen der Dateien. Einige Dateien könnten fehlen.", - "core.errorfileexistssamename": "Eine Datei mit diesem Namen existiert bereits.", + "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.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.", "core.erroropenfilenoextension": "Fehler beim Öffnen: Die Datei hat keine Extension.", "core.erroropenpopup": "Die Aktivität versucht, ein Popup zu öffnen. Popups werden in der App aber nicht unterstützt.", @@ -297,7 +298,8 @@ "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", - "core.fileuploader.selectafile": "Wähle eine Datei.", + "core.fileuploader.readingfileperc": "Datei lesen: {{$a}}%", + "core.fileuploader.selectafile": "Datei wählen", "core.fileuploader.uploadafile": "Datei hochladen", "core.fileuploader.uploading": "Hochladen", "core.fileuploader.uploadingperc": "Hochladen: {{$a}}%", @@ -323,13 +325,15 @@ "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": "

In Kürze wird eine E-Mail an {{$a}} gesendet.

Du findest eine einfache Anleitung, wie du die Registrierung abschließt. Bei Schwierigkeiten frage den Administrator 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. 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.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 Informationen: https://docs.moodle.org/dev/Moodle_Mobile_development_using_Chrome_or_Chromium", + "core.login.erroraccesscontrolalloworigin": "Der Cross-Origin.Aufruf wurde zurückgewiesen. Weitere Informationen: https://docs.moodle.org/dev/Moodle_Mobile_development_using_Chrome_or_Chromium", "core.login.errordeletesite": "Fehler beim Entfernen der Website aus der Liste. Versuche es noch einmal.", "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.helpmelogin": "

Es gibt viele tausend Moodle-Websites auf der Welt. Die mobile App kann sich nur mit einem Moodle verbinden, für das der mobile Zugriff freigegeben ist.

\n

Alle Informationen findest du unter http://docs.moodle.org/de/Mobile_app.

\n


Wenn du die App mit einer Demo-Website testen möchtest, schreibe teacher oder student in das Feld URL der Website. Tippe dann auf die Taste Verbinden.

", + "core.login.helpmelogin": "

Auf der Welt gibt es sehr viele Websites mit Moodle. Diese App kann aber nur mit den 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/Mobiles_Moodle

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", "core.login.invalidmoodleversion": "Falsche Version. Mindestens Moodle 2.4 ist notwendig.", @@ -337,12 +341,16 @@ "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 bitte die Moodle Classic App.", + "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 zusammenarbeitet.

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, indem du eine ältere App-Version verwendest, die du hier herunterladen kannst.", "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.mobileservicesnotenabled": "Der mobile Zugriff ist für diese Website nicht aktiviert. Frage den Administrator, wenn du den mobilen Zugriff 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.passwordrequired": "Kennwort fehlt", @@ -351,6 +359,8 @@ "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.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.", "core.login.reconnectssodescription": "Die Authentifizierung ist abgelaufen oder ungültig. Du musst dich im Webbrowser neu anmelden.", @@ -358,21 +368,23 @@ "core.login.selectsite": "Wähle deine Website", "core.login.signupplugindisabled": "{{$a}} ist nicht aktiviert.", "core.login.siteaddress": "URL der Website", + "core.login.sitehasredirect": "Deine Website enthält mindestens eine HTTP-Weiterleitung. Die App kann Weiterleitungen verarbeiten, weswegen der Verbindungaufbau zur Website fehlschlagen könnte..", "core.login.siteinmaintenance": "Die Website ist im Wartungsmodus.", "core.login.sitepolicynotagreederror": "Zustimmungserklärung nicht bestätigt", "core.login.siteurl": "URL der Website", - "core.login.siteurlrequired": "Die URL der Website ist notwendig, z.B. https://www.meinewebsite.de", - "core.login.stillcantconnect": "Kannst du immer noch nicht verbinden?", + "core.login.siteurlrequired": "Die URL der Website ist notwendig, z.B. https://www.meinmoode.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?", - "core.login.webservicesnotenabled": "Die Webservices sind für die Website nicht aktiviert. Frage den Administrator, wenn du den mobilen Zugriff aktiviert haben möchtest.", + "core.login.webservicesnotenabled": "Die Webservices sind für die Website deaktiviert. Frage den Administrator, wenn du den mobilen Zugriff aktiviert haben möchtest.", "core.lostconnection": "Die Authentifizierung ist abgelaufen oder ungültig. Du musst dich neu anmelden.", "core.mainmenu.appsettings": "Einstellungen", "core.mainmenu.changesite": "Website wechseln", - "core.mainmenu.togglemenu": "Menü umschalten", "core.mainmenu.website": "Website im Browser", + "core.networkerroriframemsg": "Dieser Inhalt ist offline nicht verfügbar. Du musst online sein und es dann noch einmal versuchen.", "core.networkerrormsg": "Problem mit der Verbindung. Prüfe die Verbindung und versuche es noch einmal.", "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.notapplicable": "n/a", "core.notsent": "Nicht gesendet", @@ -382,6 +394,7 @@ "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.percentagenumber": "{{$a}}%", "core.pulltorefresh": "Zum Aktualisieren runterziehen", + "core.question.cannotdeterminestatus": "Status kann nicht festgestellt werden", "core.question.errorattachmentsnotsupported": "Die App erlaubt keine Antworten mit Dateianhängen.", "core.question.errorinlinefilesnotsupported": "Die App unterstützt keine Bearbeitung von integrierten Dateien.", "core.question.errorquestionnotsupported": "Die App unterstützt diesen Fragetyp nicht: {{$a}}.", @@ -396,23 +409,25 @@ "core.settings.appready": "App nutzbar", "core.settings.cannotsyncoffline": "Offline kann nicht synchronisiert werden.", "core.settings.cannotsyncwithoutwifi": "Die Daten wurden nicht synchronisiert, weil die Einstellungen dies nur mit WLAN erlauben. Stelle eine WLAN-Verbindung her.", + "core.settings.compilationinfo": "Compilation Info", "core.settings.cordovadevicemodel": "Cordova Device Model", "core.settings.cordovadeviceosversion": "Cordova Device OS Version", "core.settings.cordovadeviceplatform": "Cordova Device Platform", "core.settings.cordovadeviceuuid": "Cordova Device UUID", "core.settings.cordovaversion": "Cordova Version", + "core.settings.debugdisplaydescription": "Wenn diese Option aktiviert ist, werden zusätzliche Fehlerdaten angezeigt.", "core.settings.deletesitefiles": "Möchtest du wirklich alle von der Website '{{sitename}}' geladenen Dateien löschen?", "core.settings.deletesitefilestitle": "Dateien löschen", "core.settings.deviceinfo": "Geräteinformationen", "core.settings.deviceos": "Geräte-OS", "core.settings.devicewebworkers": "Device Web Workers unterstützt", "core.settings.displayformat": "Bildschirmformat", - "core.settings.enabledownloadsection": "Herunterladen von Abschnitten aktivieren", + "core.settings.enabledownloadsection": "Abschnitte herunterladen", "core.settings.enablerichtexteditor": "Texteditor aktivieren", - "core.settings.enablerichtexteditordescription": "Der Texteditor wird überall dort verwendet, wo er für die Textbearbeitung sinnvoll ist.", + "core.settings.enablerichtexteditordescription": "Der Texteditor wird verfügbar, wenn Texte eingegeben werden.", "core.settings.enablesyncwifi": "Nur mit WLAN synchronierieren", "core.settings.errordeletesitefiles": "Fehler beim Löschen der Dateien", - "core.settings.errorsyncsite": "Fehler beim Synchronisieren der Daten. Prüfe die Internetverbindung und versuche es noch einmal.", + "core.settings.errorsyncsite": "Fehler beim Synchronisieren der Daten. Du musst online sein und es dann noch einmal versuchen.", "core.settings.estimatedfreespace": "Verfügbarer Speicherplatz", "core.settings.filesystemroot": "Dateisystembasis", "core.settings.localnotifavailable": "Lokale Mitteilungen verfügbar", @@ -422,6 +437,7 @@ "core.settings.networkstatus": "Internetverbindung", "core.settings.privacypolicy": "Datenschutzerklärung", "core.settings.reportinbackground": "Fehler automatisch senden", + "core.settings.showdownloadoptions": "Optionen zum Herunterladen anzeigen", "core.settings.spaceusage": "Speicherplatz", "core.settings.synchronization": "Synchronisieren", "core.settings.synchronizenow": "Synchronisieren", @@ -430,35 +446,52 @@ "core.settings.versionname": "Versionsname", "core.settings.wificonnection": "WLAN-Verbindung", "core.sharedfiles.chooseaccountstorefile": "Wähle ein Nutzerkonto, um die Datei dort zu speichern.", - "core.sharedfiles.chooseactionrepeatedfile": "Eine Datei mit diesem Namen existiert bereits. Möchtest du die vorhandene Datei ersetzen oder umbenennen in '{{$a}}'?", - "core.sharedfiles.errorreceivefilenosites": "Keine Websites gespeichert. Füge zuerst eine Website hinzu, bevor du eine Datei über die App teilst.", + "core.sharedfiles.chooseactionrepeatedfile": "Eine Datei mit gleichem Namen existiert bereits. Möchtest du die vorhandene Datei ersetzen oder umbenennen in '{{$a}}'?", + "core.sharedfiles.errorreceivefilenosites": "Keine gespeicherten Websites. Füge zuerst eine Website hinzu, bevor du eine Datei über die App teilst.", "core.sharedfiles.nosharedfiles": "Keine geteilten Dateien auf dieser Website", "core.sharedfiles.nosharedfilestoupload": "Du hast hier noch keine Datei zum Hochladen. Wenn du eine Datei aus einer anderen App hochladen möchtest, suche diese Datei und tippe auf die Taste 'Öffnen in'.", "core.sharedfiles.rename": "Umbenennen", "core.sharedfiles.replace": "Ersetzen", "core.sharedfiles.sharedfiles": "Geteilte Dateien", - "core.sharedfiles.successstorefile": "Die Datei wurde gespeichert. Du kannst die Datei in den Bereich 'Meine Dateien' hochladen oder bei Aktivitäten verwenden.", + "core.sharedfiles.successstorefile": "Die Datei wurde gespeichert. Du kannst die Datei in den Bereich 'Meine Dateien' hochladen oder in einer Aktivität verwenden.", "core.sitemaintenance": "", "core.sizetb": "TB", "core.sorry": "Sorry ...", + "core.strftimedate": "%d. %B %Y", + "core.strftimedatefullshort": "%d.%m.%y", + "core.strftimedateshort": "%d. %B", + "core.strftimedatetime": "%d. %B %Y, %H:%M", + "core.strftimedatetimeshort": "%d.%m.%Y %H:%M", + "core.strftimedaydate": "%A, %d. %B %Y", + "core.strftimedaydatetime": "%A, %d. %B %Y, %H:%M", + "core.strftimedayshort": "%A, %d. %B", + "core.strftimedaytime": "%a, %H:%M", + "core.strftimemonthyear": "%B %Y", + "core.strftimerecent": "%d. %b, %H:%M", + "core.strftimerecentfull": "%a, %d. %b %Y, %H:%M", + "core.strftimetime": "%H:%M", "core.tablet": "Tablet", "core.thereisdatatosync": "Die Offline-Daten {{$a}} müssen synchronisiert werden.", "core.thisdirection": "ltr", "core.tryagain": "Versuche es noch einmal.", "core.twoparagraphs": "{{p1}}

{{p2}}", "core.uhoh": "Uh oh!", - "core.unexpectederror": "Unerwarteter Fehler! Starte die App neu und versuche es noch einmal.", - "core.unicodenotsupported": "Manche Emojis werden von der Website nicht unterstützt. Die betreffenden Zeichen werden beim Senden der Mitteilung gelöscht.", + "core.unexpectederror": "Unerwarteter Fehler! Du musst die App neu starten und es dann noch einmal versuchen.", + "core.unicodenotsupported": "Manche Emojis können auf dieser Website nicht verwendet werden. Die betreffenden Zeichen werden beim Senden der Mitteilung gelöscht.", "core.unicodenotsupportedcleanerror": "Der Text ist leer, nachdem die Unicode-Zeichen gelöscht wurden.", "core.unknown": "Unbekannt", "core.unzipping": "Entpacken ...", "core.user.contact": "Kontakt", "core.user.detailsnotavailable": "Die Nutzerdetails zu dieser Person sind für dich nicht verfügbar.", "core.user.editingteacher": "Trainer/in", + "core.user.errorloaduser": "Fehler beim Laden der Nutzerdetails", "core.user.manager": "Manager/in", "core.user.sendemail": "E-Mail", "core.user.student": "Teilnehmer/in", "core.user.teacher": "Trainer/in ohne Bearbeitungsrecht", + "core.viewcode": "Code zeigen", + "core.vieweditor": "Editor zeigen", + "core.viewembeddedcontent": "Eingebettete Inhalte anzeigen", "core.warningofflinedatadeleted": "Die Offline-Daten von {{component}} '{{name}}' wurden gelöscht. {{error}}", "core.whoops": "Uuups!", "core.whyisthishappening": "Warum passiert dies?", diff --git a/src/assets/lang/de.json b/src/assets/lang/de.json index 66508cb8f..3bb849fb3 100644 --- a/src/assets/lang/de.json +++ b/src/assets/lang/de.json @@ -1,15 +1,61 @@ { + "addon.badges.alignment": "Kompetenz", "addon.badges.badgedetails": "Grundeinstellungen", "addon.badges.badges": "Badges", + "addon.badges.bendorsement": "Bestätigung", + "addon.badges.claimcomment": "Kommentar zur Anerkennung", + "addon.badges.claimid": "Leitsatz (URL)", "addon.badges.contact": "Kontakt", "addon.badges.dateawarded": "Verleihdatum", "addon.badges.expired": "Abgelaufen", "addon.badges.expirydate": "Ablaufdatum", + "addon.badges.imageauthoremail": "E-Mail des Bildautors", + "addon.badges.imageauthorname": "Name des Bildautors", + "addon.badges.imageauthorurl": "Autor des Bildes", + "addon.badges.imagecaption": "Bildtitel", "addon.badges.issuancedetails": "Ablaufdatum", "addon.badges.issuerdetails": "Verleiher/in", + "addon.badges.issueremail": "E-Mail", "addon.badges.issuername": "Verleiher/in", + "addon.badges.issuerurl": "URL des Verleihers", + "addon.badges.language": "Sprache", + "addon.badges.noalignment": "Für diesen Badge sind keine Kompetenzen angegeben.", "addon.badges.nobadges": "Keine Badges verfügbar", + "addon.badges.norelated": "Dieser Badge hat keine verwandten Badges.", "addon.badges.recipientdetails": "Empfängerdetails", + "addon.badges.relatedbages": "Zugeordnete Badges", + "addon.badges.version": "Version", + "addon.badges.warnexpired": "(Dieser Badge ist abgelaufen!)", + "addon.block_activitymodules.pluginname": "Aktivitäten", + "addon.block_myoverview.all": "Alle", + "addon.block_myoverview.favourites": "Markierte", + "addon.block_myoverview.future": "Künftige", + "addon.block_myoverview.hiddencourses": "Verborgene", + "addon.block_myoverview.inprogress": "Laufende", + "addon.block_myoverview.lastaccessed": "Letzter Zugriff", + "addon.block_myoverview.morecourses": "Weitere Kurse", + "addon.block_myoverview.nocourses": "Keine Kurse", + "addon.block_myoverview.past": "Vergangene", + "addon.block_myoverview.pluginname": "Kursübersicht", + "addon.block_myoverview.title": "Kursname", + "addon.block_recentlyaccessedcourses.nocourses": "Keine zuletzt besuchten Kurse", + "addon.block_recentlyaccessedcourses.pluginname": "Zuletzt besuchte Kurse", + "addon.block_recentlyaccesseditems.noitems": "Keine zuletzt genutzten Objekte", + "addon.block_recentlyaccesseditems.pluginname": "Zuletzt genutzte Objekte", + "addon.block_sitemainmenu.pluginname": "Hauptmenü", + "addon.block_starredcourses.nocourses": "Keine markierten Kurse", + "addon.block_starredcourses.pluginname": "Markierte Kurse", + "addon.block_timeline.duedate": "Fälligkeitsdatum", + "addon.block_timeline.next30days": "Nächste 30 Tage", + "addon.block_timeline.next3months": "Nächste 3 Monate", + "addon.block_timeline.next6months": "Nächste 6 Monate", + "addon.block_timeline.next7days": "Nächste 7 Tage", + "addon.block_timeline.nocoursesinprogress": "Keine laufenden Kurse", + "addon.block_timeline.noevents": "Keine bevorstehenden Aktivitäten fällig", + "addon.block_timeline.overdue": "Überfällig", + "addon.block_timeline.pluginname": "Zeitleiste", + "addon.block_timeline.sortbycourses": "Sortiert nach Kursen", + "addon.block_timeline.sortbydates": "Sortiert nach Datum", "addon.calendar.calendar": "Kalender", "addon.calendar.calendarevents": "Kalender", "addon.calendar.defaultnotificationtime": "Standardmäßige Benachrichtigungszeit", @@ -77,58 +123,104 @@ "addon.coursecompletion.complete": "Abschließen", "addon.coursecompletion.completecourse": "Kurs beenden", "addon.coursecompletion.completed": "Abgeschlossen", - "addon.coursecompletion.completiondate": "Abschlussdatum", + "addon.coursecompletion.completiondate": "Datum des Kursabschlusses", "addon.coursecompletion.completionmenuitem": "Abschluss", "addon.coursecompletion.couldnotloadreport": "Fehler beim Laden des Abschlussberichts. Versuchen Sie es später noch einmal.", "addon.coursecompletion.coursecompletion": "Kursabschluss", "addon.coursecompletion.criteria": "Kriterien", "addon.coursecompletion.criteriagroup": "Kriteriengruppe", - "addon.coursecompletion.criteriarequiredall": "Alle nachfolgenden Kriterien sind notwendig.", - "addon.coursecompletion.criteriarequiredany": "Ein nachfolgendes Kriterium ist notwendig.", - "addon.coursecompletion.inprogress": "In Arbeit", - "addon.coursecompletion.manualselfcompletion": "Manueller Selbstabschluss", - "addon.coursecompletion.notyetstarted": "Nicht begonnen", - "addon.coursecompletion.pending": "Nicht erledigt", - "addon.coursecompletion.required": "Notwendig", - "addon.coursecompletion.requiredcriteria": "Notwendige Kriterien", - "addon.coursecompletion.requirement": "Anforderung", + "addon.coursecompletion.criteriarequiredall": "Alle nachfolgenden Kriterien sind notwendig", + "addon.coursecompletion.criteriarequiredany": "Eine der nachfolgenden Kriterien ist notwendig", + "addon.coursecompletion.inprogress": "In Bearbeitung", + "addon.coursecompletion.manualselfcompletion": "Manueller eigener Abschluss", + "addon.coursecompletion.notyetstarted": "Noch nicht begonnen", + "addon.coursecompletion.pending": "Unerledigt", + "addon.coursecompletion.required": "Erforderlich", + "addon.coursecompletion.requiredcriteria": "Notwendiges Kriterium", + "addon.coursecompletion.requirement": "Anforderungen", "addon.coursecompletion.status": "Status", - "addon.coursecompletion.viewcoursereport": "Kursbericht anzeigen", - "addon.files.couldnotloadfiles": "Die Liste der Dateien konnte nicht geladen werden.", + "addon.coursecompletion.viewcoursereport": "Kursbericht ansehen", + "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.files.files": "Dateien", "addon.files.privatefiles": "Meine Dateien", "addon.files.sitefiles": "Dateien der Website", "addon.messageoutput_airnotifier.processorsettingsdesc": "Geräte konfigurieren", + "addon.messages.acceptandaddcontact": "Annehmen und zu Kontakten hinzufügen", "addon.messages.addcontact": "Kontakt hinzufügen", - "addon.messages.blockcontact": "Kontakt sperren", - "addon.messages.blockcontactconfirm": "Möchten Sie von dieser Person keine Mitteilungen mehr empfangen?", + "addon.messages.addcontactconfirm": "Möchten Sie wirklich {{$a}} zu Ihren Kontakten hinzufügen?", + "addon.messages.addtofavourites": "Markierung setzen", + "addon.messages.addtoyourcontacts": "Zu Kontakten hinzufügen", "addon.messages.blocknoncontacts": "Mitteilungen nur für Kontakte zulassen", + "addon.messages.blockuser": "Person sperren", + "addon.messages.blockuserconfirm": "Möchten Sie wirklich {{$a}} sperren?", + "addon.messages.contactableprivacy": "Mitteilungen akzeptieren von:", + "addon.messages.contactableprivacy_coursemember": "Meine Kontakte und jeder aus meinen Kursen", + "addon.messages.contactableprivacy_onlycontacts": "Nur meine Kontakte", + "addon.messages.contactableprivacy_site": "Jeder im System", + "addon.messages.contactblocked": "Kontakt blockiert", "addon.messages.contactlistempty": "Die Kontaktliste ist leer.", "addon.messages.contactname": "Name", + "addon.messages.contactrequestsent": "Kontaktanfrage gesendet", "addon.messages.contacts": "Kontakte", + "addon.messages.decline": "Ablehnen", + "addon.messages.deleteallconfirm": "Möchten Sie wirklich die gesamte Kommunikation löschen? Die Kommunikation bleibt allerdings bei den anderen beteiligten Personen erhalten.", + "addon.messages.deleteconversation": "Kommunikation 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.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.groupconversations": "Gruppe", + "addon.messages.groupinfo": "Gruppeninfo", + "addon.messages.individualconversations": "Persönlich", + "addon.messages.info": "Info", + "addon.messages.isnotinyourcontacts": "{{$a}} gehört nicht zu Ihren Kontakten", "addon.messages.message": "Mitteilung", "addon.messages.messagenotsent": "Die Mitteilung wurde nicht gesendet. Versuchen Sie es später noch einmal.", "addon.messages.messagepreferences": "Mitteilungen", "addon.messages.messages": "Mitteilungen", "addon.messages.newmessage": "Neue Mitteilung", "addon.messages.newmessages": "Neue Mitteilungen", - "addon.messages.nomessages": "Keine Mitteilungen", + "addon.messages.nocontactrequests": "Keine Kontaktanfragen", + "addon.messages.nocontactsgetstarted": "Keine Kontakte", + "addon.messages.nofavourites": "Keine markierten Mitteilungen", + "addon.messages.nogroupconversations": "Keine Gruppenmitteilungen", + "addon.messages.noindividualconversations": "Keine persönlichen Mitteilungen", + "addon.messages.nomessagesfound": "Keine Mitteilungen gefunden", + "addon.messages.noncontacts": "Weitere Personen", "addon.messages.nousersfound": "Keine Personen gefunden", + "addon.messages.numparticipants": "{{$a}} Teilnehmer/innen", "addon.messages.removecontact": "Kontakt entfernen", - "addon.messages.removecontactconfirm": "Der Kontakt wird aus Ihrer Kontaktliste gelöscht.", + "addon.messages.removecontactconfirm": "Möchten Sie wirklich {{$a}} aus Ihren Kontakten entfernen?", + "addon.messages.removefromfavourites": "Markierung 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 Nachricht senden zu können.", + "addon.messages.searchcombined": "Personen und Mitteilungen suchen", + "addon.messages.searchnocontactsfound": "Keine Kontakte gefunden", + "addon.messages.searchnomessagesfound": "Keine Mitteilungen gefunden", + "addon.messages.searchnononcontactsfound": "Keine weiteren Personen gefunden", + "addon.messages.sendcontactrequest": "Kontaktanfrage senden", + "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.unblockcontact": "Kontakt aktivieren", + "addon.messages.unabletomessage": "Sie können dieser Person keine Nachricht senden.", + "addon.messages.unblockuser": "Person entsperren", + "addon.messages.unblockuserconfirm": "Möchten Sie {{$a}} wirklich freigeben?", + "addon.messages.userwouldliketocontactyou": "{{$a}} möchte Sie 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 Sie kontaktieren", + "addon.messages.you": "Sie:", + "addon.messages.youhaveblockeduser": "Sie haben diese Person in der Vergangenheit gesperrt", + "addon.messages.yourcontactrequestpending": "Ihre Kontaktanfrage an {{$a}} ist noch offen", "addon.mod_assign.acceptsubmissionstatement": "Bestätigen Sie das Abgabestatement zur Eigenständigkeit.", "addon.mod_assign.addattempt": "Weiteren Versuch zulassen", "addon.mod_assign.addnewattempt": "Neuen Versuch hinzufügen", @@ -165,7 +257,9 @@ "addon.mod_assign.grade": "Bewertung", "addon.mod_assign.graded": "Bewertet", "addon.mod_assign.gradedby": "Bewertet von", + "addon.mod_assign.gradedfollowupsubmit": "Bewertet - empfangene Abgabe nachverfolgen", "addon.mod_assign.gradedon": "Bewertet am", + "addon.mod_assign.gradelocked": "Diese Bewertung ist gesperrt oder wurde im Bewertungsbereich überschrieben.", "addon.mod_assign.gradenotsynced": "Bewertung nicht synchronisiert", "addon.mod_assign.gradeoutof": "Bewertung (max. {{$a}})", "addon.mod_assign.gradingstatus": "Bewertungsstatus", @@ -180,6 +274,7 @@ "addon.mod_assign.markingworkflowstatereadyforrelease": "Fertig zur Freigabe", "addon.mod_assign.markingworkflowstatereadyforreview": "Bewertung abgeschlossen", "addon.mod_assign.markingworkflowstatereleased": "Freigegeben", + "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.noattempt": "Kein Versuch", @@ -202,7 +297,7 @@ "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, so dass Informationen fehlen könnten.", + "addon.mod_assign.submissionnotsupported": "Diese Abgabe wird von der App nicht unterstützt und es könnten Informationen 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", @@ -234,6 +329,7 @@ "addon.mod_assign_submission_file.pluginname": "Dateiabgabe", "addon.mod_assign_submission_onlinetext.pluginname": "Texteingabe online", "addon.mod_book.errorchapter": "Fehler beim Lesen des Kapitels", + "addon.mod_book.modulenameplural": "Bücher", "addon.mod_chat.beep": "Beep", "addon.mod_chat.currentusers": "Aktuelle Nutzer/innen", "addon.mod_chat.enterchat": "Chat betreten", @@ -246,6 +342,7 @@ "addon.mod_chat.messagebeepsyou": "{{$a}} hat Sie angepiepst!", "addon.mod_chat.messageenter": "{{$a}} hat den Chat gerade betreten", "addon.mod_chat.messageexit": "{{$a}} hat den Chat verlassen", + "addon.mod_chat.modulenameplural": "Chats", "addon.mod_chat.mustbeonlinetosendmessages": "Sie müssen online sein, um Mitteilungen zu senden.", "addon.mod_chat.nomessages": "Keine Mitteilungen", "addon.mod_chat.send": "Senden", @@ -256,6 +353,7 @@ "addon.mod_choice.errorgetchoice": "Fehler beim Laden der Abstimmung", "addon.mod_choice.expired": "Diese Abstimmung ist seit {{$a}} beendet. Eine Auswahl ist nicht mehr möglich.", "addon.mod_choice.full": "(Nicht verfügbar)", + "addon.mod_choice.modulenameplural": "Abstimmungen", "addon.mod_choice.noresultsviewable": "Ergebnisse sind aktuell nicht sichtbar.", "addon.mod_choice.notopenyet": "Die Aktivität ist nicht verfügbar bis {{$a}}.", "addon.mod_choice.numberofuser": "Anzahl der Antworten", @@ -293,8 +391,10 @@ "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.fields": "Felder", + "addon.mod_data.foundrecords": "Einträge gefunden: {{$a.num}}/{{$a.max}} (Filter zurücksetzen)", "addon.mod_data.latlongboth": "Längen- und Breitengrad müssen eingetragen werden.", "addon.mod_data.menuchoose": "Auswählen ...", + "addon.mod_data.modulenameplural": "Datenbanken", "addon.mod_data.more": "Einzelansicht", "addon.mod_data.nomatch": "Keine passenden Einträge gefunden", "addon.mod_data.norecords": "Keine Einträge in der Datenbank", @@ -326,6 +426,7 @@ "addon.mod_feedback.feedbackopen": "Antworten erlauben ab", "addon.mod_feedback.mapcourses": "Diesem Feedback Kurse zuordnen", "addon.mod_feedback.mode": "Modus", + "addon.mod_feedback.modulenameplural": "Feedbacks", "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}})", @@ -346,6 +447,7 @@ "addon.mod_feedback.started": "Begonnen", "addon.mod_feedback.this_feedback_is_already_submitted": "Sie haben diese Aktivität bereits beendet.", "addon.mod_folder.emptyfilelist": "Keine Dateien", + "addon.mod_folder.modulenameplural": "Verzeichnisse", "addon.mod_forum.addanewdiscussion": "Neues Thema hinzufügen", "addon.mod_forum.addanewquestion": "Neue Frage hinzufügen", "addon.mod_forum.addanewtopic": "Neues Thema hinzufügen", @@ -368,6 +470,7 @@ "addon.mod_forum.modeflatnewestfirst": "Anzeige nach Zeit (neu > alt)", "addon.mod_forum.modeflatoldestfirst": "Anzeige nach Zeit (alt > neu)", "addon.mod_forum.modenested": "Anzeige geschachtelt", + "addon.mod_forum.modulenameplural": "Foren", "addon.mod_forum.numdiscussions": "{{numdiscussions}} Themen", "addon.mod_forum.numreplies": "{{numreplies}} Antworten", "addon.mod_forum.posttoforum": "Beitrag absenden", @@ -403,9 +506,11 @@ "addon.mod_glossary.fillfields": "Begriff und Definition sind Pflichtfelder.", "addon.mod_glossary.fullmatch": "Nur vollständige Wörter", "addon.mod_glossary.linking": "Autoverlinkung", + "addon.mod_glossary.modulenameplural": "Glossare", "addon.mod_glossary.noentriesfound": "Keine Einträge", "addon.mod_glossary.searchquery": "Suchanfrage", "addon.mod_imscp.deploymenterror": "Fehler im IMS-Content!", + "addon.mod_imscp.modulenameplural": "IMS-Content", "addon.mod_imscp.showmoduledescription": "Beschreibung anzeigen", "addon.mod_lesson.answer": "Antwort", "addon.mod_lesson.attempt": "Versuch: {{$a}}", @@ -449,6 +554,7 @@ "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.nolessonattempts": "Bisher keine Versuche", "addon.mod_lesson.nolessonattemptsgroup": "Keines der {{$a}} Gruppenmitglieder hat zu dieser Lektion einen Versuch gemacht.", @@ -493,7 +599,9 @@ "addon.mod_lti.errorgetlti": "Fehler beim Laden der Moduldaten", "addon.mod_lti.errorinvalidlaunchurl": "Die Launch-URL ist ungültig.", "addon.mod_lti.launchactivity": "Aktivität starten", + "addon.mod_lti.modulenameplural": "Externe Tools", "addon.mod_page.errorwhileloadingthepage": "Fehler beim Laden des Seiteninhalts", + "addon.mod_page.modulenameplural": "Textseiten", "addon.mod_quiz.attemptfirst": "Erster Versuch", "addon.mod_quiz.attemptlast": "Letzter Versuch", "addon.mod_quiz.attemptnumber": "Versuch", @@ -510,7 +618,7 @@ "addon.mod_quiz.connectionerror": "Sie haben die Netzwerkverbindung verloren. Das automatische Speichern konnte daher nicht ausgeführt werden.\n\nMachen Sie sich eine Notiz über alle in den letzten Minuten eingegebenen Antworten und versuchen Sie die Vernindung wieder herzustellen.\n\nSobald die Verbindung wieder hergestellt wurde, sollten Sie die Antworten erneut speichern. Diese Nachricht verschwindet dann.", "addon.mod_quiz.continueattemptquiz": "Letzten Versuch fortsetzen", "addon.mod_quiz.continuepreview": "Letzte Vorschau fortsetzen", - "addon.mod_quiz.errorbehaviournotsupported": "Der Test kann in der App nicht durchgeführt werden, weil das Frageverhalten nicht unterstützt wird:", + "addon.mod_quiz.errorbehaviournotsupported": "Der Test kann nicht in der App durchgeführt werden, weil das Frageverhalten nicht unterstützt wird:", "addon.mod_quiz.errordownloading": "Fehler beim Laden notwendiger Daten", "addon.mod_quiz.errorgetattempt": "Fehler beim Laden der Versuche", "addon.mod_quiz.errorgetquestions": "Fehler beim Laden der Fragen", @@ -528,6 +636,7 @@ "addon.mod_quiz.grademethod": "Bewertungsmethode", "addon.mod_quiz.gradesofar": "{{$a.method}}: {{$a.mygrade}} / {{$a.quizgrade}}.", "addon.mod_quiz.marks": "Punkte", + "addon.mod_quiz.modulenameplural": "Tests", "addon.mod_quiz.mustbesubmittedby": "Dieser Versuch muss abgegeben werden vor {{$a}}.", "addon.mod_quiz.noquestions": "Es wurden noch keine Fragen eingetragen.", "addon.mod_quiz.noreviewattempt": "Sie dürfen diesen Versuch nicht prüfen.", @@ -572,6 +681,7 @@ "addon.mod_quiz.yourfinalgradeis": "Ihre Gesamtbewertung für diesen Test: {{$a}}", "addon.mod_resource.errorwhileloadingthecontent": "Fehler beim Laden des Inhalts", "addon.mod_resource.modifieddate": "Geändert {{$a}}", + "addon.mod_resource.modulenameplural": "Dateien", "addon.mod_resource.openthefile": "Datei öffnen", "addon.mod_resource.uploadeddate": "hochgeladen {{$a}}", "addon.mod_scorm.asset": "Anlage", @@ -608,6 +718,7 @@ "addon.mod_scorm.incomplete": "Unvollständig", "addon.mod_scorm.lastattempt": "Letzter vollständiger Versuch", "addon.mod_scorm.mode": "Einstellung", + "addon.mod_scorm.modulenameplural": "Lernpakete", "addon.mod_scorm.newattempt": "Neuen Versuch beginnen", "addon.mod_scorm.noattemptsallowed": "Zahl zulässiger Versuche", "addon.mod_scorm.noattemptsmade": "Zahl Ihrer Versuche", @@ -627,10 +738,12 @@ "addon.mod_survey.errorgetsurvey": "Fehler beim Laden der Umfragedaten", "addon.mod_survey.ifoundthat": "Dies findet statt:", "addon.mod_survey.ipreferthat": "Ich bevorzuge es so:", + "addon.mod_survey.modulenameplural": "Umfragen", "addon.mod_survey.responses": "Antworten", "addon.mod_survey.results": "Ergebnisse", "addon.mod_survey.surveycompletednograph": "Sie haben diese Umfrage abgeschlossen.", "addon.mod_url.accessurl": "URL öffnen", + "addon.mod_url.modulenameplural": "Links/URLs", "addon.mod_url.pointingtourl": "URL, auf die das Material zeigt", "addon.mod_wiki.cannoteditpage": "Sie können diese Seite nicht bearbeiten.", "addon.mod_wiki.createpage": "Seite erstellen", @@ -639,6 +752,7 @@ "addon.mod_wiki.errornowikiavailable": "Dieses Wiki hat noch keinen Inhalt.", "addon.mod_wiki.gowikihome": "Zur Wiki-Startseite", "addon.mod_wiki.map": "Spezialseiten", + "addon.mod_wiki.modulenameplural": "Wikis", "addon.mod_wiki.newpagehdr": "Neue Seite", "addon.mod_wiki.newpagetitle": "Titel für neue Seite\n", "addon.mod_wiki.nocontent": "Kein Inhalt auf dieser Seite", @@ -646,7 +760,7 @@ "addon.mod_wiki.pageexists": "Diese Seite existiert bereits.", "addon.mod_wiki.pagename": "Seitenname", "addon.mod_wiki.subwiki": "Teilwiki", - "addon.mod_wiki.titleshouldnotbeempty": "Der Titel kann nicht leer sein.", + "addon.mod_wiki.titleshouldnotbeempty": "Der Titel darf nicht leer sein.", "addon.mod_wiki.viewpage": "Seite anzeigen", "addon.mod_wiki.wikipage": "Wiki-Seite", "addon.mod_wiki.wrongversionlock": "Jemand anderes hat die Seite aktualisiert, während Sie daran gearbeitet haben. Deswegen ist der von Ihnen verwendete Seiteninhalt veraltet.", @@ -677,6 +791,7 @@ "addon.mod_workshop.gradinggradecalculated": "Berechnete Beurteilungspunkte", "addon.mod_workshop.gradinggradeof": "Beurteilungspunkte (von {{$a}})", "addon.mod_workshop.gradinggradeover": "Beurteilungspunkte überschreiben", + "addon.mod_workshop.modulenameplural": "Gegenseitige Beurteilungen", "addon.mod_workshop.nogradeyet": "Bisher keine Punkte", "addon.mod_workshop.notassessed": "Bisher nicht beurteilt", "addon.mod_workshop.notoverridden": "Nicht überschrieben", @@ -693,6 +808,7 @@ "addon.mod_workshop.submissiongrade": "Punkte für Einreichung", "addon.mod_workshop.submissiongradeof": "Punkte für Einreichungen (of {{$a}})", "addon.mod_workshop.submissionrequiredcontent": "Text eingeben oder Datei hinzufügen", + "addon.mod_workshop.submissionrequiredtitle": "Sie müssen einen Titel eingeben.", "addon.mod_workshop.submissionsreport": "Abgabeübersicht für gegenseitige Beurteilung", "addon.mod_workshop.submissiontitle": "Titel", "addon.mod_workshop.switchphase10": "In Vorbereitungsphase wechseln", @@ -1041,6 +1157,8 @@ "core.accounts": "Nutzerkonten", "core.add": "Hinzufügen", "core.agelocationverification": "Überprüfung von Alter und Aufenthaltsort", + "core.ago": "{{$a}} alt", + "core.all": "Alle", "core.allparticipants": "Alle Teilnehmer/innen", "core.android": "Android", "core.answer": "Antwort", @@ -1078,7 +1196,7 @@ "core.confirmdeletefile": "Möchten Sie diese Datei wirklich löschen?", "core.confirmloss": "Möchten Sie wirklich alle Änderungen verlieren?", "core.confirmopeninbrowser": "Möchten Sie dies im Webbrowser öffnen?", - "core.considereddigitalminor": "Sie benötigen eine Bestätigigung Ihrer Eltern zur Registrierung", + "core.considereddigitalminor": "Sie sind zu jung, um ein Nutzerkonto für diese Website zu erstellen.", "core.content": "Inhalt", "core.contenteditingsynced": "Der Inhalt, den Sie gerade bearbeiten, wurde synchronisiert.", "core.contentlinks.chooseaccount": "Nutzerkonto wählen", @@ -1108,17 +1226,21 @@ "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.manualcompletionnotsynced": "Manueller Abschluss wurde nicht synchronisiert", "core.course.nocontentavailable": "Momentan sind keine Inhalte verfügbar", "core.course.overriddennotice": "Die endgültige Bewertung zu dieser Aktivität wurde manuell bearbeitet.", + "core.course.refreshcourse": "Kurs neu laden", "core.course.sections": "Abschnitte", "core.course.useactivityonbrowser": "Sie können dies im Webbrowser verwenden.", + "core.course.warningmanualcompletionmodified": "Manueller Abschluss einer Aktivität wurde auf der Website geändert.", + "core.course.warningofflinemanualcompletiondeleted": "Manueller Offline-Abschluss des Kurses '{{name}}' wurde gelöscht. {{error}}", "core.coursedetails": "Kursdetails", + "core.courses.addtofavourites": "Diesen Kurs markieren", "core.courses.allowguests": "Dieser Kurs erlaubt einen Gastzugang.", "core.courses.availablecourses": "Kursliste", "core.courses.cannotretrievemorecategories": "Kursbereiche tiefer als Level {{$a}} können nicht abgerufen werden.", "core.courses.categories": "Kursbereiche", "core.courses.confirmselfenrol": "Möchten Sie sich selbst in diesen Kurs einschreiben?", - "core.courses.courseoverview": "Kursbeschreibung", "core.courses.courses": "Kurse", "core.courses.downloadcourses": "Kurse herunterladen", "core.courses.enrolme": "Selbst einschreiben", @@ -1128,33 +1250,24 @@ "core.courses.errorselfenrol": "Fehler bei der Selbsteinschreibung", "core.courses.filtermycourses": "Meine Kurse filtern", "core.courses.frontpage": "Startseite", - "core.courses.future": "Künftig", - "core.courses.inprogress": "Laufend", - "core.courses.morecourses": "Weitere Kurse", + "core.courses.hidecourse": "In der Ansicht verbergen", "core.courses.mycourses": "Meine Kurse", - "core.courses.next30days": "Nächste 30 Tage", - "core.courses.next7days": "Nächste 7 Tage", + "core.courses.mymoodle": "Dashboard", "core.courses.nocourses": "Keine Kursinformationen", - "core.courses.nocoursesfuture": "Keine künftigen Kurse", - "core.courses.nocoursesinprogress": "Keine laufenden Kurse", - "core.courses.nocoursesoverview": "Keine Kurse", - "core.courses.nocoursespast": "Keine vergangenen Kurse", "core.courses.nocoursesyet": "Keine Kurse in diesem Kursbereich", - "core.courses.noevents": "Keine bevorstehenden Aktivitäten fällig", "core.courses.nosearchresults": "Keine Ergebnisse", "core.courses.notenroled": "Sie sind nicht in diesen Kurs eingeschrieben", "core.courses.notenrollable": "Sie können sich nicht selbst in diesen Kurs einschreiben.", "core.courses.password": "Einschreibeschlüssel", - "core.courses.past": "Vergangen", "core.courses.paymentrequired": "Dieser Kurs ist entgeltpflichtig. Bitte bezahlen Sie das Teilnahmeentgelt, um in den Kurs eingeschrieben zu werden.", "core.courses.paypalaccepted": "PayPal-Zahlungen möglich", + "core.courses.removefromfavourites": "Kursmarkierung aufheben", "core.courses.search": "Suchen", "core.courses.searchcourses": "Kurse suchen", "core.courses.searchcoursesadvice": "Sie können Kurse suchen, um als Gast teilzunehmen oder sich selbst einzuschreiben, falls dies erlaubt ist.", "core.courses.selfenrolment": "Selbsteinschreibung", "core.courses.sendpaymentbutton": "Zahlung über PayPal", - "core.courses.sortbycourses": "Sortiert nach Kursen", - "core.courses.sortbydates": "Sortiert nach Daten", + "core.courses.show": "Diesen Kurs anzeigen", "core.courses.totalcoursesearchresults": "Alle Kurse: {{$a}}", "core.currentdevice": "Aktuelles Gerät", "core.datastoredoffline": "Die Daten würden auf dem mobilen Gerät gespeichert, weil sie nicht gesendet werden konnten. Sie werden automatisch später gesendet.", @@ -1187,10 +1300,11 @@ "core.errordeletefile": "Fehler beim Löschen der Datei. Versuchen Sie es noch einmal.", "core.errordownloading": "Fehler beim Laden der Datei", "core.errordownloadingsomefiles": "Fehler beim Herunterladen der Dateien. Einige Dateien könnten fehlen.", - "core.errorfileexistssamename": "Eine Datei mit diesem Namen existiert bereits.", + "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.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.", "core.erroropenfilenoextension": "Fehler beim Öffnen: Die Datei hat keine Extension.", "core.erroropenpopup": "Die Aktivität versucht, ein Popup zu öffnen. Popups werden in der App aber nicht unterstützt.", @@ -1198,6 +1312,7 @@ "core.errorsync": "Fehler beim Synchronisieren. Versuchen Sie es noch einmal.", "core.errorsyncblocked": "{{$a}} kann im Moment wegen eines laufenden Vorgangs nicht synchronisiert werden. Versuchen Sie es später noch einmal. Falls das Problem weiterhin besteht, starten Sie die App neu.", "core.explanationdigitalminor": "Diese Informationen sind notwendig um festzustellen, ob Sie sich selber registrieren dürfen. Nur wenn Sie alt genug sind, können Sie selber den Richtlinien und Nutzungsbedingungen zustimmen. Andernfalls müssen dies Ihre Erziehungsberechtigten tun.", + "core.favourites": "Markiert", "core.filename": "Dateiname", "core.filenameexist": "Der Dateiname existiert bereits: {{$a}}", "core.fileuploader.addfiletext": "Datei hinzufügen", @@ -1221,7 +1336,8 @@ "core.fileuploader.more": "Einzelansicht", "core.fileuploader.photoalbums": "Fotoalbum", "core.fileuploader.readingfile": "Datei lesen", - "core.fileuploader.selectafile": "Wählen Sie eine Datei.", + "core.fileuploader.readingfileperc": "Datei lesen: {{$a}}%", + "core.fileuploader.selectafile": "Datei wählen", "core.fileuploader.uploadafile": "Datei hochladen", "core.fileuploader.uploading": "Hochladen", "core.fileuploader.uploadingperc": "Hochladen: {{$a}}%", @@ -1276,20 +1392,23 @@ "core.login.connecttomoodle": "Zu Moodle verbinden", "core.login.contactyouradministrator": "Wenden Sie sich an den Administrator, um weitere Hilfe zu bekommen.", "core.login.contactyouradministratorissue": "Wenden Sie sich an den Administrator, um folgendes Problem prüfen zu lassen: {{$a}}", - "core.login.createaccount": "Mein neues Konto anlegen", + "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": "

In Kürze wird eine E-Mail an {{$a}} gesendet.

Sie finden eine einfache Anleitung, wie Sie die Registrierung abschließen. Bei Schwierigkeiten wenden Sie sich an den Administrator 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. 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.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 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 Informationen: https://docs.moodle.org/dev/Moodle_Mobile_development_using_Chrome_or_Chromium", + "core.login.erroraccesscontrolalloworigin": "Der Cross-Origin-Aufruf wurde zurückgewiesen. Weitere Informationen: 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", + "core.login.findyoursite": "Website suchen", "core.login.firsttime": "Sind Sie zum ersten Mal auf dieser Webseite?", "core.login.forgotten": "Anmeldename oder Kennwort vergessen?", "core.login.getanothercaptcha": "Neues Captcha laden", "core.login.help": "Hilfe", - "core.login.helpmelogin": "

Es gibt viele tausend Moodle-Websites auf der Welt. Die mobile App kann sich nur mit einem Moodle verbinden, für das der mobile Zugriff freigegeben ist.

\n

Alle Informationen finden Sie unter http://docs.moodle.org/de/Mobile_app.

\n


Um die App mit einer Demo-Website zu testen, tragen Sie bitte teacher oder student in das Feld URL der Website ein. Tippen Sie dann auf die Taste Verbinden.

", + "core.login.helpmelogin": "

Auf der Welt gibt es sehr viele Websites mit Moodle. Diese App kann aber nur mit den 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/Mobiles_Moodle

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", "core.login.invalidaccount": "Prüfen Sie Ihre Anmeldedaten oder wenden Sie sich an den Administrator der Website.", "core.login.invaliddate": "Ungültiges Datum", @@ -1300,6 +1419,9 @@ "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 bitte die Moodle Classic App.", + "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 zusammenarbeitet.

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, indem Sie eine ältere App-Version verwenden, die Sie hier herunterladen können.", "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", @@ -1310,6 +1432,7 @@ "core.login.missingfirstname": "Vorname fehlt", "core.login.missinglastname": "Nachname fehlt", "core.login.mobileservicesnotenabled": "Der mobile Zugriff ist für dieses Moodle nicht aktiviert. Wenden Sie sich an den Administrator, wenn Sie den mobilen Zugriff aktiviert haben möchten.", + "core.login.mustconfirm": "Sie müssen Ihren Zugang bestätigen", "core.login.newaccount": "Neues Nutzerkonto", "core.login.newsitedescription": "Geben Sie die URL zu Ihrer Website ein.", "core.login.notloggedin": "Sie müssen angemeldet sein.", @@ -1325,36 +1448,38 @@ "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 Image", + "core.login.recaptchachallengeimage": "reCaptcha Challenge Image", + "core.login.recaptchaexpired": "Überprüfung notwendig. 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.", "core.login.reconnectssodescription": "Die Authentifizierung ist abgelaufen oder ungültig. Sie müssen sich im Webbrowser neu anmelden.", + "core.login.resendemail": "E-Mail erneut senden", "core.login.searchby": "Suche nach:", "core.login.security_question": "Sicherheitsfrage", "core.login.selectacountry": "Land auswählen", "core.login.selectsite": "Wählen Sie Ihre Website", "core.login.signupplugindisabled": "{{$a}} ist nicht aktiviert.", "core.login.siteaddress": "URL der Website", + "core.login.sitehasredirect": "Ihre Website enthält mindestens eine HTTP-Weiterleitung. Die App kann Weiterleitungen verarbeiten, weswegen der Verbindungaufbau zur Website fehlschlagen könnte..", "core.login.siteinmaintenance": "Die Website ist im Wartungsmodus.", "core.login.sitepolicynotagreederror": "Zustimmungserklärung nicht bestätigt", "core.login.siteurl": "URL der Website", - "core.login.siteurlrequired": "Die URL der Website ist notwendig, z.B. https://www.meinewebsite.de", + "core.login.siteurlrequired": "Die URL der Website ist notwendig, z.B. https://www.meinmoode.de", "core.login.startsignup": "Neues Konto anlegen?", - "core.login.stillcantconnect": "Können Sie immer noch nicht verbinden?", + "core.login.stillcantconnect": "Können Sie sich immer noch nicht verbinden?", "core.login.supplyinfo": "Weitere Details", "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.visitchangepassword": "Möchten Sie die Website aufrufen, um das Kennwort zu ändern?", - "core.login.webservicesnotenabled": "Die Webservices sind für die Website nicht aktiviert. Wenden Sie sich an den Administrator, wenn Sie den mobilen Zugriff aktiviert haben möchten.", + "core.login.webservicesnotenabled": "Die Webservices sind für die Website deaktiviert. Wenden Sie sich an den Administrator, wenn Sie den mobilen Zugriff aktiviert haben möchten.", "core.lostconnection": "Die Authentifizierung ist abgelaufen oder ungültig. Sie müssen sich neu anmelden.", "core.mainmenu.appsettings": "Einstellungen", "core.mainmenu.changesite": "Website wechseln", "core.mainmenu.help": "Hilfe", "core.mainmenu.logout": "Logout", - "core.mainmenu.mycourses": "Meine Kurse", - "core.mainmenu.togglemenu": "Menü umschalten", "core.mainmenu.website": "Website im Browser", "core.maxsizeandattachments": "Maximale Größe für neue Dateien: {{$a.size}}, Maximale Anzahl von Anhängen: {{$a.attachments}}", "core.min": "Minute", @@ -1389,6 +1514,7 @@ "core.more": "mehr", "core.mygroups": "Meine Gruppen", "core.name": "Name", + "core.networkerroriframemsg": "Dieser Inhalt ist offline nicht verfügbar. Sie müssen online sein und es dann noch einmal versuchen.", "core.networkerrormsg": "Problem mit der Verbindung. Prüfen Sie die Verbindung und versuchen Sie es noch einmal.", "core.never": "Nie", "core.next": "Weiter", @@ -1397,6 +1523,7 @@ "core.nograde": "Keine Bewertung", "core.none": "Keine", "core.nopasswordchangeforced": "Sie können nicht weitermachen, ohne das Kennwort zu ändern.", + "core.nopermissionerror": "Sie haben aktuell keine Rechte, um dies zu tun.", "core.nopermissions": "Sie besitzen derzeit keine Rechte, dies zu tun ({{$a}}).", "core.noresults": "Keine Ergebnisse", "core.notapplicable": "n/a", @@ -1421,6 +1548,7 @@ "core.pulltorefresh": "Zum Aktualisieren runterziehen", "core.question.answer": "Antwort", "core.question.answersaved": "Antwort gespeichert", + "core.question.cannotdeterminestatus": "Status kann nicht festgestellt werden", "core.question.certainty": "Gewissheit", "core.question.complete": "Vollständig", "core.question.correct": "Richtig", @@ -1441,8 +1569,10 @@ "core.quotausage": "Aktuell sind {{$a.used}} vom möglichen Speicher {{$a.total}} belegt.", "core.redirectingtosite": "Sie werden zur Website weitergeleitet.", "core.refresh": "Aktualisieren", + "core.remove": "Löschen", "core.required": "Erforderlich", "core.requireduserdatamissing": "Im Nutzerprofil fehlen notwendige Einträge. Füllen Sie die Daten in der Website aus und versuchen Sie es noch einmal.
{{$a}}", + "core.resources": "Arbeitsmaterial", "core.restore": "Wiederherstellen", "core.retry": "Neu versuchen", "core.save": "Sichern", @@ -1459,13 +1589,15 @@ "core.settings.appready": "App nutzbar", "core.settings.cannotsyncoffline": "Offline kann nicht synchronisiert werden.", "core.settings.cannotsyncwithoutwifi": "Die Daten wurden nicht synchronisiert, weil die Einstellungen dies nur mit WLAN erlauben. StelleN Sie eine WLAN-Verbindung her.", + "core.settings.compilationinfo": "Compilation Info", "core.settings.cordovadevicemodel": "Cordova Device Model", "core.settings.cordovadeviceosversion": "Cordova Device OS Version", "core.settings.cordovadeviceplatform": "Cordova Device Platform", "core.settings.cordovadeviceuuid": "Cordova Device UUID", "core.settings.cordovaversion": "Cordova Version", - "core.settings.currentlanguage": "Eingestellte Sprache", + "core.settings.currentlanguage": "Aktuelle Sprache", "core.settings.debugdisplay": "Debug-Mitteilungen anzeigen", + "core.settings.debugdisplaydescription": "Wenn diese Option aktiviert ist, werden zusätzliche Fehlerdaten angezeigt.", "core.settings.deletesitefiles": "Möchten Sie wirklich alle von der Website '{{sitename}}' geladenen Dateien löschen?", "core.settings.deletesitefilestitle": "Dateien löschen", "core.settings.deviceinfo": "Geräteinformationen", @@ -1476,10 +1608,10 @@ "core.settings.displayformat": "Bildschirmformat", "core.settings.enabledownloadsection": "Abschnitte herunterladen", "core.settings.enablerichtexteditor": "Texteditor aktivieren", - "core.settings.enablerichtexteditordescription": "Der Texteditor wird überall dort verwendet, wo er für die Textbearbeitung sinnvoll ist.", + "core.settings.enablerichtexteditordescription": "Der Texteditor wird verfügbar, wenn Texte eingegeben werden.", "core.settings.enablesyncwifi": "Nur mit WLAN synchronierieren", "core.settings.errordeletesitefiles": "Fehler beim Löschen der Dateien", - "core.settings.errorsyncsite": "Fehler beim Synchronisieren der Daten. Prüfen Sie die Internetverbindung und versuchen Sie es noch einmal.", + "core.settings.errorsyncsite": "Fehler beim Synchronisieren der Daten. Sie müssen online sein und es dann noch einmal versuchen.", "core.settings.estimatedfreespace": "Verfügbarer Speicherplatz", "core.settings.filesystemroot": "Dateisystembasis", "core.settings.general": "Allgemeines", @@ -1496,6 +1628,7 @@ "core.settings.privacypolicy": "Datenschutzerklärung", "core.settings.reportinbackground": "Fehler automatisch senden", "core.settings.settings": "Einstellungen", + "core.settings.showdownloadoptions": "Optionen zum Herunterladen anzeigen", "core.settings.sites": "Websites", "core.settings.spaceusage": "Speicherplatz", "core.settings.synchronization": "Synchronisieren", @@ -1506,14 +1639,14 @@ "core.settings.versionname": "Versionsname", "core.settings.wificonnection": "WLAN-Verbindung", "core.sharedfiles.chooseaccountstorefile": "Wählen Sie ein Nutzerkonto, um die Datei dort zu speichern.", - "core.sharedfiles.chooseactionrepeatedfile": "Eine Datei mit diesem Namen existiert bereits. Möchten Sie die vorhandene Datei ersetzen oder umbenennen in '{{$a}}'?", - "core.sharedfiles.errorreceivefilenosites": "Keine Websites gespeichert. Fügen Sie zuerst eine Website hinzu, bevor Sie eine Datei über die App teilen.", + "core.sharedfiles.chooseactionrepeatedfile": "Eine Datei mit gleichem Namen existiert bereits. Möchten Sie die vorhandene Datei ersetzen oder umbenennen in '{{$a}}'?", + "core.sharedfiles.errorreceivefilenosites": "Keine gespeicherten Websites. Fügen Sie zuerst eine Website hinzu, bevor Sie eine Datei über die App teilen.", "core.sharedfiles.nosharedfiles": "Keine geteilten Dateien auf dieser Website", "core.sharedfiles.nosharedfilestoupload": "Sie haben hier noch keine Datei zum Hochladen. Wenn Sie eine Datei aus einer anderen App hochladen möchten, suchen Sie diese Datei und tippen Sie auf die Taste 'Öffnen in'.", "core.sharedfiles.rename": "Umbenennen", "core.sharedfiles.replace": "Ersetzen", "core.sharedfiles.sharedfiles": "Geteilte Dateien", - "core.sharedfiles.successstorefile": "Die Datei wurde gespeichert. Sie können die Datei in den Bereich 'Meine Dateien' hochladen oder bei Aktivitäten verwenden.", + "core.sharedfiles.successstorefile": "Die Datei wurde gespeichert. Sie können die Datei in den Bereich 'Meine Dateien' hochladen oder in einer Aktivität verwenden.", "core.show": "Anzeigen", "core.showmore": "Mehr anzeigen...", "core.site": "Website", @@ -1528,6 +1661,21 @@ "core.sorry": "Sorry ...", "core.sortby": "Sortiert nach", "core.start": "Start", + "core.strftimedate": "%d. %B %Y", + "core.strftimedatefullshort": "%d.%m.%y", + "core.strftimedateshort": "%d. %B", + "core.strftimedatetime": "%d. %B %Y, %H:%M", + "core.strftimedatetimeshort": "%d.%m.%Y %H:%M", + "core.strftimedaydate": "%A, %d. %B %Y", + "core.strftimedaydatetime": "%A, %d. %B %Y, %H:%M", + "core.strftimedayshort": "%A, %d. %B", + "core.strftimedaytime": "%a, %H:%M", + "core.strftimemonthyear": "%B %Y", + "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.submit": "Speichern", "core.success": "erfolgreich", "core.tablet": "Tablet", @@ -1540,8 +1688,8 @@ "core.tryagain": "Versuchen Sie es noch einmal.", "core.twoparagraphs": "{{p1}}

{{p2}}", "core.uhoh": "Uh oh!", - "core.unexpectederror": "Unerwarteter Fehler! Starten Sie die App neu und versuchen Sie es noch einmal.", - "core.unicodenotsupported": "Manche Emojis werden von dieser Website nicht unterstützt. Die betreffenden Zeichen werden beim Senden der Mitteilung gelöscht.", + "core.unexpectederror": "Unerwarteter Fehler! Sie müssen die App neu starten und es dann noch einmal versuchen.", + "core.unicodenotsupported": "Manche Emojis können auf dieser Website nicht verwendet werden. Die betreffenden Zeichen werden beim Senden der Mitteilung gelöscht.", "core.unicodenotsupportedcleanerror": "Der Text ist leer, nachdem die Unicode-Zeichen gelöscht wurden.", "core.unknown": "Unbekannt", "core.unlimited": "Unbegrenzt", @@ -1557,6 +1705,7 @@ "core.user.editingteacher": "Trainer/in", "core.user.email": "E-Mail-Adresse", "core.user.emailagain": "E-Mail-Adresse (wiederholen)", + "core.user.errorloaduser": "Fehler beim Laden der Nutzerdetails", "core.user.firstname": "Vorname", "core.user.interests": "Persönliche Interessen", "core.user.lastname": "Nachname", @@ -1576,12 +1725,15 @@ "core.usernotfullysetup": "Nutzerkonto unvollständig", "core.users": "Nutzer/innen", "core.view": "Anzeigen", + "core.viewcode": "Code zeigen", + "core.vieweditor": "Editor zeigen", + "core.viewembeddedcontent": "Eingebettete Inhalte anzeigen", "core.viewprofile": "Profil anzeigen", "core.warningofflinedatadeleted": "Die Offline-Daten von {{component}} '{{name}}' wurden gelöscht. {{error}}", "core.whatisyourage": "Wie alt sind Sie?", "core.wheredoyoulive": "In welchem Land leben Sie?", "core.whoops": "Uuups!", - "core.whyisthishappening": "Warum passiert dies?", + "core.whyisthishappening": "Warum passiert das?", "core.whyisthisrequired": "Warum ist dies notwendig?", "core.windowsphone": "Windows Phone", "core.wsfunctionnotavailable": "Die Webservice-Funktion ist nicht verfügbar.", diff --git a/src/assets/lang/el.json b/src/assets/lang/el.json index f1bc9f175..b18d7b006 100644 --- a/src/assets/lang/el.json +++ b/src/assets/lang/el.json @@ -1,15 +1,61 @@ { + "addon.badges.alignment": "Προσόν", "addon.badges.badgedetails": "Λεπτομέρειες διακριτικoύ", "addon.badges.badges": "Διακριτικά", + "addon.badges.bendorsement": "Έγκριση", + "addon.badges.claimcomment": "Σχόλιο έγκρισης", + "addon.badges.claimid": "Διεύθυνση ιστού παραλαβής", "addon.badges.contact": "Επικοινωνήστε", "addon.badges.dateawarded": "Ημερομηνία έκδοσης", "addon.badges.expired": "Έληξε", "addon.badges.expirydate": "Ημερομηνία λήξης", + "addon.badges.imageauthoremail": "Διεύθυνση ηλεκτρονικού ταχυδρομείου του δημιουργού της εικόνας", + "addon.badges.imageauthorname": "Όνομα του δημιουργού της εικόνας", + "addon.badges.imageauthorurl": "Η διεύθυνση ιστού του δημιουργού της εικόνας", + "addon.badges.imagecaption": "Λεζάντα εικόνας", "addon.badges.issuancedetails": "Λήξη διακριτικoύ", "addon.badges.issuerdetails": "Στοιχεία εκδότη", + "addon.badges.issueremail": "Διεύθυνση ηλεκτρονικού ταχυδρομείου", "addon.badges.issuername": "Όνομα εκδότη", + "addon.badges.issuerurl": "Διεύθυνση URL εκδότη", + "addon.badges.language": "Γλώσσα", + "addon.badges.noalignment": "Αυτό το διακριτικό δεν έχει καθόλου καθορισμένα προσόντα.", "addon.badges.nobadges": "Δεν υπάρχουν διαθέσιμα διακριτικά", + "addon.badges.norelated": "Αυτό το διακριτικό δεν έχει κανένα σχετικό διακριτικό.", "addon.badges.recipientdetails": "Στοιχεία αποδέκτη", + "addon.badges.relatedbages": "Σχετικά διακριτικά", + "addon.badges.version": "Έκδοση", + "addon.badges.warnexpired": "(Το διακριτικό αυτό έληξε!)", + "addon.block_activitymodules.pluginname": "Δραστηριότητες", + "addon.block_myoverview.all": "Όλα", + "addon.block_myoverview.favourites": "Με αστερίσκο", + "addon.block_myoverview.future": "Μελλοντικά", + "addon.block_myoverview.hiddencourses": "Κρυμμένα", + "addon.block_myoverview.inprogress": "Σε εξέλιξη", + "addon.block_myoverview.lastaccessed": "Τελευταία πρόσβαση", + "addon.block_myoverview.morecourses": "Περισσότερα μαθήματα", + "addon.block_myoverview.nocourses": "Κανένα μάθημα", + "addon.block_myoverview.past": "Προηγούμενα", + "addon.block_myoverview.pluginname": "Επισκόπηση μαθημάτων", + "addon.block_myoverview.title": "Όνομα μαθήματος", + "addon.block_recentlyaccessedcourses.nocourses": "Κανένα πρόσφατο μάθημα", + "addon.block_recentlyaccessedcourses.pluginname": "Μαθήματα με πρόσφατη πρόσβαση", + "addon.block_recentlyaccesseditems.noitems": "Καθόλου πρόσφατα στοιχεία", + "addon.block_recentlyaccesseditems.pluginname": "Στοιχεία με πρόσφατη πρόσβαση", + "addon.block_sitemainmenu.pluginname": "Κύριο μενού", + "addon.block_starredcourses.nocourses": "Δεν υπάρχουν μαθήματα με αστερίσκο", + "addon.block_starredcourses.pluginname": "Μαθήματα με αστερίσκο", + "addon.block_timeline.duedate": "Καταληκτική ημερομηνία", + "addon.block_timeline.next30days": "Επόμενες 30 ημέρες", + "addon.block_timeline.next3months": "Επόμενοι 3 μήνες", + "addon.block_timeline.next6months": "Επόμενοι 6 μήνες", + "addon.block_timeline.next7days": "Επόμενες 7 ημέρες", + "addon.block_timeline.nocoursesinprogress": "Καθόλου μαθήματα σε εξέλιξη", + "addon.block_timeline.noevents": "Δεν οφείλονται καθόλου επερχόμενες δραστηριότητες", + "addon.block_timeline.overdue": "Εκπρόθεσμα", + "addon.block_timeline.pluginname": "Χρονολόγιο", + "addon.block_timeline.sortbycourses": "Ταξινόμηση κατά μαθήματα", + "addon.block_timeline.sortbydates": "Ταξινόμηση κατά ημερομηνίες", "addon.calendar.calendar": "Ημερολόγιο", "addon.calendar.calendarevents": "Ημερολόγιο συμβάντων", "addon.calendar.defaultnotificationtime": "Προεπιλεγμένος χρόνος ειδοποίησης", @@ -77,17 +123,17 @@ "addon.coursecompletion.coursecompletion": "Ολοκλήρωση μαθήματος", "addon.coursecompletion.criteria": "Κριτήρια", "addon.coursecompletion.criteriagroup": "Ομάδα κριτηρίων", - "addon.coursecompletion.criteriarequiredall": "Όλα τα παρακάτω κριτήρια είναι υποχρεωτικά", - "addon.coursecompletion.criteriarequiredany": "Οποιοδήποτε από τα παρακάτω κριτήρια είναι υποχρεωτικά", + "addon.coursecompletion.criteriarequiredall": "Όλα τα παρακάτω κριτήρια είναι απαραίτητα", + "addon.coursecompletion.criteriarequiredany": "Τα παρακάτω κριτήρια είναι απαραίτητα", "addon.coursecompletion.inprogress": "Σε εξέλιξη", - "addon.coursecompletion.manualselfcompletion": "Μη αυτόματη ολοκλήρωση", + "addon.coursecompletion.manualselfcompletion": "Χειροκίνητη αυτo-ολοκλήρωση", "addon.coursecompletion.notyetstarted": "Δεν έχει ξεκινήσει ακόμα", - "addon.coursecompletion.pending": "Εκκρεμής", + "addon.coursecompletion.pending": "Σε εκκρεμότητα", "addon.coursecompletion.required": "Απαιτείται", "addon.coursecompletion.requiredcriteria": "Απαιτούμενα κριτήρια", "addon.coursecompletion.requirement": "Απαίτηση", "addon.coursecompletion.status": "Κατάσταση", - "addon.coursecompletion.viewcoursereport": "Δείτε την αναφορά μαθήματος", + "addon.coursecompletion.viewcoursereport": "Προβολή αναφοράς μαθήματος", "addon.files.couldnotloadfiles": "Η λίστα των αρχείων δεν φορτώθηκε.", "addon.files.emptyfilelist": "Δεν υπαρχουν αρχεία.", "addon.files.erroruploadnotworking": "Δυστυχώς, προσωρινά δεν είναι δυνατό να ανεβάσετε τα αρχεία στο site σας.", @@ -95,34 +141,75 @@ "addon.files.privatefiles": "Προσωπικά αρχεία", "addon.files.sitefiles": "Αρχεία ιστοτόπου", "addon.messageoutput_airnotifier.processorsettingsdesc": "Διαμόρφωση συσκευών", + "addon.messages.acceptandaddcontact": "Αποδοχή και προσθήκη στις επαφές", "addon.messages.addcontact": "Προσθήκη επαφής", - "addon.messages.blockcontact": "Φραγμός επαφής", - "addon.messages.blockcontactconfirm": "Θα σταματήσετε να λαμβάνετε μηνύματα από αυτήν την επαφή.", + "addon.messages.addcontactconfirm": "Είστε βέβαιοι ότι θέλετε να προσθέσετε τον/την {{$a}} στις επαφές σας;", + "addon.messages.addtofavourites": "Επισήμανση με αστερίσκο", + "addon.messages.addtoyourcontacts": "Προσθήκη στις επαφές", "addon.messages.blocknoncontacts": "Φραγή όλων των νέων μηνυμάτων από χρήστες που δεν βρίσκονται στη λίστα επαφών σας.", + "addon.messages.blockuser": "Αποκλεισμός χρήστη", + "addon.messages.blockuserconfirm": "Είστε βέβαιοι ότι θέλετε να αποκλείσετε τον/την {{$a}};", + "addon.messages.contactableprivacy": "Αποδοχή μηνυμάτων από:", + "addon.messages.contactableprivacy_coursemember": "Οι επαφές μου και οποιοσδήποτε από τα μαθήματά μου", + "addon.messages.contactableprivacy_onlycontacts": "Μόνο οι επαφές μου", + "addon.messages.contactableprivacy_site": "Οποιοσδήποτε στον ιστότοπο", + "addon.messages.contactblocked": "Η επαφή αποκλείστηκε", "addon.messages.contactlistempty": "Η λίστα επαφών είναι κενή", "addon.messages.contactname": "Όνομα επαφής", + "addon.messages.contactrequestsent": "Το αίτημα επαφής στάλθηκε", "addon.messages.contacts": "Επαφές", + "addon.messages.decline": "Απόρριψη", + "addon.messages.deleteallconfirm": "Είστε βέβαιοι ότι θέλετε να διαγράψετε όλη αυτή τη συνομιλία; Δεν θα διαγραφεί το αντίγραφο των άλλων συμμετεχόντων στη συζήτηση.", + "addon.messages.deleteconversation": "Διαγραφή συζήτησης", "addon.messages.errordeletemessage": "Σφάλμα κατά τη διαγραφή του μηνύματος.", "addon.messages.errorwhileretrievingcontacts": "Σφάλμα κατά την ανάκτηση των επαφών από το διακομιστή.", "addon.messages.errorwhileretrievingdiscussions": "Σφάλμα κατά την ανάκτηση των συζητήσεων από το διακομιστή.", "addon.messages.errorwhileretrievingmessages": "Σφάλμα κατά την ανάκτηση των μηνυμάτων από το διακομιστή.", + "addon.messages.groupconversations": "Ομάδα", + "addon.messages.groupinfo": "Στοιχεία ομάδας", + "addon.messages.individualconversations": "Ιδιωτικές", + "addon.messages.info": "Πληροφορίες", + "addon.messages.isnotinyourcontacts": "Ο/Η {{$a}} δεν βρίσκεται στις επαφές σας", "addon.messages.message": "Μήνυμα", "addon.messages.messagenotsent": "Το μήνυμα δεν στάλθηκε, δοκιμάστε ξανά αργότερα.", - "addon.messages.messagepreferences": "Προτιμήσεις μηνύματος", + "addon.messages.messagepreferences": "Προτιμήσεις μηνυμάτων", "addon.messages.messages": "Μηνύματα", "addon.messages.newmessage": "Νέο μήνυμα", "addon.messages.newmessages": "Νέα μηνύματα", - "addon.messages.nomessages": "Δεν υπάρχουν μηνύματα", + "addon.messages.nocontactrequests": "Καθόλου αιτήματα επαφών", + "addon.messages.nocontactsgetstarted": "Καθόλου επαφές", + "addon.messages.nofavourites": "Δεν υπάρχουν συνομιλίες με αστερίσκο", + "addon.messages.nogroupconversations": "Καθόλου ομαδικές συζητήσεις", + "addon.messages.noindividualconversations": "Καθόλου ιδιωτικές συζητήσεις", + "addon.messages.nomessagesfound": "Δε βρέθηκαν μηνύματα", + "addon.messages.noncontacts": "Μη επαφές", "addon.messages.nousersfound": "Δεν βρέθηκαν χρήστες", + "addon.messages.numparticipants": "{{$a}} συμμετέχοντες", "addon.messages.removecontact": "Αφαίρεση επαφής", - "addon.messages.removecontactconfirm": "Η επαφή θα καταργηθεί από τη λίστα επαφών σας.", + "addon.messages.removecontactconfirm": "Είστε βέβαιοι ότι θέλετε να καταργήσετε τον/την {{$a}} από τις επαφές σας;", + "addon.messages.removefromfavourites": "Αφαίρεση αστερίσκου", + "addon.messages.removefromyourcontacts": "Αφαίρεση από τις επαφές", + "addon.messages.requests": "Αιτήματα", + "addon.messages.requirecontacttomessage": "Θα πρέπει να ζητήσετε από τον/την {{$a}} να σας προσθέσει ως επαφή για να μπορέσετε να στείλετε μήνυμα.", + "addon.messages.searchcombined": "Αναζήτηση επαφών και μηνυμάτων", + "addon.messages.searchnocontactsfound": "Δεν βρέθηκαν επαφές", + "addon.messages.searchnomessagesfound": "Δεν βρέθηκαν μηνύματα", + "addon.messages.searchnononcontactsfound": "Δεν βρέθηκαν μη επαφές", + "addon.messages.sendcontactrequest": "Αποστολή αιτήματος δημιουργίας επαφής", "addon.messages.type_blocked": "Μπλοκαρισμένοι", "addon.messages.type_offline": "Εκτός σύνδεσης", "addon.messages.type_online": "Συνδεδεμένος", "addon.messages.type_search": "Αποτελέσματα αναζήτησης", "addon.messages.type_strangers": "Άλλοι", - "addon.messages.unblockcontact": "Ξεμπλόκαρε την επαφή", + "addon.messages.unabletomessage": "Δεν μπορείτε να στείλετε μήνυμα σε αυτόν τον χρήστη", + "addon.messages.unblockuser": "Ξεκλείδωμα χρήστη", + "addon.messages.unblockuserconfirm": "Είστε βέβαιοι ότι θέλετε να ξεκλειδώσετε τον/την {{$a}};", + "addon.messages.userwouldliketocontactyou": "Ο/Η {{$a}} θα ήθελε να επικοινωνήσει μαζί σας", "addon.messages.warningmessagenotsent": "Το/τα μήνυμα/μηνύματα δεν μπόρεσε/αν να σταλεί/ούν στο χρήστη {{user}}. {{error}}", + "addon.messages.wouldliketocontactyou": "Θα ήθελαν να επικοινωνήσουν μαζί σας", + "addon.messages.you": "Εσείς:", + "addon.messages.youhaveblockeduser": "Έχετε αποκλείσει αυτόν τον χρήστη στο παρελθόν", + "addon.messages.yourcontactrequestpending": "Η αίτηση επαφής σας επικοινωνίας εκκρεμεί για τον/την {{$a}}", "addon.mod_assign.acceptsubmissionstatement": "Παρακαλώ δεχθείτε τη δήλωση υποβολής.", "addon.mod_assign.addattempt": "Επέτρεψε και άλλη προσπάθεια", "addon.mod_assign.addnewattempt": "Πρόσθεσε μία νέα προσπάθεια", @@ -159,30 +246,42 @@ "addon.mod_assign.grade": "Βαθμός", "addon.mod_assign.graded": "Βαθμολογήθηκε", "addon.mod_assign.gradedby": "Βαθμολογήθηκε από", + "addon.mod_assign.gradedfollowupsubmit": "Βαθμολογήθηκε - παραλήφθηκε η υποβολή επακόλουθης δραστηριότητας", "addon.mod_assign.gradedon": "Βαθμολογήθηκε στις", + "addon.mod_assign.gradelocked": "Αυτός ο βαθμός είναι κλειδωμένος ή έχει παρακαμφθεί στο βαθμολόγιο.", "addon.mod_assign.gradenotsynced": "Η βαθμολογία δεν συγχρονίστηκε", - "addon.mod_assign.gradingstatus": "Κατάσταση Βαθμολόγησης", + "addon.mod_assign.gradeoutof": "Βαθμός στα {{$a}}", + "addon.mod_assign.gradingstatus": "Κατάσταση βαθμολόγησης", "addon.mod_assign.groupsubmissionsettings": "Ρυθμίσεις ομαδικής υποβολής", - "addon.mod_assign.hiddenuser": "Συμμετέχοντας", + "addon.mod_assign.hiddenuser": "Συμμετέχων", "addon.mod_assign.latesubmissions": "Καθυστερημένες υποβολές", + "addon.mod_assign.latesubmissionsaccepted": "Επιτρέπεται μέχρι {{$a}}", + "addon.mod_assign.markingworkflowstate": "Κατάσταση ροής εργασιών βαθμολόγησης", + "addon.mod_assign.markingworkflowstateinmarking": "Σε βαθμολόγηση", "addon.mod_assign.markingworkflowstateinreview": "Εξετάζεται", + "addon.mod_assign.markingworkflowstatenotmarked": "Δεν έχει βαθμολογηθεί", "addon.mod_assign.markingworkflowstatereadyforrelease": "Όλα έτοιμα για ανακοίνωση βαθμολογιών", + "addon.mod_assign.markingworkflowstatereadyforreview": "Η βαθμολόγηση ολοκληρώθηκε", "addon.mod_assign.markingworkflowstatereleased": "Ανακοινώθηκε", + "addon.mod_assign.modulenameplural": "Αναθέσεις εργασιών", + "addon.mod_assign.multipleteams": "Μέλος περισσότερων από μία ομάδων", "addon.mod_assign.multipleteams_desc": "Η ανάθεση απαιτεί την υποβολή σε ομάδες. Είστε μέλος περισσότερων από μία ομάδων. Για να μπορέσετε να υποβάλετε εργασία πρέπει να είστε μέλος μόνο μιας ομάδας. Επικοινωνήστε με τον διδάσκοντά σας για να αλλάξετε την συμμετοχή σας σε ομάδες.", "addon.mod_assign.noattempt": "Καμία προσπάθεια", "addon.mod_assign.nomoresubmissionsaccepted": "Επιτρέπονται μόνο στους συμμετέχοντες που τους έχει χορηγηθεί παράταση", "addon.mod_assign.noonlinesubmissions": "Δεν απαιτείται κάποια υποβολή", "addon.mod_assign.nosubmission": "Δεν έχει υποβληθεί τίποτα για την εργασία αυτή", "addon.mod_assign.notallparticipantsareshown": "Συμμετέχοντες χωρίς υποβολές δεν προβάλλονται", + "addon.mod_assign.noteam": "Δεν είναι μέλος καμίας ομάδας", "addon.mod_assign.noteam_desc": "Αυτή η ανάθεση απαιτεί την υποβολή σε ομάδες. Δεν είστε μέλος οποιασδήποτε ομάδας, έτσι δεν μπορείτε να δημιουργήσετε μια υποβολή. Επικοινωνήστε με τον διδάσκοντά σας για να προστεθείτε σε μια ομάδα.", "addon.mod_assign.notgraded": "Χωρίς βαθμό", - "addon.mod_assign.numberofdraftsubmissions": "Προσχέδια", + "addon.mod_assign.numberofdraftsubmissions": "Πρόχειρα", "addon.mod_assign.numberofparticipants": "Συμμετέχοντες", "addon.mod_assign.numberofsubmissionsneedgrading": "Απαιτείται βαθμολόγηση", "addon.mod_assign.numberofsubmittedassignments": "Υποβλήθηκαν", "addon.mod_assign.numberofteams": "Ομάδες", "addon.mod_assign.numwords": "{{$a}} λέξεις", - "addon.mod_assign.overdue": "Η εργασία είναι εκπρόθεσμη για: {{$a}}", + "addon.mod_assign.outof": "{{$a.current}} από {{$a.total}}", + "addon.mod_assign.overdue": "Η εργασία είναι εκπρόθεσμη μετά τις: {{$a}}", "addon.mod_assign.savechanges": "Αποθήκευση αλλαγών", "addon.mod_assign.submission": "Υποβολή", "addon.mod_assign.submissioneditable": "Ο μαθητής μπορεί να επεξεργαστεί αυτήν την υποβολή", @@ -194,6 +293,7 @@ "addon.mod_assign.submissionstatus_draft": "Πρόχειρο (δεν υποβλήθηκε)", "addon.mod_assign.submissionstatus_marked": "Βαθμολογήθηκε", "addon.mod_assign.submissionstatus_new": "Καμία υποβολή", + "addon.mod_assign.submissionstatus_reopened": "Άνοιξε ξανά", "addon.mod_assign.submissionstatus_submitted": "Υποβλήθηκε για βαθμολόγηση", "addon.mod_assign.submissionstatusheading": "Κατάσταση Υποβολής", "addon.mod_assign.submissionteam": "Ομάδα", @@ -201,9 +301,10 @@ "addon.mod_assign.submitassignment_help": "Από τη στιγμή που θα υποβληθεί η εργασία δεν θα μπορείτε να κάνετε οποιαδήποτε αλλαγή.", "addon.mod_assign.submittedearly": "Η εργασία υποβλήθηκε νωρίτερα κατά {{$a}}", "addon.mod_assign.submittedlate": "Η εργασία υποβλήθηκε {{$a}} αργότερα", - "addon.mod_assign.timemodified": "Τελευταία Τροποποίηση", + "addon.mod_assign.timemodified": "Τελευταία τροποποίηση", "addon.mod_assign.timeremaining": "Υπολειπόμενος χρόνος", "addon.mod_assign.ungroupedusers": "Η ρύθμιση «Απαίτηση για υποβολή ανά ομάδες» είναι ενεργοποιημένη και ορισμένοι χρήστες είτε δεν είναι μέλη κάποιας ομάδας, είτε είναι μέλη περισσότερων από μία ομάδων, οπότε δεν μπορούν να υποβάλουν εργασίες.", + "addon.mod_assign.unlimitedattempts": "Χωρίς περιορισμό", "addon.mod_assign.userswhoneedtosubmit": "Χρήστες που πρέπει να υποβάλουν: {{$a}}", "addon.mod_assign.userwithid": "Ο χρήστης με id {{id}}", "addon.mod_assign.viewsubmission": "Προβολή υποβολής", @@ -217,6 +318,7 @@ "addon.mod_assign_submission_file.pluginname": "Υποβολές αρχείων", "addon.mod_assign_submission_onlinetext.pluginname": "Υποβολές κειμένων σε ιστοσελίδα (σε σύνδεση)", "addon.mod_book.errorchapter": "Σφάλμα ανάγνωσης κεφαλαίου βιβλίου", + "addon.mod_book.modulenameplural": "Βιβλία", "addon.mod_chat.beep": "Ηχητικό σήμα", "addon.mod_chat.currentusers": "Τρέχοντες χρήστες", "addon.mod_chat.enterchat": "Κάντε κλικ εδώ για να μπείτε στο δωμάτιο συνομιλίας τώρα", @@ -229,26 +331,35 @@ "addon.mod_chat.messagebeepsyou": "Ο/Η {{$a}} μόλις σας έστειλε ηχητικό σήμα!", "addon.mod_chat.messageenter": "Ο/Η {{$a}} μόλις έχει μπει σ' αυτή τη συνομιλία", "addon.mod_chat.messageexit": "Ο/Η {{$a}} έχει φύγει από αυτή τη συνομιλία", + "addon.mod_chat.modulenameplural": "Συνομιλίες", "addon.mod_chat.mustbeonlinetosendmessages": "Πρέπει να είστε συνδεδεμένοι για να στείλετε μηνύματα", "addon.mod_chat.nomessages": "Καθόλου μηνύματα ακόμη", "addon.mod_chat.send": "Αποστολή", "addon.mod_chat.sessionstart": "Η επόμενη σύνοδος συνομιλίας θα αρχίσει στις {{$a.date}}, ({{$a.fromnow}} από τώρα)", "addon.mod_chat.talk": "Μιλήστε", "addon.mod_choice.cannotsubmit": "Λυπούμαστε, υπήρξε πρόβλημα κατά την υποβολή της επιλογής σας. Παρακαλώ προσπαθήστε ξανά.", + "addon.mod_choice.choiceoptions": "Επιλογές επιλογής", "addon.mod_choice.errorgetchoice": "Σφάλμα κατά τη λήψη των επιλεγμένων δεδομένων.", "addon.mod_choice.expired": "Η δραστηριότητα αυτή έκλεισε στις {{$a}} και δεν είναι πλέον διαθέσιμη", "addon.mod_choice.full": "(Πλήρες)", + "addon.mod_choice.modulenameplural": "Επιλογές", "addon.mod_choice.noresultsviewable": "Τα αποτελέσματα δεν είναι ορατά προς το παρόν.", "addon.mod_choice.notopenyet": "Συγγνώμη, αυτή η δραστηριότητα δεν είναι διαθέσιμη μέχρι {{$a}}", "addon.mod_choice.numberofuser": "Αριθμός απαντήσεων", "addon.mod_choice.numberofuserinpercentage": "Ποσοστό απαντήσεων", - "addon.mod_choice.previewonly": "Πρόκειται για μια προεπισκόπηση των διαθέσιμων επιλογών για αυτήν τη δραστηριότητα. Δεν θα μπορείτε να υποβάλετε την επιλογή σας μέχρι {{a}}}.", + "addon.mod_choice.previewonly": "Πρόκειται για μια προεπισκόπηση των διαθέσιμων επιλογών για αυτήν τη δραστηριότητα. Δεν θα μπορείτε να υποβάλετε την επιλογή σας μέχρι {{$a}}.", + "addon.mod_choice.publishinfoanonafter": "Ανώνυμα αποτελέσματα θα δημοσιευθούν μετά την απάντησή σας.", + "addon.mod_choice.publishinfoanonclose": "Ανώνυμα αποτελέσματα θα δημοσιευτούν μετά τη λήξη της δραστηριότητας.", + "addon.mod_choice.publishinfofullafter": "Τα πλήρη αποτελέσματα, που δείχνουν τις επιλογές όλων, θα δημοσιευθούν μετά την απάντησή σας.", + "addon.mod_choice.publishinfofullclose": "Τα πλήρη αποτελέσματα, που δείχνουν τις επιλογές όλων, θα δημοσιευθούν μετά τη λήξη της δραστηριότητας.", + "addon.mod_choice.publishinfonever": "Τα αποτελέσματα αυτής της δραστηριότητας δεν θα δημοσιευθούν μετά την απάντησή σας.", "addon.mod_choice.removemychoice": "Διαγραφή της επιλογής μου", "addon.mod_choice.responses": "Απαντήσεις", "addon.mod_choice.responsesresultgraphdescription": "{{number}}% των χρηστών επέλεξε: {{text}}.", "addon.mod_choice.responsesresultgraphheader": "Εμφάνιση γραφήματος", "addon.mod_choice.resultsnotsynced": "Τα αποτελέσματα δεν περιλαμβάνουν την τελευταία απάντησή σας. Παρακαλώ, συγχρονίστε την εφαρμογή για να τα ενημερώσετε.", "addon.mod_choice.savemychoice": "Αποθήκευση επιλογής", + "addon.mod_choice.userchoosethisoption": "Χρήστες που επέλεξαν αυτήν την επιλογή", "addon.mod_choice.yourselection": "Η επιλογή σας", "addon.mod_data.addentries": "Προσθήκη καταχωρήσεων", "addon.mod_data.advancedsearch": "Σύνθετη αναζήτηση", @@ -258,7 +369,7 @@ "addon.mod_data.ascending": "Αύξουσα", "addon.mod_data.authorfirstname": "Όνομα συγγραφέα", "addon.mod_data.authorlastname": "Επίθετο συγγραφέα", - "addon.mod_data.confirmdeleterecord": "Είστε σίγουρος/η ότι θέλετε να διαγραφεί αυτή η καταχώρηση;", + "addon.mod_data.confirmdeleterecord": "Σίγουρα θέλετε να διαγραφεί αυτή η καταχώρηση;", "addon.mod_data.descending": "Φθίνουσα", "addon.mod_data.disapprove": "Αναίρεση έγκρισης", "addon.mod_data.emptyaddform": "Δεν συμπληρώσατε κανένα από τα πεδία!", @@ -267,8 +378,10 @@ "addon.mod_data.errormustsupplyvalue": "Πρέπει να εισάγετε μια τιμή εδώ.", "addon.mod_data.expired": "Δυστυχώς, η δραστηριότητα αυτή έκλεισε στις {{$a}} και δεν είναι πλέον διαθέσιμη", "addon.mod_data.fields": "Πεδία", + "addon.mod_data.foundrecords": "Βρέθηκαν οι εγγραφές: {{$a.num}}/{{$a.max}} (Επαρχικοποίηση φίλτρων)", "addon.mod_data.latlongboth": "Απαιτούνται και γεωγραφικό πλάτος και γεωγραφικό μήκος.", "addon.mod_data.menuchoose": "Επιλέξτε....", + "addon.mod_data.modulenameplural": "Βάσεις δεδομένων", "addon.mod_data.more": "Περισσότερα", "addon.mod_data.nomatch": "Δεν βρέθηκαν καταχωρήσεις που να ταιριάζουν!", "addon.mod_data.norecords": "Δεν υπάρχουν καταχωρήσεις στη βάση δεδομένων", @@ -288,33 +401,40 @@ "addon.mod_data.usedate": "Συμπερίληψη στην αναζήτηση.", "addon.mod_feedback.analysis": "Ανάλυση", "addon.mod_feedback.anonymous": "Ανώνυμα", - "addon.mod_feedback.anonymous_entries": "Ανώνυμες καταχωρήσεις", + "addon.mod_feedback.anonymous_entries": "Ανώνυμες καταχωρήσεις ({{$a}})", "addon.mod_feedback.average": "Μέσος όρος", "addon.mod_feedback.captchaofflinewarning": "Η ανατροφοδότηση με την χρήση captcha δεν μπορεί να ολοκληρωθεί εάν δεν έχει διαμορφωθεί, εάν βρίσκεστε εκτός λειτουργίας ή με ο server δεν λειτουργεί.", "addon.mod_feedback.complete_the_form": "Απαντήστε τις ερωτήσεις...", "addon.mod_feedback.completed_feedbacks": "Απαντήσεις που έχουν υποβληθεί", - "addon.mod_feedback.continue_the_form": "Συνεχίστε τη φόρμα", + "addon.mod_feedback.continue_the_form": "Συνεχίστε την απάντηση των ερωτήσεων", "addon.mod_feedback.feedback_is_not_open": "Η ανατροφοδότηση δεν είναι ανοιχτή", "addon.mod_feedback.feedback_submitted_offline": "Αυτή η ανατροφοδότηση έχει αποθηκευτεί για να υποβληθεί αργότερα.", - "addon.mod_feedback.feedbackclose": "Κλείσε το σχόλιο στις", - "addon.mod_feedback.feedbackopen": "Άνοιξε το σχόλιο στις", + "addon.mod_feedback.feedbackclose": "Επιτρέπονται απαντήσεις μέχρι", + "addon.mod_feedback.feedbackopen": "Επιτρέπονται απαντήσεις από", "addon.mod_feedback.mapcourses": "Αντιστοίχηση ανατροφοδότησης σε μαθήματα", "addon.mod_feedback.mode": "Λειτουργία", + "addon.mod_feedback.modulenameplural": "Ανατροφοδοτήσεις", "addon.mod_feedback.next_page": "Επόμενη σελίδα", "addon.mod_feedback.non_anonymous": "Το όνομα του χρήστη θα καταγραφεί και θα εμφανίζεται με τις απαντήσεις", - "addon.mod_feedback.non_anonymous_entries": "χωρίς ανώνυμες καταχωρήσεις", + "addon.mod_feedback.non_anonymous_entries": "Χωρίς ανώνυμες καταχωρήσεις ({{$a}})", + "addon.mod_feedback.non_respondents_students": "Μαθητές που δεν απάντησαν ({{$a}})", "addon.mod_feedback.not_selected": "Δεν έχουν επιλεχθεί", + "addon.mod_feedback.not_started": "Δεν ξεκίνησαν", + "addon.mod_feedback.numberoutofrange": "Αριθμός εκτός εύρους", "addon.mod_feedback.overview": "Επισκόπηση", - "addon.mod_feedback.page_after_submit": "Σελίδα μετά την υποβολή", + "addon.mod_feedback.page_after_submit": "Μήνυμα ολοκλήρωσης", "addon.mod_feedback.preview": "Προεπισκόπηση", "addon.mod_feedback.previous_page": "Προηγούμενη σελίδα", "addon.mod_feedback.questions": "Ερωτήσεις", - "addon.mod_feedback.response_nr": "Απάντηση υπ'αριθμόν.", + "addon.mod_feedback.response_nr": "Αριθμός απάντησης", "addon.mod_feedback.responses": "Απαντήσεις", - "addon.mod_feedback.save_entries": "Υποβολή των απαντήσεων σας", + "addon.mod_feedback.save_entries": "Υποβολή των απαντήσεών σας", "addon.mod_feedback.show_entries": "Εμφάνιση απαντήσεων", + "addon.mod_feedback.show_nonrespondents": "Εμφάνιση όσων δεν απάντησαν", + "addon.mod_feedback.started": "Ξεκίνησε", "addon.mod_feedback.this_feedback_is_already_submitted": "Έχετε ολοκληρώσει ήδη αυτή την δραστηριότητα.", "addon.mod_folder.emptyfilelist": "Δεν υπάρχουν αρχεία.", + "addon.mod_folder.modulenameplural": "Φάκελοι", "addon.mod_forum.addanewdiscussion": "Προσθήκη νέου θέματος συζήτησης", "addon.mod_forum.addanewquestion": "Προσθήκη νέας ερώτησης", "addon.mod_forum.addanewtopic": "Προσθήκη νέου θέματος", @@ -327,6 +447,8 @@ "addon.mod_forum.discussionpinned": "Καρφιτσωμένο", "addon.mod_forum.discussionsubscription": "Εγγραφή στη συζήτηση", "addon.mod_forum.edit": "Επεξεργασία", + "addon.mod_forum.erroremptymessage": "Το μήνυμα της ανάρτησης δεν μπορεί να είναι άδειο", + "addon.mod_forum.erroremptysubject": "Το θέμα της ανάρτησης δεν μπορεί να είναι άδειο.", "addon.mod_forum.errorgetforum": "Σφάλμα κατά τη λήψη των δεδομένων του forum.", "addon.mod_forum.errorgetgroups": "Σφάλμα κατά τη λήψη των ρυθμίσεων της ομάδας.", "addon.mod_forum.forumnodiscussionsyet": "Δεν υπάρχουν ακόμα θέματα συζήτησης σε αυτό το forum.", @@ -335,9 +457,10 @@ "addon.mod_forum.modeflatnewestfirst": "Εμφάνιση απαντήσεων οριζοντίως, με την πιο πρόσφατη πρώτη", "addon.mod_forum.modeflatoldestfirst": "Εμφάνιση απαντήσεων οριζοντίως, με την παλαιότερη πρώτη", "addon.mod_forum.modenested": "Εμφάνιση απαντήσεων σε φωλιασμένη μορφή", + "addon.mod_forum.modulenameplural": "Φόρουμ", "addon.mod_forum.numdiscussions": "{{numdiscussions}} συζητήσεις", "addon.mod_forum.numreplies": "{{numreplies}} απαντήσεις", - "addon.mod_forum.posttoforum": "Ανάρτηση στο φόρουμ", + "addon.mod_forum.posttoforum": "Δημοσίευση στο φόρουμ", "addon.mod_forum.re": "Απάντηση:", "addon.mod_forum.refreshdiscussions": "Ανανεώστε τις συζητήσεις", "addon.mod_forum.refreshposts": "Ανανεώστε τα μηνύματα της συζήτησης", @@ -370,35 +493,42 @@ "addon.mod_glossary.fillfields": "Η έννοια και ο ορισμός είναι υποχρεωτικά πεδία.", "addon.mod_glossary.fullmatch": "Ταίριασμα ολόκληρων λέξεων μόνο", "addon.mod_glossary.linking": "Αυτόματοι σύνδεσμοι", + "addon.mod_glossary.modulenameplural": "Γλωσσάρια", "addon.mod_glossary.noentriesfound": "Δεν βρέθηκαν καταχωρήσεις.", "addon.mod_glossary.searchquery": "Αναζήτηση ερωτήματος", + "addon.mod_imscp.deploymenterror": "Σφάλμα πακέτου περιεχομένου!", + "addon.mod_imscp.modulenameplural": "Πακέτο περιοχομένου IMS", "addon.mod_imscp.showmoduledescription": "Εμφάνιση περιγραφής", "addon.mod_lesson.answer": "Απάντηση", "addon.mod_lesson.attempt": "Προσπάθεια: {{$a}}", + "addon.mod_lesson.attemptheader": "Προσπάθεια", "addon.mod_lesson.attemptsremaining": "Σας απομένουν {{$a}} προσπάθειες", - "addon.mod_lesson.averagescore": "Μέσος βαθμός", + "addon.mod_lesson.averagescore": "Μέσο σκορ", "addon.mod_lesson.averagetime": "Μέσος χρόνος", - "addon.mod_lesson.branchtable": "Πίνακας διακλάδωσης", + "addon.mod_lesson.branchtable": "Περιεχόμενο (Πίν. διακλ.)", "addon.mod_lesson.cannotfindattempt": "Σφάλμα: δεν ήταν δυνατή η εύρεση προσπάθειας", "addon.mod_lesson.cannotfinduser": "Σφάλμα: δεν ήταν δυνατή η εύρεση χρηστών", "addon.mod_lesson.clusterjump": "Ερώτηση που δεν εμφανίστηκε μέσα σε συστάδα", "addon.mod_lesson.completed": "Ολοκληρώθηκε", "addon.mod_lesson.congratulations": "Συγχαρητήρια - τελειώσατε την διδακτική ενότητα", "addon.mod_lesson.continue": "Συνέχεια", - "addon.mod_lesson.defaultessayresponse": "Η έκθεσή σας θα βαθμολογηθεί από τον καθηγητή του μαθήματος.", + "addon.mod_lesson.continuetonextpage": "Συνέχεια στην επόμενη σελίδα.", + "addon.mod_lesson.defaultessayresponse": "Η έκθεσή σας θα βαθμολογηθεί από τον διδάσκοντά σας.", "addon.mod_lesson.detailedstats": "Αναλυτικά στατιστικά", "addon.mod_lesson.didnotanswerquestion": "Δεν απάντησε αυτήν την ερώτηση.", "addon.mod_lesson.displayofgrade": "Εμφάνιση βαθμού (μόνο για μαθητές)", - "addon.mod_lesson.displayscorewithessays": "Πήρατε {{$a.score}} στα {{$a.tempmaxgrade}} για τις αυτόματα βαθμολογούμενες ερωτήσεις.
Οι {{$a.essayquestions}} ερωτήσεις έκθεσης θα βαθμολογηθούν και θα προστεθούν
στον τελικό σας βαθμό αργότερα.

Ο τρέχον βαθμός σας χωρίς τις ερωτήσεις έκθεσης είναι {{$a.score}} στα {{$a.grade}}", - "addon.mod_lesson.displayscorewithoutessays": "Ο βαθμός σας είναι {{$a.score}} (στα {{$a.grade}}).", + "addon.mod_lesson.displayscorewithessays": "

Πήρατε {{$a.score}} στα {{$a.tempmaxgrade}} για τις αυτόματα βαθμολογούμενες ερωτήσεις.

Οι {{$a.essayquestions}} ερωτήσεις έκθεσης θα βαθμολογηθούν και θα προστεθούν στο τελικό σας σκορ αργότερα.

Ο τρέχων βαθμός σας χωρίς τις ερωτήσεις έκθεσης είναι {{$a.score}} στα {{$a.grade}}.

", + "addon.mod_lesson.displayscorewithoutessays": "Το σκορ σας είναι {{$a.score}} (στα {{$a.grade}}).", "addon.mod_lesson.emptypassword": "Ο κωδικός πρόσβασης δεν μπορεί να είναι κενός", "addon.mod_lesson.enterpassword": "Παρακαλούμε εισάγετε τον κωδικό πρόσβασης:", "addon.mod_lesson.eolstudentoutoftimenoanswers": "Δεν απαντήσατε καμία ερώτηση. Πήρατε 0 σε αυτήν τη διδακτική ενότητα.", "addon.mod_lesson.errorreviewretakenotlast": "Αυτή η προσπάθεια δεν μπορεί πλέον να αναθεωρηθεί επειδή μια άλλη προσπάθεια έχει ήδη ολοκληρωθεί.", + "addon.mod_lesson.finish": "Τέλος", "addon.mod_lesson.finishretakeoffline": "Αυτή η προσπάθεια έχει ολοκληρωθεί εκτός σύνδεσης.", - "addon.mod_lesson.firstwrong": "Δυστυχώς δεν μπορείτε να κερδίσετε αυτό το βαθμό γιατί η απάντησή σας ήταν λάθος. Θέλετε να συνεχίσετε να μαντεύετε, μόνο για τη χαρά της μάθησης (δεν θα λάβετε το βαθμολογικό μπόνους);", + "addon.mod_lesson.firstwrong": "Απαντήσατε λανθασμένα. Θα θέλατε να προσπαθήσετε ξανά την ερώτηση; (Αν απαντήσετε τώρα την ερώτηση σωστά, δεν θα μετρήσει στο τελικό σκορ σας.)", + "addon.mod_lesson.gotoendoflesson": "Μετάβαση στο τέλος του μαθήματος", "addon.mod_lesson.grade": "Βαθμός", - "addon.mod_lesson.highscore": "Υψηλός βαθμός", + "addon.mod_lesson.highscore": "Υψηλό σκορ", "addon.mod_lesson.hightime": "Υψηλός χρόνος", "addon.mod_lesson.leftduringtimed": "Αποχωρήσατε κατά τη διάρκεια χρονομετρημένης διδακτικής ενότητας.
Παρακαλούμε, πατήστε Συνέχεια για να επανεκκινήσετε την ενότητα.", "addon.mod_lesson.leftduringtimednoretake": "Αποχωρήσατε κατά τη διάρκεια χρονομετρημένης διδακτικής ενότητας και
δεν έχετε το δικαίωμα να επαναλάβεται ή να συνεχίσετε την ενότητα.", @@ -406,22 +536,25 @@ "addon.mod_lesson.lessonstats": "Στατιστικά διδακτικής ενότητας", "addon.mod_lesson.linkedmedia": "Συνδεδεμένα πολυμέσα", "addon.mod_lesson.loginfail": "Αποτυχημένη σύνδεση. Παρακαλούμε προσπαθήστε ξανά...", - "addon.mod_lesson.lowscore": "Χαμηλός βαθμός", + "addon.mod_lesson.lowscore": "Χαμηλό σκορ", "addon.mod_lesson.lowtime": "Χαμηλός χρόνος", "addon.mod_lesson.maximumnumberofattemptsreached": "Συμπληρώθηκε ο μέγιστος αριθμός προσπαθειών - Μετάβαση στην επόμενη σελίδα", "addon.mod_lesson.modattemptsnoteacher": "Η ανασκόπηση από μαθητή δουλεύει μόνο για τους μαθητές", + "addon.mod_lesson.modulenameplural": "Ενότητες", "addon.mod_lesson.noanswer": "Καμία απάντηση δεν δόθηκε. Παρακαλούμε, πηγαίνετε πίσω και καταχωρήστε την απαντησή σας.", "addon.mod_lesson.nolessonattempts": "Δεν πραγματοποιήθηκαν προσπάθειες σε αυτήν τη διδακτική ενότητα.", + "addon.mod_lesson.nolessonattemptsgroup": "Δεν έχουν γίνει προσπάθειες από {{$a}} μέλη της ομάδας σε αυτή τη διδακτική ενότητα.", "addon.mod_lesson.notcompleted": "Δεν ολοκληρώθηκε", "addon.mod_lesson.numberofcorrectanswers": "Αριθμός σωστών απαντήσεων: {{$a}}", "addon.mod_lesson.numberofpagesviewed": "Αριθμός απαντημένων ερωτήσεων: {{$a}}", - "addon.mod_lesson.numberofpagesviewednotice": "Αριθμός απαντημένων ερωτήσεων: {{$a.nquestions}} (πρέπει να απαντήσετε τουλάχιστον: {{$a.minquestions}})", - "addon.mod_lesson.ongoingcustom": "Έχετε πάρει {{$a.score}} από τους τους {{$a.currenthigh}} βαθμούς μέχρι τώρα .", + "addon.mod_lesson.numberofpagesviewednotice": "Αριθμός απαντημένων ερωτήσεων: {{$a.nquestions}} (Πρέπει να απαντήσετε τουλάχιστον: {{$a.minquestions}})", + "addon.mod_lesson.ongoingcustom": "Έχετε πάρει {{$a.score}} από τους {{$a.currenthigh}} πόντους μέχρι τώρα .", "addon.mod_lesson.ongoingnormal": "Έχετε απαντήσει {{$a.correct}} σωστά από τις {{$a.viewed}} προσπάθειες.", "addon.mod_lesson.or": "Ή", "addon.mod_lesson.overview": "Σύνοψη", "addon.mod_lesson.preview": "Προεπισκόπηση", "addon.mod_lesson.progressbarteacherwarning2": "Δεν θα δείτε την μπάρα προόδου γιατί μπορείτε να επεξεργαστείτε αυτή την διδακτική ενότητα", + "addon.mod_lesson.progresscompleted": "Έχετε ολοκληρώσει το {{$a}}% της διδακτικής ενότητας", "addon.mod_lesson.question": "Ερώτηση", "addon.mod_lesson.rawgrade": "Βαθμός χωρίς επεξεργασία", "addon.mod_lesson.reports": "Αναφορές", @@ -436,13 +569,13 @@ "addon.mod_lesson.secondpluswrong": "Όχι ακριβώς. Θέλετε να δοκιμάσετε ξανά;", "addon.mod_lesson.submit": "Υποβολή", "addon.mod_lesson.teacherjumpwarning": "Μία μεταπήδηση {{$a.cluster}} ή μια μεταπήδηση {{$a.unseen}} χρησιμοποιείται σε αυτήν τη διδακτική ενότητα. Η μεταπήδηση επόμενης σελίδας θα χρησιμοποιηθεί αντί αυτών. Συνδεθείτε ως μαθητής για να δοκιμάσετε αυτές τις μεταπηδήσεις.", - "addon.mod_lesson.teacherongoingwarning": "Η τρέχουσα βαθμολογία εμφανίζεται μόνο στους μαθητές. Συνδεθείτε ως μαθητής για να δείτε την τρέχουσα βαθμολογία", + "addon.mod_lesson.teacherongoingwarning": "Το τρέχων σκορ εμφανίζεται μόνο στους μαθητές. Συνδεθείτε ως μαθητής για να δείτε το τρέχων σκορ", "addon.mod_lesson.teachertimerwarning": "Το χρονόμετρο λειτουργεί μόνο για τους μαθητές. Συνδεθείτε ως μαθητής για να δοκιμάσετε το χρονόμετρο.", "addon.mod_lesson.thatsthecorrectanswer": "Αυτή είναι η σωστή απάντηση", "addon.mod_lesson.thatsthewronganswer": "Αυτή είναι η λάθος απάντηση", "addon.mod_lesson.timeremaining": "Υπολοιπόμενος χρόνος", "addon.mod_lesson.timetaken": "Χρόνος που χρειάστηκε", - "addon.mod_lesson.unseenpageinbranch": "Ερώτηση μέσα σε διακλάδωση που δεν εμφανίσθηκε", + "addon.mod_lesson.unseenpageinbranch": "Ερώτηση που δεν εμφανίσθηκε, μέσα σε μια σελίδα περιεχομένου", "addon.mod_lesson.warningretakefinished": "Η προσπάθεια ολοκληρώθηκε στον ιστότοπο.", "addon.mod_lesson.welldone": "Μπράβο!", "addon.mod_lesson.youhaveseen": "Έχετε δει περισσότερες από μία σελίδες αυτής της διδακτικής ενότητας.
Θέλετε να ξεκινήσετε από την τελευταία σελίδα που είδατε;", @@ -452,7 +585,9 @@ "addon.mod_lti.errorgetlti": "Σφάλμα κατά τη λήψη των δεδομένων του module.", "addon.mod_lti.errorinvalidlaunchurl": "Η αρχική διεύθυνση δεν είναι έγκυρη.", "addon.mod_lti.launchactivity": "Ξεκινήστε την δραστηριότητα", + "addon.mod_lti.modulenameplural": "Εξωτερικά εργαλεία", "addon.mod_page.errorwhileloadingthepage": "Σφάλμα κατά τη φόρτωση του περιεχομένου της σελίδας.", + "addon.mod_page.modulenameplural": "Σελίδες", "addon.mod_quiz.attemptfirst": "Πρώτη προσπάθεια", "addon.mod_quiz.attemptlast": "Τελευταία προσπάθεια", "addon.mod_quiz.attemptnumber": "Προσπάθεια", @@ -461,11 +596,12 @@ "addon.mod_quiz.cannotsubmitquizdueto": "Αυτή η προσπάθεια επίλυσης του κουίζ δεν μπορεί να υποβληθεί για τους εξής λόγους:", "addon.mod_quiz.comment": "Σχόλιο", "addon.mod_quiz.completedon": "Ολοκληρώθηκε στις", - "addon.mod_quiz.confirmclose": "Πρόκειται να τερματίσετε αυτήν την προσπάθεια. Από τη στιγμή που θα τερματίσετε την προσπάθειά σας δεν θα είστε σε θέση να αλλάξετε τις απαντήσεις σας.", + "addon.mod_quiz.confirmclose": "Μόλις υποβάλετε, δεν θα μπορείτε πλέον να αλλάξετε τις απαντήσεις σας για αυτήν την προσπάθεια.", "addon.mod_quiz.confirmcontinueoffline": "Αυτή η προσπάθεια δεν έχει συγχρονιστεί από {{$ a}}. Αν έχετε συνεχίσει την προσπάθεια αυτή σε μια άλλη συσκευή από τότε, μπορεί να χάσετε δεδομένα.", "addon.mod_quiz.confirmleavequizonerror": "Παρουσιάστηκε σφάλμα κατά την αποθήκευση των απαντήσεων. Είστε σίγουροι ότι θέλετε να εξέλθετε από το κουίζ;", "addon.mod_quiz.confirmstart": "Το κουίζ έχει όριο χρόνου {{$a}}. Ο χρόνος θα μετρήσει αντίστροφα από τη στιγμή που θα ξεκινήσετε την προσπάθειά σας και πρέπει να την υποβάλλετε πριν τη λήξη του. Σίγουρα επιθυμείτε να ξεκινήσετε τώρα;", "addon.mod_quiz.confirmstartheader": "Κουίζ με χρονικό όριο", + "addon.mod_quiz.connectionerror": "Η σύνδεση δικτύου χάθηκε. (Αποτυχία αυτόματης αποθήκευσης).\n\nΣημειώστε τις απαντήσεις που καταχωρίσατε σε αυτή τη σελίδα τα τελευταία λεπτά και δοκιμάστε να συνδεθείτε ξανά.\n\nΜόλις αποκατασταθεί η σύνδεση, οι αποκρίσεις σας θα πρέπει να αποθηκευτούν και αυτό το μήνυμα θα εξαφανιστεί.", "addon.mod_quiz.continueattemptquiz": "Συνέχιση της τελευταίας προσπάθειας", "addon.mod_quiz.continuepreview": "Συνέχιση της τελευταίας προεπισκόπησης", "addon.mod_quiz.errorbehaviournotsupported": "Δεν μπορείτε να πραγματοποιήσετε αυτό το κουίζ στην εφαρμογή, διότι η συμπεριφορά του δεν υποστηρίζεται από την εφαρμογή:", @@ -486,6 +622,7 @@ "addon.mod_quiz.grademethod": "Μέθοδος βαθμολόγησης", "addon.mod_quiz.gradesofar": "{{$a.method}}: {{$a.mygrade}} / {{$a.quizgrade}}.", "addon.mod_quiz.marks": "Βαθμοί", + "addon.mod_quiz.modulenameplural": "Κουίζ", "addon.mod_quiz.mustbesubmittedby": "Αυτή η προσπάθεια πρέπει να υποβληθεί από {{$a}}.", "addon.mod_quiz.noquestions": "Καμία ερώτηση δεν έχει προστεθεί ακόμα", "addon.mod_quiz.noreviewattempt": "Δεν επιτρέπεται να αναθεωρήσετε αυτήν την προσπάθεια.", @@ -495,8 +632,8 @@ "addon.mod_quiz.outofpercent": "{{$a.grade}} από ένα μέγιστο {{$a.maxgrade}} ({{$a.percent}}%)", "addon.mod_quiz.outofshort": "{{$a.grade}}/{{$a.maxgrade}}", "addon.mod_quiz.overallfeedback": "Συνολική ανατροφοδότηση", - "addon.mod_quiz.overdue": "Καθυστερημένο", - "addon.mod_quiz.overduemustbesubmittedby": "Αυτή η προσπάθεια είναι πλέον καθυστερημένη. Έπρεπε να έχει υποβληθεί ήδη. Εάν θέλετε να βαθμολογηθεί αυτό το κουίζ, πρέπει να το υποβάλετε μέχρι {{$a}}. Εάν δεν το υποβάλλετε μέχρι τότε, δεν θα ληφθούν υπόψη βαθμολογίες για αυτή την προσπάθεια.", + "addon.mod_quiz.overdue": "Εκπρόθεσμο", + "addon.mod_quiz.overduemustbesubmittedby": "Αυτή η προσπάθεια είναι πλέον εκπρόθεσμη. Έπρεπε να έχει υποβληθεί ήδη. Εάν θέλετε να βαθμολογηθεί αυτό το κουίζ, πρέπει να το υποβάλετε μέχρι {{$a}}. Εάν δεν το υποβάλετε μέχρι τότε, δεν θα ληφθούν υπόψη βαθμολογίες για αυτή την προσπάθεια.", "addon.mod_quiz.preview": "Προεπισκόπηση", "addon.mod_quiz.previewquiznow": "Προεπισκόπηση κουίζ", "addon.mod_quiz.question": "Ερώτηση", @@ -516,6 +653,7 @@ "addon.mod_quiz.statefinished": "Ολοκληρωμένο", "addon.mod_quiz.statefinisheddetails": "Υποβλήθηκε {{$a}}", "addon.mod_quiz.stateinprogress": "Σε εξέλιξη", + "addon.mod_quiz.stateoverdue": "Εκπρόθεσμο", "addon.mod_quiz.stateoverduedetails": "Πρέπει να υποβληθεί μέχρι {{$a}}", "addon.mod_quiz.status": "Κατάσταση", "addon.mod_quiz.submitallandfinish": "Υποβολή όλων και τέλος", @@ -529,6 +667,7 @@ "addon.mod_quiz.yourfinalgradeis": "Ο τελικός σας βαθμός γι' αυτό το κουίζ είναι {{$a}}", "addon.mod_resource.errorwhileloadingthecontent": "Σφάλμα κατά τη φόρτωση του περιεχομένου.", "addon.mod_resource.modifieddate": "Τροποποιήθηκε {{$a}}", + "addon.mod_resource.modulenameplural": "Αρχεία", "addon.mod_resource.openthefile": "Ανοίξτε το αρχείο", "addon.mod_resource.uploadeddate": "Ανέβηκε στις {{$a}}", "addon.mod_scorm.asset": "Στοιχείο", @@ -563,8 +702,9 @@ "addon.mod_scorm.gradesum": "Άθροισμα βαθμού", "addon.mod_scorm.highestattempt": "Υψηλότερη βαθμολογία", "addon.mod_scorm.incomplete": "Μη ολοκληρωμένο", - "addon.mod_scorm.lastattempt": "Τελευταία προσπάθεια", + "addon.mod_scorm.lastattempt": "Τελευταία ολοκληρωμένη προσπάθεια", "addon.mod_scorm.mode": "Κατάσταση", + "addon.mod_scorm.modulenameplural": "Πακέτα SCORM", "addon.mod_scorm.newattempt": "Έναρξη νέας προσπάθειας", "addon.mod_scorm.noattemptsallowed": "Επιτρεπόμενος αριθμός προσπαθειών", "addon.mod_scorm.noattemptsmade": "Αριθμός προσπαθειών που κάνατε", @@ -584,47 +724,87 @@ "addon.mod_survey.errorgetsurvey": "Σφάλμα κατά τη λήψη των δεδομένων της έρευνας.", "addon.mod_survey.ifoundthat": "Ανακάλυψα ότι", "addon.mod_survey.ipreferthat": "Προτιμώ το ότι", + "addon.mod_survey.modulenameplural": "Έρευνες", "addon.mod_survey.responses": "Απαντήσεις", "addon.mod_survey.results": "Αποτελέσματα", "addon.mod_survey.surveycompletednograph": "Έχετε ολοκληρώσει αυτή την έρευνα.", "addon.mod_url.accessurl": "Δείτε το URL", + "addon.mod_url.modulenameplural": "Διευθύνσεις URL", "addon.mod_url.pointingtourl": "Το url αυτού του πόρου δείχνει στο", + "addon.mod_wiki.cannoteditpage": "Δεν μπορείτε να επεξεργαστείτε αυτήν τη σελίδα.", + "addon.mod_wiki.createpage": "Δημιουργία σελίδας", + "addon.mod_wiki.editingpage": "Επεξεργασία αυτής της σελίδας '{{$a}}\\", "addon.mod_wiki.errorloadingpage": "Παρουσιάστηκε σφάλμα κατά τη φόρτωση της σελίδας.", "addon.mod_wiki.errornowikiavailable": "Αυτό το wiki δεν έχει ακόμα περιεχόμενο.", "addon.mod_wiki.gowikihome": "Go Wiki home", + "addon.mod_wiki.map": "Χάρτης", + "addon.mod_wiki.modulenameplural": "Wiki (ουίκι) (πληθ.)", + "addon.mod_wiki.newpagehdr": "Νέα σελίδα", + "addon.mod_wiki.newpagetitle": "Νέος τίτλος σελίδας", + "addon.mod_wiki.nocontent": "Δεν υπάρχει περιεχόμενο για αυτήν τη σελίδα", + "addon.mod_wiki.notingroup": "Όχι σε ομάδα", + "addon.mod_wiki.pagename": "Όνομα σελίδας", "addon.mod_wiki.subwiki": "Subwiki", "addon.mod_wiki.titleshouldnotbeempty": "Ο τίτλος δεν πρέπει να είναι κενός", "addon.mod_wiki.viewpage": "Δείτε τη σελίδα", "addon.mod_wiki.wikipage": "Σελίδα Wiki", + "addon.mod_wiki.wrongversionlock": "Ένας άλλος χρήστης έχει επεξεργαστεί αυτήν τη σελίδα ενώ την επεξεργάζεστε και το περιεχόμενό σας είναι ξεπερασμένο.", + "addon.mod_workshop.alreadygraded": "Ήδη βαθμολογημένο", "addon.mod_workshop.areainstructauthors": "Οδηγίες για την υποβολή", "addon.mod_workshop.areainstructreviewers": "Οδηγίες για την αξιολόγηση", "addon.mod_workshop.assess": "Αξιολόγηση", "addon.mod_workshop.assessedsubmission": "Αξιολογημένη υποβολή", + "addon.mod_workshop.assessmentform": "Φόρμα αξιολόγησης", "addon.mod_workshop.assessmentsettings": "Ρυθμίσεις αξιολόγησης", + "addon.mod_workshop.assessmentweight": "Συντελεστής βαρύτητας αξιολόγησης", "addon.mod_workshop.assignedassessments": "Υποβολές προς αξιολόγηση που έχουν ανατεθεί", "addon.mod_workshop.assignedassessmentsnone": "Δεν σας έχει ανατεθεί υποβολή για αξιολόγηση", + "addon.mod_workshop.conclusion": "Κατάληξη", "addon.mod_workshop.createsubmission": "Έναρξη προετοιμασίας της υποβολής σας", "addon.mod_workshop.deletesubmission": "Διαγραφή υποβολής", "addon.mod_workshop.editsubmission": "Επεξεργασία υποβολής", "addon.mod_workshop.feedbackauthor": "Ανατροφοδότηση για τον συγγραφέα", + "addon.mod_workshop.feedbackby": "Ανατροφοδότηση από {{$a}}", + "addon.mod_workshop.feedbackreviewer": "Ανατροφοδότηση για τον αξιολογητή", + "addon.mod_workshop.givengrades": "Βαθμοί που δόθηκαν", "addon.mod_workshop.gradecalculated": "Υπολογισμός βαθμού για υποβολή", + "addon.mod_workshop.gradeinfo": "Βαθμός: {{$a.received}} από {{$a.max}}", "addon.mod_workshop.gradeover": "Παράκαμψη βαθμού για υποβολή", + "addon.mod_workshop.gradesreport": "Αναφορά βαθμών εργαστηρίου", "addon.mod_workshop.gradinggrade": "Βαθμός για την αξιολόγηση", + "addon.mod_workshop.gradinggradecalculated": "Υπολογισμένος βαθμός για την αξιολόγηση", "addon.mod_workshop.gradinggradeof": "Βαθμός για την αξιολόγηση (από {{$a}})", "addon.mod_workshop.gradinggradeover": "Παράκαμψη βαθμού για εκτίμηση", + "addon.mod_workshop.modulenameplural": "Εργαστήρια", + "addon.mod_workshop.nogradeyet": "Χωρίς βαθμό ακόμη", + "addon.mod_workshop.notassessed": "Δεν έχει αξιολογηθεί ακόμη", + "addon.mod_workshop.notoverridden": "Δεν παρακάμπτεται", "addon.mod_workshop.noyoursubmission": "Δεν έχετε υποβάλει την εργασία σας ακόμα", + "addon.mod_workshop.overallfeedback": "Συνολική ανατροφοδότηση", "addon.mod_workshop.publishedsubmissions": "Δημοσιευμένες υποβολές", "addon.mod_workshop.publishsubmission": "Δημοσίευση υποβολής", "addon.mod_workshop.publishsubmission_help": "Οι δημοσιευμένες υποβολές διατίθενται στους υπόλοιπους όταν το εργαστήριο είναι κλειστό.", - "addon.mod_workshop.reassess": "Επανεξέταση", + "addon.mod_workshop.reassess": "Επαναξιολόγηση", + "addon.mod_workshop.receivedgrades": "Βαθμοί που ελήφθησαν", "addon.mod_workshop.submissionattachment": "Συνημμένο", "addon.mod_workshop.submissioncontent": "Περιεχόμενο υποβολής", "addon.mod_workshop.submissiondeleteconfirm": "Σίγουρα θέλετε να διαγράψετε την παρακάτω υποβολή;", "addon.mod_workshop.submissiongrade": "Βαθμός για την υποβολή", "addon.mod_workshop.submissiongradeof": "Βαθμός για την υποβολή (από {{$a}})", + "addon.mod_workshop.submissionrequiredcontent": "Πρέπει να εισαγάγετε κάποιο κείμενο ή να προσθέσετε ένα αρχείο.", "addon.mod_workshop.submissionsreport": "Αναφορά υποβολών εργαστηρίου", "addon.mod_workshop.submissiontitle": "Τίτλος", - "addon.mod_workshop.weightinfo": "Βαρύτητα: {{$a}}", + "addon.mod_workshop.switchphase10": "Μετάβαση σε φάση εγκατάστασης", + "addon.mod_workshop.switchphase20": "Μετάβαση στη φάση υποβολής", + "addon.mod_workshop.switchphase30": "Μετάβαση στη φάση αξιολόγησης", + "addon.mod_workshop.switchphase40": "Μετάβαση στη φάση αποτίμησης", + "addon.mod_workshop.switchphase50": "Κλείσιμο εργαστηρίου", + "addon.mod_workshop.userplan": "Σχεδιασμός εργαστηρίου", + "addon.mod_workshop.userplancurrentphase": "Τρέχουσα φάση", + "addon.mod_workshop.weightinfo": "Συντελεστής βαρύτητας: {{$a}}", + "addon.mod_workshop.yourassessment": "Η αξιολόγησή σας", + "addon.mod_workshop.yourassessmentfor": "Η αξιολόγησή σας για {{$a}}", + "addon.mod_workshop.yourgrades": "Οι βαθμοί σας", "addon.mod_workshop.yoursubmission": "Η υποβολή σας", "addon.mod_workshop_assessment_accumulative.dimensioncommentfor": "Σχόλιο για {{$a}}", "addon.mod_workshop_assessment_accumulative.dimensiongradefor": "Βαθμός για {{$a}}", @@ -831,7 +1011,7 @@ "assets.countries.PM": "Σαιν-Πιερ και Μικελόν", "assets.countries.PN": "Νήσοι Πίτκαιρν", "assets.countries.PR": "Πουέρτο Ρίκο", - "assets.countries.PS": "Παλαιστίνη", + "assets.countries.PS": "Παλαιστίνη, Κράτος της", "assets.countries.PT": "Πορτογαλία", "assets.countries.PW": "Παλάου", "assets.countries.PY": "Παραγουάη", @@ -898,22 +1078,63 @@ "assets.countries.ZA": "Νότιος Αφρική", "assets.countries.ZM": "Ζάμπια", "assets.countries.ZW": "Ζιμπάμπουε", + "assets.mimetypes.application/epub_zip": "Ηλεκτρονικό βιβλίο EPUB", "assets.mimetypes.application/msword": "Έγγραφο Word", - "assets.mimetypes.application/pdf": "Έγγραφο Acrobat Reader PDF", - "assets.mimetypes.application/vnd.ms-excel": "Έγγραφο Λογιστικού Φύλλου Excel", - "assets.mimetypes.application/vnd.ms-powerpoint": "Έγγραφο Παρουσίασης Powerpoint", + "assets.mimetypes.application/pdf": "Έγγραφο PDF", + "assets.mimetypes.application/vnd.moodle.backup": "Αντίγραφο ασφαλείας Moodle", + "assets.mimetypes.application/vnd.ms-excel": "Υπολογιστικό φύλλο Excel", + "assets.mimetypes.application/vnd.ms-excel.sheet.macroEnabled.12": "Βιβλίο εργασίας με δυνατότητα μακροεντολών Excel 2007", + "assets.mimetypes.application/vnd.ms-powerpoint": "Παρουσίαση Powerpoint", + "assets.mimetypes.application/vnd.oasis.opendocument.spreadsheet": "Υπολογιστικό φύλλο OpenDocument", + "assets.mimetypes.application/vnd.oasis.opendocument.spreadsheet-template": "Πρότυπο υπολογιστικού φύλλου OpenDocument", + "assets.mimetypes.application/vnd.oasis.opendocument.text": "Έγγραφο κειμένου OpenDocument", + "assets.mimetypes.application/vnd.oasis.opendocument.text-template": "Πρότυπο κειμένου OpenDocument", + "assets.mimetypes.application/vnd.oasis.opendocument.text-web": "Πρότυπο ιστοσελίδας του OpenDocument", + "assets.mimetypes.application/vnd.openxmlformats-officedocument.presentationml.presentation": "Παρουσίαση του Powerpoint 2007", + "assets.mimetypes.application/vnd.openxmlformats-officedocument.presentationml.slideshow": "Προβολή διαφανειών PowerPoint 2007", + "assets.mimetypes.application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "Υπολογιστικό φύλλο Excel 2007", + "assets.mimetypes.application/vnd.openxmlformats-officedocument.spreadsheetml.template": "Πρότυπο του Excel 2007", + "assets.mimetypes.application/vnd.openxmlformats-officedocument.wordprocessingml.document": "Έγγραφο Word 2007", + "assets.mimetypes.application/x-iwork-keynote-sffkey": "iWork Keynote παρουσίαση", + "assets.mimetypes.application/x-iwork-numbers-sffnumbers": "iWork Numbers υπολογιστικό φύλλο", + "assets.mimetypes.application/x-iwork-pages-sffpages": "iWork Pages έγγραφο", + "assets.mimetypes.application/x-javascript": "Πηγαίος κώδικας JavaScript", + "assets.mimetypes.application/x-mspublisher": "Έγγραφο Publisher", + "assets.mimetypes.application/x-shockwave-flash": "Κινούμενα σχέδια Flash", + "assets.mimetypes.application/xhtml_xml": "Έγγραφο XHTML", + "assets.mimetypes.archive": "Αρχειοθήκη ({{$a.EXT}})", + "assets.mimetypes.audio": "Αρχείο ήχου ({{$a.EXT}})", "assets.mimetypes.default": "{{$a.mimetype}}", "assets.mimetypes.document/unknown": "Αρχείο", + "assets.mimetypes.group:archive": "Αρχειοθήκες", + "assets.mimetypes.group:audio": "Αρχεία ήχου", + "assets.mimetypes.group:document": "Αρχεία εγγράφων", "assets.mimetypes.group:html_audio": "Αρχεία ήχου με εγγενή υποστήριξη από τους περιηγητές", + "assets.mimetypes.group:html_track": "Αρχεία κομματιών πολυμέσων HTML", "assets.mimetypes.group:html_video": "Αρχεία βίντεο με εγγενή υποστήριξη από τους περιηγητές", "assets.mimetypes.group:image": "Αρχεία εικόνων", - "assets.mimetypes.group:web_image": "Αρχεία εικόνων που χρησιμοποιούνται στον ιστό", + "assets.mimetypes.group:presentation": "Αρχεία παρουσιάσεων", + "assets.mimetypes.group:sourcecode": "Πηγαίος κώδικας", + "assets.mimetypes.group:spreadsheet": "Αρχεία υπολογιστικών φύλλων", + "assets.mimetypes.group:video": "Αρχεία βίντεο", + "assets.mimetypes.group:web_audio": "Αρχεία ήχου Ιστού", + "assets.mimetypes.group:web_file": "Αρχεία Ιστού", + "assets.mimetypes.group:web_image": "Αρχεία εικόνων Ιστού", + "assets.mimetypes.group:web_video": "Αρχεία βίντεο Ιστού", "assets.mimetypes.image": "Εικόνα ({{$a.MIMETYPE2}})", + "assets.mimetypes.image/vnd.microsoft.icon": "Εικονίδιο των Windows", + "assets.mimetypes.text/css": "Cascading Style-Sheet αρχεία στυλ", + "assets.mimetypes.text/csv": "Τιμές διαχωρισμένες με κόμμα (csv)", + "assets.mimetypes.text/html": "Έγγραφο HTML", "assets.mimetypes.text/plain": "Αρχείο κειμένου", - "assets.mimetypes.text/rtf": "Αρχείο Εμπλουτισμένου Κειμένου RTF", + "assets.mimetypes.text/rtf": "Έγγραφο RTF (Αρχείο Εμπλουτισμένου Κειμένου)", + "assets.mimetypes.text/vtt": "Αρχείο υπότιτλων βίντεο Ιστού", + "assets.mimetypes.video": "Αρχείο βίντεο ({{$a.EXT}})", "core.accounts": "Λογαριασμοί χρηστών", "core.add": "Προσθήκη", "core.agelocationverification": "Επαλήθευση ηλικίας και τοποθεσίας", + "core.ago": "πριν {{$a}}", + "core.all": "Όλα", "core.allparticipants": "Όλοι οι συμμετέχοντες", "core.android": "Android", "core.answer": "Απάντηση", @@ -947,7 +1168,7 @@ "core.confirmdeletefile": "Είστε σίγουροι ότι θέλετε να διαγράψετε αυτό το αρχείο;", "core.confirmloss": "Είστε σίγουροι? Όλες οι αλλαγές θα χαθούν.", "core.confirmopeninbrowser": "Θέλετε να το ανοίξετε στο πρόγραμμα περιήγησης;", - "core.considereddigitalminor": "Θεωρείτε ψηφιακά ανήλικος.", + "core.considereddigitalminor": "Είστε πολύ νέος για να δημιουργήσετε λογαριασμό σε αυτόν τον ιστότοπο.", "core.content": "Περιεχόμενο", "core.contenteditingsynced": "Το περιεχόμενο που επεξεργάζεστε έχει συγχρονιστεί.", "core.contentlinks.chooseaccount": "Επιλέξτε λογαριασμό", @@ -980,12 +1201,12 @@ "core.course.sections": "Τομείς", "core.course.useactivityonbrowser": "", "core.coursedetails": "Λεπτομέρειες μαθήματος", + "core.courses.addtofavourites": "Επισήμανση του μαθήματος με αστερίσκο", "core.courses.allowguests": "Σε αυτό το μάθημα επιτρέπονται και οι επισκέπτες", "core.courses.availablecourses": "Διαθέσιμα μαθήματα", "core.courses.cannotretrievemorecategories": "Δεν είναι δυνατή η ανάκτηση κατηγοριών μετά από το επίπεδο {{$a}}.", "core.courses.categories": "Κατηγορίες μαθημάτων", "core.courses.confirmselfenrol": "Είστε σίγουροι ότι θέλετε να εγγραφείτε σε αυτό το μάθημα;", - "core.courses.courseoverview": "Επισκόπηση μαθημάτων", "core.courses.courses": "Μαθήματα", "core.courses.enrolme": "Εγγραφή", "core.courses.errorloadcategories": "Παρουσιάστηκε σφάλμα κατά την φόρτωση των κατηγοριών.", @@ -994,27 +1215,24 @@ "core.courses.errorselfenrol": "Παρουσιάστηκε σφάλμα κατά τη διάρκεια της αυτο-εγγραφής.", "core.courses.filtermycourses": "Φιλτράρισμα των μαθημάτων μου", "core.courses.frontpage": "Αρχική σελίδα", - "core.courses.future": "Μελλοντικά", - "core.courses.inprogress": "Σε εξέλιξη", - "core.courses.morecourses": "Περισσότερα μαθήματα", + "core.courses.hidecourse": "Απόκρυψη από την προβολή", "core.courses.mycourses": "Τα μαθήματά μου", + "core.courses.mymoodle": "Ταμπλό", "core.courses.nocourses": "Δεν υπάρχει πληροφορία του μαθήματος για προβολή.", - "core.courses.nocoursesfuture": "Κανένα μελλοντικό μάθημα", - "core.courses.nocoursesinprogress": "Κανένα μάθημα σε εξέλιξη", - "core.courses.nocoursesoverview": "Κανένα μάθημα", - "core.courses.nocoursespast": "Κανένα προηγούμενο μάθημα", "core.courses.nocoursesyet": "Δεν υπάρχουν μαθήματα σε αυτή την κατηγορία", + "core.courses.nosearchresults": "Χωρίς αποτέλεσμα", "core.courses.notenroled": "Δεν είσαι εγγεγραμμένος σε αυτό το μάθημα", "core.courses.notenrollable": "Δεν μπορείτε να αυτο-εγγραφείτε σε αυτό το μάθημα.", "core.courses.password": "Κλειδί εγγραφής", - "core.courses.past": "Προηγούμενα", "core.courses.paymentrequired": "Αυτό το μάθημα απαιτεί πληρωμή για την είσοδο.", "core.courses.paypalaccepted": "Αποδεκτές οι πληρωμές μέσω PayPal", + "core.courses.removefromfavourites": "Αφαίρεση της επισήμανσης με αστερίσκο από το μάθημα", "core.courses.search": "Αναζήτηση", "core.courses.searchcourses": "Αναζήτηση μαθημάτων", "core.courses.searchcoursesadvice": "Μπορείτε να χρησιμοποιήσετε το κουμπί Αναζήτηση μαθημάτων για πρόσβαση ως επισκέπτης ή για να αυτο-εγγραφείτε σε μαθήματα που το επιτρέπουν.", "core.courses.selfenrolment": "Αυτο-εγγραφή", "core.courses.sendpaymentbutton": "Αποστολή πληρωμής με Paypal", + "core.courses.show": "Εμφάνιση αυτού του μαθήματος", "core.courses.totalcoursesearchresults": "Συνολικά μαθήματα: {{$a}}", "core.currentdevice": "Τρέχουσα συσκευή", "core.datastoredoffline": "Τα δεδομένα αποθηκεύονται στη συσκευή, διότι δεν μπορούν να σταλούν. Θα αποσταλούν αυτόματα αργότερα.", @@ -1032,7 +1250,7 @@ "core.dfmediumdate": "LLL", "core.dftimedate": "h[:]mm A", "core.digitalminor": "Ψηφιακά ανήλικος", - "core.digitalminor_desc": "Για να δημιουργήσετε έναν λογαριασμό σε αυτόν τον ιστότοπο, πρέπει να επικοινωνήσει ο γονέας/κηδεμόνας σας με το παρακάτω άτομο.", + "core.digitalminor_desc": "Παρακαλούμε, ζητήστε από τον γονέα/κηδεμόνα σας να επικοινωνήσει με:", "core.discard": "Απόρριψη", "core.dismiss": "Απόρριψη", "core.done": "Ολοκληρώθηκε", @@ -1056,6 +1274,7 @@ "core.errorsync": "Παρουσιάστηκε σφάλμα κατά τη διαδικασία του συγχρονισμού. Παρακαλώ δοκιμάστε ξανά.", "core.errorsyncblocked": "Αυτό το {{$a}} δεν μπορεί να συγχρονιστεί τώρα εξαιτίας μιας συνεχιζόμενης διαδικασίας. Παρακαλώ δοκιμάστε ξανά αργότερα. Εάν το πρόβλημα παραμένει, δοκιμάστε να επανεκκινήσετε την εφαρμογή.", "core.explanationdigitalminor": "Αυτές οι πληροφορίες είναι απαραίτητες για να καθοριστεί αν η ηλικία σας είναι πάνω από την ηλικία ψηφιακής συναίνεσης. Αυτή είναι η ηλικία κατά την οποία ένα άτομο μπορεί να συναινέσει στους όρους και τις προϋποθέσεις και στην νόμιμη αποθήκευση και επεξεργασία των δεδομένων του.", + "core.favourites": "Με αστερίσκο", "core.filename": "Όνομα αρχείου", "core.filenameexist": "Το όνομα του αρχείου υπάρχει ήδη: {{$a}}", "core.fileuploader.addfiletext": "Προσθήκη αρχείου", @@ -1095,9 +1314,9 @@ "core.grades.grade": "Βαθμός", "core.grades.gradeitem": "Στοιχείο βαθμού", "core.grades.grades": "Βαθμοί", - "core.grades.lettergrade": "Γράμμα για βαθμό", + "core.grades.lettergrade": "Βαθμός με γράμμα", "core.grades.nogradesreturned": "Δεν επιστράφηκε κανένας βαθμός", - "core.grades.nooutcome": "Χωρίς μαθ. αποτελέσματα", + "core.grades.nooutcome": "Χωρίς μαθησιακά αποτελέσματα", "core.grades.percentage": "Ποσοστό", "core.grades.range": "Εύρος", "core.grades.rank": "Κατάταξη", @@ -1114,6 +1333,7 @@ "core.imageviewer": "Εργαλείο προβολής εικόνων", "core.info": "Πληροφορίες", "core.ios": "iOS", + "core.labelsep": ":", "core.lastaccess": "Τελευταία πρόσβαση", "core.lastmodified": "Τελευταία τροποποίηση", "core.lastsync": "Τελευταίος συγχρονισμός", @@ -1123,7 +1343,7 @@ "core.loading": "Φόρτωση", "core.loadmore": "Φόρτωση περισσότερων", "core.location": "Τόπος", - "core.login.auth_email": "Αυτο-εγγραφή βασισμένη στο ηλ. ταχυδρομείο.", + "core.login.auth_email": "Αυτο-εγγραφή βασισμένη στο ηλεκτρονικό ταχυδρομείο.", "core.login.authenticating": "Έλεγχος ταυτότητας", "core.login.cancel": "Ακύρωση", "core.login.checksiteversion": "Ελέγξτε ποια έκδοση Moodle χρησιμοποιεί το site, Moodle 2.4 ή μετέπειτα έκδοση.", @@ -1135,7 +1355,8 @@ "core.login.createaccount": "Δημιουργία του λογαριασμού μου", "core.login.createuserandpass": "Δημιουργία ενός νέου ονόματος χρήστη και κωδικού πρόσβασης για σύνδεση στον δικτυακό τόπο", "core.login.credentialsdescription": "Δώστε το όνομα χρήστη και τον κωδικό πρόσβασής σας για να συνδεθείτε.", - "core.login.emailconfirmsent": "

Ένα email έχει σταλεί στη διεύθυνση σας {{$a}}

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

Εάν συνεχίσετε να έχετε δυσκολίες επικοινωνήστε με το διαχειριστή του site.

", + "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", @@ -1148,7 +1369,7 @@ "core.login.helpmelogin": "

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

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

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

", "core.login.instructions": "Οδηγίες", "core.login.invalidaccount": "Ελέγξτε τα στοιχεία σύνδεσης ή ζητήστε από τον διαχειριστή του ιστότοπού σας να ελέγξει τη διαμόρφωση του ιστότοπου.", - "core.login.invaliddate": "Η ημερομηνία δεν είναι σωστή", + "core.login.invaliddate": "Μη έγκυρη ημερομηνία", "core.login.invalidemail": "Εσφαλμένη διεύθυνση ηλεκτρονικού ταχυδρομείου", "core.login.invalidmoodleversion": "Μη έγκυρη έκδοση Moodle. Η ελάχιστη απαιτούμενη έκδοση είναι η 2.4.", "core.login.invalidsite": "Η διεύθυνση ιστότοπου δεν είναι έγκυρη.", @@ -1166,6 +1387,7 @@ "core.login.missingfirstname": "Λείπει το όνομα", "core.login.missinglastname": "Λείπει το επώνυμο", "core.login.mobileservicesnotenabled": "Οι υπηρεσίες κινητής τηλεφωνίας δεν είναι ενεργοποιημένες στον ιστότοπό σας. Παρακαλούμε, επικοινωνήστε με τον διαχειριστή του ιστότοπου εάν πιστεύετε ότι η πρόσβαση στο κινητό θα πρέπει να είναι ενεργοποιημένη.", + "core.login.mustconfirm": "Πρέπει να επιβεβαιώσετε τον λογαριασμό σας", "core.login.newaccount": "Νέος λογαριασμός", "core.login.newsitedescription": "Καταχωρίστε τη διεύθυνση URL του ιστότοπού σας Moodle. Σημειώστε ότι ενδέχεται να μην έχει ρυθμιστεί να λειτουργεί με αυτήν την εφαρμογή.", "core.login.notloggedin": "Πρέπει να συνδεθείτε", @@ -1185,6 +1407,7 @@ "core.login.reconnect": "Επανασύνδεση", "core.login.reconnectdescription": "Η σύνδεση με το λογαριασμό σας δεν είναι έγκυρη ή έχει λήξει, πρέπει να επανασυνδεθείτε με τον ιστότοπο.", "core.login.reconnectssodescription": "Η σύνδεση με το λογαριασμό σας δεν είναι έγκυρη ή έχει λήξει, πρέπει να επανασυνδεθείτε με τον ιστότοπο. Θα πρέπει να συνδεθείτε στον ιστότοπο σε ένα παράθυρο του προγράμματος περιήγησης.", + "core.login.resendemail": "Επαναποστολή μηνύματος ηλεκτρονικού ταχυδρομείου", "core.login.security_question": "Ερώτηση ασφαλείας", "core.login.selectacountry": "Επιλέξτε μια χώρα", "core.login.signupplugindisabled": "{{$a}} δεν έχει ενεργοποιηθεί.", @@ -1199,7 +1422,7 @@ "core.login.username": "Όνομα χρήστη", "core.login.usernameoremail": "Εισαγάγετε όνομα χρήστη ή διεύθυνση email", "core.login.usernamerequired": "Το όνομα χρήστη είναι απαραίτητο", - "core.login.usernotaddederror": "Ο χρήστης \"{{$a}}\" δεν προστέθηκε - άγνωστο σφάλμα", + "core.login.usernotaddederror": "Ο χρήστης δεν προστέθηκε - άγνωστο σφάλμα", "core.login.visitchangepassword": "Θέλετε να επισκεφτείτε τον ιστότοπο για να αλλάξετε τον κωδικό πρόσβασης;", "core.login.webservicesnotenabled": "Οι υπηρεσίες Web δεν είναι ενεργοποιημένες στον ιστότοπό σας. Παρακαλούμε, επικοινωνήστε με τον διαχειριστή του δικτυακού σας τόπου για το Moodle εάν πιστεύετε ότι η πρόσβαση στο κινητό θα πρέπει να είναι ενεργοποιημένη.", "core.lostconnection": "Η σύνδεσή σας είναι άκυρη ή έχει λήξει. Πρέπει να ξανασυνδεθείτε στο site.", @@ -1207,14 +1430,12 @@ "core.mainmenu.changesite": "Αλλαγή ιστότοπου", "core.mainmenu.help": "Βοήθεια", "core.mainmenu.logout": "Αποσύνδεση", - "core.mainmenu.mycourses": "Τα μαθήματά μου", - "core.mainmenu.togglemenu": "Αλλαγή μενού", "core.mainmenu.website": "Ιστότοπος", "core.maxsizeandattachments": "Μέγιστο μέγεθος για νέα αρχεία: {{$a.size}}, μέγιστος αριθμός συνημμένων: {{$a.attachments}}", "core.min": "λεπτό", "core.mins": "λεπτά", "core.mod_assign": "Εργασία", - "core.mod_assignment": "Εργασία 2.2 (Απενεργοποιημένη)", + "core.mod_assignment": "Ανάθεση (εργασίας) 2.2 (Απενεργοποιημένο)", "core.mod_book": "Βιβλίο", "core.mod_chat": "Συνομιλία", "core.mod_choice": "Επιλογή", @@ -1226,8 +1447,8 @@ "core.mod_folder": "Φάκελος", "core.mod_forum": "Φόρουμ", "core.mod_glossary": "Γλωσσάριο", - "core.mod_ims": "IMS πακέτο περιοχομένου", - "core.mod_imscp": "IMS πακέτο περιοχομένου", + "core.mod_ims": "Πακέτο περιοχομένου IMS", + "core.mod_imscp": "Πακέτο περιοχομένου IMS", "core.mod_label": "Ταμπέλα", "core.mod_lesson": "Διδακτική ενότητα", "core.mod_lti": "Εξωτερικό εργαλείο", @@ -1236,10 +1457,12 @@ "core.mod_resource": "Αρχείο", "core.mod_scorm": "πακέτο SCORM", "core.mod_survey": "Έρευνα", + "core.mod_url": "Διεύθυνση URL", "core.mod_wiki": "Wiki", "core.mod_workshop": "Εργαστήριο", "core.moduleintro": "Περιγραφή", "core.more": "περισσότερα", + "core.mygroups": "Οι ομάδες μου", "core.name": "Όνομα", "core.networkerrormsg": "Το δίκτυο δεν είναι ενεργοποιημένο ή δεν δουλεύει.", "core.never": "Ποτέ", @@ -1247,7 +1470,7 @@ "core.no": "Όχι", "core.nocomments": "Χωρίς σχόλια", "core.nograde": "Δεν υπάρχει βαθμός", - "core.none": "Κανένας", + "core.none": "Καμία", "core.nopasswordchangeforced": "Δεν μπορείτε να προχωρήσετε χωρίς να αλλάξετε τον κωδικό πρόσβασής σας.", "core.nopermissions": "Συγγνώμη, αλλά αυτή τη στιγμή δεν έχετε τα δικαιώματα για να το κάνετε αυτό ({{$a}})", "core.noresults": "Κανένα αποτέλεσμα", @@ -1256,11 +1479,12 @@ "core.notsent": "Δεν εστάλη", "core.now": "τώρα", "core.numwords": "{{$a}} λέξεις", - "core.offline": "Εκτός σύνδεσης", + "core.offline": "Χωρίς σύνδεση", "core.ok": "ΟΚ", - "core.online": "Σε απευθείας σύνδεση", + "core.online": "Με σύνδεση", "core.openfullimage": "Πατήστε εδώ για να δείτε την εικόνα σε πλήρες μέγεθος", "core.openinbrowser": "Ανοίξτε στον περιηγητή.", + "core.othergroups": "Άλλες ομάδες", "core.pagea": "Σελίδα {{$a}}", "core.paymentinstant": "Χρησιμοποιήστε το παρακάτω πλήκτρο για να πληρώσετε και να εγγραφείτε μέσα σε λίγα λεπτά!", "core.percentagenumber": "{{$a}}%", @@ -1271,6 +1495,7 @@ "core.pulltorefresh": "Τραβήξτε προς τα κάτω για ανανέωση", "core.question.answer": "Απάντηση", "core.question.answersaved": "Η απάντηση αποθηκεύτηκε", + "core.question.certainty": "Βεβαιότητα", "core.question.complete": "Ολοκλήρωση", "core.question.correct": "Σωστό", "core.question.errorattachmentsnotsupported": "Η εφαρμογή δεν υποστηρίζει ακόμα την προσάρτηση αρχείων σε απαντήσεις.", @@ -1278,16 +1503,22 @@ "core.question.errorquestionnotsupported": "Αυτός ο τύπος ερωτήματος δεν υποστηρίζεται από την εφαρμογή: {{$a}}.", "core.question.feedback": "Ανατροφοδότηση", "core.question.howtodraganddrop": "Πατήστε για να επιλέξετε και στη συνέχεια, πατήστε για να αφήσετε.", + "core.question.incorrect": "Λανθασμένη", + "core.question.information": "Πληροφορίες", "core.question.invalidanswer": "Ημιτελής απάντηση", "core.question.notanswered": "Δεν απαντήθηκε", "core.question.notyetanswered": "Δεν έχει απαντηθεί ακόμα", + "core.question.partiallycorrect": "Μερικώς σωστή", "core.question.questionmessage": "Ερώτηση {{$a}}: {{$b}}", "core.question.questionno": "Ερώτηση {{$a}}", + "core.question.requiresgrading": "Απαιτεί βαθμολόγηση", "core.quotausage": "Χρησιμοποιείτε αυτή τη στιγμή {{$a.used}} από το συνολικό όριο {{$a.total}}.", "core.redirectingtosite": "Θα μεταφερθείτε στο site.", "core.refresh": "Ανανέωση", + "core.remove": "Αφαίρεση", "core.required": "Απαιτείται", "core.requireduserdatamissing": "Αυτός ο χρήστης δεν έχει συμπληρωμένα κάποια απαιτούμενα στοιχεία του προφίλ του. Συμπληρώστε τα δεδομένα στο Moodle σας και προσπαθήστε ξανά.
{{$a}}", + "core.resources": "Πόροι", "core.restore": "Επαναφορά", "core.retry": "Προσπαθήστε ξανά", "core.save": "Αποθήκευση", @@ -1317,6 +1548,7 @@ "core.settings.deviceos": "Λειτουργικό σύστημα συσκευής", "core.settings.devicewebworkers": "Υποστηρίζονται οι συσκευές Web Workers", "core.settings.disableall": "Απενεργοποίηση ειδοποιήσεων", + "core.settings.disabled": "Απενεργοποιημένο", "core.settings.displayformat": "Μορφή εμφάνισης", "core.settings.enabledownloadsection": "Ενεργοποιήστε τις ενότητες λήψης", "core.settings.enablerichtexteditor": "Ενεργοποιήστε τον επεξεργαστή εμπλουτισμένου κειμένου", @@ -1332,8 +1564,8 @@ "core.settings.localnotifavailable": "Διαθέσιμες τοπικές ειδοποιήσεις", "core.settings.locationhref": "Webview URL", "core.settings.locked": "Κλειδωμένο", - "core.settings.loggedin": "Σε απευθείας σύνδεση", - "core.settings.loggedoff": "Εκτός σύνδεσης", + "core.settings.loggedin": "Με σύνδεση", + "core.settings.loggedoff": "Χωρίς σύνδεση", "core.settings.navigatorlanguage": "Γλώσσα πλοήγησης", "core.settings.navigatoruseragent": "Navigator userAgent", "core.settings.networkstatus": "Κατάσταση σύνδεσης στο Internet", @@ -1371,6 +1603,21 @@ "core.sizetb": "TB", "core.sorry": "Λυπάμαι...", "core.sortby": "Ταξινόμηση κατά", + "core.strftimedate": "%d %B %Y", + "core.strftimedatefullshort": "%d/%m/%y", + "core.strftimedateshort": "%d %B", + "core.strftimedatetime": "%d %B %Y, %I:%M %p", + "core.strftimedatetimeshort": "%d/%m/%y, %H:%M", + "core.strftimedaydate": "%A, %d %B %Y", + "core.strftimedaydatetime": "%A, %d %B %Y, %I:%M %p", + "core.strftimedayshort": "%A, %d %B", + "core.strftimedaytime": "%a, %H:%M", + "core.strftimemonthyear": "%B %Y", + "core.strftimerecent": "%d %b, %H:%M", + "core.strftimerecentfull": "%a, %d %b %Y, %I:%M %p", + "core.strftimetime": "%I:%M %p", + "core.strftimetime12": "%I:%M %p", + "core.strftimetime24": "%H:%M", "core.submit": "Υποβολή", "core.success": "Επιτυχία", "core.tablet": "Tablet", @@ -1412,10 +1659,11 @@ "core.user.roles": "Ρόλοι", "core.user.sendemail": "Email", "core.user.student": "Μαθητής", - "core.user.teacher": "Διδάσκων χωρίς δικαιώματα αλλαγής", + "core.user.teacher": "Περιορισμένος διδάσκων", "core.user.webpage": "Ιστοσελίδα", "core.userdeleted": "Αυτός ο λογαριασμός χρήστη έχει διαγραφεί", "core.userdetails": "Λεπτομέρειες χρήστη", + "core.usernotfullysetup": "Ο χρήστης δεν ρυθμίστηκε πλήρως", "core.users": "Χρήστες", "core.view": "Προβολή", "core.viewprofile": "Επισκόπηση του προφίλ", @@ -1427,7 +1675,7 @@ "core.whyisthisrequired": "Γιατί αυτό απαιτείται;", "core.windowsphone": "Windows Phone", "core.wsfunctionnotavailable": "Η λειτουργία διαδικτύου δεν είναι διαθέσιμη.", - "core.year": "χρονιά", - "core.years": "χρόνια", + "core.year": "έτος", + "core.years": "έτη", "core.yes": "Ναι" } \ No newline at end of file diff --git a/src/assets/lang/en-us.json b/src/assets/lang/en-us.json index 139a6c7d4..b9522e16b 100644 --- a/src/assets/lang/en-us.json +++ b/src/assets/lang/en-us.json @@ -1,4 +1,5 @@ { + "addon.block_sitemainmenu.pluginname": "Main menu", "addon.mod_assign.markingworkflowstate": "Grading workflow state", "addon.mod_assign.markingworkflowstateinmarking": "In grading", "addon.mod_assign.markingworkflowstatenotmarked": "Not graded", @@ -11,6 +12,19 @@ "core.login.loginsteps": "For full access to this site, you first need to create an account.", "core.paymentinstant": "Use the button below to pay and be enrolled within minutes!", "core.settings.license": "License", + "core.strftimedate": "%B %d, %Y", + "core.strftimedatefullshort": "%m/%d/%y", + "core.strftimedateshort": "%B %d", + "core.strftimedatetime": "%B %d %Y, %I:%M %p", + "core.strftimedatetimeshort": "%m/%d/%y, %H:%M", + "core.strftimedaydate": "%A, %B %d, %Y", + "core.strftimedaydatetime": "%A, %B %d, %Y, %I:%M %p", + "core.strftimedayshort": "%A, %B %d", + "core.strftimedaytime": "%a, %H:%M", + "core.strftimemonthyear": "%B %Y", + "core.strftimerecent": "%I:%M %p, %b %d", + "core.strftimerecentfull": "%a, %b %d, %Y, %I:%M %p", + "core.strftimetime": "%I:%M %p", "core.thisdirection": "ltr", "core.user.lastname": "Last name" } \ No newline at end of file diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index d44abe8ad..58ab6beb3 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -37,7 +37,7 @@ "addon.block_myoverview.nocourses": "No courses", "addon.block_myoverview.past": "Past", "addon.block_myoverview.pluginname": "Course overview", - "addon.block_myoverview.title": "Title", + "addon.block_myoverview.title": "Course name", "addon.block_recentlyaccessedcourses.nocourses": "No recent courses", "addon.block_recentlyaccessedcourses.pluginname": "Recently accessed courses", "addon.block_recentlyaccesseditems.noitems": "No recent items", @@ -50,7 +50,7 @@ "addon.block_timeline.next3months": "Next 3 months", "addon.block_timeline.next6months": "Next 6 months", "addon.block_timeline.next7days": "Next 7 days", - "addon.block_timeline.nocoursesinprogress": "No in progress courses", + "addon.block_timeline.nocoursesinprogress": "No in-progress courses", "addon.block_timeline.noevents": "No upcoming activities due", "addon.block_timeline.overdue": "Overdue", "addon.block_timeline.pluginname": "Timeline", @@ -186,7 +186,7 @@ "addon.messages.newmessage": "New message", "addon.messages.newmessages": "New messages", "addon.messages.nocontactrequests": "No contact requests", - "addon.messages.nocontactsgetstarted": "Try searching for someone to add them as a contact", + "addon.messages.nocontactsgetstarted": "No contacts", "addon.messages.nofavourites": "No starred conversations", "addon.messages.nogroupconversations": "No group conversations", "addon.messages.noindividualconversations": "No private conversations", @@ -205,7 +205,7 @@ "addon.messages.searchnomessagesfound": "No messages found", "addon.messages.searchnononcontactsfound": "No non contacts found", "addon.messages.sendcontactrequest": "Send contact request", - "addon.messages.showdeletemessages": "Show delete options", + "addon.messages.showdeletemessages": "Show delete messages", "addon.messages.type_blocked": "Blocked", "addon.messages.type_offline": "Offline", "addon.messages.type_online": "Online", @@ -818,8 +818,8 @@ "addon.mod_workshop.switchphase50": "Close workshop", "addon.mod_workshop.userplan": "Workshop planner", "addon.mod_workshop.userplancurrentphase": "Current phase", - "addon.mod_workshop.warningassessmentmodified": "The assessment was modified on the site.", - "addon.mod_workshop.warningsubmissionmodified": "The submission was modified on the site.", + "addon.mod_workshop.warningassessmentmodified": "The submission was modified on the site.", + "addon.mod_workshop.warningsubmissionmodified": "The assessment was modified on the site.", "addon.mod_workshop.weightinfo": "Weight: {{$a}}", "addon.mod_workshop.yourassessment": "Your assessment", "addon.mod_workshop.yourassessmentfor": "Your assessment for {{$a}}", @@ -1166,7 +1166,7 @@ "core.areyousure": "Are you sure?", "core.back": "Back", "core.cancel": "Cancel", - "core.cannotconnect": "Cannot connect: Verify that you have typed the URL correctly.", + "core.cannotconnect": "Cannot connect: Verify that you have correctly typed the URL and that your site uses Moodle 2.4 or later.", "core.cannotdownloadfiles": "File downloading is disabled. Please contact your site administrator.", "core.captureaudio": "Record audio", "core.capturedimage": "Taken picture.", @@ -1196,7 +1196,7 @@ "core.confirmdeletefile": "Are you sure you want to delete this file?", "core.confirmloss": "Are you sure? All changes will be lost.", "core.confirmopeninbrowser": "Do you want to open it in a web browser?", - "core.considereddigitalminor": "You are considered to be a digital minor.", + "core.considereddigitalminor": "You are too young to create an account on this site.", "core.content": "Content", "core.contenteditingsynced": "The content you are editing has been synced.", "core.contentlinks.chooseaccount": "Choose account", @@ -1288,7 +1288,7 @@ "core.dfmediumdate": "LLL", "core.dftimedate": "h[:]mm A", "core.digitalminor": "Digital minor", - "core.digitalminor_desc": "To create an account on this site please have your parent/guardian contact the following person.", + "core.digitalminor_desc": "Please ask your parent/guardian to contact:", "core.discard": "Discard", "core.dismiss": "Dismiss", "core.done": "Done", @@ -1313,7 +1313,7 @@ "core.errorsync": "An error occurred while synchronising. Please try again.", "core.errorsyncblocked": "This {{$a}} cannot be synchronised right now because of an ongoing process. Please try again later. If the problem persists, try restarting the app.", "core.explanationdigitalminor": "This information is required to determine if your age is over the digital age of consent. This is the age when an individual can consent to terms and conditions and their data being legally stored and processed.", - "core.favourites": "Favourites", + "core.favourites": "Starred", "core.filename": "Filename", "core.filenameexist": "File name already exists: {{$a}}", "core.fileuploader.addfiletext": "Add file", @@ -1462,7 +1462,7 @@ "core.login.selectsite": "Please select your site:", "core.login.signupplugindisabled": "{{$a}} is not enabled.", "core.login.siteaddress": "Site address", - "core.login.sitehasredirect": "Your site contains at least one HTTP redirect. The app cannot follow redirects and so cannot connect to your site.", + "core.login.sitehasredirect": "Your site contains at least one HTTP redirect. The app cannot follow redirects, this could be the issue that's preventing the app from connecting to your site.", "core.login.siteinmaintenance": "Your site is in maintenance mode", "core.login.sitepolicynotagreederror": "Site policy not agreed.", "core.login.siteurl": "Site URL", @@ -1524,8 +1524,8 @@ "core.nograde": "No grade", "core.none": "None", "core.nopasswordchangeforced": "You cannot proceed without changing your password.", - "core.nopermissionerror": "Sorry, but you do not currently have permissions to do that.", - "core.nopermissions": "Sorry, but you do not currently have permissions to do that ({{$a}})", + "core.nopermissionerror": "Sorry, but you do not currently have permissions to do that", + "core.nopermissions": "Sorry, but you do not currently have permissions to do that ({{$a}}).", "core.noresults": "No results", "core.notapplicable": "n/a", "core.notice": "Notice", @@ -1598,7 +1598,7 @@ "core.settings.cordovaversion": "Cordova version", "core.settings.currentlanguage": "Current language", "core.settings.debugdisplay": "Display debug messages", - "core.settings.debugdisplaydescription": "If enabled, additional information about errors will be displayed.", + "core.settings.debugdisplaydescription": "If enabled, error modals will display more data about the error if possible.", "core.settings.deletesitefiles": "Are you sure that you want to delete the downloaded files from the site '{{sitename}}'?", "core.settings.deletesitefilestitle": "Delete site files", "core.settings.deviceinfo": "Device info", diff --git a/src/assets/lang/es-mx.json b/src/assets/lang/es-mx.json index f90533da0..990808e9d 100644 --- a/src/assets/lang/es-mx.json +++ b/src/assets/lang/es-mx.json @@ -1,15 +1,61 @@ { + "addon.badges.alignment": "Competencia", "addon.badges.badgedetails": "Detalles de insignia", "addon.badges.badges": "Insignias", + "addon.badges.bendorsement": "Aprobación (idoneidad)", + "addon.badges.claimcomment": "Comentario a la Aprobación (Idoneidad)", + "addon.badges.claimid": "URL de Pretensión", "addon.badges.contact": "Contacto", "addon.badges.dateawarded": "Fecha de emisión", "addon.badges.expired": "Caducada", "addon.badges.expirydate": "Fecha de caducidad", + "addon.badges.imageauthoremail": "Email del autor de la imagen", + "addon.badges.imageauthorname": "Nombre del autor de la imagen", + "addon.badges.imageauthorurl": "URL del autor de la imagen", + "addon.badges.imagecaption": "Letrerito de la imagen", "addon.badges.issuancedetails": "Caducidad de insignia", "addon.badges.issuerdetails": "Detalles del emisor", + "addon.badges.issueremail": "Email", "addon.badges.issuername": "Nombre del emisor", + "addon.badges.issuerurl": "URL del emisor", + "addon.badges.language": "Idioma", + "addon.badges.noalignment": "La insignia no tiene ninguna competencia especificada.", "addon.badges.nobadges": "No hay insignias disponibles.", + "addon.badges.norelated": "La insignia no tiene ninguna insignia relacionada.", "addon.badges.recipientdetails": "Detalles de receptores", + "addon.badges.relatedbages": "Insignias relacionadas", + "addon.badges.version": "Versión", + "addon.badges.warnexpired": "(Esta insignia ha caducado)", + "addon.block_activitymodules.pluginname": "Actividades", + "addon.block_myoverview.all": "Todos", + "addon.block_myoverview.favourites": "Destacados", + "addon.block_myoverview.future": "Futuros", + "addon.block_myoverview.hiddencourses": "Ocultos", + "addon.block_myoverview.inprogress": "En progreso", + "addon.block_myoverview.lastaccessed": "Último accedido", + "addon.block_myoverview.morecourses": "Más cursos", + "addon.block_myoverview.nocourses": "Ningún curso", + "addon.block_myoverview.past": "Pasados", + "addon.block_myoverview.pluginname": "Vista general del curso", + "addon.block_myoverview.title": "Nombre del curso", + "addon.block_recentlyaccessedcourses.nocourses": "Sin cursos recientes", + "addon.block_recentlyaccessedcourses.pluginname": "Cursos accedidos recientemente", + "addon.block_recentlyaccesseditems.noitems": "Sin ítems recientes", + "addon.block_recentlyaccesseditems.pluginname": "Ítems accedidos recientemente", + "addon.block_sitemainmenu.pluginname": "Menú principal", + "addon.block_starredcourses.nocourses": "No hay cursos destacados", + "addon.block_starredcourses.pluginname": "Cursos destacados", + "addon.block_timeline.duedate": "Fecha esperada", + "addon.block_timeline.next30days": "Próximos 30 días", + "addon.block_timeline.next3months": "Próximos 3 meses", + "addon.block_timeline.next6months": "Próximos 6 meses", + "addon.block_timeline.next7days": "Próximos 7 días", + "addon.block_timeline.nocoursesinprogress": "Sin cursos en progreso", + "addon.block_timeline.noevents": "Sin actividades próximas pendientes", + "addon.block_timeline.overdue": "Atrasados", + "addon.block_timeline.pluginname": "Línea de tiempo", + "addon.block_timeline.sortbycourses": "Ordenar por cursos", + "addon.block_timeline.sortbydates": "Ordenar por fechas", "addon.calendar.calendar": "Calendario", "addon.calendar.calendarevents": "Eventos del calendario", "addon.calendar.defaultnotificationtime": "Hora de notificación por defecto", @@ -76,23 +122,23 @@ "addon.competency.xcompetenciesproficientoutofyincourse": "Usted es capaz/perito/experto en {{$a.x}} de un total de {{$a.y}} competencias en este curso.", "addon.coursecompletion.complete": "Completo", "addon.coursecompletion.completecourse": "Completar curso", - "addon.coursecompletion.completed": "Finalizado", + "addon.coursecompletion.completed": "Completado", "addon.coursecompletion.completiondate": "Fecha de finalización", "addon.coursecompletion.completionmenuitem": "Finalización", "addon.coursecompletion.couldnotloadreport": "No pudo cargarse el reporte de finalización del curso. Por favor inténtelo nuevamente más tarde.", "addon.coursecompletion.coursecompletion": "Finalización del curso", "addon.coursecompletion.criteria": "Criterios", "addon.coursecompletion.criteriagroup": "Grupo de criterios", - "addon.coursecompletion.criteriarequiredall": "Todos los criterios que aparecen debajo son necesarios.", - "addon.coursecompletion.criteriarequiredany": "Es necesario uno cualquiera de los criterios que aparecen debajo.", + "addon.coursecompletion.criteriarequiredall": "Son necesarios todos los criterios que aparecen más abajo", + "addon.coursecompletion.criteriarequiredany": "Es necesario cualquiera de los criterios que aparecen más abajo", "addon.coursecompletion.inprogress": "En curso", - "addon.coursecompletion.manualselfcompletion": "Auto finalización manual", - "addon.coursecompletion.notyetstarted": "Todavía no comenzado", + "addon.coursecompletion.manualselfcompletion": "Auto-finalizar manualmente", + "addon.coursecompletion.notyetstarted": "Aún no ha comenzado", "addon.coursecompletion.pending": "Pendiente", "addon.coursecompletion.required": "Obligatorio", - "addon.coursecompletion.requiredcriteria": "Criterios requeridos", + "addon.coursecompletion.requiredcriteria": "Criterios necesarios", "addon.coursecompletion.requirement": "Requisito", - "addon.coursecompletion.status": "Estado", + "addon.coursecompletion.status": "Estatus", "addon.coursecompletion.viewcoursereport": "Ver reporte del curso", "addon.files.couldnotloadfiles": "La lista de archivos no pudo cargarse.", "addon.files.emptyfilelist": "No hay archivos para mostrar.", @@ -101,36 +147,80 @@ "addon.files.privatefiles": "Archivos privados", "addon.files.sitefiles": "Archivos del sitio", "addon.messageoutput_airnotifier.processorsettingsdesc": "Configurar dispositivos", + "addon.messages.acceptandaddcontact": "Aceptar y añadir a contactos", "addon.messages.addcontact": "Añadir contacto", - "addon.messages.blockcontact": "Bloquear contacto", - "addon.messages.blockcontactconfirm": "Usted dejará de recibir mensajes de este contacto.", + "addon.messages.addcontactconfirm": "¿Está seguro de querer añadir a {{$a}} a sus contactos?", + "addon.messages.addtofavourites": "Marcar con estrella", + "addon.messages.addtoyourcontacts": "Añadir a contactos", "addon.messages.blocknoncontacts": "Bloquear mensajes de usuarios que no figuren en mi lista de contactos", + "addon.messages.blockuser": "Bloquear usuario", + "addon.messages.blockuserconfirm": "¿Está seguro de querer bloquear a {{$a}}?", + "addon.messages.contactableprivacy": "Aceptar mensaje de:", + "addon.messages.contactableprivacy_coursemember": "Mis contactos y cualquiera en mis cursos", + "addon.messages.contactableprivacy_onlycontacts": "Solamente mis contactos", + "addon.messages.contactableprivacy_site": "Cualquiera en el sitio", + "addon.messages.contactblocked": "Contacto bloqueado", "addon.messages.contactlistempty": "La lista de contactos está vacía", "addon.messages.contactname": "Nombre del contacto", + "addon.messages.contactrequestsent": "Solicitud de contacto enviada", "addon.messages.contacts": "Contactos", + "addon.messages.decline": "Declinar", + "addon.messages.deleteallconfirm": "¿Está usted seguro de querer eliminar toda esta conversación? Esto no la eliminará para otros participantes de la conversación", + "addon.messages.deleteconversation": "Eliminar conversación", "addon.messages.deletemessage": "Eliminar mensaje", "addon.messages.deletemessageconfirmation": "¿Está seguro de querer eliminar este mensaje? Será eliminado solamente de su historial de mensaje y todavía será visible por el usuario que envió o recibió el mensaje.", "addon.messages.errordeletemessage": "Error al eliminar el mensaje", "addon.messages.errorwhileretrievingcontacts": "Error al recuperar los contactos del servidor.", "addon.messages.errorwhileretrievingdiscussions": "Error al recuperar las discusiones del servidor.", "addon.messages.errorwhileretrievingmessages": "Error al recuperar mensajes del servidor.", + "addon.messages.errorwhileretrievingusers": "Error al recuperar usuarios del servidor.", + "addon.messages.groupconversations": "Grupo", + "addon.messages.groupinfo": "Información del grupo", + "addon.messages.individualconversations": "Privado", + "addon.messages.info": "Información", + "addon.messages.isnotinyourcontacts": "{{$a}} no está en sus contactos", "addon.messages.message": "Mensaje", "addon.messages.messagenotsent": "El mensaje no fue enviado. Por favor inténtelo nuevamente después.", "addon.messages.messagepreferences": "Preferencias de Mensaje", "addon.messages.messages": "Mensajes", "addon.messages.newmessage": "Nuevo mensaje", "addon.messages.newmessages": "Nuevos mensajes", - "addon.messages.nomessages": "No hay mensajes", + "addon.messages.nocontactrequests": "Sin solicitudes de contacto", + "addon.messages.nocontactsgetstarted": "Sin contactos", + "addon.messages.nofavourites": "Sin conversaciones destacadas", + "addon.messages.nogroupconversations": "Sin conversaciones grupales", + "addon.messages.noindividualconversations": "Sin conversaciones privadas", + "addon.messages.nomessagesfound": "No se encontraron mensajes", + "addon.messages.noncontacts": "No-contactos", "addon.messages.nousersfound": "No se encontraron usuarios", + "addon.messages.numparticipants": "{{$a}} participantes", "addon.messages.removecontact": "Eliminar contacto", - "addon.messages.removecontactconfirm": "El contacto será eliminado de su lista de contactos.", + "addon.messages.removecontactconfirm": "¿Está seguro de querer quitar a {{$a}} de sus contactos?", + "addon.messages.removefromfavourites": "Quitar la marca de estrella", + "addon.messages.removefromyourcontacts": "Quitar de los contactos", + "addon.messages.requests": "Solicitudes", + "addon.messages.requirecontacttomessage": "Usted necesita solicitarle a {{$a}} que lo añada a Usted como contacto para poder mensajearle.", + "addon.messages.searchcombined": "Buscar personas y mensajes", + "addon.messages.searchnocontactsfound": "No se encontraron contactos", + "addon.messages.searchnomessagesfound": "No se encontraron mensajes", + "addon.messages.searchnononcontactsfound": "No se encontraron no-contactos", + "addon.messages.sendcontactrequest": "Enviar solicitud de contacto", + "addon.messages.showdeletemessages": "Mostrar mensajes eliminados", "addon.messages.type_blocked": "Bloqueado", "addon.messages.type_offline": "Fuera-de-línea", "addon.messages.type_online": "En-línea", "addon.messages.type_search": "Resultados de la búsqueda", "addon.messages.type_strangers": "Otros", - "addon.messages.unblockcontact": "Desbloquear contacto", + "addon.messages.unabletomessage": "Usted no puede mensajear a este usuario", + "addon.messages.unblockuser": "Des-bloquear usuario", + "addon.messages.unblockuserconfirm": "¿Está seguro de querer des-bloquear a {{$a}}?", + "addon.messages.userwouldliketocontactyou": "{{$a}} quisiera contactarlo a Usted", + "addon.messages.warningconversationmessagenotsent": "No se pudo enviar mensaje(s) a conversación {{conversation}}. {{error}}", "addon.messages.warningmessagenotsent": "No se pudo enviar mensaje(s) al usuario {{user}}. {{error}}", + "addon.messages.wouldliketocontactyou": "Quisiera contactarlo a Usted", + "addon.messages.you": "Usted:", + "addon.messages.youhaveblockeduser": "Usted ha bloqueado a este usuario en el pasado", + "addon.messages.yourcontactrequestpending": "Su solicitud de contacto está pendiente con {{$a}}", "addon.mod_assign.acceptsubmissionstatement": "Por favor acepte la declaratoria de envío.", "addon.mod_assign.addattempt": "Permitir otro intento", "addon.mod_assign.addnewattempt": "Añadir un nuevo intento", @@ -167,7 +257,9 @@ "addon.mod_assign.grade": "Calificación", "addon.mod_assign.graded": "Calificado", "addon.mod_assign.gradedby": "Calificado por", + "addon.mod_assign.gradedfollowupsubmit": "Calificada - envío posterior recibido", "addon.mod_assign.gradedon": "Calificado en", + "addon.mod_assign.gradelocked": "La calificación está bloqueada o anulada en el libro de calificaciones.", "addon.mod_assign.gradenotsynced": "Calificación no sincronizada", "addon.mod_assign.gradeoutof": "Calificación sobre {{$a}}", "addon.mod_assign.gradingstatus": "Estatus de calificación", @@ -182,15 +274,16 @@ "addon.mod_assign.markingworkflowstatereadyforrelease": "Listo para liberar", "addon.mod_assign.markingworkflowstatereadyforreview": "Calificación completada", "addon.mod_assign.markingworkflowstatereleased": "Liberada", + "addon.mod_assign.modulenameplural": "Tareas", "addon.mod_assign.multipleteams": "Miembro de más de un grupo", - "addon.mod_assign.multipleteams_desc": "Esta tarea requiere envío en grupo. Usted es miembro de más de un grupo. Para poder enviar, Usted debe de ser miembro de exactamente un grupo. Por favor, contacte con su profesor para actualizar su membresía de grupo.", + "addon.mod_assign.multipleteams_desc": "Esta tarea requiere envío en grupo. Usted es miembro de más de un grupo. Para poder enviar, Usted debe de ser miembro de exactamente un grupo. Por favor, póngase en contacto con su profesor para actualizar su membresía de grupo.", "addon.mod_assign.noattempt": "Sin intento", "addon.mod_assign.nomoresubmissionsaccepted": "Solamente permitido para participantes que hayan recibido una extensión", "addon.mod_assign.noonlinesubmissions": "Esta tarea no requiere que usted envíe nada de forma online", "addon.mod_assign.nosubmission": "No se ha enviado nada en esta tarea", "addon.mod_assign.notallparticipantsareshown": "Los participantes que no hayan hecho un envío no son mostrados.", "addon.mod_assign.noteam": "No es miembro de ningún grupo", - "addon.mod_assign.noteam_desc": "Esta tarea requiere envío en grupos. Usted no es miembro de ningún grupo, por lo que no puede crear un envío Por favor, contacte con su profesor para ser añadido a un grupo.", + "addon.mod_assign.noteam_desc": "Esta tarea requiere envío en grupos. Usted no es miembro de ningún grupo, por lo que no puede crear un envío Por favor, póngase en contacto con su profesor para ser añadido a un grupo.", "addon.mod_assign.notgraded": "No calificado", "addon.mod_assign.numberofdraftsubmissions": "Borradores", "addon.mod_assign.numberofparticipants": "Participantes", @@ -236,6 +329,7 @@ "addon.mod_assign_submission_file.pluginname": "Envíos de archivo", "addon.mod_assign_submission_onlinetext.pluginname": "Envíos de texto en línea", "addon.mod_book.errorchapter": "Error al leer capítulo del libro.", + "addon.mod_book.modulenameplural": "Libros", "addon.mod_chat.beep": "Bip", "addon.mod_chat.currentusers": "Usuarios", "addon.mod_chat.enterchat": "Entrar a la sala", @@ -248,6 +342,7 @@ "addon.mod_chat.messagebeepsyou": "{{$a}} le acaba de enviar un beep", "addon.mod_chat.messageenter": "{{$a}} entró a la sala", "addon.mod_chat.messageexit": "{{$a}} salió de la sala", + "addon.mod_chat.modulenameplural": "Chats", "addon.mod_chat.mustbeonlinetosendmessages": "Usted debe de estar en-línea para enviar mensajes.", "addon.mod_chat.nomessages": "Aún no hay mensajes", "addon.mod_chat.send": "Enviar", @@ -258,6 +353,7 @@ "addon.mod_choice.errorgetchoice": "Error al obtener datos de elección.", "addon.mod_choice.expired": "Lo sentimos, esta actividad se cerró el {{$a}} y ya no está disponible", "addon.mod_choice.full": "(Lleno)", + "addon.mod_choice.modulenameplural": "Elecciones", "addon.mod_choice.noresultsviewable": "Los resultados no pueden verse en este momento.", "addon.mod_choice.notopenyet": "Lo sentimos, esta actividad no estará disponible hasta {{$a}}", "addon.mod_choice.numberofuser": "Número de respuestas", @@ -295,8 +391,10 @@ "addon.mod_data.errormustsupplyvalue": "Usted debe proporcionar un valor aquí.", "addon.mod_data.expired": "Lo sentimos, esta actividad se cerró el {{$a}} y ya no está disponible", "addon.mod_data.fields": "Campos", + "addon.mod_data.foundrecords": "Registros encontrados: {{$a.num}}/{{$a.max}} (Reset filters)", "addon.mod_data.latlongboth": "Tanto la Latitud como la Longitud son necesarias.", "addon.mod_data.menuchoose": "Seleccionar...", + "addon.mod_data.modulenameplural": "Bases de datos", "addon.mod_data.more": "Más", "addon.mod_data.nomatch": "No se han encontrado entradas", "addon.mod_data.norecords": "No entradas en la base de datos", @@ -328,6 +426,7 @@ "addon.mod_feedback.feedbackopen": "Permitir respuestas de", "addon.mod_feedback.mapcourses": "Asignar retroalimentación a cursos", "addon.mod_feedback.mode": "Modo", + "addon.mod_feedback.modulenameplural": "Retroalimentaciones", "addon.mod_feedback.next_page": "Siguiente página", "addon.mod_feedback.non_anonymous": "Los nombres de los usuarios se mostrarán y registrarán con las respuestas", "addon.mod_feedback.non_anonymous_entries": "Entradas no anónimas ({{$a}})", @@ -348,6 +447,7 @@ "addon.mod_feedback.started": "Comenzado", "addon.mod_feedback.this_feedback_is_already_submitted": "Usted ya ha finalizado esta actividad.", "addon.mod_folder.emptyfilelist": "No hay archivos para mostrar.", + "addon.mod_folder.modulenameplural": "Carpetas (folders)", "addon.mod_forum.addanewdiscussion": "Añadir un nuevo tópico/tema de discusión aquí", "addon.mod_forum.addanewquestion": "Añadir una nueva pregunta", "addon.mod_forum.addanewtopic": "Añadir un nuevo tópico/tema", @@ -370,6 +470,7 @@ "addon.mod_forum.modeflatnewestfirst": "Ordenar desde el más reciente", "addon.mod_forum.modeflatoldestfirst": "Ordenar desde el más antiguo", "addon.mod_forum.modenested": "Mostrar respuestas anidadas", + "addon.mod_forum.modulenameplural": "Foros", "addon.mod_forum.numdiscussions": "{{numdiscussions}} discusiones", "addon.mod_forum.numreplies": "{{numreplies}} respuestas", "addon.mod_forum.posttoforum": "Enviar al foro", @@ -405,9 +506,11 @@ "addon.mod_glossary.fillfields": "Los campos Concepto y Definición son obligatorios.", "addon.mod_glossary.fullmatch": "Sólo enlazar palabras completas", "addon.mod_glossary.linking": "Auto-enlace", + "addon.mod_glossary.modulenameplural": "Glosarios", "addon.mod_glossary.noentriesfound": "No se encontraron entradas.", "addon.mod_glossary.searchquery": "Consulta de búsqueda", "addon.mod_imscp.deploymenterror": "¡Error en paquete de contenidos!", + "addon.mod_imscp.modulenameplural": "Paquetes de contenido IMS", "addon.mod_imscp.showmoduledescription": "Mostrar descripción", "addon.mod_lesson.answer": "Respuesta", "addon.mod_lesson.attempt": "Intento: {{$a}}", @@ -451,6 +554,7 @@ "addon.mod_lesson.lowtime": "Tiempo mínimo", "addon.mod_lesson.maximumnumberofattemptsreached": "Se ha alcanzado el número máximo de intentos. Traslado a la página siguiente", "addon.mod_lesson.modattemptsnoteacher": "La revisión del estudiante sólo está disponible para los estudiantes.", + "addon.mod_lesson.modulenameplural": "Lecciones", "addon.mod_lesson.noanswer": "Una o más preguntas no tienen respuesta dada. Por favor regrese y envíe una respuesta.", "addon.mod_lesson.nolessonattempts": "No se han hecho intentos de esta lección.", "addon.mod_lesson.nolessonattemptsgroup": "No se han hecho intentos por miembros del grupo {{$a}} en esta lección.", @@ -495,7 +599,9 @@ "addon.mod_lti.errorgetlti": "Error al obtener datos del módulo", "addon.mod_lti.errorinvalidlaunchurl": "La URL a lanzar no es válida.", "addon.mod_lti.launchactivity": "Lanzar la actividad", + "addon.mod_lti.modulenameplural": "Herramientas externas", "addon.mod_page.errorwhileloadingthepage": "Error al cargar el contenido de la página.", + "addon.mod_page.modulenameplural": "Páginas", "addon.mod_quiz.attemptfirst": "Primer intento", "addon.mod_quiz.attemptlast": "Último intento", "addon.mod_quiz.attemptnumber": "Intento", @@ -530,6 +636,7 @@ "addon.mod_quiz.grademethod": "Método de calificación", "addon.mod_quiz.gradesofar": "{{$a.method}}: {{$a.mygrade}} / {{$a.quizgrade}}.", "addon.mod_quiz.marks": "Puntos", + "addon.mod_quiz.modulenameplural": "Exámenes", "addon.mod_quiz.mustbesubmittedby": "Este intento debe ser enviado para {{$a}}.", "addon.mod_quiz.noquestions": "Aún no se han agregado preguntas", "addon.mod_quiz.noreviewattempt": "Usted no tiene permiso para revisar este intento.", @@ -540,7 +647,7 @@ "addon.mod_quiz.outofshort": "{{$a.grade}}/{{$a.maxgrade}}", "addon.mod_quiz.overallfeedback": "Retroalimentación global según calificación", "addon.mod_quiz.overdue": "Vencido", - "addon.mod_quiz.overduemustbesubmittedby": "Este intento actualmente está retrasado. Usted ya debería de haberlo enviado. Si Usted pretende que le califiquen este examen, debe de enviarlo a más tardar antes de {{$a}}.Si no lo envía para entonces, no se le contarán puntos de calificación para este intento.", + "addon.mod_quiz.overduemustbesubmittedby": "Este intento actualmente está vencido. Usted ya debería de haberlo enviado. Si Usted pretende que le califiquen este examen, debe de enviarlo a más tardar antes de {{$a}}.Si no lo envía para entonces, no se le contarán puntos de calificación para este intento.", "addon.mod_quiz.preview": "Vista previa", "addon.mod_quiz.previewquiznow": "Previsualizar el examen ahora", "addon.mod_quiz.question": "Pregunta", @@ -560,7 +667,7 @@ "addon.mod_quiz.statefinished": "Terminado", "addon.mod_quiz.statefinisheddetails": "Envió {{$a}}", "addon.mod_quiz.stateinprogress": "En progreso", - "addon.mod_quiz.stateoverdue": "Retrasado", + "addon.mod_quiz.stateoverdue": "Atrasado", "addon.mod_quiz.stateoverduedetails": "Debe ser enviado para {{$a}}", "addon.mod_quiz.status": "Estatus", "addon.mod_quiz.submitallandfinish": "Enviar todo y terminar", @@ -574,6 +681,7 @@ "addon.mod_quiz.yourfinalgradeis": "Su calificación final en este examen es {{$a}}.", "addon.mod_resource.errorwhileloadingthecontent": "Error al cargar el contenido.", "addon.mod_resource.modifieddate": "Modificado {{$a}}", + "addon.mod_resource.modulenameplural": "Archivos", "addon.mod_resource.openthefile": "Abrir el archivo", "addon.mod_resource.uploadeddate": "Subido {{$a}}", "addon.mod_scorm.asset": "Recurso", @@ -610,6 +718,7 @@ "addon.mod_scorm.incomplete": "Incompleto", "addon.mod_scorm.lastattempt": "Último intento", "addon.mod_scorm.mode": "Moda", + "addon.mod_scorm.modulenameplural": "Paquetes SCORM", "addon.mod_scorm.newattempt": "Comenzar un nuevo intento", "addon.mod_scorm.noattemptsallowed": "Número de intentos permitidos", "addon.mod_scorm.noattemptsmade": "Número de intentos realizados", @@ -629,10 +738,12 @@ "addon.mod_survey.errorgetsurvey": "Error al obtener datos de la encuesta.", "addon.mod_survey.ifoundthat": "Encontrado:", "addon.mod_survey.ipreferthat": "Prefiero esto", + "addon.mod_survey.modulenameplural": "Encuestas", "addon.mod_survey.responses": "Respuestas", "addon.mod_survey.results": "Resultados", "addon.mod_survey.surveycompletednograph": "Usted ha completado esta encuesta.", "addon.mod_url.accessurl": "Acceder a la URL", + "addon.mod_url.modulenameplural": "URLs", "addon.mod_url.pointingtourl": "URL a donde apunta este recurso.", "addon.mod_wiki.cannoteditpage": "Usted no puede editar esta página", "addon.mod_wiki.createpage": "Crear Página", @@ -641,6 +752,7 @@ "addon.mod_wiki.errornowikiavailable": "Este wiki todavía no tiene ningún contenido.", "addon.mod_wiki.gowikihome": "Ir a la primera página del wiki", "addon.mod_wiki.map": "Mapa", + "addon.mod_wiki.modulenameplural": "Wikis", "addon.mod_wiki.newpagehdr": "Página nueva", "addon.mod_wiki.newpagetitle": "Título nuevo de la página", "addon.mod_wiki.nocontent": "No hay contenido para esta página", @@ -679,6 +791,7 @@ "addon.mod_workshop.gradinggradecalculated": "Calificación calculada por valoración", "addon.mod_workshop.gradinggradeof": "Calificación por valoración (de {{$a}})", "addon.mod_workshop.gradinggradeover": "Anular calificación por valoración", + "addon.mod_workshop.modulenameplural": "Talleres", "addon.mod_workshop.nogradeyet": "Aún no hay calificación", "addon.mod_workshop.notassessed": "Aún no evaluado", "addon.mod_workshop.notoverridden": "No anulado", @@ -1044,6 +1157,8 @@ "core.accounts": "Cuentas", "core.add": "Agregar", "core.agelocationverification": "Verificación de ubicación y edad", + "core.ago": "hace {{$a}}", + "core.all": "Todos", "core.allparticipants": "Todos los participantes", "core.android": "Android", "core.answer": "Respuesta", @@ -1081,7 +1196,7 @@ "core.confirmdeletefile": "¿Está seguro de que desea eliminar este archivo?", "core.confirmloss": "¿Está Usted seguro? Se perderán todos los cambios.", "core.confirmopeninbrowser": "¿Desea Usted abrirla en un navegador web?", - "core.considereddigitalminor": "Se considera que Usted es un menor digital.", + "core.considereddigitalminor": "Usted es demasiado joven para crear una cuenta en este sitio.", "core.content": "Contenido", "core.contenteditingsynced": "El contenido que Usted está editando ha sido sincronizado.", "core.contentlinks.chooseaccount": "Elegir cuenta", @@ -1111,18 +1226,21 @@ "core.course.errorgetmodule": "Error al obtener datos de la actividad.", "core.course.hiddenfromstudents": "Oculto para los estudiantes", "core.course.hiddenoncoursepage": "Disponible pero no mostrada en página del curso", + "core.course.manualcompletionnotsynced": "Finalización manual no sincronizada", "core.course.nocontentavailable": "Sin contenido disponible al momento.", "core.course.overriddennotice": "La calificación final de esta actividad ha sido ajustada manualmente.", "core.course.refreshcourse": "Refrescar curso", "core.course.sections": "Secciones", "core.course.useactivityonbrowser": "Usted todavía puede usarla si emplea el navegador web de su dispositivo.", + "core.course.warningmanualcompletionmodified": "La finalización manual de una actividad fue modificada en el sitio.", + "core.course.warningofflinemanualcompletiondeleted": "Una parte de la finalización manual del curso '{{name}}' ha sido eliminada. {{error}}", "core.coursedetails": "Detalles del curso", + "core.courses.addtofavourites": "Marcar con estrella este curso", "core.courses.allowguests": "Este curso permite la entrada de invitados", "core.courses.availablecourses": "Cursos disponibles", "core.courses.cannotretrievemorecategories": "No se pueden recuperar categorías más profundas que el nivel {{$a}}.", "core.courses.categories": "Categorías", "core.courses.confirmselfenrol": "¿Está Usted seguro de querer inscribirse a Usted mismo en este curso?", - "core.courses.courseoverview": "Vista general del curso", "core.courses.courses": "Cursos", "core.courses.downloadcourses": "Descargar cursos", "core.courses.enrolme": "Inscribirme", @@ -1132,35 +1250,24 @@ "core.courses.errorselfenrol": "Ocurrio un error al auto-inscribir.", "core.courses.filtermycourses": "Filtrar mis cursos", "core.courses.frontpage": "Portada", - "core.courses.future": "Futuro", - "core.courses.inprogress": "En progreso", - "core.courses.morecourses": "Más cursos", + "core.courses.hidecourse": "Ocultar de vista", "core.courses.mycourses": "Mis cursos", - "core.courses.next30days": "Próximos 30 días", - "core.courses.next7days": "Próximos 7 días", + "core.courses.mymoodle": "Tablero", "core.courses.nocourses": "No hay información del curso para mostrar.", - "core.courses.nocoursesfuture": "Sin cursos futuros", - "core.courses.nocoursesinprogress": "Ningun curso en progreso", - "core.courses.nocoursesoverview": "Sin cursos", - "core.courses.nocoursespast": "Sin cursos pasados", "core.courses.nocoursesyet": "No hay cursos en esta categoría", - "core.courses.noevents": "Sin actividades próximas pendientes", "core.courses.nosearchresults": "Sin resultados", "core.courses.notenroled": "Usted no está inscrito en este curso", "core.courses.notenrollable": "Usted no puede inscribirse Usted mismo en este curso.", "core.courses.password": "Clave de inscripción", - "core.courses.past": "Pasado", "core.courses.paymentrequired": "Para entrar a este curso es necesario pagar.", "core.courses.paypalaccepted": "Pagos PayPal aceptados", - "core.courses.recentlyoverdue": "Recientemente retrasado", + "core.courses.removefromfavourites": "Quitar estrella de este curso", "core.courses.search": "Buscar", "core.courses.searchcourses": "Buscar cursos", "core.courses.searchcoursesadvice": "Usted puede usar el botón de 'buscar cursos' para encontrar cursos a los cuales acceder como un invitado o para inscribirse Usted mismo en los cursos que lo permitan.", "core.courses.selfenrolment": "Auto-inscripción", "core.courses.sendpaymentbutton": "Enviar pago por Paypal", - "core.courses.sortbycourses": "Ordenar por cursos", - "core.courses.sortbydates": "Ordenar por fechas", - "core.courses.timeline": "Línea de Tiempo", + "core.courses.show": "Mostrar este curso", "core.courses.totalcoursesearchresults": "Total de cursos: {{$a}}", "core.currentdevice": "Dispositivo actual", "core.datastoredoffline": "Los datos se almacenaron en el dispositivo debido a que no se pudieron enviar. Serán enviados automáticamente más tarde.", @@ -1180,7 +1287,7 @@ "core.dfmediumdate": "LLL", "core.dftimedate": "h[:]mm A", "core.digitalminor": "Menor digital", - "core.digitalminor_desc": "Para crear una cuenta en este sitio por favor solicite a su padre o guardián que contacte a la siguiente persona.", + "core.digitalminor_desc": "Por favor pídale a su padre o tutor que se ponga en contacto con:", "core.discard": "Descartar", "core.dismiss": "Descartar", "core.done": "Hecho", @@ -1205,6 +1312,7 @@ "core.errorsync": "Ocurrió un error al sincronizar. Por favor inténtelo nuevamente.", "core.errorsyncblocked": "Este/a {{$a}} no puede sincronizarse ahorita porque hay un proceso trabajando. Por favor inténtelo nuevamente más tarde. Si el problema persiste, intente reiniciar la App.", "core.explanationdigitalminor": "Esta información es necesaria para determinar si su edad está arriba de la edad digital de consentimiento. Esta es la edad cuando un individuo puede consentir a los términos y condiciones y que sus datos sean legalmente almacenados y procesados.", + "core.favourites": "Destacados", "core.filename": "Nombre del archivo", "core.filenameexist": "El nombre del archivo ya existe: {{$a}}", "core.fileuploader.addfiletext": "Añadir archivo", @@ -1228,6 +1336,7 @@ "core.fileuploader.more": "Más", "core.fileuploader.photoalbums": "Álbumes de fotos", "core.fileuploader.readingfile": "Leyendo archivo", + "core.fileuploader.readingfileperc": "Letendo archivo: {{$a}}%", "core.fileuploader.selectafile": "Seleccionar un archivo", "core.fileuploader.uploadafile": "Subir un archivo", "core.fileuploader.uploading": "Subiendo", @@ -1286,12 +1395,15 @@ "core.login.createaccount": "Crear mi cuenta nueva", "core.login.createuserandpass": "Elegir su nombre_de_usuario y contraseña", "core.login.credentialsdescription": "Por favor proporcione su nombre_de_usuario y contraseña para ingresar.", - "core.login.emailconfirmsent": "

Debería haberse enviado un Email a su dirección en {{$a}}

El mensaje contiene instrucciones sencillas para completar su registro.

Si continúa teniendo dificultades, contacte al administrador del sitio.

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

Hemos enviado un correo electrónico a {{$a}}

\n

En él encontrará instrucciones sencillas para concluir el proceso.

\n

Si tuviera alguna dificultad, póngase en contacto con el Administrador del Sistema.

", + "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", + "core.login.findyoursite": "Encuentre su sitio", "core.login.firsttime": "Registrarse como usuario", "core.login.forgotten": "¿Olvidó su nombre_de_usuario o contraseña?", "core.login.getanothercaptcha": "Obtener otro CAPTCHA", @@ -1299,7 +1411,7 @@ "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", "core.login.invalidaccount": "Por favor, verifique sus datos de acceso, o consulte con el administrador para revisar la configuración del sitio.", - "core.login.invaliddate": "Fecha inválida", + "core.login.invaliddate": "Fecha no válida", "core.login.invalidemail": "Dirección de correo no válida", "core.login.invalidmoodleversion": "Versión de Moodle inválida. La versión mínima requerida es 2.4", "core.login.invalidsite": "La URL del sitio es inválida.", @@ -1309,6 +1421,7 @@ "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", @@ -1319,6 +1432,7 @@ "core.login.missingfirstname": "Escribir: nombre", "core.login.missinglastname": "Falta apellido(s)", "core.login.mobileservicesnotenabled": "El acceso Mobile no está habilitado en su sitio. Por favor, contacte a su administrador del sitio si Usted piensa que se debería habilitar.", + "core.login.mustconfirm": "Necesita confirmar su cuenta", "core.login.newaccount": "Nueva cuenta", "core.login.newsitedescription": "Por favor escriba la URL de su sitio Moodle. Tenga en cuenta que podría no estar configurado para trabajar con esta App.", "core.login.notloggedin": "Usted necesita estar ingresado.", @@ -1340,12 +1454,14 @@ "core.login.reconnect": "Reconectar", "core.login.reconnectdescription": "Su ficha (''token'') para autenticación es inválida o ha caducado; Usted tiene que reconectarse al sitio.", "core.login.reconnectssodescription": "Su ficha (''token'') para autenticación es inválida o ha caducado; Usted tiene que reconectarse al sitio. Usted tiene que ingresar al sitio en una ventana del navegador.", + "core.login.resendemail": "Re-enviar Email", "core.login.searchby": "Buscar por:", "core.login.security_question": "Pregunta de seguridad", "core.login.selectacountry": "Seleccione su país", "core.login.selectsite": "Por favor seleccione su sitio:", "core.login.signupplugindisabled": "{{$a}} no está habilitado/a.", "core.login.siteaddress": "Dirección del sitio", + "core.login.sitehasredirect": "Su sitio contiene al menos una redirección HTTP. La App no puede seguir redirecciones; esto podría ser el problema que está impidiéndole a la App que se conecte a su sitio.", "core.login.siteinmaintenance": "Su sitio está en modo de mantenimiento", "core.login.sitepolicynotagreederror": "No se aceptó la política del sitio.", "core.login.siteurl": "URL del sitio", @@ -1364,8 +1480,6 @@ "core.mainmenu.changesite": "Cambiar sitio", "core.mainmenu.help": "Ayuda", "core.mainmenu.logout": "Salir", - "core.mainmenu.mycourses": "Mis cursos", - "core.mainmenu.togglemenu": "Alternar menú", "core.mainmenu.website": "Página web", "core.maxsizeandattachments": "Tamaño máximo para archivos nuevos: {{$a.size}}, anexos máximos: {{$a.attachments}}", "core.min": "minutos", @@ -1400,6 +1514,7 @@ "core.more": "más", "core.mygroups": "Mis grupos", "core.name": "Nombre", + "core.networkerroriframemsg": "El contenido no está disponible fuera de línea. Por favor conéctese a Internet e inténtelo de nuevo.", "core.networkerrormsg": "Hubo un problema para conectarse al sitio. Por favor revise su conexión e inténtelo nuevamente.", "core.never": "Nunca", "core.next": "Siguiente", @@ -1408,6 +1523,7 @@ "core.nograde": "No hay calificación", "core.none": "Ninguno(a)", "core.nopasswordchangeforced": "Usted no puede proceder sin cambiar su contraseña.", + "core.nopermissionerror": "Lo sentimos, pero Usted actualmente no tiene permisos para hacer eso", "core.nopermissions": "Lo sentimos, pero por el momento no tiene permiso para hacer eso ({{$a}})", "core.noresults": "No hay resultados", "core.notapplicable": "no disp.", @@ -1432,6 +1548,7 @@ "core.pulltorefresh": "''Pull'' para refrescar", "core.question.answer": "Respuesta", "core.question.answersaved": "Respuesta guardada", + "core.question.cannotdeterminestatus": "No pudo determinarse el estado", "core.question.certainty": "Certeza", "core.question.complete": "Completada", "core.question.correct": "Correcta", @@ -1452,8 +1569,10 @@ "core.quotausage": "Actualmente Usted ha usado {{$a.used}} de sus {{$a.total}} límite.", "core.redirectingtosite": "Usted será redireccionado al sitio.", "core.refresh": "Refrescar", + "core.remove": "Quitar", "core.required": "Obligatorio", "core.requireduserdatamissing": "A este usuario le faltan algunos datos requeridos del perfil. Por favor, llene estos datos en su sitio e inténtelo nuevamente.
{{$a}}", + "core.resources": "Recursos", "core.restore": "Restaurar", "core.retry": "Reintentar", "core.save": "Guardar", @@ -1478,6 +1597,7 @@ "core.settings.cordovaversion": "Versión Cordova", "core.settings.currentlanguage": "Idioma actual", "core.settings.debugdisplay": "Mostrar mensajes de depuración", + "core.settings.debugdisplaydescription": "Si se habilita, errores modales mostrará más datos acerca del error si fuese posible,", "core.settings.deletesitefiles": "¿Está seguro de que desea borrar todos los archivos descargados de este sitio?", "core.settings.deletesitefilestitle": "Eliminar archivos del sitio", "core.settings.deviceinfo": "Información del dispositivo", @@ -1508,6 +1628,7 @@ "core.settings.privacypolicy": "Política de privacidad", "core.settings.reportinbackground": "Reportar errores automáticamente", "core.settings.settings": "Configuración", + "core.settings.showdownloadoptions": "Mostrar opciones para descarga", "core.settings.sites": "Sitios", "core.settings.spaceusage": "Espacio", "core.settings.synchronization": "Sincronización", @@ -1540,6 +1661,21 @@ "core.sorry": "Lo siento...", "core.sortby": "Ordenar por", "core.start": "Inicio", + "core.strftimedate": "%d de %B de %Y", + "core.strftimedatefullshort": "%d/%m/%y", + "core.strftimedateshort": "%d de %B", + "core.strftimedatetime": "%d de %B de %Y, %H:%M", + "core.strftimedatetimeshort": "%d/%m/%y, %H:%M", + "core.strftimedaydate": "%A, %d de %B de %Y", + "core.strftimedaydatetime": "%A, %d de %B de %Y, %H:%M", + "core.strftimedayshort": "%A, %d %B", + "core.strftimedaytime": "%a, %H:%M", + "core.strftimemonthyear": "%B %Y", + "core.strftimerecent": "%d %b, %H:%M", + "core.strftimerecentfull": "%a, %d de %b de %Y, %H:%M", + "core.strftimetime": "%H:%M", + "core.strftimetime12": "%I:%M %p", + "core.strftimetime24": "%H:%M", "core.submit": "Enviar", "core.success": "Éxito", "core.tablet": "Tableta", @@ -1582,7 +1718,7 @@ "core.user.roles": "Roles", "core.user.sendemail": "Email", "core.user.student": "Estudiante", - "core.user.teacher": "Profesor sin derechos de edición", + "core.user.teacher": "Profesor sin permiso de edición", "core.user.webpage": "Página web", "core.userdeleted": "Esta cuenta se ha cancelado", "core.userdetails": "Detalles de usuario", @@ -1591,6 +1727,7 @@ "core.view": "Ver", "core.viewcode": "Ver código", "core.vieweditor": "Ver editor", + "core.viewembeddedcontent": "Ver contenido incrustado", "core.viewprofile": "Ver perfil", "core.warningofflinedatadeleted": "Los datos fuera-de-línea de {{component}} '{{name}}' han sido borrados. {{error}}", "core.whatisyourage": "¿Qué edad tiene?", diff --git a/src/assets/lang/es.json b/src/assets/lang/es.json index d3e461723..600de9111 100644 --- a/src/assets/lang/es.json +++ b/src/assets/lang/es.json @@ -8,11 +8,42 @@ "addon.badges.issuancedetails": "Caducidad de la insignia", "addon.badges.issuerdetails": "Detalles del emisor", "addon.badges.issuername": "Nombre del emisor", + "addon.badges.issuerurl": "URL del emisor", "addon.badges.nobadges": "No hay insignias disponibles", "addon.badges.recipientdetails": "Detalles del destinatario", + "addon.badges.warnexpired": "(¡Esta insignia ha expirado!)", + "addon.block_activitymodules.pluginname": "Actividades", + "addon.block_myoverview.all": "Todo", + "addon.block_myoverview.favourites": "Destacados", + "addon.block_myoverview.future": "Futuros", + "addon.block_myoverview.hiddencourses": "Ocultos", + "addon.block_myoverview.inprogress": "En progreso", + "addon.block_myoverview.morecourses": "Más cursos", + "addon.block_myoverview.nocourses": "Sin cursos", + "addon.block_myoverview.past": "Pasados", + "addon.block_myoverview.pluginname": "Vista general de curso", + "addon.block_myoverview.title": "Nombre del curso", + "addon.block_recentlyaccessedcourses.nocourses": "No hay cursos recientes", + "addon.block_recentlyaccessedcourses.pluginname": "Cursos accedidos recientemente", + "addon.block_recentlyaccesseditems.noitems": "Sin elementos recientes", + "addon.block_recentlyaccesseditems.pluginname": "Elementos accedidos recientemente", + "addon.block_sitemainmenu.pluginname": "Menú principal", + "addon.block_starredcourses.nocourses": "No hay cursos destacados", + "addon.block_starredcourses.pluginname": "Cursos destacados", + "addon.block_timeline.duedate": "Fecha de vencimiento", + "addon.block_timeline.next30days": "Próximos 30 días", + "addon.block_timeline.next3months": "Próximos 3 meses", + "addon.block_timeline.next6months": "Próximos 6 meses", + "addon.block_timeline.next7days": "Próximos 7 días", + "addon.block_timeline.nocoursesinprogress": "No hay cursos actuales", + "addon.block_timeline.noevents": "No hay actividades previstas", + "addon.block_timeline.overdue": "Atrasados", + "addon.block_timeline.pluginname": "Línea de tiempo", + "addon.block_timeline.sortbycourses": "Ordenar por curso", + "addon.block_timeline.sortbydates": "Ordenar por fecha", "addon.calendar.calendar": "Calendario", "addon.calendar.calendarevents": "Eventos de calendario", - "addon.calendar.defaultnotificationtime": "Tiempo de notificación por defecto", + "addon.calendar.defaultnotificationtime": "Hora de notificación por defecto", "addon.calendar.errorloadevent": "Error cargando el evento.", "addon.calendar.errorloadevents": "Error cargando los eventos.", "addon.calendar.eventendtime": "Hora final", @@ -88,12 +119,15 @@ "addon.files.sitefiles": "Archivos del sitio", "addon.messageoutput_airnotifier.processorsettingsdesc": "Configurar dispositivos", "addon.messages.addcontact": "Añadir contacto", - "addon.messages.blockcontact": "Bloquear contacto", - "addon.messages.blockcontactconfirm": "Usted dejará de recibir mensajes de este contacto.", + "addon.messages.addtoyourcontacts": "Añadir a tus contactos", "addon.messages.blocknoncontacts": "Bloquear mensajes de usuarios que no figuren en mi lista de contactos", + "addon.messages.contactblocked": "Contacto bloqueado", "addon.messages.contactlistempty": "Lista de contactos vacía", "addon.messages.contactname": "Nombre del contacto", "addon.messages.contacts": "Contactos", + "addon.messages.deleteallconfirm": "¿Está seguro de que desea eliminar toda la conversación?", + "addon.messages.deletemessage": "Eliminar mensaje", + "addon.messages.deletemessageconfirmation": "¿Está seguro que quiere eliminar este mensaje? Será eliminado solamente de su historial de mensaje y todavía será visible por el usuario que envió o recibió el mensaje.", "addon.messages.errordeletemessage": "Error borrando el mensaje.", "addon.messages.errorwhileretrievingcontacts": "Error al recuperar los contactos del servidor.", "addon.messages.errorwhileretrievingdiscussions": "Error al recuperar las discusiones del servidor.", @@ -104,17 +138,21 @@ "addon.messages.messages": "Mensajes", "addon.messages.newmessage": "Nuevo mensaje", "addon.messages.newmessages": "Nuevos mensajes", - "addon.messages.nomessages": "No hay mensajes", + "addon.messages.nomessagesfound": "No se encontraron mensajes", + "addon.messages.noncontacts": "No contactos", "addon.messages.nousersfound": "No se encuentran usuarios", "addon.messages.removecontact": "Eliminar contacto", "addon.messages.removecontactconfirm": "El contacto se eliminará de su lista de contactos.", + "addon.messages.removefromyourcontacts": "Eliminar de tus contactos", + "addon.messages.requests": "Solicitudes", + "addon.messages.searchcombined": "Buscar personas y mensajes", "addon.messages.type_blocked": "Bloqueado", "addon.messages.type_offline": "Desconectado", "addon.messages.type_online": "En línea", "addon.messages.type_search": "Resultados de la búsqueda", "addon.messages.type_strangers": "Otros", - "addon.messages.unblockcontact": "Desbloquear contacto", "addon.messages.warningmessagenotsent": "No se pudo enviar mensaje(s) al usuario {{user}}. {{error}}", + "addon.messages.you": "Tú:", "addon.mod_assign.acceptsubmissionstatement": "Por favor acepte las condiciones de envío.", "addon.mod_assign.addattempt": "Permitir otro intento", "addon.mod_assign.addnewattempt": "Añadir una nueva entrega", @@ -152,6 +190,7 @@ "addon.mod_assign.graded": "Calificado", "addon.mod_assign.gradedby": "Calificado por", "addon.mod_assign.gradedon": "Calificado sobre", + "addon.mod_assign.gradelocked": "Esta calificación está bloqueada o ha sido modificada en el libro de calificaciones", "addon.mod_assign.gradenotsynced": "Calificación no sincronizada", "addon.mod_assign.gradeoutof": "Calificación sobre {{$a}}", "addon.mod_assign.gradingstatus": "Estado de la calificación", @@ -166,6 +205,7 @@ "addon.mod_assign.markingworkflowstatereadyforrelease": "Lista para publicar", "addon.mod_assign.markingworkflowstatereadyforreview": "Calificación terminada", "addon.mod_assign.markingworkflowstatereleased": "Publicada", + "addon.mod_assign.modulenameplural": "Tareas", "addon.mod_assign.multipleteams": "Miembro de más de un grupo.", "addon.mod_assign.multipleteams_desc": "Esta tarea requiere entrega por grupos. Usted es miembro de más de un grupo. Para poder realizar entregas debe ser miembro solamente de un grupo. Por favor, contacte con su profesor para que modifique su pertenencia a grupos.", "addon.mod_assign.noattempt": "No entregado", @@ -220,6 +260,7 @@ "addon.mod_assign_submission_file.pluginname": "Archivos enviados", "addon.mod_assign_submission_onlinetext.pluginname": "Entregas de texto en línea", "addon.mod_book.errorchapter": "Error al leer el capítulo del libro.", + "addon.mod_book.modulenameplural": "Libros", "addon.mod_chat.beep": "Beep", "addon.mod_chat.currentusers": "Usuarios", "addon.mod_chat.enterchat": "Entrar a la sala", @@ -232,6 +273,7 @@ "addon.mod_chat.messagebeepsyou": "{{$a}} le acaba de enviar un beep", "addon.mod_chat.messageenter": "{{$a}} entró a la sala", "addon.mod_chat.messageexit": "{{$a}} salió de la sala", + "addon.mod_chat.modulenameplural": "Chats", "addon.mod_chat.mustbeonlinetosendmessages": "Usted debe de estar conectado a Internet para enviar mensajes", "addon.mod_chat.nomessages": "Aún no hay mensajes", "addon.mod_chat.send": "Enviar", @@ -242,6 +284,7 @@ "addon.mod_choice.errorgetchoice": "Se ha producido un error recuperando los datos de la consulta.", "addon.mod_choice.expired": "Lo sentimos, esta actividad se cerró el {{$a}} y ya no está disponible", "addon.mod_choice.full": "(Lleno)", + "addon.mod_choice.modulenameplural": "Consultas", "addon.mod_choice.noresultsviewable": "Los resultados no pueden verse en este momento.", "addon.mod_choice.notopenyet": "Lo sentimos, esta actividad no estará disponible hasta {{$a}}", "addon.mod_choice.numberofuser": "Número de respuestas", @@ -274,11 +317,15 @@ "addon.mod_data.emptyaddform": "¡No ha rellenado ningún campo!", "addon.mod_data.entrieslefttoadd": "Debe agregar {{$a.entriesleft}} entrada(s) más para poder finalizar esta actividad", "addon.mod_data.entrieslefttoaddtoview": "Debe añadir {{$a.entrieslefttoview}} entrada(s) antes de poder ver las entradas de otros participantes.", + "addon.mod_data.errorapproving": "Error al aprobar o desaprobar una entrada.", + "addon.mod_data.errordeleting": "Error al eliminar entrada.", "addon.mod_data.errormustsupplyvalue": "Debe proporcionar un valor aquí.", "addon.mod_data.expired": "Lo sentimos, esta actividad se cerró el {{$a}} y ya no está disponible", "addon.mod_data.fields": "Campos", + "addon.mod_data.foundrecords": "Registros encontrados: {{$a.num}}/{{$a.max}} (Reset filters)", "addon.mod_data.latlongboth": "Tanto la latitud como la longitud son necesarias.", "addon.mod_data.menuchoose": "Seleccionar...", + "addon.mod_data.modulenameplural": "Bases de datos", "addon.mod_data.more": "Más", "addon.mod_data.nomatch": "No se han encontrado entradas", "addon.mod_data.norecords": "No hay entradas en la base de datos", @@ -310,6 +357,7 @@ "addon.mod_feedback.feedbackopen": "Permitir respuestas de", "addon.mod_feedback.mapcourses": "Asignar encuesta a cursos", "addon.mod_feedback.mode": "Modo", + "addon.mod_feedback.modulenameplural": "Encuestas", "addon.mod_feedback.next_page": "Siguiente página", "addon.mod_feedback.non_anonymous": "Los nombres de los usuarios se mostrarán y registrarán con las respuestas", "addon.mod_feedback.non_anonymous_entries": "entradas no anónimas", @@ -329,7 +377,8 @@ "addon.mod_feedback.show_nonrespondents": "Mostrar no respondientes", "addon.mod_feedback.started": "comenzado", "addon.mod_feedback.this_feedback_is_already_submitted": "Usted ya ha finalizado esta actividad.", - "addon.mod_folder.emptyfilelist": "No hay archivos que mostrar", + "addon.mod_folder.emptyfilelist": "No hay archivos que mostrar.", + "addon.mod_folder.modulenameplural": "Carpetas", "addon.mod_forum.addanewdiscussion": "Añadir un nuevo tema de discusión", "addon.mod_forum.addanewquestion": "Añadir una nueva pregunta", "addon.mod_forum.addanewtopic": "Añadir un nuevo tema", @@ -352,6 +401,7 @@ "addon.mod_forum.modeflatnewestfirst": "Ordenar desde el más reciente", "addon.mod_forum.modeflatoldestfirst": "Ordenar desde el más antiguo", "addon.mod_forum.modenested": "Mostrar respuestas anidadas", + "addon.mod_forum.modulenameplural": "Foros", "addon.mod_forum.numdiscussions": "{{numdiscussions}} discusiones", "addon.mod_forum.numreplies": "{{numreplies}} respuestas", "addon.mod_forum.posttoforum": "Enviar al foro", @@ -371,7 +421,7 @@ "addon.mod_glossary.bycategory": "Agrupar por categoría", "addon.mod_glossary.bynewestfirst": "El más reciente primero", "addon.mod_glossary.byrecentlyupdated": "Actualizado recientemente", - "addon.mod_glossary.bysearch": "Busca", + "addon.mod_glossary.bysearch": "Buscar", "addon.mod_glossary.cannoteditentry": "No se puede editar la entrada", "addon.mod_glossary.casesensitive": "Esta entrada es en Mayúsculas y minúsculas", "addon.mod_glossary.categories": "Categorías", @@ -387,9 +437,11 @@ "addon.mod_glossary.fillfields": "Los campos Concepto y Definición son obligatorios.", "addon.mod_glossary.fullmatch": "Sólo enlazar palabras completas", "addon.mod_glossary.linking": "Auto-enlace", + "addon.mod_glossary.modulenameplural": "Glosarios", "addon.mod_glossary.noentriesfound": "No se han encontrado entradas.", "addon.mod_glossary.searchquery": "Tú búsqueda", "addon.mod_imscp.deploymenterror": "Error en el paquete de contenidos", + "addon.mod_imscp.modulenameplural": "Paquete de contenidos IMS", "addon.mod_imscp.showmoduledescription": "Mostrar descripción", "addon.mod_lesson.answer": "Respuesta", "addon.mod_lesson.attempt": "Intento: {{$a}}", @@ -414,6 +466,7 @@ "addon.mod_lesson.emptypassword": "La contraseña no puede estar vacía", "addon.mod_lesson.enterpassword": "Por favor, escriba la contraseña:", "addon.mod_lesson.eolstudentoutoftimenoanswers": "No ha contestado a ninguna pregunta. En esta lección ha obtenido 0 puntos.", + "addon.mod_lesson.errorprefetchrandombranch": "Esta lección contiene un salto hacia una página aleatoria de contenido. No puede ser intentada en la aplicación hasta que haya sido comenzada en un navegador web.", "addon.mod_lesson.errorreviewretakenotlast": "Este intento no puede ser revisado ya que se ha terminado otro intento.", "addon.mod_lesson.finish": "Terminado", "addon.mod_lesson.finishretakeoffline": "Este intento se ha terminado en fuera-de-línea.", @@ -432,6 +485,7 @@ "addon.mod_lesson.lowtime": "Tiempo bajo", "addon.mod_lesson.maximumnumberofattemptsreached": "Se ha alcanzado el número máximo de intentos. Traslado a la página siguiente", "addon.mod_lesson.modattemptsnoteacher": "La revisión del estudiante sólo está disponible para los estudiantes.", + "addon.mod_lesson.modulenameplural": "Lecciones", "addon.mod_lesson.noanswer": "No se ha dado respuesta", "addon.mod_lesson.nolessonattempts": "No se han hecho intentos de practicar esta lección.", "addon.mod_lesson.nolessonattemptsgroup": "{{$a}} miembros del grupo no han realizado ningún intento en esta lección.", @@ -476,7 +530,9 @@ "addon.mod_lti.errorgetlti": "Se ha producido un error recuperando los datos del módulo.", "addon.mod_lti.errorinvalidlaunchurl": "La dirección URL no es válida.", "addon.mod_lti.launchactivity": "Ejecutar la actividad", + "addon.mod_lti.modulenameplural": "basicltis", "addon.mod_page.errorwhileloadingthepage": "Error al cargar el contenido de la página.", + "addon.mod_page.modulenameplural": "Páginas", "addon.mod_quiz.attemptfirst": "Primer intento", "addon.mod_quiz.attemptlast": "Último intento", "addon.mod_quiz.attemptnumber": "Intento", @@ -511,6 +567,7 @@ "addon.mod_quiz.grademethod": "Método de calificación", "addon.mod_quiz.gradesofar": "{{$a.method}}: {{$a.mygrade}} / {{$a.quizgrade}}.", "addon.mod_quiz.marks": "Puntos", + "addon.mod_quiz.modulenameplural": "Cuestionarios", "addon.mod_quiz.mustbesubmittedby": "Este intento debe ser presentado por {{$a}}.", "addon.mod_quiz.noquestions": "Aún no se han agregado preguntas", "addon.mod_quiz.noreviewattempt": "Usted no tiene permiso para revisar este intento.", @@ -555,6 +612,7 @@ "addon.mod_quiz.yourfinalgradeis": "Su calificación final en este cuestionario es {{$a}}", "addon.mod_resource.errorwhileloadingthecontent": "Se ha producido un error cargando el contenido.", "addon.mod_resource.modifieddate": "Actualizado {{$a}}", + "addon.mod_resource.modulenameplural": "Archivos", "addon.mod_resource.openthefile": "Abrir el archivo", "addon.mod_resource.uploadeddate": "Subido {{$a}}", "addon.mod_scorm.asset": "Recurso", @@ -591,6 +649,7 @@ "addon.mod_scorm.incomplete": "Incompleto", "addon.mod_scorm.lastattempt": "Último intento", "addon.mod_scorm.mode": "Moda", + "addon.mod_scorm.modulenameplural": "Paquetes SCORM", "addon.mod_scorm.newattempt": "Comenzar un nuevo intento", "addon.mod_scorm.noattemptsallowed": "Número de intentos permitidos", "addon.mod_scorm.noattemptsmade": "Número de intentos realizados", @@ -610,10 +669,12 @@ "addon.mod_survey.errorgetsurvey": "Se ha producido un error recuperando los datos de la encuesta.", "addon.mod_survey.ifoundthat": "Encontrado:", "addon.mod_survey.ipreferthat": "Prefiero esto", + "addon.mod_survey.modulenameplural": "Encuestas", "addon.mod_survey.responses": "Respuestas", "addon.mod_survey.results": "Resultados", "addon.mod_survey.surveycompletednograph": "Has completado esta encuesta.", "addon.mod_url.accessurl": "Ir a la URL", + "addon.mod_url.modulenameplural": "URLs", "addon.mod_url.pointingtourl": "La URL donde apunta este recurso", "addon.mod_wiki.cannoteditpage": "No puedes editar esta página.", "addon.mod_wiki.createpage": "Crear Página", @@ -622,6 +683,7 @@ "addon.mod_wiki.errornowikiavailable": "Este wiki todavía no tiene ningun contenido.", "addon.mod_wiki.gowikihome": "Ir al inicio del wiki", "addon.mod_wiki.map": "Mapa", + "addon.mod_wiki.modulenameplural": "Wikis", "addon.mod_wiki.newpagehdr": "Página nueva", "addon.mod_wiki.newpagetitle": "Título nuevo de la página", "addon.mod_wiki.nocontent": "No hay contenido para esta página", @@ -640,6 +702,7 @@ "addon.mod_workshop.assessedsubmission": "Envío evaluado", "addon.mod_workshop.assessmentform": "Formato de evaluación", "addon.mod_workshop.assessmentsettings": "Configuración de la evaluación", + "addon.mod_workshop.assessmentstrategynotsupported": "Estrategia de calificación {{$a}} no soportada", "addon.mod_workshop.assessmentweight": "Ponderación de la evaluación", "addon.mod_workshop.assignedassessments": "Envíos asignados para evaluar", "addon.mod_workshop.assignedassessmentsnone": "No tiene envíos asignados para evaluar", @@ -659,6 +722,7 @@ "addon.mod_workshop.gradinggradecalculated": "Calificación calculada de la evaluación", "addon.mod_workshop.gradinggradeof": "Calificación de la evaluación (de {{$a}})", "addon.mod_workshop.gradinggradeover": "Pasar por alto calificación de la evaluación", + "addon.mod_workshop.modulenameplural": "Talleres", "addon.mod_workshop.nogradeyet": "Aún no hay calificación", "addon.mod_workshop.notassessed": "Aún no evaluado", "addon.mod_workshop.notoverridden": "No anulado", @@ -675,6 +739,7 @@ "addon.mod_workshop.submissiongrade": "Calificación por el envío", "addon.mod_workshop.submissiongradeof": "Calificación por el envío (de {{$a}})", "addon.mod_workshop.submissionrequiredcontent": "Debe introducir algún texto o añadir un archivo.", + "addon.mod_workshop.submissionrequiredtitle": "Debe introducir algún título.", "addon.mod_workshop.submissionsreport": "Informe de envíos del taller", "addon.mod_workshop.submissiontitle": "Título", "addon.mod_workshop.switchphase10": "Cambiar a la fase de configuración", @@ -684,6 +749,8 @@ "addon.mod_workshop.switchphase50": "Cerrar taller", "addon.mod_workshop.userplan": "Planificador de taller", "addon.mod_workshop.userplancurrentphase": "Fase actual", + "addon.mod_workshop.warningassessmentmodified": "El envío fue modificado en el sitio.", + "addon.mod_workshop.warningsubmissionmodified": "La evaluación fue modificada en el sitio.", "addon.mod_workshop.weightinfo": "Ponderación: {{$a}}", "addon.mod_workshop.yourassessment": "Su evaluación", "addon.mod_workshop.yourassessmentfor": "Su valoración para {{$a}}", @@ -711,7 +778,7 @@ "addon.notes.sitenotes": "Notas del sitio", "addon.notes.userwithid": "Usuario con id {{id}}", "addon.notes.warningnotenotsent": "No se pudieron añadir notas al curso {{course}}. {{error}}", - "addon.notifications.errorgetnotifications": "Error al obtener notificaciones", + "addon.notifications.errorgetnotifications": "Error al obtener notificaciones.", "addon.notifications.markallread": "Marcar todo como leído", "addon.notifications.notificationpreferences": "Preferencias de notificación", "addon.notifications.notifications": "Notificaciones", @@ -990,6 +1057,8 @@ "core.accounts": "Cuentas", "core.add": "Agregar", "core.agelocationverification": "Verificación de localización y edad", + "core.ago": "hace {{$a}}", + "core.all": "Todos", "core.allparticipants": "Todos los participantes", "core.android": "Android", "core.answer": "Respuesta", @@ -999,6 +1068,10 @@ "core.cancel": "Cancelar", "core.cannotconnect": "No se puede conectar: Verifique que la URL es correcta y que el sitio Moodle usa la versión 2.4 o posterior.", "core.cannotdownloadfiles": "La descarga de archivos está deshabilitada en su servicio Mobile. Por favor contacte al administrador de su sitio.", + "core.captureaudio": "Grabar audio", + "core.capturedimage": "Foto tomada.", + "core.captureimage": "Tomar foto", + "core.capturevideo": "Grabar video", "core.category": "Categoría", "core.choose": "Elegir", "core.choosedots": "Elegir...", @@ -1043,22 +1116,25 @@ "core.course.couldnotloadsectioncontent": "No se ha podido cargar el contenido de la sección, por favor inténtelo más tarde.", "core.course.couldnotloadsections": "No se ha podido cargar las secciones, por favor inténtelo más tarde.", "core.course.coursesummary": "Resumen del curso", + "core.course.errordownloadingcourse": "Error al descargar el curso.", "core.course.errordownloadingsection": "Error durante la descarga de la sección.", "core.course.errorgetmodule": "Se ha producido un error recuperando los datos del módulo.", "core.course.hiddenfromstudents": "No mostrado a los estudiantes", "core.course.hiddenoncoursepage": "Disponibles pero no visibles en la página del curso", "core.course.nocontentavailable": "No hay contenido disponible en este momento.", "core.course.overriddennotice": "La calificación final de esta actividad ha sido ajustada manualmente.", + "core.course.refreshcourse": "Refrescar curso", "core.course.sections": "Secciones", "core.course.useactivityonbrowser": "Puede acceder a esta actividad mediante el navegador de su dispositivo.", "core.coursedetails": "Detalles del curso", + "core.courses.addtofavourites": "Destacar este curso", "core.courses.allowguests": "Este curso permite la entrada de invitados", "core.courses.availablecourses": "Cursos disponibles", "core.courses.cannotretrievemorecategories": "No se pueden recuperar categorías más profundas que el nivel {{$a}}.", "core.courses.categories": "Categorías", "core.courses.confirmselfenrol": "¿Está seguro que desea auto-matricularse en este curso?", - "core.courses.courseoverview": "Vista general del curso", "core.courses.courses": "Cursos", + "core.courses.downloadcourses": "Descargar cursos", "core.courses.enrolme": "Matricularme", "core.courses.errorloadcategories": "Ocurrió un error al cargar categorías.", "core.courses.errorloadcourses": "Se ha producido un error cargando los cursos.", @@ -1066,28 +1142,24 @@ "core.courses.errorselfenrol": "Se ha producido un error durante la auto-matriculación.", "core.courses.filtermycourses": "Filtrar mis cursos", "core.courses.frontpage": "Página Principal", - "core.courses.future": "Futuros", - "core.courses.inprogress": "En progreso", - "core.courses.morecourses": "Más cursos", + "core.courses.hidecourse": "Ocultar de este panel", "core.courses.mycourses": "Mis cursos", + "core.courses.mymoodle": "Área personal", "core.courses.nocourses": "No hay información del curso para mostrar.", - "core.courses.nocoursesfuture": "Sin cursos futuros", - "core.courses.nocoursesinprogress": "Sin cursos en progreso", - "core.courses.nocoursesoverview": "Sin cursos", - "core.courses.nocoursespast": "Sin cursos pasados", "core.courses.nocoursesyet": "No hay cursos en esta categoría", "core.courses.nosearchresults": "Sin resultados", "core.courses.notenroled": "Usted no está matriculado en este curso", "core.courses.notenrollable": "No puede auto-matricularse en este curso.", "core.courses.password": "Clave", - "core.courses.past": "Pasados", "core.courses.paymentrequired": "Para entrar a este curso es necesario pagar.", "core.courses.paypalaccepted": "Pagos PayPal aceptados", + "core.courses.removefromfavourites": "Quitar estrella a este curso", "core.courses.search": "Buscar", "core.courses.searchcourses": "Buscar cursos", "core.courses.searchcoursesadvice": "Puede utilizar el botón de buscar cursos para acceder como invitado o auto-matricularse en los cursos que lo permitan.", "core.courses.selfenrolment": "Auto-matriculación", "core.courses.sendpaymentbutton": "Enviar pago por Paypal", + "core.courses.show": "Mostrar este curso", "core.courses.totalcoursesearchresults": "Total de cursos: {{$a}}", "core.currentdevice": "Dispositivo actual", "core.datastoredoffline": "Los datos se almacenaron en el dispositivo debido a que no se pudieron enviar. Serán enviados automáticamente más tarde.", @@ -1097,6 +1169,7 @@ "core.decsep": ",", "core.defaultvalue": "Por defecto ({{$a}})", "core.delete": "Borrar", + "core.deletedoffline": "Eliminado fuera de línea", "core.deleting": "Borrando", "core.description": "Descripción", "core.dfdaymonthyear": "MM-DD-YYYY", @@ -1123,6 +1196,7 @@ "core.errorinvalidform": "El formulario contiene datos inválidos. Por favor, asegúrese de rellenar todos los campos requeridos y que los datos son válidos.", "core.errorinvalidresponse": "Se ha recibido una respuesta no válida. Por favor contactar con el administrador de Moodle si el error persiste.", "core.errorloadingcontent": "Error cargando contenido.", + "core.errorofflinedisabled": "La navegación fuera de línea está deshabilitada en su sitio. Necesita estar conectado a Internet para usar la aplicación.", "core.erroropenfilenoapp": "Error durante la apertura del archivo: no se encontró ninguna aplicación capaz de abrir este tipo de archivo.", "core.erroropenfilenoextension": "Se ha producido un error abriendo el archivo: el archivo no tiene extensión.", "core.erroropenpopup": "Esta actividad está intentando abrir una ventana emergente. Esta aplicación no lo soporta.", @@ -1130,6 +1204,7 @@ "core.errorsync": "Ocurrió un error al sincronizar. Por favor inténtelo de nuevo más tarde.", "core.errorsyncblocked": "Este/a {{$a}} no puede sincronizarse ahora mismo porque hay un proceso trabajando. Por favor inténtelo de nuevo más tarde. Si el problema persiste, intente reiniciar la aplicación.", "core.explanationdigitalminor": "Esta información es necesaria para determinar si su edad está por encima de la edad digital de consentimiento. Esta es la edad a la que un individuo puede aceptar los términos y condiciones y que sus datos sean legalmente almacenados y procesados.", + "core.favourites": "Destacados", "core.filename": "Nombre del archivo", "core.filenameexist": "El nombre de archivo ya existe: {{$a}}", "core.fileuploader.addfiletext": "Añadir fichero", @@ -1190,6 +1265,7 @@ "core.ios": "iOs", "core.labelsep": ":", "core.lastaccess": "Último acceso", + "core.lastdownloaded": "Última descarga", "core.lastmodified": "Última modificación", "core.lastsync": "Última sincronización", "core.layoutgrid": "Rejilla", @@ -1210,7 +1286,8 @@ "core.login.createaccount": "Crear cuenta", "core.login.createuserandpass": "Crear un nuevo usuario y contraseña para acceder al sistema", "core.login.credentialsdescription": "Introduzca su nombre se usuario y contraseña para entrar", - "core.login.emailconfirmsent": "

Se ha enviado un email a su dirección {{$a}}

Contiene instrucciones para completar el proceso de registro.

Si continúa teniendo algún problema, contacte con el administrador de su sitio.

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

Hemos enviado un correo electrónico a {{$a}}

\n

En él encontrará instrucciones sencillas para concluir el proceso.

\n

Si tuviera alguna dificultad, contacte con el Administrador del Sistema.

", + "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", @@ -1220,17 +1297,19 @@ "core.login.forgotten": "¿Olvidó su nombre de usuario o contraseña?", "core.login.getanothercaptcha": "Obtener otro CAPTCHA", "core.login.help": "Ayuda", - "core.login.helpmelogin": "

Hay miles de sitios que usan Moodle en el mundo. Esta aplicación sólo puede acceder a aquellos que han habilitado acceso móvil.

Si no puede conectar, contacte con su administrador de Moodle para que lea la siguiente información http://docs.moodle.org/en/Mobile_app

Para probar la aplicación en un sitio de demostración teclee teacher o student en el campo Dirección del sitio y haga clic en el botón Conectar.

", + "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", "core.login.invalidaccount": "Por favor, verifique sus datos de acceso, o consulte con el administrador para revisar la configuración del sitio.", - "core.login.invaliddate": "Fecha incorrecta", + "core.login.invaliddate": "Fecha no válida", "core.login.invalidemail": "Dirección de correo no válida", - "core.login.invalidmoodleversion": "Versión de Moodle inválida. La versión mínima requerida es:", + "core.login.invalidmoodleversion": "Versión de Moodle inválida. La versión mínima requerida es 2.4.", "core.login.invalidsite": "La URL del sitio es inválida.", "core.login.invalidtime": "Hora incorrecta", "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.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", @@ -1241,6 +1320,7 @@ "core.login.missingfirstname": "Falta el nombre dado", "core.login.missinglastname": "Falta el apellido(s)", "core.login.mobileservicesnotenabled": "El acceso móvil no está habilitado para este sitio, por favor, contacte con su administrador si piensa que debería estar habilitado.", + "core.login.mustconfirm": "Necesita confirmar el acceso", "core.login.newaccount": "Nueva cuenta", "core.login.newsitedescription": "Introduzca la URL de su sitio Moodle. Tenga en cuenta que podría no estar configurado para trabajar con esta aplicación.", "core.login.notloggedin": "Ha de entrar al sitio.", @@ -1257,11 +1337,16 @@ "core.login.problemconnectingerrorcontinue": "Compruebe de nuevo que ha introducido correctamente la dirección del sitio.", "core.login.profileinvaliddata": "Valor no válido", "core.login.recaptchachallengeimage": "reto reCAPTCHA", + "core.login.recaptchaexpired": "La verificación ha expirado. Contesta nuevamente la pregunta de seguridad.", + "core.login.recaptchaincorrect": "La respuesta a la pregunta de seguridad es incorrecta.", "core.login.reconnect": "Reconectar", "core.login.reconnectdescription": "El token de autenticación no es válido o ha caducado, ha de volver a conectarse al sitio.", "core.login.reconnectssodescription": "El token de autenticación no es válido o ha caducado, ha de volver a conectarse al sitio. Necesita hacerlo desde un navegador web.", + "core.login.resendemail": "Reenviar correo", + "core.login.searchby": "Buscar por:", "core.login.security_question": "Pregunta de seguridad", "core.login.selectacountry": "Seleccione su país", + "core.login.selectsite": "Por favor seleccione su sitio:", "core.login.signupplugindisabled": "{{$a}} no está habilitado", "core.login.siteaddress": "Dirección del sitio", "core.login.siteinmaintenance": "Su sitio está en modo mantenimiento", @@ -1282,8 +1367,6 @@ "core.mainmenu.changesite": "Cambiar de sitio", "core.mainmenu.help": "Ayuda", "core.mainmenu.logout": "Salir", - "core.mainmenu.mycourses": "Mis cursos", - "core.mainmenu.togglemenu": "Cambiar menú", "core.mainmenu.website": "Página web", "core.maxsizeandattachments": "Tamaño máximo para nuevos archivos: {{$a.size}}, número máximo de archivos adjuntos: {{$a.attachments}}", "core.min": "minutos", @@ -1369,8 +1452,10 @@ "core.quotausage": "Actualmente has utilizado {{$a.used}} de tu {{$a.total}} límite.", "core.redirectingtosite": "Será redirigido al sitio.", "core.refresh": "Recargar", + "core.remove": "Quitar", "core.required": "Obligatorio", "core.requireduserdatamissing": "En este perfil de usuario faltan datos requeridos. Por favor, rellene estos datos e inténtelo otra vez.
{{$a}}", + "core.resources": "Recursos", "core.restore": "Restaurar", "core.retry": "Reintentar", "core.save": "Guardar", @@ -1387,6 +1472,7 @@ "core.settings.appready": "App preparada", "core.settings.cannotsyncoffline": "No puede sincronizarse fuera de línea.", "core.settings.cannotsyncwithoutwifi": "No puede sincronizarse porque las configuraciones actuales solamente permiten sincronizar cuando está conectado a Wi-Fi. Por favor, conéctese a una red Wi-Fi.", + "core.settings.compilationinfo": "Información de compilación", "core.settings.cordovadevicemodel": "Modelo de dispositivo Cordova", "core.settings.cordovadeviceosversion": "Versión de OS de dispositivo Cordova", "core.settings.cordovadeviceplatform": "Plataforma de dispositivo Cordova", @@ -1455,6 +1541,19 @@ "core.sizetb": "TB", "core.sorry": "Disculpe...", "core.sortby": "Ordenar por", + "core.strftimedate": "%d de %B de %Y", + "core.strftimedatefullshort": "%d/%m/%y", + "core.strftimedateshort": "%d de %B", + "core.strftimedatetime": "%d de %B de %Y, %H:%M", + "core.strftimedatetimeshort": "%d/%m/%Y %H:%M", + "core.strftimedaydate": "%A, %d de %B de %Y", + "core.strftimedaydatetime": "%A, %d de %B de %Y, %H:%M", + "core.strftimedayshort": "%A, %d %B", + "core.strftimedaytime": "%a, %H:%M", + "core.strftimemonthyear": "%B %Y", + "core.strftimerecent": "%d de %b, %H:%M", + "core.strftimerecentfull": "%a, %d de %b de %Y, %H:%M", + "core.strftimetime": "%H:%M", "core.submit": "Enviar", "core.success": "Éxito", "core.tablet": "Tablet", @@ -1484,6 +1583,7 @@ "core.user.editingteacher": "Profesor", "core.user.email": "Dirección de correo", "core.user.emailagain": "Correo (de nuevo)", + "core.user.errorloaduser": "Error cargando el usuario.", "core.user.firstname": "Nombre", "core.user.interests": "Intereses", "core.user.lastname": "Apellido(s)", @@ -1502,6 +1602,8 @@ "core.userdetails": "Detalles de usuario", "core.users": "Usuarios", "core.view": "Vista", + "core.viewcode": "Ver código", + "core.vieweditor": "Ver editor", "core.viewprofile": "Ver perfil", "core.warningofflinedatadeleted": "Los datos fuera de línea de {{component}} '{{name}}' han sido borrados. {{error}}", "core.whatisyourage": "¿Qué edad tiene?", diff --git a/src/assets/lang/eu.json b/src/assets/lang/eu.json index 2825ca48d..b4fd38f8b 100644 --- a/src/assets/lang/eu.json +++ b/src/assets/lang/eu.json @@ -1,15 +1,61 @@ { + "addon.badges.alignment": "Konpetentzia", "addon.badges.badgedetails": "Dominaren xehetasunak", "addon.badges.badges": "Dominak", + "addon.badges.bendorsement": "Onarpena", + "addon.badges.claimcomment": "Onarpenaren iruzkina", + "addon.badges.claimid": "Erreklamatzeko URLa", "addon.badges.contact": "Kontaktua", "addon.badges.dateawarded": "Emate-data", "addon.badges.expired": "Iraungita", "addon.badges.expirydate": "Epemugaren data", + "addon.badges.imageauthoremail": "Irudiaren egilearen e-posta", + "addon.badges.imageauthorname": "Irudiaren egilearen izena", + "addon.badges.imageauthorurl": "Irudiaren egilearen URLa", + "addon.badges.imagecaption": "Irudi-argazkia", "addon.badges.issuancedetails": "Dominaren iraungitzea", "addon.badges.issuerdetails": "Emailearen xehetasunak", + "addon.badges.issueremail": "E-posta", "addon.badges.issuername": "Emailearen izena", + "addon.badges.issuerurl": "Emailearen URLa", + "addon.badges.language": "Hizkuntza", + "addon.badges.noalignment": "Dominak ez du konpetentziarik ezarrita.", "addon.badges.nobadges": "Ez dago dominarik eskura.", + "addon.badges.norelated": "Dominak ez du erlazionatutako beste dominarik.", "addon.badges.recipientdetails": "Jasotzailearen zehaztasunak", + "addon.badges.relatedbages": "Erlazionatutako dominak", + "addon.badges.version": "Bertsioa", + "addon.badges.warnexpired": "(Domina hau iraungita dago!)", + "addon.block_activitymodules.pluginname": "Jarduerak", + "addon.block_myoverview.all": "Guztiak", + "addon.block_myoverview.favourites": "Nabarmendua", + "addon.block_myoverview.future": "Etorkizunean", + "addon.block_myoverview.hiddencourses": "Ezkutuan", + "addon.block_myoverview.inprogress": "Martxan", + "addon.block_myoverview.lastaccessed": "Azken sarrera", + "addon.block_myoverview.morecourses": "Ikastaro gehiago", + "addon.block_myoverview.nocourses": "Ez dago ikastarorik", + "addon.block_myoverview.past": "Iraganean", + "addon.block_myoverview.pluginname": "Ikuspegi orokorra", + "addon.block_myoverview.title": "Ikastaroaren izena", + "addon.block_recentlyaccessedcourses.nocourses": "Ez dago duela gutxiko ikastarorik", + "addon.block_recentlyaccessedcourses.pluginname": "Duela gutxi bisitatutako ikastaroak", + "addon.block_recentlyaccesseditems.noitems": "Ez dago duela gutxiko elementurik", + "addon.block_recentlyaccesseditems.pluginname": "Duela gutxi bisitatutako elementuak", + "addon.block_sitemainmenu.pluginname": "Menu nagusia", + "addon.block_starredcourses.nocourses": "Ez dago nabarmendutako ikastarorik", + "addon.block_starredcourses.pluginname": "Nabarmendutako ikastaroak", + "addon.block_timeline.duedate": "Entregatze-data", + "addon.block_timeline.next30days": "Datozen 30 egunetan", + "addon.block_timeline.next3months": "Datozen 3 hilabetetan", + "addon.block_timeline.next6months": "Datozen 6 hilabetetan", + "addon.block_timeline.next7days": "Datozen 7 egunetan", + "addon.block_timeline.nocoursesinprogress": "Ez dago ikastarorik martxa", + "addon.block_timeline.noevents": "Ez da jarduerarik programatu", + "addon.block_timeline.overdue": "Atzeratuta", + "addon.block_timeline.pluginname": "Kronologia", + "addon.block_timeline.sortbycourses": "Ordenatu ikastaroen arabera", + "addon.block_timeline.sortbydates": "Ordenatu daten arabera", "addon.calendar.calendar": "Egutegia", "addon.calendar.calendarevents": "Egutegiko gertakariak", "addon.calendar.defaultnotificationtime": "Berezko jakinarazpen-ordua", @@ -30,30 +76,30 @@ "addon.calendar.typesite": "Web guneko ekitaldia", "addon.calendar.typeuser": "Erabiltzailearen ekitaldia", "addon.competency.activities": "Jarduerak", - "addon.competency.competencies": "Gaitasunak", - "addon.competency.competenciesmostoftennotproficientincourse": "Ikastaro honetan sarriago ez-gai diren gaitasunak", - "addon.competency.coursecompetencies": "Ikastaroko gaitasunak", - "addon.competency.coursecompetencyratingsarenotpushedtouserplans": "Ikastaro honetako gaitasunen kalifikazioek ez dute ikasketa-planean eragiten.", - "addon.competency.coursecompetencyratingsarepushedtouserplans": "Ikastaro honetan gaitasunen kalifikazioek ikasketa-planak berehala eguneratzen dituzte.", - "addon.competency.crossreferencedcompetencies": "Erreferentzia gurutzatuko gaitasunak", + "addon.competency.competencies": "Konpetentziak", + "addon.competency.competenciesmostoftennotproficientincourse": "Ikastaro honetan sarriago ez-gai diren konpetentziak", + "addon.competency.coursecompetencies": "Ikastaroko konpetentziak", + "addon.competency.coursecompetencyratingsarenotpushedtouserplans": "Ikastaro honetako konpetentzien kalifikazioek ez dute ikasketa-planean eragiten.", + "addon.competency.coursecompetencyratingsarepushedtouserplans": "Ikastaro honetan konpetentzien kalifikazioek ikasketa-planak berehala eguneratzen dituzte.", + "addon.competency.crossreferencedcompetencies": "Erreferentzia gurutzatuko konpetentziak", "addon.competency.duedate": "Entregatze-data", "addon.competency.errornocompetenciesfound": "Ez da gaitasunik aurkitu", "addon.competency.evidence": "Ebidentzia", - "addon.competency.evidence_competencyrule": "Gaitasun-araua bete da.", + "addon.competency.evidence_competencyrule": "Konpetentzia-araua bete da.", "addon.competency.evidence_coursecompleted": "'{{$a}}' ikastaroa osatu da.", "addon.competency.evidence_coursemodulecompleted": "'{{$a}}' jarduera osatu da.", "addon.competency.evidence_courserestored": "Puntuazioak '{{$a}}' ikastaroarekin batera berreskuratu ziren.", "addon.competency.evidence_evidenceofpriorlearninglinked": "Aurretik ikasitakoaren '{{$a}}' ebidentzia estekatu da.", "addon.competency.evidence_evidenceofpriorlearningunlinked": "Aurretik ikasitakoaren '{{$a}}' ebidentziaren esteka kendu da.", - "addon.competency.evidence_manualoverride": "Gaitasunen puntuazioa eskuz ezarri da.", - "addon.competency.evidence_manualoverrideincourse": "Gaitasunen puntuazioa eskuz ezarri da '{{$a}}' ikastaroan.", - "addon.competency.evidence_manualoverrideinplan": "Gaitasunen puntuazioa eskuz ezarri da '{{$a}}' ikasketa-planean.", - "addon.competency.learningplancompetencies": "Ikasketa-planaren gaitasunak", + "addon.competency.evidence_manualoverride": "Konpetentzien puntuazioa eskuz ezarri da.", + "addon.competency.evidence_manualoverrideincourse": "Konpetentzien puntuazioa eskuz ezarri da '{{$a}}' ikastaroan.", + "addon.competency.evidence_manualoverrideinplan": "Konpetentzien puntuazioa eskuz ezarri da '{{$a}}' ikasketa-planean.", + "addon.competency.learningplancompetencies": "Ikasketa-planaren konpetentziak", "addon.competency.learningplans": "Ikasketa-planak", "addon.competency.myplans": "Nire ikasketa-planak", "addon.competency.noactivities": "Ez dago jarduerarik", "addon.competency.nocompetencies": "Gaitasunik ez", - "addon.competency.nocrossreferencedcompetencies": "Ez dago gaitasun honekiko erreferentzia gurutzatua duen beste gaitasunik.", + "addon.competency.nocrossreferencedcompetencies": "Ez dago konpetentzia honekiko erreferentzia gurutzatua duen beste konpetentziarik.", "addon.competency.noevidence": "Ez dago ebidentziarik", "addon.competency.noplanswerecreated": "Ez da ikasketa-planik sortu.", "addon.competency.path": "Bidea:", @@ -72,8 +118,8 @@ "addon.competency.usercompetencystatus_inreview": "Berrikusten", "addon.competency.usercompetencystatus_waitingforreview": "Berrikusketaren zain", "addon.competency.userplans": "Ikasketa-planak", - "addon.competency.xcompetenciesproficientoutofy": "{{$a.y}} gaitasunetik {{$a.x}} gai dira", - "addon.competency.xcompetenciesproficientoutofyincourse": "Ikastaro honetako {{$a.y}} gaitasunetik {{$a.x}}-tan zara gai.", + "addon.competency.xcompetenciesproficientoutofy": "{{$a.y}} konpetentziatik {{$a.x}} gai dira", + "addon.competency.xcompetenciesproficientoutofyincourse": "Ikastaro honetako {{$a.y}} konpetentziatik {{$a.x}}-tan zara gai.", "addon.coursecompletion.complete": "Osoa", "addon.coursecompletion.completecourse": "Ikastaroa osatu ", "addon.coursecompletion.completed": "Osatuta", @@ -83,15 +129,15 @@ "addon.coursecompletion.coursecompletion": "Ikastaro-osaketa", "addon.coursecompletion.criteria": "Irizpidea", "addon.coursecompletion.criteriagroup": "Irizpide-multzoa", - "addon.coursecompletion.criteriarequiredall": "Beheko irizpide guztiak dira beharrezko.", - "addon.coursecompletion.criteriarequiredany": "Beheko hainbat irizpide dira beharrezko.", + "addon.coursecompletion.criteriarequiredall": "Beheko irizpide guztiak dira beharrezko", + "addon.coursecompletion.criteriarequiredany": "Beheko hainbat irizpide dira beharrezko", "addon.coursecompletion.inprogress": "Ari da", "addon.coursecompletion.manualselfcompletion": "Norberak eskuz osatu", "addon.coursecompletion.notyetstarted": "Ez da hasi", "addon.coursecompletion.pending": "Egin gabe", "addon.coursecompletion.required": "Beharrezkoa", "addon.coursecompletion.requiredcriteria": "Irizpidea behar da", - "addon.coursecompletion.requirement": "Eskakizuna", + "addon.coursecompletion.requirement": "Betekizuna", "addon.coursecompletion.status": "Egoera", "addon.coursecompletion.viewcoursereport": "Ikastaroaren txostena ikusi", "addon.files.couldnotloadfiles": "Ezin izan da fitxategien zerrenda kargatu.", @@ -101,34 +147,80 @@ "addon.files.privatefiles": "Fitxategi pribatuak", "addon.files.sitefiles": "Guneko fitxategiak", "addon.messageoutput_airnotifier.processorsettingsdesc": "Gailuak konfiguratu", + "addon.messages.acceptandaddcontact": "Onartu eta gehitu kontaktuetara", "addon.messages.addcontact": "Gehitu kontaktua", - "addon.messages.blockcontact": "Blokeatu kontaktua", - "addon.messages.blockcontactconfirm": "Kontaktu honen mezuak jasotzeari utziko diozu.", - "addon.messages.blocknoncontacts": "Ez utzi kontaktu ez direnei niri mezuak bidaltzen", + "addon.messages.addcontactconfirm": "Ziur al zaude {{$a}} gehitu nahi duzula zure kontaktuetara?", + "addon.messages.addtofavourites": "Izarra", + "addon.messages.addtoyourcontacts": "Gehitu kontaktuetara", + "addon.messages.blocknoncontacts": "Ez utzi niri mezuak bidaltzen kontaktu ez direnei", + "addon.messages.blockuser": "Blokeatu erabiltzailea", + "addon.messages.blockuserconfirm": "Ziur al zaude {{$a}} blokeatu nahi duzula?", + "addon.messages.contactableprivacy": "Onartu hauen mezuak:", + "addon.messages.contactableprivacy_coursemember": "Nire kontaktuak eta nire ikastaroetako edonor", + "addon.messages.contactableprivacy_onlycontacts": "Nire kontaktuak bakarrik", + "addon.messages.contactableprivacy_site": "Guneko edonor", + "addon.messages.contactblocked": "Kontaktua blokeatu da", "addon.messages.contactlistempty": "Kontaktu zerrenda hutsik dago", "addon.messages.contactname": "Kontaktuaren izena", + "addon.messages.contactrequestsent": "Kontaktu-eskaria bidali da", "addon.messages.contacts": "Kontaktuak", + "addon.messages.decline": "Ez onartu", + "addon.messages.deleteallconfirm": "Ziur zaude elkarrizketa oso hau ezabatu nahi duzula? Honek ez die elkarrizketa beste partaideei ezabatuko.", + "addon.messages.deleteconversation": "Ezabatu elkarrizketa", + "addon.messages.deletemessage": "Ezabatu mezua", + "addon.messages.deletemessageconfirmation": "Ziur mezu hau ezabatu nahi duzula? Mezua zure mezuen historiatik baino ez da ezabatuko eta bidali edo jaso zuten beste erabiltzaileek oraindik ikusgai izango dute.", "addon.messages.errordeletemessage": "Errorea mezua ezabatzean.", "addon.messages.errorwhileretrievingcontacts": "Errore bat gertatu da kontaktuak zerbitzaritik jasotzean.", "addon.messages.errorwhileretrievingdiscussions": "Errore bat gertatu da elkarrizketak zerbitzaritik jasotzean.", "addon.messages.errorwhileretrievingmessages": "Errore bat gertatu da mezuak zerbitzaritik jasotzean.", + "addon.messages.errorwhileretrievingusers": "Errorea erabiltzaileak zerbitzaritik jasotzerakoan", + "addon.messages.groupconversations": "Taldea", + "addon.messages.groupinfo": "Taldearen informazioa", + "addon.messages.individualconversations": "Pribatua", + "addon.messages.info": "Informazioa", + "addon.messages.isnotinyourcontacts": "{{$a}} ez dago zure kontaktuetan", "addon.messages.message": "Mezua", "addon.messages.messagenotsent": "Mezua ez da bidali. Mesedez, saiatu beranduago.", "addon.messages.messagepreferences": "Mezuen hobespenak", "addon.messages.messages": "Mezuak", "addon.messages.newmessage": "Mezu berria", "addon.messages.newmessages": "Mezu beriak", - "addon.messages.nomessages": "Mezurik ez", + "addon.messages.nocontactrequests": "Ez dago kontaktu-eskaririk", + "addon.messages.nocontactsgetstarted": "Ez dago kontakturik", + "addon.messages.nofavourites": "Ez dago nabarmendutako elkarrizketarik", + "addon.messages.nogroupconversations": "Taldeko elkarrizketarik ez", + "addon.messages.noindividualconversations": "Elkarrizketa pribaturik ez", + "addon.messages.nomessagesfound": "Ez da mezurik aurkitu", + "addon.messages.noncontacts": "Kontaktu ez direnak", "addon.messages.nousersfound": "Ez da erabiltzailerik aurkitu", + "addon.messages.numparticipants": "{{$a}} partaide", "addon.messages.removecontact": "Ezabatu kontaktua", - "addon.messages.removecontactconfirm": "Kontaktua zure kontaktuen zerrendatik ezabatuko da.", + "addon.messages.removecontactconfirm": "Ziur al zaude {{$a}} kendu nahi duzula zure kontaktuetatik?", + "addon.messages.removefromfavourites": "Kendu izarra", + "addon.messages.removefromyourcontacts": "Ezabatu kontaktuetatik", + "addon.messages.requests": "Eskariak", + "addon.messages.requirecontacttomessage": "{{$a}}-(r)i egin behar diozu kontaktu-eskaria mezua bidali ahal izateko.", + "addon.messages.searchcombined": "Jendea eta mezuak bilatu", + "addon.messages.searchnocontactsfound": "Ez da kontakturik aurkitu", + "addon.messages.searchnomessagesfound": "Ez da mezurik aurkitu", + "addon.messages.searchnononcontactsfound": "Ez da \"ez-kontaktua\" aurkitu", + "addon.messages.sendcontactrequest": "Bidali kontaktu-eskaria", + "addon.messages.showdeletemessages": "Erakutsi ezabatutako mezuak", "addon.messages.type_blocked": "Blokeatuta", "addon.messages.type_offline": "Lineaz kanpo", "addon.messages.type_online": "Online", "addon.messages.type_search": "Bilaketaren emaitzak", "addon.messages.type_strangers": "Beste batzuk", - "addon.messages.unblockcontact": "Desblokeatu kontaktua", + "addon.messages.unabletomessage": "Erabiltzaile honi ezin diozu mezurik bidali", + "addon.messages.unblockuser": "Desblokeatu erabiltzailea", + "addon.messages.unblockuserconfirm": "Ziur al zaude {{$a}} desblokeatu nahi duzula?", + "addon.messages.userwouldliketocontactyou": "{{$a}}-(e)k zurekin jarri nahi du harremanetan", + "addon.messages.warningconversationmessagenotsent": "Ezin izan d(ir)a mezua(k) {{conversation}} elkarrizketara bidali. {{error}}", "addon.messages.warningmessagenotsent": "Ezin izan d(ir)a mezua(k) bidali {{user}} erabiltzaileari. {{error}}", + "addon.messages.wouldliketocontactyou": "Zurekin harremanetan jarri nahi luke", + "addon.messages.you": "Zu:", + "addon.messages.youhaveblockeduser": "Iraganean erabiltzaile hau blokeatu duzu", + "addon.messages.yourcontactrequestpending": "{{$a}}-(r)en kontaktua izateko eskaria zain dago", "addon.mod_assign.acceptsubmissionstatement": "Mesedez onartu bidalketa-sententzia.", "addon.mod_assign.addattempt": "Baimendu beste saiakera bat", "addon.mod_assign.addnewattempt": "Gehitu saiakera berria", @@ -165,7 +257,9 @@ "addon.mod_assign.grade": "Kalifikazioa", "addon.mod_assign.graded": "Kalifikatua", "addon.mod_assign.gradedby": "Nork kalifikatua", + "addon.mod_assign.gradedfollowupsubmit": "Kalifikatuta - bidalketaren jakinarazpena jaso da", "addon.mod_assign.gradedon": "Noiz kalifikatua", + "addon.mod_assign.gradelocked": "Kalifikazio hau blokeatu edo aldatu da kalifikazio-liburuan.", "addon.mod_assign.gradenotsynced": "Kalifikazioa ez da sinkronizatu", "addon.mod_assign.gradeoutof": "Kalifikazioa ({{$a}}-(e)ra arte)", "addon.mod_assign.gradingstatus": "Kalifikazioaren egoera", @@ -180,6 +274,7 @@ "addon.mod_assign.markingworkflowstatereadyforrelease": "Ikusgai jartzeko prest", "addon.mod_assign.markingworkflowstatereadyforreview": "Kalifikazioa osatua", "addon.mod_assign.markingworkflowstatereleased": "Ikusgai", + "addon.mod_assign.modulenameplural": "Zereginak", "addon.mod_assign.multipleteams": "Talde bateko baino gehiagoko partaidea", "addon.mod_assign.multipleteams_desc": "Zereginak taldekako bidalketa behartzen du. Zu talde bat baino gehiagoko kidea zara. Bidalketak egin ahal izateko talde bakar bateko kidea izan behar duzu. Mesedez jarri harremanetan zure irakaslearekin zure talde-partaidetzak aldatu ditzan.", "addon.mod_assign.noattempt": "Saiakerarik ez", @@ -234,9 +329,10 @@ "addon.mod_assign_submission_file.pluginname": "Fitxategi-bidalketak", "addon.mod_assign_submission_onlinetext.pluginname": "Baimendu on-line testuen bidalketa", "addon.mod_book.errorchapter": "Errorea liburuaren atala irakurtzean.", + "addon.mod_book.modulenameplural": "Liburuak", "addon.mod_chat.beep": "Abisua", "addon.mod_chat.currentusers": "Oraingo erabiltzaileak", - "addon.mod_chat.enterchat": "Sakatu hemen txat-gelara sartzeko", + "addon.mod_chat.enterchat": "Egin klik hemen txat-gelara sartzeko", "addon.mod_chat.entermessage": "Idatzi zure mezua", "addon.mod_chat.errorwhileconnecting": "Errorea txatera konektatzean.", "addon.mod_chat.errorwhilegettingchatdata": "Errorea txataren datuak eskuratzean.", @@ -246,6 +342,7 @@ "addon.mod_chat.messagebeepsyou": "{{$a}}-(e)k dio: Aizu! Hemen nago!", "addon.mod_chat.messageenter": "{{$a}} oraintxe sartu da gelan", "addon.mod_chat.messageexit": "{{$a}} irten egin da gelatik", + "addon.mod_chat.modulenameplural": "Txat-gelak", "addon.mod_chat.mustbeonlinetosendmessages": "Online egon behar zara mezuak bidali ahal izateko.", "addon.mod_chat.nomessages": "Ez dago mezurik oraindik", "addon.mod_chat.send": "Bidali", @@ -256,6 +353,7 @@ "addon.mod_choice.errorgetchoice": "Errorea kontsultaren datuak eskuratzean.", "addon.mod_choice.expired": "Sentitzen dugu, jarduera hau {{$a}}(e)an itxi zen eta dagoeneko ez dago eskuragarri.", "addon.mod_choice.full": "(Beteta)", + "addon.mod_choice.modulenameplural": "Kontsultak", "addon.mod_choice.noresultsviewable": "Emaitzak ezin dira orain ikusi", "addon.mod_choice.notopenyet": "Sentitzen dugu, baina jarduera hau ez dago erabiltzeko moduan {{$a}} arte.", "addon.mod_choice.numberofuser": "Erantzun-kopurua", @@ -293,8 +391,10 @@ "addon.mod_data.errormustsupplyvalue": "Hemen balio bat eman behar duzu.", "addon.mod_data.expired": "Sentitzen dugu, jarduera hau {{$a}} datan itxi zen eta dagoeneko ez dago eskuragarri", "addon.mod_data.fields": "Eremuak", + "addon.mod_data.foundrecords": "Erregistroak: {{$a.num}}/{{$a.max}} (Garbitu iragazkiak)", "addon.mod_data.latlongboth": "Latitudea eta longitudea beharrekoak dira.", "addon.mod_data.menuchoose": "Aukeratu...", + "addon.mod_data.modulenameplural": "Datu-baseak", "addon.mod_data.more": "Gehiago", "addon.mod_data.nomatch": "Ez da sarrera egokirik aurkitu!", "addon.mod_data.norecords": "Datu-basean sarrerarik ez", @@ -309,8 +409,8 @@ "addon.mod_data.search": "Bilatu", "addon.mod_data.selectedrequired": "Aukeratutako guztia beharrezko da", "addon.mod_data.single": "Ikusi banaka", - "addon.mod_data.timeadded": "Gehitu zeneko unea", - "addon.mod_data.timemodified": "Denbora aldatu da", + "addon.mod_data.timeadded": "Gehitze-data", + "addon.mod_data.timemodified": "Aldatze-data", "addon.mod_data.usedate": "Sartu bilaketan.", "addon.mod_feedback.analysis": "Analisia", "addon.mod_feedback.anonymous": "Anonimoa", @@ -326,6 +426,7 @@ "addon.mod_feedback.feedbackopen": "Noiztik baimendu erantzunak", "addon.mod_feedback.mapcourses": "Esleitu feedback-a ikastaroetarako", "addon.mod_feedback.mode": "Modua", + "addon.mod_feedback.modulenameplural": "Inkesta", "addon.mod_feedback.next_page": "Hurrengo orria", "addon.mod_feedback.non_anonymous": "Erabiltzaile-izena erantzunekin erakutsiko da", "addon.mod_feedback.non_anonymous_entries": "Sarrera anonimorik ez ({{$a}})", @@ -346,6 +447,7 @@ "addon.mod_feedback.started": "Hasita", "addon.mod_feedback.this_feedback_is_already_submitted": "Dagoeneko egina duzu jarduera hau.", "addon.mod_folder.emptyfilelist": "Ez dago fitxategirik erakusteko.", + "addon.mod_folder.modulenameplural": "Karpetak", "addon.mod_forum.addanewdiscussion": "Gehitu eztabaidagai berria", "addon.mod_forum.addanewquestion": "Gehitu galdera berria", "addon.mod_forum.addanewtopic": "Gehitu gai berria", @@ -368,9 +470,10 @@ "addon.mod_forum.modeflatnewestfirst": "Erantzunak era lauan erakutsi, berrienak lehen", "addon.mod_forum.modeflatoldestfirst": "Erantzunak era lauan erakutsi, zaharrenak lehen", "addon.mod_forum.modenested": "Erantzunak hariaren arabera erakutsi", + "addon.mod_forum.modulenameplural": "Foroak", "addon.mod_forum.numdiscussions": "{{numdiscussions}} elkarrizketa", "addon.mod_forum.numreplies": "{{numreplies}} erantzun", - "addon.mod_forum.posttoforum": "Mezua forora bidali", + "addon.mod_forum.posttoforum": "Bidali mezua forora", "addon.mod_forum.re": "Er:", "addon.mod_forum.refreshdiscussions": "Freskatu eztabaidak", "addon.mod_forum.refreshposts": "Freskatu eztabaidetako mezuak", @@ -403,9 +506,11 @@ "addon.mod_glossary.fillfields": "Kontzeptua eta azalpena derrigorrezko datuak dira", "addon.mod_glossary.fullmatch": "Hitz osoak bakarrik bilatu", "addon.mod_glossary.linking": "Autoesteka", + "addon.mod_glossary.modulenameplural": "Glosategiak", "addon.mod_glossary.noentriesfound": "Ez da sarrerarik aurkitu", "addon.mod_glossary.searchquery": "Egin bilaketa", "addon.mod_imscp.deploymenterror": "Errorea eduki-paketea egitean!", + "addon.mod_imscp.modulenameplural": "IMS eduki-paketeak", "addon.mod_imscp.showmoduledescription": "Deskribapena erakutsi", "addon.mod_lesson.answer": "Erantzuna", "addon.mod_lesson.attempt": "Saiakera: {{$a}}", @@ -425,7 +530,7 @@ "addon.mod_lesson.detailedstats": "Estatistika zehatzak", "addon.mod_lesson.didnotanswerquestion": "Galdera honi ez diozu erantzunik eman.", "addon.mod_lesson.displayofgrade": "Kalifikazioa erakutsi (ikasleentzat soilik)", - "addon.mod_lesson.displayscorewithessays": "

Automatikoki kalifikatutako galderetan hau da zure puntuazioa: {{$a.score}}\nposible zen gehienezko puntuazio honetatik: {{$a.tempmaxgrade}}.

\n

{{$a.essayquestions}} galdera(k) beranduago kalifikatuko dira eta zure azken emaitzari erantsiko zaizkio aurrerago.

\n

Oraingoz, eta entsegu-galdera(k) kontuan hartu gabe, zure emaitza {{$a.score}} (e)koa da {{$a.grade}} (e)tik

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

Automatikoki kalifikatutako galderetan hau da zure puntuazioa: {{$a.score}}\nposible zen gehienezko puntuazio honetatik: {{$a.tempmaxgrade}}.

\n

{{$a.essayquestions}} entsegu motako galdera(k) beranduago kalifikatuko dira eta zure azken emaitzari erantsiko zaizkio aurrerago.

\n

Oraingoz, eta entsegu-galdera(k) kontuan hartu gabe, zure emaitza {{$a.score}} (e)koa da {{$a.grade}} (e)tik

", "addon.mod_lesson.displayscorewithoutessays": "Zure puntuazioa hau da: {{$a.score}} (gehienezkoa hau zen: {{$a.grade}}).", "addon.mod_lesson.emptypassword": "Pasahitza ezin da hutsik egon", "addon.mod_lesson.enterpassword": "Idatzi pasahitza, mesedez:", @@ -439,7 +544,7 @@ "addon.mod_lesson.grade": "Kalifikazioa", "addon.mod_lesson.highscore": "Puntuazio altua", "addon.mod_lesson.hightime": "Denbora altua", - "addon.mod_lesson.leftduringtimed": "Denbora-epe finkoko ikasgaia eten da.
Berriz hasteko \"Jarraitu\" botoia sakatu, mesedez.", + "addon.mod_lesson.leftduringtimed": "Denbora-epe finkoko ikasgaia eten da.
Berriz hasteko egin klik \"Jarraitu\" botoian, mesedez.", "addon.mod_lesson.leftduringtimednoretake": "Denbora-epe finkoko ikasgaia eten da eta
ez dago berriz hasteko edo jarraitzeko baimenik.", "addon.mod_lesson.lessonmenu": "Ikasgaiaren menua", "addon.mod_lesson.lessonstats": "Ikasgaiaren estatistikak", @@ -449,6 +554,7 @@ "addon.mod_lesson.lowtime": "Denbora baxua", "addon.mod_lesson.maximumnumberofattemptsreached": "Gehienezko saiakera-kopurura iritsi zara. Hurrengo orrira jauzi", "addon.mod_lesson.modattemptsnoteacher": "Ikaslearen berrikuspena soilik ikasleen eskura dago.", + "addon.mod_lesson.modulenameplural": "Ikasgaiak", "addon.mod_lesson.noanswer": "Galdera batek edo gehiagok ez du erantzunik. Mesedez, joan atzera eta osatu.", "addon.mod_lesson.nolessonattempts": "Ez da ikasgai hau praktikatzeko saiakerarik egin.", "addon.mod_lesson.nolessonattemptsgroup": "{{$a}} taldeko partaideek ez dute saiakerarik egin ikasgai honetan.", @@ -466,7 +572,7 @@ "addon.mod_lesson.question": "Galdera", "addon.mod_lesson.rawgrade": "Puntuazio gordina", "addon.mod_lesson.reports": "Txostenak", - "addon.mod_lesson.response": "Ebazpena", + "addon.mod_lesson.response": "Erantzuna", "addon.mod_lesson.retakefinishedinsync": "Lineaz kanpoko saiakera bat sinkronizatu da. Berrikusi nahi duzu?", "addon.mod_lesson.retakelabelfull": "{{retake}}: {{grade}} {{timestart}} ({{duration}})", "addon.mod_lesson.retakelabelshort": "{{retake}}: {{grade}} {{timestart}}", @@ -493,7 +599,9 @@ "addon.mod_lti.errorgetlti": "Errorea moduluaren datuak eskuratzean.", "addon.mod_lti.errorinvalidlaunchurl": "Hasierako URLa ez da baliagarria", "addon.mod_lti.launchactivity": "Abiarazi jarduera", + "addon.mod_lti.modulenameplural": "Kanpoko tresnak", "addon.mod_page.errorwhileloadingthepage": "Errorea orriaren edukia kargatzean.", + "addon.mod_page.modulenameplural": "Orriak", "addon.mod_quiz.attemptfirst": "Lehen saiakera", "addon.mod_quiz.attemptlast": "Azken saiakera", "addon.mod_quiz.attemptnumber": "Saiakera", @@ -528,6 +636,7 @@ "addon.mod_quiz.grademethod": "Kalifikazio-metodoa", "addon.mod_quiz.gradesofar": "{{$a.method}}: {{$a.mygrade}} / {{$a.quizgrade}}.", "addon.mod_quiz.marks": "Puntuak", + "addon.mod_quiz.modulenameplural": "Galdetegiak", "addon.mod_quiz.mustbesubmittedby": "Saiakera hau {{$a}}-(e)k bidali behar du.", "addon.mod_quiz.noquestions": "Ez da galderarik gehitu oraindik", "addon.mod_quiz.noreviewattempt": "Ez duzu saiakera hau berrikusteko baimenik.", @@ -540,7 +649,7 @@ "addon.mod_quiz.overdue": "Atzeratuta", "addon.mod_quiz.overduemustbesubmittedby": "Saiakera hau epez kanpo dago. Dagoeneko bidalita egon beharko luke. Galdetegia kalifikatua izatea nahi baduzu, {{$a}}-rako bidali behar duzu. Data horretarako bidaltzen ez baduzu bidaltzen, saiakera hau ez da aintzat hartuko.", "addon.mod_quiz.preview": "Aurreikusi", - "addon.mod_quiz.previewquiznow": "Galdetegia orain aurreikusi", + "addon.mod_quiz.previewquiznow": "Aurreikusi galdetegia orain", "addon.mod_quiz.question": "Galdera", "addon.mod_quiz.quiznavigation": "Galdetegiaren nabigazioa", "addon.mod_quiz.quizpassword": "Galdetegiaren pasahitza", @@ -550,7 +659,7 @@ "addon.mod_quiz.review": "Birpasatu", "addon.mod_quiz.reviewofattempt": "{{$a}} saiakeraren berrikusketa", "addon.mod_quiz.reviewofpreview": "Aurrebistaren berrikusketa", - "addon.mod_quiz.showall": "Galdera guztiak orri batean erakutsi", + "addon.mod_quiz.showall": "Erakutsi galdera guztiak orri bakarrean", "addon.mod_quiz.showeachpage": "Erakutsi orri bat aldi bakoitzean", "addon.mod_quiz.startattempt": "Hasi saiakera", "addon.mod_quiz.startedon": "Noiz hasita", @@ -572,6 +681,7 @@ "addon.mod_quiz.yourfinalgradeis": "Galdetegi honetan zure azken nota {{$a}} da.", "addon.mod_resource.errorwhileloadingthecontent": "Errorea edukia kargatzean.", "addon.mod_resource.modifieddate": "Aldatze-data: {{$a}}", + "addon.mod_resource.modulenameplural": "Fitxategiak", "addon.mod_resource.openthefile": "Ireki fitxategia", "addon.mod_resource.uploadeddate": "Igoera-data: {{$a}}", "addon.mod_scorm.asset": "Baliabidea", @@ -608,6 +718,7 @@ "addon.mod_scorm.incomplete": "Osatu gabea", "addon.mod_scorm.lastattempt": "Osatutako azken saiakera", "addon.mod_scorm.mode": "Modua", + "addon.mod_scorm.modulenameplural": "SCORM paketeak", "addon.mod_scorm.newattempt": "Saiakera berria hasi", "addon.mod_scorm.noattemptsallowed": "Baimendutako saiakera-kopurua", "addon.mod_scorm.noattemptsmade": "Egin duzun saiakera-kopurua", @@ -627,10 +738,12 @@ "addon.mod_survey.errorgetsurvey": "Errorea hausnarketaren datuak eskuratzean.", "addon.mod_survey.ifoundthat": "Hau aurkitu dut", "addon.mod_survey.ipreferthat": "Nahiago dut", + "addon.mod_survey.modulenameplural": "Hausnarketak", "addon.mod_survey.responses": "Erantzunak", "addon.mod_survey.results": "Emaitzak", "addon.mod_survey.surveycompletednograph": "Bete duzu hausnarketa hau", "addon.mod_url.accessurl": "URLan sartu", + "addon.mod_url.modulenameplural": "URLak", "addon.mod_url.pointingtourl": "Baliabideak estekatzen duen URLa", "addon.mod_wiki.cannoteditpage": "Ezin duzu orri hau editatu.", "addon.mod_wiki.createpage": "Sortu orria", @@ -639,6 +752,7 @@ "addon.mod_wiki.errornowikiavailable": "Wiki honek oraindik ez du edukirik.", "addon.mod_wiki.gowikihome": "Joan wikiaren lehen orrira", "addon.mod_wiki.map": "Mapa", + "addon.mod_wiki.modulenameplural": "Wikiak", "addon.mod_wiki.newpagehdr": "Orri berria", "addon.mod_wiki.newpagetitle": "Orri berriaren izenburua", "addon.mod_wiki.nocontent": "Ez dago edukirik orri honetarako", @@ -677,6 +791,7 @@ "addon.mod_workshop.gradinggradecalculated": "Kalkulatutako kalifikazioa ebaluaziorako", "addon.mod_workshop.gradinggradeof": "Ebaluazioaren kalifikazioa ({{$a}}(e)tik)", "addon.mod_workshop.gradinggradeover": "Indargabetu kalifikazioa ebaluaziorako", + "addon.mod_workshop.modulenameplural": "Tailerrak", "addon.mod_workshop.nogradeyet": "Ez da oraindik kalifikatu", "addon.mod_workshop.notassessed": "Ebaluatu gabea", "addon.mod_workshop.notoverridden": "Baliogabetu gabe", @@ -693,6 +808,7 @@ "addon.mod_workshop.submissiongrade": "Bidalketaren kalifikazioa", "addon.mod_workshop.submissiongradeof": "Bidalketaren kalifikazioa ({{$a}}(e)tik)", "addon.mod_workshop.submissionrequiredcontent": "Testuren bat idatzi edo fitxategiren bat gehitu behar duzu.", + "addon.mod_workshop.submissionrequiredtitle": "Izenburu bat idatzi behar duzu.", "addon.mod_workshop.submissionsreport": "Tailerraren bidalketa-txostena", "addon.mod_workshop.submissiontitle": "Izenburua", "addon.mod_workshop.switchphase10": "Aldatu ezarpenak zehazteko aldira", @@ -715,9 +831,9 @@ "addon.mod_workshop_assessment_accumulative.mustchoosegrade": "Kalifikazio bat aukeratu behar duzu alderdi honetarako", "addon.mod_workshop_assessment_comments.dimensioncommentfor": "Iruzkina {{$a}}-(e)rako", "addon.mod_workshop_assessment_comments.dimensionnumber": "{{$a}} alderdia", - "addon.mod_workshop_assessment_numerrors.dimensioncommentfor": "Iruzkina {{$a}}-(e)rako", + "addon.mod_workshop_assessment_numerrors.dimensioncommentfor": "Iruzkina {{$a}}rentzat", "addon.mod_workshop_assessment_numerrors.dimensiongradefor": "Kalifikazioa {{$a}}-(e)rako", - "addon.mod_workshop_assessment_numerrors.dimensionnumber": "{{$a}} baieztapena", + "addon.mod_workshop_assessment_numerrors.dimensionnumber": "{{$a}}. baieztapena", "addon.mod_workshop_assessment_rubric.dimensionnumber": "{{$a}} irizpidea", "addon.mod_workshop_assessment_rubric.mustchooseone": "Elementu hauetako bat aukeratu behar duzu", "addon.notes.addnewnote": "Gehitu ohar berria", @@ -1032,7 +1148,7 @@ "assets.mimetypes.image": "Irudia ({{$a.MIMETYPE2}})", "assets.mimetypes.image/vnd.microsoft.icon": "Windows-eko ikonoa", "assets.mimetypes.text/css": "Kaskadako estilo-orriak (CSS)", - "assets.mimetypes.text/csv": "Komaz banatutako baloreak (CSV)", + "assets.mimetypes.text/csv": "Komaz banatutako balioak (CSV)", "assets.mimetypes.text/html": "HTML dokumentua", "assets.mimetypes.text/plain": "Testu-fitxategia", "assets.mimetypes.text/rtf": "RTF dokumentua", @@ -1041,6 +1157,8 @@ "core.accounts": "Kontuak", "core.add": "Gehitu", "core.agelocationverification": "Adina eta kokapenaren egiaztapena", + "core.ago": "Orain dela {{$a}}", + "core.all": "Guztia(k)", "core.allparticipants": "Partaide guztiak", "core.android": "Android", "core.answer": "Erantzun", @@ -1058,7 +1176,7 @@ "core.choose": "Aukeratu", "core.choosedots": "Aukeratu...", "core.clearsearch": "Bilaketa garbia", - "core.clicktohideshow": "Sakatu zabaltzeko edo tolesteko", + "core.clicktohideshow": "Egin klik zabaltzeko edo tolesteko", "core.clicktoseefull": "Klik egin eduki guztiak ikusteko.", "core.close": "Itxi", "core.comments": "Iruzkinak", @@ -1078,7 +1196,7 @@ "core.confirmdeletefile": "Ziur al zaude fitxategi hau ezabatu nahi duzula?", "core.confirmloss": "Ziur zaude? Aldaketa guztiak galdu egingo dira.", "core.confirmopeninbrowser": "Nabigatzailean ireki nahi duzu?", - "core.considereddigitalminor": "Digitalki adin txikikoa zarela ulertzen da.", + "core.considereddigitalminor": "Gazteegia zara gune honetan kontua sortzeko.", "core.content": "Edukia", "core.contenteditingsynced": "Editatzen ari zaren edukiak sinkronizatu dira.", "core.contentlinks.chooseaccount": "Aukeratu kontua", @@ -1108,17 +1226,21 @@ "core.course.errorgetmodule": "Errore bat gertatu da jardueraren datuak eskuratzean.", "core.course.hiddenfromstudents": "Ezkutuan ikasleentzat", "core.course.hiddenoncoursepage": "Eskuragarri baina ikastaro-orrian erakutsi gabe", + "core.course.manualcompletionnotsynced": "Eskuzko osatzea ez da sinkronizatu.", "core.course.nocontentavailable": "Ez dago edukirik eskuragarri momentu honetan.", "core.course.overriddennotice": "Jarduera honetako zure azken kalifikazioa eskuz egokitu da.", + "core.course.refreshcourse": "Freskatu ikastaroa", "core.course.sections": "Atalak", "core.course.useactivityonbrowser": "Hala ere zure gailuko nabigatzailea erabilita erabil dezakezu.", + "core.course.warningmanualcompletionmodified": "Jardueraren eskuzko osatzea gunean aldatua izan da.", + "core.course.warningofflinemanualcompletiondeleted": "'{{name}}' ikastaroaroko lineaz kanpoko eskuzko osatze batzuk ezabatuak izan dira. {{error}}", "core.coursedetails": "Ikastaro-xehetasunak", + "core.courses.addtofavourites": "Nabarmendu ikastaro hau", "core.courses.allowguests": "Ikastaro honetan bisitariak sar daitezke", "core.courses.availablecourses": "Eskura dauden ikastaroak", "core.courses.cannotretrievemorecategories": "{{$a}}. maila baino sakonagoko kategoriak ezin dira eskuratu.", "core.courses.categories": "Ikastaro-kategoriak", "core.courses.confirmselfenrol": "Ziur zaude ikastaro honetan izena eman nahi duzula?", - "core.courses.courseoverview": "Ikastaroaren ikuspegi orokorra", "core.courses.courses": "Ikastaroak", "core.courses.downloadcourses": "Jaitsi ikastaroak", "core.courses.enrolme": "Matrikulatu nazazu", @@ -1128,28 +1250,24 @@ "core.courses.errorselfenrol": "Errore bat gertatu da matrikulazio automatikoa egitean", "core.courses.filtermycourses": "Nire ikastaroak iragazi", "core.courses.frontpage": "Hasiera-orria", - "core.courses.future": "Etorkizunean", - "core.courses.inprogress": "Martxan", - "core.courses.morecourses": "Ikastaro gehiago", + "core.courses.hidecourse": "Ezkutatu ikuspegian", "core.courses.mycourses": "Nire ikastaroak", + "core.courses.mymoodle": "Aginte-panela", "core.courses.nocourses": "Ez dago ikastaroei buruzko informaziorik", - "core.courses.nocoursesfuture": "Ez dago ikastarorik etorkizunean", - "core.courses.nocoursesinprogress": "Ez dago martxan dagoen ikastarorik", - "core.courses.nocoursesoverview": "Ez dago ikastarorik", - "core.courses.nocoursespast": "Ez dago ikastarorik iraganean", "core.courses.nocoursesyet": "Ez dago ikastarorik kategoria honetan", "core.courses.nosearchresults": "Emaitzarik ez", "core.courses.notenroled": "Ez zaude matrikulatuta ikastaro honetan", "core.courses.notenrollable": "Ezin duzu zeure burua ikastaro honetan matrikulatu.", "core.courses.password": "Matrikulazio-giltza", - "core.courses.past": "Iraganean", "core.courses.paymentrequired": "Ikastaro hau ordainpekoa da", "core.courses.paypalaccepted": "Paypal ordainketak onartu dira", + "core.courses.removefromfavourites": "Ez nabarmendu ikastaro hau", "core.courses.search": "Bilatu", "core.courses.searchcourses": "Bilatu Ikastaroak", "core.courses.searchcoursesadvice": "Bilatu ikastaroak botoia erabil dezakezu ikastaroak topatu eta bisitari gisa sartu edo bertan matrikulatzeko ikastaroak baimentzen badu.", "core.courses.selfenrolment": "Matrikulazio automatikoa", "core.courses.sendpaymentbutton": "Paypal bidezko ordainketa bidali", + "core.courses.show": "Erakutsi ikastaro hau", "core.courses.totalcoursesearchresults": "Ikastaroak guztira: {{$a}}", "core.currentdevice": "Oraingo gailua", "core.datastoredoffline": "Informazioa gailuan gorde da ezin izan delako bidali. Beranduago automatikoki bidaliko da.", @@ -1169,7 +1287,7 @@ "core.dfmediumdate": "LLL", "core.dftimedate": "h[:]mm A", "core.digitalminor": "Digitalki adin txikikoa", - "core.digitalminor_desc": "Gune honetan kontu bat sortzeko, mesedez, eska iezaiozu zeure guraso/tutore bati hurrengo pertsonarekin harremanetan jartzeko.", + "core.digitalminor_desc": "Eska iezaiozu zure guraso/tutore bati hurrengo pertsonarekin harremanetan jartzeko:", "core.discard": "Baztertu", "core.dismiss": "Baztertu", "core.done": "Eginda", @@ -1186,6 +1304,7 @@ "core.errorinvalidform": "Formularioak baliozkoak ez diren datuak dauzka. Mesedez egiaztatu derrigorrezko eremuak bete dituzula eta datuak egokiak direla.", "core.errorinvalidresponse": "Erantzun baliogabea jaso da. Errorea iraunkorra bada mesedez jar zaitez harremanetan zure guneko kudeatzailearekin .", "core.errorloadingcontent": "Errorea edukia kargatzean.", + "core.errorofflinedisabled": "Lineaz kanpoko nabigazioa desgaituta dago zure gunean. App-a erabiltzeko Internetera konektaturik egon behar duzu.", "core.erroropenfilenoapp": "Errorea fitxategia irekitzean: ez da aurkitu fitxategi mota hau irekitzeko app-rik.", "core.erroropenfilenoextension": "Errorea fitxategia irekitzean: fitxategiak ez dauka luzapenik.", "core.erroropenpopup": "Jarduera hau leiho berri bat zabaltzen saiatzen ari da. Hau ez da app honetan onartzen.", @@ -1193,6 +1312,7 @@ "core.errorsync": "Errorea gertatu da sinkronizatzean. Mesedez, saiatu berriz.", "core.errorsyncblocked": "{{$a}} hau ezin izan da orain sinkronizatu prozesu bat martxan dagoelako. Mesedez, saiatu berriz beranduago. Arazoa errepikatzen bada, saiatu app-a berrabiarazten.", "core.explanationdigitalminor": "Informazio hau beharrezkoa da zure adina onespen-adin digitaletik goragokoa dela egiaztatzeko. Adin honetara heltzean norbanakoek baldintzei onespena eman diezaiekete eta ondorioz euren datuak legalki gorde eta prozesatu daitezke.", + "core.favourites": "Nabarmenduta", "core.filename": "Fitxategiaren izena", "core.filenameexist": "Fitxategi-izena dagoeneko existitzen da: {{$a}}", "core.fileuploader.addfiletext": "Gehitu fitxategia", @@ -1216,6 +1336,7 @@ "core.fileuploader.more": "Gehiago", "core.fileuploader.photoalbums": "Argazki-bildumak", "core.fileuploader.readingfile": "Fitxategia irakurtzen", + "core.fileuploader.readingfileperc": "Fitxategia irakurtzen: %{{$a}}", "core.fileuploader.selectafile": "Aukeratu fitxategi bat", "core.fileuploader.uploadafile": "Igo fitxategia", "core.fileuploader.uploading": "Igotzen", @@ -1234,7 +1355,7 @@ "core.grades.grades": "Kalifikazioak", "core.grades.lettergrade": "Letren bidezko kalifikazioa", "core.grades.nogradesreturned": "Ez da kalifikaziorik itzuli", - "core.grades.nooutcome": "Ez dago ikas-emaitzarik", + "core.grades.nooutcome": "Ez dago ikaste-emaitzarik", "core.grades.percentage": "Portzentajea", "core.grades.range": "Ibiltartea", "core.grades.rank": "Sailkapena", @@ -1271,23 +1392,26 @@ "core.login.connecttomoodle": "Moodle-ra konektatu", "core.login.contactyouradministrator": "Zure guneko kudeatzailearekin harremanetan jarri laguntza gehiagorako.", "core.login.contactyouradministratorissue": "Mesedez, eskatu zure guneko kudeatzaileari hurrengo arazoa ikuskatu dezala: {{$a}}", - "core.login.createaccount": "Nire kontu berria sortu", + "core.login.createaccount": "Sortu nire kontu berria", "core.login.createuserandpass": "Aukeratu zure erabiltzaile-izena eta pasahitza", "core.login.credentialsdescription": "Mesedez saioa hasteko zure sartu erabiltzaile eta pasahitza.", - "core.login.emailconfirmsent": "

Zure {{$a}} helbidera mezu bat bidali da.

Zure erregistroa amaitzeko argibide erraz batzuk ditu.

Arazorik baduzu, jarri harremanetan kudeatzailearekin.

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

E-posta mezu bat bidali dugu zure hurrengo helbide honetara: {{$a}}

\n

Izena ematen amaitzeko argibide erraz batzuk ditu.

\n

Arazorik baduzu, jarri harremanetan kudeatzailearekin.

", + "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-mezua 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.", + "core.login.findyoursite": "Aurkitu zure gunea", "core.login.firsttime": "Hau al da zure lehen aldia hemen?", "core.login.forgotten": "Zure erabiltzaile-izena edo pasahitza ahaztu dituzu?", "core.login.getanothercaptcha": "Beste CAPTCHA bat lortu", "core.login.help": "Laguntza", - "core.login.helpmelogin": "

Milaka Moodle gune dago munduan zehar. App hau soilik Mobile sarbidea gaituta duten Moodle guneetan sartzeko gai da.

Zure Moodle gunera konektatzeko gai ez bazara konektatu nahi zaren Moodle guneko administratzailearekin harremanetan jarri behar zara eta hurrengo esteka irakurtzeko esan: http://docs.moodle.org/en/Mobile_app

.

App honen demo gunea probatzeko idatzi teacher edo student Gunearen helbidea eremuan eta Gehitu botoia sakatu.

", + "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", "core.login.invalidaccount": "Zure erabiltzaile eta pasahitza egiaztatu itzazu edo zure guneko administratzaileari guneko ezarpenak egiaztatzeko eskatu.", - "core.login.invaliddate": "Data baliogabea", + "core.login.invaliddate": "Data ezegokia", "core.login.invalidemail": "E-posta helbide baliogabea", "core.login.invalidmoodleversion": "Moodle bertsio baliogabea. Gutxieneko bertsioa 2.4 da.", "core.login.invalidsite": "Guneko URLa ez da zuzena", @@ -1295,16 +1419,20 @@ "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. Mesedez, jaitsi ezazu 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", "core.login.loginbutton": "Hasi saioa", "core.login.logininsiterequired": "Gunean web-nabigatzaile baten bidez sartu behar zara.", - "core.login.loginsteps": "Gune honetara sarbide osoa izateko, lehenik eta behin kontua sortu behar duzu. in ikastarotara sartzeko web-gune honetako erabiltzaile izan behar duzu\neta horretarako kontu bat sortu behar duzu.\n\nNola sortu kontu berria:\n
    \n
  1. Sakatu lotura honetan KONTU BERRIA eta formularioa bete zure datuekin.
  2. \n
  3. E-posta mezu bat bidaliko dugu berehala zure e-posta helbidera.
  4. \n
  5. Mezua irakurri eta agertzen den loturan klik egin.
  6. \n
  7. Zure kontua berretsi eta barruan izango zara.
  8. \n
  9. Orduan, nahi duzun ikastaroa aukeratu.
  10. \n
  11. \"Partaide-giltza\" bat eskatzen badizu, erabili matrikulatutakoan eman zizutena
  12. \n
  13. Kontu berria sortutakoan, eta zenbait kasutan matrikula egindakoan, ikastarorako sarbidea izango duzu.
  14. \n
  15. Hemendik aurrera zure erabiltzaile-izena eta pasahitza besterik ez dituzu sartu beharko hasiera orrian zeure ikastaroan parte hartzeko.
  16. \n
", + "core.login.loginsteps": "Gune honetara sarbide osoa izateko, lehenik eta behin kontua sortu behar duzu.", "core.login.missingemail": "E-posta helbidea falta da", "core.login.missingfirstname": "Izena falta da", "core.login.missinglastname": "Deitura falta da", "core.login.mobileservicesnotenabled": "Mobile bidezko sarbidea gaitu gabe dago zure gunean. Mesedez, jar zaitez harremanetan guneko kudeatzailearekin, mobile sarbidea gaitu behar dela uste baduzu.", + "core.login.mustconfirm": "Zure kontua baieztatu behar dituzu", "core.login.newaccount": "Kontu berria", "core.login.newsitedescription": "Sartu mesedez zure Moodle guneko URLa. Kontuan hartu app honekin funtzionatzeko gunea aurretik konfiguraturik egon behar dela.", "core.login.notloggedin": "Autentifikaturik egon behar duzu.", @@ -1316,25 +1444,29 @@ "core.login.policyagree": "Web gune honetan jarraitu aurretik baldintzekin ados egon behar duzu. Ados al zaude?", "core.login.policyagreement": "Gunearen politiken onespena", "core.login.policyagreementclick": "Gunearen politikak irakurtzeko esteka", - "core.login.potentialidps": "Hasi saioa beste kontu bat erabiliz:", + "core.login.potentialidps": "Sartu beste kontu bat erabiliz:", "core.login.problemconnectingerror": "Arazoak izaten ari gara hona konektatzean", "core.login.problemconnectingerrorcontinue": "Berriz egiaztatu helbidea ondo idatzi duzula eta ondoren saiatu berriz.", - "core.login.profileinvaliddata": "Baloreak ez du balio", + "core.login.profileinvaliddata": "Balio ezegokia", "core.login.recaptchachallengeimage": "reCAPTCHA erronkaren irudia", + "core.login.recaptchaexpired": "Egiaztatzea iraungitu da. Erantzun berriz ere segurtasun-galdera.", + "core.login.recaptchaincorrect": "Segurtasun-galderaren erantzuna okerra da.", "core.login.reconnect": "Berriz konektatu", "core.login.reconnectdescription": "Zure autentikazio-token-a ez da baliozkoa edo iraungitu da. Gunera berriz konektatu beharko zara.", "core.login.reconnectssodescription": "Zure autentikazio-token-a ez da baliozkoa edo iraungitu da. Gunera berriz konektatu beharko duzu. Gunean web-nabigatzaile baten bidez sartu behar duzu.", + "core.login.resendemail": "Berriz bidali mezua", "core.login.searchby": "Bilatu honen arabera:", "core.login.security_question": "Segurtasun-galdera", "core.login.selectacountry": "Herrialde bat aukeratu", "core.login.selectsite": "Aukeratu mesedez zure gunea:", "core.login.signupplugindisabled": "{{$a}} ez dago gaituta.", "core.login.siteaddress": "Gunearen helbidea", + "core.login.sitehasredirect": "Zure gunean HTTP berbideraketaren bat dauka. App-ak ezin ditu berbideraketak jarraitu, hau izan daiteke zure gunera konektatzea ekiditen duen arazoa.", "core.login.siteinmaintenance": "Zure gunea mantenu-moduan dago", "core.login.sitepolicynotagreederror": "Ez da guneko politika onartu.", "core.login.siteurl": "Gunearen URLa", "core.login.siteurlrequired": "Gunearen URLa behar da, http://www.zuremoodlegunea.eus adibidez", - "core.login.startsignup": "Kontu berri bat sortu", + "core.login.startsignup": "Sortu kontu berri bat", "core.login.stillcantconnect": "Oraindik ezin zara konektatu?", "core.login.supplyinfo": "Xehetasun gehiago", "core.login.username": "Erabiltzaile-izena", @@ -1348,8 +1480,6 @@ "core.mainmenu.changesite": "Aldatu gunea", "core.mainmenu.help": "Laguntza", "core.mainmenu.logout": "Irten", - "core.mainmenu.mycourses": "Nire ikastaroak", - "core.mainmenu.togglemenu": "Aldatu menua", "core.mainmenu.website": "Webgunea", "core.maxsizeandattachments": "Gehienezko tamaina fitxategi berrietarako: {{$a.size}}, gehienezko eranskin-kopurua: {{$a.attachments}}", "core.min": "minutu", @@ -1384,6 +1514,7 @@ "core.more": "gehiago", "core.mygroups": "Nire taldeak", "core.name": "Izena", + "core.networkerroriframemsg": "Eduki hau ez dago eskuragarri lineaz kanpo. Internetera konektatu zaitez eta ondoren saiatu zaitez berriro, mesedez.", "core.networkerrormsg": "Arazo bat izan da gunearekin konektatzerakoan. Mesedez egiaztatu zure konexioa eta ondoren berriz saiatu zaitez.", "core.never": "Inoiz ez", "core.next": "Hurrengoa", @@ -1392,7 +1523,8 @@ "core.nograde": "Kalifikaziorik ez", "core.none": "Bat ere ez", "core.nopasswordchangeforced": "Ezin duzu jarraitu zure pasahitza aldatu gabe.", - "core.nopermissions": "Sentitzen dugu, baina oraingoz ez duzu hori egiteko baimenik ({{$a}})", + "core.nopermissionerror": "Sentitzen dugu, baina une honetan ez duzu hori egiteko baimenik.", + "core.nopermissions": "Sentitzen dugu, baina oraingoz ez duzu hori egiteko baimenik ({{$a}}).", "core.noresults": "Emaitzarik ez", "core.notapplicable": "ezin da aplikatu", "core.notice": "Abisua", @@ -1416,6 +1548,7 @@ "core.pulltorefresh": "Sakatu freskatzeko", "core.question.answer": "Erantzuna", "core.question.answersaved": "Erantzuna gorde da", + "core.question.cannotdeterminestatus": "Ezin izan da egoera zehaztu.", "core.question.certainty": "Ziurtasuna", "core.question.complete": "Osatu", "core.question.correct": "Zuzena", @@ -1436,8 +1569,10 @@ "core.quotausage": "Une honetan {{$a.used}} erabili dituzu, eta zure muga {{$a.total}} da.", "core.redirectingtosite": "Gunera berbideratua izango zara.", "core.refresh": "Freskatu", + "core.remove": "Kendu", "core.required": "Beharrezkoa", "core.requireduserdatamissing": "Erabiltzaile honek beharrezkoak diren profileko datuak bete gabe ditu. Mesedez, bete itzazu datu hauek zure gunean eta saiatu berriz.
{{$a}}", + "core.resources": "Baliabideak", "core.restore": "Berreskuratu", "core.retry": "Berriz saiatu", "core.save": "Gorde", @@ -1446,7 +1581,7 @@ "core.searchresults": "Bilaketaren emaitzak", "core.sec": "seg", "core.secs": "segundu", - "core.seemoredetail": "Klik egin hemen xehetasun gehiago ikusteko", + "core.seemoredetail": "Egin klik hemen xehetasun gehiago ikusteko", "core.send": "Bidali", "core.sending": "Bidaltzen", "core.serverconnection": "Errorea zerbitzariarekin konektatzean", @@ -1454,6 +1589,7 @@ "core.settings.appready": "App-a prest", "core.settings.cannotsyncoffline": "Ezin da sinkronizatu lineaz kanpo.", "core.settings.cannotsyncwithoutwifi": "Ezin da sinkronizatu oraingo ezarpenek sinkronizazio Wi-Fi bidez konektaturik egonda soilik baimentzen dutelako. Mesedez konektatu Wi-Fi sare batera.", + "core.settings.compilationinfo": "Konpilazioaren informazioa", "core.settings.cordovadevicemodel": "Cordova device eredua", "core.settings.cordovadeviceosversion": "Cordova device SE bertsioa", "core.settings.cordovadeviceplatform": "Cordova device plataforma", @@ -1461,6 +1597,7 @@ "core.settings.cordovaversion": "Cordova bertsioa", "core.settings.currentlanguage": "Oraingo hizkuntza", "core.settings.debugdisplay": "Arazketa-mezuak erakutsi", + "core.settings.debugdisplaydescription": "Gaituz gero, ahal denean errore-mezuek errorearen inguruko informazio gehiago erakutsiko dute.", "core.settings.deletesitefiles": "Ziur zaude '{{sitename}}' gunetik jaitsitako fitxategiak ezabatu nahi dituzula?", "core.settings.deletesitefilestitle": "Ezabatu guneko fitxategiak", "core.settings.deviceinfo": "Gailuaren informazioa", @@ -1491,6 +1628,7 @@ "core.settings.privacypolicy": "Pribatutasun politika.", "core.settings.reportinbackground": "Erroreak automatikoki jakinarazi", "core.settings.settings": "Ezarpenak", + "core.settings.showdownloadoptions": "Erakutsi deskargatzeko aukerak", "core.settings.sites": "Guneak", "core.settings.spaceusage": "Lekuaren erabilera", "core.settings.synchronization": "Sinkronizazioa", @@ -1521,7 +1659,22 @@ "core.sizemb": "MB", "core.sizetb": "TB", "core.sorry": "Barkatu...", - "core.sortby": "Zeren arabera ordenatu", + "core.sortby": "Ordenatze-irizpidea", + "core.strftimedate": "%Y(e)ko %Bk %d", + "core.strftimedatefullshort": "%y/%m/%d", + "core.strftimedateshort": "%Bk %d", + "core.strftimedatetime": "%Y(e)ko %Bren %d(e)n, %H:%M(e)an", + "core.strftimedatetimeshort": "%Y/%m/%d %H:%M", + "core.strftimedaydate": "%A, %Y(e)ko %Bk %d", + "core.strftimedaydatetime": "%A, %Y(e)ko %Bren %d(e)an, %H:%M(e)an", + "core.strftimedayshort": "%A, %Bk %d", + "core.strftimedaytime": "%a, %H:%M(e)an", + "core.strftimemonthyear": "%Y(e)ko %B", + "core.strftimerecent": "%bk %d, %H:%M(e)an", + "core.strftimerecentfull": "%a, %Y(e)ko %bren %d(e)an, %H:%M(e)an", + "core.strftimetime": "%H:%M %p", + "core.strftimetime12": "%I:%M %p", + "core.strftimetime24": "%H:%M", "core.submit": "Bidali", "core.success": "Ondo!", "core.tablet": "Tablet-a", @@ -1551,6 +1704,7 @@ "core.user.editingteacher": "Irakaslea", "core.user.email": "E-posta helbidea", "core.user.emailagain": "E-posta (berriro)", + "core.user.errorloaduser": "Errorea erabiltzailea kargatzean.", "core.user.firstname": "Izena", "core.user.interests": "Interesguneak", "core.user.lastname": "Deitura", @@ -1570,6 +1724,9 @@ "core.usernotfullysetup": "Erabiltzailea ez dago guztiz prest", "core.users": "Erabiltzaileak", "core.view": "Ikusi", + "core.viewcode": "Ikusi kodea", + "core.vieweditor": "Ikusi editorea", + "core.viewembeddedcontent": "Ikusi txertatutako edukia", "core.viewprofile": "Profila ikusi", "core.warningofflinedatadeleted": "'{{name}}' {{component}}-aren lineaz kanpoko informazioa ezabatua izan da. {{error}}", "core.whatisyourage": "Zein da zure adina?", diff --git a/src/assets/lang/fa.json b/src/assets/lang/fa.json index 491b27090..203f03f75 100644 --- a/src/assets/lang/fa.json +++ b/src/assets/lang/fa.json @@ -8,8 +8,29 @@ "addon.badges.issuancedetails": "انقضای مدال", "addon.badges.issuerdetails": "مشخصات صادرکننده", "addon.badges.issuername": "نام صادرکننده", + "addon.badges.issuerurl": "آدرس صادرکننده", "addon.badges.nobadges": "مدالی موجود نیست.", "addon.badges.recipientdetails": "مشخصات دریافت‌کننده", + "addon.badges.warnexpired": "(این مدال منقضی شده است!)", + "addon.block_activitymodules.pluginname": "فعالیت‌ها", + "addon.block_myoverview.future": "آینده", + "addon.block_myoverview.inprogress": "در جریان", + "addon.block_myoverview.lastaccessed": "آخرین زمان دسترسی", + "addon.block_myoverview.morecourses": "درس‌های بیشتر", + "addon.block_myoverview.nocourses": "درسی وجود ندارد", + "addon.block_myoverview.past": "گذشته", + "addon.block_myoverview.pluginname": "نمای کلی درس", + "addon.block_myoverview.title": "نام درس", + "addon.block_recentlyaccessedcourses.pluginname": "درس‌های اخیرا مراجعه‌شده", + "addon.block_sitemainmenu.pluginname": "منوی اصلی", + "addon.block_timeline.duedate": "مهلت", + "addon.block_timeline.next30days": "۳۰ روز آینده", + "addon.block_timeline.next3months": "۳ ماه آینده", + "addon.block_timeline.next6months": "۶ ماه آینده", + "addon.block_timeline.next7days": "۷ روز آینده", + "addon.block_timeline.noevents": "مهلت هیچ فعالیتی نزدیک نیست", + "addon.block_timeline.sortbycourses": "مرتب‌سازی بر اساس درس‌ها", + "addon.block_timeline.sortbydates": "مرتب‌سازی بر اساس تاریخ‌ها", "addon.calendar.calendar": "تقویم", "addon.calendar.calendarevents": "رویدادهای تقویم", "addon.calendar.defaultnotificationtime": "زمان پیش‌فرض اطلاع‌رسانی", @@ -44,6 +65,7 @@ "addon.competency.learningplancompetencies": "شایستگی‌های برنامه یادگیری", "addon.competency.learningplans": "برنامه‌های آزموشی", "addon.competency.myplans": "برنامه‌های یادگیری من", + "addon.competency.nocompetencies": "بدون شایستگی", "addon.competency.nocrossreferencedcompetencies": "هیچ شایستگی دیگری به این شایستگی به‌طور متقابل ارجاع داده نشده است.", "addon.competency.noevidence": "بدون مدرک", "addon.competency.noplanswerecreated": "هیچ برنامهٔ یادگیری‌ای ساخته نشده است.", @@ -65,27 +87,78 @@ "addon.competency.userplans": "برنامه‌های یادگیری", "addon.competency.xcompetenciesproficientoutofy": "در {{$a.x}} شایستگی از مجموع {{$a.y}} شایستگی مهارت کسب شده است", "addon.competency.xcompetenciesproficientoutofyincourse": "شما در {{$a.x}} شایستگی از {{$a.y}} شایستگی ماهر هستید.", + "addon.coursecompletion.complete": "کامل شد", "addon.coursecompletion.completecourse": "کامل‌کردن درس", + "addon.coursecompletion.completed": "تکمیل‌شده", + "addon.coursecompletion.completiondate": "تاریخ تکمیل", "addon.coursecompletion.completionmenuitem": "تکمیل", + "addon.coursecompletion.couldnotloadreport": "امکان بارگذاری گزارش تکمیل فعالیت درس نیست. لطفا دوباره بعداً تلاش کنید", + "addon.coursecompletion.coursecompletion": "تکمیل درس", + "addon.coursecompletion.criteria": "ضابطه", + "addon.coursecompletion.criteriagroup": "گروه ضوابط", + "addon.coursecompletion.criteriarequiredall": "تمام ضوابط زیر باید برآورده شوند", + "addon.coursecompletion.criteriarequiredany": "حداقل یکی از ضوابط زیر برآورده شود", + "addon.coursecompletion.inprogress": "در جریان", + "addon.coursecompletion.manualselfcompletion": "علامت زدن به عنوان کامل توسط خود افراد", + "addon.coursecompletion.notyetstarted": "هنوز شروع نشده است", + "addon.coursecompletion.pending": "در حال بررسی", + "addon.coursecompletion.required": "لازم است", + "addon.coursecompletion.requiredcriteria": "ضوابط مورد نیاز", + "addon.coursecompletion.requirement": "نیازمندی", "addon.coursecompletion.status": "وضعیت", - "addon.coursecompletion.viewcoursereport": "مشاهدهٔ گزارش درس", + "addon.coursecompletion.viewcoursereport": "مشاهده گزارش درس", + "addon.files.couldnotloadfiles": "لیست فایل‌ها نمی‌تواند باگذاری شود.", + "addon.files.emptyfilelist": "هیچ فایلی برای نمایش وجود ندارد.", "addon.files.files": "فایل‌ها", "addon.files.privatefiles": "فایل‌های شخصی", "addon.files.sitefiles": "فایل‌های سایت", "addon.messageoutput_airnotifier.processorsettingsdesc": "پیکربندی دستگاه‌ها", + "addon.messages.acceptandaddcontact": "پذیرفتن و اضافه‌کردن به مخاطبین", "addon.messages.addcontact": "افزودن به مخاطبين", - "addon.messages.blockcontact": "مسدود کردن مخاطب", + "addon.messages.addtoyourcontacts": "اضافه‌شدن به لیست مخاطبین", "addon.messages.blocknoncontacts": "افرادی که در لیست مخاطبین من نیستند نتوانند برای من پیام بفرستند", + "addon.messages.blockuser": "مسدود کردن کاربر", + "addon.messages.blockuserconfirm": "آیا مطمئن هستید که می‌خواهید {{$a}} را مسدود کنید؟", + "addon.messages.contactableprivacy_coursemember": "مخاطبین من و کسانی که در درس‌های من هستند", + "addon.messages.contactableprivacy_onlycontacts": "فقط کسانی که در لیست مخاطبین من هستند", + "addon.messages.contactblocked": "مخاطب مسدود شده است", + "addon.messages.contactlistempty": "لیست مخاطبین خالی است", "addon.messages.contactname": "نام مخاطب", + "addon.messages.contactrequestsent": "درخواست اضافه شدن به مخاطبین ارسال شد", "addon.messages.contacts": "مخاطبین", + "addon.messages.decline": "نپذیرفتن", + "addon.messages.deleteallconfirm": "آیا مطمئن هستید که می‌خواهید کل این مکالمه را حذف کنید؟ این کار موجب پاک شدن مکالمه برای سایر شرکت‌کنندگان مکالمه نخواهد شد.", + "addon.messages.deletemessage": "حذف پیغام", "addon.messages.errorwhileretrievingdiscussions": "خطا در دریافت مباحثه‌ها از کارگزار.", + "addon.messages.info": "اطلاعات", "addon.messages.message": "پیام", + "addon.messages.messagenotsent": "پیغام ارسال نشد. لطفا دوباره بعداً تلاش کنید", "addon.messages.messagepreferences": "ترجیحات پیام‌دهی", "addon.messages.messages": "پیام‌ها", "addon.messages.newmessage": "پیام جدید", - "addon.messages.nomessages": "هیچ پیغامی منتظر جواب نیست", + "addon.messages.newmessages": "پیغام جدید", + "addon.messages.nocontactrequests": "درخواستی برای اضافه کردن به مخاطبین وجود ندارد", + "addon.messages.nocontactsgetstarted": "مخاطبی در لیست نیست", + "addon.messages.nomessagesfound": "پیامی پیدا نشد", + "addon.messages.noncontacts": "خارج از لیست مخاطبین", + "addon.messages.nousersfound": "هیچ کاربری پیدا نشد", "addon.messages.removecontact": "حذف کردن مخاطب", - "addon.messages.unblockcontact": "خارج کردن مخاطب از حالت مسدود", + "addon.messages.removefromyourcontacts": "حذف‌شدن از لیست مخاطبین", + "addon.messages.searchcombined": "جستجو بین افراد و پیام‌ها", + "addon.messages.searchnocontactsfound": "مخاطبی پیدا نشد", + "addon.messages.sendcontactrequest": "ارسال درخواست اضافه‌شدن به مخاطبین", + "addon.messages.showdeletemessages": "نمایش گزینهٔ حذف پیغام‌ها", + "addon.messages.type_blocked": "مسدود شده", + "addon.messages.type_offline": "آفلاین", + "addon.messages.type_online": "آنلاین", + "addon.messages.type_search": "نتایج جستجو", + "addon.messages.type_strangers": "دیگران", + "addon.messages.unabletomessage": "شما نمی‌توانید به این کاربر پیام بفرستید", + "addon.messages.unblockuserconfirm": "آیا مطمئن هستید که می‌خواهید {{$a}} را مسدود بودن خارج کنید؟", + "addon.messages.userwouldliketocontactyou": "{{$a}} تمایل دارد که شما را به لیست مخاطبینش اضافه کند", + "addon.messages.warningmessagenotsent": "امکان ارسال پیغام به کاربر {{user}} وجود ندارد. {{error}}", + "addon.messages.you": "شما:", + "addon.mod_assign.acceptsubmissionstatement": "لطفا پذیریش بیانیه‌ی تحویل تکلیف را تایید کنید.", "addon.mod_assign.addattempt": "اجازه‌دادن یک بار تلاش دیگر", "addon.mod_assign.addnewattempt": "اضافه‌کردن یک تلاش جدید", "addon.mod_assign.addnewattemptfromprevious": "اضافه‌کردن یک تلاش جدید بر اساس ارسال قبلی", @@ -111,6 +184,7 @@ "addon.mod_assign.graded": "نمره داده شده است", "addon.mod_assign.gradedby": "نمره دهنده", "addon.mod_assign.gradedon": "تاریخ ثبت نمره", + "addon.mod_assign.gradenotsynced": "نمره هم‌گام‌ نشد", "addon.mod_assign.gradeoutof": "نمره از {{$a}}", "addon.mod_assign.gradingstatus": "وضعیت تصحیح", "addon.mod_assign.groupsubmissionsettings": "تنظیمات تحویل گروهی", @@ -122,6 +196,7 @@ "addon.mod_assign.markingworkflowstatereadyforrelease": "آمادهٔ انتشار", "addon.mod_assign.markingworkflowstatereadyforreview": "نمره‌دهی کامل شده", "addon.mod_assign.markingworkflowstatereleased": "منتشر‌شده", + "addon.mod_assign.modulenameplural": "تکالیف", "addon.mod_assign.noattempt": "هیچ چیزی تحویل داده نشده است", "addon.mod_assign.nosubmission": "هیچ چیزی برای این تکلیف تحویل داده نشده است", "addon.mod_assign.notgraded": "نمره داده نشده است", @@ -163,26 +238,38 @@ "addon.mod_assign_submission_comments.pluginname": "توضیحات برای تحویل", "addon.mod_assign_submission_file.pluginname": "تحویل فایل", "addon.mod_assign_submission_onlinetext.pluginname": "نوشتن متن برخط", + "addon.mod_book.modulenameplural": "کتاب‌ها", "addon.mod_chat.beep": "بوق", "addon.mod_chat.currentusers": "کاربران حاضر", "addon.mod_chat.enterchat": "برای ورود به اتاق گفتگو اینجا کلیک کنید", + "addon.mod_chat.errorwhileconnecting": "خطا در هنگام اتصال به چت", + "addon.mod_chat.errorwhilegettingchatdata": "خطا در هنگام دریافت داده‌های چت", + "addon.mod_chat.errorwhilegettingchatusers": "خطا در هنگام دریافت کاربران چت", + "addon.mod_chat.errorwhileretrievingmessages": "خطا در هنگام بازیابی پیغام‌ها از سرور", + "addon.mod_chat.errorwhilesendingmessage": "خطا در هنگام ارسال پیغام", "addon.mod_chat.messagebeepsyou": "{{$a}} برای شما بوق فرستاد!", "addon.mod_chat.messageenter": "{{$a}} وارد اتاق گفتگو شد", "addon.mod_chat.messageexit": "{{$a}} اتاق گفتگو را ترک کرد", + "addon.mod_chat.modulenameplural": "اتاق‌های گفتگو", + "addon.mod_chat.mustbeonlinetosendmessages": "شما برای ارسال پیغام، باید آنلاین باشید", "addon.mod_chat.nomessages": "هنوز پیامی گفته نشده است", "addon.mod_chat.send": "ارسال", "addon.mod_chat.sessionstart": "جلسهٔ بعدی گفتگو {{$a.date}} شروع خواهد شد ({{$a.fromnow}} دیگر)", "addon.mod_chat.talk": "صحبت", "addon.mod_choice.choiceoptions": "گزینه‌های انتخابی", + "addon.mod_choice.errorgetchoice": "خطا در دریافت داده‌های ماژول انتخاب", "addon.mod_choice.expired": "با عرض پوزش، این فعالیت در {{$a}} بسته شد و دیگر در دسترس نیست", "addon.mod_choice.full": "(پر)", + "addon.mod_choice.modulenameplural": "انتخاب‌ها", "addon.mod_choice.noresultsviewable": "نتایج در این لحظه قابل دیدن نیستند.", "addon.mod_choice.notopenyet": "با عرض معذرت، این فعالیت تا قبل از {{$a}} در دسترس نیست", "addon.mod_choice.numberofuser": "تعداد کاربران", "addon.mod_choice.numberofuserinpercentage": "درصد پاسخ‌ها", "addon.mod_choice.removemychoice": "حذف انتخاب من", "addon.mod_choice.responses": "پاسخ‌ها", + "addon.mod_choice.responsesresultgraphdescription": "{{number}}% از کاربران گزینه : {{text}} انتخاب کرده‌اند", "addon.mod_choice.responsesresultgraphheader": "نمایش نمودار", + "addon.mod_choice.resultsnotsynced": "پاسخ آخر شما قبل از اینکه در نتایج لحاظ گردد باید هم‌گام‌سازی شود.", "addon.mod_choice.savemychoice": "ذخیرهٔ انتخاب من", "addon.mod_choice.userchoosethisoption": "کاربرانی که این گزینه را انتخاب کرده‌اند", "addon.mod_choice.yourselection": "انتخاب شما", @@ -199,9 +286,12 @@ "addon.mod_data.emptyaddform": "شما هیچ فیلدی را پر نکردید!", "addon.mod_data.entrieslefttoadd": "برای کامل کردن این فعالیت باید {{$a.entriesleft}} دادهٔ دیگر وارد کنید", "addon.mod_data.entrieslefttoaddtoview": "باید {{$a.entrieslefttoview}} دادهٔ دیگر وارد کنید تا بتوانید داده‌های وارد شده توسط سایرین را ببینید.", + "addon.mod_data.errordeleting": "خطا در حذف کردن داده‌های ورودی", "addon.mod_data.expired": "متأسفیم، این فعالیت در {{$a}} بسته شد و دیگر در دسترس نیست", "addon.mod_data.fields": "فیلدها", + "addon.mod_data.foundrecords": "رکوردهای پیدا شده: {{$a.num}} از {{$a.max}} (بازنشانی فیلترها)", "addon.mod_data.menuchoose": "انتخاب کنید...", + "addon.mod_data.modulenameplural": "بانک‌های اطلاعاتی", "addon.mod_data.more": "نمایش جزئیات", "addon.mod_data.nomatch": "دادهٔ مطابقی پیدا نشد!", "addon.mod_data.norecords": "بانک اطلاعاتی خالی است", @@ -225,10 +315,12 @@ "addon.mod_feedback.completed_feedbacks": "جواب‌های ارائه شده", "addon.mod_feedback.continue_the_form": "ادامه دادن فرم", "addon.mod_feedback.feedback_is_not_open": "بازخورد باز نیست", + "addon.mod_feedback.feedback_submitted_offline": "این بازخورد ذخیره شد تا بعداً ثبت شود", "addon.mod_feedback.feedbackclose": "بستن بازخورد در", "addon.mod_feedback.feedbackopen": "باز کردن بازخورد در", "addon.mod_feedback.mapcourses": "نسبت دادن بازخورد به درس‌ها", "addon.mod_feedback.mode": "حالت", + "addon.mod_feedback.modulenameplural": "بازخوردها", "addon.mod_feedback.next_page": "صفحهٔ بعد", "addon.mod_feedback.non_anonymous": "نام کاربران ثبت و به همراه پاسخ‌ها نمایش داده خواهد شد", "addon.mod_feedback.non_anonymous_entries": "اطلاعا وارد شده به صورت غیر ناشناس", @@ -247,6 +339,8 @@ "addon.mod_feedback.show_nonrespondents": "نمایش کسانی که پاسخ نداده‌اند", "addon.mod_feedback.started": "شروع کرده است", "addon.mod_feedback.this_feedback_is_already_submitted": "شما قبلا این فعالیت را کامل کرده‌اید.", + "addon.mod_folder.emptyfilelist": "هیچ فایلی برای نمایش وجود ندارد", + "addon.mod_folder.modulenameplural": "پوشه‌ها", "addon.mod_forum.addanewdiscussion": "شروع یک مباحثهٔ جدید", "addon.mod_forum.addanewquestion": "طرح یک سؤال جدید", "addon.mod_forum.addanewtopic": "طرح مباحثهٔ جدید", @@ -261,11 +355,15 @@ "addon.mod_forum.edit": "ویرایش", "addon.mod_forum.erroremptymessage": "متن ارسالی نمی‌تواند خالی باشد", "addon.mod_forum.erroremptysubject": "موضوع مطلب نمی‌تواند خالی باشد.", + "addon.mod_forum.errorgetforum": "خطا در دریافت داده‌های تالارگفتگو", + "addon.mod_forum.errorgetgroups": "خطا در دریافت تنظیمات گروه", "addon.mod_forum.forumnodiscussionsyet": "هنوز هیچ مباحثه ای در این تالار شروع نشده است.", + "addon.mod_forum.group": "گروه", "addon.mod_forum.message": "متن", "addon.mod_forum.modeflatnewestfirst": "نمایش مطالب به صورت مسطح (از جدید به قدیمی)", "addon.mod_forum.modeflatoldestfirst": "نمایش مطالب به صورت مسطح (از قدیمی به جدید)", "addon.mod_forum.modenested": "نمایش مطالب به صورت تو در تو", + "addon.mod_forum.modulenameplural": "تالارهای گفتگو", "addon.mod_forum.numdiscussions": "{{numdiscussions}} مباحثه", "addon.mod_forum.numreplies": "{{numreplies}} پاسخ", "addon.mod_forum.posttoforum": "طرح در تالار", @@ -279,6 +377,13 @@ "addon.mod_glossary.addentry": "تعریف یک اصطلاح جدید", "addon.mod_glossary.aliases": "کلمات کلیدی", "addon.mod_glossary.attachment": "فایل ضمیمه", + "addon.mod_glossary.byalphabet": "الفبایی", + "addon.mod_glossary.byauthor": "گروه بر اساس نویسنده", + "addon.mod_glossary.bycategory": "گروه بر اساس دسته", + "addon.mod_glossary.bynewestfirst": "جدیدترین اول", + "addon.mod_glossary.byrecentlyupdated": "اخیرا به‌روز شد", + "addon.mod_glossary.bysearch": "جستجو", + "addon.mod_glossary.cannoteditentry": "ویرایش داده ورودی امکان ندارد", "addon.mod_glossary.casesensitive": "بزرگ و کوچک بودن حروف این اصطلاح مهم است", "addon.mod_glossary.categories": "دسته‌ها", "addon.mod_glossary.concept": "اصطلاح", @@ -288,6 +393,8 @@ "addon.mod_glossary.fillfields": "اصطلاح و تعریف جزو موارد ضروری هستند.", "addon.mod_glossary.fullmatch": "تطابق در کل کلمه", "addon.mod_glossary.linking": "پیوند دادن خودکار", + "addon.mod_glossary.modulenameplural": "واژه‌نامه‌ها", + "addon.mod_imscp.showmoduledescription": "نمایش توضیحات", "addon.mod_lesson.answer": "جواب", "addon.mod_lesson.attempt": "تلاش: {{$a}}", "addon.mod_lesson.attemptsremaining": "می‌توانید {{$a}} بار دیگر تلاش کنید", @@ -323,6 +430,7 @@ "addon.mod_lesson.lowtime": "کمترین زمان", "addon.mod_lesson.maximumnumberofattemptsreached": "حداکثر دفعات تلاش مجاز را انجام داده‌اید - حرکت به صفحهٔ بعد", "addon.mod_lesson.modattemptsnoteacher": "قابلیت مرور فقط برای شاگردان کار می‌کند.", + "addon.mod_lesson.modulenameplural": "مباحث درسی", "addon.mod_lesson.noanswer": "به یک یا چند سؤال پاسخی داده نشده است. لطفاً بازگردید و پاسخی را ارائه نمائید.", "addon.mod_lesson.nolessonattempts": "هیچ تلاشی در این مبحث درسی صورت نگرفته است.", "addon.mod_lesson.notcompleted": "تمام نشده", @@ -359,6 +467,8 @@ "addon.mod_lesson.youranswer": "پاسخ شما", "addon.mod_lesson.yourcurrentgradeisoutof": "نمرهٔ فعلی شما {{$a.grade}} از {{$a.total}} است", "addon.mod_lesson.youshouldview": "باید حداقل به {{$a}} سؤال پاسخ دهید", + "addon.mod_lti.modulenameplural": "ابزارهای خارجی", + "addon.mod_page.modulenameplural": "صفحه‌ها", "addon.mod_quiz.attemptfirst": "اولین تلاش", "addon.mod_quiz.attemptlast": "آخرین تلاش", "addon.mod_quiz.attemptnumber": "تلاش", @@ -381,6 +491,7 @@ "addon.mod_quiz.grademethod": "روش نمره دادن", "addon.mod_quiz.gradesofar": "{{$a.method}}: {{$a.mygrade}} از {{$a.quizgrade}}.", "addon.mod_quiz.marks": "جمع نمره", + "addon.mod_quiz.modulenameplural": "آزمون‌ها", "addon.mod_quiz.mustbesubmittedby": "تا پیش از {{$a}} باید پاسخ‌های خود را ثبت کنید.", "addon.mod_quiz.noquestions": "هنوز سؤالی اضافه نشده است", "addon.mod_quiz.outof": "{{$a.grade}} از {{$a.maxgrade}}", @@ -414,6 +525,8 @@ "addon.mod_quiz.timeleft": "زمان باقیمانده", "addon.mod_quiz.timetaken": "زمان صرف شده", "addon.mod_quiz.yourfinalgradeis": "نمرهٔ نهایی شما در این آزمون {{$a}} است.", + "addon.mod_resource.modulenameplural": "فایل‌ها", + "addon.mod_resource.openthefile": "باز کردن فایل", "addon.mod_resource.uploadeddate": "(تاریخ بارگذاری: {{$a}})", "addon.mod_scorm.asset": "دارایی", "addon.mod_scorm.assetlaunched": "دارایی - دیده شده", @@ -422,6 +535,7 @@ "addon.mod_scorm.browse": "پیش‌نمایش", "addon.mod_scorm.browsed": "به صورت پیش‌نمایش دیده شده", "addon.mod_scorm.browsemode": "حالت پیش‌نمایش", + "addon.mod_scorm.cannotcalculategrade": "نمرات نمی‌توانند محاسبه شوند", "addon.mod_scorm.completed": "کامل شده", "addon.mod_scorm.contents": "محتویات", "addon.mod_scorm.enter": "ورود", @@ -441,6 +555,7 @@ "addon.mod_scorm.incomplete": "ناتمام", "addon.mod_scorm.lastattempt": "آخرین تلاش", "addon.mod_scorm.mode": "حالت", + "addon.mod_scorm.modulenameplural": "بسته‌های اسکورم", "addon.mod_scorm.newattempt": "شروع یک تلاش جدید", "addon.mod_scorm.noattemptsallowed": "تعداد تلاش‌های مجاز", "addon.mod_scorm.noattemptsmade": "تعداد تلاش‌هایی که انجام داده‌اید", @@ -453,10 +568,14 @@ "addon.mod_survey.cannotsubmitsurvey": "متاسفیم. مشکلی در ثبت نظرسنجی شما وجود داشت. لطفا دوباره تلاش کنید.", "addon.mod_survey.ifoundthat": "متوجه شدم که:", "addon.mod_survey.ipreferthat": "ترجیح می‌دهم که:", + "addon.mod_survey.modulenameplural": "ارزیابی‌ها", "addon.mod_survey.responses": "پاسخ‌ها", + "addon.mod_survey.results": "نتایج", + "addon.mod_url.modulenameplural": "پیوندها", "addon.mod_wiki.createpage": "ایجاد صفحه", "addon.mod_wiki.editingpage": "ویرایش این صفحه: «{{$a}}»", "addon.mod_wiki.map": "معماری صفحه‌ها", + "addon.mod_wiki.modulenameplural": "ویکی‌ها", "addon.mod_wiki.newpagetitle": "عنوان صفحهٔ جدید", "addon.mod_wiki.nocontent": "محتوایی برای این صفحه وجود ندارد", "addon.mod_wiki.notingroup": "بدون گروه", @@ -487,6 +606,7 @@ "addon.mod_workshop.gradinggradecalculated": "نمرهٔ محاسبه شده برای ارزشیابی", "addon.mod_workshop.gradinggradeof": "نمره برای ارزشیابی (از {{$a}})", "addon.mod_workshop.gradinggradeover": "ابطال نمرهٔ محاسبه شده و استفاده از این نمره برای ارزشیابی", + "addon.mod_workshop.modulenameplural": "کارگاه‌ها", "addon.mod_workshop.nogradeyet": "هنوز نمره ندارد", "addon.mod_workshop.notassessed": "هنوز ارزشیابی نشده است", "addon.mod_workshop.notoverridden": "باطل نشود", @@ -520,9 +640,11 @@ "addon.notes.personalnotes": "یادداشت‌های خصوصی", "addon.notes.publishstate": "دامنه", "addon.notes.sitenotes": "یادداشت‌های سایت", + "addon.notes.userwithid": "کاربر با ID {{id}}", "addon.notifications.markallread": "علامت‌زدن همه به‌عنوان خوانده‌شده", "addon.notifications.notificationpreferences": "ترجیحات اطلاعیه‌ها", "addon.notifications.notifications": "هشدارها", + "addon.notifications.playsound": "پخش صدا", "addon.notifications.therearentnotificationsyet": "هیچ هشداری وجود ندارد", "assets.countries.AD": "آندورا", "assets.countries.AE": "امارات متحدهٔ عربی", @@ -825,16 +947,24 @@ "assets.mimetypes.video": "فایل ویدیویی ({{$a.EXT}})", "core.accounts": "حساب‌های کاربری", "core.add": "اضافه کردن", + "core.ago": "{{$a}} قبل", + "core.all": "همه", "core.allparticipants": "همهٔ اعضاء", + "core.android": "اندروید", "core.answer": "پاسخ", "core.answered": "پاسخ داده شد", "core.areyousure": "آیا مطمئن هستید؟", "core.back": "بازگشت", "core.cancel": "انصراف", "core.cannotconnect": "اتصال به سایت ممکن نبود. بررسی کنید که نشانی سایت را درست وارد کرده باشید و اینکه سایت شما از مودل ۲٫۴ یا جدیدتر استفاده کند.", + "core.captureaudio": "ضبط صدا", + "core.capturedimage": "عکس گرفته‌شده.", + "core.captureimage": "گرفتن عکس", + "core.capturevideo": "ضبط ویديو", "core.category": "طبقه", "core.choose": "انتخاب کنید", "core.choosedots": "انتخاب کنید...", + "core.clearsearch": "پاک کردن جستجو", "core.clicktohideshow": "برای باز یا بسته شدن کلیک کنید", "core.comments": "نظرات", "core.commentscount": "نظرات ({{$a}})", @@ -847,6 +977,7 @@ "core.confirmcanceledit": "آیا مطمئنید که می‌خواهید این صفحه را ترک کنید؟ تمام تغییرات از بین خواهند رفت.", "core.confirmdeletefile": "آیا مطمئنید که می‌خواهید این فایل را حذف کنید؟", "core.confirmloss": "آیا مطمئن هستید؟ تمام تغییرات از بین خواهند رفت.", + "core.considereddigitalminor": "سن شما برای ایجاد حساب کاربری در این سایت کم ایست.", "core.content": "محتوا", "core.continue": "ادامه", "core.course": "درس", @@ -868,24 +999,16 @@ "core.courses.availablecourses": "درس‌های موجود", "core.courses.categories": "طبقه‌های درسی", "core.courses.confirmselfenrol": "آیا مطمئنید که می‌خواهید خود را در این درس ثبت‌نام کنید؟", - "core.courses.courseoverview": "مرور اجمالی درس", "core.courses.courses": "درس‌ها", "core.courses.errorloadcourses": "در هنگام بارگیری درس‌ها خطایی رخ داد.", "core.courses.filtermycourses": "پالایش درس‌های من", "core.courses.frontpage": "صفحهٔ اول", - "core.courses.future": "آینده", - "core.courses.inprogress": "در جریان", - "core.courses.morecourses": "درس‌های بیشتر", "core.courses.mycourses": "درس‌های من", + "core.courses.mymoodle": "میز کار", "core.courses.nocourses": "هیچ درسی برای نمایش وجود ندارد", - "core.courses.nocoursesfuture": "هیچ درسی برای آینده وجود ندارد", - "core.courses.nocoursesinprogress": "هیچ درسِ درجریانی وجود ندارد", - "core.courses.nocoursesoverview": "هیچ درسی", - "core.courses.nocoursespast": "هیچ درسی برای گذشته وجود ندارد", "core.courses.nocoursesyet": "درسی در این طبقه وجود ندارد", "core.courses.nosearchresults": "بدون نتیجه", "core.courses.notenroled": "شما در این درس ثبت‌نام نیستید", - "core.courses.past": "گذشته", "core.courses.paymentrequired": "ثبت‌نام در این درس مستلزم پرداخت شهریه است.", "core.courses.search": "جستجو", "core.courses.searchcourses": "جستجو بین درس‌ها", @@ -898,11 +1021,13 @@ "core.defaultvalue": "پیش‌فرض ({{$a}})", "core.delete": "حذف", "core.description": "توصیف", + "core.digitalminor_desc": "لطفا از سرپرست یا والدین خود بخواهید تا با این شخص تماس بگیرد:", "core.done": "پر کرده است", "core.download": "دریافت", "core.edit": "ویرایش ", "core.error": "خطا", "core.errordownloading": "خطا در دانلود فایل", + "core.favourites": "ستاره‌دار", "core.fileuploader.addfiletext": "اضافه‌کردن فایل", "core.fileuploader.confirmuploadfile": "شما در آستانهٔ ارسال {{size}} هستید. آیا مطمئنید که می‌خواهید ادامه دهید؟", "core.fileuploader.confirmuploadunknownsize": "ما نتوانستیم حجم ارسال را محاسبه کنیم. آیا مطمئنید که می‌خواهید ادامه دهید؟", @@ -949,6 +1074,8 @@ "core.login.connecttomoodle": "اتصال به مودل", "core.login.createaccount": "ایجاد حساب کاربری من", "core.login.createuserandpass": "نام کاربری و رمز عبور خود را انتخاب کنید", + "core.login.emailconfirmsent": "

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

\n

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

\n

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

", + "core.login.emailconfirmsentsuccess": "ارسال ایمیل تاییدیه موفقیت آمیز بود", "core.login.enterthewordsabove": "کلمات بالا را وارد نمائید", "core.login.firsttime": "برای اولین بار به این صفحه آمده‌اید؟", "core.login.forgotten": "نام کاربری و یا رمز ورود خود را فراموش کرده‌اید؟", @@ -957,6 +1084,7 @@ "core.login.helpmelogin": "

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

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

\n

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

", "core.login.instructions": "دستورالعمل", "core.login.invalidaccount": "لطفا اطلاعات ورودخود را بررسی نمایید", + "core.login.invaliddate": "تاریخ نامعتبر", "core.login.invalidemail": "آدرس پست الکترونیک نامعتبر", "core.login.invalidmoodleversion": "این نسخه از برنامه سایت قابلیت اتصال به موبایل ندارد", "core.login.invalidsite": "نشانی سایت معتبر نیست", @@ -967,6 +1095,7 @@ "core.login.missingfirstname": "نام را وارد کنید", "core.login.missinglastname": "نام خانوادگی را وارد کنید", "core.login.mobileservicesnotenabled": "سرویس دسترسی موبایل در سایت شما فعال نیست", + "core.login.mustconfirm": "شما نیاز به تایید حساب کاربری خود دارید", "core.login.newaccount": "حساب کاربری جدید", "core.login.password": "رمز ورود", "core.login.passwordforgotten": "رمز ورود فراموش شده", @@ -978,6 +1107,7 @@ "core.login.policyagreementclick": "مشاهدهٔ موافقت‌نامهٔ خط مشی سایت", "core.login.potentialidps": "ورود با استفاده از حساب شما روی:", "core.login.profileinvaliddata": "مقدار نامعتبر", + "core.login.resendemail": "ارسال مجدد ایمیل", "core.login.security_question": "سؤال امنیتی", "core.login.selectacountry": "انتخاب کشور", "core.login.siteaddress": "آدرس سایت", @@ -995,7 +1125,6 @@ "core.mainmenu.appsettings": "تنظیمات برنامه", "core.mainmenu.help": "راهنمایی", "core.mainmenu.logout": "خروج از سایت", - "core.mainmenu.mycourses": "درس‌های من", "core.mainmenu.website": "پایگاه اینترنتی", "core.maxsizeandattachments": "حداکثر اندازه برای فایل‌های جدید: {{$a.size}}، حداکثر تعداد فایل‌های پیوست: {{$a.attachments}}", "core.min": "دقیقه", @@ -1066,9 +1195,13 @@ "core.question.partiallycorrect": "پاسخ نیمه درست", "core.question.questionno": "سؤال {{$a}}", "core.question.requiresgrading": "نمره‌دهی لازم است", + "core.quotausage": "شما در حال حاضر {{$a.used}} از {{$a.total}} محدودیت‌تان را استفاده کرده‌اید.", "core.refresh": "تازه‌سازی", + "core.remove": "حذف", "core.required": "لازم است", + "core.resources": "منابع", "core.restore": "بازیابی", + "core.save": "ذخیره", "core.search": "جستجو", "core.searchresults": "نتیجهٔ جستجو", "core.sec": "ثانیه", @@ -1115,6 +1248,19 @@ "core.sizemb": "مگابایت", "core.sorry": "متاسفیم...", "core.sortby": "مرتب شدن بر اساس", + "core.strftimedate": "%d %B %Y", + "core.strftimedatefullshort": "%d/%m/%y", + "core.strftimedateshort": "%d %B", + "core.strftimedatetime": "%d %B %Y، %I:%M %p", + "core.strftimedatetimeshort": "%y/%m/%d، %H:%M", + "core.strftimedaydate": "%A، %d %B %Y", + "core.strftimedaydatetime": "%A، %d %B %Y، %I:%M %p", + "core.strftimedayshort": "%A، %d %B", + "core.strftimedaytime": "%a، %H:%M", + "core.strftimemonthyear": "%B %Y", + "core.strftimerecent": "%d %b، %H:%M", + "core.strftimerecentfull": "%a، %d %b %Y، %I:%M %p", + "core.strftimetime": "%I:%M %p", "core.submit": "ارسال", "core.success": "موفق", "core.teachers": "استاد", @@ -1131,17 +1277,21 @@ "core.user.country": "کشور", "core.user.description": "توصیف", "core.user.details": "جزئیات", + "core.user.editingteacher": "استاد", "core.user.email": "آدرس پست الکترونیک", "core.user.emailagain": "پست الکترونیک (دوباره)", "core.user.firstname": "نام", "core.user.interests": "علایق", "core.user.lastname": "نام خانوادگی", + "core.user.manager": "مدیر", "core.user.newpicture": "عکس جدید", "core.user.noparticipants": "این درس شرکت‌کننده‌ای ندارد", "core.user.participants": "شرکت کنندگان", "core.user.phone1": "تلفن", "core.user.phone2": "تلفن همراه", "core.user.roles": "نقش‌ها", + "core.user.student": "شاگرد", + "core.user.teacher": "استاد بدون حق ویرایش", "core.user.webpage": "صفحهٔ وب", "core.userdeleted": "این حساب کاربری حذف شده است", "core.userdetails": "با جزئیات", @@ -1149,6 +1299,7 @@ "core.users": "کاربران", "core.view": "مشاهده", "core.viewprofile": "مشاهدهٔ صفحهٔ مشخصات فردی", + "core.whatisyourage": "سن شما چقدر است؟", "core.year": "سال", "core.years": "سال", "core.yes": "بله" diff --git a/src/assets/lang/fi.json b/src/assets/lang/fi.json index 3268eea51..1d24b0083 100644 --- a/src/assets/lang/fi.json +++ b/src/assets/lang/fi.json @@ -7,8 +7,18 @@ "addon.badges.issuancedetails": "Osaamismerkin vanhentuminen", "addon.badges.issuerdetails": "Osaamismerkin myöntäjän tiedot", "addon.badges.issuername": "Osaamismerkin myöntäjän nimi", + "addon.badges.issuerurl": "Myöntäjän URL-osoite", "addon.badges.nobadges": "Yhtään osaamismerkkiä ei ole tarjolla", "addon.badges.recipientdetails": "Vastaanottajan tiedot", + "addon.badges.warnexpired": "(Tämä osaamismerkki on vanhentunut)", + "addon.block_activitymodules.pluginname": "Aktiviteetit", + "addon.block_myoverview.future": "Tulevat kurssit", + "addon.block_myoverview.inprogress": "Keskeneräiset kurssit", + "addon.block_myoverview.morecourses": "Lisää kursseja", + "addon.block_myoverview.nocourses": "Ei kursseja", + "addon.block_myoverview.past": "Menneet kurssit", + "addon.block_myoverview.pluginname": "Kurssien yhteenveto", + "addon.block_sitemainmenu.pluginname": "Päävalikko", "addon.calendar.calendar": "Kalenteri", "addon.calendar.calendarevents": "Kalenteritapahtumat", "addon.calendar.defaultnotificationtime": "Oletusilmoitusaika", @@ -76,17 +86,19 @@ "addon.coursecompletion.completionmenuitem": "Suoritus", "addon.coursecompletion.couldnotloadreport": "Kurssin suoritusraporttia ei pystytty lataamaan. Ole hyvä ja yritä myöhemmin uudelleen.", "addon.coursecompletion.coursecompletion": "Kurssin suoritus", - "addon.coursecompletion.criteria": "Kriteerit", + "addon.coursecompletion.criteria": "Kriteeri", "addon.coursecompletion.criteriagroup": "Kriteeriryhmä", - "addon.coursecompletion.criteriarequiredall": "Kaikki alla mainitut kriteerit vaaditaan.", - "addon.coursecompletion.inprogress": "Meneillään", - "addon.coursecompletion.notyetstarted": "Ei vielä alkanut", + "addon.coursecompletion.criteriarequiredall": "Kaikki alla olevat kriteerit vaaditaan", + "addon.coursecompletion.criteriarequiredany": "Jokin alla olevista kriteereistä vaaditaan", + "addon.coursecompletion.inprogress": "Kesken", + "addon.coursecompletion.manualselfcompletion": "Opiskelijan itse hyväksymät suoritukset", + "addon.coursecompletion.notyetstarted": "Ei vielä aloitettu", "addon.coursecompletion.pending": "Odottaa", "addon.coursecompletion.required": "Vaadittu", "addon.coursecompletion.requiredcriteria": "Vaaditut kriteerit", "addon.coursecompletion.requirement": "Vaatimus", - "addon.coursecompletion.status": "Status", - "addon.coursecompletion.viewcoursereport": "Näytä kurssiraportti", + "addon.coursecompletion.status": "Tilanne", + "addon.coursecompletion.viewcoursereport": "Näytä kurssin raportti", "addon.files.couldnotloadfiles": "Tiedostolistaa ei pystytty lataamaan.", "addon.files.emptyfilelist": "Ei näytettäviä tiedostoja.", "addon.files.erroruploadnotworking": "Valitettavasti tiedostojen lataaminen järjestelmään ei tällä hetkellä onnistu.", @@ -95,12 +107,13 @@ "addon.files.sitefiles": "Sivuston tiedostot", "addon.messageoutput_airnotifier.processorsettingsdesc": "Määrittele laitteistot", "addon.messages.addcontact": "Lisää kontakti", - "addon.messages.blockcontact": "Estä kontakti", - "addon.messages.blockcontactconfirm": "Et saa enää viestejä tältä yhteystietohenkilöltä.", + "addon.messages.addtoyourcontacts": "Lisää kontakteihisi", "addon.messages.blocknoncontacts": "Estä kaikki viestit, jos lähettäjä ei ole kontaktilistallani", + "addon.messages.contactblocked": "Kontakti estetty", "addon.messages.contactlistempty": "Yhteystietolistasi on tyhjä", "addon.messages.contactname": "Yhteystiedon nimi", "addon.messages.contacts": "Kontaktit", + "addon.messages.deleteallconfirm": "Oletko varma, että haluat poistaa tämän koko keskustelun?", "addon.messages.errordeletemessage": "Viestiä poistettaessa tapahtui virhe.", "addon.messages.errorwhileretrievingcontacts": "Virhe ladattaessa yhteystietoja palvelimelta.", "addon.messages.errorwhileretrievingdiscussions": "Virhe ladattaessa keskusteluja palvelimelta.", @@ -111,15 +124,18 @@ "addon.messages.messages": "Viestit", "addon.messages.newmessage": "Uusi viesti", "addon.messages.newmessages": "Uusia viestejä", - "addon.messages.nomessages": "Ei odottavia viestejä", + "addon.messages.nomessagesfound": "Viestejä ei löytynyt", + "addon.messages.noncontacts": "Ei kontaktilistalla", "addon.messages.nousersfound": "Käyttäjiä ei löytynyt", "addon.messages.removecontact": "Poista kontakti", "addon.messages.removecontactconfirm": "Tämä yhteystieto poistetaan yhteystietolistaltasi.", + "addon.messages.removefromyourcontacts": "Poista kontakteistasi", + "addon.messages.searchcombined": "Hae käyttäjiä ja viestejä", "addon.messages.type_blocked": "Estetty", "addon.messages.type_search": "Hakutulokset", "addon.messages.type_strangers": "Muut", - "addon.messages.unblockcontact": "Salli kontakti", "addon.messages.warningmessagenotsent": "Ei voitu lähettää viestiä/viestejä käyttäjälle {{user}}. {{error}}", + "addon.messages.you": "Sinä:", "addon.mod_assign.acceptsubmissionstatement": "Ole hyvä ja hyväksy vakuutus oman työn osuudesta.", "addon.mod_assign.addattempt": "Anna toinen suorituskerta", "addon.mod_assign.addnewattempt": "Anna uusi suorituskerta", @@ -157,6 +173,7 @@ "addon.mod_assign.graded": "Arvioitu", "addon.mod_assign.gradedby": "Arvioija", "addon.mod_assign.gradedon": "Arviointipäivä", + "addon.mod_assign.gradelocked": "Tämä arvosana on lukittu tai ylitetty arviointikirjassa", "addon.mod_assign.gradenotsynced": "Arvosanaa ei ole synkronoitu", "addon.mod_assign.gradeoutof": "Arvosana (0 - {{$a}})", "addon.mod_assign.gradingstatus": "Arvioinnin tila", @@ -171,6 +188,7 @@ "addon.mod_assign.markingworkflowstatereadyforrelease": "Valmis julkaistavaksi", "addon.mod_assign.markingworkflowstatereadyforreview": "Arviointi tehty", "addon.mod_assign.markingworkflowstatereleased": "Julkaistu", + "addon.mod_assign.modulenameplural": "Tehtävät", "addon.mod_assign.multipleteams": "Useamman kuin yhden ryhmän jäsen", "addon.mod_assign.noattempt": "Ei suorituskertoja", "addon.mod_assign.nomoresubmissionsaccepted": "Vain opiskelijat, joille on annettu lisäaikaa, voivat enää palauttaa vastauksensa.", @@ -223,6 +241,7 @@ "addon.mod_assign_submission_file.pluginname": "Tiedostojen palautus", "addon.mod_assign_submission_onlinetext.pluginname": "Verkkotekstipalautukset", "addon.mod_book.errorchapter": "Virhe kirjan luvun lukemisessa.", + "addon.mod_book.modulenameplural": "Kirjat", "addon.mod_chat.beep": "kutsu", "addon.mod_chat.currentusers": "Tämänhetkiset osallistujat", "addon.mod_chat.enterchat": "Napsauta tästä päästäksesi mukaan chattiin", @@ -235,6 +254,7 @@ "addon.mod_chat.messagebeepsyou": "{{$a}} on juuri kutsunut sinua!", "addon.mod_chat.messageenter": "{{$a}} on juuri tullut mukaan chattiin", "addon.mod_chat.messageexit": "{{$a}} on lopettanut chatin", + "addon.mod_chat.modulenameplural": "Chatit", "addon.mod_chat.mustbeonlinetosendmessages": "Sinun on oltava online-tilassa lähettääksesi viestejä.", "addon.mod_chat.nomessages": "Ei viestejä", "addon.mod_chat.send": "Lähetä", @@ -245,10 +265,17 @@ "addon.mod_choice.errorgetchoice": "Virhe haettaessa valinnan tietoja.", "addon.mod_choice.expired": "Tämä aktiviteeti on suljettu {{$a}} eikä ole enää käytettävissä.", "addon.mod_choice.full": "(Täysi)", + "addon.mod_choice.modulenameplural": "Valinnat", "addon.mod_choice.noresultsviewable": "Tulokset eivät ole juuri nyt nähtävillä", "addon.mod_choice.notopenyet": "Tämä aktiviteetti ei ole saatavilla ennen kuin {{$a}}", "addon.mod_choice.numberofuser": "Osallistujamäärä", "addon.mod_choice.numberofuserinpercentage": "Osallistujamäärä prosentteina", + "addon.mod_choice.previewonly": "Tämä on vain esikatselu valinnasta. Valinta avautuu {{$a}}.", + "addon.mod_choice.publishinfoanonafter": "Nimettömät vastaukset julkaistaan vastaamisen jälkeen.", + "addon.mod_choice.publishinfoanonclose": "Nimettömät vastaukset julkaistaan, kun vastausaika on päättynyt.", + "addon.mod_choice.publishinfofullafter": "Nimet ja vastaukset julkaistaan vastaamisen jälkeen.", + "addon.mod_choice.publishinfofullclose": "Nimet ja vastaukset julkaistaan, kun vastausaika on päättynyt.", + "addon.mod_choice.publishinfonever": "Vastauksia ei julkaista vastaamisen jälkeen.", "addon.mod_choice.removemychoice": "Poista valintani", "addon.mod_choice.responses": "Vastaukset", "addon.mod_choice.responsesresultgraphdescription": "{{number}}% käyttäjistä valitsi vaihtoehdon: {{text}}.", @@ -275,7 +302,9 @@ "addon.mod_data.errordeleting": "Virhe poistettaessa merkintää.", "addon.mod_data.expired": "Tämä aktiviteetti on jo suljettu ({{$a}}).", "addon.mod_data.fields": "Kentät", + "addon.mod_data.foundrecords": "Hakusanoja löytyi: {{$a.num}}/{{$a.max}} (Poista suodattimet)", "addon.mod_data.menuchoose": "Valitse...", + "addon.mod_data.modulenameplural": "Tietokannat", "addon.mod_data.more": "Lisää...", "addon.mod_data.nomatch": "Haluttuja hakusanoja ei löytynyt!", "addon.mod_data.norecords": "Tietokannassa ei ole hakusanoja", @@ -307,6 +336,7 @@ "addon.mod_feedback.feedbackopen": "Avaa palaute", "addon.mod_feedback.mapcourses": "Yhdistä palaute kursseihin", "addon.mod_feedback.mode": "Tila", + "addon.mod_feedback.modulenameplural": "Palautteet", "addon.mod_feedback.next_page": "Seuraava sivu", "addon.mod_feedback.non_anonymous": "Vastaajien nimet tallennetaan ja näytetään vastausten kanssa", "addon.mod_feedback.non_anonymous_entries": "vastaukset nimillä", @@ -326,6 +356,7 @@ "addon.mod_feedback.started": "aloitettu", "addon.mod_feedback.this_feedback_is_already_submitted": "Olet jo tehnyt tämän aktiviteetin.", "addon.mod_folder.emptyfilelist": "Ei näytettäviä tiedostoja.", + "addon.mod_folder.modulenameplural": "Kansiot", "addon.mod_forum.addanewdiscussion": "Lisää uusi keskustelu", "addon.mod_forum.addanewquestion": "Lisää uusi kysymys", "addon.mod_forum.addanewtopic": "Lisää uusi aihe", @@ -348,6 +379,7 @@ "addon.mod_forum.modeflatnewestfirst": "Näytä vastaukset peräkkäin, uusin ensin", "addon.mod_forum.modeflatoldestfirst": "Näytä vastaukset peräkkäin, vanhin ensin", "addon.mod_forum.modenested": "Näytä vastaukset sisäkkäin", + "addon.mod_forum.modulenameplural": "Keskustelualueet", "addon.mod_forum.numdiscussions": "{{numdiscussions}} keskustelua", "addon.mod_forum.numreplies": "{{numreplies}} vastausta", "addon.mod_forum.posttoforum": "Lähetä viesti", @@ -383,9 +415,11 @@ "addon.mod_glossary.fillfields": "Käsite ja määritelmä ovat pakollisia kenttiä.", "addon.mod_glossary.fullmatch": "Linkitä vain kokonaisiin sanoihin", "addon.mod_glossary.linking": "Automaattinen linkitys", + "addon.mod_glossary.modulenameplural": "Sanastot", "addon.mod_glossary.noentriesfound": "Merkintöjä ei löytynyt.", "addon.mod_glossary.searchquery": "Hakukysely", "addon.mod_imscp.deploymenterror": "Sisällön pakkaamisvirhe!", + "addon.mod_imscp.modulenameplural": "IMS-sisältöpaketit", "addon.mod_imscp.showmoduledescription": "Näytä kuvaus", "addon.mod_lesson.answer": "Vastaus", "addon.mod_lesson.attempt": "Suorituskerta: {{$a}}", @@ -429,6 +463,7 @@ "addon.mod_lesson.lowtime": "Lyhin suoritusaika", "addon.mod_lesson.maximumnumberofattemptsreached": "Maksimimäärä suorituskertoja - siirrytään seuraavalle sivulle", "addon.mod_lesson.modattemptsnoteacher": "Vain opiskelijat voivat esikatsella", + "addon.mod_lesson.modulenameplural": "Oppitunnit", "addon.mod_lesson.noanswer": "Vastauksesi puuttuu vähintään yhdestä kysymyksestä. Palaathan vastaamaan.", "addon.mod_lesson.nolessonattempts": "Tässä oppitunnissa ei ole vielä suorituksia", "addon.mod_lesson.notcompleted": "Ei valmis", @@ -470,7 +505,9 @@ "addon.mod_lti.errorgetlti": "Virhe ladattaessa moduulin tietoja.", "addon.mod_lti.errorinvalidlaunchurl": "Laukaisu-URL ei ole kelvollinen.", "addon.mod_lti.launchactivity": "Käynnistä aktiviteetti", + "addon.mod_lti.modulenameplural": "Ulkoiset työkalut", "addon.mod_page.errorwhileloadingthepage": "Sivun sisältöä ladattaessa tapahtui virhe.", + "addon.mod_page.modulenameplural": "Sivut", "addon.mod_quiz.attemptfirst": "Ensimmäinen suorituskerta", "addon.mod_quiz.attemptlast": "Viimeisin suorituskerta", "addon.mod_quiz.attemptnumber": "Suorituskerta", @@ -505,6 +542,7 @@ "addon.mod_quiz.grademethod": "Arviointitapa", "addon.mod_quiz.gradesofar": "{{$a.method}}: {{$a.mygrade}} / {{$a.quizgrade}}.", "addon.mod_quiz.marks": "Pistettä", + "addon.mod_quiz.modulenameplural": "Tentit", "addon.mod_quiz.mustbesubmittedby": "Tämä suoritus on palautettava {{$a}} mennessä.", "addon.mod_quiz.noquestions": "Kysymyksiä ei ole vielä lisätty.", "addon.mod_quiz.noreviewattempt": "Et voi katsella tätä suorituskertaa", @@ -549,6 +587,7 @@ "addon.mod_quiz.yourfinalgradeis": "Lopullinen arvosanasi tästä tentistä on: {{$a}}", "addon.mod_resource.errorwhileloadingthecontent": "Virhe ladattaessa sisältöä.", "addon.mod_resource.modifieddate": "Muokattu {{$a}}", + "addon.mod_resource.modulenameplural": "Tiedostot", "addon.mod_resource.openthefile": "Avaa tiedosto", "addon.mod_resource.uploadeddate": "Ladattu {{$a}}", "addon.mod_scorm.asset": "Sivu", @@ -585,6 +624,7 @@ "addon.mod_scorm.incomplete": "Kesken", "addon.mod_scorm.lastattempt": "Viimeisin suorituskerta", "addon.mod_scorm.mode": "Muoto", + "addon.mod_scorm.modulenameplural": "SCORM-paketit", "addon.mod_scorm.newattempt": "Aloita uusi suorituskerta", "addon.mod_scorm.noattemptsallowed": "Sallittujen suorituskertojen määrä", "addon.mod_scorm.noattemptsmade": "Suorituskertojesi määrä", @@ -604,14 +644,17 @@ "addon.mod_survey.errorgetsurvey": "Virhe ladattaessa kyselyn tietoja.", "addon.mod_survey.ifoundthat": "Huomasin tällä kurssilla, että", "addon.mod_survey.ipreferthat": "Olisi hyvä, että", + "addon.mod_survey.modulenameplural": "Kyselyt", "addon.mod_survey.responses": "Vastauksia", "addon.mod_survey.results": "Tulokset", + "addon.mod_url.modulenameplural": "Verkko-osoitteet", "addon.mod_wiki.cannoteditpage": "Et saa muokata tätä sivua", "addon.mod_wiki.createpage": "Luo sivu", "addon.mod_wiki.editingpage": "Muokataan tätä sivua '{{$a}}'", "addon.mod_wiki.errorloadingpage": "Virhe ladattaessa sivua.", "addon.mod_wiki.errornowikiavailable": "Tässä Wikissä ei ole vielä yhtään sivua luotuna.", "addon.mod_wiki.map": "Kartta", + "addon.mod_wiki.modulenameplural": "Wikit", "addon.mod_wiki.newpagehdr": "Uusi sivu", "addon.mod_wiki.newpagetitle": "Uusi sivun otsikko", "addon.mod_wiki.nocontent": "Tälle sivulle ei ole sisältöä", @@ -648,6 +691,7 @@ "addon.mod_workshop.gradinggradecalculated": "Arvioinnin laskettu arvosana", "addon.mod_workshop.gradinggradeof": "Vertaisarvioinnin arvosana (max {{$a}})", "addon.mod_workshop.gradinggradeover": "Sivuuta vertaisarvioinnin arvosana", + "addon.mod_workshop.modulenameplural": "Työpajat", "addon.mod_workshop.nogradeyet": "Ei arvioitu", "addon.mod_workshop.notassessed": "Ei vielä vertaisarvioitu", "addon.mod_workshop.notoverridden": "Ei ylitetty", @@ -970,6 +1014,9 @@ "assets.mimetypes.text/rtf": "Rtf-tiedosto", "core.accounts": "Käyttäjätilit", "core.add": "Lisää", + "core.agelocationverification": "Iän ja sijainnin vahvistus", + "core.ago": "{{$a}} sitten", + "core.all": "Kaikki", "core.allparticipants": "Kaikki osallistujat", "core.answer": "Vastaus", "core.answered": "Vastattu", @@ -1002,6 +1049,7 @@ "core.confirmdeletefile": "Oletko varma että haluat poistaa tämän tiedoston?", "core.confirmloss": "Oletko varma? Kaikki muutokset katoavat.", "core.confirmopeninbrowser": "Haluatko avata tämän www-selaimessa?", + "core.considereddigitalminor": "Olet liian nuori luodaksi tunnuksen tälle sivustolle.", "core.content": "Sisältö", "core.contenteditingsynced": "Sisältö, jota muokkaat, on synkronoitu.", "core.contentlinks.chooseaccount": "Valitse käyttäjätili", @@ -1039,7 +1087,6 @@ "core.courses.cannotretrievemorecategories": "Kategorioita, jotka ovat \"syvemmällä\" kuin tasolla {{$a}} ei voida noutaa.", "core.courses.categories": "Kurssikategoriat", "core.courses.confirmselfenrol": "Oletko varma, että haluat lisätä itsesi kurssin osallistujaksi?", - "core.courses.courseoverview": "Kurssin yhteenveto", "core.courses.courses": "Kurssit", "core.courses.enrolme": "Lisää minut kurssin osallistujaksi", "core.courses.errorloadcategories": "Kategorioita ladattaessa tapahtui virhe.", @@ -1048,21 +1095,14 @@ "core.courses.errorselfenrol": "Itserekisteröitymisessä tapahtui virhe.", "core.courses.filtermycourses": "Suodata kursseja", "core.courses.frontpage": "Etusivu", - "core.courses.future": "Tulevat kurssit", - "core.courses.inprogress": "Keskeneräiset kurssit", - "core.courses.morecourses": "Lisää kursseja", "core.courses.mycourses": "Omat kurssini", + "core.courses.mymoodle": "Oma Moodle", "core.courses.nocourses": "Ei kurssitietoja näytettäväksi", - "core.courses.nocoursesfuture": "Ei tulevia kursseja", - "core.courses.nocoursesinprogress": "Ei keskeneräisiä kursseja", - "core.courses.nocoursesoverview": "Ei kursseja", - "core.courses.nocoursespast": "Ei menneitä kursseja", "core.courses.nocoursesyet": "Ei kursseja tässä kategoriassa", "core.courses.nosearchresults": "Ei tuloksia", "core.courses.notenroled": "Et ole ilmoittautuneena kurssille", "core.courses.notenrollable": "Et voi itserekisteröityä tälle kursille.", "core.courses.password": "Kurssiavain", - "core.courses.past": "Menneet kurssit", "core.courses.paymentrequired": "Tämä kurssi vaatii osallistumismaksun.", "core.courses.paypalaccepted": "PayPal maksu hyväksytty", "core.courses.search": "Hae", @@ -1081,6 +1121,8 @@ "core.deleting": "Poistetaan", "core.description": "Kuvaus", "core.dfdaymonthyear": "KK-PP-VVVV", + "core.digitalminor": "Digitaalinen alaikäinen", + "core.digitalminor_desc": "Pyydä huoltajaasi ottamaan yhteyttä:", "core.discard": "Hylkää", "core.dismiss": "Hylkää", "core.done": "Tehty", @@ -1103,6 +1145,7 @@ "core.errorrenamefile": "Tiedoston uudelleennimeämisessä tapahtui virhe. Ole hyvä ja yritä uudelleen.", "core.errorsync": "Synkronoinnissa tapahtui virhe. Ole hyvä ja yritä uudelleen.", "core.errorsyncblocked": "{{$a}} ei voida synkrnoida juuri nyt koska toinen prosessi on kesken. Ole hyvä ja yritä myöhemmin uudelleen. Jos ongelma jatkuu ole hyvä ja käynnistä mobiilisovellus uudelleen.", + "core.explanationdigitalminor": "Tämä tieto tarvitaan, jotta tiedetään, oletko riittävän vanha. Suojaikäraja on se ikä, jolloin voit itsenäisesti hyväksyä ehdot henkilötietojen käsittelylle.", "core.filename": "Tiedostonimi", "core.filenameexist": "Tiedoston nimi on jo käytössä: {{$a}}", "core.fileuploader.addfiletext": "Lisää tiedosto", @@ -1179,7 +1222,8 @@ "core.login.createaccount": "Luo uusi käyttäjätunnus.", "core.login.createuserandpass": "Valitse käyttäjätunnus ja salasana", "core.login.credentialsdescription": "Ole hyvä ja kirjoita käyttäjänimesi ja salasanasi, jotta voit kirjautua sisään.", - "core.login.emailconfirmsent": "

Sähköpostiviesti lähettiin osoitteeseen {{$a}}

Se sisältää ohjeet rekisteröinnin loppuunviemisestä.

Jos et onnistu rekisteröitymään ohjeista huolimatta, ole hyvä ja ota yhteyttä järjestelmän pääkäyttäjään.

", + "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.", @@ -1208,6 +1252,7 @@ "core.login.missingfirstname": "Puuttuva etunimi", "core.login.missinglastname": "Puuttuva sukunimi", "core.login.mobileservicesnotenabled": "Mobiilipalvelut eivät ole tällä hetkellä päällä järjestelmässä. Ole hyvä ja ota yhteyttä järjestelmän pääkäyttäjään ja pyydä heitä ottamaan mobiilipalvelut käyttöön.", + "core.login.mustconfirm": "Vahvista kirjautumisesi", "core.login.newaccount": "Uusi tunnus", "core.login.newsitedescription": "Ole hyvä ja kirjoita Moodle-sivuston verkko-osoite (URL). Ota huomioon, että mobiilipalvelut eivät välttämättä ole kytkettyinä päälle järjestelmässä.", "core.login.notloggedin": "Sinun täytyy olla kirjautuneena sisään.", @@ -1248,8 +1293,6 @@ "core.mainmenu.changesite": "Vaihda sivustoa", "core.mainmenu.help": "Ohje", "core.mainmenu.logout": "Kirjaudu ulos", - "core.mainmenu.mycourses": "Omat kurssini", - "core.mainmenu.togglemenu": "Valikko päälle/pois", "core.mainmenu.website": "Www-sivusto", "core.maxsizeandattachments": "Uusien tiedostojen kokoraja: {{$a.size}} ja liitetiedostojen maksimimäärä: {{$a.attachments}}", "core.min": "min", @@ -1330,8 +1373,10 @@ "core.question.requiresgrading": "Vaatii arvioinnin", "core.redirectingtosite": "Sinut uudelleenohjataan sivustolle.", "core.refresh": "Päivitä", + "core.remove": "Poista", "core.required": "Vaadittu", "core.requireduserdatamissing": "Tältä käyttäjältä puuttuu vaadittuja tietoja profiilistaan. Ole hyvä ja täytä tiedot sivustolla ja yritä uudestaan.
{{$a}}", + "core.resources": "Aineistot", "core.restore": "Palauta", "core.retry": "Yritä uudelleen", "core.save": "Tallenna", @@ -1392,6 +1437,19 @@ "core.sizemb": "Mt", "core.sorry": "Anteeksi..", "core.sortby": "Lajittele", + "core.strftimedate": "%d %B %Y", + "core.strftimedatefullshort": "%d/%m/%y", + "core.strftimedateshort": "%d %B", + "core.strftimedatetime": "%d %B %Y, %H:%M", + "core.strftimedatetimeshort": "%d.%m.%Y %H:%M", + "core.strftimedaydate": "%A, %d %B %Y", + "core.strftimedaydatetime": "%A, %d %B %Y, %H:%M", + "core.strftimedayshort": "%A, %d %B", + "core.strftimedaytime": "%a, %H:%M", + "core.strftimemonthyear": "%B %Y", + "core.strftimerecent": "%d %b, %H:%M", + "core.strftimerecentfull": "%a, %d %b %Y, %H:%M", + "core.strftimetime": "%H:%M", "core.submit": "Lähetä", "core.success": "Valmis!", "core.tablet": "Tabletti-tietokone", @@ -1422,7 +1480,7 @@ "core.user.firstname": "Etunimi", "core.user.interests": "Kiinnostukset", "core.user.lastname": "Sukunimi", - "core.user.manager": "Hallinoija", + "core.user.manager": "Hallinnoija", "core.user.newpicture": "Uusi kuva", "core.user.noparticipants": "Tälle kurssille ei löytynyt osallistujia", "core.user.participants": "Osallistujat", diff --git a/src/assets/lang/fr.json b/src/assets/lang/fr.json index d26845141..161983566 100644 --- a/src/assets/lang/fr.json +++ b/src/assets/lang/fr.json @@ -1,15 +1,61 @@ { + "addon.badges.alignment": "Compétence", "addon.badges.badgedetails": "Description du badge", "addon.badges.badges": "Badges", + "addon.badges.bendorsement": "Approbation", + "addon.badges.claimcomment": "Commentaire d'approbation", + "addon.badges.claimid": "URL de revendication", "addon.badges.contact": "Contact", "addon.badges.dateawarded": "Date de remise", "addon.badges.expired": "Échu", "addon.badges.expirydate": "Date d'échéance", + "addon.badges.imageauthoremail": "Courriel de l'auteur de l'image", + "addon.badges.imageauthorname": "Nom de l'auteur de l'image", + "addon.badges.imageauthorurl": "URL de l'auteur de l'image", + "addon.badges.imagecaption": "Légende de l'image", "addon.badges.issuancedetails": "Échéance du badge", "addon.badges.issuerdetails": "Détail de l'émetteur", + "addon.badges.issueremail": "Courriel", "addon.badges.issuername": "Nom de l'émetteur", + "addon.badges.issuerurl": "URL de l'émetteur", + "addon.badges.language": "Langue", + "addon.badges.noalignment": "ce badge n'a pas de compétences spécifiées.", "addon.badges.nobadges": "Il n'y a pas de badge disponible.", + "addon.badges.norelated": "Ce badge n'a aucun badge associé.", "addon.badges.recipientdetails": "Infos détenteur", + "addon.badges.relatedbages": "Badges associés", + "addon.badges.version": "Version", + "addon.badges.warnexpired": "(Ce page est arrivé à échéance !)", + "addon.block_activitymodules.pluginname": "Activités", + "addon.block_myoverview.all": "Tout", + "addon.block_myoverview.favourites": "Favoris", + "addon.block_myoverview.future": "À venir", + "addon.block_myoverview.hiddencourses": "Cachés", + "addon.block_myoverview.inprogress": "En cours", + "addon.block_myoverview.lastaccessed": "Dernier accès", + "addon.block_myoverview.morecourses": "Plus de cours", + "addon.block_myoverview.nocourses": "Pas de cours", + "addon.block_myoverview.past": "Passés", + "addon.block_myoverview.pluginname": "Vue d'ensemble des cours", + "addon.block_myoverview.title": "Nom", + "addon.block_recentlyaccessedcourses.nocourses": "Pas de cours récent", + "addon.block_recentlyaccessedcourses.pluginname": "Cours consultés récemment", + "addon.block_recentlyaccesseditems.noitems": "Pas d'élément récent", + "addon.block_recentlyaccesseditems.pluginname": "Éléments consultés récemment", + "addon.block_sitemainmenu.pluginname": "Menu principal", + "addon.block_starredcourses.nocourses": "Aucun cours marqué comme favori", + "addon.block_starredcourses.pluginname": "Cours marqués comme favoris", + "addon.block_timeline.duedate": "Délai de remise", + "addon.block_timeline.next30days": "30 prochains jours", + "addon.block_timeline.next3months": "3 prochains mois", + "addon.block_timeline.next6months": "6 prochains mois", + "addon.block_timeline.next7days": "7 prochains jours", + "addon.block_timeline.nocoursesinprogress": "Aucun cours actif", + "addon.block_timeline.noevents": "Aucune activité à rendre à venir", + "addon.block_timeline.overdue": "En retard", + "addon.block_timeline.pluginname": "Chronologie", + "addon.block_timeline.sortbycourses": "Trier par cours", + "addon.block_timeline.sortbydates": "Trier par date", "addon.calendar.calendar": "Calendrier", "addon.calendar.calendarevents": "Événements du calendrier", "addon.calendar.defaultnotificationtime": "Heure de notification par défaut", @@ -80,20 +126,20 @@ "addon.coursecompletion.completiondate": "Date d'achèvement", "addon.coursecompletion.completionmenuitem": "Achèvement", "addon.coursecompletion.couldnotloadreport": "Impossible de charger le rapport d'achèvement de cours. Veuillez essayer plus tard.", - "addon.coursecompletion.coursecompletion": "Achèvement du cours", + "addon.coursecompletion.coursecompletion": "Achèvement de cours", "addon.coursecompletion.criteria": "Critères", "addon.coursecompletion.criteriagroup": "Groupe de critères", - "addon.coursecompletion.criteriarequiredall": "Tous les critères ci-dessous sont requis.", - "addon.coursecompletion.criteriarequiredany": "Un des critères ci-dessous est requis.", + "addon.coursecompletion.criteriarequiredall": "Tous les critères ci-dessous sont requis", + "addon.coursecompletion.criteriarequiredany": "Un des critères ci-dessous est requis", "addon.coursecompletion.inprogress": "En cours", "addon.coursecompletion.manualselfcompletion": "Auto-achèvement manuel", "addon.coursecompletion.notyetstarted": "Pas encore commencé", - "addon.coursecompletion.pending": "En attente", + "addon.coursecompletion.pending": "En suspens", "addon.coursecompletion.required": "Requis", "addon.coursecompletion.requiredcriteria": "Critères requis", - "addon.coursecompletion.requirement": "Condition", + "addon.coursecompletion.requirement": "Prérequis", "addon.coursecompletion.status": "Statut", - "addon.coursecompletion.viewcoursereport": "Afficher le rapport du cours", + "addon.coursecompletion.viewcoursereport": "Consulter le rapport du cours", "addon.files.couldnotloadfiles": "La liste des fichiers n'a pas pu être chargée.", "addon.files.emptyfilelist": "Aucun fichier à afficher.", "addon.files.erroruploadnotworking": "Il n'est actuellement pas possible de déposer des fichiers sur votre site.", @@ -101,34 +147,80 @@ "addon.files.privatefiles": "Fichiers personnels", "addon.files.sitefiles": "Fichiers du site", "addon.messageoutput_airnotifier.processorsettingsdesc": "Configurer les appareils", + "addon.messages.acceptandaddcontact": "Accepter et ajouter aux contacts", "addon.messages.addcontact": "Ajouter ce contact", - "addon.messages.blockcontact": "Bloquer ce contact", - "addon.messages.blockcontactconfirm": "Vous ne recevrez plus de messages de ce contact.", + "addon.messages.addcontactconfirm": "Voulez-vous vraiment ajouter {{$a}} à vos contacts ?", + "addon.messages.addtofavourites": "Marquer comme favori", + "addon.messages.addtoyourcontacts": "Ajouter aux contacts", "addon.messages.blocknoncontacts": "Empêcher les utilisateurs hors liste de contacts de m'envoyer des messages personnels", + "addon.messages.blockuser": "Bloquer l'utilisateur", + "addon.messages.blockuserconfirm": "Voulez-vous vraiment bloquer {{$a}} ?", + "addon.messages.contactableprivacy": "Accepter des messages de :", + "addon.messages.contactableprivacy_coursemember": "Mes contacts et tout le monde dans mes cours", + "addon.messages.contactableprivacy_onlycontacts": "Mes contacts seulement", + "addon.messages.contactableprivacy_site": "Tout le monde sur le site", + "addon.messages.contactblocked": "Contact bloqué", "addon.messages.contactlistempty": "La liste des contacts est vide", "addon.messages.contactname": "Nom du contact", + "addon.messages.contactrequestsent": "Demande de contact envoyée", "addon.messages.contacts": "Contacts", + "addon.messages.decline": "Décliner", + "addon.messages.deleteallconfirm": "Voulez-vous vraiment supprimer l'intégralité de cette conversation ? Cette opération ne la supprimera pas pour les autres participants à la conversation.", + "addon.messages.deleteconversation": "Supprimer la conversation", + "addon.messages.deletemessage": "Supprimer le message", + "addon.messages.deletemessageconfirmation": "Voulez-vous vraiment supprimer ce message ? Il ne sera supprimé que de votre historique de messagerie et continuera d'être visible par l'utilisateur qui a envoyé ou reçu le message.", "addon.messages.errordeletemessage": "Erreur lors de la suppression du message.", "addon.messages.errorwhileretrievingcontacts": "Erreur lors de la récupération de contacts depuis le serveur.", "addon.messages.errorwhileretrievingdiscussions": "Erreur lors de la récupération de discussions depuis le serveur.", "addon.messages.errorwhileretrievingmessages": "Erreur lors de la récupération de messages depuis le serveur.", + "addon.messages.errorwhileretrievingusers": "Error lors de la récupération des utilisateurs depuis le serveur.", + "addon.messages.groupconversations": "Groupe", + "addon.messages.groupinfo": "Info du groupe", + "addon.messages.individualconversations": "Privée", + "addon.messages.info": "Info", + "addon.messages.isnotinyourcontacts": "{{$a}} n'est pas dans vos contacts", "addon.messages.message": "Message personnel", "addon.messages.messagenotsent": "Ce message n'a pas été envoyé. Veuillez essayer plus tard.", "addon.messages.messagepreferences": "Préférences des messages", "addon.messages.messages": "Messages personnels", "addon.messages.newmessage": "Nouveau message", "addon.messages.newmessages": "Nouveaux messages", - "addon.messages.nomessages": "Aucun message", + "addon.messages.nocontactrequests": "Aucune demande de contact", + "addon.messages.nocontactsgetstarted": "Aucun contact", + "addon.messages.nofavourites": "Aucune conversation favorite", + "addon.messages.nogroupconversations": "Pas de conversation de groupe", + "addon.messages.noindividualconversations": "Pas de conversation privée", + "addon.messages.nomessagesfound": "Aucun message personnel trouvé", + "addon.messages.noncontacts": "Non contact", "addon.messages.nousersfound": "Aucun utilisateur trouvé", + "addon.messages.numparticipants": "{{$a}} participants", "addon.messages.removecontact": "Supprimer ce contact", - "addon.messages.removecontactconfirm": "Le contact sera retiré de votre liste.", + "addon.messages.removecontactconfirm": "Voulez-vous vraiment retirer {{$a}} de vos contacts ?", + "addon.messages.removefromfavourites": "Retirer des favoris", + "addon.messages.removefromyourcontacts": "Retirer des contacts", + "addon.messages.requests": "Demandes", + "addon.messages.requirecontacttomessage": "Vous devez demander à {{$a}} de vous ajouter comme contact pour pouvoir lui envoyer un message", + "addon.messages.searchcombined": "Rechercher des personnes et des messages", + "addon.messages.searchnocontactsfound": "Aucun contact trouvé", + "addon.messages.searchnomessagesfound": "Aucun message trouvé", + "addon.messages.searchnononcontactsfound": "Aucun non contact trouvé", + "addon.messages.sendcontactrequest": "Envoyer une demande de cotnact", + "addon.messages.showdeletemessages": "Afficher les messages supprimés", "addon.messages.type_blocked": "Bloqué", "addon.messages.type_offline": "Hors connexion", "addon.messages.type_online": "En ligne", "addon.messages.type_search": "Résultats de recherche", "addon.messages.type_strangers": "Autres", - "addon.messages.unblockcontact": "Débloquer ce contact", + "addon.messages.unabletomessage": "Vous ne pouvez pas envoyer un message à cet utilisateur", + "addon.messages.unblockuser": "Débloquer l'utilisateur", + "addon.messages.unblockuserconfirm": "Voulez-vous vraiment débloquer {{$a}} ?", + "addon.messages.userwouldliketocontactyou": "{{$a}} aimerait vous contacter", + "addon.messages.warningconversationmessagenotsent": "Impossible d'envoyer le(s) message(s) à la conversation {{conversation}}. {{error}}", "addon.messages.warningmessagenotsent": "Impossible d'envoyer de message à l'utilisateur {{user}}. {{error}}", + "addon.messages.wouldliketocontactyou": "Aimerait vous contacter", + "addon.messages.you": "Vous :", + "addon.messages.youhaveblockeduser": "Vous avez bloqué cet utilisateur antérieurement", + "addon.messages.yourcontactrequestpending": "Votre demande de contact avec {{$a}} est en suspend", "addon.mod_assign.acceptsubmissionstatement": "Veuillez accepter les conditions d'envoi.", "addon.mod_assign.addattempt": "Autoriser une autre tentative", "addon.mod_assign.addnewattempt": "Ajouter une tentative", @@ -165,7 +257,9 @@ "addon.mod_assign.grade": "Note", "addon.mod_assign.graded": "Noté", "addon.mod_assign.gradedby": "Évalué par", + "addon.mod_assign.gradedfollowupsubmit": "Évalué - travail supplémentaire reçu", "addon.mod_assign.gradedon": "Évalué le", + "addon.mod_assign.gradelocked": "Cette note est verrouillée ou modifiée dans le carnet de notes.", "addon.mod_assign.gradenotsynced": "Note non synchronisée", "addon.mod_assign.gradeoutof": "Note sur {{$a}}", "addon.mod_assign.gradingstatus": "Statut de l'évaluation", @@ -180,6 +274,7 @@ "addon.mod_assign.markingworkflowstatereadyforrelease": "Prêt pour publication", "addon.mod_assign.markingworkflowstatereadyforreview": "Évaluation terminée", "addon.mod_assign.markingworkflowstatereleased": "Publié", + "addon.mod_assign.modulenameplural": "Devoirs", "addon.mod_assign.multipleteams": "Membre de plusieurs groupes", "addon.mod_assign.multipleteams_desc": "Ce devoir nécessite la remise des travaux en groupes. Vous faites partie de plusieurs groupes. Pour pouvoir remettre un travail, vous devez ne faire partie que d'un seul groupe. Veuillez contacter votre enseignant pour qu'il change votre appartenance aux groupes.", "addon.mod_assign.noattempt": "Aucune tentative", @@ -234,6 +329,7 @@ "addon.mod_assign_submission_file.pluginname": "Remises de fichiers", "addon.mod_assign_submission_onlinetext.pluginname": "Remise de textes en ligne", "addon.mod_book.errorchapter": "Erreur lors de la lecture du chapitre", + "addon.mod_book.modulenameplural": "Livres", "addon.mod_chat.beep": "Bip", "addon.mod_chat.currentusers": "Utilisateurs en ligne", "addon.mod_chat.enterchat": "Cliquer ici pour participer au chat", @@ -246,6 +342,7 @@ "addon.mod_chat.messagebeepsyou": "{{$a}} vient de vous biper !", "addon.mod_chat.messageenter": "{{$a}} vient d'entrer", "addon.mod_chat.messageexit": "{{$a}} a quitté ce chat", + "addon.mod_chat.modulenameplural": "Chats", "addon.mod_chat.mustbeonlinetosendmessages": "Vous devez être en ligne pour envoyer des messages.", "addon.mod_chat.nomessages": "Pas encore de messages", "addon.mod_chat.send": "Envoyer", @@ -256,6 +353,7 @@ "addon.mod_choice.errorgetchoice": "Erreur lors de l'obtention des données du choix.", "addon.mod_choice.expired": "Désolé, cette activité s'est terminée le {{$a}} et n'est plus disponible", "addon.mod_choice.full": "(complet)", + "addon.mod_choice.modulenameplural": "Sondages", "addon.mod_choice.noresultsviewable": "Les résultats ne sont actuellement pas visibles.", "addon.mod_choice.notopenyet": "Désolé, cette activité ne sera disponible que le {{$a}}", "addon.mod_choice.numberofuser": "Nombre de réponses", @@ -293,8 +391,10 @@ "addon.mod_data.errormustsupplyvalue": "Veuillez renseigner une valeur.", "addon.mod_data.expired": "Cette activité est fermée depuis {{$a}} et n'est plus disponible", "addon.mod_data.fields": "Champs", + "addon.mod_data.foundrecords": "Fiches trouvées : {{$a.num}}/{{$a.max}} (Réinitialiser les filtres)", "addon.mod_data.latlongboth": "La latitude et la longitude sont requises.", "addon.mod_data.menuchoose": "Sélectionner...", + "addon.mod_data.modulenameplural": "Bases de données", "addon.mod_data.more": "Plus", "addon.mod_data.nomatch": "Aucune fiche trouvée !", "addon.mod_data.norecords": "Aucune fiche dans la base de données", @@ -326,6 +426,7 @@ "addon.mod_feedback.feedbackopen": "Permettre les réponses dès le", "addon.mod_feedback.mapcourses": "Associer le feedback aux cours", "addon.mod_feedback.mode": "Mode", + "addon.mod_feedback.modulenameplural": "Feedbacks", "addon.mod_feedback.next_page": "Page suivante", "addon.mod_feedback.non_anonymous": "Le nom du participant sera enregistré et affiché avec ses réponses", "addon.mod_feedback.non_anonymous_entries": "Réponses non anonymes ({{$a}})", @@ -346,6 +447,7 @@ "addon.mod_feedback.started": "Commencé", "addon.mod_feedback.this_feedback_is_already_submitted": "Vous avez déjà effectué cette activité.", "addon.mod_folder.emptyfilelist": "Aucun fichier à afficher.", + "addon.mod_folder.modulenameplural": "Dossiers", "addon.mod_forum.addanewdiscussion": "Ajouter une discussion", "addon.mod_forum.addanewquestion": "Ajouter une nouvelle question", "addon.mod_forum.addanewtopic": "Ajouter un nouveau sujet", @@ -368,6 +470,7 @@ "addon.mod_forum.modeflatnewestfirst": "Réponses en ligne, la plus récente en premier", "addon.mod_forum.modeflatoldestfirst": "Réponses en ligne, la plus ancienne en premier", "addon.mod_forum.modenested": "Réponses emboîtées", + "addon.mod_forum.modulenameplural": "Forums", "addon.mod_forum.numdiscussions": "{{numdiscussions}} discussions", "addon.mod_forum.numreplies": "{{numreplies}} réponses", "addon.mod_forum.posttoforum": "Envoyer", @@ -403,9 +506,11 @@ "addon.mod_glossary.fillfields": "Les champs concept et définition sont obligatoires.", "addon.mod_glossary.fullmatch": "Correspondance mot pour mot", "addon.mod_glossary.linking": "Liaison automatique", + "addon.mod_glossary.modulenameplural": "Glossaires", "addon.mod_glossary.noentriesfound": "Aucun article trouvé.", "addon.mod_glossary.searchquery": "Texte recherché", "addon.mod_imscp.deploymenterror": "Erreur dans le contenu du paquetage !", + "addon.mod_imscp.modulenameplural": "Paquetages IMS Content", "addon.mod_imscp.showmoduledescription": "Afficher la description", "addon.mod_lesson.answer": "Réponse", "addon.mod_lesson.attempt": "Tentative : {{$a}}", @@ -449,6 +554,7 @@ "addon.mod_lesson.lowtime": "Plus courte durée", "addon.mod_lesson.maximumnumberofattemptsreached": "Le nombre maximal de tentatives a été atteint - On passe à la page suivante", "addon.mod_lesson.modattemptsnoteacher": "La critique par les étudiants ne fonctionne que pour les étudiants.", + "addon.mod_lesson.modulenameplural": "Leçons", "addon.mod_lesson.noanswer": "Une ou plusieurs questions n'ont pas de réponse donnée. Veuillez revenir en arrière et donner une réponse.", "addon.mod_lesson.nolessonattempts": "Personne n'a encore fait cette leçon.", "addon.mod_lesson.nolessonattemptsgroup": "Aucune tentative n'a été effectuée dans cette leçon par des membres du groupe {{$a}}.", @@ -493,7 +599,9 @@ "addon.mod_lti.errorgetlti": "Erreur lors de l'obtention des données du module.", "addon.mod_lti.errorinvalidlaunchurl": "L'URL de lancement n'est pas valide.", "addon.mod_lti.launchactivity": "Lancer l'activité", + "addon.mod_lti.modulenameplural": "Outils externes", "addon.mod_page.errorwhileloadingthepage": "Erreur lors du chargement du contenu de la page.", + "addon.mod_page.modulenameplural": "Pages", "addon.mod_quiz.attemptfirst": "Première tentative", "addon.mod_quiz.attemptlast": "Dernière tentative", "addon.mod_quiz.attemptnumber": "Tentative", @@ -528,6 +636,7 @@ "addon.mod_quiz.grademethod": "Méthode d'évaluation", "addon.mod_quiz.gradesofar": "{{$a.method}} : {{$a.mygrade}} / {{$a.quizgrade}}.", "addon.mod_quiz.marks": "Points", + "addon.mod_quiz.modulenameplural": "Tests", "addon.mod_quiz.mustbesubmittedby": "Cette tentative doit être envoyée avant {{$a}}.", "addon.mod_quiz.noquestions": "Aucune question n'a été encore ajoutée", "addon.mod_quiz.noreviewattempt": "Vous n'êtes pas autorisé à relire cette tentative.", @@ -572,6 +681,7 @@ "addon.mod_quiz.yourfinalgradeis": "Votre note finale pour ce test est {{$a}}.", "addon.mod_resource.errorwhileloadingthecontent": "Erreur lors du chargement du contenu", "addon.mod_resource.modifieddate": "Modifié {{$a}}", + "addon.mod_resource.modulenameplural": "Fichiers", "addon.mod_resource.openthefile": "Ouvrir le fichier", "addon.mod_resource.uploadeddate": "Déposé le {{$a}}", "addon.mod_scorm.asset": "Élément", @@ -608,6 +718,7 @@ "addon.mod_scorm.incomplete": "Incomplet", "addon.mod_scorm.lastattempt": "Dernière tentative terminée", "addon.mod_scorm.mode": "Mode", + "addon.mod_scorm.modulenameplural": "Paquetages SCORM", "addon.mod_scorm.newattempt": "Commencer une nouvelle tentative", "addon.mod_scorm.noattemptsallowed": "Nombre de tentatives permises", "addon.mod_scorm.noattemptsmade": "Nombre de tentatives effectuées", @@ -627,10 +738,12 @@ "addon.mod_survey.errorgetsurvey": "Erreur lors de l'obtention des données de la consultation.", "addon.mod_survey.ifoundthat": "J'ai trouvé que", "addon.mod_survey.ipreferthat": "J'aime mieux quand", + "addon.mod_survey.modulenameplural": "Consultations", "addon.mod_survey.responses": "Réponses", "addon.mod_survey.results": "Résultats", "addon.mod_survey.surveycompletednograph": "Vous avez terminé ce sondage.", "addon.mod_url.accessurl": "Accéder à l'URL", + "addon.mod_url.modulenameplural": "URLs", "addon.mod_url.pointingtourl": "URL vers laquelle cette ressource dirige.", "addon.mod_wiki.cannoteditpage": "Vous ne pouvez pas modifier cette page", "addon.mod_wiki.createpage": "Créer une page", @@ -639,6 +752,7 @@ "addon.mod_wiki.errornowikiavailable": "Ce wiki n'a pas encore de contenu.", "addon.mod_wiki.gowikihome": "Vers la première page du wiki", "addon.mod_wiki.map": "Carte", + "addon.mod_wiki.modulenameplural": "Wikis", "addon.mod_wiki.newpagehdr": "Nouvelle page", "addon.mod_wiki.newpagetitle": "Titre de la nouvelle page", "addon.mod_wiki.nocontent": "Cette page n'a pas de contenu", @@ -677,6 +791,7 @@ "addon.mod_workshop.gradinggradecalculated": "Note calculée pour l'évaluation", "addon.mod_workshop.gradinggradeof": "Note pour l'évaluation (sur {{$a}})", "addon.mod_workshop.gradinggradeover": "Modifier la note de l'évaluation", + "addon.mod_workshop.modulenameplural": "Ateliers", "addon.mod_workshop.nogradeyet": "Pas encore de note", "addon.mod_workshop.notassessed": "Pas encore évalué", "addon.mod_workshop.notoverridden": "Pas modifié", @@ -693,6 +808,7 @@ "addon.mod_workshop.submissiongrade": "Note pour le travail remis", "addon.mod_workshop.submissiongradeof": "Note pour le travail remis (sur {{$a}})", "addon.mod_workshop.submissionrequiredcontent": "Veuillez saisir du texte ou ajouter un fichier.", + "addon.mod_workshop.submissionrequiredtitle": "Veuillez indiquer un titre.", "addon.mod_workshop.submissionsreport": "Rapport de remise de l'atelier", "addon.mod_workshop.submissiontitle": "Titre", "addon.mod_workshop.switchphase10": "Passer à la phase de configuration", @@ -1041,6 +1157,8 @@ "core.accounts": "Comptes", "core.add": "Ajouter", "core.agelocationverification": "Vérification de l'âge et du lieu", + "core.ago": "Il y a {{$a}}", + "core.all": "Tout", "core.allparticipants": "Tous les participants", "core.android": "Android", "core.answer": "Réponse", @@ -1078,7 +1196,7 @@ "core.confirmdeletefile": "Voulez-vous vraiment supprimer ce fichier ?", "core.confirmloss": "Vraiment ? Toutes les modifications seront perdues.", "core.confirmopeninbrowser": "Voulez-vous l'ouvrir dans un navigateur ?", - "core.considereddigitalminor": "Vous êtes considéré comme un mineur numérique.", + "core.considereddigitalminor": "Vous êtes trop jeune pour créer un compte sur ce site.", "core.content": "Contenu", "core.contenteditingsynced": "Le contenu que vous modifiez a été synchronisé.", "core.contentlinks.chooseaccount": "Sélectionner un compte", @@ -1108,17 +1226,21 @@ "core.course.errorgetmodule": "Erreur lors de l'obtention des données du module.", "core.course.hiddenfromstudents": "Caché pour les étudiants", "core.course.hiddenoncoursepage": "Disponible, mais pas affiché sur la page de cours", + "core.course.manualcompletionnotsynced": "Achèvement manuel non synchronisé.", "core.course.nocontentavailable": "Aucun contenu disponible actuellement.", "core.course.overriddennotice": "Votre note finale pour cette activité a été ajustée manuellement.", + "core.course.refreshcourse": "Actualiser le cours", "core.course.sections": "Sections", "core.course.useactivityonbrowser": "Vous pouvez continuer à l'utiliser avec le navigateur de votre appareil.", + "core.course.warningmanualcompletionmodified": "L'achèvement manuel d'une activité a été modifié sur le site.", + "core.course.warningofflinemanualcompletiondeleted": "Certains achèvement manuels hors ligne du cours « {{name}} » ont été supprimés. {{error}}", "core.coursedetails": "Informations détaillées du cours", + "core.courses.addtofavourites": "Marquer comme favori", "core.courses.allowguests": "Les visiteurs anonymes sont autorisés dans ce cours", "core.courses.availablecourses": "Cours disponibles", "core.courses.cannotretrievemorecategories": "Les catégories de niveau plus grand que {{$a}} ne peuvent pas être chargées.", "core.courses.categories": "Catégories de cours", "core.courses.confirmselfenrol": "Voulez-vous vraiment vous inscrire dans ce cours ?", - "core.courses.courseoverview": "Aperçu du cours", "core.courses.courses": "Cours", "core.courses.downloadcourses": "Télécharger les cours", "core.courses.enrolme": "M'inscrire", @@ -1128,33 +1250,24 @@ "core.courses.errorselfenrol": "Une erreur est survenue durant l'auto-inscription.", "core.courses.filtermycourses": "Filtrer mes cours", "core.courses.frontpage": "Page d'accueil", - "core.courses.future": "À venir", - "core.courses.inprogress": "En cours", - "core.courses.morecourses": "Plus de cours", + "core.courses.hidecourse": "Cacher de l'affichage", "core.courses.mycourses": "Mes cours", - "core.courses.next30days": "30 prochains jours", - "core.courses.next7days": "7 prochains jours", + "core.courses.mymoodle": "Tableau de bord", "core.courses.nocourses": "Aucune information de cours à afficher.", - "core.courses.nocoursesfuture": "Pas de cours à venir", - "core.courses.nocoursesinprogress": "Pas de cours en cours", - "core.courses.nocoursesoverview": "Aucun cours", - "core.courses.nocoursespast": "Pas de cours passé", "core.courses.nocoursesyet": "Il n'y a pas encore de cours", - "core.courses.noevents": "Aucune activité à rendre à venir", "core.courses.nosearchresults": "Aucun résultat", "core.courses.notenroled": "Vous n'êtes pas inscrit à ce cours", "core.courses.notenrollable": "Vous ne pouvez pas vous inscrire vous-même à ce cours.", "core.courses.password": "Clef d'inscription", - "core.courses.past": "Passé", "core.courses.paymentrequired": "Un paiement est requis pour accéder à ce cours.", "core.courses.paypalaccepted": "Paiements par PayPal acceptés", + "core.courses.removefromfavourites": "Retirer ce cours des favoris", "core.courses.search": "Rechercher", "core.courses.searchcourses": "Rechercher des cours", "core.courses.searchcoursesadvice": "Veuillez utiliser le bouton de recherche de cours pour accéder anonymement à des cours ou vous inscrire vous-même à des cours qui le permettent.", "core.courses.selfenrolment": "Auto-inscription", "core.courses.sendpaymentbutton": "Envoyer un paiement avec PayPal", - "core.courses.sortbycourses": "Trier par cours", - "core.courses.sortbydates": "Trier par date", + "core.courses.show": "Afficher ce cours", "core.courses.totalcoursesearchresults": "Nombre de cours : {{$a}}", "core.currentdevice": "Appareil actuel", "core.datastoredoffline": "Données stockées sur l'appareil, car elles n'ont pas pu être envoyées. Elles seront automatiquement envoyées ultérieurement.", @@ -1174,7 +1287,7 @@ "core.dfmediumdate": "LLL", "core.dftimedate": "hh[:]mm", "core.digitalminor": "Mineur numérique", - "core.digitalminor_desc": "Pour créer un compte sur ce site, veuillez demander à votre représentant légal de contacter la personne suivante.", + "core.digitalminor_desc": "Veuillez demander à votre représentant légal de contacter :", "core.discard": "Ignorer", "core.dismiss": "Rejeter", "core.done": "Terminé", @@ -1191,13 +1304,15 @@ "core.errorinvalidform": "Le formulaire comporte des données non valides. Veuillez vous assurer que tous les champs requis sont renseignés et que les données sont valides.", "core.errorinvalidresponse": "Réponse reçue non valide. Veuillez contacter l'administrateur de votre plateforme si l'erreur persiste.", "core.errorloadingcontent": "Erreur lors du chargement du contenu.", + "core.errorofflinedisabled": "La consultation hors ligne est désactivée sur votre site. Vous devez vous connecter à Internet pour utiliser l'app.", "core.erroropenfilenoapp": "Erreur lors de l'ouverture du fichier : aucune app trouvée pour ouvrir ce type de fichier.", "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.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 pourvoir donner son consentement à l'accès et au stockage par un tiers des données personnelles la concernant.", + "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.", + "core.favourites": "Favori", "core.filename": "Nom de fichier", "core.filenameexist": "Le nom de fichier existe déjà : {{$a}}", "core.fileuploader.addfiletext": "Ajouter un fichier", @@ -1221,6 +1336,7 @@ "core.fileuploader.more": "Plus", "core.fileuploader.photoalbums": "Albums photos", "core.fileuploader.readingfile": "Lecture du fichier", + "core.fileuploader.readingfileperc": "Lecture du fichier : {{$a}} %", "core.fileuploader.selectafile": "Choisir un fichier", "core.fileuploader.uploadafile": "Déposer un fichier", "core.fileuploader.uploading": "Envoi", @@ -1279,20 +1395,23 @@ "core.login.createaccount": "Créer mon compte", "core.login.createuserandpass": "Créer un compte", "core.login.credentialsdescription": "Veuillez fournir votre nom d'utilisateur et votre mot de passe pour vous connecter.", - "core.login.emailconfirmsent": "

Un message vous a été envoyé par courriel à l'adresse {{$a}}

Il contient des instructions vous permettant de terminer votre enregistrement.

En cas de difficulté, veuillez contacter l'administrateur de la plateforme.

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

Un message vous a été envoyé à l'adresse de courriel {{$a}}.

Il contient les instructions pour terminer votre enregistrement.

Si vous rencontrez des difficultés, veuillez contacter l'administrateur du site.

", + "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.", + "core.login.findyoursite": "Trouver votre site", "core.login.firsttime": "Première visite sur ce site ?", "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 existe plusieurs milliers de sites Moodle dans le monde. Cette app vous permet de vous connecter aux sites Moodle qui ont explicitement activé l'accès via l'app mobile.

Si vous ne pouvez pas vous connecter à votre site Moodle, veuillez contacter son administrateur local et lui demander de lire http://docs.moodle.org/en/Mobile_app

Pour tester l'app dans une plateforme Moodle de démonstration, tapez teacher ou student dans le champ Adresse du site et tapoter le bouton de connexion.

", + "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", "core.login.invalidaccount": "Veuillez vérifier vos données de connexion ou demander à l'administrateur de votre site de vérifier sa configuration.", - "core.login.invaliddate": "Date non valide", + "core.login.invaliddate": "Date invalide", "core.login.invalidemail": "Adresse de courriel incorrecte", "core.login.invalidmoodleversion": "Version de Moodle non valide. La version minimale requise est 2.4.", "core.login.invalidsite": "Cette URL n'est pas valide.", @@ -1300,6 +1419,9 @@ "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", @@ -1310,6 +1432,7 @@ "core.login.missingfirstname": "Le prénom ne peut pas être vide", "core.login.missinglastname": "Le nom ne peut pas être vide", "core.login.mobileservicesnotenabled": "Les Services Moodle ne sont pas activés sur votre site. Veuillez contacter l'administrateur de votre site si vous pensez qu'ils devraient être activés.", + "core.login.mustconfirm": "Vous devez confirmer l'enregistrement de votre compte", "core.login.newaccount": "Nouveau compte", "core.login.newsitedescription": "Veuillez saisir l'URL de votre plateforme Moodle. Notez qu'elle peut ne pas être configurée pour fonctionner avec cette app.", "core.login.notloggedin": "Vous devez être connecté.", @@ -1326,15 +1449,19 @@ "core.login.problemconnectingerrorcontinue": "Veuillez vérifier l'adresse indiquée et essayer à nouveau.", "core.login.profileinvaliddata": "Valeur incorrecte", "core.login.recaptchachallengeimage": "Image reCAPTCHA", + "core.login.recaptchaexpired": "Vérification échue. Veuillez de nouveau répondre à la question de sécurité.", + "core.login.recaptchaincorrect": "La réponse à la question de sécurité est incorrecte.", "core.login.reconnect": "Reconnecter", "core.login.reconnectdescription": "Votre jeton d'authentification est non valide ou échu. Veuillez vous reconnecter à la plateforme.", "core.login.reconnectssodescription": "Votre jeton d'authentification est non valide ou échu. Veuillez vous reconnecter à la plateforme, en vous connectant dans un navigateur.", + "core.login.resendemail": "Renvoyer le courriel", "core.login.searchby": "Rechercher par :", "core.login.security_question": "Question de sécurité", "core.login.selectacountry": "Choisir un pays", "core.login.selectsite": "Veuillez sélectionner votre site :", "core.login.signupplugindisabled": "{{$a}} n'est pas activée.", "core.login.siteaddress": "Adresse de la plateforme", + "core.login.sitehasredirect": "Votre site contient au moins une redirection HTTP. L'app ne peut pas suivre les redirections. C'est sans doute la raison empêche l'app de se connecter à votre site.", "core.login.siteinmaintenance": "Votre site est en mode de maintenance", "core.login.sitepolicynotagreederror": "Règlement du site pas accepté.", "core.login.siteurl": "URL du site", @@ -1353,8 +1480,6 @@ "core.mainmenu.changesite": "Changer de plateforme", "core.mainmenu.help": "Aide", "core.mainmenu.logout": "Déconnexion", - "core.mainmenu.mycourses": "Mes cours", - "core.mainmenu.togglemenu": "Menu", "core.mainmenu.website": "Site web", "core.maxsizeandattachments": "Taille maximale des nouveaux fichiers : {{$a.size}}. Nombre maximal d'annexes : {{$a.attachments}}", "core.min": "min", @@ -1389,6 +1514,7 @@ "core.more": "suite", "core.mygroups": "Mes groupes", "core.name": "Nom", + "core.networkerroriframemsg": "Ce contenu n'est pas disponible hors ligne. Veuillez vous connecter à Internet et ressayer.", "core.networkerrormsg": "Un problème est survenu lors de la connexion au site. Veuillez vérifier votre connexion et essayer à nouveau.", "core.never": "Jamais", "core.next": "Suivant", @@ -1397,6 +1523,7 @@ "core.nograde": "Pas de note", "core.none": "Aucun", "core.nopasswordchangeforced": "Vous ne pouvez pas continuer sans changer votre mot de passe.", + "core.nopermissionerror": "Vous n'avez pas l'autorisation de faire cela", "core.nopermissions": "Désolé, vous n'avez actuellement pas les droits d'accès requis pour effectuer ceci ({{$a}})", "core.noresults": "Aucun résultat", "core.notapplicable": "n/a", @@ -1421,6 +1548,7 @@ "core.pulltorefresh": "Tirer pour actualiser", "core.question.answer": "Réponse", "core.question.answersaved": "Réponse enregistrée", + "core.question.cannotdeterminestatus": "Impossible de déterminer le statut", "core.question.certainty": "Certitude", "core.question.complete": "Terminer", "core.question.correct": "Correct", @@ -1441,8 +1569,10 @@ "core.quotausage": "Vous utilisez actuellement {{$a.used}} de votre quota de {{$a.total}}.", "core.redirectingtosite": "Vous allez être redirigé vers le site.", "core.refresh": "Actualiser", + "core.remove": "Supprimer", "core.required": "Requis", "core.requireduserdatamissing": "Il manque certaines données au profil de cet utilisateur. Veuillez compléter ces données dans votre plateforme et essayer à nouveau.
{{$a}}", + "core.resources": "Ressources", "core.restore": "Restauration", "core.retry": "Essayer à nouveau", "core.save": "Enregistrer", @@ -1459,6 +1589,7 @@ "core.settings.appready": "Prêt pour l'app", "core.settings.cannotsyncoffline": "Impossible de synchroniser hors connexion.", "core.settings.cannotsyncwithoutwifi": "Impossible de synchroniser car les réglages ne permettent la synchronisation que lorsque une connexion Wi-Fi est établie. Veuillez vous connecter à un réseau Wi-Fi.", + "core.settings.compilationinfo": "Info de compilation", "core.settings.cordovadevicemodel": "Modèle Cordova Device", "core.settings.cordovadeviceosversion": "Version OS Cordova Device", "core.settings.cordovadeviceplatform": "Plateforme Cordova Device", @@ -1466,6 +1597,7 @@ "core.settings.cordovaversion": "Version Cordova", "core.settings.currentlanguage": "Langue active", "core.settings.debugdisplay": "Afficher les informations de débogage", + "core.settings.debugdisplaydescription": "Si ce réglage est activé, les dialogues d'erreur afficheront plus d'informations sur l'erreur, dans la mesure du possible.", "core.settings.deletesitefiles": "Voulez-vous vraiment supprimer les fichiers téléchargés depuis le site « {{sitename}} » ?", "core.settings.deletesitefilestitle": "Supprimer les fichiers du site", "core.settings.deviceinfo": "Info sur l'appareil", @@ -1496,6 +1628,7 @@ "core.settings.privacypolicy": "Politique de confidentialité", "core.settings.reportinbackground": "Annoncer les erreurs automatiquement", "core.settings.settings": "Paramètres", + "core.settings.showdownloadoptions": "Afficher les options de téléchargement", "core.settings.sites": "Sites", "core.settings.spaceusage": "Espace utilisé", "core.settings.synchronization": "Synchronisation", @@ -1527,6 +1660,21 @@ "core.sizetb": "To", "core.sorry": "Désolé...", "core.sortby": "Trier par", + "core.strftimedate": "%d %B %Y", + "core.strftimedatefullshort": "%d %b %y", + "core.strftimedateshort": "%d %B", + "core.strftimedatetime": "%d %B %Y, %H:%M", + "core.strftimedatetimeshort": "%d %b %y, %H:%M", + "core.strftimedaydate": "%A %d %B %Y", + "core.strftimedaydatetime": "%A %d %B %Y, %H:%M", + "core.strftimedayshort": "%A %d %B", + "core.strftimedaytime": "%a, %H:%M", + "core.strftimemonthyear": "%B %Y", + "core.strftimerecent": "%d %b, %H:%M", + "core.strftimerecentfull": "%a %d %b %Y, %H:%M", + "core.strftimetime": "%H:%M", + "core.strftimetime12": "%I:%M", + "core.strftimetime24": "%H:%M", "core.submit": "Envoyer", "core.success": "Succès", "core.tablet": "Tablette", @@ -1556,6 +1704,7 @@ "core.user.editingteacher": "Enseignant", "core.user.email": "Adresse de courriel", "core.user.emailagain": "Courriel (confirmation)", + "core.user.errorloaduser": "Erreur lors du chargement de l'utilisateur.", "core.user.firstname": "Prénom", "core.user.interests": "Centres d'intérêt", "core.user.lastname": "Nom", @@ -1568,13 +1717,16 @@ "core.user.roles": "Rôles", "core.user.sendemail": "Courriel", "core.user.student": "Étudiant", - "core.user.teacher": "Enseignant non-éditeur", + "core.user.teacher": "Enseignant non éditeur", "core.user.webpage": "Page Web", "core.userdeleted": "Le compte de cet utilisateur a été supprimé", "core.userdetails": "Informations détaillées", "core.usernotfullysetup": "Utilisateur pas complètement défini", "core.users": "Utilisateurs", "core.view": "Affichage", + "core.viewcode": "Afficher le code", + "core.vieweditor": "Afficher l'éditeur", + "core.viewembeddedcontent": "Afficher le contenu intégré", "core.viewprofile": "Consulter le profil", "core.warningofflinedatadeleted": "Des données locales de {{component}} « {{name}} » ont été supprimées. {{error}}", "core.whatisyourage": "Quel âge avez-vous ?", diff --git a/src/assets/lang/he.json b/src/assets/lang/he.json index 2c1bd1eca..945a4102d 100644 --- a/src/assets/lang/he.json +++ b/src/assets/lang/he.json @@ -8,8 +8,18 @@ "addon.badges.issuancedetails": "מועד תפוגת ההישג", "addon.badges.issuerdetails": "פרטי הגורם אשר העניק את ההישג", "addon.badges.issuername": "שם מעניק ההישג", + "addon.badges.issuerurl": "קישור מעניק ההישג", "addon.badges.nobadges": "אין הישגים זמינים.", "addon.badges.recipientdetails": "פרטי המכותב", + "addon.badges.warnexpired": "(תוקפו של הישג זה פג!)", + "addon.block_activitymodules.pluginname": "פעילויות", + "addon.block_myoverview.future": "עתידי", + "addon.block_myoverview.inprogress": "בלמידה", + "addon.block_myoverview.morecourses": "קורסים נוספים", + "addon.block_myoverview.nocourses": "טרם נרשמתם לקורס כלשהו", + "addon.block_myoverview.past": "ארכיון", + "addon.block_myoverview.pluginname": "עדכונים בקורסים שלי", + "addon.block_sitemainmenu.pluginname": "תפריט ראשי", "addon.calendar.calendar": "לוח-שנה", "addon.calendar.calendarevents": "אירועי לוח שנה", "addon.calendar.errorloadevent": "שגיאה בטעינת האירוע.", @@ -70,18 +80,18 @@ "addon.coursecompletion.completed": "הושלם", "addon.coursecompletion.completiondate": "תאריך השלמה", "addon.coursecompletion.completionmenuitem": "השלמה", - "addon.coursecompletion.coursecompletion": "השלמת קורס", - "addon.coursecompletion.criteria": "מדד־הערכה", - "addon.coursecompletion.criteriagroup": "קבוצת מדדיי־הערכה", - "addon.coursecompletion.criteriarequiredall": "כל מדדיי־הערכה להלן נדרשים", - "addon.coursecompletion.criteriarequiredany": "אחד ממדדיי־הערכה להלן נדרש", + "addon.coursecompletion.coursecompletion": "תנאי השלמת קורס", + "addon.coursecompletion.criteria": "תנאי", + "addon.coursecompletion.criteriagroup": "קבוצת תנאים", + "addon.coursecompletion.criteriarequiredall": "כל התנאים המצויינים מטה נדרשים", + "addon.coursecompletion.criteriarequiredany": "לפחות אחד מהתנאים המצויינים מטה נדרשים", "addon.coursecompletion.inprogress": "בתהליך", - "addon.coursecompletion.manualselfcompletion": "הזנת השלמה עצמית", - "addon.coursecompletion.notyetstarted": "עדיין לא החל", - "addon.coursecompletion.pending": "בהמתנה", - "addon.coursecompletion.required": "נדרש", - "addon.coursecompletion.requiredcriteria": "מדד־הערכה נדרש", - "addon.coursecompletion.requirement": "דרישה", + "addon.coursecompletion.manualselfcompletion": "השלמה עצמאית ידנית", + "addon.coursecompletion.notyetstarted": "עדיין לא התחיל", + "addon.coursecompletion.pending": "בתהליך למידה", + "addon.coursecompletion.required": "דרוש", + "addon.coursecompletion.requiredcriteria": "תנאי נדרש", + "addon.coursecompletion.requirement": "דרישות", "addon.coursecompletion.status": "מצב", "addon.coursecompletion.viewcoursereport": "צפיה בדוח הקורס", "addon.files.couldnotloadfiles": "לא ניתן לטעון את רשימת הקבצים.", @@ -90,11 +100,13 @@ "addon.files.privatefiles": "הקבצים שלי", "addon.files.sitefiles": "קבצי האתר", "addon.messages.addcontact": "הוספת איש קשר", - "addon.messages.blockcontact": "חסימת איש הקשר", + "addon.messages.addtoyourcontacts": "הוספה לרשימת החברים", "addon.messages.blocknoncontacts": "חסימת כל המסרים החדשים מאנשים שלא נמצאים ברשימת אנשי הקשר שלי", + "addon.messages.contactblocked": "משתמש חסום", "addon.messages.contactlistempty": "רשימת אנשי הקשר ריקה", "addon.messages.contactname": "שם איש קשר", "addon.messages.contacts": "אנשי קשר", + "addon.messages.deleteallconfirm": "האם למחוק את כל השיחה?", "addon.messages.errorwhileretrievingcontacts": "שגיאה בזמן טעינת אנשי קשר מהשרת.", "addon.messages.errorwhileretrievingdiscussions": "שגיאה בזמן טעינת הדיונים מהשרת.", "addon.messages.errorwhileretrievingmessages": "שגיאה בזמן טעינת המסרים מהשרת.", @@ -103,15 +115,18 @@ "addon.messages.messagepreferences": "העדפות מסרים", "addon.messages.messages": "מסרים", "addon.messages.newmessage": "הודעה חדשה", - "addon.messages.nomessages": "אין מסרים ממתינים", + "addon.messages.nomessagesfound": "לא נמצאו מסרים", + "addon.messages.noncontacts": "שאינם אנשי קשר", "addon.messages.nousersfound": "לא נמצאו משתמשים", "addon.messages.removecontact": "הסרת איש הקשר", + "addon.messages.removefromyourcontacts": "הסרה מרשימת החברים", + "addon.messages.searchcombined": "חיפוש משתמשים ומסרים", "addon.messages.type_blocked": "חסומים", "addon.messages.type_offline": "לא מחוברים", "addon.messages.type_online": "מחוברים", "addon.messages.type_search": "תוצאות חיפוש", "addon.messages.type_strangers": "אחרים", - "addon.messages.unblockcontact": "ביטול חסימת איש הקשר", + "addon.messages.you": "את/ה:", "addon.mod_assign.addattempt": "אפשר נסיון נוסף", "addon.mod_assign.addnewattempt": "הוספת נסיון חדש", "addon.mod_assign.addnewattemptfromprevious": "הוספת נסיון נוסף המבוסס על ההגשה האחרונה", @@ -142,6 +157,7 @@ "addon.mod_assign.graded": "נבדק", "addon.mod_assign.gradedby": "נבדק על-ידי", "addon.mod_assign.gradedon": "הציון ניתן על", + "addon.mod_assign.gradelocked": "הציון נעול או שעודכן בגליון הציונים", "addon.mod_assign.gradeoutof": "ציון מתוך {{$a}}", "addon.mod_assign.gradingstatus": "מצב מתן הציון", "addon.mod_assign.groupsubmissionsettings": "הגדרות הגשה בקבוצות", @@ -155,6 +171,7 @@ "addon.mod_assign.markingworkflowstatereadyforrelease": "הניקוד ממתין להפצה", "addon.mod_assign.markingworkflowstatereadyforreview": "בדיקה ראשונית הסתיימה", "addon.mod_assign.markingworkflowstatereleased": "הניקוד זמין לסטודנט", + "addon.mod_assign.modulenameplural": "מטלות", "addon.mod_assign.multipleteams": "חבר ביותר מקבוצה אחת", "addon.mod_assign.noattempt": "אין נסיונות", "addon.mod_assign.nomoresubmissionsaccepted": "מורשה רק למשתתפים שניתנה להם האפשרות להגשה מאוחרת", @@ -204,6 +221,7 @@ "addon.mod_assign_submission_file.pluginname": "הגשות קובץ", "addon.mod_assign_submission_onlinetext.pluginname": "הגשות תוכן מקוון", "addon.mod_book.errorchapter": "שגיאה בעת קריאת הפרק", + "addon.mod_book.modulenameplural": "ספרים", "addon.mod_chat.beep": "ציפצוף", "addon.mod_chat.currentusers": "משתמשים נוכחיים", "addon.mod_chat.enterchat": "הקליקו כאן כדי להיכנס לרב-שיח הנוכחי", @@ -211,6 +229,7 @@ "addon.mod_chat.messagebeepsyou": "{{$a}} ציפצף לך הרגע!", "addon.mod_chat.messageenter": "{{$a}} נכנס/ה לרב-שיח זה", "addon.mod_chat.messageexit": "{{$a}} עזב/ה רב-שיח זה", + "addon.mod_chat.modulenameplural": "רבי־שיח", "addon.mod_chat.mustbeonlinetosendmessages": "עליך להיות מחובר בכדי לשלוח הודעות.", "addon.mod_chat.nomessages": "אין הודעות עדיין", "addon.mod_chat.send": "שליחה", @@ -221,6 +240,7 @@ "addon.mod_choice.errorgetchoice": "שגיאה בטעינת המידע של שאלת הבחירה.", "addon.mod_choice.expired": "מצטערים, פעילות זו נסגרה על {{$a}} והיא איננה זמינה יותר", "addon.mod_choice.full": "(מלא)", + "addon.mod_choice.modulenameplural": "שאלות־סקר", "addon.mod_choice.noresultsviewable": "כרגע לא ניתן לצפות בתוצאות.", "addon.mod_choice.notopenyet": "לצערנו, פעילות זו אינה זמינה עד {{$a}}", "addon.mod_choice.numberofuser": "מספר המשתתפים", @@ -250,8 +270,10 @@ "addon.mod_data.errormustsupplyvalue": "יש להזין ערך בשדה זה.", "addon.mod_data.expired": "מצטערים, פעילות זו נסגרה ב {{$a}} ואיננה זמינה יותר.", "addon.mod_data.fields": "שדות", + "addon.mod_data.foundrecords": "נמצאו פריטים: {{$a.num}}/{{$a.max}} (איפוס מסננים)", "addon.mod_data.latlongboth": "נדרשים נתוני קו־רוחב וגם קו־אורך", "addon.mod_data.menuchoose": "יש לבחור...", + "addon.mod_data.modulenameplural": "בסיסי־נתונים", "addon.mod_data.more": "עוד", "addon.mod_data.nomatch": "לא נמצאו פריטים מתאימים!", "addon.mod_data.norecords": "אין פריטים בבסיס הנתונים", @@ -281,6 +303,7 @@ "addon.mod_feedback.feedbackopen": "אפשרת תשובות מ", "addon.mod_feedback.mapcourses": "שיוך שאלון־מותנה עבור הקורסים", "addon.mod_feedback.mode": "מצב", + "addon.mod_feedback.modulenameplural": "שאלון־מותנה", "addon.mod_feedback.next_page": "העמוד הבא", "addon.mod_feedback.non_anonymous": "שמות המשתמשים ישמרו ויוצגו יחד עם התשובות", "addon.mod_feedback.non_anonymous_entries": "לא ניתן לענות באופן אנונימי (לא מזוהה)", @@ -300,6 +323,7 @@ "addon.mod_feedback.started": "התחיל", "addon.mod_feedback.this_feedback_is_already_submitted": "השלמתם פעילות זו, בעבר.", "addon.mod_folder.emptyfilelist": "אין קבצים להציג.", + "addon.mod_folder.modulenameplural": "תקיות קבצים", "addon.mod_forum.addanewdiscussion": "הוספת נושא חדש לדיון", "addon.mod_forum.addanewquestion": "הוספת שאלה חדשה", "addon.mod_forum.addanewtopic": "הוספת נושא חדש", @@ -322,6 +346,7 @@ "addon.mod_forum.modeflatnewestfirst": "הצגת תגובות בצורה שטוחה, החדשות ביותר ראשונות", "addon.mod_forum.modeflatoldestfirst": "הצגת תגובות בצורה שטוחה, הישנות ביותר ראשונות", "addon.mod_forum.modenested": "הצגת תגובות באופן מקונן", + "addon.mod_forum.modulenameplural": "פורומים", "addon.mod_forum.numdiscussions": "{{numdiscussions}} דיונים", "addon.mod_forum.numreplies": "{{numreplies}} תגובות", "addon.mod_forum.posttoforum": "שליחת הודעה לפורום", @@ -342,7 +367,9 @@ "addon.mod_glossary.fillfields": "יש למלא את שדות המושג וההגדרה.", "addon.mod_glossary.fullmatch": "התאם מילים שלמות בלבד", "addon.mod_glossary.linking": "יצירת קישורים באופן אוטומטי", + "addon.mod_glossary.modulenameplural": "אגרוני מונחים", "addon.mod_imscp.deploymenterror": "שגיאה בחבילת התוכן", + "addon.mod_imscp.modulenameplural": "חבילות תוכן IMS", "addon.mod_imscp.showmoduledescription": "הצגת תיאור", "addon.mod_lesson.answer": "תשובה", "addon.mod_lesson.attempt": "ניסיון: {{$a}}", @@ -383,6 +410,7 @@ "addon.mod_lesson.lowtime": "זמן נמוך", "addon.mod_lesson.maximumnumberofattemptsreached": "הושג המספר המירבי של נסיונות - עובר לעמוד הבא", "addon.mod_lesson.modattemptsnoteacher": "סקירת סטודנטים עובדת רק בשביל סטודנטים.", + "addon.mod_lesson.modulenameplural": "שיעורים", "addon.mod_lesson.noanswer": "לא ניתנה תשובה", "addon.mod_lesson.nolessonattempts": "לא נעשו נסיונות מענה לשיעור זה.", "addon.mod_lesson.notcompleted": "לא גמור", @@ -420,7 +448,9 @@ "addon.mod_lesson.yourcurrentgradeisoutof": "הציון הנוכחי שלך הוא {{$a.grade}} מתוך {{$a.total}}.", "addon.mod_lesson.youshouldview": "על התשובה שלך להיות לפחות: {{$a}}", "addon.mod_lti.errorgetlti": "שגיאה בטעינת מידע המודול.", + "addon.mod_lti.modulenameplural": "כלים/תכנים חיצוניים (LTI)", "addon.mod_page.errorwhileloadingthepage": "שגיאה בזמן טעינת תוכן הדף.", + "addon.mod_page.modulenameplural": "דפים", "addon.mod_quiz.attemptfirst": "ניסיון מענה ראשון", "addon.mod_quiz.attemptlast": "נסיון מענה אחרון", "addon.mod_quiz.attemptnumber": "נסיון מענה", @@ -441,6 +471,7 @@ "addon.mod_quiz.grademethod": "שיטת מתן ציונים", "addon.mod_quiz.gradesofar": "{{$a.method}}: {{$a.mygrade}} / {{$a.quizgrade}}.", "addon.mod_quiz.marks": "נקודות", + "addon.mod_quiz.modulenameplural": "בחנים", "addon.mod_quiz.mustbesubmittedby": "יש צורך להגיש את נסיון מענה זה", "addon.mod_quiz.noquestions": "בשלב זה, יש להוסיף שאלות לבוחן על מנת שאפשר יהיה להציג אותו בפני התלמידים", "addon.mod_quiz.noreviewattempt": "אין הרשאה לצפות בנסיון מענה זה", @@ -473,7 +504,7 @@ "addon.mod_quiz.stateoverdue": "באיחור", "addon.mod_quiz.stateoverduedetails": "מוכרח להיות מוגש עד {{$a}}", "addon.mod_quiz.status": "מצב", - "addon.mod_quiz.submitallandfinish": "הגשה סופית וסיום ניסיון המענה (בוחן)", + "addon.mod_quiz.submitallandfinish": "הגשה סופית, וסיום ניסיון המענה", "addon.mod_quiz.summaryofattempt": "סיכום הנסיון", "addon.mod_quiz.summaryofattempts": "סיכום ניסיונות המענה הקודמים שלך", "addon.mod_quiz.timeleft": "זמן נותר", @@ -481,6 +512,7 @@ "addon.mod_quiz.yourfinalgradeis": "ציונך הסופי בבוחן זה הוא {{$a}}", "addon.mod_resource.errorwhileloadingthecontent": "שגיאה בזמן טעינת התוכן.", "addon.mod_resource.modifieddate": "{{$a}} השתנה", + "addon.mod_resource.modulenameplural": "קבצים", "addon.mod_resource.openthefile": "הצגת הקובץ", "addon.mod_resource.uploadeddate": "{{$a}} הועלה", "addon.mod_scorm.asset": "נכס", @@ -507,6 +539,7 @@ "addon.mod_scorm.incomplete": "התחיל וטרם הסתיים", "addon.mod_scorm.lastattempt": "ניסיון מענה אחרון שהושלם", "addon.mod_scorm.mode": "מצב", + "addon.mod_scorm.modulenameplural": "חבילות לומדה - SCORM", "addon.mod_scorm.newattempt": "התחלת ניסיון מענה חדש", "addon.mod_scorm.noattemptsallowed": "מספר הנסיונות המותר", "addon.mod_scorm.noattemptsmade": "מספר הנסיונות שביצעת", @@ -520,15 +553,18 @@ "addon.mod_survey.errorgetsurvey": "שגיאה בטעינת מידע אודות הסקר.", "addon.mod_survey.ifoundthat": "מצאתי ש-", "addon.mod_survey.ipreferthat": "אני מעדיף ש-", + "addon.mod_survey.modulenameplural": "תבניות סקרים מובנות", "addon.mod_survey.responses": "תגובות", "addon.mod_survey.results": "תוצאות", "addon.mod_survey.surveycompletednograph": "השלמת את הסקר.", "addon.mod_url.accessurl": "גישה לקישור", + "addon.mod_url.modulenameplural": "כתובות אינטרנט", "addon.mod_url.pointingtourl": "משאב קישור זה מפנה אל", "addon.mod_wiki.cannoteditpage": "לא ניתן לערוך עמוד זה", "addon.mod_wiki.createpage": "יצירת דף חדש", "addon.mod_wiki.editingpage": "עריכת עמוד זה {{$a}}", "addon.mod_wiki.map": "דפים מיוחדים", + "addon.mod_wiki.modulenameplural": "ויקי(ם)", "addon.mod_wiki.newpagehdr": "דף חדש", "addon.mod_wiki.newpagetitle": "כותרת דף חדש", "addon.mod_wiki.nocontent": "אין תוכן לדף זה", @@ -544,7 +580,7 @@ "addon.mod_workshop.assessmentform": "טופס הערכה", "addon.mod_workshop.assessmentsettings": "הגדרות ההערכה", "addon.mod_workshop.assessmentweight": "משקל ההערכה", - "addon.mod_workshop.assignedassessments": "הגשות המוקצות להערכה", + "addon.mod_workshop.assignedassessments": "הגשות להן נדרשת הערכה ומתן ציון", "addon.mod_workshop.assignedassessmentsnone": "אין לך הקצאה של הגשות להערכה", "addon.mod_workshop.conclusion": "סיכום", "addon.mod_workshop.createsubmission": "התחלת ההגשה שלך", @@ -553,7 +589,7 @@ "addon.mod_workshop.feedbackauthor": "משוב למחבר", "addon.mod_workshop.feedbackby": "משוב על־ידי: {{$a}}", "addon.mod_workshop.feedbackreviewer": "משוב למעריך", - "addon.mod_workshop.givengrades": "הציונים שהוענקו", + "addon.mod_workshop.givengrades": "הציונים שהתקבלו מהסטודנטים:", "addon.mod_workshop.gradecalculated": "ציון מחושב להגשה", "addon.mod_workshop.gradeinfo": "ציון: {{$a.received}} מ-{{$a.max}}", "addon.mod_workshop.gradeover": "ציון עוקף להגשה", @@ -562,6 +598,7 @@ "addon.mod_workshop.gradinggradecalculated": "ציון מחושב להערכה", "addon.mod_workshop.gradinggradeof": "ציון להערכה ({{$a}})", "addon.mod_workshop.gradinggradeover": "ציון עוקף להערכה", + "addon.mod_workshop.modulenameplural": "הערכות־עמיתים", "addon.mod_workshop.nogradeyet": "טרם ניתן ציון", "addon.mod_workshop.notassessed": "טרם הוערך", "addon.mod_workshop.notoverridden": "לא עודכן", @@ -571,7 +608,7 @@ "addon.mod_workshop.publishsubmission": "פרסום ההגשה", "addon.mod_workshop.publishsubmission_help": "הגשות שפורסמו זמינות לאחרים כאשר פעילות הערכת עמיתים מסתיימת.", "addon.mod_workshop.reassess": "הערכה מחדש", - "addon.mod_workshop.receivedgrades": "ציונים שהתקבלו", + "addon.mod_workshop.receivedgrades": "ציונים שניתנו לסטודנטים:", "addon.mod_workshop.submissionattachment": "נספח", "addon.mod_workshop.submissioncontent": "תוכן ההגשה", "addon.mod_workshop.submissiondeleteconfirm": "האם בטוח כי ברצונך למחוק את ההגשה הבאה?", @@ -579,10 +616,10 @@ "addon.mod_workshop.submissiongradeof": "ציון ההגשה (מתוך {{$a}})", "addon.mod_workshop.submissionsreport": "דוח הגשות", "addon.mod_workshop.submissiontitle": "כותרת", - "addon.mod_workshop.switchphase10": "מעבר לשלב ההכנה", - "addon.mod_workshop.switchphase20": "מעבר לשלב ההגשה", - "addon.mod_workshop.switchphase30": "מעבר לשלם ההערכות", - "addon.mod_workshop.switchphase40": "מעבר לשלב חישוב בציונים", + "addon.mod_workshop.switchphase10": "הקליקו, למעבר לשלב ההכנה", + "addon.mod_workshop.switchphase20": "הקליקו, למעבר לשלב ההגשה", + "addon.mod_workshop.switchphase30": "הקליקו, למעבר לשלב הערכות", + "addon.mod_workshop.switchphase40": "הקליקו, למעבר לשלב חישוב הציונים", "addon.mod_workshop.switchphase50": "סגירת הערכת העמיתים", "addon.mod_workshop.userplan": "טבלת תכנון הערכת עמיתים", "addon.mod_workshop.userplancurrentphase": "שלב נוכחי", @@ -887,6 +924,8 @@ "core.accounts": "חשבונות", "core.add": "הוספה", "core.agelocationverification": "אימות גיל ומיקום", + "core.ago": "לפני {{$a}}", + "core.all": "הכל", "core.allparticipants": "כל המשתתפים", "core.android": "אנדרואיד", "core.answer": "תשובה", @@ -929,23 +968,15 @@ "core.courses.allowguests": "הקורס הזה מרשה למשתמשים אורחים להכנס", "core.courses.availablecourses": "קורסים זמינים", "core.courses.categories": "קטגוריות קורסים", - "core.courses.courseoverview": "סקירה כללית של הקורס", "core.courses.courses": "קורסים", "core.courses.errorloadcourses": "התרחשה שגיאה בזמן טעינת הקורסים.", "core.courses.frontpage": "העמוד הראשי", - "core.courses.future": "עתידי", - "core.courses.inprogress": "בלמידה", - "core.courses.morecourses": "קורסים נוספים", "core.courses.mycourses": "הקורסים שלי", + "core.courses.mymoodle": "עדכונים בקורסים שלי", "core.courses.nocourses": "אין מידע שניתן להציג על הקורס", - "core.courses.nocoursesfuture": "טרם נרשמתם לקורס עתידי כלשהו", - "core.courses.nocoursesinprogress": "טרם נרשמתם לקורס המתרחש כעת", - "core.courses.nocoursesoverview": "אף קורס", - "core.courses.nocoursespast": "אינכם רשומים לקורס אשר הסתיימו", "core.courses.nocoursesyet": "אין קורסים בקטגוריה זו", "core.courses.nosearchresults": "אין תוצאות", "core.courses.notenroled": "אינך רשום לקורס זה", - "core.courses.past": "ארכיון", "core.courses.paymentrequired": "קורס זה מצריך תשלום לכניסה", "core.courses.paypalaccepted": "התקבלו תשלומי Paypal", "core.courses.search": "חיפוש", @@ -1036,6 +1067,7 @@ "core.login.createaccount": "יצירת חשבון חדש", "core.login.createuserandpass": "הזנת שם־משתמש וסיסמה", "core.login.credentialsdescription": "יש להזין את שם המשתמש והסיסמה שלך כדי להתחבר", + "core.login.emailconfirmsent": "

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

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

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

", "core.login.enterthewordsabove": "יש להזין את מילים (תווים או מספרים) המוצגים מעלה (בתמונה)", "core.login.errordeletesite": "התרחשה שגיאה בזמן מחיקת אתר זה. אנא נסה שוב.", "core.login.errorupdatesite": "התרחשה שגיאה בזמן עדכון אסימון האתר (token).", @@ -1046,6 +1078,7 @@ "core.login.helpmelogin": "

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

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

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

", "core.login.instructions": "הוראות", "core.login.invalidaccount": "אנא בדוק את פרטי ההתחברות שלך או פנה למנהל האתר לבדיקת תצורת האתר.", + "core.login.invaliddate": "תאריך לא תקף", "core.login.invalidemail": "כתובת דואר אלקטרוני לא תקפה", "core.login.invalidmoodleversion": "גרסת מוודל לא תקינה. נדרשת גרסה 2.4 ומעלה.", "core.login.invalidsite": "כתובת האתר אינה תקינה.", @@ -1058,6 +1091,7 @@ "core.login.missingfirstname": "חסר שם פרטי", "core.login.missinglastname": "חסר שם משפחה", "core.login.mobileservicesnotenabled": "Mobile Services לא מאופשרים באתר זה. אנא פנה למנהל האתר באם רצונך לשנות זאת.", + "core.login.mustconfirm": "עליך לאשר את ההתחברות שלך", "core.login.newaccount": "חשבון חדש", "core.login.newsitedescription": "יש להזין את כתובת אתר המוודל שלך. יש לשים לב כי יתכן והאתר אינו מוגדר לעבוד עם יישומון זו.", "core.login.notloggedin": "עליך להיות מחובר/ת", @@ -1091,8 +1125,6 @@ "core.mainmenu.appsettings": "הגדרות יישומון", "core.mainmenu.help": "עזרה", "core.mainmenu.logout": "התנתקות", - "core.mainmenu.mycourses": "הקורסים שלי", - "core.mainmenu.togglemenu": "החלפת מצב תפריט", "core.mainmenu.website": "אתר אינטרנט", "core.maxsizeandattachments": "נפח קבצים מירבי: {{$a.size}}, מספר קבצים מצורפים מירבי: {{$a.attachments}}", "core.min": "דקה", @@ -1169,8 +1201,10 @@ "core.question.requiresgrading": "נדרש מתן ציון", "core.quotausage": "השתמשת כרגע ב {{$a.used}} מהמגבלה שלך בסך {{$a.total}}.", "core.refresh": "רענון", + "core.remove": "הסרה", "core.required": "דרוש", "core.requireduserdatamissing": "למשתמש זה חסרים שדות נדרשים בפרופיל המשתמש. יש להשלים מידע זה באתר המוודל שלך ולנסות שוב.
{{$a}}", + "core.resources": "משאבים", "core.restore": "שחזור", "core.save": "שמירה", "core.search": "חיפוש", @@ -1223,6 +1257,19 @@ "core.sizekb": "KB", "core.sizemb": "MB", "core.sortby": "מיון לפי", + "core.strftimedate": "%d/%m/%Y", + "core.strftimedatefullshort": "%d/%m/%y", + "core.strftimedateshort": "%d %B", + "core.strftimedatetime": "%d/%m/%Y, %H:%M", + "core.strftimedatetimeshort": "%d/%m/%Y %H:%M", + "core.strftimedaydate": "%d/%m/%Y", + "core.strftimedaydatetime": "%d/%m/%Y, %H:%M", + "core.strftimedayshort": "%d/%m/%Y", + "core.strftimedaytime": "%a, %H:%M", + "core.strftimemonthyear": "%B %Y", + "core.strftimerecent": "%d %b, %H:%M", + "core.strftimerecentfull": "%d/%m/%Y, %H:%M", + "core.strftimetime": "%H:%M", "core.submit": "שמירה", "core.success": "הצלחה", "core.tablet": "טאבלט", @@ -1242,13 +1289,13 @@ "core.user.description": "תיאור", "core.user.details": "פרטים", "core.user.detailsnotavailable": "פרטי משתמש זה אינם זמינים לך.", - "core.user.editingteacher": "מרצה", + "core.user.editingteacher": "מורה", "core.user.email": "כתובת דואר אלקטרוני", "core.user.emailagain": "דואר אלקטרוני (שוב)", "core.user.firstname": "שם פרטי", "core.user.interests": "תחומי עניין", "core.user.lastname": "שם משפחה", - "core.user.manager": "מנהל", + "core.user.manager": "מנהל/ת", "core.user.newpicture": "תמונה חדשה", "core.user.noparticipants": "לא נמצאו משתתפים עבור קורס זה", "core.user.participants": "משתתפים", @@ -1256,7 +1303,7 @@ "core.user.phone2": "טלפון נייד", "core.user.roles": "תפקידים", "core.user.student": "סטודנט", - "core.user.teacher": "עוזר/ת הוראה", + "core.user.teacher": "מורה לא עורך", "core.user.webpage": "דף בית באינטרנט", "core.userdeleted": "חשבון משתמש זה נמחק", "core.userdetails": "מאפייניי המשתמש", diff --git a/src/assets/lang/hr.json b/src/assets/lang/hr.json index 97a640531..cfb4a3eff 100644 --- a/src/assets/lang/hr.json +++ b/src/assets/lang/hr.json @@ -8,8 +8,18 @@ "addon.badges.issuancedetails": "Istek značke", "addon.badges.issuerdetails": "Detalji o izdavaču", "addon.badges.issuername": "Ime izdavača", + "addon.badges.issuerurl": "URL izdavača", "addon.badges.nobadges": "Nema dostupnih značaka.", "addon.badges.recipientdetails": "Podaci o dobitniku", + "addon.badges.warnexpired": "(Ova značka je istekla!)", + "addon.block_activitymodules.pluginname": "Aktivnosti", + "addon.block_myoverview.future": "Buduće", + "addon.block_myoverview.inprogress": "U tijeku", + "addon.block_myoverview.morecourses": "Prikaz ostalih e-kolegija", + "addon.block_myoverview.nocourses": "Nema e-kolegija", + "addon.block_myoverview.past": "Prošlo", + "addon.block_myoverview.pluginname": "Pregled e-kolegija", + "addon.block_sitemainmenu.pluginname": "Glavni izbornik", "addon.calendar.calendar": "Kalendar", "addon.calendar.calendarevents": "Događaji u kalendaru", "addon.calendar.eventendtime": "Završava", @@ -27,20 +37,28 @@ "addon.competency.planstatusdraft": "Nacrt", "addon.coursecompletion.complete": "Dovršeno", "addon.coursecompletion.completecourse": "Dovrši e-kolegij", - "addon.coursecompletion.completiondate": "Datum dovršetka", + "addon.coursecompletion.completed": "Završeno", + "addon.coursecompletion.completiondate": "Datum završetka", "addon.coursecompletion.completionmenuitem": "Dovršenost", + "addon.coursecompletion.coursecompletion": "Dovršenost e-kolegija", "addon.coursecompletion.criteria": "Kriterij", + "addon.coursecompletion.criteriagroup": "Grupa kriterija", + "addon.coursecompletion.criteriarequiredall": "Potrebno je zadovoljenje svih doljnjih kriterija", + "addon.coursecompletion.criteriarequiredany": "Potrebno je zadovoljenje bilo kojeg doljnjeg kriterija", "addon.coursecompletion.inprogress": "U tijeku", - "addon.coursecompletion.required": "Obvezno", - "addon.coursecompletion.requiredcriteria": "Obvezni kriterij", - "addon.coursecompletion.requirement": "Uvjet", - "addon.coursecompletion.status": "Stanje", + "addon.coursecompletion.manualselfcompletion": "Ručni dovršetak", + "addon.coursecompletion.notyetstarted": "Nije još započelo", + "addon.coursecompletion.pending": "Na čekanju", + "addon.coursecompletion.required": "Obvezatno", + "addon.coursecompletion.requiredcriteria": "Obvezatni kriterij", + "addon.coursecompletion.requirement": "Zahtjev", + "addon.coursecompletion.status": "Status", + "addon.coursecompletion.viewcoursereport": "Prikaz izvješća e-kolegija", "addon.files.emptyfilelist": "Nema datoteka za prikaz.", "addon.files.files": "Datoteke", "addon.files.privatefiles": "Osobne datoteke korisnika", "addon.files.sitefiles": "Site files", "addon.messages.addcontact": "Dodaj kontakt", - "addon.messages.blockcontact": "Blokiraj kontakt", "addon.messages.blocknoncontacts": "Blokiraj nepoznate korisnike", "addon.messages.contacts": "Kontakti", "addon.messages.message": "Poruka", @@ -48,12 +66,12 @@ "addon.messages.messages": "Poruke", "addon.messages.newmessage": "Nova poruka", "addon.messages.newmessages": "Nove poruke", - "addon.messages.nomessages": "Nema poruka na čekanju", + "addon.messages.nomessagesfound": "Nema poruka za prikazati", "addon.messages.removecontact": "Ukloni kontakt", + "addon.messages.searchcombined": "Pretraži korisnike i poruke", "addon.messages.type_offline": "Offline", "addon.messages.type_online": "Online", "addon.messages.type_strangers": "Ostali", - "addon.messages.unblockcontact": "Odblokiraj osobu (kontakt)", "addon.mod_assign.addattempt": "Predajte drugu zadaću", "addon.mod_assign.addnewattempt": "Predajte novu zadaću", "addon.mod_assign.addnewattemptfromprevious": "Dopusti novi pokušaj zasnovan na prethodno predanoj zadaći", @@ -97,6 +115,7 @@ "addon.mod_assign.markingworkflowstatereadyforrelease": "Spremno za objavu", "addon.mod_assign.markingworkflowstatereadyforreview": "Ocjenjivanje dovršeno", "addon.mod_assign.markingworkflowstatereleased": "Objavljeno", + "addon.mod_assign.modulenameplural": "Zadaće", "addon.mod_assign.noattempt": "Zadaća nije predana", "addon.mod_assign.nomoresubmissionsaccepted": "Daljnja predaja zadaća nije dopuštena", "addon.mod_assign.noonlinesubmissions": "Za ovu zadaću ne morate ništa predavati online", @@ -141,6 +160,7 @@ "addon.mod_assign_submission_file.pluginname": "Postavljanje datoteke", "addon.mod_assign_submission_onlinetext.pluginname": "Predaja online tekstova", "addon.mod_book.errorchapter": "Pogreška pri učitavanju poglavlja u knjizi", + "addon.mod_book.modulenameplural": "Knjige", "addon.mod_chat.beep": "Bocni", "addon.mod_chat.currentusers": "Trenutačno prijavljeni korisnici", "addon.mod_chat.enterchat": "Kliknite ovdje za pristup chatu", @@ -148,6 +168,7 @@ "addon.mod_chat.messagebeepsyou": "{{$a}} vas je upravo bocnuo!", "addon.mod_chat.messageenter": "Korisnik {{$a}} je došao na chat", "addon.mod_chat.messageexit": "Korisnik {{$a}} je otišao s chata", + "addon.mod_chat.modulenameplural": "Razgovori", "addon.mod_chat.nomessages": "Nema poruka (još)", "addon.mod_chat.send": "Pošalji", "addon.mod_chat.sessionstart": "Sljedeća chat sesija će biti pokrenuta u: {{$a}}", @@ -155,6 +176,7 @@ "addon.mod_choice.choiceoptions": "Postavke odabira", "addon.mod_choice.expired": "Nažalost, ova aktivnost je zatvorena od {{$a}} i nije više dostupna", "addon.mod_choice.full": "(Popunjeno)", + "addon.mod_choice.modulenameplural": "Odabiri", "addon.mod_choice.noresultsviewable": "Rezultate trenutačno nije moguće prikazati.", "addon.mod_choice.notopenyet": "Nažalost, ova aktivnost nije dostupna do {{$a}}", "addon.mod_choice.numberofuser": "Broj sudionika", @@ -180,7 +202,9 @@ "addon.mod_data.entrieslefttoaddtoview": "Morate dodati barem {{$a.entriesleft}} zapisa kako bi mogli pregledavati zapise drugih sudionika.", "addon.mod_data.expired": "Nažalost, ova aktivnost je zatvorena {{$a}} i nije više dostupna", "addon.mod_data.fields": "Polja", + "addon.mod_data.foundrecords": "Pronađeni zapisi: {{$a.num}}/{{$a.max}}(Vrati stare postavke filtra)", "addon.mod_data.menuchoose": "Odaberite...", + "addon.mod_data.modulenameplural": "Baze podataka", "addon.mod_data.more": "Više", "addon.mod_data.nomatch": "Nema odgovarajućih zapisa!", "addon.mod_data.norecords": "U bazi podataka nema zapisa", @@ -209,6 +233,7 @@ "addon.mod_feedback.feedbackopen": "Dostupno od", "addon.mod_feedback.mapcourses": "Pridruži anketu e-kolegijima", "addon.mod_feedback.mode": "Vrsta", + "addon.mod_feedback.modulenameplural": "Ankete", "addon.mod_feedback.next_page": "Sljedeća stranica", "addon.mod_feedback.non_anonymous": "Ime korisnika će se zapisati i prikazati uz odgovore", "addon.mod_feedback.non_anonymous_entries": "neanonimni unosi", @@ -228,6 +253,7 @@ "addon.mod_feedback.started": "započeto", "addon.mod_feedback.this_feedback_is_already_submitted": "Već ste obavili ovu aktivnost.", "addon.mod_folder.emptyfilelist": "Nema datoteka za prikaz.", + "addon.mod_folder.modulenameplural": "Mape", "addon.mod_forum.addanewdiscussion": "Dodaj novu raspravu", "addon.mod_forum.addanewquestion": "Dodajte novo pitanje", "addon.mod_forum.addanewtopic": "Dodajte novu temu", @@ -246,6 +272,7 @@ "addon.mod_forum.modeflatnewestfirst": "Prikaz odgovora, počevši s najnovijim", "addon.mod_forum.modeflatoldestfirst": "Prikaz odgovora, počevši s najstarijim", "addon.mod_forum.modenested": "Prikaz odgovora u hijerarhijskoj strukturi", + "addon.mod_forum.modulenameplural": "Forumi", "addon.mod_forum.posttoforum": "Pošaljite poruku na forum", "addon.mod_forum.re": "Re:", "addon.mod_forum.reply": "Odgovori (reply)", @@ -270,7 +297,9 @@ "addon.mod_glossary.fillfields": "Pojam i definicija su obvezatna polja.", "addon.mod_glossary.fullmatch": "Poveži samo cijele riječi", "addon.mod_glossary.linking": "Automatsko povezivanje", + "addon.mod_glossary.modulenameplural": "Rječnici", "addon.mod_imscp.deploymenterror": "Pogreška pri pokretanju paketa!", + "addon.mod_imscp.modulenameplural": "IMS paketi", "addon.mod_imscp.showmoduledescription": "Prikaži opis", "addon.mod_lesson.answer": "Odgovor", "addon.mod_lesson.attempt": "Pokušaj: {{$a}}", @@ -310,6 +339,7 @@ "addon.mod_lesson.lowtime": "Najslabije vrijeme", "addon.mod_lesson.maximumnumberofattemptsreached": "Dosegnut je maksimalni broj pokušaja - prelazi se na sljedeću stranicu", "addon.mod_lesson.modattemptsnoteacher": "Pregled studentima vrijedi samo za studente.", + "addon.mod_lesson.modulenameplural": "Lekcije", "addon.mod_lesson.noanswer": "Nema predanih odgovora. Molimo vratite se natrag i odgovorite na pitanje.", "addon.mod_lesson.nolessonattempts": "U lekciji trenutačno nema pokušaja.", "addon.mod_lesson.notcompleted": "Nije završeno", @@ -349,6 +379,8 @@ "addon.mod_lesson.yourcurrentgradeisoutof": "Vaša trenutačna ocjena je {{$a.grade}} od mogućih {{$a.total}}", "addon.mod_lesson.youshouldview": "Trebali bi odgovoriti na barem: {{$a}}", "addon.mod_lti.launchactivity": "Pokreni aktivnost", + "addon.mod_lti.modulenameplural": "Vanjski alati", + "addon.mod_page.modulenameplural": "Stranice", "addon.mod_quiz.attemptfirst": "Prvi pokušaj", "addon.mod_quiz.attemptlast": "Posljednji pokušaj", "addon.mod_quiz.attemptnumber": "Pokušaj", @@ -369,6 +401,7 @@ "addon.mod_quiz.grademethod": "Način ocjenjivanja", "addon.mod_quiz.gradesofar": "{{$a.method}}: {{$a.mygrade}} / {{$a.quizgrade}}.", "addon.mod_quiz.marks": "Bodovi", + "addon.mod_quiz.modulenameplural": "Testovi", "addon.mod_quiz.mustbesubmittedby": "Ovaj je test potrebno predati do {{$a}}.", "addon.mod_quiz.noquestions": "U ovom testu trenutačno nema pitanja", "addon.mod_quiz.noreviewattempt": "Nemate mogućnost pregledati ovaj pokušaj.", @@ -407,6 +440,7 @@ "addon.mod_quiz.timeleft": "Preostalo vrijeme", "addon.mod_quiz.timetaken": "Proteklo vrijeme", "addon.mod_quiz.yourfinalgradeis": "Vaša završna ocjena na ovom testu je {{$a}}", + "addon.mod_resource.modulenameplural": "Datoteke", "addon.mod_resource.openthefile": "Otvori datoteku", "addon.mod_scorm.asset": "Statički resurs", "addon.mod_scorm.assetlaunched": "Pregled statičkih resursa", @@ -432,6 +466,7 @@ "addon.mod_scorm.incomplete": "Nedovršeno", "addon.mod_scorm.lastattempt": "Posljednji dovršeni pokušaj", "addon.mod_scorm.mode": "Način", + "addon.mod_scorm.modulenameplural": "SCORM paketi", "addon.mod_scorm.newattempt": "Započni novi pokušaj", "addon.mod_scorm.noattemptsallowed": "Broj dopuštenih pokušaja", "addon.mod_scorm.noattemptsmade": "Broj pokušaja koji ste vi učinili", @@ -443,12 +478,15 @@ "addon.mod_scorm.suspended": "Zaključano", "addon.mod_survey.ifoundthat": "Otkrio sam da", "addon.mod_survey.ipreferthat": "I prefer that", + "addon.mod_survey.modulenameplural": "Upitnici", "addon.mod_survey.responses": "Odgovori", "addon.mod_survey.results": "Rezultati", + "addon.mod_url.modulenameplural": "Poveznice", "addon.mod_wiki.cannoteditpage": "Ne možete urediti ovu stranicu.", "addon.mod_wiki.createpage": "Stvori stranicu", "addon.mod_wiki.editingpage": "Uređivanje stranice '{{$a}}'", "addon.mod_wiki.map": "Sadržaj", + "addon.mod_wiki.modulenameplural": "Wikiji", "addon.mod_wiki.newpagehdr": "Nova stranica", "addon.mod_wiki.newpagetitle": "Naslov nove stranice", "addon.mod_wiki.nocontent": "Na ovoj stranici nema sadržaja", @@ -482,6 +520,7 @@ "addon.mod_workshop.gradinggradecalculated": "Izračunata ocjena za obavljene procjene", "addon.mod_workshop.gradinggradeof": "Ocjena za obavljenu procjenu (od {{$a}})", "addon.mod_workshop.gradinggradeover": "Poništi prethodnu ocjenu za obavljene procjene", + "addon.mod_workshop.modulenameplural": "Radionice", "addon.mod_workshop.nogradeyet": "Još nema ocjene", "addon.mod_workshop.notassessed": "Rad još nije procijenjen ", "addon.mod_workshop.notoverridden": "Nije poništeno", @@ -792,6 +831,9 @@ "assets.mimetypes.text/rtf": "RTF dokument", "core.accounts": "Korisnički računi", "core.add": "Dodajte", + "core.agelocationverification": "Provjera dobi i lokacije", + "core.ago": "prije {{$a}}", + "core.all": "Sve", "core.allparticipants": "Svi sudionici", "core.android": "Android", "core.answer": "Odgovor", @@ -831,26 +873,18 @@ "core.courses.allowguests": "Ovaj e-kolegij dopušta pristup gostima (anonimnim korisnicima)", "core.courses.availablecourses": "Dostupni e-kolegiji", "core.courses.categories": "Popis e-kolegija", - "core.courses.courseoverview": "Pregled e-kolegija", "core.courses.courses": "E-kolegiji", "core.courses.enrolme": "Upiši me", "core.courses.filtermycourses": "Filtriraj moje e-kolegije", "core.courses.frontpage": "Naslovnica", - "core.courses.future": "Buduće", - "core.courses.inprogress": "U tijeku", - "core.courses.morecourses": "Prikaz ostalih e-kolegija", "core.courses.mycourses": "Moji e-kolegiji", + "core.courses.mymoodle": "Moja naslovnica", "core.courses.nocourses": "Nema podataka o e-kolegijima za prikaz.", - "core.courses.nocoursesfuture": "Nema budućih e-kolegija", - "core.courses.nocoursesinprogress": "Nema e-kolegija u tijeku", - "core.courses.nocoursesoverview": "Nema e-kolegija", - "core.courses.nocoursespast": "Nema prošlih e-kolegija", "core.courses.nocoursesyet": "U ovoj kategoriji nema e-kolegija", "core.courses.nosearchresults": "Nema rezultata", "core.courses.notenroled": "Niste upisani kao student na ovom e-kolegiju", "core.courses.notenrollable": "Ne možete se samostalno upisati na ovaj e-kolegij.", "core.courses.password": "Lozinka e-kolegija", - "core.courses.past": "Prošlo", "core.courses.paymentrequired": "Ovaj e-kolegij zahtjeva plaćanje za pristup.", "core.courses.paypalaccepted": "Plaćanje PayPalom omogućeno", "core.courses.search": "Pretraži", @@ -925,6 +959,8 @@ "core.login.connecttomoodle": "Prijavi se na Moodle", "core.login.createaccount": "Stvori moj novi korisnički račun", "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.forgotten": "Zaboravili ste svoje korisničko ime ili lozinku?", @@ -941,6 +977,7 @@ "core.login.missingemail": "Nedostaje adresa e-pošte", "core.login.missingfirstname": "Nedostaje ime", "core.login.missinglastname": "Nedostaje prezime", + "core.login.mustconfirm": "Morate potvrditi vašu prijavu sustavu (login)", "core.login.newaccount": "Novi korisnički račun", "core.login.notloggedin": "Morate biti prijavljeni", "core.login.password": "Lozinka", @@ -956,10 +993,13 @@ "core.login.reconnect": "Spoji se ponovno", "core.login.security_question": "Sigurnosno pitanje", "core.login.selectacountry": "Odaberite državu", + "core.login.selectsite": "Odaberi sjedište:", "core.login.signupplugindisabled": "{{$a}} nije omogućen(o).", "core.login.siteaddress": "Adresa poslužitelja", + "core.login.siteinmaintenance": "Poslužitelj je trenutno u načinu održavanja.", "core.login.siteurl": "URL poslužitelja", "core.login.startsignup": "Stvori novi korisnički račun!", + "core.login.stillcantconnect": "Još se ne možeš spojiti?", "core.login.supplyinfo": "Molimo unesite osobne informacije", "core.login.username": "Korisničko ime", "core.login.usernameoremail": "Unesite korisničko ime ILI adresu e-pošte", @@ -969,7 +1009,6 @@ "core.mainmenu.changesite": "Promijeni poslužitelj", "core.mainmenu.help": "Pomoć", "core.mainmenu.logout": "Odjava", - "core.mainmenu.mycourses": "Moji e-kolegiji", "core.mainmenu.website": "Web", "core.maxsizeandattachments": "Najveća dopuštena veličina za nove datoteke: {{$a.size}}, najveći broj privitaka: {{$a.attachments}}", "core.min": "min", @@ -1042,7 +1081,9 @@ "core.question.questionno": "Pitanje {{$a}}", "core.question.requiresgrading": "Zahtijeva ocjenjivanje", "core.refresh": "Osvježavanje", + "core.remove": "Ukloni", "core.required": "Obvezatno", + "core.resources": "Resursi", "core.restore": "Vraćanje iz kopije", "core.search": "Pretraži", "core.searching": "Pretraga", @@ -1052,6 +1093,13 @@ "core.seemoredetail": "Kliknite ovdje za više detalja", "core.sending": "Slanje", "core.serverconnection": "Pogreška pri spajanju na poslužitelj", + "core.settings.about": "O", + "core.settings.appready": "Aplikacija spremna", + "core.settings.cordovadevicemodel": "Model Cordova uređaja", + "core.settings.cordovadeviceosversion": "OS verzija Cordova uređaja", + "core.settings.cordovadeviceplatform": "Platforma Cordova uređaja", + "core.settings.cordovadeviceuuid": "UUID Cordova uređaja", + "core.settings.cordovaversion": "Verzija Cordove", "core.settings.currentlanguage": "Trenutačno važeći jezik", "core.settings.debugdisplay": "Prikaži debug poruke", "core.settings.deviceinfo": "Informacija o uređaju", @@ -1071,6 +1119,7 @@ "core.settings.synchronizenow": "Sinkronizirajte sada", "core.settings.syncsettings": "Postavke sinkronizacije", "core.settings.total": "Ukupno", + "core.settings.versioncode": "Verzija kôda", "core.settings.versionname": "Naziv inačice", "core.settings.wificonnection": "Wifi povezanost", "core.sharedfiles.rename": "Preimenuj", @@ -1088,16 +1137,32 @@ "core.sizemb": "MB", "core.sizetb": "TB", "core.sortby": "Sortiraj prema", + "core.strftimedate": "%d %B %Y", + "core.strftimedatefullshort": "%d/%m/%y", + "core.strftimedateshort": "%d %B", + "core.strftimedatetime": "%d %B %Y, %H:%M", + "core.strftimedatetimeshort": "%d/%m/%y, %H:%M", + "core.strftimedaydate": "%A, %d %B %Y", + "core.strftimedaydatetime": "%A, %d %B %Y, %H:%M", + "core.strftimedayshort": "%A, %d %B", + "core.strftimedaytime": "%a, %H:%M", + "core.strftimemonthyear": "%B %Y", + "core.strftimerecent": "%d %b, %H:%M", + "core.strftimerecentfull": "%a, %d %b %Y, %H:%M", + "core.strftimetime": "%H:%M", "core.submit": "Predaj", "core.success": "Uspješno", "core.tablet": "Tablet", "core.teachers": "Nastavnici", + "core.thereisdatatosync": "Postoje offline {{$a}} koje treba sinkronizirati.", "core.thisdirection": "ltr", "core.time": "Vrijeme", "core.timesup": "Vrijeme je isteklo!", "core.today": "Danas", + "core.tryagain": "Pokušaj opet", "core.twoparagraphs": "{{p1}}

{{p2}}", "core.uhoh": "Ups!", + "core.unexpectederror": "Nepredviđena greška. Zatvorite aplikaciju, ponovno je pokrenite i pokušajte opet.", "core.unknown": "Nepoznato", "core.unlimited": "Neograničeno", "core.upgraderunning": "Sustav je u postupku nadogradnje, molimo pokušajte kasnije.", @@ -1107,9 +1172,11 @@ "core.user.country": "Država", "core.user.description": "Opis", "core.user.details": "Detalji", + "core.user.detailsnotavailable": "Detalji o navedenom korisniku nisu vam dostupni.", "core.user.editingteacher": "Nastavnik", "core.user.email": "Adresa e-pošte", "core.user.emailagain": "E-pošta (ponovno)", + "core.user.errorloaduser": "Greška pri učitavanju korisnika.", "core.user.firstname": "Ime", "core.user.interests": "Interesi", "core.user.lastname": "Prezime", @@ -1122,14 +1189,20 @@ "core.user.roles": "Uloge", "core.user.sendemail": "E-pošta", "core.user.student": "Student", + "core.user.teacher": "Nastavnik bez ovlasti za uređivanje", "core.user.webpage": "Web stranica", "core.userdeleted": "Ovaj korisnički račun je izbrisan", "core.userdetails": "Detalji o korisniku", "core.users": "Korisnici", "core.view": "Prikaz", + "core.viewcode": "Prikaži kôd", + "core.vieweditor": "Prikaži uređivač", "core.viewprofile": "Prikaži profil", + "core.warningofflinedatadeleted": "Offline podaci iz {{component}} '{{name}}' su izbrisani. {{error}}", "core.whoops": "Ups!", + "core.whyisthishappening": "Zašto se navedeno događa?", "core.windowsphone": "Windows Phone", + "core.wsfunctionnotavailable": "Funkcija web servisa nije dostupna.", "core.year": "godina", "core.years": "godine", "core.yes": "Da" diff --git a/src/assets/lang/hu.json b/src/assets/lang/hu.json index 0d3e83e18..50d22e23b 100644 --- a/src/assets/lang/hu.json +++ b/src/assets/lang/hu.json @@ -1,15 +1,61 @@ { + "addon.badges.alignment": "Készség", "addon.badges.badgedetails": "Részletek", "addon.badges.badges": "Kitűzők", + "addon.badges.bendorsement": "Jóváhagyás", + "addon.badges.claimcomment": "Megjegyzés a támogató megerősítéshez", + "addon.badges.claimid": "Igény webcíme", "addon.badges.contact": "Kapcsolat", "addon.badges.dateawarded": "Kiadás dátuma", "addon.badges.expired": "Lejárt", "addon.badges.expirydate": "Lejárat időpontja", + "addon.badges.imageauthoremail": "Kép szerzőjének e-mailje", + "addon.badges.imageauthorname": "A kép szerzőjének a neve", + "addon.badges.imageauthorurl": "A kép szerzőjének webcíme", + "addon.badges.imagecaption": "Képfelirat", "addon.badges.issuancedetails": "A kitűző lejárata", "addon.badges.issuerdetails": "Az adományozó adatai", + "addon.badges.issueremail": "E-mail", "addon.badges.issuername": "Az adományozó neve", + "addon.badges.issuerurl": "Az adományozó webcíme", + "addon.badges.language": "Nyelv", + "addon.badges.noalignment": "A kitűzőhöz nincs megadva készség.", "addon.badges.nobadges": "Nincs elérhető kitűző.", + "addon.badges.norelated": "A kitűzőhöz nincsenek kapcsolódó kitűzők.", "addon.badges.recipientdetails": "A megjutalmazott adatai", + "addon.badges.relatedbages": "Kapcsolódó kitűzők", + "addon.badges.version": "Változat", + "addon.badges.warnexpired": "(A kitűző érvényessége lejárt.)", + "addon.block_activitymodules.pluginname": "Tevékenységek", + "addon.block_myoverview.all": "Összes", + "addon.block_myoverview.favourites": "Bejelölve", + "addon.block_myoverview.future": "Jövőbeni", + "addon.block_myoverview.hiddencourses": "Rejtett", + "addon.block_myoverview.inprogress": "Folyamatban lévő", + "addon.block_myoverview.lastaccessed": "Utolsó hozzáférés", + "addon.block_myoverview.morecourses": "További kurzusok", + "addon.block_myoverview.nocourses": "Nincs kurzus", + "addon.block_myoverview.past": "Korábbi", + "addon.block_myoverview.pluginname": "Kurzusáttekintés", + "addon.block_myoverview.title": "Cím", + "addon.block_recentlyaccessedcourses.nocourses": "Nincsenek friss kurzusok", + "addon.block_recentlyaccessedcourses.pluginname": "Mostanában felkeresett kurzusok", + "addon.block_recentlyaccesseditems.noitems": "Nincsenek friss tételek.", + "addon.block_recentlyaccesseditems.pluginname": "Mostanában felkeresett tételek", + "addon.block_sitemainmenu.pluginname": "Főmenü", + "addon.block_starredcourses.nocourses": "Nincsenek", + "addon.block_starredcourses.pluginname": "Megjelölt kurzusok", + "addon.block_timeline.duedate": "Esedékesség", + "addon.block_timeline.next30days": "A következő 30 nap", + "addon.block_timeline.next3months": "A következő 3 hónap", + "addon.block_timeline.next6months": "A következő 6 hónap", + "addon.block_timeline.next7days": "A következő 7 nap", + "addon.block_timeline.nocoursesinprogress": "Nincs aktuális kurzus", + "addon.block_timeline.noevents": "Nincs esedékes tevékenység", + "addon.block_timeline.overdue": "Lejárt", + "addon.block_timeline.pluginname": "Idősor", + "addon.block_timeline.sortbycourses": "Rendezés kurzusonként", + "addon.block_timeline.sortbydates": "Rendezés dátumonként", "addon.calendar.calendar": "Naptár", "addon.calendar.eventendtime": "Befejezési időpont", "addon.calendar.eventstarttime": "Kezdési időpont", @@ -68,21 +114,80 @@ "addon.competency.xcompetenciesproficientoutofy": "{{$a.x}} / {{$a.y}} készség eredményes", "addon.competency.xcompetenciesproficientoutofyincourse": "Ön a kurzusban {{$a.y}} közül {{$a.x}} készség tekintetében eredményesnek bizonyul.", "addon.coursecompletion.completecourse": "Kurzus teljesítése", + "addon.coursecompletion.completed": "Teljesítve", + "addon.coursecompletion.completiondate": "Teljesítés időpontja", "addon.coursecompletion.completionmenuitem": "Teljesítés", + "addon.coursecompletion.coursecompletion": "Kurzus teljesítése", + "addon.coursecompletion.criteria": "Követelmények", + "addon.coursecompletion.criteriagroup": "Követelménycsoport", + "addon.coursecompletion.criteriarequiredall": "Az összes alábbi követelmény teljesítendő", + "addon.coursecompletion.criteriarequiredany": "Bármely alábbi követelmény teljesítendő", + "addon.coursecompletion.inprogress": "Folyamatban", + "addon.coursecompletion.manualselfcompletion": "Saját teljesítés kézzel", + "addon.coursecompletion.notyetstarted": "Még nem kezdődött el", + "addon.coursecompletion.pending": "Függőben", + "addon.coursecompletion.required": "Kitöltendő", + "addon.coursecompletion.requiredcriteria": "Előírt követelmények", + "addon.coursecompletion.requirement": "Követelmény", + "addon.coursecompletion.status": "Állapot", + "addon.coursecompletion.viewcoursereport": "Kurzusjelentés megtekintése", "addon.files.files": "Állományok", "addon.files.privatefiles": "Saját állományaim", "addon.files.sitefiles": "Portál állományai", + "addon.messages.acceptandaddcontact": "Elfogadás és hozzáadás a kapcsolatokhoz", "addon.messages.addcontact": "Kapcsolat hozzáadása", - "addon.messages.blockcontact": "Kapcsolat zárolása", + "addon.messages.addcontactconfirm": "Biztosan hozzáadja {{$a}}-t a kapcsolataihoz", + "addon.messages.addtofavourites": "Megjelölés", + "addon.messages.addtoyourcontacts": "Felvétel a kapcsolatok közé", "addon.messages.blocknoncontacts": "Ismeretlenek üzeneteit ne kapjam meg", + "addon.messages.blockuser": "Felhasználó kizárása", + "addon.messages.blockuserconfirm": "Biztosan kizárja {{$a}}-t?", + "addon.messages.contactableprivacy": "Üzenet fogadhatók innen:", + "addon.messages.contactableprivacy_coursemember": "Kapcsolataim és bárki a kurzusomból", + "addon.messages.contactableprivacy_onlycontacts": "Csak a kapcsolataim", + "addon.messages.contactableprivacy_site": "A portálon bárki", + "addon.messages.contactblocked": "Kapcsolat zárolva", + "addon.messages.contactrequestsent": "Kapcsolatfelvételi kérés elküldve", "addon.messages.contacts": "Kapcsolatok", + "addon.messages.decline": "Elutasítás", + "addon.messages.deleteallconfirm": "Biztosan törli az egész beszélgetést? Ezzel egyéb résztvevők részére nem törli a beszélgetésből.", + "addon.messages.deleteconversation": "Beszélgetés törlése", + "addon.messages.groupconversations": "Csoport", + "addon.messages.groupinfo": "Csoportinfó", + "addon.messages.individualconversations": "Magán", + "addon.messages.info": "Infó", + "addon.messages.isnotinyourcontacts": "{{$a}} nem szerepel a kapcsolatai között", "addon.messages.message": "Üzenet", "addon.messages.messagepreferences": "Üzenet beállításai", "addon.messages.messages": "Üzenetek", "addon.messages.newmessage": "Új üzenet", - "addon.messages.nomessages": "Nincs üzenet.", + "addon.messages.nocontactrequests": "Nincs kapcsolatkérés", + "addon.messages.nocontactsgetstarted": "Nincs kapcsolata", + "addon.messages.nofavourites": "Nincsenek megjelölt beszélgetések", + "addon.messages.nogroupconversations": "Nincsenek csoportbeszélgetések", + "addon.messages.noindividualconversations": "Nincsenek magánbeszélgetések", + "addon.messages.nomessagesfound": "Nem találtam üzeneteket", + "addon.messages.noncontacts": "Nem kapcsolat", + "addon.messages.numparticipants": "{{$a}} résztvevő", "addon.messages.removecontact": "Kapcsolat törlése", - "addon.messages.unblockcontact": "Kapcsolat zárolásának feloldása", + "addon.messages.removecontactconfirm": "Biztosan eltávolítja {{$a}}-t a kapcsolatai közül?", + "addon.messages.removefromfavourites": "Megjelölés törlése", + "addon.messages.removefromyourcontacts": "Törlés a kapcsolatok közül", + "addon.messages.requests": "Kérések", + "addon.messages.requirecontacttomessage": "Az üzenetküldéshez kérje meg {{$a}}-t, hogy vegye fel Önt a kapcsolatok közé.", + "addon.messages.searchcombined": "Emberek vagy üzenetek keresése", + "addon.messages.searchnocontactsfound": "Nem található kapcsolat", + "addon.messages.searchnomessagesfound": "Nincs üzenet", + "addon.messages.searchnononcontactsfound": "Kapcsolaton kívüliek nem találhatók", + "addon.messages.sendcontactrequest": "Kapcsolatkérés küldése", + "addon.messages.unabletomessage": "Ennek a felhasználónak nem küldhet üzenetet", + "addon.messages.unblockuser": "Felhasználó kizárásának feloldása", + "addon.messages.unblockuserconfirm": "Biztosan feloldja {{$a}} kizárását?", + "addon.messages.userwouldliketocontactyou": "{{$a}} szeretne kapcsolatba lépni Önnel", + "addon.messages.wouldliketocontactyou": "Szeretnék kapcsolatba lépni Önnel", + "addon.messages.you": "Ön:", + "addon.messages.youhaveblockeduser": "Kirábban kizárta ezt a felhasználót", + "addon.messages.yourcontactrequestpending": "Kapcsolatkérése {{$a}} esetén függőben", "addon.mod_assign.addattempt": "Újabb próbálkozás engedélyezése", "addon.mod_assign.addnewattempt": "Új próbálkozás hozzáadása", "addon.mod_assign.addnewattemptfromprevious": "Új próbálkozás hozzáadása az előző leadás alapján", @@ -112,7 +217,9 @@ "addon.mod_assign.grade": "Pont", "addon.mod_assign.graded": "Pontozott", "addon.mod_assign.gradedby": "Osztályozta", + "addon.mod_assign.gradedfollowupsubmit": "Osztályozott - nyomon követő leadott munka átvéve", "addon.mod_assign.gradedon": "Osztályozás időpontja", + "addon.mod_assign.gradelocked": "Ez az osztályzat zárolva van vagy felülírták az osztályozónaplóban", "addon.mod_assign.gradeoutof": "{{$a}} pontból", "addon.mod_assign.gradingstatus": "Osztályozás állapota", "addon.mod_assign.groupsubmissionsettings": "Csoportos leadás beállításai", @@ -126,6 +233,7 @@ "addon.mod_assign.markingworkflowstatereadyforrelease": "Közzétételre kész", "addon.mod_assign.markingworkflowstatereadyforreview": "Az értékelés véget ért.", "addon.mod_assign.markingworkflowstatereleased": "Közzétéve", + "addon.mod_assign.modulenameplural": "Feladatok", "addon.mod_assign.multipleteams": "Több csoportnak is tagja", "addon.mod_assign.multipleteams_desc": "A feladat csoportos leadott munkát ír elő. Ön egynél több csoport tagja. Leadáshoz csak egy csoportnak lehet a tagja. Csoporttagságának módosításáért forduljon tanárához.", "addon.mod_assign.noattempt": "Nincs próbálkozás", @@ -175,6 +283,7 @@ "addon.mod_assign_submission_file.pluginname": "Leadás állományban", "addon.mod_assign_submission_onlinetext.pluginname": "Online szövegben leadott munkák", "addon.mod_book.errorchapter": "Hiba a fejezet olvasása közben.", + "addon.mod_book.modulenameplural": "Könyvek", "addon.mod_chat.beep": "Hangjelzés", "addon.mod_chat.currentusers": "Mostani felhasználók", "addon.mod_chat.enterchat": "Kattintson ide a csevegésbe való bekapcsolódáshoz", @@ -182,6 +291,7 @@ "addon.mod_chat.messagebeepsyou": "{{$a}} most csöngetett Önnek!", "addon.mod_chat.messageenter": "{{$a}} most lépett be a csevegésbe", "addon.mod_chat.messageexit": "{{$a}} most lépett ki a csevegésből", + "addon.mod_chat.modulenameplural": "Csevegések", "addon.mod_chat.nomessages": "Még nincs üzenet", "addon.mod_chat.send": "Elküld", "addon.mod_chat.sessionstart": "A következő csevegés kezdete {{$a.date}}, (mostantól {{$a.fromnow}})", @@ -190,6 +300,7 @@ "addon.mod_choice.choiceoptions": "Választási lehetőségek", "addon.mod_choice.expired": "Ez a tevékenység {{$a}} időpontban lezárult és már nem érhető el", "addon.mod_choice.full": "(Betelt)", + "addon.mod_choice.modulenameplural": "Válaszlehetőségek", "addon.mod_choice.noresultsviewable": "Az eredmények jelenleg nem tekinthetők meg.", "addon.mod_choice.notopenyet": "Ez a tevékenység {{$a}} időpontig nem elérhető", "addon.mod_choice.numberofuser": "Válaszok száma", @@ -223,8 +334,10 @@ "addon.mod_data.errormustsupplyvalue": "Itt egy értéket kell megadnia.", "addon.mod_data.expired": "A tevékenység {{$a}} időpontban lezárult és már nem érhető el.", "addon.mod_data.fields": "Mezők", + "addon.mod_data.foundrecords": "Vannak rekordok: {{$a.num}}/{{$a.max}} (Szűrők visszaállítása)", "addon.mod_data.latlongboth": "Mind a szélesség, mind a hosszúság kitöltendő.", "addon.mod_data.menuchoose": "Választás...", + "addon.mod_data.modulenameplural": "Adatbázisok", "addon.mod_data.more": "Tovább", "addon.mod_data.nomatch": "Nincs egyező fogalom!", "addon.mod_data.norecords": "Nincsenek bejegyzések az adatbázisban", @@ -254,6 +367,7 @@ "addon.mod_feedback.feedbackopen": "Válaszok engedélyezése a feladótól", "addon.mod_feedback.mapcourses": "A visszajelzés illesztése kurzusokhoz.", "addon.mod_feedback.mode": "Mód", + "addon.mod_feedback.modulenameplural": "Visszajelzések", "addon.mod_feedback.next_page": "Következő oldal", "addon.mod_feedback.non_anonymous": "A felhasználó nevét a rendszer naplózza és a válaszok mellett megjeleníti", "addon.mod_feedback.non_anonymous_entries": "Nem névtelen bejegyzések ({{$a}})", @@ -273,6 +387,7 @@ "addon.mod_feedback.show_nonrespondents": "Nem válaszolók megjelenítése", "addon.mod_feedback.started": "Elkezdődött", "addon.mod_feedback.this_feedback_is_already_submitted": "Ezt a tevékenységet már befejezte.", + "addon.mod_folder.modulenameplural": "Mappák", "addon.mod_forum.addanewdiscussion": "Új vitatéma hozzáadása", "addon.mod_forum.addanewquestion": "Új kérdés hozzáadása", "addon.mod_forum.addanewtopic": "Új téma hozzáadása", @@ -291,6 +406,7 @@ "addon.mod_forum.modeflatnewestfirst": "Válaszok egymás után, a legújabbal kezdve", "addon.mod_forum.modeflatoldestfirst": "Válaszok egymás után, a legrégebbivel kezdve", "addon.mod_forum.modenested": "Válaszok beágyazott formában", + "addon.mod_forum.modulenameplural": "Fórumok", "addon.mod_forum.posttoforum": "Hozzászólás a fórumhoz", "addon.mod_forum.re": "Tárgy:", "addon.mod_forum.reply": "Válasz", @@ -309,7 +425,9 @@ "addon.mod_glossary.fillfields": "A fogalom és a meghatározás kötelezően kitöltendő.", "addon.mod_glossary.fullmatch": "Csak egész szavak egyezése", "addon.mod_glossary.linking": "Automatikus kapcsolás", + "addon.mod_glossary.modulenameplural": "Fogalomtárak", "addon.mod_imscp.deploymenterror": "Hiba a tartalomcsomagban!", + "addon.mod_imscp.modulenameplural": "IMS tartalomcsomagok", "addon.mod_lesson.answer": "Válasz", "addon.mod_lesson.attempt": "Próbálkozás: {{$a}}", "addon.mod_lesson.attemptheader": "Próbálkozás", @@ -349,6 +467,7 @@ "addon.mod_lesson.lowtime": "Legrövidebb idő", "addon.mod_lesson.maximumnumberofattemptsreached": "A próbálkozások maximális számát elérte - áttérés a következő oldalra.", "addon.mod_lesson.modattemptsnoteacher": "Önellenőrzést csak tanulók végezhetnek.", + "addon.mod_lesson.modulenameplural": "Leckék", "addon.mod_lesson.noanswer": "Nincs megadva válasz. Lépjen vissza és adjon le egy választ.", "addon.mod_lesson.nolessonattempts": "A leckével kapcsolatosan nem történt próbálkozás.", "addon.mod_lesson.nolessonattemptsgroup": "{{$a}} csoporttag a leckével nem próbálkozott.", @@ -386,6 +505,8 @@ "addon.mod_lesson.youranswer": "Válasza", "addon.mod_lesson.yourcurrentgradeisoutof": "Jelenlegi pontjainak száma {{$a.grade}} pont a {{$a.total}} pontból", "addon.mod_lesson.youshouldview": "Minimálisan megválaszolandó: {{$a}}", + "addon.mod_lti.modulenameplural": "Külső eszközök", + "addon.mod_page.modulenameplural": "Oldalak", "addon.mod_quiz.attemptfirst": "Első próbálkozás", "addon.mod_quiz.attemptlast": "Utolsó próbálkozás", "addon.mod_quiz.attemptnumber": "Próbálkozás", @@ -407,6 +528,7 @@ "addon.mod_quiz.grademethod": "Pontozási módszer", "addon.mod_quiz.gradesofar": "{{$a.method}}: {{$a.mygrade}} / {{$a.quizgrade}}.", "addon.mod_quiz.marks": "Pontok", + "addon.mod_quiz.modulenameplural": "Tesztek", "addon.mod_quiz.mustbesubmittedby": "A próbálkozást {{$a}} időpontig le kell adni.", "addon.mod_quiz.noquestions": "Nincs még hozzáadva kérdés", "addon.mod_quiz.noreviewattempt": "Ezt a próbálkozást nem ellenőrizheti.", @@ -446,6 +568,7 @@ "addon.mod_quiz.timetaken": "Felhasznált idő", "addon.mod_quiz.yourfinalgradeis": "Végső pontja erre a tesztre {{$a}}", "addon.mod_resource.modifieddate": "Módosítás dátuma: {{$a}}", + "addon.mod_resource.modulenameplural": "Tananyagok", "addon.mod_resource.uploadeddate": "Feltöltés dátuma: {{$a}}", "addon.mod_scorm.asset": "Tudáselem", "addon.mod_scorm.assetlaunched": "Tudáselem - megtekintve", @@ -471,6 +594,7 @@ "addon.mod_scorm.incomplete": "Nem teljes", "addon.mod_scorm.lastattempt": "Utolsó befejezett próbálkozás", "addon.mod_scorm.mode": "Leggyakoribb", + "addon.mod_scorm.modulenameplural": "SCORM/AICC csomagok", "addon.mod_scorm.newattempt": "Új próbálkozás elkezdése", "addon.mod_scorm.noattemptsallowed": "Engedélyezett próbálkozások száma", "addon.mod_scorm.noattemptsmade": "Próbálkozásainak száma", @@ -482,17 +606,20 @@ "addon.mod_scorm.suspended": "Felfüggesztve", "addon.mod_survey.ifoundthat": "Azt találtam", "addon.mod_survey.ipreferthat": "Inkább ezt szeretném", + "addon.mod_survey.modulenameplural": "Felmérések", "addon.mod_survey.responses": "Tanuló által adott válaszok", "addon.mod_survey.surveycompletednograph": "A felmérést befejezte.", + "addon.mod_url.modulenameplural": "Webcímek", "addon.mod_wiki.cannoteditpage": "Ezt az oldalt nem szerkesztheti.", "addon.mod_wiki.createpage": "Oldal létrehozása", "addon.mod_wiki.editingpage": "'{{$a}}' oldal szerkesztése", "addon.mod_wiki.map": "Térkép", + "addon.mod_wiki.modulenameplural": "Wikik", "addon.mod_wiki.newpagehdr": "Új oldal", "addon.mod_wiki.newpagetitle": "Új oldalcím", "addon.mod_wiki.nocontent": "Az oldalhoz nincs tartalom", "addon.mod_wiki.notingroup": "Nem része a csoportnak", - "addon.mod_wiki.pageexists": "Az oldal már létezik. Átirányítás az oldalra.", + "addon.mod_wiki.pageexists": "Az oldal már létezik.", "addon.mod_wiki.pagename": "Oldal neve", "addon.mod_wiki.wrongversionlock": "A szerkesztés közben egy másik felhasználó szerkesztette ezt az oldalt, így az Ön tartalma elavult.", "addon.mod_workshop.alreadygraded": "Már pontozták", @@ -521,6 +648,7 @@ "addon.mod_workshop.gradinggradecalculated": "Értékelés számított pontja", "addon.mod_workshop.gradinggradeof": "értékelési pont (/ {{$a}})", "addon.mod_workshop.gradinggradeover": "Értékelésre adott pont felülírása", + "addon.mod_workshop.modulenameplural": "Műhelymunkák", "addon.mod_workshop.nogradeyet": "Még nincs pont", "addon.mod_workshop.notassessed": "Még nincs értékelve", "addon.mod_workshop.notoverridden": "Nincs felülírva", @@ -879,6 +1007,8 @@ "core.accounts": "Fiókok", "core.add": "Hozzáadás", "core.agelocationverification": "Életkor és hely ellenőrzése", + "core.ago": "{{$a}} óta", + "core.all": "Mind", "core.allparticipants": "Összes résztvevő", "core.answer": "Válasz", "core.answered": "Megválaszolva", @@ -904,46 +1034,38 @@ "core.completion-alt-manual-y": "Teljesítve: {{$a}}, teljesítetlenként való megjelöléséhez válassza ki.", "core.completion-alt-manual-y-override": "Befejezve: {{$a.modname}} (beállította {{$a.overrideuser}}). Válassza ki befejezetlenként való megjelölésre.", "core.confirmdeletefile": "Biztosan törli ezt az állományt?", - "core.considereddigitalminor": "Digitálisan kiskorúnak minősülsz.", + "core.considereddigitalminor": "Túl fiatal vagy a portálon fiók létrehozásához.", "core.content": "Tartalom", "core.continue": "Folytatás", "core.course": "Kurzus", "core.course.contents": "Tartalomjegyzék", "core.course.coursesummary": "Kurzusösszegzés", + "core.course.downloadcourse": "Kurzus letöltése", "core.course.hiddenfromstudents": "Tanulók elől rejtve", "core.course.hiddenoncoursepage": "Elérhető, de a kurzusoldalon nem látható", "core.course.overriddennotice": "A tevékenységgel kapcsolatos végső pontja kézzel módosítva lett.", "core.course.sections": "Szekciók", "core.coursedetails": "Kurzusadatok", + "core.courses.addtofavourites": "Jelölje meg a kurzust", "core.courses.allowguests": "Ez a kurzus megengedi vendégek belépését", "core.courses.availablecourses": "Felvehető kurzusok", "core.courses.categories": "Kurzuskategóriák", - "core.courses.courseoverview": "Kurzus áttekintése", "core.courses.courses": "Kurzusok", "core.courses.frontpage": "Kezdőoldal", - "core.courses.future": "Jövőbeni", - "core.courses.inprogress": "Folyamatban lévő", - "core.courses.morecourses": "További kurzusok", + "core.courses.hidecourse": "Elrejtés", "core.courses.mycourses": "Kurzusaim", - "core.courses.next30days": "A következő 30 nap", - "core.courses.next7days": "A következő 7 nap", + "core.courses.mymoodle": "Irányítópult", "core.courses.nocourses": "Nincs megjelenítendő kurzusinformáció.", - "core.courses.nocoursesfuture": "Nincs jövőbeni kurzus", - "core.courses.nocoursesinprogress": "Nincs folyamatban lévő kurzus", - "core.courses.nocoursesoverview": "Nincs kurzus", - "core.courses.nocoursespast": "Nincs korábbi kurzus", "core.courses.nocoursesyet": "Ebben a kategóriában nincsenek kurzusok", - "core.courses.noevents": "Nincs esedékes tevékenység", "core.courses.nosearchresults": "Nincs eredmény", "core.courses.notenroled": "Ezt a kurzust nem vette föl", - "core.courses.past": "Korábbi", "core.courses.paymentrequired": "Ebbe a kurzusba csak fizetés után léphet be.", "core.courses.paypalaccepted": "PayPal-lel való fizetést elfogadunk", + "core.courses.removefromfavourites": "Kurzusbejelölés törlése", "core.courses.search": "Keresés", "core.courses.searchcourses": "Kurzusok keresése", "core.courses.sendpaymentbutton": "Fizetés küldése Paypal-lel", - "core.courses.sortbycourses": "Rendezés kurzusonként", - "core.courses.sortbydates": "Rendezés dátumonként", + "core.courses.show": "Ezen kurzus megjelenítése", "core.date": "Dátum", "core.day": "nap", "core.days": "nap", @@ -952,13 +1074,14 @@ "core.delete": "Törlés", "core.description": "Leírás", "core.digitalminor": "Digitálisan kiskorú", - "core.digitalminor_desc": "Fiók létrehozásához szülőd/gondviselőd lépjen kapcsolatba az alábbi személlyel.", + "core.digitalminor_desc": "Fiók létrehozásához szülőd/gondviselőd lépjen kapcsolatba az alábbi személlyel:", "core.done": "Kész", "core.download": "Letöltés", "core.edit": "Szerkesztés", "core.error": "Hiba", "core.errordownloading": "Nem sikerült letölteni az állományt.", "core.explanationdigitalminor": "Erre az adatra nagykorúsága megállapításához van szükség. A nagykorú jogosult feltételeket vállalni, adatai pedig tárolhatók és feldolgozhatók.", + "core.favourites": "Megjelölt", "core.filename": "Állománynév", "core.fileuploader.addfiletext": "Állomány hozzáadása", "core.fileuploader.errorcapturingaudio": "Hiba a hang rögzítése közben.", @@ -1006,6 +1129,8 @@ "core.login.cancel": "Mégse", "core.login.createaccount": "Új felhasználói azonosítóm létrehozása", "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.forgotten": "Elfelejtette felhasználónevét vagy jelszavát?", @@ -1014,6 +1139,7 @@ "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", "core.login.invalidaccount": "Ellenőrizze belépési adatait vagy ellenőriztesse a rendszergazdával a portál beállításait.", + "core.login.invaliddate": "Érvénytelen dátum", "core.login.invalidemail": "Érvénytelen e-mail cím", "core.login.invalidmoodleversion": "Érvénytelen Moodle-verzió. A minimális verziószám a 2.4.", "core.login.invalidsite": "A portál-URL nem érvényes.", @@ -1025,6 +1151,7 @@ "core.login.missingfirstname": "Hiányzó keresztnév", "core.login.missinglastname": "Hiányzó vezetéknév", "core.login.mobileservicesnotenabled": "Portálján a mobil szolgáltatások nincsenek engedélyezve. A mobil elérhetőség bekapcsolásához forduljon a rendszergazdához.", + "core.login.mustconfirm": "Meg kell erősítenie a fiókját", "core.login.newaccount": "Új fiók", "core.login.password": "Jelszó", "core.login.passwordforgotten": "Elfelejtett jelszó", @@ -1036,6 +1163,7 @@ "core.login.policyagreementclick": "Ugrópont a portál használati feltételeiről szóló megállapodáshoz", "core.login.potentialidps": "Lépjen be itteni fiókjával:", "core.login.profileinvaliddata": "Érvénytelen érték", + "core.login.resendemail": "E-mail újbóli elküldése", "core.login.security_question": "Biztonsági kérdés", "core.login.selectacountry": "Válasszon egy országot", "core.login.siteinmaintenance": "A portálja karbantartási üzemmódban van.", @@ -1051,7 +1179,6 @@ "core.lostconnection": "A kapcsolat megszakadt, kacsolódjon újból. Jele érvénytelen.", "core.mainmenu.help": "Súgó", "core.mainmenu.logout": "Kilépés", - "core.mainmenu.mycourses": "Kurzusaim", "core.mainmenu.website": "Weboldal", "core.maxsizeandattachments": "Új állományok maximális mérete: {{$a.size}}, maximális csatolt állomány: {{$a.attachments}}", "core.min": "p", @@ -1093,7 +1220,7 @@ "core.nocomments": "Nincs megjegyzés.", "core.nograde": "Nincs pont", "core.none": "Nincs", - "core.nopermissions": "Ehhez ({{$a}}) jelenleg nincs engedélye", + "core.nopermissions": "Ehhez ({{$a}}) jelenleg nincs engedélye.", "core.noresults": "Nincs eredmény", "core.notice": "Tájékoztatás", "core.now": "most", @@ -1125,7 +1252,9 @@ "core.question.requiresgrading": "Pontozandó", "core.quotausage": "Összesen {{$a.total}} egységből {{$a.used}}-t használt fel.", "core.refresh": "Frissítés", + "core.remove": "Törlés", "core.required": "Kitöltendő", + "core.resources": "tananyagok", "core.restore": "Helyreállítás", "core.save": "Mentés", "core.search": "Keresés", @@ -1165,6 +1294,21 @@ "core.sizekb": "KB", "core.sizemb": "MB", "core.sortby": "Rendezési szempont", + "core.strftimedate": "%Y. %B %d.", + "core.strftimedatefullshort": "%y/%m/%d", + "core.strftimedateshort": "%B %d.", + "core.strftimedatetime": "%Y. %B %d., %H:%M", + "core.strftimedatetimeshort": "%Y/%m/%d %H:%M", + "core.strftimedaydate": "%Y. %B %d., %A", + "core.strftimedaydatetime": "%Y. %B %d., %A, %H:%M", + "core.strftimedayshort": "%B, %d., %A", + "core.strftimedaytime": "%H:%M, %a", + "core.strftimemonthyear": "%Y. %B", + "core.strftimerecent": "%b %d., %H:%M", + "core.strftimerecentfull": "%Y. %b. %d., %H:%M", + "core.strftimetime": "%H:%M", + "core.strftimetime12": "%I:%M %p", + "core.strftimetime24": "%H:%M", "core.submit": "Leadás", "core.success": "Sikerült", "core.teachers": "Tanárok", @@ -1181,17 +1325,21 @@ "core.user.country": "Ország", "core.user.description": "Leírás", "core.user.details": "Részletek", + "core.user.editingteacher": "Tanár", "core.user.email": "E-mail cím", "core.user.emailagain": "E-mail (ismét)", "core.user.firstname": "Keresztnév", "core.user.interests": "Érdeklődési kör", "core.user.lastname": "Vezetéknév", + "core.user.manager": "Igazgató", "core.user.newpicture": "Új kép", "core.user.noparticipants": "A kurzushoz nincsenek résztvevők!", "core.user.participants": "Résztvevők", "core.user.phone1": "Telefon", "core.user.phone2": "Mobiltelefon", "core.user.roles": "Szerepek", + "core.user.student": "Tanuló", + "core.user.teacher": "Nem szerkesztő tanár", "core.user.webpage": "Weboldal", "core.userdeleted": "Ez a felhasználó törölve lett", "core.userdetails": "Felhasználó adatai", diff --git a/src/assets/lang/id.json b/src/assets/lang/id.json index c2c4c2fb0..43197d536 100644 --- a/src/assets/lang/id.json +++ b/src/assets/lang/id.json @@ -1,4 +1,6 @@ { + "addon.block_activitymodules.pluginname": "Aktivitas", + "addon.block_sitemainmenu.pluginname": "Menu utama", "addon.calendar.calendar": "Kalender", "addon.calendar.calendarevents": "Kalender Acara", "addon.calendar.defaultnotificationtime": "Pemberitahuan waktu default", @@ -15,23 +17,9 @@ "addon.competency.errornocompetenciesfound": "Tidak ditemukan kompetensi", "addon.competency.nocompetencies": "Tidak ada kompetensi", "addon.coursecompletion.complete": "Lengkap", - "addon.coursecompletion.completed": "Selesai", - "addon.coursecompletion.completiondate": "Tanggal penyelesaian", "addon.coursecompletion.couldnotloadreport": "Tidak dapat memuat laporan penyelesaian materi", - "addon.coursecompletion.coursecompletion": "Penyelesaian materi", - "addon.coursecompletion.criteria": "Kriteria", - "addon.coursecompletion.criteriagroup": "Kelompok kriteria", - "addon.coursecompletion.criteriarequiredall": "Semua kriteria di bawah ini diperlukan", - "addon.coursecompletion.criteriarequiredany": "Kriteria apapun di bawah ini diperlukan", - "addon.coursecompletion.inprogress": "Dalam proses", - "addon.coursecompletion.manualselfcompletion": "Panduan penyelesaian manual", - "addon.coursecompletion.notyetstarted": "Belum dimulai", - "addon.coursecompletion.pending": "Tertunda", - "addon.coursecompletion.required": "Wajib", - "addon.coursecompletion.requiredcriteria": "Kriteria wajib", - "addon.coursecompletion.requirement": "Persyaratan", + "addon.coursecompletion.required": "Diwajibkan", "addon.coursecompletion.status": "Status", - "addon.coursecompletion.viewcoursereport": "Lihat laporan materi", "addon.files.couldnotloadfiles": "Daftar file tidak dapat dimuat", "addon.files.emptyfilelist": "Tidak ada file yang bisa ditampilkan.", "addon.files.erroruploadnotworking": "Sayang sekali saat ini tidak memungkinkan untuk mengunggah file ke situs Anda.", @@ -40,8 +28,6 @@ "addon.files.sitefiles": "File situs", "addon.messageoutput_airnotifier.processorsettingsdesc": "Konfigurasikan perangkat", "addon.messages.addcontact": "Tambahkan kontak", - "addon.messages.blockcontact": "Blokir kontak", - "addon.messages.blockcontactconfirm": "Anda akan berhenti menerima pesan dari kontak ini.", "addon.messages.blocknoncontacts": "Cegah pengguna yang tidak ada dalam kontak untuk mengirim pesan kepada saya", "addon.messages.contactlistempty": "Daftar kontak kosong.", "addon.messages.contactname": "Nama kontak", @@ -54,15 +40,16 @@ "addon.messages.messagenotsent": "Pesan tidak terkirim. Silahkan coba lagi.", "addon.messages.messages": "Pesan-pesan", "addon.messages.newmessages": "Pesan baru", + "addon.messages.nomessagesfound": "Tidak ada pesan ditemukan", "addon.messages.nousersfound": "Tidak ada pengguna ditemukan", "addon.messages.removecontact": "Hapus kontak", "addon.messages.removecontactconfirm": "Kontak akan dihapus dari daftar kontak Anda.", + "addon.messages.searchcombined": "Cari orang dan pesan", "addon.messages.type_blocked": "Blokir", "addon.messages.type_offline": "Ofline", "addon.messages.type_online": "Online", "addon.messages.type_search": "Hasil pencarian", "addon.messages.type_strangers": "Lainnya", - "addon.messages.unblockcontact": "Buka blokiran kontak", "addon.messages.warningmessagenotsent": "Tidak dapat mengirim pesan ke pengguna {{user}}. {{kesalahan}}", "addon.mod_assign.acceptsubmissionstatement": "Mohon terima pernyataan penyerahan.", "addon.mod_assign.addsubmission": "Tambahkan pengajuan (tugas/laporan)", @@ -86,6 +73,7 @@ "addon.mod_assign_feedback_comments.pluginname": "Komentar umpan balik.", "addon.mod_assign_submission_file.pluginname": "Pengiriman berkas", "addon.mod_assign_submission_onlinetext.pluginname": "Pengiriman teks daring", + "addon.mod_book.modulenameplural": "Buku-buku", "addon.mod_chat.beep": "Beep", "addon.mod_chat.currentusers": "Pengguna saat ini", "addon.mod_chat.enterchat": "Klik disini untuk masuk chat sekarang", @@ -98,6 +86,7 @@ "addon.mod_chat.messagebeepsyou": "{{$a}} baru saja membeep Anda!", "addon.mod_chat.messageenter": "{{$a}} baru saja memasuki obrolan ini", "addon.mod_chat.messageexit": "{{$a}} telah meninggalkan obrolan ini", + "addon.mod_chat.modulenameplural": "Obrolan", "addon.mod_chat.mustbeonlinetosendmessages": "Anda harus online untuk mengirim pesan.", "addon.mod_chat.nomessages": "Tidak ada pesan", "addon.mod_chat.send": "Kirim", @@ -134,6 +123,7 @@ "addon.mod_forum.modeflatnewestfirst": "Tampilkan tanggapan secara secara flat, dari yang terbaru", "addon.mod_forum.modeflatoldestfirst": "Tampilkan tanggapan secara secara flat, dari yang terlama", "addon.mod_forum.modenested": "Tampilkan tanggapan dalam bentuk sarang", + "addon.mod_forum.modulenameplural": "Forum", "addon.mod_forum.numdiscussions": "{{numdiscussions}} diskusi", "addon.mod_forum.numreplies": "{{numreplies}} jawaban", "addon.mod_forum.posttoforum": "Post ke forum", @@ -195,6 +185,7 @@ "addon.mod_lesson.lowtime": "Daftar waktu terendah", "addon.mod_lesson.maximumnumberofattemptsreached": "Sudah mencapai jumlah maksimum dari percobaan-Lanjut ke halaman berikutnya", "addon.mod_lesson.modattemptsnoteacher": "Siswa hanya boleh meninjau pekerjaan siswa.", + "addon.mod_lesson.modulenameplural": "Pelajaran", "addon.mod_lesson.noanswer": "Tidak ada jawaban diberikan", "addon.mod_lesson.nolessonattempts": "Belum ada percobaan yang dilakukan pada pelajaran kali ini.", "addon.mod_lesson.notcompleted": "Tidak selesai", @@ -257,6 +248,7 @@ "addon.mod_quiz.gradehighest": "Nilai Tertinggi", "addon.mod_quiz.grademethod": "Metode penilaian", "addon.mod_quiz.marks": "Tanda", + "addon.mod_quiz.modulenameplural": "Kuis", "addon.mod_quiz.noquestions": "Belum ada pertanyaan yang dimasukkan", "addon.mod_quiz.opentoc": "Buka navigasi popover.", "addon.mod_quiz.question": "Pertanyaan", @@ -289,6 +281,7 @@ "addon.mod_survey.errorgetsurvey": "Terjadi kesalahan mendapatkan data survey.", "addon.mod_survey.ifoundthat": "Saya menemukan bahwa", "addon.mod_survey.ipreferthat": "Saya lebih menyukai bahwa", + "addon.mod_survey.modulenameplural": "Survei", "addon.mod_survey.results": "Hasil.", "addon.mod_url.accessurl": "Akses URL", "addon.mod_url.pointingtourl": "URL poin sumber ini ke", @@ -558,6 +551,8 @@ "core.accounts": "Akun", "core.add": "Tambah", "core.agelocationverification": "Verifikasi usia dan lokasi", + "core.ago": "{{$a}} yang lalu", + "core.all": "Semua", "core.allparticipants": "Semua peserta", "core.android": "Android", "core.answer": "Jawaban", @@ -619,7 +614,6 @@ "core.courses.cannotretrievemorecategories": "Kategori yang lebih dalam dari tingkat {{$ a}} tidak dapat diambil.", "core.courses.categories": "Kategori Kursus", "core.courses.confirmselfenrol": "Apakah Anda yakin ingin mendaftarkan diri mengikuti materi ini?", - "core.courses.courseoverview": "Tinjauan luas kursus", "core.courses.courses": "Kursus", "core.courses.enrolme": "Daftarkan saya", "core.courses.errorloadcategories": "Terjadi kesalahan saat memuat kategori.", @@ -630,7 +624,6 @@ "core.courses.frontpage": "Halaman depan", "core.courses.mycourses": "Kursus Yang Saya Ikuti", "core.courses.nocourses": "Tidak ada informasi kursus untuk ditampilkan.", - "core.courses.nocoursesoverview": "Tidak ada kursus", "core.courses.nocoursesyet": "Belum ada kursus saat ini", "core.courses.notenrollable": "Anda tidak dapat mendaftarkan diri ke materi.", "core.courses.password": "kunci pendaftaran", @@ -753,7 +746,7 @@ "core.login.createaccount": "Buat keanggotaan baru", "core.login.createuserandpass": "Buat nama dan password pengguna baru untuk penggunaan login", "core.login.credentialsdescription": "Silahkan masukkan username dan password untuk log in", - "core.login.emailconfirmsent": "

An email should have been sent to your address at {{$a}}

It contains easy instructions to complete your registration.

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

", + "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.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", @@ -766,7 +759,7 @@ "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", "core.login.invalidaccount": "Silahkan periksa rincian login Anda atau tanyakan pada administrator situs Anda untuk memeriksa konfigurasi situs.", - "core.login.invaliddate": "Tanggal tidak valid.", + "core.login.invaliddate": "Tanggal tidak valid", "core.login.invalidemail": "Kesalahan pada alamat email", "core.login.invalidmoodleversion": "Versi Moodle tidak valid.versi minimum yang dibutuhkan adalah 2.4.", "core.login.invalidsite": "Situs URL tidak valid.", @@ -783,6 +776,7 @@ "core.login.missingfirstname": "Nama depan tidak diisi", "core.login.missinglastname": "Nama akhir tidak diisi", "core.login.mobileservicesnotenabled": "Layanan Mobile tidak aktif di situs Anda. Silahkan hubungi kontak administrator jika menurut Anda akses mobile perlu diaktifkan.", + "core.login.mustconfirm": "Anda perlu mengkonfirmasikan login Anda", "core.login.newaccount": "Anggota baru", "core.login.newsitedescription": "Silahkan masukkan URL situs Mobile. Perlu diperhatikan bahwa ini mungkin tidak dikonfigurasi untuk bekerja dengan aplikasi ini.", "core.login.notloggedin": "Anda butuh log in.", @@ -822,12 +816,11 @@ "core.mainmenu.changesite": "Mengubah situs", "core.mainmenu.help": "Bantuan", "core.mainmenu.logout": "Keluar", - "core.mainmenu.mycourses": "Kursus Yang Saya Ikuti", - "core.mainmenu.togglemenu": "Menu Toggle", "core.mainmenu.website": "Situs web", "core.maxsizeandattachments": "Ukuran maksimal untuk file baru: {{$a.size}}, lampiran maksimum: {{$a.attachments}}", "core.min": "min", "core.mins": "min", + "core.mod_assignment": "Penugasan 2.2 (Non-aktif)", "core.mod_book": "Buku", "core.mod_chat": "Obrolan", "core.mod_file": "File", @@ -885,8 +878,10 @@ "core.quotausage": "Saat ini Anda telah menggunakan {$ a-> used}} dari batas {$ a-> total}} Anda.", "core.redirectingtosite": "Anda akan diarahkan ke situs.", "core.refresh": "Refresh", + "core.remove": "Hapus", "core.required": "Diwajibkan", "core.requireduserdatamissing": "Pengguna ini tidak memiliki beberapa data profil yang dibutuhkan. Tolong isi data ini di Moodle Anda dan coba lagi.
{{$ a}}", + "core.resources": "Sumber", "core.restore": "Kembalikan", "core.retry": "Coba lagi", "core.save": "Simpan", @@ -963,6 +958,14 @@ "core.sizetb": "TB", "core.sorry": "Maaf...", "core.sortby": "Urutan berdasarkan", + "core.strftimedate": "%d %B %Y", + "core.strftimedateshort": "%d %B", + "core.strftimedatetime": "%d %B %Y, %H:%M", + "core.strftimedaydate": "%A, %d %B %Y", + "core.strftimedaydatetime": "%A, %d %B %Y, %H:%M", + "core.strftimerecent": "%d %b, %H:%M", + "core.strftimerecentfull": "%a, %d %b %Y, %H:%M", + "core.strftimetime": "%H:%M", "core.submit": "Sampaikan", "core.success": "Sukses", "core.tablet": "Tablet", @@ -986,13 +989,13 @@ "core.user.country": "Negara", "core.user.description": "Keterangan", "core.user.detailsnotavailable": "Rincian pengguna ini tidak tersedia untuk Anda", - "core.user.editingteacher": "Guru", + "core.user.editingteacher": "Pengajar", "core.user.email": "Alamat Email", "core.user.emailagain": "Email (lagi)", "core.user.firstname": "Nama Depan", "core.user.interests": "Minat", "core.user.lastname": "Nama akhir", - "core.user.manager": "Pengelola", + "core.user.manager": "Manajer", "core.user.newpicture": "Foto baru", "core.user.participants": "Peserta", "core.user.phone1": "Telepon", @@ -1000,7 +1003,7 @@ "core.user.roles": "Peran", "core.user.sendemail": "Email", "core.user.student": "Siswa", - "core.user.teacher": "Pengajar non-editing", + "core.user.teacher": "Pengajar yang tidak mengedit", "core.user.webpage": "Halaman Web", "core.userdeleted": "Anggota ini telah dihapus", "core.userdetails": "Detail pengguna", diff --git a/src/assets/lang/it.json b/src/assets/lang/it.json index ec29ad534..f8df51554 100644 --- a/src/assets/lang/it.json +++ b/src/assets/lang/it.json @@ -1,15 +1,36 @@ { + "addon.badges.alignment": "Competenza", "addon.badges.badgedetails": "Dettagli badge", "addon.badges.badges": "Badge", + "addon.badges.bendorsement": "Garanzia di terzi", + "addon.badges.claimcomment": "Commento della garanzia di terzi", "addon.badges.contact": "Contatto", "addon.badges.dateawarded": "Data di rilascio", "addon.badges.expired": "Scaduto", "addon.badges.expirydate": "Data di scadenza", "addon.badges.issuancedetails": "Scadenza badge", "addon.badges.issuerdetails": "Dettagli di chi rilascia il badge", + "addon.badges.issueremail": "Email", "addon.badges.issuername": "Nome di chi rilascia il badge", + "addon.badges.issuerurl": "URL di di chi rilascia il basge", + "addon.badges.language": "Lingua", "addon.badges.nobadges": "Non sono presenti badge.", "addon.badges.recipientdetails": "Dettagli destinatario", + "addon.badges.version": "Versione", + "addon.badges.warnexpired": "(Questo badge è scaduto!)", + "addon.block_activitymodules.pluginname": "Attività", + "addon.block_myoverview.future": "Prossimi", + "addon.block_myoverview.inprogress": "In svolgimento", + "addon.block_myoverview.morecourses": "Altri corsi", + "addon.block_myoverview.nocourses": "Non ci sono corsi", + "addon.block_myoverview.past": "Conclusi", + "addon.block_myoverview.pluginname": "Panoramica corsi", + "addon.block_myoverview.title": "Titolo", + "addon.block_sitemainmenu.pluginname": "Menu principale", + "addon.block_timeline.next30days": "Prossimi 30 giorni", + "addon.block_timeline.next3months": "Prossimi 3 mesi", + "addon.block_timeline.next6months": "Prossimi 6 mesi", + "addon.block_timeline.next7days": "Prossimi 7 giorni", "addon.calendar.calendar": "Calendario", "addon.calendar.calendarevents": "Eventi nel calendario", "addon.calendar.defaultnotificationtime": "Orario di notifica di default", @@ -77,20 +98,20 @@ "addon.coursecompletion.completiondate": "Data di completamento", "addon.coursecompletion.completionmenuitem": "Completamento", "addon.coursecompletion.couldnotloadreport": "Non è stato possibile caricare il report di completamento del corso, per favore riprova più tardi.", - "addon.coursecompletion.coursecompletion": "Completamento del corso", + "addon.coursecompletion.coursecompletion": "Criteri di completamento", "addon.coursecompletion.criteria": "Criteri", "addon.coursecompletion.criteriagroup": "Gruppo di criteri", "addon.coursecompletion.criteriarequiredall": "E' richiesto il soddisfacimento di tutti i criteri elencati", "addon.coursecompletion.criteriarequiredany": "E' richiesto il soddisfacimento di almeno uno dei criteri elencati", - "addon.coursecompletion.inprogress": "In svolgimento", + "addon.coursecompletion.inprogress": "In corso", "addon.coursecompletion.manualselfcompletion": "Conferma personale di completamento", - "addon.coursecompletion.notyetstarted": "Non iniziato", + "addon.coursecompletion.notyetstarted": "Non ancora iniziato", "addon.coursecompletion.pending": "In attesa", - "addon.coursecompletion.required": "Da soddisfare", + "addon.coursecompletion.required": "Obbligatorio", "addon.coursecompletion.requiredcriteria": "Criteri da soddisfare", "addon.coursecompletion.requirement": "Requisito", "addon.coursecompletion.status": "Stato", - "addon.coursecompletion.viewcoursereport": "Visualizza report del corso", + "addon.coursecompletion.viewcoursereport": "Visualizza il report del corso", "addon.files.couldnotloadfiles": "Non è stato possibile caricare l'elenco dei file.", "addon.files.emptyfilelist": "Non ci sono file da visualizzare.", "addon.files.erroruploadnotworking": "Al momento non è possibile caricare file sul sito.", @@ -98,34 +119,70 @@ "addon.files.privatefiles": "File personali", "addon.files.sitefiles": "File del sito", "addon.messageoutput_airnotifier.processorsettingsdesc": "Configura dsipositivi", + "addon.messages.acceptandaddcontact": "Accetta ed aggiungi ai contatti", "addon.messages.addcontact": "Aggiungi contatto", - "addon.messages.blockcontact": "Blocca contatto", - "addon.messages.blockcontactconfirm": "Non riceverai più messaggi da questo contatto.", + "addon.messages.addcontactconfirm": "Sei sicuro di aggiungere {{$a}} ai contatti?", + "addon.messages.addtofavourites": "Contrassegna", + "addon.messages.addtoyourcontacts": "Aggiungi ai contatti", "addon.messages.blocknoncontacts": "Evita messaggi da parte di utenti che non fanno parte dei miei contatti", + "addon.messages.blockuser": "Blocca utente", + "addon.messages.blockuserconfirm": "Sei sicuro di bloccare {{$a}}?", + "addon.messages.contactableprivacy": "Accetta messaggi da:", + "addon.messages.contactableprivacy_coursemember": "I miei contatti e i miei compagni di corso", + "addon.messages.contactableprivacy_onlycontacts": "I miei contatti solamente", + "addon.messages.contactableprivacy_site": "Tutti gli utenti del sito", + "addon.messages.contactblocked": "Contatto bloccato", "addon.messages.contactlistempty": "L'elenco dei contatti è vuoto", "addon.messages.contactname": "Nome del contatto", + "addon.messages.contactrequestsent": "Richiesta di contatto inviata", "addon.messages.contacts": "Contatti", + "addon.messages.decline": "Rifiuta", + "addon.messages.deleteallconfirm": "Sei sicuro di eliminare l'intera conversazione? La conversazione non sarà eliminata per gli altri partecipanti.", + "addon.messages.deleteconversation": "Elimina conversazione", "addon.messages.errordeletemessage": "Si è verificato un errore durante l'eliminazione del messaggio.", "addon.messages.errorwhileretrievingcontacts": "Si è verificato un errore durante la ricezione dei contatti dal server.", "addon.messages.errorwhileretrievingdiscussions": "Si è verificato un errore durante la ricezione delle discussioni dal server.", "addon.messages.errorwhileretrievingmessages": "Si è verificato un errore durante la ricezione dei messaggi dal server.", + "addon.messages.groupconversations": "Gruppo", + "addon.messages.groupinfo": "Informazioni sul gruppo", + "addon.messages.individualconversations": "privato", + "addon.messages.info": "Informazioni", + "addon.messages.isnotinyourcontacts": "{{$a}} non è nei tuoi contatti", "addon.messages.message": "Messaggio", "addon.messages.messagenotsent": "Il messaggio non è stato inviato, per favore riprova più tardi.", "addon.messages.messagepreferences": "Preferenze messaggi", "addon.messages.messages": "Messaggi", "addon.messages.newmessage": "Nuovo messaggio", "addon.messages.newmessages": "Nuovi messaggi", - "addon.messages.nomessages": "Nessun messaggio", + "addon.messages.nocontactrequests": "Nessuna richiesta di contatto", + "addon.messages.nocontactsgetstarted": "Nessun contatto", + "addon.messages.nofavourites": "Nessuna conversazione contrassegnata", + "addon.messages.nogroupconversations": "Nessuna conversazione di gruppo", + "addon.messages.noindividualconversations": "Nessuna conversazione privata", + "addon.messages.nomessagesfound": "Non è stato trovato nessun messaggio", + "addon.messages.noncontacts": "Non tra i contatti", "addon.messages.nousersfound": "Non sono stati trovati utenti", - "addon.messages.removecontact": "Cancella contatti", - "addon.messages.removecontactconfirm": "Il contatto sarà eliminato dalla lista dei contatti.", + "addon.messages.numparticipants": "{{$a}} partecipanti", + "addon.messages.removecontact": "Elimina contatto", + "addon.messages.removecontactconfirm": "Sei sicuro di eliminare {{$a}} dai contatti?", + "addon.messages.removefromfavourites": "Rimuovi contrassegno", + "addon.messages.removefromyourcontacts": "Elimina dai contatti", + "addon.messages.searchcombined": "Cerca persone e messaggi", + "addon.messages.sendcontactrequest": "Invia richiesta di contatto", "addon.messages.type_blocked": "Bloccato", "addon.messages.type_offline": "Offline", "addon.messages.type_online": "Online", "addon.messages.type_search": "Risultati della ricerca", "addon.messages.type_strangers": "Altri", - "addon.messages.unblockcontact": "Sblocca contatto", + "addon.messages.unabletomessage": "Non puoi inviare messaggi a questo utente", + "addon.messages.unblockuser": "Sblocca utente", + "addon.messages.unblockuserconfirm": "Sei sicuro di sbloccare {{$a}}?", + "addon.messages.userwouldliketocontactyou": "{{$a}} desidera entrare in contatto", "addon.messages.warningmessagenotsent": "Non è stato possibile inviare messaggi all'utente {{user}}. {{error}}", + "addon.messages.wouldliketocontactyou": "Desidera entrare in contatto", + "addon.messages.you": "Tu:", + "addon.messages.youhaveblockeduser": "Hai bloccato l'utente in precedenza", + "addon.messages.yourcontactrequestpending": "La richiesta di contatto con {{$a}} è in attesa", "addon.mod_assign.addattempt": "Consenti tentativo ulteriore", "addon.mod_assign.addnewattempt": "Aggiungi tentativo", "addon.mod_assign.addnewattemptfromprevious": "Aggiungi tentativo sulla base della consegna precedente", @@ -157,6 +214,7 @@ "addon.mod_assign.graded": "Valutata", "addon.mod_assign.gradedby": "Valutatore", "addon.mod_assign.gradedon": "Data di valutazione", + "addon.mod_assign.gradelocked": "Questa valutazione è bloccata oppure modificata nel registro del valutatore.", "addon.mod_assign.gradenotsynced": "Valutazione non sincronizzata", "addon.mod_assign.gradeoutof": "Punteggio (su {{$a}})", "addon.mod_assign.gradingstatus": "Stato valutazione", @@ -171,6 +229,7 @@ "addon.mod_assign.markingworkflowstatereadyforrelease": "Pronta per il rilascio", "addon.mod_assign.markingworkflowstatereadyforreview": "Valutazione completata", "addon.mod_assign.markingworkflowstatereleased": "Rilasciata", + "addon.mod_assign.modulenameplural": "Compiti", "addon.mod_assign.multipleteams": "Appartieni a più gruppi", "addon.mod_assign.multipleteams_desc": "Il compito prevede la consegna di gruppo ma tu sei membro di più di un gruppo. Per poter consegnare, devi essere membro di un solo gruppo. Per favore contatta il docente per modificare la tua appartenenza ai gruppi.", "addon.mod_assign.noattempt": "Nessun tentativo", @@ -225,6 +284,7 @@ "addon.mod_assign_submission_file.pluginname": "Consegna file", "addon.mod_assign_submission_onlinetext.pluginname": "Consegne testo online", "addon.mod_book.errorchapter": "Si è verificato un errore durante la lettura di un capitolo del libro", + "addon.mod_book.modulenameplural": "Libri", "addon.mod_chat.beep": "Beep", "addon.mod_chat.currentusers": "Utenti attivi", "addon.mod_chat.enterchat": "Entra nella chat", @@ -237,6 +297,7 @@ "addon.mod_chat.messagebeepsyou": "{{$a}} ti ha richiamato!", "addon.mod_chat.messageenter": "{{$a}} è entrato nella chat", "addon.mod_chat.messageexit": "{{$a}} ha lasciato la chat", + "addon.mod_chat.modulenameplural": "Chat", "addon.mod_chat.mustbeonlinetosendmessages": "Per inviare messaggi devi essere online.", "addon.mod_chat.nomessages": "Non ci sono ancora messaggi", "addon.mod_chat.send": "Invia", @@ -247,6 +308,7 @@ "addon.mod_choice.errorgetchoice": "Si è verificato un errore durante la ricezione di dati della scelta.", "addon.mod_choice.expired": "Spiacente, questa attività è stata chiusa il {{$a}} e non è più disponibile", "addon.mod_choice.full": "(Completo)", + "addon.mod_choice.modulenameplural": "Scelte", "addon.mod_choice.noresultsviewable": "I risultati non sono al momento visualizzabili.", "addon.mod_choice.notopenyet": "Spiacente, questa attività non è disponibile fino al {{$a}}", "addon.mod_choice.numberofuser": "Numero di risposte", @@ -283,8 +345,10 @@ "addon.mod_data.errormustsupplyvalue": "Devi inserire un valore.", "addon.mod_data.expired": "Spiacente, l'attività non è più disponibile poiché è stata chiusa il {{$a}} ", "addon.mod_data.fields": "Campi", + "addon.mod_data.foundrecords": "Record trovati: {{$a.num}}/{{$a.max}} (Reset filtri)", "addon.mod_data.latlongboth": "Devi compilare sia la latitudine sia la longitudine.", "addon.mod_data.menuchoose": "Scegli...", + "addon.mod_data.modulenameplural": "Database", "addon.mod_data.more": "Dettagli", "addon.mod_data.nomatch": "Non è stato trovato nessun record che corrisponda!", "addon.mod_data.norecords": "Nessun record è presente nel Database", @@ -315,6 +379,7 @@ "addon.mod_feedback.feedbackopen": "Apertura", "addon.mod_feedback.mapcourses": "Associa feedback ai corsi", "addon.mod_feedback.mode": "Modalità", + "addon.mod_feedback.modulenameplural": "Feedback", "addon.mod_feedback.next_page": "Pagina successiva", "addon.mod_feedback.non_anonymous": "Il nome del partecipante verrà registrato e visualizzato nelle risposte", "addon.mod_feedback.non_anonymous_entries": "Risposte non anonime ({{$a}})", @@ -335,6 +400,7 @@ "addon.mod_feedback.started": "Aperto", "addon.mod_feedback.this_feedback_is_already_submitted": "Hai già completato questa attività.", "addon.mod_folder.emptyfilelist": "Non ci sono file da visualizzare.", + "addon.mod_folder.modulenameplural": "Cartelle", "addon.mod_forum.addanewdiscussion": "Aggiungi un argomento di discussione", "addon.mod_forum.addanewquestion": "Aggiungi nuova domanda", "addon.mod_forum.addanewtopic": "Aggiungi nuovo argomento", @@ -357,6 +423,7 @@ "addon.mod_forum.modeflatnewestfirst": "Visualizza le repliche in formato lineare, con le più recenti all'inizio", "addon.mod_forum.modeflatoldestfirst": "Visualizza le repliche in formato lineare, con le più vecchie all'inizio", "addon.mod_forum.modenested": "Visualizza le repliche in formato nidificato", + "addon.mod_forum.modulenameplural": "Forum", "addon.mod_forum.numdiscussions": "{{numdiscussions}} discussioni", "addon.mod_forum.numreplies": "{{numreplies}} interventi", "addon.mod_forum.posttoforum": "Invia al forum", @@ -387,8 +454,10 @@ "addon.mod_glossary.fillfields": "Concetto e definizione sono campi obbligatori.", "addon.mod_glossary.fullmatch": "Collega solo le parole intere", "addon.mod_glossary.linking": "Link automatico", + "addon.mod_glossary.modulenameplural": "Glossari", "addon.mod_glossary.noentriesfound": "Non sono state trovate voci.", "addon.mod_imscp.deploymenterror": "Si è verificato un errore nel pacchetto", + "addon.mod_imscp.modulenameplural": "IMS content package", "addon.mod_imscp.showmoduledescription": "Visualizza descrizione", "addon.mod_lesson.answer": "Risposta", "addon.mod_lesson.attempt": "Tentativo: {{$a}}", @@ -430,6 +499,7 @@ "addon.mod_lesson.lowtime": "Tempi peggiori", "addon.mod_lesson.maximumnumberofattemptsreached": "E' stato raggiunto il massimo numero di tentativi - Passaggio alla pagina successiva", "addon.mod_lesson.modattemptsnoteacher": "La revisione funziona solo per gli studenti.", + "addon.mod_lesson.modulenameplural": "Lezioni", "addon.mod_lesson.noanswer": "Non è stata data risposta ad una o più domande. Torna indietro e dai una risposta.", "addon.mod_lesson.nolessonattempts": "Non è stato effettuato nessun tentativo su questa lezione.", "addon.mod_lesson.nolessonattemptsgroup": "La lezione non è stata tentata da {{$a}} membri del gruppo.", @@ -473,7 +543,9 @@ "addon.mod_lti.errorgetlti": "Si è verificato un errore durante la ricezione dei dati del modulo.", "addon.mod_lti.errorinvalidlaunchurl": "l'URL di lancio non è valida.", "addon.mod_lti.launchactivity": "Lancia l'attività", + "addon.mod_lti.modulenameplural": "Tool Esterni", "addon.mod_page.errorwhileloadingthepage": "Si è verificato un errore durante il caricamento della pagina.", + "addon.mod_page.modulenameplural": "Pagine", "addon.mod_quiz.attemptfirst": "Primo tentativo", "addon.mod_quiz.attemptlast": "Ultimo tentativo", "addon.mod_quiz.attemptnumber": "Tentativo", @@ -503,6 +575,7 @@ "addon.mod_quiz.grademethod": "Metodo di valutazione", "addon.mod_quiz.gradesofar": "{{$a.method}}: {{$a.mygrade}} / {{$a.quizgrade}}.", "addon.mod_quiz.marks": "Punteggio", + "addon.mod_quiz.modulenameplural": "Quiz", "addon.mod_quiz.mustbesubmittedby": "Questo tentativo deve essere inviato entro {{$a}}.", "addon.mod_quiz.noquestions": "Ancora non sono state aggiunte domande", "addon.mod_quiz.noreviewattempt": "Non hai il privilegio di rivedere questo tentativo.", @@ -543,6 +616,7 @@ "addon.mod_quiz.yourfinalgradeis": "Il tuo voto finale per questo quiz è {{$a}}", "addon.mod_resource.errorwhileloadingthecontent": "Si è verificato un errore durante il caricamento del contenuto.", "addon.mod_resource.modifieddate": "Modificato il {{$a}}", + "addon.mod_resource.modulenameplural": "File", "addon.mod_resource.openthefile": "Apri il file", "addon.mod_resource.uploadeddate": "Caricato il {{$a}}", "addon.mod_scorm.asset": "Asset", @@ -579,6 +653,7 @@ "addon.mod_scorm.incomplete": "Incompleto", "addon.mod_scorm.lastattempt": "Ultimo tentativo completato", "addon.mod_scorm.mode": "Modalità", + "addon.mod_scorm.modulenameplural": "Pacchetti SCORM", "addon.mod_scorm.newattempt": "Avvia un nuovo tentativo", "addon.mod_scorm.noattemptsallowed": "Numero di tentativi consentito", "addon.mod_scorm.noattemptsmade": "Numero di tentativi che hai effettuato", @@ -598,10 +673,12 @@ "addon.mod_survey.errorgetsurvey": "Si è verificato un errore durante la ricezione di dati del sondaggio.", "addon.mod_survey.ifoundthat": "Realmente", "addon.mod_survey.ipreferthat": "Idealmente", + "addon.mod_survey.modulenameplural": "Sondaggi", "addon.mod_survey.responses": "Risposte", "addon.mod_survey.results": "Risultati", "addon.mod_survey.surveycompletednograph": "Hai completato il sondaggio.", "addon.mod_url.accessurl": "Accedi all'URL", + "addon.mod_url.modulenameplural": "URL", "addon.mod_url.pointingtourl": "URL dove punta la risorsa.", "addon.mod_wiki.cannoteditpage": "Non puoi modificare questa pagina", "addon.mod_wiki.createpage": "Crea pagina", @@ -610,6 +687,7 @@ "addon.mod_wiki.errornowikiavailable": "Il wiki non ha contenuti.", "addon.mod_wiki.gowikihome": "Vai alla prima pagina del wiki", "addon.mod_wiki.map": "Mappa", + "addon.mod_wiki.modulenameplural": "Wiki", "addon.mod_wiki.newpagehdr": "Nuova pagina", "addon.mod_wiki.newpagetitle": "Titolo nuova pagina", "addon.mod_wiki.nocontent": "Questa pagina non ha contenuti", @@ -647,6 +725,7 @@ "addon.mod_workshop.gradinggradecalculated": "Voto calcolato per la valutazione", "addon.mod_workshop.gradinggradeof": "Voto per la valutazione (su {{$a}})", "addon.mod_workshop.gradinggradeover": "Modifica il voto per la valutazione", + "addon.mod_workshop.modulenameplural": "Workshop", "addon.mod_workshop.nogradeyet": "Senza voto", "addon.mod_workshop.notassessed": "Non valutata", "addon.mod_workshop.notoverridden": "Non modificati", @@ -1008,6 +1087,8 @@ "core.accounts": "Profili", "core.add": "Aggiungi", "core.agelocationverification": "Verifica dell'età e della della nazione", + "core.ago": "{{$a}} fa", + "core.all": "Tutti", "core.allparticipants": "Tutti i partecipanti", "core.android": "Android", "core.answer": "Risposta", @@ -1045,7 +1126,7 @@ "core.confirmdeletefile": "Sei sicuro di voler eliminare questo file?", "core.confirmloss": "Sei sicuro? Le modifiche andranno perdute.", "core.confirmopeninbrowser": "Desideri aprirlo nel browser?", - "core.considereddigitalminor": "Sei un minore digitale.", + "core.considereddigitalminor": "Sei troppo giovane per creare un account su questo sito.", "core.content": "Contenuto", "core.contentlinks.chooseaccount": "Seleziona account", "core.continue": "Continua", @@ -1073,7 +1154,6 @@ "core.courses.availablecourses": "Corsi disponibili", "core.courses.categories": "Categorie di corso", "core.courses.confirmselfenrol": "Desideri iscriverti al corso?", - "core.courses.courseoverview": "Panoramica corsi", "core.courses.courses": "Corsi", "core.courses.downloadcourses": "Scarica corsi", "core.courses.enrolme": "Iscrivimi", @@ -1083,21 +1163,14 @@ "core.courses.errorselfenrol": "Si è verificato un errore mentre ti stavi iscrivendo.", "core.courses.filtermycourses": "Filtra i miei corsi", "core.courses.frontpage": "Pagina home", - "core.courses.future": "Prossimi", - "core.courses.inprogress": "In svolgimento", - "core.courses.morecourses": "Altri corsi", "core.courses.mycourses": "I miei corsi", + "core.courses.mymoodle": "Dashboard", "core.courses.nocourses": "Non sono presenti informazioni sui corsi.", - "core.courses.nocoursesfuture": "Non ci sono corsi di prossima attivazione", - "core.courses.nocoursesinprogress": "Non ci sono corsi in svolgimento", - "core.courses.nocoursesoverview": "Nessun corso", - "core.courses.nocoursespast": "Non ci sono corsi conclusi", "core.courses.nocoursesyet": "Questa categoria non ha corsi", "core.courses.nosearchresults": "Nessun risultato", "core.courses.notenroled": "Non sei iscritto in questo corso", "core.courses.notenrollable": "Non puoi iscriverti al corso.", "core.courses.password": "Chiave di iscrizione", - "core.courses.past": "Conclusi", "core.courses.paymentrequired": "L'accesso al corso richiede un pagamento.", "core.courses.paypalaccepted": "Pagamenti Paypal accettati", "core.courses.search": "Cerca", @@ -1105,6 +1178,7 @@ "core.courses.searchcoursesadvice": "Puoi utilizzare la ricerca corsi per accedere come ospite oppure iscriverti nei corsi che lo consentono.", "core.courses.selfenrolment": "Iscrizione spontanea", "core.courses.sendpaymentbutton": "Invia pagamento tramite Paypal", + "core.courses.show": "Visualizza corso", "core.courses.totalcoursesearchresults": "Totale corsi: {{$a}}", "core.currentdevice": "Dispositivo attuale", "core.date": "Data", @@ -1217,6 +1291,7 @@ "core.login.createaccount": "Crea il mio nuovo account", "core.login.createuserandpass": "Scegli username e password", "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.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.", @@ -1228,7 +1303,7 @@ "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", "core.login.invalidaccount": "Verificare le proprie credenziali o chiedere all'amministratore del sito di controllare la configurazione del sito.", - "core.login.invaliddate": "Data non valida", + "core.login.invaliddate": "Data non corretta", "core.login.invalidemail": "Indirizzo email non valido", "core.login.invalidmoodleversion": "Versione di Moodle non valida. La versione minima richiesta è la 2.4:", "core.login.invalidsite": "L'URL del sito non è valida", @@ -1245,6 +1320,7 @@ "core.login.missingfirstname": "Non hai inserito il nome", "core.login.missinglastname": "Non hai inserito il cognome", "core.login.mobileservicesnotenabled": "I servizi mobile non sono abilitati nel sito. Per richiederne l'abilitazione, contattare l'amministrazione del sito.", + "core.login.mustconfirm": "È necessario confermare l'account", "core.login.newaccount": "Nuovo account", "core.login.newsitedescription": "Inserisci l'URL del sito Moodle al quale vuoi collegarti. Da notare che il sito deve essere configurato per funzionare con questa app.", "core.login.notloggedin": "Devi essere autenticato", @@ -1271,7 +1347,7 @@ "core.login.siteurl": "URL del sito", "core.login.siteurlrequired": "L'URL del sito è obbligatoria. Esempio:http://www.yourmoodlesite.abc or https://www.yourmoodlesite.efg", "core.login.startsignup": "Crea un account", - "core.login.supplyinfo": "Ulteriori dettagli", + "core.login.supplyinfo": "Ulteriori informazioni", "core.login.username": "Username", "core.login.usernameoremail": "Inserisci username o indirizzo email", "core.login.usernamerequired": "Username obbligatorio", @@ -1282,8 +1358,6 @@ "core.mainmenu.changesite": "Cambia sito", "core.mainmenu.help": "Aiuto", "core.mainmenu.logout": "Esci", - "core.mainmenu.mycourses": "I miei corsi", - "core.mainmenu.togglemenu": "Attiva/disattiva menu", "core.mainmenu.website": "Sito web", "core.maxsizeandattachments": "Dimensione massima per i file nuovi: {{$a.size}}, numero massimo di allegati: {{$a.attachments}}", "core.min": "min.", @@ -1364,8 +1438,10 @@ "core.question.requiresgrading": "Richiede valutazione", "core.quotausage": "Hai utilizzato {{$a.used}} su un massimo di {{$a.total}}", "core.refresh": "Aggiorna", + "core.remove": "Rimuovi", "core.required": "Obbligatorio", "core.requireduserdatamissing": "Nel profilo di questo utente mancano alcuni dati. Per favore compila i dati mancanti e riprova.
{{$a}}", + "core.resources": "Risorse", "core.restore": "Ripristino", "core.retry": "Riprova", "core.save": "Salva", @@ -1409,7 +1485,7 @@ "core.settings.locationhref": "URL Webview", "core.settings.locked": "Bloccato", "core.settings.loggedin": "Online", - "core.settings.loggedoff": "Non online", + "core.settings.loggedoff": "Offline", "core.settings.navigatorlanguage": "Navigator language", "core.settings.navigatoruseragent": "Navigator userAgent", "core.settings.networkstatus": "Stato della connessione internet", @@ -1440,6 +1516,21 @@ "core.sizemb": "MB", "core.sizetb": "TB", "core.sortby": "Ordina per", + "core.strftimedate": "%d %B %Y", + "core.strftimedatefullshort": "%d/%m/%y", + "core.strftimedateshort": "%d %B", + "core.strftimedatetime": "%d %B %Y, %H:%M", + "core.strftimedatetimeshort": "%d/%m/%Y %H:%M", + "core.strftimedaydate": "%A, %d %B %Y", + "core.strftimedaydatetime": "%A, %d %B %Y, %H:%M", + "core.strftimedayshort": "%A %d %B", + "core.strftimedaytime": "%a, %H:%M", + "core.strftimemonthyear": "%B %Y", + "core.strftimerecent": "%d %b %Y, %H:%M:%S", + "core.strftimerecentfull": "%a, %d %b %Y, %H:%M", + "core.strftimetime": "%H:%M", + "core.strftimetime12": "%I:%M %p", + "core.strftimetime24": "%H:%M", "core.submit": "Invia", "core.success": "Operazione eseguita correttamente", "core.tablet": "Tablet", diff --git a/src/assets/lang/ja.json b/src/assets/lang/ja.json index 266d161ee..1d5e8b03d 100644 --- a/src/assets/lang/ja.json +++ b/src/assets/lang/ja.json @@ -1,15 +1,61 @@ { + "addon.badges.alignment": "コンピテンシー", "addon.badges.badgedetails": "バッジ詳細", "addon.badges.badges": "バッジ", + "addon.badges.bendorsement": "承認", + "addon.badges.claimcomment": "承認コメント", + "addon.badges.claimid": "請求URL", "addon.badges.contact": "連絡先", "addon.badges.dateawarded": "発効日", "addon.badges.expired": "有効期限切れ", "addon.badges.expirydate": "有効期限", + "addon.badges.imageauthoremail": "イメージ作成者メール", + "addon.badges.imageauthorname": "イメージ作成者名", + "addon.badges.imageauthorurl": "イメージ作成者URL", + "addon.badges.imagecaption": "イメージ説明", "addon.badges.issuancedetails": "バッジ有効期限", "addon.badges.issuerdetails": "発行者詳細", + "addon.badges.issueremail": "Eメール", "addon.badges.issuername": "発行者名", + "addon.badges.issuerurl": "発行者URL", + "addon.badges.language": "言語", + "addon.badges.noalignment": "このバッジには指定されたコンピテンシーはありません。", "addon.badges.nobadges": "利用できるバッジはありません。", + "addon.badges.norelated": "このバッジには関連バッジはありません。", "addon.badges.recipientdetails": "取得者詳細", + "addon.badges.relatedbages": "関連バッジ", + "addon.badges.version": "バージョン", + "addon.badges.warnexpired": "(このバッジの有効期限は切れています!)", + "addon.block_activitymodules.pluginname": "活動", + "addon.block_myoverview.all": "すべて", + "addon.block_myoverview.favourites": "星付き", + "addon.block_myoverview.future": "未来", + "addon.block_myoverview.hiddencourses": "非表示", + "addon.block_myoverview.inprogress": "進行中", + "addon.block_myoverview.lastaccessed": "最終アクセス", + "addon.block_myoverview.morecourses": "コースをさらに", + "addon.block_myoverview.nocourses": "コースなし", + "addon.block_myoverview.past": "過去", + "addon.block_myoverview.pluginname": "コース概要", + "addon.block_myoverview.title": "コース名", + "addon.block_recentlyaccessedcourses.nocourses": "最近のコースなし", + "addon.block_recentlyaccessedcourses.pluginname": "最近アクセスされたコース", + "addon.block_recentlyaccesseditems.noitems": "最近のアイテムなし", + "addon.block_recentlyaccesseditems.pluginname": "最近アクセスされたアイテム", + "addon.block_sitemainmenu.pluginname": "メインメニュー", + "addon.block_starredcourses.nocourses": "星付きコースなし", + "addon.block_starredcourses.pluginname": "星付きコース", + "addon.block_timeline.duedate": "期限", + "addon.block_timeline.next30days": "次の30日", + "addon.block_timeline.next3months": "次の3ヶ月", + "addon.block_timeline.next6months": "次の6ヶ月", + "addon.block_timeline.next7days": "次の7日", + "addon.block_timeline.nocoursesinprogress": "進行中のコースはありません。", + "addon.block_timeline.noevents": "直近に期限到来する活動はありません。", + "addon.block_timeline.overdue": "期限切れ", + "addon.block_timeline.pluginname": "タイムライン", + "addon.block_timeline.sortbycourses": "コースで並べ替える", + "addon.block_timeline.sortbydates": "日付で並べ替える", "addon.calendar.calendar": "カレンダー", "addon.calendar.calendarevents": "カレンダーイベント", "addon.calendar.defaultnotificationtime": "デフォルト通知時間", @@ -76,24 +122,24 @@ "addon.competency.xcompetenciesproficientoutofyincourse": "あなたはこのコースに関して {{$a.x}} / {{$a.y}} のコンピテンシーで熟達しています。", "addon.coursecompletion.complete": "完了", "addon.coursecompletion.completecourse": "コースを完了する", - "addon.coursecompletion.completed": "完了しました", - "addon.coursecompletion.completiondate": "完了した日", + "addon.coursecompletion.completed": "完了", + "addon.coursecompletion.completiondate": "完了日時", "addon.coursecompletion.completionmenuitem": "完了", "addon.coursecompletion.couldnotloadreport": "コース完了の読み込みができませんでした。後でもう一度試してください。", "addon.coursecompletion.coursecompletion": "コース完了", "addon.coursecompletion.criteria": "クライテリア", "addon.coursecompletion.criteriagroup": "クライテリアグループ", - "addon.coursecompletion.criteriarequiredall": "以下すべてのクライテリアが必要", - "addon.coursecompletion.criteriarequiredany": "以下のクライテリアいずれかが必要", + "addon.coursecompletion.criteriarequiredall": "下記のクライテリアすべてが必須である", + "addon.coursecompletion.criteriarequiredany": "下記いくつかのクライテリアが必須である", "addon.coursecompletion.inprogress": "進行中", - "addon.coursecompletion.manualselfcompletion": "手動自己完了", - "addon.coursecompletion.notyetstarted": "まだ開始されていない", - "addon.coursecompletion.pending": "保留中", - "addon.coursecompletion.required": "必要な", - "addon.coursecompletion.requiredcriteria": "必要なクライテリア", - "addon.coursecompletion.requirement": "要求", - "addon.coursecompletion.status": "状態", - "addon.coursecompletion.viewcoursereport": "コースレポートを見る", + "addon.coursecompletion.manualselfcompletion": "手動による自己完了", + "addon.coursecompletion.notyetstarted": "未開始", + "addon.coursecompletion.pending": "保留", + "addon.coursecompletion.required": "必須", + "addon.coursecompletion.requiredcriteria": "必須クライテリア", + "addon.coursecompletion.requirement": "前提条件", + "addon.coursecompletion.status": "ステータス", + "addon.coursecompletion.viewcoursereport": "コースレポートを表示する", "addon.files.couldnotloadfiles": "以下のファイルが読み込みできませんでした。", "addon.files.emptyfilelist": "表示するファイルがありません。", "addon.files.erroruploadnotworking": "残念ながら、現在、あなたのサイトにファイルをアップロードすることはできません。", @@ -101,34 +147,75 @@ "addon.files.privatefiles": "プライベートファイル", "addon.files.sitefiles": "サイトファイル", "addon.messageoutput_airnotifier.processorsettingsdesc": "デバイスの設定", + "addon.messages.acceptandaddcontact": "承認およびコンタクトに追加する", "addon.messages.addcontact": "コンタクトに追加する", - "addon.messages.blockcontact": "受信拒否", - "addon.messages.blockcontactconfirm": "この連絡先からのメッセージ受信を停止します。", + "addon.messages.addcontactconfirm": "本当にあなたのコンタクトに {{$a}} を追加してもよろしいですか?", + "addon.messages.addtofavourites": "星を付ける", + "addon.messages.addtoyourcontacts": "コンタクトに追加する", "addon.messages.blocknoncontacts": "不明なユーザをブロックする", + "addon.messages.blockuser": "ユーザをブロックする", + "addon.messages.blockuserconfirm": "本当に {{$a}} をブロックしてもよろしいですか?", + "addon.messages.contactableprivacy": "受け入れるメッセージの送信元:", + "addon.messages.contactableprivacy_coursemember": "マイコンタクトおよびマイコースの誰でも", + "addon.messages.contactableprivacy_onlycontacts": "マイコンタクトのみ", + "addon.messages.contactableprivacy_site": "サイトの誰でも", + "addon.messages.contactblocked": "コンタクトブロック済み", "addon.messages.contactlistempty": "連絡先リストが空", "addon.messages.contactname": "連絡先名称", + "addon.messages.contactrequestsent": "コンタクトリクエストが送信されました。", "addon.messages.contacts": "コンタクト", + "addon.messages.decline": "拒否", + "addon.messages.deleteallconfirm": "本当にこの会話すべてを削除してもよろしいですか? これにより他の会話参加者の会話が削除されるこはありません。", + "addon.messages.deleteconversation": "会話を削除する", "addon.messages.errordeletemessage": "メッセージ消去中にエラーが発生しました。", "addon.messages.errorwhileretrievingcontacts": "サーバから連絡先を取得中にエラーが発生しました。", "addon.messages.errorwhileretrievingdiscussions": "サーバからディスカッションを受信中にエラーが発生しました。", "addon.messages.errorwhileretrievingmessages": "サーバからメッセージを受信中にエラーが発生しました。", + "addon.messages.groupconversations": "グループ", + "addon.messages.groupinfo": "グループ情報", + "addon.messages.individualconversations": "プライベート", + "addon.messages.info": "情報", + "addon.messages.isnotinyourcontacts": "あなたのコンタクトに {{$a}} は登録されていません。", "addon.messages.message": "メッセージ", "addon.messages.messagenotsent": "メッセージは送信されませんでした。後で再び試みてください。", "addon.messages.messagepreferences": "メッセージプリファレンス", "addon.messages.messages": "メッセージ", "addon.messages.newmessage": "新しいメッセージ", "addon.messages.newmessages": "新規メッセージ...", - "addon.messages.nomessages": "メッセージはありません。", + "addon.messages.nocontactrequests": "コンタクトリストはありません。", + "addon.messages.nocontactsgetstarted": "コンタクトなし", + "addon.messages.nofavourites": "星付きの会話はありません。", + "addon.messages.nogroupconversations": "グループ会話なし", + "addon.messages.noindividualconversations": "プライベート会話なし", + "addon.messages.nomessagesfound": "メッセージが見つかりませんでした。", + "addon.messages.noncontacts": "非コンタクト", "addon.messages.nousersfound": "ユーザが見つかりません", + "addon.messages.numparticipants": "{{$a}} 参加者", "addon.messages.removecontact": "コンタクトから削除する", - "addon.messages.removecontactconfirm": "連絡先はあなたの連絡先リストから削除されます。", + "addon.messages.removecontactconfirm": "本当にあなたのコンタクトから {{$a}} を削除してもよろしいですか?", + "addon.messages.removefromfavourites": "星を解除する", + "addon.messages.removefromyourcontacts": "コンタクトから削除する", + "addon.messages.requests": "リクエスト", + "addon.messages.requirecontacttomessage": "あなたがメッセージを送信できるようにするには {{$a}} にコンタクトへの追加をリクエストする必要があります。", + "addon.messages.searchcombined": "人およびメッセージを検索する", + "addon.messages.searchnocontactsfound": "コンタクトが見つかりませんでした。", + "addon.messages.searchnomessagesfound": "メッセージが見つかりませんでした。", + "addon.messages.searchnononcontactsfound": "コンタクトなしが見つかりませんでした。", + "addon.messages.sendcontactrequest": "コンタクトリクエストを送信する", "addon.messages.type_blocked": "ブロックされています", "addon.messages.type_offline": "オフライン", "addon.messages.type_online": "オンライン", "addon.messages.type_search": "結果の検索", "addon.messages.type_strangers": "その他", - "addon.messages.unblockcontact": "コンタクトの拒否を解除する", + "addon.messages.unabletomessage": "あなたはこのユーザにメッセージを送信できません。", + "addon.messages.unblockuser": "ユーザのブロックを解除する", + "addon.messages.unblockuserconfirm": "本当に {{$a}} をブロック解除してもよろしいですか?", + "addon.messages.userwouldliketocontactyou": "{{$a}} があなたにコンタクトしたいようです。", "addon.messages.warningmessagenotsent": "ユーザ {{user}} へのメッセージ送信ができませんでした。 {{error}}", + "addon.messages.wouldliketocontactyou": "あなたへのコンタクト希望", + "addon.messages.you": "あなた:", + "addon.messages.youhaveblockeduser": "あなたは過去にこのユーザをブロックしたことがあります。", + "addon.messages.yourcontactrequestpending": "あなたのコンタクトリクエストは {{$a}} により保留されています。", "addon.mod_assign.acceptsubmissionstatement": "提出時宣誓文を受諾してください。", "addon.mod_assign.addattempt": "別の提出を許可する", "addon.mod_assign.addnewattempt": "新しい提出を追加する", @@ -165,7 +252,9 @@ "addon.mod_assign.grade": "評定", "addon.mod_assign.graded": "評定済み", "addon.mod_assign.gradedby": "評定者", + "addon.mod_assign.gradedfollowupsubmit": "評定済み - フォローアップ提出受信済み", "addon.mod_assign.gradedon": "評定日時", + "addon.mod_assign.gradelocked": "この評点はロックされているか評定表内で上書きされています。", "addon.mod_assign.gradenotsynced": "評定が同期できませんでした", "addon.mod_assign.gradeoutof": "{{$a}} 点中の評点", "addon.mod_assign.gradingstatus": "評定ステータス", @@ -180,6 +269,7 @@ "addon.mod_assign.markingworkflowstatereadyforrelease": "リリース準備完了", "addon.mod_assign.markingworkflowstatereadyforreview": "採点完了", "addon.mod_assign.markingworkflowstatereleased": "リリース済み", + "addon.mod_assign.modulenameplural": "課題", "addon.mod_assign.multipleteams": "2つ以上のグループのメンバー", "addon.mod_assign.multipleteams_desc": "グループで課題を提出する必要があります。あなたは2つ以上のグループのメンバーです。提出できるためにはあなたは1つのみのグループのメンバーである必要があります。あなたのグループメンバーシップを変更するには教師にご連絡ください。", "addon.mod_assign.noattempt": "未提出", @@ -234,6 +324,7 @@ "addon.mod_assign_submission_file.pluginname": "ファイル提出", "addon.mod_assign_submission_onlinetext.pluginname": "オンラインテキスト提出", "addon.mod_book.errorchapter": "ブックの章の読み込み中にエラーが発生しました。", + "addon.mod_book.modulenameplural": "ブック", "addon.mod_chat.beep": "ビープ", "addon.mod_chat.currentusers": "現在のユーザ", "addon.mod_chat.enterchat": "ここをクリックしてチャットルームに入室する", @@ -246,6 +337,7 @@ "addon.mod_chat.messagebeepsyou": "{{$a}} があなたにビープしました!", "addon.mod_chat.messageenter": "このチャットに {{$a}} が入室しました。", "addon.mod_chat.messageexit": "このチャットから {{$a}} が退室しました。", + "addon.mod_chat.modulenameplural": "チャット", "addon.mod_chat.mustbeonlinetosendmessages": "メッセージを送信するにはオンラインでなければなりません。", "addon.mod_chat.nomessages": "メッセージがありません。", "addon.mod_chat.send": "送信", @@ -256,6 +348,7 @@ "addon.mod_choice.errorgetchoice": "選択データの取得中にエラーが発生しました。", "addon.mod_choice.expired": "申し訳ございません、この活動は {{$a}} に終了しているため、これ以上利用することはできません。", "addon.mod_choice.full": "(上限到達)", + "addon.mod_choice.modulenameplural": "投票", "addon.mod_choice.noresultsviewable": "現在、投票結果は閲覧できません。", "addon.mod_choice.notopenyet": "申し訳ございません、この活動は {{$a}} まで利用することができません。", "addon.mod_choice.numberofuser": "投票者数", @@ -291,8 +384,10 @@ "addon.mod_data.errormustsupplyvalue": "あなたはここで値を提供する必要があります。", "addon.mod_data.expired": "申し訳ございません、この活動は {{$a}} に終了して利用することはできません。", "addon.mod_data.fields": "フィールド", + "addon.mod_data.foundrecords": "{{$a.num}}/{{$a.max}} 件のレコードが見つかりました (フィルタをリセットする)", "addon.mod_data.latlongboth": "緯度および経度の両方とも必須です。", "addon.mod_data.menuchoose": "選択 ...", + "addon.mod_data.modulenameplural": "データベース", "addon.mod_data.more": "詳細", "addon.mod_data.nomatch": "該当するエントリが見つかりませんでした!", "addon.mod_data.norecords": "データベースにエントリはありません。", @@ -324,6 +419,7 @@ "addon.mod_feedback.feedbackopen": "フィードバック開始日時", "addon.mod_feedback.mapcourses": "フィードバックをコースにマップする", "addon.mod_feedback.mode": "モード", + "addon.mod_feedback.modulenameplural": "フィードバック", "addon.mod_feedback.next_page": "次のページ", "addon.mod_feedback.non_anonymous": "ユーザ名を記録して回答と共に表示する", "addon.mod_feedback.non_anonymous_entries": "非匿名エントリ ({{$a}})", @@ -344,6 +440,7 @@ "addon.mod_feedback.started": "開始済み", "addon.mod_feedback.this_feedback_is_already_submitted": "あなたはすでにこの活動を完了しています。", "addon.mod_folder.emptyfilelist": "表示するファイルがありません。", + "addon.mod_folder.modulenameplural": "フォルダ", "addon.mod_forum.addanewdiscussion": "新しいディスカッショントピックを追加する", "addon.mod_forum.addanewquestion": "新しい質問を追加する", "addon.mod_forum.addanewtopic": "新しいトピックを追加する", @@ -366,6 +463,7 @@ "addon.mod_forum.modeflatnewestfirst": "返信を新しいものからフラット表示する", "addon.mod_forum.modeflatoldestfirst": "返信を古いものからフラット表示する", "addon.mod_forum.modenested": "返信をネスト表示する", + "addon.mod_forum.modulenameplural": "フォーラム", "addon.mod_forum.numdiscussions": "ディスカッション数 {{numdiscussions}}", "addon.mod_forum.numreplies": "返信数 {{numreplies}}", "addon.mod_forum.posttoforum": "フォーラムに投稿する", @@ -401,9 +499,11 @@ "addon.mod_glossary.fillfields": "用語および定義は必須入力フィールドです。", "addon.mod_glossary.fullmatch": "完全一致のみ", "addon.mod_glossary.linking": "オートリンク", + "addon.mod_glossary.modulenameplural": "用語集", "addon.mod_glossary.noentriesfound": "エントリが見つかりませんでした。", "addon.mod_glossary.searchquery": "検索内容", "addon.mod_imscp.deploymenterror": "コンテンツパッケージエラー!", + "addon.mod_imscp.modulenameplural": "IMSコンテンツパッケージ", "addon.mod_imscp.showmoduledescription": "説明の表示", "addon.mod_lesson.answer": "答え", "addon.mod_lesson.attempt": "受験: {{$a}}", @@ -446,6 +546,7 @@ "addon.mod_lesson.lowtime": "最短時間", "addon.mod_lesson.maximumnumberofattemptsreached": "最大受験回数に達しました - 次のページに移動しています。", "addon.mod_lesson.modattemptsnoteacher": "学生レビューは学生にのみ表示されます。", + "addon.mod_lesson.modulenameplural": "レッスン", "addon.mod_lesson.noanswer": "1つまたはそれ以上の問題が解答されていません。戻って解答を送信してください。", "addon.mod_lesson.nolessonattempts": "このレッスンは受験されていません。", "addon.mod_lesson.nolessonattemptsgroup": "このレッスンでは {{$a}} グループメンバーによる受験はありません。", @@ -490,7 +591,9 @@ "addon.mod_lti.errorgetlti": "モジュールデータ取得中にエラーが発生しました。", "addon.mod_lti.errorinvalidlaunchurl": "起動するURLが不正です。", "addon.mod_lti.launchactivity": "アクティビティを起動", + "addon.mod_lti.modulenameplural": "外部ツール", "addon.mod_page.errorwhileloadingthepage": "ページ内容を読み込み中にエラーが発生しました。", + "addon.mod_page.modulenameplural": "ページ", "addon.mod_quiz.attemptfirst": "最初の受験", "addon.mod_quiz.attemptlast": "最新の受験", "addon.mod_quiz.attemptnumber": "受験", @@ -499,7 +602,7 @@ "addon.mod_quiz.cannotsubmitquizdueto": "このクイズの回答は、以下の理由で提出できませんでした:", "addon.mod_quiz.comment": "コメント", "addon.mod_quiz.completedon": "完了日時", - "addon.mod_quiz.confirmclose": "送信した場合、あなたはこれ以上受験の答えを変更できないようになります。", + "addon.mod_quiz.confirmclose": "送信した場合、あなたは今回の受験の解答をこれ以上変更することはできません。", "addon.mod_quiz.confirmcontinueoffline": "この回答は、理由「 {{$a}} 」により、同期できませんでした。もしあなたが別のデバイスで回答を続けていた場合、データが失われている可能性があります。", "addon.mod_quiz.confirmleavequizonerror": "回答の保存中にエラーが発生しました。クイズを終了してもよいですか?", "addon.mod_quiz.confirmstart": "小テストには {{$a}} の時間制限があります。あなたが受験を開始した時点から時間が計測されます。あなたは有効期限前に受験を送信する必要があります。今から開始してもよろしいですか?", @@ -525,6 +628,7 @@ "addon.mod_quiz.grademethod": "評定方法", "addon.mod_quiz.gradesofar": "{{$a.method}}: {{$a.mygrade}} / {{$a.quizgrade}}", "addon.mod_quiz.marks": "得点", + "addon.mod_quiz.modulenameplural": "小テスト", "addon.mod_quiz.mustbesubmittedby": "この受験は {{$a}} までに送信される必要があります。", "addon.mod_quiz.noquestions": "まだ問題が追加されていません。", "addon.mod_quiz.noreviewattempt": "あなたはこの受験のレビューを許可されていません。", @@ -569,6 +673,7 @@ "addon.mod_quiz.yourfinalgradeis": "あなたの小テスト最終評点は {{$a}} です。", "addon.mod_resource.errorwhileloadingthecontent": "内容を読み込み中にエラーが発生しました。", "addon.mod_resource.modifieddate": "修正 {{$a}}", + "addon.mod_resource.modulenameplural": "ファイル", "addon.mod_resource.openthefile": "ファイルを開く", "addon.mod_resource.uploadeddate": "アップロード {{$a}}", "addon.mod_scorm.asset": "アセット", @@ -605,6 +710,7 @@ "addon.mod_scorm.incomplete": "不完全", "addon.mod_scorm.lastattempt": "最新の完了済み受験", "addon.mod_scorm.mode": "モード", + "addon.mod_scorm.modulenameplural": "SCORMパッケージ", "addon.mod_scorm.newattempt": "新しい受験を開始する", "addon.mod_scorm.noattemptsallowed": "許可された受験回数", "addon.mod_scorm.noattemptsmade": "あなたの受験回数", @@ -624,10 +730,12 @@ "addon.mod_survey.errorgetsurvey": "調査データの取得中にエラーが発生しました。", "addon.mod_survey.ifoundthat": "私は次のことを発見しました:", "addon.mod_survey.ipreferthat": "私は次のことが好きです:", + "addon.mod_survey.modulenameplural": "調査", "addon.mod_survey.responses": "回答", "addon.mod_survey.results": "結果", "addon.mod_survey.surveycompletednograph": "あなたはこの調査を完了しました。", "addon.mod_url.accessurl": "URLへのアクセス", + "addon.mod_url.modulenameplural": "URL", "addon.mod_url.pointingtourl": "このリソースを示すURL", "addon.mod_wiki.cannoteditpage": "あなたはこのページを編集できません。", "addon.mod_wiki.createpage": "ページを作成する", @@ -636,6 +744,7 @@ "addon.mod_wiki.errornowikiavailable": "このwikiにはまだ内容がありません。", "addon.mod_wiki.gowikihome": "Wikiのホームへ移動", "addon.mod_wiki.map": "マップ", + "addon.mod_wiki.modulenameplural": "Wiki", "addon.mod_wiki.newpagehdr": "新しいページ", "addon.mod_wiki.newpagetitle": "新しいページタイトル", "addon.mod_wiki.nocontent": "このページにはコンテンツがありません。", @@ -673,6 +782,7 @@ "addon.mod_workshop.gradinggradecalculated": "評価に対する計算済み評点", "addon.mod_workshop.gradinggradeof": "評価に対する評点 (最大 {{$a}})", "addon.mod_workshop.gradinggradeover": "評価に対する評点をオーバーライドする", + "addon.mod_workshop.modulenameplural": "ワークショップ", "addon.mod_workshop.nogradeyet": "未評価", "addon.mod_workshop.notassessed": "未評価", "addon.mod_workshop.notoverridden": "未オーバーライド", @@ -1035,6 +1145,8 @@ "core.accounts": "アカウント", "core.add": "追加", "core.agelocationverification": "年齢および場所の確認", + "core.ago": "{{$a}} 前", + "core.all": "すべて", "core.allparticipants": "すべての参加者", "core.android": "Android", "core.answer": "回答", @@ -1068,7 +1180,7 @@ "core.confirmdeletefile": "本当にこのファイルを削除してもよろしいですか?", "core.confirmloss": "本当ですか? すべての変更が失われます。", "core.confirmopeninbrowser": "これをブラウザで開きますか?", - "core.considereddigitalminor": "あなたはデジタル未成年であると考えられます。", + "core.considereddigitalminor": "あなたはこのサイトでアカウントを作成するには若すぎます。", "core.content": "コンテンツ", "core.contenteditingsynced": "編集中のコンテンツが同期されました。", "core.contentlinks.chooseaccount": "アカウントの選択", @@ -1082,40 +1194,32 @@ "core.course.allsections": "全てのセクション", "core.course.contents": "コンテンツ", "core.course.coursesummary": "コース概要", + "core.course.downloadcourse": "コースをダウンロードする", "core.course.hiddenfromstudents": "学生から非表示", "core.course.hiddenoncoursepage": "利用可、しかしコースページに表示しない", "core.course.overriddennotice": "この活動に関するあなたの評点は手動で調整されました。", "core.course.sections": "セクション", "core.coursedetails": "コース詳細", + "core.courses.addtofavourites": "このコースに星を付ける", "core.courses.allowguests": "このコースにはゲストユーザも入ることができます。", "core.courses.availablecourses": "コース一覧", "core.courses.categories": "コースカテゴリ", - "core.courses.courseoverview": "コース概要", "core.courses.courses": "コース", "core.courses.frontpage": "フロントページ", - "core.courses.future": "未来", - "core.courses.inprogress": "進行中", - "core.courses.morecourses": "コースをさらに", + "core.courses.hidecourse": "非表示にする", "core.courses.mycourses": "マイコース", - "core.courses.next30days": "次の30日", - "core.courses.next7days": "次の7日", + "core.courses.mymoodle": "ダッシュボード", "core.courses.nocourses": "表示するコース情報はありません。", - "core.courses.nocoursesfuture": "未来のコースはありません。", - "core.courses.nocoursesinprogress": "進行中のコースはありません。", - "core.courses.nocoursesoverview": "コースなし", - "core.courses.nocoursespast": "過去のコースはありません。", "core.courses.nocoursesyet": "このカテゴリにコースはありません。", - "core.courses.noevents": "直近に期限到来する活動はありません。", "core.courses.nosearchresults": "該当なし", "core.courses.notenroled": "あなたはこのコースに登録されていません。", - "core.courses.past": "過去", "core.courses.paymentrequired": "このコースへの登録は有料です。", "core.courses.paypalaccepted": "PayPal支払済み", + "core.courses.removefromfavourites": "このコースの星を外す", "core.courses.search": "検索", "core.courses.searchcourses": "コースを検索する", "core.courses.sendpaymentbutton": "PayPalで支払いを送信する", - "core.courses.sortbycourses": "コースで並べ替える", - "core.courses.sortbydates": "日付で並べ替える", + "core.courses.show": "このコースを表示する", "core.currentdevice": "現在のデバイス", "core.datastoredoffline": "送信できなかったため、データはデバイスに保存されました。後で自動的に送信されます。", "core.date": "日付", @@ -1133,7 +1237,7 @@ "core.dfmediumdate": "LLL", "core.dftimedate": "h[:]mm A", "core.digitalminor": "デジタル未成年", - "core.digitalminor_desc": "アカウントを作成するにはあなたの保護者が次の人に連絡するようにしてください。", + "core.digitalminor_desc": "あなたの保護者が次の人に連絡するようにしてください:", "core.discard": "無視", "core.dismiss": "取消", "core.done": "完了", @@ -1157,6 +1261,7 @@ "core.errorsync": "同期中にエラーが発生しました。再度実行してください。", "core.errorsyncblocked": "実行中のプロセスがあったため、この {{$a}} はすぐに同期できませんでした。再度実行してください。問題が継続する場合、アプリを再起動してください。", "core.explanationdigitalminor": "この情報はあなたの年齢がデジタル許可年齢以上であることを確認するため必要です。これは個人が利用条件および自分のデータが合法的に保存および処理されることに同意できる年齢です。", + "core.favourites": "星付き", "core.filename": "ファイル名", "core.filenameexist": "ファイル名がすでに存在しています:{{$a}}", "core.fileuploader.addfiletext": "ファイルを追加する", @@ -1217,6 +1322,8 @@ "core.login.cancel": "キャンセル", "core.login.createaccount": "私の新しいアカウントを作成する", "core.login.createuserandpass": "あなたのユーザ名 およびパスワードを入力してください。", + "core.login.emailconfirmsent": "

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

\n

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

\n

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

", + "core.login.emailconfirmsentsuccess": "確認メールが正常に送信されました。", "core.login.enterthewordsabove": "上記の言葉を入力してください。", "core.login.firsttime": "はじめての方ですか?", "core.login.forgotten": "あなたのユーザ名またはパスワードを忘れましたか?", @@ -1225,6 +1332,7 @@ "core.login.helpmelogin": "

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

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

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

", "core.login.instructions": "説明", "core.login.invalidaccount": "あなたがログインするのに必要な情報を再確認し、サイト管理者にサイトの設定と合致しているか確認するよう依頼してください。", + "core.login.invaliddate": "日付が正しくありません", "core.login.invalidemail": "無効なメールアドレスです。", "core.login.invalidmoodleversion": "Moodleのバージョンが古すぎます。少なくともこれより新しいMoodleである必要があります:", "core.login.invalidsite": "サイトURLが正しくありません。", @@ -1237,6 +1345,7 @@ "core.login.missingfirstname": "名が入力されていません。", "core.login.missinglastname": "姓が入力されていません。", "core.login.mobileservicesnotenabled": "あなたのサイトではモバイルサービスが有効になっていません。モバイルアクセスが必要と思うなら、あなたのMoodleサイト管理者にその相談をしてください。", + "core.login.mustconfirm": "あなたのアカウントを確認してください。", "core.login.newaccount": "新しいアカウント", "core.login.password": "パスワード", "core.login.passwordforgotten": "パスワード喪失", @@ -1248,6 +1357,7 @@ "core.login.policyagreementclick": "サイト使用許諾にリンクする", "core.login.potentialidps": "あなたのアカウントを使用してログインします:", "core.login.profileinvaliddata": "値が有効ではありません。", + "core.login.resendemail": "メールを再送する", "core.login.security_question": "セキュリティ質問", "core.login.selectacountry": "国を選択する", "core.login.siteinmaintenance": "このサイトはメンテナンス中です", @@ -1264,7 +1374,6 @@ "core.mainmenu.appsettings": "アプリ設定", "core.mainmenu.help": "ヘルプ", "core.mainmenu.logout": "ログアウト", - "core.mainmenu.mycourses": "マイコース", "core.mainmenu.website": "ウェブサイト", "core.maxsizeandattachments": "新しいファイルの最大サイズ: {{$a.size}} / 最大添付: {{$a.attachments}}", "core.min": "分", @@ -1348,8 +1457,10 @@ "core.quotausage": "現在、あなたは {{$a.total}} 制限のうち {{$a.used}} を使用しています。", "core.redirectingtosite": "サイトにリダイレクトされます。", "core.refresh": "リフレッシュ", + "core.remove": "削除", "core.required": "必須", "core.requireduserdatamissing": "このユーザは必須のプロフィールデータが欠けています。Moodleでデータを補い、再度開いてください。
{{$a}}", + "core.resources": "リソース", "core.restore": "リストア", "core.retry": "再実行", "core.save": "保存", @@ -1393,6 +1504,21 @@ "core.sizetb": "TB", "core.sorry": "すみません...", "core.sortby": "並べ替え", + "core.strftimedate": "%Y年 %m月 %d日", + "core.strftimedatefullshort": "%y/%m/%d", + "core.strftimedateshort": "%m/%d", + "core.strftimedatetime": "%Y年 %m月 %d日 %H:%M", + "core.strftimedatetimeshort": "%y年 %m月 %d日 %H:%M", + "core.strftimedaydate": "%Y年 %m月 %d日(%A)", + "core.strftimedaydatetime": "%Y年 %m月 %d日(%A) %H:%M", + "core.strftimedayshort": "%Y年 %m月 %d日", + "core.strftimedaytime": "(%a) %H:%M", + "core.strftimemonthyear": "%Y年 %m月", + "core.strftimerecent": "%m月 %d日 %H:%M", + "core.strftimerecentfull": "%Y年 %m月 %d日(%a) %H:%M", + "core.strftimetime": "%H:%M", + "core.strftimetime12": "%I:%M %p", + "core.strftimetime24": "%H:%M", "core.submit": "送信", "core.success": "成功", "core.tablet": "タブレット", @@ -1418,17 +1544,21 @@ "core.user.country": "国", "core.user.description": "説明", "core.user.details": "詳細", + "core.user.editingteacher": "教師", "core.user.email": "メールアドレス", "core.user.emailagain": "メールアドレス (もう一度)", "core.user.firstname": "名", "core.user.interests": "興味のあること", "core.user.lastname": "姓", + "core.user.manager": "マネージャ", "core.user.newpicture": "新しい画像", "core.user.noparticipants": "このコースには参加者が登録されていません。", "core.user.participants": "参加者", "core.user.phone1": "電話", "core.user.phone2": "携帯電話", "core.user.roles": "ロール", + "core.user.student": "学生", + "core.user.teacher": "編集権限のない教師", "core.user.webpage": "ウェブページ", "core.userdeleted": "このユーザのアカウントは削除されました。", "core.userdetails": "ユーザ詳細", diff --git a/src/assets/lang/km.json b/src/assets/lang/km.json index 61f75f855..94efcf7a4 100644 --- a/src/assets/lang/km.json +++ b/src/assets/lang/km.json @@ -10,13 +10,33 @@ "addon.badges.nobadges": "មិនមានផ្លាកសញ្ញាទេ", "addon.badges.recipientdetails": "ព័ត៌មានអំពីអ្នកទទួល", "addon.block_activitymodules.pluginname": "សកម្មភាព", + "addon.block_myoverview.all": "ទាំងអស់", + "addon.block_myoverview.favourites": "បានដាក់ផ្កាយ", "addon.block_myoverview.future": "អនាគត", + "addon.block_myoverview.hiddencourses": "បានលាក់", "addon.block_myoverview.inprogress": "កំពុងដំណើរការ", + "addon.block_myoverview.lastaccessed": "បានចូលដំណើរការចុងក្រោយ", "addon.block_myoverview.morecourses": "វគ្គសិក្សាច្រើនទៀត", "addon.block_myoverview.nocourses": "មិនមានវគ្គសិក្សាទេ", "addon.block_myoverview.past": "អតីតកាល", "addon.block_myoverview.pluginname": "ទិដ្ឋភាពទូទៅនៃវគ្គសិក្សា", + "addon.block_myoverview.title": "ឈ្មោះវគ្គសិក្សា", + "addon.block_recentlyaccessedcourses.nocourses": "មិនមានវគ្គសិក្សាថ្មីៗទេ", + "addon.block_recentlyaccessedcourses.pluginname": "វគ្គសិក្សាដែលទើបតែបានចូលមើលថ្មីៗ", + "addon.block_recentlyaccesseditems.noitems": "មិនមានធាតុថ្មីៗទេ", + "addon.block_recentlyaccesseditems.pluginname": "ធាតុដែលទើបតែបានចូលមើលថ្មីៗ", "addon.block_sitemainmenu.pluginname": "ម៉ឺនុយមេ", + "addon.block_timeline.duedate": "កាលបរិច្ឆេទកំណត់", + "addon.block_timeline.next30days": "30 ថ្ងៃបន្ទាប់", + "addon.block_timeline.next3months": "3 ខែបន្ទាប់", + "addon.block_timeline.next6months": "6 ខែបន្ទាប់", + "addon.block_timeline.next7days": "7 ថ្ងៃបន្ទាប់", + "addon.block_timeline.nocoursesinprogress": "គ្មានវគ្គសិក្សាដែលកំពុងដំណើរការ", + "addon.block_timeline.noevents": "មិនមានសកម្មភាពជិតមកដល់ពេលកំណត់ទេ", + "addon.block_timeline.overdue": "ហួសកំណត់", + "addon.block_timeline.pluginname": "បន្ទាត់កាលសម័យ", + "addon.block_timeline.sortbycourses": "តម្រៀបតាមវគ្គសិក្សា", + "addon.block_timeline.sortbydates": "តម្រៀបតាមកាលបរិច្ឆេទ", "addon.calendar.calendar": "ប្រតិទិន", "addon.calendar.calendarevents": "ព្រឹត្តិការណ៍ក្នុងប្រតិទិន", "addon.calendar.defaultnotificationtime": "ពេលវេលាជូនដំណឹងតាមលំនាំដើម", @@ -38,7 +58,7 @@ "addon.competency.competenciesmostoftennotproficientincourse": "សមត្ថភាពដែលមិនស្ទាត់ជំនាញជារឿយៗបំផុតក្នុងវគ្គសិក្សា", "addon.competency.coursecompetencies": "សមត្ថភាពក្នុងវគ្គសិក្សា", "addon.competency.coursecompetencyratingsarepushedtouserplans": "ការវាយតម្លៃសមត្ថភាពក្នុងវគ្គសិក្សានេះត្រូវបានធ្វើបច្ចុប្បន្នភាពភ្លាមៗនៅក្នុងផែនការសិក្សា។", - "addon.competency.errornocompetenciesfound": "គ្មានសមត្ថភាពត្រូវបានរកឃើញ", + "addon.competency.errornocompetenciesfound": "គ្មានសមត្ថភាពត្រូវបានរកឃើញទេ", "addon.competency.evidence_competencyrule": "លក្ខខណ្ឌសម្រាប់សមត្ថភាពត្រូវបានបំពេញ។", "addon.competency.evidence_coursecompleted": "វគ្គសិក្សា '{{$a}}' បានបញ្ចប់។", "addon.competency.evidence_coursemodulecompleted": "សកម្មភាព '{{$a}}' បានបញ្ចប់។", @@ -79,26 +99,86 @@ "addon.coursecompletion.viewcoursereport": "មើលរបាយការណ៍វគ្គសិក្សា", "addon.files.couldnotloadfiles": "មិនអាចដំណើរការបញ្ជីឯកសារទេ។", "addon.files.emptyfilelist": "មិនមានឯកសារសម្រាប់បង្ហាញទេ។", - "addon.files.erroruploadnotworking": "ជាអកុសល បច្ចុប្បន្នអ្នកមិនអាចផ្ទុកឡើងឯកសារទៅតំបន់បណ្តាញរបស់អ្នកបានទេ។", + "addon.files.erroruploadnotworking": "ជាអកុសល បច្ចុប្បន្នអ្នកមិនអាចផ្ទុកឡើងនូវឯកសារទៅតំបន់បណ្តាញរបស់អ្នកបានទេ។", "addon.files.files": "ឯកសារ", "addon.files.privatefiles": "ឯកសារផ្ទាល់ខ្លួន", "addon.files.sitefiles": "ឯកសារតំបន់បណ្ដាញ", + "addon.messageoutput_airnotifier.processorsettingsdesc": "កំណត់រចនាសម្ព័ន្ធឧបករណ៍", + "addon.messages.acceptandaddcontact": "ព្រមទទួលនិងបន្ថែមទៅកាន់បញ្ជីទំនាក់ទំនង", "addon.messages.addcontact": "បន្ថែមទំនាក់ទំនង", + "addon.messages.addcontactconfirm": "តើអ្នកប្រាកដជាចង់បន្ថែម {{$a}} ទៅកាន់បញ្ជីទំនាក់ទំនងរបស់អ្នកមែនទេ?", + "addon.messages.addtofavourites": "ដាក់ផ្កាយ", "addon.messages.addtoyourcontacts": "បន្ថែមទៅកាន់បញ្ជីទំនាក់ទំនងរបស់អ្នក", "addon.messages.blocknoncontacts": "ទប់ស្កាត់សារថ្មីទាំងអស់ពីមនុស្សដែលមិនមាននៅក្នុងបញ្ជីទំនាក់ទំនងរបស់ខ្ញុំ", + "addon.messages.blockuser": "ប្លុកអ្នកប្រើ", + "addon.messages.blockuserconfirm": "តើអ្នកពិតជាចង់ប្លុក {{$a}} មែនទេ?", + "addon.messages.contactableprivacy": "ទទួលសារពី ៖", + "addon.messages.contactableprivacy_coursemember": "ទំនាក់ទំនងរបស់ខ្ញុំ និងអ្នកដែលនៅក្នុងវគ្គសិក្សារបស់ខ្ញុំ", + "addon.messages.contactableprivacy_onlycontacts": "តែទំនាក់ទំនងរបស់ខ្ញុំប៉ុណ្ណោះ", + "addon.messages.contactableprivacy_site": "គ្រប់គ្នាលើតំបន់បណ្តាញ", "addon.messages.contactblocked": "ទំនាក់ទំនងត្រូវបានទប់ស្កាត់", + "addon.messages.contactlistempty": "បញ្ជីទំនាក់ទំនងគឺទទេ", + "addon.messages.contactname": "ឈ្មោះទំនាក់ទំនង", + "addon.messages.contactrequestsent": "សំណើទំនាក់ទំនងត្រូវបានផ្ញើ", "addon.messages.contacts": "ទំនាក់ទំនង", + "addon.messages.decline": "បដិសេធ", "addon.messages.deleteallconfirm": "តើអ្នកប្រាកដជាចង់លុបការសន្ទនាទាំងស្រុងមែនទេ?", + "addon.messages.deleteconversation": "លុបការសន្ទនា", + "addon.messages.deletemessage": "លុបសារ", + "addon.messages.deletemessageconfirmation": "តើអ្នកប្រាកដថាចង់លុបសារនេះដែរឬទេ? វានឹងត្រូវបានលុបចេញតែពីបញ្ជីសាររបស់អ្នកប៉ុណ្ណោះ ហើយអ្នកប្រើដទៃដែលបានផ្ញើ ឬទទួលសារនេះនឹងនៅតែអាចមើលឃើញវាដដែល។", + "addon.messages.errordeletemessage": "មានកំហុសពេលលុបសារនេះ។", + "addon.messages.errorwhileretrievingcontacts": "មានកំហុសនៅពេលកំពុងទាញយកព័ត៌មានទំនាក់ទំនងពីម៉ាស៊ីនបម្រើ", + "addon.messages.errorwhileretrievingdiscussions": "មានកំហុសនៅពេលកំពុងទាញយកការពិភាក្សាពីម៉ាស៊ីនបម្រើ", + "addon.messages.errorwhileretrievingmessages": "មានកំហុសនៅពេលកំពុងទាញយកសារពីម៉ាស៊ីនបម្រើ", + "addon.messages.errorwhileretrievingusers": "មានកំហុសនៅពេលកំពុងទាញយកអ្នកប្រើពីម៉ាស៊ីនបម្រើ", + "addon.messages.groupconversations": "ក្រុម", + "addon.messages.groupinfo": "ព័ត៌មានក្រុម", + "addon.messages.individualconversations": "ឯកជន", + "addon.messages.info": "ព័ត៌មាន", + "addon.messages.isnotinyourcontacts": "{{$a}} មិនស្ថិតក្នុងបញ្ជីទំនាក់ទំនងរបស់អ្នកទេ", "addon.messages.message": "សារ", + "addon.messages.messagenotsent": "សារនេះមិនត្រូវបានផ្ញើចេញទេ។ សូមព្យាយាមម្ដងទៀតពេលក្រោយ។", "addon.messages.messagepreferences": "ការកំណត់ផ្ទាល់ខ្លួនសម្រាប់សារ", "addon.messages.messages": "សារ", "addon.messages.newmessage": "សារថ្មី", - "addon.messages.nomessages": "មិនមានសារកំពុងរង់ចាំឡើយ", + "addon.messages.newmessages": "សារថ្មី", + "addon.messages.nocontactrequests": "មិនមានសំណើទំនាក់ទំនងទេ", + "addon.messages.nocontactsgetstarted": "គ្មានទំនាក់ទំនង", + "addon.messages.nofavourites": "មិនមានការសន្ទនាដែលបានដាក់ផ្កាយទេ", + "addon.messages.nogroupconversations": "មិនមានការសន្ទនាក្រុមទេ", + "addon.messages.noindividualconversations": "មិនមានការសន្ទនាឯកជនទេ", + "addon.messages.nomessagesfound": "រកមិនឃើញសារ", "addon.messages.noncontacts": "មិននៅក្នុងបញ្ជីទំនាក់ទំនង", + "addon.messages.nousersfound": "រកពុំឃើញអ្នកប្រើប្រាស់ទេ", + "addon.messages.numparticipants": "អ្នកចូលរួម {{$a}} នាក់", "addon.messages.removecontact": "យកទំនាក់ទំនងចេញ", + "addon.messages.removecontactconfirm": "តើអ្នកពិតជាចង់ដក {{$a}} ពីបញ្ជីទំនាក់ទំនងរបស់អ្នកមែនទេ?", + "addon.messages.removefromfavourites": "ដកផ្កាយ", "addon.messages.removefromyourcontacts": "ដកចេញពីបញ្ជីទំនាក់ទំនងរបស់អ្នក", + "addon.messages.requests": "សំណើ", + "addon.messages.requirecontacttomessage": "អ្នកត្រូវស្នើឲ្យ {{$a}} ដាក់អ្នកក្នុងបញ្ជីទំនាក់ទំនងដើម្បីអាចផ្ញើសារទៅគាត់បាន។", "addon.messages.searchcombined": "ស្វែងរកមនុស្ស និងសារ", + "addon.messages.searchnocontactsfound": "រកមិនឃើញទំនាក់ទំនងទេ", + "addon.messages.searchnomessagesfound": "រកមិនឃើញសារទេ", + "addon.messages.searchnononcontactsfound": "រកមិនឃើញអ្នកដែលមិននៅក្នុងបញ្ជីទំនាក់ទំនងទេ", + "addon.messages.sendcontactrequest": "ផ្ញើសំណើទំនាក់ទំនង", + "addon.messages.showdeletemessages": "បង្ហាញសារដែលបានលុប", + "addon.messages.type_blocked": "ត្រូវបានរារាំង", + "addon.messages.type_offline": "ក្រៅបណ្ដាញ", + "addon.messages.type_online": "ក្នុងបណ្ដាញ", + "addon.messages.type_search": "លទ្ធផលនៃការស្វែងរក", + "addon.messages.type_strangers": "ផ្សេងៗ", + "addon.messages.unabletomessage": "អ្នកមិនអាចផ្ញើសារទៅកាន់អ្នកប្រើនេះទេ", + "addon.messages.unblockuser": "ឈប់ប្លុកអ្នកប្រើ", + "addon.messages.unblockuserconfirm": "តើអ្នកពិតជាចង់ឈប់ប្លុក{{$a}}មែនទេ?", + "addon.messages.userwouldliketocontactyou": "{{$a}} ចង់ទាក់ទងអ្នក", + "addon.messages.warningconversationmessagenotsent": "មិនអាចផ្ញើសារចូលក្នុងការសន្ទនា {{conversation}} បានទេ។ {{error}}", + "addon.messages.warningmessagenotsent": "មិនអាចផ្ញើសារទៅអ្នកប្រើប្រាស់ {{user}}។ {{error}}", + "addon.messages.wouldliketocontactyou": "ចង់ទាក់ទងអ្នក", "addon.messages.you": "អ្នកៈ", + "addon.messages.youhaveblockeduser": "អ្នកបានប្លុកអ្នកប្រើនេះពីមុន", + "addon.messages.yourcontactrequestpending": "សំណើទំនាក់ទំនងរបស់អ្នកស្ថិតក្នុងការរង់ចាំជាមួយនឹង {{$a}}", + "addon.mod_assign.acceptsubmissionstatement": "សូមទទួលយកតាមសេចក្តីថ្លែងអំពីការប្រគល់កិច្ចការ។", "addon.mod_assign.addattempt": "អនុញ្ញាតឲ្យប្រគល់កិច្ចការម្តងទៀត", "addon.mod_assign.addnewattempt": "ប្រគល់កិច្ចការម្តងទៀត", "addon.mod_assign.addnewattemptfromprevious": "ប្រគល់កិច្ចការថ្មីមួយយោងតាមកិច្ចការដែលបានប្រគល់ពីមុន", @@ -113,6 +193,9 @@ "addon.mod_assign.attemptreopenmethod_manual": "ដោយផ្ទាល់", "addon.mod_assign.attemptreopenmethod_untilpass": "ដោយស្វ័យប្រវត្តិរហូតដល់ជាប់", "addon.mod_assign.attemptsettings": "ការកំណត់សម្រាប់ការប្រគល់កិច្ចការម្តងទៀត", + "addon.mod_assign.cannoteditduetostatementsubmission": "អ្នកមិនអាចបន្ថែម ឬ កែកិច្ចការនៅក្នុងកម្មវិធីទូរស័ព្ទបានទេ ព្រោះមិនអាចទាញយកសេចក្តីថ្លែងអំពីការប្រគល់កិច្ចការពីបណ្ដាញបាន។", + "addon.mod_assign.cannotgradefromapp": "របៀបដាក់ពិន្ទុមួយចំនួនពុំទាន់អាចប្រើប្រាស់បាននៅលើកម្មវិធីទូរស័ព្ទបានទេ និង មិនអាចកែប្រែបានទៀតផង។", + "addon.mod_assign.cannotsubmitduetostatementsubmission": "អ្នកមិនអាចធ្វើការប្រគល់កិច្ចការតាមរយៈទូរស័ព្ទបានទេ ព្រោះមិនអាចទាញយកសេចក្តីថ្លែងអំពីការប្រគល់កិច្ចការពីបណ្តាញបាន។", "addon.mod_assign.confirmsubmission": "តើអ្នកប្រាកដជាចង់ប្រគល់កិច្ចការរបស់អ្នកសម្រាប់ការពិន្ទុមែនទេ? អ្នកនឹងមិនអាចកែប្រែកិច្ចការរបស់អ្នកទៀតទេ។", "addon.mod_assign.currentattempt": "នេះជាការព្យាយាម {{$a}}។", "addon.mod_assign.currentattemptof": "នេះជាការព្យាយាមទី {{$a.attemptnumber}} (អាចព្យាយាមរហូតដល់ទៅ {{$a.maxattempts}} ដង)។", @@ -124,10 +207,14 @@ "addon.mod_assign.duedatereached": "ពេលដល់កំណត់សម្រាប់កិច្ចការនេះបានផុតទៅហើយ", "addon.mod_assign.editingstatus": "កែប្រែស្ថានភាព", "addon.mod_assign.editsubmission": "កែសម្រួលការដាក់ស្នើរបស់ខ្ញុំ", + "addon.mod_assign.erroreditpluginsnotsupported": "អ្នកមិនអាចបន្ថែម ឬ កែកិច្ចការនៅក្នុងកម្មវិធីទូរស័ព្ទបានទេ ព្រោះកម្មវិធីបន្ថែមមួយចំនួនមិនទាន់អាចប្រើបានទេសម្រាប់ការកែតម្រូវ។", + "addon.mod_assign.errorshowinginformation": "ពុំអាចបង្ហាញព័ត៌មានពីកិច្ចការបានទេ។", + "addon.mod_assign.feedbacknotsupported": "មតិមូលវិចារនេះមិនអាចប្រើបានក្នុងកម្មវិធីទូរស័ព្ទទេ ហើយ អាចមិនមានព័ត៌មានទាំងអស់ទេ ។", "addon.mod_assign.grade": "ពិន្ទុ", "addon.mod_assign.graded": "បានដាក់ពិន្ទុ", "addon.mod_assign.gradedby": "បានដាក់ពិន្ទុដោយ", "addon.mod_assign.gradedon": "បានដាក់ពិន្ទុនៅ", + "addon.mod_assign.gradenotsynced": "ពិន្ទុមិនទាន់បានបញ្ជូន។", "addon.mod_assign.gradeoutof": "ពិន្ទុលើ {{$a}}", "addon.mod_assign.gradingstatus": "ស្ថានភាពនៃការដាក់ពិន្ទុ", "addon.mod_assign.groupsubmissionsettings": "ការកំណត់សម្រាប់ការប្រគល់កិច្ចការជាក្រុម", @@ -148,6 +235,7 @@ "addon.mod_assign.nomoresubmissionsaccepted": "អនុញ្ញាតតែអ្នកដែលទទួលបានការពន្យារពេលប៉ុណ្ណោះ", "addon.mod_assign.noonlinesubmissions": "កិច្ចការនេះមិនទាមទារឲ្យអ្នកប្រគល់អ្វីនៅលើបណ្តាញទេ", "addon.mod_assign.nosubmission": "មិនមានអ្វីត្រូវបានប្រគល់សម្រាប់កិច្ចការនេះទេ", + "addon.mod_assign.notallparticipantsareshown": "អ្នកចូលរួមដែលមិនទាន់បានប្រគល់កិច្ចការ មិនត្រូវបានបង្ហាញទេ។", "addon.mod_assign.noteam": "មិនជាសមាជិកក្នុងក្រុមណាឡើយ", "addon.mod_assign.noteam_desc": "កិច្ចការនេះទាមទារការប្រគល់កិច្ចការជាក្រុម។ អ្នកមិនជាសមាជិកក្នុងក្រុមណាមួយឡើយ។ ដូច្នេះអ្នកមិនអាចប្រគល់កិច្ចការទេ។ សូមទាក់ទងគ្រូរបស់អ្នកដើម្បីប្តូរសមាជិកភាពរបស់អ្នក។", "addon.mod_assign.notgraded": "មិនបានដាក់ពិន្ទុ", @@ -163,6 +251,7 @@ "addon.mod_assign.submission": "ការដាក់ស្នើ", "addon.mod_assign.submissioneditable": "សិស្សអាចកែសម្រួលកិច្ចការនេះ", "addon.mod_assign.submissionnoteditable": "សិស្សមិនអាចកែសម្រួលកិច្ចការនេះទេ", + "addon.mod_assign.submissionnotsupported": "កិច្ចការនេះមិនអាចប្រើបានក្នុងកម្មវិធីទូរស័ព្ទទេ ហើយ អាចមិនមានព័ត៌មានទាំងអស់ទេ ។", "addon.mod_assign.submissionslocked": "កិច្ចការនេះមិនទាមទារការប្រគល់កិច្ចការទេ។", "addon.mod_assign.submissionstatus": "ស្ថានភាពនៃកិច្ចការ", "addon.mod_assign.submissionstatus_": "មិនមានកិច្ចការបានប្រគល់ទេ", @@ -182,7 +271,10 @@ "addon.mod_assign.ungroupedusers": "ការកំណត់ \"ទាមទារឲ្យមានក្រុមសម្រាប់ប្រគល់កិច្ចការ\" ត្រូវបានបើក ហើយអ្នកប្រើខ្លះមិនជាសមាជិកក្នុងក្រុមណាមួយទេ ហើយខ្លះជាសមាជិកក្នុងក្រុមលើសពីមួយ។ ដូច្នេះគេមិនអាចប្រគល់កិច្ចការទេ។", "addon.mod_assign.unlimitedattempts": "មិនកំណត់", "addon.mod_assign.userswhoneedtosubmit": "អ្នកដែលត្រូវប្រគល់កិច្ចការ៖ {{$a}}", + "addon.mod_assign.userwithid": "អ្នកប្រើប្រាស់ដែលមានលេខសម្គាល់ {{id}}", "addon.mod_assign.viewsubmission": "មើលកិច្ចការដែលបានប្រគល់", + "addon.mod_assign.warningsubmissiongrademodified": "ពិន្ទុសម្រាប់កិច្ចការត្រូវបានកែប្រែនៅលើបណ្ដាញ។", + "addon.mod_assign.warningsubmissionmodified": "កិច្ចការរបស់អ្នកប្រើប្រាស់ត្រូវបានកែប្រែនៅលើបណ្ដាញ។", "addon.mod_assign.wordlimit": "កំណត់ចំនួនពាក្យ", "addon.mod_assign_feedback_comments.pluginname": "មតិមូលវិចារណ៍", "addon.mod_assign_feedback_editpdf.pluginname": "ដាក់ចំណារពន្យល់លើ PDF", @@ -196,16 +288,23 @@ "addon.mod_chat.currentusers": "អ្នកប្រើបច្ចុប្បន្ន", "addon.mod_chat.enterchat": "ចុចទីនេះដើម្បីចូលការជជែកកំសាន្ដនៅពេលឥឡូវ", "addon.mod_chat.entermessage": "បញ្ចូលសាររបស់អ្នក", + "addon.mod_chat.errorwhileconnecting": "មានកំហុសអំឡុងពេលភ្ជាប់ទៅកាន់ការជជែក។", + "addon.mod_chat.errorwhilegettingchatdata": "មានកំហុសអំឡុងពេលទាញយកទិន្នន័យការជជែក។", + "addon.mod_chat.errorwhilegettingchatusers": "មានកំហុសអំឡុងពេលទាញយកអ្នកប្រើប្រាស់សារជជែក។", + "addon.mod_chat.errorwhileretrievingmessages": "មានកំហុសអំឡុងពេលទាញយកសារពីម៉ាស៊ីនបម្រើ។", + "addon.mod_chat.errorwhilesendingmessage": "មានកំហុសអំឡុងពេលផ្ញើសារ។", "addon.mod_chat.messagebeepsyou": "{{$a}} ទើបតែបានបន្លឺសំឡេងដល់អ្នក !", "addon.mod_chat.messageenter": "{{$a}} បានចូលការជជែកកំសាន្ដនេះ", "addon.mod_chat.messageexit": "{{$a}} បានចេញពីការជជែកនេះ", "addon.mod_chat.modulenameplural": "ជជែក", + "addon.mod_chat.mustbeonlinetosendmessages": "អ្នកត្រូវតែភ្ជាប់ទៅកាន់បណ្ដាញអ៊ីនធឺណិតដើម្បីផ្ញើសារ។", "addon.mod_chat.nomessages": "គ្មានសារនៅឡើយទេ", "addon.mod_chat.send": "ផ្ញើ", "addon.mod_chat.sessionstart": "សម័យជជែកបន្ទាប់នឹងចាប់ផ្តើមនៅ {{$a.date}}, ({{$a.fromnow}} ពីពេលនេះ)", "addon.mod_chat.talk": "និយាយ", "addon.mod_choice.cannotsubmit": "សូមអភ័យទោស! មានបញ្ហាក្នុងការផ្ញើជម្រើសរបស់អ្នក។ សូមព្យាយាមម្តងទៀត។", "addon.mod_choice.choiceoptions": "ជម្រើសសម្រាប់ជ្រើស", + "addon.mod_choice.errorgetchoice": "មានកំហុសក្នុងការទាញយកទិន្នន័យជម្រើស។", "addon.mod_choice.expired": "សូមអភ័យទោស, សកម្មភាពនេះត្រូវបានបិទនៅ {{$a}} និងមិនមានទៀតទេ", "addon.mod_choice.full": "(ពេញលេញ)", "addon.mod_choice.modulenameplural": "ជម្រើស", @@ -216,7 +315,9 @@ "addon.mod_choice.previewonly": "នេះគ្រាន់តែការមើលទុកជាមុននៃជម្រើសដែលមានសម្រាប់សកម្មភាពនេះប៉ុណ្ណោះ។ អ្នកមិនអាចផ្ញើជម្រើសរបស់អ្នករហូតដល់ {{$a}}។", "addon.mod_choice.removemychoice": "យកជម្រើសរបស់ខ្ញុំចេញ", "addon.mod_choice.responses": "ចម្លើយ", + "addon.mod_choice.responsesresultgraphdescription": "អ្នកប្រើប្រាស់ {{number}}% បានជ្រើសយកជម្រើស: {{text}}។", "addon.mod_choice.responsesresultgraphheader": "ការបង្ហាញក្រាប", + "addon.mod_choice.resultsnotsynced": "ចម្លើយចុងក្រោយរបស់អ្នកត្រូវតែបានបញ្ជូនអោយរួចរាល់មុនពេលដែលវាត្រូវបានដាក់ចូលទៅក្នុងលទ្ធផល។", "addon.mod_choice.savemychoice": "រក្សាទុកជម្រើសរបស់ខ្ញុំ", "addon.mod_choice.userchoosethisoption": "អ្នកដែលបានជ្រើសជម្រើសនេះ", "addon.mod_choice.yourselection": "ជម្រើសរបស់អ្នក", @@ -234,9 +335,12 @@ "addon.mod_data.emptyaddform": "អ្នកមិនបានបំពេញវាលណាមួយឡើយ !", "addon.mod_data.entrieslefttoadd": "អ្នកត្រូវតែបន្ថែម {{$a.entriesleft}} ធាតុបន្ថែមដើម្បីបញ្ចប់សកម្មភាពនេះ", "addon.mod_data.entrieslefttoaddtoview": "អ្នកត្រូវតែបន្ថែម {{$a.entrieslefttoview}} ធាតុបន្ថែម មុនពេលអ្នកអាចមើលធាតុរបស់អ្នកចូលរួមផ្សេងទៀត ។", + "addon.mod_data.errorapproving": "មានកំហុសក្នុងការទទួលយក ឬ ឈប់ទទួលយកទិន្នន័យដែលវាយចូល។", + "addon.mod_data.errordeleting": "មានកំហុសក្នុងការលុបធាតុដែលបានបញ្ចូល។", "addon.mod_data.errormustsupplyvalue": "អ្នកត្រូវតែផ្ដល់ព័ត៌មាននៅទីនេះ ។", "addon.mod_data.expired": "សូមអភ័យទោស! សកម្មភាពនេះបានបិទនៅ {{$a}} ហើយមិនមានទៀតទេ។", "addon.mod_data.fields": "វាល", + "addon.mod_data.foundrecords": "រកឃើញកំណត់ត្រា ៖ {{$a.num}}/{{$a.max}} (កំណត់តម្រងឡើងវិញ)", "addon.mod_data.latlongboth": "ទាមទារទាំងរយៈទទឹង និងរយៈបណ្ដោយ។", "addon.mod_data.menuchoose": "ជ្រើស...", "addon.mod_data.modulenameplural": "មូលដ្ឋានទិន្នន័យ", @@ -261,10 +365,12 @@ "addon.mod_feedback.anonymous": "អនាមិក", "addon.mod_feedback.anonymous_entries": "ធាតុអនាមិក ({{$a}})", "addon.mod_feedback.average": "មធ្យមភាគ", + "addon.mod_feedback.captchaofflinewarning": "សកម្មភាពមូលវិចារដោយមាន CAPTCHA មិនអាចបញ្ចប់នៅក្រៅបណ្តាញ ឬនៅពេលមិនទាន់បានកំណត់រចនាសម្ព័ន្ធ ឬនៅពេលម៉ាស៊ីនបម្រើមានបញ្ហាបានទេ។", "addon.mod_feedback.complete_the_form": "ឆ្លើយសំណួរ...", "addon.mod_feedback.completed_feedbacks": "បានប្រគល់ចម្លើយ", "addon.mod_feedback.continue_the_form": "បន្តឆ្លើយសំណួរ...", "addon.mod_feedback.feedback_is_not_open": "មូលវិចារណ៍មិនត្រូវបានបើកទេ", + "addon.mod_feedback.feedback_submitted_offline": "មូលវិចារនេះត្រូវបានរក្សាទុកដើម្បីប្រគល់ពេលក្រោយ។", "addon.mod_feedback.feedbackclose": "អនុញ្ញាតឲ្យឆ្លើយដល់", "addon.mod_feedback.feedbackopen": "អនុញ្ញាតឲ្យឆ្លើយដល់", "addon.mod_feedback.mapcourses": "ផ្គូផ្គងមូលវិចារណ៍ទៅកាន់វគ្គសិក្សា", @@ -287,6 +393,7 @@ "addon.mod_feedback.show_nonrespondents": "បង្ហាញអ្នកមិនបានឆ្លើយ", "addon.mod_feedback.started": "បានចាប់ផ្តើម", "addon.mod_feedback.this_feedback_is_already_submitted": "អ្នកបានបញ្ចប់សកម្មភាពនេះរួចហើយ។", + "addon.mod_folder.emptyfilelist": "មិនមានឯកសារសម្រាប់បង្ហាញទេ។", "addon.mod_folder.modulenameplural": "ថតឯកសារ", "addon.mod_forum.addanewdiscussion": "បន្ថែមប្រធានបទកិច្ចពិភាក្សាថ្មី", "addon.mod_forum.addanewquestion": "បន្ថែមសំណួរថ្មី", @@ -302,13 +409,21 @@ "addon.mod_forum.edit": "កែសម្រួល", "addon.mod_forum.erroremptymessage": "ខ្លឹមសារប្រកាសមិនអាចទទេឡើយ", "addon.mod_forum.erroremptysubject": "ប្រធានបទប្រកាសមិនអាចទទេឡើយ។", + "addon.mod_forum.errorgetforum": "មានកំហុសក្នុងការទាញយកទិន្នន័យក្នុងវេទិកា។", + "addon.mod_forum.errorgetgroups": "មានកំហុសក្នុងទាញយកការកំណត់ក្រុម។", + "addon.mod_forum.forumnodiscussionsyet": "មិនទាន់មានការពិភាក្សានៅឡើយទេនៅក្នុងវេទិកានេះ។", + "addon.mod_forum.group": "ក្រុម", "addon.mod_forum.message": "សារ", "addon.mod_forum.modeflatnewestfirst": "បង្ហាញការឆ្លើយតបស្មើ ថ្មីៗបង្ហាញមុន", "addon.mod_forum.modeflatoldestfirst": "បង្ហាញការឆ្លើយតបស្មើ ចាស់ៗបង្ហាញមុន", "addon.mod_forum.modenested": "បង្ហាញការឆ្លើយតបជាទម្រង់ក្នុងគ្នា", "addon.mod_forum.modulenameplural": "វេទិកា", + "addon.mod_forum.numdiscussions": "ការពិភាក្សា {{numdiscussions}}", + "addon.mod_forum.numreplies": "ការឆ្លើយតប {{numreplies}}", "addon.mod_forum.posttoforum": "ប្រកាសទៅវេទិកា", "addon.mod_forum.re": "ឆ្លើយតប ៖", + "addon.mod_forum.refreshdiscussions": "ដំណើរការកិច្ចពិភាក្សាសាជាថ្មី", + "addon.mod_forum.refreshposts": "ដំណើរការប្រកាសសាជាថ្មី", "addon.mod_forum.reply": "ឆ្លើយតប", "addon.mod_forum.subject": "ប្រធានបទ", "addon.mod_forum.unread": "មិនបានអាន", @@ -316,18 +431,34 @@ "addon.mod_glossary.addentry": "បន្ថែមធាតុថ្មី", "addon.mod_glossary.aliases": "ពាក្យគន្លឹះ", "addon.mod_glossary.attachment": "ឯកសារភ្ជាប់", + "addon.mod_glossary.browsemode": "ស្វែងរកធាតុ", + "addon.mod_glossary.byalphabet": "តាមលំដាប់អក្ខរក្រម", + "addon.mod_glossary.byauthor": "ចាត់ជាក្រុមតាមអ្នកនិពន្ធ", + "addon.mod_glossary.bycategory": "ចាត់ជាក្រុមតាមប្រភេទ", + "addon.mod_glossary.bynewestfirst": "ថ្មីបំផុតមុន", + "addon.mod_glossary.byrecentlyupdated": "បានកែប្រែថ្មីៗ", + "addon.mod_glossary.bysearch": "ស្វែងរក", + "addon.mod_glossary.cannoteditentry": "មិនអាចកែប្រែធាតុបានទេ", "addon.mod_glossary.casesensitive": "ធាតុនេះគឺប្រកាន់អក្សរតូចធំ", "addon.mod_glossary.categories": "ប្រភេទ", "addon.mod_glossary.concept": "គំនិត", "addon.mod_glossary.definition": "ការកំណត់", + "addon.mod_glossary.entriestobesynced": "ធាតុដែលត្រូវធ្វើសមកាលកម្ម", + "addon.mod_glossary.entrypendingapproval": "ធាតុនេះគឺកំពុងរង់ចាំការយល់ព្រម។", "addon.mod_glossary.entryusedynalink": "ធាតុនេះគួរតែត្រូវបានតភ្ជាប់ដោយស្វ័យប្រវត្តិ", "addon.mod_glossary.errconceptalreadyexists": "មានគំនិតនេះរួចហើយ ។ គ្មានច្បាប់ចម្លងណាដែលបានអនុញ្ញាតនៅក្នុងសទ្ទានុក្រមនោះទេ ។", + "addon.mod_glossary.errorloadingentries": "មានកំហុសមួយបានកើតឡើងនៅពេលបង្ហាញធាតុ។", + "addon.mod_glossary.errorloadingentry": "មានកំហុសមួយបានកើតឡើងនៅពេលបង្ហាញធាតុ។", + "addon.mod_glossary.errorloadingglossary": "មានកំហុសមួយបានកើតឡើងនៅពេលផ្ទុកសន្ទានុក្រម។", "addon.mod_glossary.fillfields": "គំនិត និងការកំណត់គឺជាវាលចាំបាច់ ។", "addon.mod_glossary.fullmatch": "ផ្គូផ្គងតែពាក្យទាំងមូលប៉ុណ្ណោះ", "addon.mod_glossary.linking": "ការតភ្ជាប់ដោយស្វ័យប្រវត្តិ", "addon.mod_glossary.modulenameplural": "សទ្ទានុក្រម", + "addon.mod_glossary.noentriesfound": "រកមិនឃើញធាតុទេ។", + "addon.mod_glossary.searchquery": "ពាក្យស្វែងរក", "addon.mod_imscp.deploymenterror": "កំហុសទាក់ទងនឹងកញ្ចប់មាតិកា", "addon.mod_imscp.modulenameplural": "កញ្ចប់មាតិកា IMS", + "addon.mod_imscp.showmoduledescription": "បង្ហាញពីការពិពណ៍នា", "addon.mod_lesson.answer": "ចម្លើយ", "addon.mod_lesson.attempt": "ការឆ្លើយ ៖ {{$a}}", "addon.mod_lesson.attemptheader": "ការសាកល្បង", @@ -351,7 +482,10 @@ "addon.mod_lesson.emptypassword": "ពាក្យសម្ងាត់មិនអាចទទេទេ", "addon.mod_lesson.enterpassword": "សូមបញ្ចូលពាក្យសម្ងាត់ ៖", "addon.mod_lesson.eolstudentoutoftimenoanswers": "អ្នកមិនបានឆ្លើយសំណួរមួយទេ ។ អ្នកបានទទួលពិន្ទុ ០ សម្រាប់មេរៀននេះ ។", + "addon.mod_lesson.errorprefetchrandombranch": "មេរៀននេះមានតំណលោតទៅកាន់ទំព័រមាតិកាចៃដន្យមួយ។ អ្នកមិនអាចសាកល្បងធ្វើមេរៀននេះនៅក្នុងកម្មវិធីទូរស័ព្ទទេ លុះត្រាតែវាត្រូវបានចាប់ផ្តើមដោយប្រើកម្មវិធីរុករកសិន។", + "addon.mod_lesson.errorreviewretakenotlast": "ការសាកល្បងនេះលែងអាចត្រួតពិនិត្យបានទៀតហើយ", "addon.mod_lesson.finish": "បញ្ចប់", + "addon.mod_lesson.finishretakeoffline": "ការព្យាយាមនេះគឺត្រូវបានបញ្ឈប់នៅពេលក្រៅបណ្តាញ។", "addon.mod_lesson.firstwrong": "ជាអកុសល អ្នកមិនអាចទទួលបានពិន្ទុមួយនេះទេ ពីព្រោះចម្លើយរបស់អ្នកមិនត្រឹមត្រូវ ។ តើអ្នកនឹងបន្តការទាយទៀតឬ គ្រាន់តែសម្រាប់ការកំសាន្តនៃការរៀនប៉ុណ្ណោះ (ប៉ុន្តែគ្មានក្រេឌីតពិន្ទុទេ) ?", "addon.mod_lesson.gotoendoflesson": "ទៅកាន់ចុងនៃមេរៀន", "addon.mod_lesson.grade": "ពិន្ទុ", @@ -386,6 +520,9 @@ "addon.mod_lesson.rawgrade": "ពិន្ទុសុទ្ធ", "addon.mod_lesson.reports": "របាយការណ៍", "addon.mod_lesson.response": "ចម្លើយ", + "addon.mod_lesson.retakefinishedinsync": "ការព្យាយាមនៅក្រៅបណ្តាញគឺត្រូវបានធ្វើសមកាលកម្ម។ តើអ្នកចង់ពីនិត្យមើលវាឡើងវិញដែរឬទេ?", + "addon.mod_lesson.retakelabelfull": "{{retake}}: {{grade}} {{timestart}} ({{duration}})", + "addon.mod_lesson.retakelabelshort": "{{retake}}: {{grade}} {{timestart}}", "addon.mod_lesson.review": "ពិនិត្យឡើងវិញ", "addon.mod_lesson.reviewlesson": "ពិនិត្យមេរៀនឡើងវិញ", "addon.mod_lesson.reviewquestionback": "បាទ/ចាស ខ្ញុំចង់ព្យាយាមម្ដងទៀត", @@ -400,26 +537,44 @@ "addon.mod_lesson.timeremaining": "ពេលវេលានៅសល់", "addon.mod_lesson.timetaken": "ពេលវេលាដែលជ្រើស", "addon.mod_lesson.unseenpageinbranch": "សំណួរដែលមិនឃើញនៅក្នុងមែកធាង", + "addon.mod_lesson.warningretakefinished": "ការព្យាយាមគឺបានបញ្ចប់នៅក្នុងគេហទំព័រ", "addon.mod_lesson.welldone": "ធ្វើបានល្អណាស់ !", "addon.mod_lesson.youhaveseen": "អ្នកបានឃើញទំព័រមេរៀននេះច្រើនរួចហើយ ។
តើអ្នកចង់ចាប់ផ្ដើមនៅទំព័រចុងក្រោយដែលអ្នកបានឃើញឬ ?", "addon.mod_lesson.youranswer": "ចម្លើយរបស់អ្នក", "addon.mod_lesson.yourcurrentgradeisoutof": "ពិន្ទុបច្ចុប្បន្នរបស់អ្នកគឺ {{$a.grade}} នៃ {{$a.total}}", "addon.mod_lesson.youshouldview": "អ្នកគួរតែឆ្លើយយ៉ាងហោច ៖ {{$a}}", + "addon.mod_lti.errorgetlti": "មានកំហុសក្នុងការទាញយកទិន្នន័យម៉ូឌុល", + "addon.mod_lti.errorinvalidlaunchurl": "URL នេះមិនត្រឹមត្រូវ", + "addon.mod_lti.launchactivity": "បង្ហាញសកម្មភាព", "addon.mod_lti.modulenameplural": "ឧបករណ៍ខាងក្រៅ", + "addon.mod_page.errorwhileloadingthepage": "មានកំហុសក្នុងការទាញយកខ្លឹមសារមកលើទំព័រនេះ", "addon.mod_page.modulenameplural": "ទំព័រ", "addon.mod_quiz.attemptfirst": "ការឆ្លើយលើកទីមួយ", "addon.mod_quiz.attemptlast": "ការឆ្លើយចុងក្រោយ", "addon.mod_quiz.attemptnumber": "ការសាកល្បង", "addon.mod_quiz.attemptquiznow": "ឆ្លើយកម្រងសំណួរឥឡូវ", "addon.mod_quiz.attemptstate": "សភាព", + "addon.mod_quiz.cannotsubmitquizdueto": "ការឆ្លើយសំណួរនេះមិនអាចបញ្ជូនទៅដោយហេតុផលខាងក្រោម៖", "addon.mod_quiz.comment": "មតិយោបល់", "addon.mod_quiz.completedon": "បានបញ្ចប់នៅ", "addon.mod_quiz.confirmclose": "អ្នករៀបនឹងបញ្ចប់ការឆ្លើយនេះហើយ ។ នៅពេលដែលអ្នកបញ្ចប់ការឆ្លើយ អ្នកនឹងមិនអាចផ្លាស់ប្ដូរចម្លើយរបស់អ្នកបានទេ ។", + "addon.mod_quiz.confirmcontinueoffline": "ការឆ្លើយនេះមិនត្រូវបានធ្វើសមកាលកម្ម តាំងពី {{$a}} ។ ប្រសិនជាអ្នកបានបន្តឆ្លើយនៅក្នុងឧបករណ៏ផ្សេងទៀត នោះ អ្នកប្រហែលនឹងបាត់បង់ទិន្នន័យនោះ។", + "addon.mod_quiz.confirmleavequizonerror": "មានកំហុសមួយអំឡុងពេលអ្នកកំពុងរក្សាទុកចម្លើយ ។ តើអ្នកប្រាកដឬថាអ្នកចង់ចេញពីសំណួរនេះឬទេ?", "addon.mod_quiz.connectionerror": "បានដាច់តំណភ្ជាប់បណ្តាញ។ (ការរក្សាទុកដោយស្វ័យប្រវត្តិបានបរាជ័យ)។\n\nចូរកត់ចំណាំនូវចម្លើយដែលបានបញ្ចូលក្នុងទំព័រនេះកាលពីពេលពីរ បីនាទីមុន។ បន្ទាប់ព្យាយាមតភ្ជាប់ម្តងទៀត។\n\nខណៈដែលការតភ្ជាប់បណ្តាញបានត្រឡប់មកវិញ នោះចម្លើយរបស់អ្នកគួរតែត្រូវបានរក្សាទុក ហើយសារនេះនឹងបាត់។", "addon.mod_quiz.continueattemptquiz": "បន្តការឆ្លើយចុងក្រោយ", "addon.mod_quiz.continuepreview": "បន្តការមើលជាមុនចុងក្រោយ", + "addon.mod_quiz.errorbehaviournotsupported": "សំណួរនេះគឺមិនអាចឆ្លើយបាននៅក្នុងកម្មវិធីនេះទេ ព្រោះតែសំណួរបែបនេះគឺមិនត្រូវបានគាំទ្រដោយកម្មវិធី", + "addon.mod_quiz.errordownloading": "មានកំហុសក្នុងការ ទាញយកទិន្នន័យដែលត្រូវការ", + "addon.mod_quiz.errorgetattempt": "មានកំហុស ក្នុងការទាញយកទិន្នន័យដែលឆ្លើយ", + "addon.mod_quiz.errorgetquestions": "មានកំហុស ក្នុងការទាញយកសំណួរ", + "addon.mod_quiz.errorgetquiz": "មានកំហុស ក្នុងការទាញយកទិន្នន័យសំណួរ", + "addon.mod_quiz.errorparsequestions": "មានកំហុសមួយអំឡុងពេលអ្នកកំពុងអានសំណួរ។ សូម ឆ្លើយសំណួរនេះនៅក្នុងកម្មវិធីរុកវិញ", + "addon.mod_quiz.errorquestionsnotsupported": "សំណួរនេះគឺមិនអាចឆ្លើយបាននៅក្នុងកម្មវិធីនេះទេ ព្រោះតែមានសំណួរដែលមិនត្រូវបានគាំទ្រដោយកម្មវិធី", + "addon.mod_quiz.errorrulesnotsupported": "សំណួរនេះគឺមិនអាចឆ្លើយបាននៅក្នុង កម្មវិធីនេះទេ ព្រោះតែសិទ្ធិក្នុងការប្រើប្រាស់នេះ មិនត្រូវបានគាំទ្រដោយកម្មវិធី។", + "addon.mod_quiz.errorsaveattempt": "មានកំហុស អំឡុងពេលរក្សាទុកទិន្នន័យនៃចម្លើយ", "addon.mod_quiz.feedback": "មតិយោបល់", "addon.mod_quiz.finishattemptdots": "បញ្ចប់ការសាកល្បង...", + "addon.mod_quiz.finishnotsynced": "បានបញ្ចប់ប៉ុន្តែមិនបានធ្វើសមកាលកម្មទេ", "addon.mod_quiz.grade": "ដាក់ពិន្ទុ", "addon.mod_quiz.gradeaverage": "ពិន្ទុមធ្យម", "addon.mod_quiz.gradehighest": "ពិន្ទុខ្ពស់បំផុត", @@ -431,6 +586,7 @@ "addon.mod_quiz.noquestions": "គ្មានសំណួរត្រូវបានបន្ថែមនៅឡើយទេ", "addon.mod_quiz.noreviewattempt": "អ្នកមិនត្រូវបានអនុញ្ញាតឲ្យពិនិត្យការសាកល្បងនេះឡើងវិញឡើយ។", "addon.mod_quiz.notyetgraded": "មិនទាន់បានដាក់ពិន្ទុ", + "addon.mod_quiz.opentoc": "បើកម៉ឺនុយរុករកដែលលេចចេញមក", "addon.mod_quiz.outof": "{{$a.grade}} លើពិន្ទុអតិបរមានៃ {{$a.maxgrade}}", "addon.mod_quiz.outofpercent": "{{$a.grade}} លើពិន្ទុអតិបរមានៃ {{$a.maxgrade}} ({{$a.percent}}%)", "addon.mod_quiz.outofshort": "{{$a.grade}}/{{$a.maxgrade}}", @@ -463,9 +619,14 @@ "addon.mod_quiz.summaryofattempts": "សង្ខេបនៃចម្លើយមុនរបស់អ្នក", "addon.mod_quiz.timeleft": "ពេលវេលានៅសល់", "addon.mod_quiz.timetaken": "ពេលវេលាចំណាយ", + "addon.mod_quiz.warningattemptfinished": "កាឆ្លើយក្រៅបណ្ដាញត្រូវបានបោះបង់ចោលនៅពេលវា បានបញ្ចប់នៅលើគេហទំព័រ ឬមិនត្រូវបានរកឃើញ", + "addon.mod_quiz.warningdatadiscarded": "ចម្លើយក្រៅបណ្តាញមួយចំនួនត្រូវបានបោះបង់ដោយសារតែសំណួរត្រូវបានកែប្រែលើបណ្តាញ", + "addon.mod_quiz.warningdatadiscardedfromfinished": "ការឆ្លើយមិនទាន់បញ្ចប់ដោយសារតែចម្លើយក្រៅបណ្ដាញមួយចំនួនត្រូវបានបោះបង់។ សូមពិនិត្យឡើងវិញនូវចម្លើយរបស់អ្នកហើយស្នើសុំឡើងវិញ", "addon.mod_quiz.yourfinalgradeis": "ពិន្ទុចុងក្រោយរបស់អ្នកសម្រាប់កម្រងសំណួរនេះគឺ {{$a}} ។", + "addon.mod_resource.errorwhileloadingthecontent": "មានកំហុសនៅអំឡុងពេលទាញយកអត្ថបទ", "addon.mod_resource.modifieddate": "បានកែប្រែ {{$a}}", "addon.mod_resource.modulenameplural": "ធនធាន", + "addon.mod_resource.openthefile": "បើកឯកសារ", "addon.mod_resource.uploadeddate": "បានផ្ទុកឡើង {{$a}}", "addon.mod_scorm.asset": "ធនធាន", "addon.mod_scorm.assetlaunched": "ធនធាន - បានមើល", @@ -474,9 +635,19 @@ "addon.mod_scorm.browse": "មើលជាមុន", "addon.mod_scorm.browsed": "បានរកមើល", "addon.mod_scorm.browsemode": "របៀបមើលជាមុន", + "addon.mod_scorm.cannotcalculategrade": "ពិន្ទុមិនត្រូវបានគណនា", "addon.mod_scorm.completed": "បានបញ្ចប់", "addon.mod_scorm.contents": "មាតិកា", + "addon.mod_scorm.dataattemptshown": "ទិន្នន័យនេះគឺបានឆ្លើយរួចនៅ លេខ {{number}}", "addon.mod_scorm.enter": "ចូល", + "addon.mod_scorm.errorcreateofflineattempt": "មានកំហុសអំឡុងពេលបង្កើតការឆ្លើយក្រៅបណ្តាញ។ សូមព្យាយាមម្ដងទៀត", + "addon.mod_scorm.errordownloadscorm": "មានកំហុសពេលកំពុងទាញយក SCORM: \"{{name}}\"", + "addon.mod_scorm.errorgetscorm": "មានកំហុសពេលកំពុងទទួលយក SCORM", + "addon.mod_scorm.errorinvalidversion": "សូមអភ័យទោស កម្មវិធីប្រើបានតែ SCORM 1.2", + "addon.mod_scorm.errornotdownloadable": "ការទាញយកកញ្ចប់ SCORM គឺត្រូវបានបិទ សូមទាក់ទងទៅកាន់ អ្នកគ្រប់គ្រង គេហទំព័ររបស់អ្នក", + "addon.mod_scorm.errornovalidsco": "កញ្ចប់ SCORM នេះ មិនអាចមើលឃើញ SCOទេ", + "addon.mod_scorm.errorpackagefile": "សូមអភ័យទោស កម្មវិធី នេះប្រើបានតែ កញ្ចប់ ZIP តែប៉ុណ្ណោះ", + "addon.mod_scorm.errorsyncscorm": "មានកំហុសមួយអំឡុងពេលធ្វើសមកាលកម្ម។ ទិន្ន សូមព្យាយាមម្ដងទៀត", "addon.mod_scorm.failed": "បានបរាជ័យ", "addon.mod_scorm.firstattempt": "ការឆ្លើយលើកទីមួយ", "addon.mod_scorm.gradeaverage": "ពិន្ទុមធ្យម", @@ -493,19 +664,33 @@ "addon.mod_scorm.noattemptsallowed": "ចំនួនលើកដែលអនុញ្ញាតឲ្យឆ្លើយ", "addon.mod_scorm.normal": "ធម្មតា", "addon.mod_scorm.notattempted": "មិនបានឆ្លើយ", + "addon.mod_scorm.offlineattemptnote": "ការឆ្លើយនេះមិនទាន់ត្រូវបានធ្វើសមកាលកម្មទេ", + "addon.mod_scorm.offlineattemptovermax": "ការឆ្លើយនេះមិនអាចផ្ញើចេញទេ ព្រោះការឆ្លើយលើសចំនួន កំណត់", "addon.mod_scorm.organizations": "ការរៀបចំ", "addon.mod_scorm.passed": "បានហុច", "addon.mod_scorm.reviewmode": "របៀបពិនិត្យឡើងវិញ", + "addon.mod_scorm.scormstatusnotdownloaded": "កញ្ចប់ SCORM នេះគឺមិនត្រុវបានទាញយក។ វានឹងទាញយកដោយស្វ័យប្រវត្តិនៅពេលដែលអ្នកបើកវា", + "addon.mod_scorm.scormstatusoutdated": "កញ្ចប់ SCORM គឺត្រូវបានបញ្ចាក់តាំងពីកាទាញយកលើកចុងក្រោយ។ វានឹងទាញយកដោយស្វ័យប្រវត្តនៅពេលដែលអោយបើវា", "addon.mod_scorm.suspended": "បានផ្អាក", + "addon.mod_scorm.warningofflinedatadeleted": "ទិន្នន័យនៃចម្លើយក្រៅបណ្តាញចំនួន {{number}} ត្រូវបានបោះបង់ព្រោះវាមិនអាចរាប់ជាចម្លើយ ថ្មីនោះទេ", + "addon.mod_scorm.warningsynconlineincomplete": "ចម្លើយមួយចំនួនមិនអាចធ្វើសមកាលកម្មនៅលើគេហទំព័រនេះទេពីព្រោះចម្លើយចុងក្រោយមិនទាន់បានបញ្ចប់។ សូមបញ្ចប់ការឆ្លើយនេះជាមុនសិន។", + "addon.mod_survey.cannotsubmitsurvey": "សូមទោស មានបញ្ហាក្នុងបញ្ចូនចម្លើយនៃស្ទង់មតិរបស់អ្នក។ សូមព្យាយាមម្តងទៀត។", + "addon.mod_survey.errorgetsurvey": "មានកំហុសក្នុងការទទួលទិន្នន័យស្ទង់មតិ។", "addon.mod_survey.ifoundthat": "ខ្ញុំយល់ថា", "addon.mod_survey.ipreferthat": "ខ្ញុំចូលចិត្តដូចនោះ", "addon.mod_survey.modulenameplural": "ការស្ទង់មតិ", "addon.mod_survey.responses": "ចម្លើយ", + "addon.mod_survey.results": "លទ្ធផល", "addon.mod_survey.surveycompletednograph": "អ្នកបានបញ្ចប់ការស្ទង់មតិនេះ។", + "addon.mod_url.accessurl": "ចូលប្រើURL", "addon.mod_url.modulenameplural": "តំណ URL", + "addon.mod_url.pointingtourl": "URLដែលធនធានចង្អុលទៅ", "addon.mod_wiki.cannoteditpage": "អ្នកមិនអាចកែសម្រួលទំព័រនេះទេ។", "addon.mod_wiki.createpage": "បង្កើតទំព័រ", "addon.mod_wiki.editingpage": "កែសម្រួលទំព័រ '{{$a}}' នេះ", + "addon.mod_wiki.errorloadingpage": "មានកំហុសមួយបានកើតឡើងនៅពេលបង្ហាញគេហទំព័រ", + "addon.mod_wiki.errornowikiavailable": "វីគីនេះមិនទាន់មានខ្លឹមសារនៅឡើយទេ។", + "addon.mod_wiki.gowikihome": "ទៅទំព័រដំបូងនៃវីគី", "addon.mod_wiki.map": "ផែនទី", "addon.mod_wiki.modulenameplural": "វិគី", "addon.mod_wiki.newpagehdr": "ទំព័រថ្មី", @@ -514,6 +699,10 @@ "addon.mod_wiki.notingroup": "មិននៅក្នុងក្រុម", "addon.mod_wiki.pageexists": "ទំព័រនេះមានរួចហើយ។", "addon.mod_wiki.pagename": "ឈ្មោះទំព័រ", + "addon.mod_wiki.subwiki": "ទំព័ររងរបស់វីគី", + "addon.mod_wiki.titleshouldnotbeempty": "ចំណងជើងត្រូវមាន", + "addon.mod_wiki.viewpage": "មើលទំព័រ", + "addon.mod_wiki.wikipage": "ទំព័រវីគី", "addon.mod_wiki.wrongversionlock": "អ្នកប្រើផ្សេងទៀតបានកែសម្រួលទំព័រនេះខណៈដែលអ្នកកំពុងកែសម្រួល ហើយមាតិការបស់អ្នកហួសសម័យ។", "addon.mod_workshop.alreadygraded": "បានដាក់ពិន្ទុហើយ", "addon.mod_workshop.areainstructauthors": "សេចក្តីណែនាំសម្រាប់ការប្រគល់កិច្ចការ", @@ -521,6 +710,7 @@ "addon.mod_workshop.assess": "វាយតម្លៃ", "addon.mod_workshop.assessmentform": "ទម្រង់បែបបទសម្រាប់ការវាយតម្លៃ", "addon.mod_workshop.assessmentsettings": "ការកំណត់សម្រាប់ការវាយតម្លៃ", + "addon.mod_workshop.assessmentstrategynotsupported": "យុទ្ធសាស្រ្ដវាយតម្លៃ {{$a}} មិនគាំទ្រ", "addon.mod_workshop.assignedassessments": "កិច្ចការដែលបានផ្តល់ឲ្យវាយតម្លៃ", "addon.mod_workshop.assignedassessmentsnone": "អ្នកមិនមានកិច្ចការដែលបានផ្តល់ឲ្យវាយតម្លៃ", "addon.mod_workshop.conclusion": "សេចក្តីសន្និដ្ឋាន", @@ -556,6 +746,7 @@ "addon.mod_workshop.submissiongrade": "ពិន្ទុសម្រាប់កិច្ចការ", "addon.mod_workshop.submissiongradeof": "ពិន្ទុសម្រាប់កិច្ចការ (លើ {{$a}})", "addon.mod_workshop.submissionrequiredcontent": "អ្នកត្រូវបញ្ចូលអត្ថបទ ឬបន្ថែមឯកសារមួយ។", + "addon.mod_workshop.submissionrequiredtitle": "អ្នកត្រូវតែបំពេញចំណងជើង", "addon.mod_workshop.submissionsreport": "របាយការណ៍ប្រគល់កិច្ចការក្នុងសិក្ខាសាលា", "addon.mod_workshop.submissiontitle": "ចំណងជើង", "addon.mod_workshop.switchphase10": "ប្តូរទៅដំណាក់កាលរៀបចំ", @@ -565,6 +756,8 @@ "addon.mod_workshop.switchphase50": "បិទសិក្ខាសាលា", "addon.mod_workshop.userplan": "ឧបករណ៍រៀបចំសិក្ខាសាលា", "addon.mod_workshop.userplancurrentphase": "ដំណាក់កាលបច្ចុប្បន្ន", + "addon.mod_workshop.warningassessmentmodified": "ការដាក់ស្នើត្រូវបានកែប្រែនៅលើគេហទំព័រ", + "addon.mod_workshop.warningsubmissionmodified": "ការវាយតម្លៃត្រូវបានកែប្រែនៅលើគេហទំព័រ", "addon.mod_workshop.weightinfo": "ទម្ងន់៖ {{$a}}", "addon.mod_workshop.yourassessment": "ការវាយតម្លៃរបស់អ្នក", "addon.mod_workshop.yourassessmentfor": "ការវាយតម្លៃរបស់អ្នកសម្រាប់ {{$a}}", @@ -578,8 +771,14 @@ "addon.notes.personalnotes": "ចំណាំផ្ទាល់ខ្លួន", "addon.notes.publishstate": "ស្ថានភាព", "addon.notes.sitenotes": "ចំណាំនៃតំបន់បណ្ដាញ", + "addon.notes.userwithid": "អ្នកប្រើដែលមានលេខសម្គាល់ {{id}}", + "addon.notes.warningnotenotsent": "មិនអាចបន្ថែមការកត់ចំណាំលើវគ្គសិក្សា {{course}}. {{error}}", + "addon.notifications.errorgetnotifications": "មានកំហុសក្នុងការទទួលការជូនដំណឹង", "addon.notifications.markallread": "ដាក់សម្គាល់ទាំងអស់ថាបានអាន", "addon.notifications.notificationpreferences": "ការកំណត់ផ្ទាល់ខ្លួនសម្រាប់ដំណឹង", + "addon.notifications.notifications": "ការជូនដំណឹង", + "addon.notifications.playsound": "សំឡេង", + "addon.notifications.therearentnotificationsyet": "គ្មានការជូនដំណឹង", "assets.countries.AD": "អង់ដូរ៉ា", "assets.countries.AE": "អារ៉ាប់រួម", "assets.countries.AF": "អាហ្គានីស្ថាន", @@ -876,19 +1075,30 @@ "assets.mimetypes.video": "ឯកសារវីដេអូ ({{$a.EXT}})", "core.accounts": "គណនី", "core.add": "បន្ថែម", + "core.ago": "{{$a}} កន្លងទៅ", "core.all": "ទាំងអស់", "core.allparticipants": "អ្នកចូលរួមទាំងអស់", + "core.android": "ប្រព័ន្ធប្រតិបត្តិការ Android", "core.answer": "ចម្លើយ", "core.answered": "បានឆ្លើយ", "core.areyousure": "តើអ្នកប្រាកដទេ?", "core.back": "ត្រឡប់", "core.cancel": "បោះបង់", + "core.cannotconnect": "មិនអាចតភ្ជាប់៖ ផ្ទៀងផ្ទាត់ថាអ្នកបានវាយ URL ត្រឹមត្រូវហើយគេហទំព័ររបស់អ្នកប្រើលើMoodle 2.4 ឬជំនាន់ថ្មីជាងនេះ។", + "core.cannotdownloadfiles": "ការទាញយកឯកសារត្រូវបានបិទ។ សូមទាក់ទងអ្នកគ្រប់គ្រងគេហទំព័ររបស់អ្នក។", + "core.captureaudio": "ថតសំឡេង", + "core.capturedimage": "រូបភាពដែលបានថត", + "core.captureimage": "ថតរូបភាព", + "core.capturevideo": "ថតវីដេអូ", "core.category": "ប្រភេទ", "core.choose": "ជ្រើស", "core.choosedots": "ជ្រើសរើស...", + "core.clearsearch": "លុបចោលការស្វែងរក", "core.clicktohideshow": "ចុចដើម្បីពន្លាឬវេញ", + "core.clicktoseefull": "ចុចដើម្បីមើលខ្លឹមសារពេញ", "core.comments": "មតិ", "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}})", @@ -899,46 +1109,152 @@ "core.completion-alt-manual-n-override": "មិនបានបញ្ចប់៖ {{$a.modname}} (បានកំណត់ដោយ {{$a.overrideuser}})។ ជ្រើសដើម្បីដាក់សម្គាល់ថាបានបញ្ចប់។", "core.completion-alt-manual-y": "បានបញ្ចប់៖ {{$a}}។ ជ្រើសដើម្បីដាក់សម្គាល់ថាមិនបានបញ្ចប់។", "core.completion-alt-manual-y-override": "បានបញ្ចប់៖ {{$a.modname}} (បានកំណត់ដោយ {{$a.overrideuser}})។ ជ្រើសដើម្បីដាក់សម្គាល់ថាមិនបានបញ្ចប់។", + "core.confirmcanceledit": "តើអ្នកប្រាកដថាចង់ចាកចេញពីទំព័រនេះឬទេ? ការផ្លាស់ប្ដូរទាំងអស់នឹងត្រូវបាត់បង់។", + "core.confirmloss": "តើអ្នកប្រាកដឬទេ? ការផ្លាស់ប្ដូរទាំងអស់នឹងត្រូវបាត់បង់។", + "core.confirmopeninbrowser": "តើអ្នកចង់បើកវានៅក្នុងកម្មវិធីរុករកដែរឬទេ?", "core.content": "មាតិកា", + "core.contenteditingsynced": "ខ្លឹមសារដែលអ្នកកំពុងកែត្រូវបានធ្វើសមកាលកម្ម។", + "core.contentlinks.chooseaccount": "ជ្រើសរើសគណនី", + "core.contentlinks.chooseaccounttoopenlink": "ជ្រើសរើសគណនីដើម្បីបើកតំណនេះ", + "core.contentlinks.confirmurlothersite": "តំណភ្ជាប់នេះជាកម្មសិទ្ធិរបស់គេហទំព័រផ្សេងទៀត។ តើអ្នកចង់បើកវាទេ?", + "core.contentlinks.errornoactions": "មិនអាចរកឃើញសកម្មភាពដើម្បីអនុវត្តជាមួយតំណនេះទេ។", + "core.contentlinks.errornosites": "មិនអាចរកឃើញគេហទំព័រដើម្បីគ្រប់គ្រងជាមួយតំណនេះទេ។", "core.continue": "បន្ត", + "core.copiedtoclipboard": "អត្ថបទត្រូវបានចម្លងទៅclipboard", "core.course": "វគ្គសិក្សា", + "core.course.activitydisabled": "ស្ថាប័នរបស់អ្នកបានបិទដំណើរការសកម្មភាពនេះនៅក្នុងកម្មវិធីទូរសព្ទ", + "core.course.activitynotyetviewableremoteaddon": "ស្ថាប័នរបស់អ្នកបានដំឡើងកម្មវិធីជំនួយដែលមិនទាន់ត្រូវបានគាំទ្រ។", + "core.course.activitynotyetviewablesiteupgradeneeded": "ការដំឡើង Moodle ដោយស្ថាប័ន របស់អ្នកត្រូវការធ្វើបច្ចុប្បន្នភាព។", + "core.course.allsections": "ផ្នែកទាំងអស់", + "core.course.askadmintosupport": "ទាក់ទងអ្នកគ្រប់គ្រងគេហទំព័រនេះហើយប្រាប់ ថាអ្នកចង់ប្រើសកម្មភាពនេះជាមួយកម្មវិធី Moodle Mobile", + "core.course.confirmdeletemodulefiles": "តើអ្នកប្រាកដថាចង់លុបឯកសារទាំងនេះឬ?", + "core.course.confirmdownload": "អ្នកដូចជាចង់ទាញយកឯកសារទំហំ {{size}} នេះ ។ តើអ្នកប្រាកដថាអ្នកចង់បន្តឬទេ?", + "core.course.confirmdownloadunknownsize": "មិនអាចគណនាទំហំនៃការទាញយកបានទេ។ តើអ្នកប្រាកដថាអ្នកចង់បន្តឬទេ?", + "core.course.confirmpartialdownloadsize": "អ្នកដូចជាចង់នឹងទាញយក យ៉ាងហោចណាស់ {{size}} ។ តើអ្នកប្រាកដថាអ្នកចង់បន្តឬទេ?", + "core.course.contents": "ខ្លឹមសារ", + "core.course.couldnotloadsectioncontent": "មិនអាចផ្ទុកមាតិកាផ្នែកបានទេ។ សូមព្យាយាមម្ដងទៀតនៅពេលក្រោយ។", + "core.course.couldnotloadsections": "មិនអាចផ្ទុកផ្នែកមាតិកាបានទេ។ សូមព្យាយាមម្ដងទៀតនៅពេលក្រោយ។", "core.course.coursesummary": "សេចក្ដីសង្ខេបអំពីវគ្គសិក្សា", + "core.course.downloadcourse": "ទាញយកវគ្គសិក្សា", + "core.course.errordownloadingcourse": "កំហុសក្នុងការទាញយកវគ្គសិក្សា", + "core.course.errordownloadingsection": "កំហុសក្នុងការទាញយកផ្នែក", + "core.course.errorgetmodule": "កំហុសក្នុងការទទួលសកម្មភាពទិន្នន័យ", "core.course.hiddenfromstudents": "លាក់ពីសិស្ស", "core.course.hiddenoncoursepage": "អាចចូលមើលបានតែមិនបង្ហាញក្នុងទំព័រវគ្គសិក្សា", + "core.course.manualcompletionnotsynced": "ការបំពេញដោយមិនស្វ័យប្រវត្តិមិនត្រូវបានធ្វើសមកាលកម្ម", + "core.course.nocontentavailable": "មិនមានខ្លឹមសារនៅពេលនេះទេ", "core.course.overriddennotice": "ពិន្ទុចុងក្រោយរបស់អ្នកពីសកម្មភាពនេះ ត្រូវបានលៃតម្រូវដោយដៃ ។", + "core.course.refreshcourse": "ធ្វើឱ្យវគ្គសិក្សាថ្មីឡើងវិញ", "core.course.sections": "ផ្នែក", + "core.course.useactivityonbrowser": "ប្រភេទជ្រៅជាងកម្រិត {{$a}} មិនអាចទាញយកបានទេ", + "core.course.warningmanualcompletionmodified": "ការបំពេញដោយមិនស្វ័យប្រវត្តិត្រូវបានកែតម្រូវនៅលើគេហទំព័រ", + "core.course.warningofflinemanualcompletiondeleted": "ការបំពេញដោយដៃនៅក្រៅបណ្តាញខ្លះនៃវគ្គសិក្សា '{{name}}' បានលុប{{error}}", "core.coursedetails": "ព័ត៌មានលម្អិតអំពីវគ្គសិក្សា", + "core.courses.addtofavourites": "ដាក់ផ្កាយវគ្គសិក្សានេះ", "core.courses.allowguests": "វគ្គសិក្សានេះអនុញ្ញាតឲ្យអ្នកប្រើជាភ្ញៀវចូលបាន", "core.courses.availablecourses": "វគ្គសិក្សាដែលមាន", + "core.courses.cannotretrievemorecategories": "ប្រភេទនៅជ្រៅជាងកម្រិត {{$a}} ដូច្នេះមិនអាចទាញយកបានទេ", "core.courses.categories": "ប្រភេទវគ្គសិក្សា", + "core.courses.confirmselfenrol": "តើអ្នកប្រាកដថាអ្នកចង់ចុះឈ្មោះចូលរៀនវគ្គសិក្សានេះដែរឬទេ?", "core.courses.courses": "វគ្គសិក្សា", + "core.courses.downloadcourses": "ទាញយកវគ្គសិក្សា", + "core.courses.enrolme": "ចុះឈ្មោះខ្ញុំ", + "core.courses.errorloadcategories": "កំហុសមួយបានកើតឡើងនៅពេលផ្ទុកប្រភេទ", + "core.courses.errorloadcourses": "កំហុសមួយបានកើតឡើងនៅពេលផ្ទុកវគ្គសិក្សា", + "core.courses.errorsearching": "កំហុសមួយបានកើតឡើងនៅពេលកំពុងស្វែងរក", + "core.courses.errorselfenrol": "កំហុសមួយបានកើតឡើងនៅពេលចុះឈ្មោះដោយខ្លួនឯង", + "core.courses.filtermycourses": "ត្រងវគ្គសិក្សារបស់ខ្ញុំ", "core.courses.frontpage": "ទំព័រមុខ", + "core.courses.hidecourse": "លាក់ពីទិដ្ឋភាព", "core.courses.mycourses": "វគ្គសិក្សារបស់ខ្ញុំ", "core.courses.mymoodle": "ផ្ទៃតាប្លូ", "core.courses.nocourses": "គ្មានព័ត៌មានវគ្គសិក្សាត្រូវបង្ហាញឡើយ ។", "core.courses.nocoursesyet": "គ្មានវគ្គសិក្សាក្នុងប្រភេទនេះទេ", "core.courses.nosearchresults": "គ្មានលទ្ធផល", "core.courses.notenroled": "អ្នកមិនបានចុះឈ្មោះចូលរៀនក្នុងវគ្គសិក្សានេះទេ។", + "core.courses.notenrollable": "អ្នកមិនអាចចុះឈ្មោះខ្លួនឯងក្នុងវគ្គនេះទេ", + "core.courses.password": "កូនសោចុះឈ្មោះ", "core.courses.paymentrequired": "វគ្គសិក្សានេះទាមទារឲ្យអ្នកបង់ប្រាក់សម្រាប់ធាតុនីមួយៗ ។", "core.courses.paypalaccepted": "បានព្រមទទួលការបង់ប្រាក់តាម PayPal", + "core.courses.removefromfavourites": "ដកផ្កាយពីវគ្គសិក្សានេះ", "core.courses.search": "ស្វែងរក", "core.courses.searchcourses": "ស្វែងរកវគ្គសិក្សា", + "core.courses.searchcoursesadvice": "អ្នកអាចប្រើប៊ូតុងស្វែងរកវគ្គសិក្សាដើម្បី ចូលជាភ្ញៀវឬក៏ចុះឈ្មោះរបស់អ្នកទៅក្នុងវគ្គសិក្សាដែលបានអនុញ្ញាតអោយ។", + "core.courses.selfenrolment": "ការចុះឈ្មោះដោយខ្លួនឯង", "core.courses.sendpaymentbutton": "ផ្ញើការបង់ប្រាក់តាមរយៈ PayPal", + "core.courses.show": "បង្ហាញវគ្គសិក្សានេះ", + "core.courses.totalcoursesearchresults": "វគ្គសិក្សាសរុប៖ {{$a}}", + "core.currentdevice": "ឧបករណ៍បច្ចុប្បន្ន", + "core.datastoredoffline": "ទិន្នន័យត្រូវបានផ្ទុកក្នុងឧបករណ៍ព្រោះតែវាមិនអាចបញ្ជូនបាន។ វានឹងបញ្ជូនដោយស្វ័យប្រវត្តិនៅពេលក្រោយ។", "core.date": "កាលបរិច្ឆេទ", "core.day": "ថ្ងៃ", "core.days": "ថ្ងៃ", "core.decsep": ".", "core.delete": "លុប", + "core.deletedoffline": "ត្រូវបានលុបក្រៅបណ្តាញ", + "core.deleting": "កំពុងលុប", "core.description": "ពិពណ៌នា", + "core.dfdaymonthyear": "MM-DD-YYYY", + "core.dfdayweekmonth": "ddd, D MMM", + "core.dffulldate": "dddd, D MMMM YYYY h[:]mm A", + "core.dflastweekdate": "ddd", + "core.dfmediumdate": "LLL", + "core.dftimedate": "h[:]mm A", + "core.discard": "បោះបង់", + "core.dismiss": "បដិសេធ", "core.done": "ធ្វើរួច", "core.download": "ទាញយក", + "core.downloading": "កំពុងទាញយក", "core.edit": "កែសម្រួល", + "core.emptysplit": "ទំព័រនេះនឹងបង្ហាញទំព័រទទេរបើសិនជាផ្ទាំងខាងឆ្វេងមិនមានអ្វីឬក៏កំពុងដំណើរការ", "core.error": "កំហុស", + "core.errorchangecompletion": "Aកំហុសបានកើតឡើងខណៈពេលដែលកំពុងផ្លាស់ប្តូរស្ថានភាពបញ្ចប់។ សូមព្យាយាមម្តងទៀត", + "core.errordeletefile": "កំហុសក្នុងការលុបឯកសារ។ សូមព្យាយាមម្តងទៀត។", + "core.errordownloading": "កំហុសក្នុងកាទាញយកឯកសារ", + "core.errordownloadingsomefiles": "កំហុសក្នុងកាទាញយកឯកសារ។ ឯកសារខ្លះប្រហែលជាបាត់បង់។", + "core.errorfileexistssamename": "ឯកសារដែលមានឈ្មោះនេះមានរួចហើយ។", + "core.errorinvalidform": "សំណុំបែបបទផ្ទុកទិន្នន័យមិនត្រឹមត្រូវ។ សូមពិនិត្យមើលថាវាលដែលត្រូវការទាំងអស់ត្រូវបានបំពេញហើយទិន្នន័យគឺត្រឹមត្រូវ។", + "core.errorinvalidresponse": "ចម្លើយនេះមិនត្រឹមត្រូវ ។ សូមទាក់ទងអ្នកគ្រប់គ្រងគេហទំព័ររបស់អ្នកបើសិនជានៅតែមានកំហុសកើតឡើង។", + "core.errorloadingcontent": "កំហុសក្នុងការផ្ទុកខ្លឹមសារ។", + "core.errorofflinedisabled": "ការរុករកក្រៅបណ្តាញត្រូវបានបិទនៅក្នុងគេហទំព័ររបស់អ្នក។ អ្នកត្រូវភ្ជាប់ទៅអ៊ិនធឺណេតដើម្បីប្រើប្រាស់កម្មវិធីនេះ។", + "core.erroropenfilenoapp": "កំហុសក្នុងការបើកឯកសារ “មិនមានកម្មវិធីសម្រាប់បើកឯកសារនេះទេ”", + "core.erroropenfilenoextension": "កំហុសក្នុងការបើកឯកសារ “ឯកសារនេះមិនមានផ្នែកបន្ថែម”", + "core.erroropenpopup": "សកម្មភាពនេះកំពុងតែព្យាយាមបើកសារពន្យល់មួយ។ វាមិនត្រូវបានគាំទ្រក្នុងកម្មវិធីទេ។", + "core.errorrenamefile": "កំហុសក្នុងការប្តូរឈ្មោះឯកសារ។ សូមព្យាយាមម្តងទៀត", + "core.errorsync": "កំហុសពេលកំពុងធ្វើសមកាលកម្ម។ សូមព្យាយាមម្តងទៀត", + "core.errorsyncblocked": "{{$a}}នេះមិនអាចធ្វើសមកាលកម្មបានទេនៅពេលនេះដោយសារតែដំណើរការដែលកំពុងកើតឡើង។ សូមព្យាយាមម្តងទៀតនៅពេលក្រោយ។ បើសិនជាបញ្ហានៅតែកើតមានឡើង សូមសាកល្បងចាប់ផ្តើមកម្មវិធីម្តងទៀត។", + "core.favourites": "បានដាក់ផ្កាយ", + "core.filenameexist": "ឈ្មោះនៃឯកសារនេះធ្លាប់មានរួចហើយ: {{$a}}", + "core.fileuploader.addfiletext": "បន្ថែមឯកសារ", + "core.fileuploader.audio": "សំឡេង", + "core.fileuploader.camera": "កាមេរ៉ា", + "core.fileuploader.confirmuploadfile": "អ្នកកំពុងតែនឹងផ្ទុកឡើងនូវឯកសារ{{size}}។ តើអ្នកប្រាកដទេថាចង់បន្តឬទេ?", + "core.fileuploader.confirmuploadunknownsize": "Iទំហំដែលផ្ទុកមិនអាចធ្វើការគណនាបានទេ។ តើអ្នកប្រាកដទេថាចង់បន្តឬទេ?", + "core.fileuploader.errorcapturingaudio": "កំហុសក្នុងការថតសំឡេង", + "core.fileuploader.errorcapturingimage": "កំហុសក្នុងការថតរូបភាព", + "core.fileuploader.errorcapturingvideo": "កំហុសក្នុងការថតវីដេអូ", + "core.fileuploader.errorgettingimagealbum": "កំហុសក្នុងការទាញយករូបភាពពីអាល់ប៊ុម", + "core.fileuploader.errormustbeonlinetoupload": "អ្នកត្រូវស្ថិតនៅក្នុងបណ្តាញដើម្បីដាក់បញ្ចូលឯកសារ", + "core.fileuploader.errornoapp": "អ្នកមិនបានដំឡើងកម្មវិធីដើម្បីដំណើរការសកម្មភាពនេះ។", + "core.fileuploader.errorreadingfile": "កំហុសក្នុងការអានឯកសារ", + "core.fileuploader.errorwhileuploading": "កំហុសពេលកំពុងផ្ទុកឯកសារ", + "core.fileuploader.file": "ឯកសារ", "core.fileuploader.filesofthesetypes": "ប្រភេទឯកសារដែលទទួលយកបាន៖", + "core.fileuploader.fileuploaded": "ឯកសារត្រូវបានផ្ទុកដោយជោគជ័យ", + "core.fileuploader.maxbytesfile": "ឯកសារ{{$a.file}}ធំពេក។ ទំហំអតិប្បរមាដែលអ្នកអាចផ្ទុកបានគឺ{{$a.size}}", "core.fileuploader.more": "ច្រើនទៀត", + "core.fileuploader.photoalbums": "អាល់ប៊ុមរូបភាព", + "core.fileuploader.readingfile": "ឯកសារកំពុងអាន", + "core.fileuploader.readingfileperc": "កំពុងអានឯកសារ: {{$a}}%", + "core.fileuploader.selectafile": "ជ្រើសរើសឯកសារ", + "core.fileuploader.uploadafile": "ផ្ទុកឯកសារឡើង", + "core.fileuploader.uploading": "កំពុងផ្ទុកឡើង", + "core.fileuploader.uploadingperc": "កំពុងផ្ទុកឡើង: {{$a}}%", + "core.fileuploader.video": "វីដេអូ", "core.folder": "ថត", "core.forcepasswordchangenotice": "អ្នកត្រូវតែផ្លាស់ប្ដូរពាក្យសម្ងាត់របស់អ្នកដើម្បីបន្ត ។", "core.fulllistofcourses": "វគ្គសិក្សាទាំងអស់", + "core.fullnameandsitename": "{{fullname}} ({{sitename}})", "core.grades.average": "មធ្យមភាគ", "core.grades.badgrade": "ពិន្ទុដែលផ្ដល់មិនត្រឹមត្រូវ", "core.grades.contributiontocoursetotal": "ចំណែកក្នុងពិន្ទុសរុបក្នុងវគ្គសិក្សា", @@ -955,55 +1271,121 @@ "core.grades.weight": "ភាពសំខាន់", "core.groupsseparate": "ក្រុមផ្សេងគ្នា", "core.groupsvisible": "មើលឃើញក្រុម", + "core.hasdatatosync": "{{$a}}នេះត្រូវបានធ្វើសមកាលកម្មនៅក្រៅបណ្តាញ", "core.help": "ជំនួយ", "core.hide": "លាក់", "core.hour": "ម៉ោង", "core.hours": "ម៉ោង", + "core.humanreadablesize": "{{size}} {{unit}}", + "core.image": "រូបភាព", + "core.imageviewer": "មើលរូបភាព", "core.info": "ព័ត៌មាន", + "core.ios": "iOS", "core.labelsep": ":", "core.lastaccess": "ចូលដំណើរការចុងក្រោយ", + "core.lastdownloaded": "ការទាញយកចុងក្រោយ", "core.lastmodified": "កែប្រែចុងក្រោយ", + "core.lastsync": "ការធ្វើសមកាលកម្មចុងក្រោយ", "core.list": "បញ្ជី", "core.listsep": ",", + "core.loading": "កំពុងដំណើរការ", + "core.loadmore": "ផ្ទុកបន្ថែម", "core.location": "ទីតាំង", "core.login.auth_email": "ចុះឈ្មោះខ្លួនឯង តាមអ៊ីមែល", + "core.login.authenticating": "កំពុងផ្ទៀងផ្ទាត់", "core.login.cancel": "បោះបង់", + "core.login.checksiteversion": "ពិនិត្យមើលថាគេហទំព័ររបស់អ្នកប្រើប្រាស់ Moodle 2.4 ឬក៏ជំនាន់ក្រោយៗមកទៀត", + "core.login.confirmdeletesite": "តើអ្នកប្រាកដទេថាអ្នកចង់លុបគេហទំព័រគេហទំព័រនេះ {{sitename}}?", + "core.login.connect": "ភ្ជាប់!", + "core.login.connecttomoodle": "ភ្ជាប់ទៅ Moodle", + "core.login.contactyouradministrator": "ទាក់ទងទៅអ្នកគ្រប់គ្រងគេហទំព័ររបស់អ្នកដើម្បីជំនួយផ្សេងៗទៀត", + "core.login.contactyouradministratorissue": "សុំជំនួយពីអ្នកគ្រប់គ្រងគេហទំព័រ របស់អ្នកដើម្បីពិនិត្យមើលបញ្ហានេះ៖ {{$a}}", "core.login.createaccount": "បង្កើតគណនីថ្មីរបស់ខ្ញុំ", "core.login.createuserandpass": "ជ្រើសឈ្មោះអ្នកប្រើ និងពាក្យសម្ងាត់របស់អ្នក", + "core.login.credentialsdescription": "សូមបញ្ជូលឈ្មោះអ្នកប្រើនិងពាក្យសម្ងាត់របស់អ្នកដើម្បីចូលទៅក្នុងគណនី", "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": "កំហុសមួយបានកើតឡើងខណៈពេលធ្វើបច្ចុប្បន្នភាពនៅលើគេហទំព័រ", + "core.login.findyoursite": "ស្វែងរកគេហទំព័ររបស់អ្នក", "core.login.firsttime": "តើអ្នកមកទីនេះជាលើកដំបូងឬ ?", "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": "សេចក្ដីណែនាំ", + "core.login.invalidaccount": "សូមពិនិត្យមើលព័ត៌មានលំអិតអំពីការចូលប្រើឬសុំគេហទំព័រជំនួយពីអ្នកគ្រប់គ្រងគេហទំព័ររបស់អ្នកដើម្បិពិនិត្យមើលការកំណត់ឡើងវិញ។", "core.login.invaliddate": "កាលបរិច្ឆេទមិនត្រឹមត្រូវ", "core.login.invalidemail": "អាសយដ្ឋានអ៊ីមែលមិនត្រឹមត្រូវ", + "core.login.invalidmoodleversion": "កំណែកម្មវិធី Moodle មិនត្រឹមត្រូវ។ យ៉ាងហោចណាស់ក៏ត្រឹម គឺ 2.4ដែរ", + "core.login.invalidsite": "អាសយដ្ឋាន URL មិនត្រឹមត្រូវ", + "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": "ចូល", + "core.login.loginbutton": "ចូល", + "core.login.logininsiterequired": "អ្នកចាំបាច់ត្រូវចូលទៅក្នុងគេហទំព័រនៅក្នុងផ្ទាំងកម្មវិធីអ៊ីនធឺណិត។", "core.login.loginsteps": "សួស្ដី ! ដើម្បីអាចចូលដំណើរការវគ្គសិក្សាបានពេញលេញបាន\nអ្នកត្រូវចំណាយពេលបន្តិច ដើម្បីបង្កើតគណនីថ្មីសម្រាប់ខ្លួនអ្នក\nនៅលើតំបន់បណ្ដាញនេះ ។ វគ្គសិក្សានីមួយៗក៏អាចមាន\n\"កូនសោចុះឈ្មោះ\" របស់វាផងដែរ ប៉ុន្តែពេលនេះអ្នកមិនទាន់\nត្រូវការវានៅឡើយទេ ។ សូមអនុវត្តតាមជំហានខាងក្រោម\nដើម្បីបង្កើតគណនីថ្មី ៖\n
    \n
  1. បំពេញព័ត៌មានលម្អិតរបស់អ្នក នៅក្នុងសំណុំបែបបទ គណនីថ្មី
  2. \n
  3. សារមួយនឹងត្រូវបានផ្ញើទៅអាសយដ្ឋានអ៊ីមែលរបស់អ្នក ។
  4. \n
  5. អានអ៊ីមែលរបស់អ្នក ហើយចុចលើតំណបណ្ដាញ នៅក្នុងអ៊ីមែលនោះ ។
  6. \n
  7. គណនីរបស់អ្នកនឹងត្រូវបានអះអាង ហើយអ្នកនឹងអាចចូលបាន ។
  8. \n
  9. ឥឡូវ ជ្រើសវគ្គសិក្សាដែលអ្នកចង់ចូលរួម ។
  10. \n
  11. ប្រសិនបើវាទាមទារ \"កូនសោចុះឈ្មោះ\" សូមប្រើកូនសោដែលគ្រូរបស់អ្នកឲ្យ ។ អ្នកនឹងត្រូវបាន \"ចុះឈ្មោះ\" នៅក្នុងវគ្គសិក្សាហើយ ។
  12. \n
  13. ឥឡូវ អ្នកអាចចូលដំណើរការវគ្គសិក្សាដោយពេញលេញបាន ។ ចាប់ពីពេលនេះទៅ អ្នកនឹងត្រូវបញ្ចូលឈ្មោះអ្នកប្រើ និងពាក្យសម្ងាត់ផ្ទាល់ខ្លួនរបស់អ្នក (នៅក្នុងសំណុំបែបបទលើទំព័រនេះ) ដើម្បីចូល និងចូលដំណើរការវគ្គសិក្សាណាមួយដែលអ្នកបានចុះឈ្មោះ ។
  14. \n
", "core.login.missingemail": "បាត់អាសយដ្ឋានអ៊ីមែល", "core.login.missingfirstname": "បាត់នាមខ្លួន", "core.login.missinglastname": "បាត់នាមត្រកូល", + "core.login.mobileservicesnotenabled": "ការចូលប្រើទូរសព្ទមិនត្រូវបានបើកនៅលើគេហទំព័ររបស់អ្នកទេ។ សូមទាក់ទងអ្នកគ្រប់គ្រងគេហទំព័ររបស់អ្នកប្រសិនបើអ្នកគិតថាវាគួរត្រូវបានបើក។", "core.login.mustconfirm": "អ្នកត្រូវអះអាងការចូលរបស់អ្នក", "core.login.newaccount": "គណនីថ្មី", + "core.login.newsitedescription": "សូមបញ្ចូល URL នៃគេហទំព័រ Moodle របស់អ្នក។ ចំណាំថាវាប្រហែលជាមិនត្រូវបានកំណត់ ឱ្យដំណើរការជាមួយកម្មវិធីនេះទេ។", + "core.login.notloggedin": "អ្នកតម្រូវអោយចូលប្រើ", "core.login.password": "ពាក្យសម្ងាត់", "core.login.passwordforgotten": "ភ្លេចពាក្យសម្ងាត់", "core.login.passwordforgotteninstructions2": "ដើម្បីកំណត់ពាក្យសម្ងាត់របស់អ្នកជាថ្មី សូមបញ្ចូលឈ្មោះអ្នកប្រើ ឬអាសយដ្ឋានអ៊ីមែលរបស់អ្នកនៅខាងក្រោម។ ប្រសិនបើយើងអាចរកឃើញអ្នកនៅក្នុងមូលដ្ឋានទិន្នន័យ នោះអ៊ីម៉ែលមួយនឹងត្រូវបានផ្ញើទៅកាន់អាសយដ្ឋានអ៊ីម៉ែលរបស់អ្នកជាមួយនឹងការណែនាំអំពីរបៀបចូលក្នុងគណនីអ្នកប្រើម្តងទៀត។", + "core.login.passwordrequired": "តម្រូវឱ្យមានពាក្យសម្ងាត់", "core.login.policyaccept": "ខ្ញុំយល់ និងយល់ព្រម", "core.login.policyagree": "អ្នកត្រូវតែយល់ព្រមចំពោះគោលការណ៍នេះ ដើម្បីបន្តប្រើតំបន់បណ្ដាញនេះ ។ តើអ្នកយល់ព្រមឬទេ ?", "core.login.policyagreement": "កិច្ចព្រមព្រៀងលើគោលការណ៍តំបន់បណ្ដាញ", "core.login.policyagreementclick": "ចុចទីនេះ ដើម្បីអានកិច្ចព្រមព្រៀងលើគោលការណ៍តំបន់បណ្ដាញ", "core.login.potentialidps": "ចូលដោយប្រើគណនីរបស់អ្នកនៅ", + "core.login.problemconnectingerror": "យើងកំពុងមានបញ្ហាក្នុងការតភ្ជាប់", + "core.login.problemconnectingerrorcontinue": "ពិនិត្យឡើងវិញនូវអ្វីដែលអ្នកបានបញ្ចូលអាសយដ្ឋាន ហើយព្យាយាមម្តងទៀត", "core.login.profileinvaliddata": "តម្លៃមិនត្រឹមត្រូវ", + "core.login.recaptchachallengeimage": "reCAPTCHA ជាមួយរូបភាព", + "core.login.recaptchaexpired": "ការផ្ទៀងផ្ទាត់បានផុតកំណត់។ ឆ្លើយសំណួរសុវត្ថិភាពម្តងទៀត", + "core.login.recaptchaincorrect": "ការឆ្លើយចំពោះសំណួរសុវត្ថិភាពមិនត្រឹមត្រូវ", + "core.login.reconnect": "ភ្ជាប់ឡើងវិញ", + "core.login.reconnectdescription": "និមិត្តសញ្ញាផ្ទៀងផ្ទាត់របស់អ្នកមិនត្រឹមត្រូវឬផុតកំណត់។ អ្នកត្រូវភ្ជាប់ប្រព័ន្ធគេហទំព័រឡើងវិញ។", + "core.login.reconnectssodescription": "និមិត្តសញ្ញាផ្ទៀងផ្ទាត់របស់អ្នកមិនត្រឹមត្រូវឬផុតកំណត់។ អ្នកត្រូវភ្ជាប់ប្រព័ន្ធគេហទំព័រឡើងវិញ។ អ្នកចាំបាច់ត្រូវចូលទៅក្នុងគេហទំព័រនៅក្នុងផ្ទាំងកម្មវិធីអ៊ីនធឺណិត។", + "core.login.searchby": "ស្វែងរកតាម៖", "core.login.security_question": "សំណួរសុវត្ថិភាព", "core.login.selectacountry": "ជ្រើសប្រទេស", + "core.login.selectsite": "សូមជ្រើសរើសគេហទំព័ររបស់អ្នក៖", + "core.login.signupplugindisabled": "{{$a}} មិនត្រូវបានបើក", + "core.login.siteaddress": "អាសយដ្ឋាននៃគេហទំព័រ", + "core.login.sitehasredirect": "Your site contains at least one HTTP redirect. The app cannot follow redirects, this could be the issue that's preventing the app from connecting to your site.", + "core.login.siteinmaintenance": "គេហទំព័ររបស់អ្នកស្ថិតនៅក្នុងស្ថានភាពជួសជុល", + "core.login.sitepolicynotagreederror": "គោលការណ៍តំបន់បណ្ដាញមិនត្រូវបានយល់ព្រម", + "core.login.siteurl": "អាសយដ្ឋានគេហទំព័រ", + "core.login.siteurlrequired": "ទាមទារការបញ្ជូលអាសយដ្ឋាននៃគេហទំព័រ។ ឧទារហរណ៏ http://www.yourmoodlesite.org", "core.login.startsignup": "បង្កើតគណនីថ្មី", + "core.login.stillcantconnect": "នៅតែមិនអាចភ្ជាប់ទេឬ?", "core.login.supplyinfo": "ព័ត៌មានលម្អិតបន្ថែម", "core.login.username": "ឈ្មោះអ្នកប្រើ", "core.login.usernameoremail": "បញ្ចូលឈ្មោះអ្នកប្រើ ឬអាសយដ្ឋានអ៊ីមែល", + "core.login.usernamerequired": "ត្រូវការឈ្មោះអ្នកប្រើប្រាស់", "core.login.usernotaddederror": "មិនបានបន្ថែមអ្នកប្រើ \"{{$a}}\" ឡើយ - មិនស្គាល់កំហុស", + "core.login.visitchangepassword": "តើអ្នកចង់ទៅគេហទំព័រដើម្បីប្តូរពាក្យសម្ងាត់ទេ?", + "core.login.webservicesnotenabled": "សេវានៃការប្រើបណ្តាញមិនត្រូវបានបើកនៅក្នុងគេហទំព័ររបស់អ្នកទេ។ សូមទាក់ទងអ្នកគ្រប់គ្រងគេហទំព័ររបស់អ្នកប្រសិនបើអ្នកគិតថាគួរតែត្រូវអនុញ្ញាតឱ្យប្រើ។", + "core.lostconnection": "និមិត្តសញ្ញាផ្ទៀងផ្ទាត់របស់អ្នកមិនត្រឹមត្រូវឬផុតកំណត់។ អ្នកនឹងត្រូវភ្ជាប់ឡើងវិញជាមួយគេហទំព័រនេះ។", + "core.mainmenu.appsettings": "ការកំណត់កម្មវិធី", + "core.mainmenu.changesite": "ផ្លាស់ប្តូរគេហទំព័រ", "core.mainmenu.help": "ជំនួយ", "core.mainmenu.logout": "ចេញ", + "core.mainmenu.website": "គេហទំព័រ", "core.maxsizeandattachments": "ទំហំអតិបរមាសម្រាប់ឯកសារថ្មីៈ {{$a.size}} ឯកសារភ្ជាប់អតិបរមា: {{$a.attachments}}", "core.min": "នាទី", "core.mins": "នាទី", @@ -1036,62 +1418,131 @@ "core.more": "ច្រើនទៀត", "core.mygroups": "ក្រុមរបស់ខ្ញុំ", "core.name": "ឈ្មោះ", + "core.networkerroriframemsg": "មិនអាចធ្វើនៅក្រៅបណ្តាញទេ។ សូមភ្ជាប់អ៊ីនធឺណិតហើយព្យាយាមម្តងទៀត", + "core.networkerrormsg": "មានបញ្ហាក្នុងការភ្ជាប់ទៅគេហទំព័រ។ សូមពិនិត្យមើលការតភ្ជាប់របស់អ្នកហើយព្យាយាមម្តងទៀត។", "core.never": "មិនធ្លាប់", "core.next": "បន្ទាប់", "core.no": "ទេ", "core.nograde": "គ្មានពិន្ទុ", "core.none": "គ្មាន", + "core.nopasswordchangeforced": "អ្នកមិនអាចបន្តដោយគ្មានការផ្លាស់ប្តូរពាក្យសម្ងាត់របស់អ្នក។", + "core.nopermissionerror": "សូមទោស! អ្នកពុំមានសិទ្ធិធ្វើវាទេ!", "core.nopermissions": "សូមអភ័យទោស បច្ចុប្បន្នអ្នកគ្មានសិទ្ធិក្នុងការធ្វើដូចនោះទេ ({{$a}})", "core.noresults": "គ្មានលទ្ធផល", + "core.notapplicable": "មិនមាន", "core.notice": "ចំណាំ", + "core.notsent": "មិនបានផ្ញើ", "core.now": "ឥឡូវ", "core.numwords": "{{$a}} ពាក្យ", "core.offline": "ក្រៅបណ្ដាញ", "core.ok": "យល់ព្រម", "core.online": "លើបណ្ដាញ", + "core.openfullimage": "សូមចុចនៅទីនេះដើម្បីបង្ហាញរូបភាពទំហំពេញ", + "core.openinbrowser": "បើកក្នុងកម្មវិធីរុករក", "core.othergroups": "ក្រុមផ្សេងទៀត", "core.paymentinstant": "ប្រើប៊ូតុងខាងក្រោម ដើម្បីបង់ប្រាក់ និងចុះឈ្មោះក្នុងរយៈពេលតែប៉ុន្មាននាទីប៉ុណ្ណោះ !", + "core.percentagenumber": "{{$a}}%", "core.phone": "ទូរស័ព្ទ", "core.pictureof": "រូបភាព {{$a}}", "core.previous": "មុន", + "core.pulltorefresh": "ទាញដើម្បីដំណើរការឡើងវិញ", "core.question.answer": "ចម្លើយ", "core.question.answersaved": "ចម្លើយបានរក្សាទុក", + "core.question.cannotdeterminestatus": "មិនអាចបង្ហាញពីស្ថានភាពបានទេ", "core.question.complete": "បញ្ចប់", "core.question.correct": "ត្រូវ", + "core.question.errorattachmentsnotsupported": "កម្មវិធីមិនគាំទ្រការភ្ជាប់ឯកសារទៅកាន់ចម្លើយបានទេ។", + "core.question.errorinlinefilesnotsupported": "កម្មវិធីមិនអាចកែសំណេរបន្ទាតជាប់គ្នាបានទេ។", + "core.question.errorquestionnotsupported": "ប្រភេទសំណួរនេះមិនត្រូវបានគាំទ្រដោយកម្មវិធីទេ {{$a}}", "core.question.feedback": "មូលវិចារណ៍", + "core.question.howtodraganddrop": "ចុចលើ”ទម្លាក់”", "core.question.incorrect": "មិនត្រឹមត្រូវ", "core.question.information": "ព័ត៌មាន", "core.question.invalidanswer": "ចម្លើយមិនពេញលេញ", "core.question.notanswered": "មិនបានឆ្លើយ", "core.question.notyetanswered": "មិនទាន់បានឆ្លើយ", "core.question.partiallycorrect": "ត្រូវមួយផ្នែក", + "core.question.questionmessage": "សំណួរ{{$a}}: {{$b}}", "core.question.questionno": "សំណួរ {{$a}}", "core.question.requiresgrading": "ទាមទារការដាក់ពិន្ទុ", + "core.redirectingtosite": "អ្នកនឹងត្រូវបានប្ដូរទិសទៅគេហទំព័រនេះ។", "core.refresh": "ធ្វើឲ្យស្រស់", "core.remove": "យកចេញ", "core.required": "ទាមទារ", + "core.requireduserdatamissing": "អ្នកប្រើប្រាស់នេះខ្វះទិន្នន័យប្រវត្តិរូបដែលត្រូវការ។ សូមបញ្ចូលទិន្នន័យនៅក្នុងគេហទំព័ររបស់អ្នកហើយព្យាយាមម្តងទៀត។
{{$a}}", "core.resources": "ធនធាន", "core.restore": "ស្ដារ", + "core.retry": "ព្យាយាមម្តងទៀត", "core.save": "រក្សាទុក", "core.search": "ស្វែងរក", + "core.searching": "កំពុងស្វែងរក", "core.searchresults": "លទ្ធផលស្វែងរក", "core.sec": "វិ.", "core.secs": "វិ.", "core.seemoredetail": "ចុចទីនេះដើម្បីមើលសេចក្ដីលម្អិតបន្ថែម", "core.send": "ផ្ញើ", "core.sending": "កំពុងផ្ញើ", + "core.settings.about": "អំពី", + "core.settings.appready": "កម្មវិធីរួចរាល់", + "core.settings.cannotsyncoffline": "មិនអាចធ្វើសមកាលកម្មខណៈនៅក្រៅបណ្ដាញ", + "core.settings.cannotsyncwithoutwifi": "មិនអាចធ្វើសមកាលកម្មបានទេព្រោះការកំណត់បច្ចុប្បន្នអនុញ្ញាតឱ្យធ្វើសមកាលកម្មនៅពេលភ្ជាប់Wi-Fi។ សូមភ្ជាប់ទៅបណ្តាញ Wi-Fi", + "core.settings.compilationinfo": "ការចងក្រងព័ត៌មាន", + "core.settings.cordovadevicemodel": "ម៉ូដែលឧបករណ៍ Cordova", + "core.settings.cordovadeviceosversion": "កំណែប្រព័ន្ធប្រតិបត្តិការរបស់ឧបករណ៏ Cordova", + "core.settings.cordovadeviceplatform": "កំណែប្រព័ន្ធវេទិការរបស់ឧបករណ៏ Cordova", + "core.settings.cordovadeviceuuid": "Cordova device UUID", + "core.settings.cordovaversion": "កំណែរ Cordova", "core.settings.currentlanguage": "ភាសាបច្ចុប្បន្ន", "core.settings.debugdisplay": "បង្ហាញសារបំបាត់កំហុស", + "core.settings.debugdisplaydescription": "ប្រសិនបើអ្នកជ្រើសរើសបើកវា នោះវានឹងបង្ហាញព័ត៌មាននៃបញ្ហាដែលអាចកើតឡើង!", + "core.settings.deletesitefiles": "តើអ្នកប្រាកដទេថាអ្នកចង់លុបឯកសារមកពីគេហទំព័រ{sitename}}ឬទេ?", + "core.settings.deletesitefilestitle": "កន្លែងលុបឯកសារនៅគេហទំព័រ", + "core.settings.deviceinfo": "ព័ត៌មានឧបករណ៍", + "core.settings.deviceos": "ប្រព័ន្ធប្រតិបត្តិរបស់ឧបករណ៍", + "core.settings.devicewebworkers": "បុគ្គលិកផ្នែកបណ្ដាញឧបករណ៍បានគាំទ្រ", "core.settings.disableall": "បិទការជូនដំណឹង", "core.settings.disabled": "បានបិទ", + "core.settings.displayformat": "ទម្រង់នៃការបង្ហាញ", + "core.settings.enabledownloadsection": "បើកដំណើរការផ្នែកទាញយក", + "core.settings.enablerichtexteditor": "បើកកម្មវិធីកែសម្រួលអត្ថបទ", + "core.settings.enablerichtexteditordescription": "ប្រសិនបើបើកកម្មវិធីកែសម្រួលអត្ថបទ នោះអ្នកអាចកែតម្រូវខ្លឹមសារបាន", + "core.settings.enablesyncwifi": "អនុញ្ញាតឱ្យធ្វើសមកាលកម្មនៅពេលតែមាន Wi-Fi ប៉ុណ្ណោះ", + "core.settings.errordeletesitefiles": "មានកំហុសក្នុងការលុបឯកសារនៅគេហទំព័រ", + "core.settings.errorsyncsite": "កំហុសក្នុងការធ្វើសមកាលកម្មទិន្នន័យនៅគេហទំព័រ។ សូមពិនិត្យការភ្ជាប់អ៊ីនធឺណិតរបស់អ្នកហើយព្យាយាមម្តងទៀត", + "core.settings.estimatedfreespace": "ប៉ាន់ស្មានទំហំដែលនៅទំនេរ", + "core.settings.filesystemroot": "ឯកសារក្នុងប្រព័ន្ធត្រូវបានដាក់បញ្ចូល", "core.settings.general": "ទូទៅ", "core.settings.language": "ភាសា", "core.settings.license": "អាជ្ញាបណ្ណ GPL", + "core.settings.localnotifavailable": "ការជូនដំណឹងក្នុងតំបន់អាចប្រើបាន", + "core.settings.locationhref": "មើល URLនៅលើបណ្តាញ", "core.settings.loggedin": "ក្នុងបណ្តាញ", "core.settings.loggedoff": "ក្រៅបណ្តាញ", + "core.settings.navigatorlanguage": "ភាសាកម្មវិធីរុករក", + "core.settings.navigatoruseragent": "កម្មវិធីរុករកអ្នកប្រើប្រាស់", + "core.settings.networkstatus": "ស្ថានភាពតភ្ជាប់អ៊ីនធឺណិត", + "core.settings.privacypolicy": "គោលការណ៍ឯកជន", + "core.settings.reportinbackground": "រាយការណ៍កំហុសដោយស្វ័យប្រវត្តិ", "core.settings.settings": "ការកំណត់", + "core.settings.showdownloadoptions": "បង្ហាញជម្រើសដែលអាចទាញយក", "core.settings.sites": "តំបន់បណ្ដាញ", + "core.settings.spaceusage": "ទំហំនៃការប្រើប្រាស់", + "core.settings.synchronization": "ការធ្វើសមកាលកម្ម", + "core.settings.synchronizenow": "ធ្វើសមកាលកម្ម ឥឡូវនេះ", + "core.settings.syncsettings": "កំណត់ការ សមកាលកម្ម", "core.settings.total": "សរុប", + "core.settings.versioncode": "កំណែនៃភាសាកូដ", + "core.settings.versionname": "ឈ្មោះនៃកំណែ", + "core.settings.wificonnection": "ការភ្ជាប់ Wi-Fi", + "core.sharedfiles.chooseaccountstorefile": "ជ្រើសរើសគណនីមួយដើម្បីរក្សាទុកឯកសារ", + "core.sharedfiles.chooseactionrepeatedfile": "ឯកសារដែលមានឈ្មោះនេះមានរួចហើយ។ តើអ្នកចង់ជំនួសឯកសារដែលមានស្រាបសូមប្តូរឈ្មោះវា{{$a}}", + "core.sharedfiles.errorreceivefilenosites": "មិនមានគេហទំព័រត្រូវបានរក្សាទុកទេ។ សូមបន្ថែមគេហទំព័រមួយមុនពេលចែករំលែកឯកសារជាមួយកម្មវិធី", + "core.sharedfiles.nosharedfiles": "មិនមានឯកសារដែលបានចែករំលែកនៅលើគេហទំព័រនេះទេ", + "core.sharedfiles.nosharedfilestoupload": "អ្នកមិនមានឯកសារដើម្បីផ្ទុកឡើងនៅទីនេះទេ។ ប្រសិនបើអ្នកចង់ផ្ទុកឡើងនូវឯកសារមួយពីកម្មវិធី ផ្សេងទៀត សូមស្វែងរកទីតាំងឯកសារហើយចុចលើប៊ូតុង Open in", + "core.sharedfiles.rename": "ប្តូរឈ្មោះ", + "core.sharedfiles.replace": "ជំនួស", + "core.sharedfiles.sharedfiles": "ចែករំលែកឯកសារ", + "core.sharedfiles.successstorefile": "ឯកសារត្រូវបានរក្សាទុកដោយជោគជ័យ។ ជ្រើសរើសឯកសារដើម្បីផ្ទុកឡើងទៅជាឯកសារឯកជនរបស់អ្នក ឬប្រើក្នុងសកម្មភាពណាមួយ", "core.show": "បង្ហាញ", "core.showmore": "បង្ហាញច្រើន...", "core.site": "តំបន់បណ្ដាញ", @@ -1102,22 +1553,51 @@ "core.sizegb": "ជ.ប.", "core.sizekb": "គ.ប.", "core.sizemb": "ម.ប.", + "core.sizetb": "TB", + "core.sorry": "សូមទោស...", "core.sortby": "តម្រៀបតាម", + "core.strftimedate": "%d %B %Y", + "core.strftimedatefullshort": "%d/%m/%y", + "core.strftimedateshort": "%d %B", + "core.strftimedatetime": "%d %B %Y, %I:%M %p", + "core.strftimedatetimeshort": "%d/%m/%y, %H:%M", + "core.strftimedaydate": "%A, %d %B %Y", + "core.strftimedaydatetime": "%A, %d %B %Y, %I:%M %p", + "core.strftimedayshort": "%A, %d %B", + "core.strftimedaytime": "%a, %H:%M", + "core.strftimemonthyear": "%B %Y", + "core.strftimerecent": "%d %b, %H:%M", + "core.strftimerecentfull": "%a, %d %b %Y, %I:%M %p", + "core.strftimetime": "%I:%M %p", "core.submit": "ដាក់ស្នើ", "core.success": "ជោគជ័យ", + "core.tablet": "ថេប្លេត", "core.teachers": "គ្រូ", + "core.thereisdatatosync": "គ្មានអ៊ីនធឺណិត{{$a}} ដែលត្រូវធ្វើសមកាលកម្ម", "core.thisdirection": "ltr", "core.time": "ពេលវេលា", "core.timesup": "ដល់ពេលហើយ !", "core.today": "ថ្ងៃនេះ", + "core.tryagain": "ព្យាយាមម្ដងទៀត", + "core.twoparagraphs": "{{p1}}

{{p2}}", + "core.uhoh": "អូ ហូ!!", + "core.unexpectederror": "កំហុសដែលមិននឹកស្មានដល់។ សូមបិទនិងបើកកម្មវិធីម្តងទៀតបន្ទាប់មកព្យាយាមម្តងទៀត", + "core.unicodenotsupported": "Emojis មួយចំនួនមិនត្រូវបានគាំទ្រនៅលើគេហទំព័រនេះទេ។ រូបនេះនឹងត្រូវបានដកចេញនៅពេលផ្ញើសារ", + "core.unicodenotsupportedcleanerror": "អត្ថបទទទេត្រូវបានរកឃើញនៅពេលសម្អាតអក្សរយូនីកូដ.", + "core.unknown": "មិនស្គាល់", "core.unlimited": "មិនកំណត់", + "core.unzipping": "ពន្លាត", "core.user.address": "អាសយដ្ឋាន", "core.user.city": "ទីក្រុង", + "core.user.contact": "ទំនាក់ទំនង", "core.user.country": "ប្រទេស", "core.user.description": "ពិពណ៌នា", + "core.user.details": "សេចក្ដីលម្អិត", + "core.user.detailsnotavailable": "សេចក្ដីលម្អិតរបស់អ្នកប្រើនេះមិនមានសម្រាប់អ្នកទេ", "core.user.editingteacher": "គ្រូ", "core.user.email": "អាសយដ្ឋានអ៊ីមែល", "core.user.emailagain": "អ៊ីមែល (ម្ដងទៀត)", + "core.user.errorloaduser": "កំហុសក្នុងការផ្ទុកអ្នកប្រើ", "core.user.firstname": "នាមខ្លួន", "core.user.interests": "ចំណាប់អារម្មណ៍", "core.user.lastname": "នាមត្រកូល", @@ -1127,6 +1607,7 @@ "core.user.phone1": "ទូរស័ព្ទ", "core.user.phone2": "ទូរស័ព្ទចល័ត", "core.user.roles": "តួនាទី", + "core.user.sendemail": "អ៊ីមែល", "core.user.student": "សិស្ស", "core.user.teacher": "គ្រូមិនកែសម្រួល", "core.user.webpage": "ទំព័របណ្ដាញ", @@ -1134,6 +1615,14 @@ "core.userdetails": "ព័ត៌មានលម្អិតអំពីអ្នកប្រើ", "core.users": "អ្នកប្រើ", "core.view": "មើល", + "core.viewcode": "មើលកូដ", + "core.vieweditor": "មើលអ្នកកែសម្រួល", + "core.viewembeddedcontent": "បង្ហាញខ្លឹមសារដែលបានដាក់បញ្ចូល", + "core.warningofflinedatadeleted": "ទិន្នន័យក្រៅបណ្ដាញពី{{component}} '{{name}}\\ត្រូវបានលុប។ {{error}}", + "core.whoops": "អូស!", + "core.whyisthishappening": "ហេតុអ្វីរឿងនេះកើតឡើង?", + "core.windowsphone": "ប្រព័ន្ធប្រតិបត្តិការ Windows Phone", + "core.wsfunctionnotavailable": "មុខងារសេវាបណ្ដាញមិនមានទេ", "core.year": "ឆ្នាំ", "core.years": "ឆ្នាំ", "core.yes": "បាទ/ចាស" diff --git a/src/assets/lang/kn.json b/src/assets/lang/kn.json index 5ed139b52..f2b9841db 100644 --- a/src/assets/lang/kn.json +++ b/src/assets/lang/kn.json @@ -1,5 +1,6 @@ { "addon.badges.badges": "ಲಾಂಛನಗಳು", + "addon.block_sitemainmenu.pluginname": "ಮುಖ್ಯ ಪರಿವಿಡಿ", "addon.calendar.calendar": "ಪಂಚಾಂಗ", "addon.calendar.calendarevents": "ಪಂಚಾಂಗದ ಘಟನೆಗಳು", "addon.calendar.errorloadevent": "ಘಟನೆಯನ್ನು ತೋರಿಸುವಲ್ಲಿ ದೋಷವಿದೆ", @@ -14,7 +15,7 @@ "addon.coursecompletion.complete": "ಮುಕ್ತಾಯ", "addon.coursecompletion.completed": "ಮುಗಿದಿದೆ", "addon.coursecompletion.completiondate": "ಮುಗಿದ ದಿನಾಂಕ", - "addon.coursecompletion.coursecompletion": "ಅಭ್ಯಾಸಕ್ರಮದ ಮುಕ್ತಾಯ", + "addon.coursecompletion.coursecompletion": "ಅಭ್ಯಾಸಕ್ರಮದ ಪ್ರಗತಿ", "addon.coursecompletion.criteria": "ಮಾನದಂಡ", "addon.coursecompletion.criteriagroup": "ಮಾನದಂಡದ ಗುಂಪು", "addon.coursecompletion.criteriarequiredall": "ಕೆಳಗಿನ ಎಲ್ಲಾ ಮಾನದಂಡಗಳು ಬೇಕಿದೆ", @@ -25,7 +26,6 @@ "addon.coursecompletion.pending": "ಬಾಕಿ ಇದೆ", "addon.coursecompletion.required": "ಬೇಕಾಗಿದೆ", "addon.coursecompletion.requiredcriteria": "ಬೇಕಿರುವ ಮಾನದಂಡ", - "addon.coursecompletion.requirement": "ಅಗತ್ಯತೆ", "addon.coursecompletion.status": "ಸ್ಥಿತಿಗತಿ", "addon.coursecompletion.viewcoursereport": "ಅಭ್ಯಾಸಕ್ರಮದ ವರದಿಯನ್ನು ನೋಡಿ", "addon.files.couldnotloadfiles": "ಕಡತಗಳ ಪಟ್ಟಿಯನ್ನು ತೋರಿಸಲಾಗುತ್ತಿಲ್ಲ", @@ -33,7 +33,6 @@ "addon.files.erroruploadnotworking": "ನಿಮ್ಮ ತಾಣಕ್ಕೆ ಯಾವುದೇ ಕಡತಗಳನ್ನು ಈಗ ಸೇರಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ", "addon.files.privatefiles": "ಖಾಸಗಿ ಕಡತಗಳು", "addon.messageoutput_airnotifier.processorsettingsdesc": "ಸಾಧನಗಳನ್ನು ಸಂರಚಿಸಿ", - "addon.messages.blockcontactconfirm": "ಈ ಸಂಪರ್ಕದಿಂದ ನಿಮಗೆ ಯಾವುದೇ ಸಂದೇಶಗಳು ಬರುವುದಿಲ್ಲ", "addon.messages.contactlistempty": "ಸಂಪರ್ಕ ಪಟ್ಟಿ ಖಾಲಿ ಇದೆ", "addon.messages.contactname": "ಸಂಪರ್ಕದ ಹೆಸರು", "addon.messages.errordeletemessage": "ಸಂದೇಶವನ್ನು ಅಳಿಸುವ ವೇಳೆ ತಪ್ಪಿದೆ", @@ -59,6 +58,7 @@ "addon.mod_assign.errorshowinginformation": "ನಿವೇದನೆಯ ಮಾಹಿತಿಯನ್ನು ತೋರಿಸಲಾಗುತ್ತಿಲ್ಲ", "addon.mod_assign.feedbacknotsupported": "ಈ ಮರುಮಾಹಿತಿ ಅನ್ವಯಕದಲ್ಲಿ ಬೆಂಬಲಿತವಾಗಿಲ್ಲ ಹಾಗು ಸಂಪೂರ್ಣ ಮಾಹಿತಿಯನ್ನು ಒಳಗೊಂಡಿಲ್ಲದಿರಬಹುದು", "addon.mod_assign.gradenotsynced": "ಶ್ರೇಣಿ ನವೀಕರಣಗೊಂಡಿಲ್ಲ", + "addon.mod_assign.modulenameplural": "ಕಾರ್ಯ ನಿಯೋಜನೆ", "addon.mod_assign.notallparticipantsareshown": "ನಿವೇದನೆಯನ್ನು ಮಾಡಿರದ ಭಾಗಿದಾರರನ್ನು ತೋರಿಸಲಾಗುವುದಿಲ್ಲ", "addon.mod_assign.numberofteams": "ಗುಂಪುಗಳು", "addon.mod_assign.numwords": "{{$a}} ಪದಗಳು", @@ -75,6 +75,7 @@ "addon.mod_chat.mustbeonlinetosendmessages": "ನೀವು ಸಂದೇಶಗಳನ್ನು ಕಳಿಸಲು ಆನ್‌ಲೈನ್‌ ಅಲ್ಲಿರಬೇಕು", "addon.mod_chat.nomessages": "ಯಾವುದೇ ಸಂದೇಶಗಳಿಲ್ಲ", "addon.mod_choice.errorgetchoice": "ಆಯ್ಕೆಯ ದತ್ತಾಂಶವನ್ನು ಪಡೆಯುವಲ್ಲಿ ತಪ್ಪಿದೆ", + "addon.mod_choice.modulenameplural": "ಆಯ್ಕೆಗಳು", "addon.mod_choice.responsesresultgraphdescription": "{{number}}% ಬಳಕೆದಾರರು ಈ ಆಯ್ಕೆಯನ್ನು ಮಾಡಿದ್ದಾರೆ: {{text}}.", "addon.mod_choice.resultsnotsynced": "ನಿಮ್ಮ ಕೊನೆಯ ಪ್ರತಿಕ್ರಿಯೆ ಫಲಿತಾಂಶಕ್ಕೆ ಸೇರ್ಪಡೆಗೊಳ್ಳುವ ಮುನ್ನ ನವೀಕರಣಗೊಂಡಿರಬೇಕು", "addon.mod_data.errorapproving": "ಪ್ರವೇಶವನ್ನು ಅನುಮೋದಿಸುವ ಅಥವಾ ಅನುಮೋದಿಸದಿರುವಲ್ಲಿ ತಪ್ಪಿದೆ", @@ -90,6 +91,7 @@ "addon.mod_forum.errorgetgroups": "ಗುಂಪಿನ ವ್ಯವಸ್ಥೆಯನ್ನು ಪಡೆದುಕೊಳ್ಳುವಲ್ಲಿ ತಪ್ಪಿದೆ", "addon.mod_forum.forumnodiscussionsyet": "ಈ ಚರ್ಚಾ ವೇದಿಕೆಯಲ್ಲಿ ಈವರೆಗೂ ಯಾವುದೇ ಚರ್ಚೆಗಳಿಲ್ಲ", "addon.mod_forum.group": "ಗುಂಪು", + "addon.mod_forum.modulenameplural": "ವೇದಿಕೆಗಳು", "addon.mod_forum.numdiscussions": "{{numdiscussions}} ಚರ್ಚೆಗಳು", "addon.mod_forum.numreplies": "{{numreplies}} ಹಿಮ್ಮಾಹಿತಿಗಳು", "addon.mod_forum.refreshdiscussions": "ಚರ್ಚೆಗಳನ್ನು ನವೀಕರಿಸಿ", @@ -171,6 +173,7 @@ "addon.notifications.therearentnotificationsyet": "ಅಧಿಸೂಚನೆಗಳಿಲ್ಲ", "core.accounts": "ಖಾತೆಗಳು", "core.add": "ಸೇರಿಸು", + "core.all": "ಎಲ್ಲ", "core.answer": "ಉತ್ತರ", "core.cannotconnect": "ಸಂಪರ್ಕಗೋಳಿಸಲು ಆಗುತ್ತಿಲ್ಲ: ಕೊಂಡಿಯ ಹೆಸರನ್ನು ಸರಿಯಾಗಿ ಬರೆದಿದ್ದೀರೆಂದು ಪರಿಶೀಲಿಸಿ ಹಾಗು ನಿಮ್ಮ ಮೂಡಲ್‌ ೨.೪ ಅಥವಾ ನಂತರದ್ದೆಂದು ಖಾತ್ರಿಪಡಿಸಿಕೊಳ್ಳಿ", "core.cannotdownloadfiles": "ಕಡತವನ್ನು ತೆಗೆದುಕೊಳ್ಳದಂತೆ ತಡೆಯಲಾಗಿದೆ. ದಯಮಾಡಿ ನಿಮ್ಮ ತಾಣದ ಆಡಳಿತಾಧಿಕಾರಿಯನ್ನು ಸಂಪರ್ಕಿಸಿ", @@ -211,7 +214,6 @@ "core.course.contents": "ಪರಿವಿಡಿ", "core.course.couldnotloadsectioncontent": "ವಿಭಾಗದ ವಿಷಯವಸ್ತುವನ್ನು ತೋರಿಸಲಾಗುತ್ತಿಲ್ಲ. ದಯವಿಟ್ಟು ನಂತರ ಪ್ರಯತ್ನಿಸಿ.", "core.course.couldnotloadsections": "ವಿಭಾಗಗಳನ್ನು ತೋರಿಸಲಾಗುತ್ತಿಲ್ಲ. ದಯವಿಟ್ಟು ನಂತರ ಪ್ರಯತ್ನಿಸಿ.", - "core.course.downloadcourse": "ಅಭ್ಯಾಸಕ್ರಮವನ್ನು ತೆಗೆದುಕೊಳ್ಳಿ", "core.course.errordownloadingcourse": "ಅಭ್ಯಾಸಕ್ರಮವನ್ನು ತೆಗೆದುಕೊಳ್ಳುವಲ್ಲಿ ತಪ್ಪಿದೆ", "core.course.errordownloadingsection": "ವಿಭಾಗವನ್ನು ತೆಗೆದುಕೊಳ್ಳುವಲ್ಲಿ ತಪ್ಪಿದೆ", "core.course.errorgetmodule": "ಚಟುವಟಿಕೆಯ ದತ್ತಾಂಶವನ್ನು ತೆಗೆದುಕೊಳ್ಳುವಲ್ಲಿ ತಪ್ಪಿದೆ", @@ -219,7 +221,6 @@ "core.course.useactivityonbrowser": "ಇದನ್ನು ನೀವು ನಿಮ್ಮ ಸಾಧನದ ವೆಬ್ ಬ್ರೌಸರ್‌ನಿಂದಲೂ ಉಪಯೋಗಿಸಬಹುದು.", "core.courses.cannotretrievemorecategories": "{{$a}} ಈ ಹಂತಕ್ಕೂ ಆಳದಲ್ಲಿರುವ ವಿಭಾಗಗಳನ್ನು ಪುನಃ ಸಂಪಾದಿಸಲಾಗುವುದಿಲ್ಲ", "core.courses.confirmselfenrol": "ನೀವು ಈ ಅಭ್ಯಾಸಕ್ರಮದಕ್ಕೆ ನೋಂದಾಯಿಸಿಕೊಳ್ಳಲು ಇಚ್ಛಿಸುವಿರೆ?", - "core.courses.courseoverview": "ಅಭ್ಯಾಸಕ್ರಮದ ಪಕ್ಷಿನೋಟ", "core.courses.courses": "ವಿಷಯಗಳು", "core.courses.downloadcourses": "ಅಭ್ಯಾಸಕ್ರಮಗಳನ್ನು ತೆಗೆದುಕೊಳ್ಳಿ", "core.courses.enrolme": "ನನ್ನನ್ನು ನೋಂದಾಯಿಸಿ", @@ -230,6 +231,7 @@ "core.courses.filtermycourses": "ನನ್ನ ಅಭ್ಯಾಸಕ್ರಮಗಳನ್ನು ಸೋಸಿ", "core.courses.frontpage": "ಮುಖಪುಟ", "core.courses.mycourses": "ನನ್ನ ಅಭ್ಯಾಸಕ್ರಮಗಳು", + "core.courses.mymoodle": "ತಡೆತೆರೆ", "core.courses.nocourses": "ತೋರಿಸಲು ಯಾವುದೇ ಮಾಹಿತಿ ಇಲ್ಲ", "core.courses.notenrollable": "ನೀವು ಈ ಅಭ್ಯಾಸಕ್ರಮಕ್ಕೆ ನೋಂದಾಯಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ", "core.courses.password": "ನೋಂದಣಿ ಕೀಲಿ", @@ -353,8 +355,6 @@ "core.lostconnection": "ನಿಮ್ಮ ದೃಢೀಕರಣದ ಟೋಕನ್‌ ಅಸಿಂಧುವಾಗಿದೆ ಅಥವಾ ಮುಗಿದಿದೆ. ನೀವು ತಾಣಕ್ಕೆ ಮರುಸಂಪರ್ಕ ಹೊಂದಿರಿ.", "core.mainmenu.appsettings": "ಆಪ್ ಸಿದ್ಧತೆಗಳು", "core.mainmenu.changesite": "ಜಾಲತಾಣವನ್ನು ಬದಲಿಸು", - "core.mainmenu.mycourses": "ನನ್ನ ಅಭ್ಯಾಸಕ್ರಮಗಳು", - "core.mainmenu.togglemenu": "ಮೆಲ್ ಪರಿವಿಡಿ", "core.mainmenu.website": "ಜಾಲತಾಣ", "core.mygroups": "ನನ್ನ ಗುಂಪುಗಳು", "core.networkerrormsg": "ತಾಣಕ್ಕೆ ಸಂಪರ್ಕಿಸುವಲ್ಲಿ ಸಮಸ್ಯೆಯೊಂದಿದೆ. ದಯಮಾಡಿ ನಿಮ್ಮ ಸಂಪರ್ಕವನ್ನು ಪರಿಶೀಲಿಸಿ ಹಾಗು ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ.", @@ -381,6 +381,7 @@ "core.question.questionmessage": "ಪ್ರಶ್ನೆ {{$a}}: {{$b}}", "core.redirectingtosite": "ನೀವು ಈ ತಾಣಕ್ಕೆ ಮರುನಿರ್ದೇಶನಗೊಳ್ಳುತ್ತೀರಿ", "core.requireduserdatamissing": "ಈ ಬಳಕೆದಾರನಿಗೆ ಕೆಲವು ವ್ಯಕ್ತಿಚಿತ್ರದ ದತ್ತಾಂಶದ ಕೊರತೆಯಿದೆ. ನಿಮ್ಮ ತಾಣದಲ್ಲಿ ಈ ದತ್ತಾಂಶವನ್ನು ತುಂಬಿ ಮತ್ತೆ ಪ್ರಯತ್ನಿಸಿ.
{{$a}}", + "core.resources": "ಸಂಪನ್ಮೂಲಗಳು", "core.restore": "ಪುನಃಸ್ಥಾಪನೆ", "core.retry": "ಮರುಪ್ರಯತ್ನಿಸಿ", "core.searching": "ಹುಡುಕಲಾಗುತ್ತಿದೆ", @@ -407,6 +408,17 @@ "core.sitehome.sitenews": "ತಾಣದ ವಾರ್ತೆಗಳು", "core.sizetb": "TB", "core.sorry": "ಕ್ಷಮಿಸಿ...", + "core.strftimedate": "%d %B %Y", + "core.strftimedateshort": "%d %B", + "core.strftimedatetime": "%d %B %Y, %I:%M %p", + "core.strftimedaydate": "%A, %d %B %Y", + "core.strftimedaydatetime": "%A, %d %B %Y, %I:%M %p", + "core.strftimedayshort": "%A, %d %B", + "core.strftimedaytime": "%a, %H:%M", + "core.strftimemonthyear": "%B %Y", + "core.strftimerecent": "%d %b, %H:%M", + "core.strftimerecentfull": "%a, %d %b %Y, %I:%M %p", + "core.strftimetime": "%I:%M %p", "core.tablet": "ಟ್ಯಾಬ್ಲೆಟ್", "core.teachers": "ಶಿಕ್ಷಕರು", "core.thereisdatatosync": "ಕೆಲವು ಆಫ್‌ಲೈನ್‌ {{$a}} ನವೀಕರಿಸಬೇಕು", @@ -425,14 +437,14 @@ "core.user.country": "ದೇಶ", "core.user.detailsnotavailable": "ಈ ಬಳಕೆದಾರರ ವಿವರ ನಿಮಗೆ ಲಭ್ಯವಿಲ್ಲ", "core.user.editingteacher": "ಶಿಕ್ಷಕರು", - "core.user.manager": "ನಿರ್ವಾಹಕರು", + "core.user.manager": "ನಿರ್ವಾಹಕ", "core.user.newpicture": "ಹೊಸ ಚಿತ್ರ", "core.user.participants": "ಭಾಗಿಗಳು", "core.user.phone1": "ದೂರವಾಣಿ", "core.user.phone2": "ಜಂಗಮವಾಣಿ", "core.user.sendemail": "ಮಿಂಚಂಚೆ", "core.user.student": "ವಿದ್ಯಾರ್ಥಿ", - "core.user.teacher": "ಸಂಪಾದನೆ-ರಹಿತ ಶಿಕ್ಷಕರು", + "core.user.teacher": "ಪರಿಷ್ಕರಣೆ ಮಾಡಲಾಗದ ಶಿಕ್ಷಕರು", "core.user.webpage": "ಅಂತರ್ಜಾಲ ಪುಟ", "core.userdetails": "ಬಳಕೆದಾರ ಮಾಹಿತಿ", "core.users": "ಬಳಕೆದಾರರು", diff --git a/src/assets/lang/ko.json b/src/assets/lang/ko.json index 3e61a03ad..e78f198f3 100644 --- a/src/assets/lang/ko.json +++ b/src/assets/lang/ko.json @@ -8,8 +8,12 @@ "addon.badges.issuancedetails": "뱃지 만료기한", "addon.badges.issuerdetails": "발행자 세부정보", "addon.badges.issuername": "발행자 이름", + "addon.badges.issuerurl": "발행자 URL", "addon.badges.nobadges": "사용가능한 뱃지가 없습니다.", "addon.badges.recipientdetails": "수신자 세부사항", + "addon.badges.warnexpired": "(이 뱃지는 만료되었습니다)", + "addon.block_activitymodules.pluginname": "학습활동", + "addon.block_sitemainmenu.pluginname": "주 메뉴", "addon.calendar.calendar": "달력", "addon.calendar.calendarevents": "달력 일정", "addon.calendar.defaultnotificationtime": "기본 알림 시간", @@ -27,22 +31,22 @@ "addon.competency.nocompetencies": "역량 없음", "addon.coursecompletion.complete": "완전한", "addon.coursecompletion.completecourse": "강좌 완료", - "addon.coursecompletion.completed": "완료 됨", - "addon.coursecompletion.completiondate": "완료일", + "addon.coursecompletion.completed": "완료됨", + "addon.coursecompletion.completiondate": "이수 날짜", "addon.coursecompletion.completionmenuitem": "이수", "addon.coursecompletion.couldnotloadreport": "강좌 완료 보고서를 로드 할 수 없습니다. 나중에 다시 시도 해주십시오.", "addon.coursecompletion.coursecompletion": "강좌 완료", "addon.coursecompletion.criteria": "기준", - "addon.coursecompletion.criteriagroup": "기준 그룹", - "addon.coursecompletion.criteriarequiredall": "아래 모든 기준이 필요합니다.", - "addon.coursecompletion.criteriarequiredany": "아래 기준을 충족해야 합니다.", + "addon.coursecompletion.criteriagroup": "기준 모둠", + "addon.coursecompletion.criteriarequiredall": "아래의 모든 기준이 필요합니다.", + "addon.coursecompletion.criteriarequiredany": "아래의 어떤 기준도 필요합니다,", "addon.coursecompletion.inprogress": "진행 중", - "addon.coursecompletion.manualselfcompletion": "수동 완료", - "addon.coursecompletion.notyetstarted": "시작 전", - "addon.coursecompletion.pending": "연기", - "addon.coursecompletion.required": "필수", - "addon.coursecompletion.requiredcriteria": "요구 기준", - "addon.coursecompletion.requirement": "요구사항", + "addon.coursecompletion.manualselfcompletion": "강좌이수 수동확인", + "addon.coursecompletion.notyetstarted": "아직 시작 안했습니다.", + "addon.coursecompletion.pending": "유예", + "addon.coursecompletion.required": "필수사항", + "addon.coursecompletion.requiredcriteria": "필수 기준", + "addon.coursecompletion.requirement": "필수", "addon.coursecompletion.status": "상태", "addon.coursecompletion.viewcoursereport": "강좌 보고서 보기", "addon.files.couldnotloadfiles": "파일 목록을 로드할 수 없습니다.", @@ -52,8 +56,6 @@ "addon.files.sitefiles": "파일 창고", "addon.messageoutput_airnotifier.processorsettingsdesc": "장치 구성", "addon.messages.addcontact": "연락 추가", - "addon.messages.blockcontact": "연락 차단", - "addon.messages.blockcontactconfirm": "이 연락처의 메시지는 더 이상 수신되지 않습니다.", "addon.messages.blocknoncontacts": "연락처에 없는 사람들이 나에게 메세지 보내는 것 방지", "addon.messages.contactlistempty": "연락처가 비어 있습니다.", "addon.messages.contactname": "연락처 이름", @@ -66,16 +68,16 @@ "addon.messages.messagenotsent": "메시지가 전송되지 않았습니다. 다시 시도해 주세요.", "addon.messages.messages": "메세지", "addon.messages.newmessages": "새로운 메시지", - "addon.messages.nomessages": "대기중인 메세지 없음", + "addon.messages.nomessagesfound": "발견된 메세지 없음", "addon.messages.nousersfound": "사용자가 없습니다.", "addon.messages.removecontact": "연락처 제거", "addon.messages.removecontactconfirm": "연락처가 연락처 목록에서 제거됩니다.", + "addon.messages.searchcombined": "복합 검색", "addon.messages.type_blocked": "차단된", "addon.messages.type_offline": "오프라인", "addon.messages.type_online": "온라인", "addon.messages.type_search": "검색 결과", "addon.messages.type_strangers": "기타", - "addon.messages.unblockcontact": "차단되지 않은 연락처", "addon.messages.warningmessagenotsent": "{{user}} 사용자에게 메시지를 보낼 수 없습니다. {{error}}", "addon.mod_assign.acceptsubmissionstatement": "제출 명세서를 수락하십시오.", "addon.mod_assign.addattempt": "또 다른 시도 추가", @@ -121,6 +123,7 @@ "addon.mod_assign.hiddenuser": "참가자", "addon.mod_assign.latesubmissions": "늦은 제출", "addon.mod_assign.latesubmissionsaccepted": "연장 허가를 받은 학생들만 아직 과제를 제출할 수 있습니다", + "addon.mod_assign.modulenameplural": "과제제출", "addon.mod_assign.noattempt": "시도 없음", "addon.mod_assign.nomoresubmissionsaccepted": "더 이상 제출을 받지 않습니다", "addon.mod_assign.noonlinesubmissions": "이 과제는 온라인으로 제출하는 것을 요구하지 않습니다.", @@ -169,6 +172,7 @@ "addon.mod_assign_submission_file.pluginname": "파일 제출", "addon.mod_assign_submission_onlinetext.pluginname": "온라인 텍스트 제출", "addon.mod_book.errorchapter": "책의 장을 읽는데 오류", + "addon.mod_book.modulenameplural": "책", "addon.mod_chat.beep": "호출", "addon.mod_chat.currentusers": "현재 참여자", "addon.mod_chat.enterchat": "대화에 참여하려면 여기를 클릭!", @@ -181,6 +185,7 @@ "addon.mod_chat.messagebeepsyou": "{{$a}}가 나를 호출했음!", "addon.mod_chat.messageenter": "{{$a}} 대화방에 들어옴", "addon.mod_chat.messageexit": "{{$a}} 대화방을 나감", + "addon.mod_chat.modulenameplural": "대화모음", "addon.mod_chat.mustbeonlinetosendmessages": "메시지를 보내려면 온라인 상태 여야합니다.", "addon.mod_chat.nomessages": "아직 메시지 없음", "addon.mod_chat.send": "전송", @@ -190,6 +195,7 @@ "addon.mod_choice.errorgetchoice": "선택 데이터를 가져 오는 중 오류가 발생했습니다.", "addon.mod_choice.expired": "죄송합니다. 이 활동은 {{$a}} 에 종료되어서 더 이상 사용할 수 없습니다.", "addon.mod_choice.full": "(마감됨)", + "addon.mod_choice.modulenameplural": "간편설문", "addon.mod_choice.noresultsviewable": "지금은 결과를 볼 수 없습니다.", "addon.mod_choice.notopenyet": "죄송합니다만, {{$a}} 까지는 이용할 수 없습니다.", "addon.mod_choice.numberofuser": "사용자", @@ -220,7 +226,9 @@ "addon.mod_data.errordeleting": "항목을 삭제하는 중 오류가 발생했습니다.", "addon.mod_data.expired": "죄송합니다만, 이 활동은 {{$a}}에 종료되었으므로 더 이상 이용할 수 없습니다.", "addon.mod_data.fields": "항목들", + "addon.mod_data.foundrecords": "기록 발견 : {{$a.num}}/{{$a.max}} (필터 초기화)", "addon.mod_data.menuchoose": "선택...", + "addon.mod_data.modulenameplural": "데이터베이스", "addon.mod_data.more": "더 이상", "addon.mod_data.nomatch": "해당되는 게시물이 없음!", "addon.mod_data.norecords": "데이터베이스에 입력된 내용 없음", @@ -251,6 +259,7 @@ "addon.mod_feedback.feedbackopen": "답안 입력 시작 시간", "addon.mod_feedback.mapcourses": "피드백을 강좌에 연결", "addon.mod_feedback.mode": "모드", + "addon.mod_feedback.modulenameplural": "피드백 활동", "addon.mod_feedback.next_page": "다음 페이지", "addon.mod_feedback.non_anonymous": "기명, 응답내용 공개", "addon.mod_feedback.non_anonymous_entries": "익명 기록 없음", @@ -270,6 +279,7 @@ "addon.mod_feedback.started": "시작되었음", "addon.mod_feedback.this_feedback_is_already_submitted": "당신은 이미 이 활동을 완료하였습니다.", "addon.mod_folder.emptyfilelist": "표시할 파일이 없습니다.", + "addon.mod_folder.modulenameplural": "폴더", "addon.mod_forum.addanewdiscussion": "새 토론 주제 추가", "addon.mod_forum.addanewquestion": "새 질문 추가", "addon.mod_forum.addanewtopic": "새로운 주제 추가", @@ -289,6 +299,7 @@ "addon.mod_forum.modeflatnewestfirst": "새 답글부터 내용 보기", "addon.mod_forum.modeflatoldestfirst": "옛 답글부터 내용 보기", "addon.mod_forum.modenested": "주제 중심으로 답글 보기", + "addon.mod_forum.modulenameplural": "포럼모음", "addon.mod_forum.numdiscussions": "{{numdiscussions}}개의 토론", "addon.mod_forum.numreplies": "{{numreplies}}개의 답글", "addon.mod_forum.posttoforum": "포럼에 올리기", @@ -324,9 +335,11 @@ "addon.mod_glossary.fillfields": "필수적으로 채워넣어야 하는 항목", "addon.mod_glossary.fullmatch": "완전히 일치하는 단어만", "addon.mod_glossary.linking": "자동 연결", + "addon.mod_glossary.modulenameplural": "용어활용", "addon.mod_glossary.noentriesfound": "항목을 찾을 수 없습니다.", "addon.mod_glossary.searchquery": "질문 검색", "addon.mod_imscp.deploymenterror": "콘텐츠 패키지 오류!", + "addon.mod_imscp.modulenameplural": "IMS 콘덴츠팩키지", "addon.mod_imscp.showmoduledescription": "설명 표시", "addon.mod_lesson.answer": "답안", "addon.mod_lesson.attempt": "{{$a}} 번째 시도", @@ -369,6 +382,7 @@ "addon.mod_lesson.lowtime": "최단 시간", "addon.mod_lesson.maximumnumberofattemptsreached": "최대 허용 시도횟수에 도달하였습니다. 다음 페이지로 갑니다.", "addon.mod_lesson.modattemptsnoteacher": "검토과정은 학생에게만 해당됨", + "addon.mod_lesson.modulenameplural": "완전학습", "addon.mod_lesson.noanswer": "답을 하지 않았습니다. 되돌아 가서 답을 입력하세요.", "addon.mod_lesson.nolessonattempts": "이 학습에 대해 아무런 시도도 없었음.", "addon.mod_lesson.notcompleted": "완료하지 않았음", @@ -412,7 +426,9 @@ "addon.mod_lti.errorgetlti": "모듈 데이터를 가져 오는 중 오류가 발생했습니다.", "addon.mod_lti.errorinvalidlaunchurl": "시작 URL이 유효하지 않습니다.", "addon.mod_lti.launchactivity": "활동 시작", + "addon.mod_lti.modulenameplural": "기본 LTI", "addon.mod_page.errorwhileloadingthepage": "페이지 내용을 로드하는 중 오류가 발생했습니다.", + "addon.mod_page.modulenameplural": "웹페이지", "addon.mod_quiz.attemptfirst": "첫번째 시도", "addon.mod_quiz.attemptlast": "마지막 시도", "addon.mod_quiz.attemptnumber": "시도", @@ -444,6 +460,7 @@ "addon.mod_quiz.grademethod": "채점 방법", "addon.mod_quiz.gradesofar": "{{$a.method}}: {{$a.mygrade}} / {{$a.quizgrade}}.", "addon.mod_quiz.marks": "점수", + "addon.mod_quiz.modulenameplural": "퀴즈", "addon.mod_quiz.mustbesubmittedby": "이 시도는 {{$a}}가 제출해야 합니다.", "addon.mod_quiz.noquestions": "아직 퀴즈가 추가되지 않음", "addon.mod_quiz.noreviewattempt": "이 시도를 검토하도록 허용되지 않았습니다.", @@ -486,6 +503,7 @@ "addon.mod_quiz.warningdatadiscardedfromfinished": "일부 오프라인 응답이 삭제 되었기 때문에 미완료 상태로 시도하십시오. 답변을 검토 한 다음 다시 제출하십시오.", "addon.mod_quiz.yourfinalgradeis": "이번 퀴즈의 최종 점수는 {{$a}} 입니다.", "addon.mod_resource.errorwhileloadingthecontent": "콘텐츠를 로드하는 중 오류가 발생했습니다.", + "addon.mod_resource.modulenameplural": "파일", "addon.mod_resource.openthefile": "파일 열기", "addon.mod_scorm.asset": "에셋", "addon.mod_scorm.assetlaunched": "에셋 - 보았음", @@ -521,6 +539,7 @@ "addon.mod_scorm.incomplete": "미완성됨", "addon.mod_scorm.lastattempt": "마지막 시도", "addon.mod_scorm.mode": "모드", + "addon.mod_scorm.modulenameplural": "스콤 패키지", "addon.mod_scorm.newattempt": "새로운 시도 시작하기", "addon.mod_scorm.noattemptsallowed": "허용된 시도 수", "addon.mod_scorm.noattemptsmade": "시도한 횟수", @@ -540,9 +559,11 @@ "addon.mod_survey.errorgetsurvey": "설문 데이터를 가져 오는 중 오류가 발생했습니다.", "addon.mod_survey.ifoundthat": "을 발견하다.", "addon.mod_survey.ipreferthat": "을 더 좋아하다.", + "addon.mod_survey.modulenameplural": "설문조사", "addon.mod_survey.responses": "응답", "addon.mod_survey.results": "결과", "addon.mod_url.accessurl": "URL 접속", + "addon.mod_url.modulenameplural": "URLs", "addon.mod_url.pointingtourl": "리소스가 가리키는 URL입니다.", "addon.mod_wiki.cannoteditpage": "이 페이지를 편집 할 수 없습니다.", "addon.mod_wiki.createpage": "페이지 만들기", @@ -551,6 +572,7 @@ "addon.mod_wiki.errornowikiavailable": "이 위키는 아직 내용이 없습니다.", "addon.mod_wiki.gowikihome": "위키 첫 페이지로 이동", "addon.mod_wiki.map": "맵", + "addon.mod_wiki.modulenameplural": "위키문서", "addon.mod_wiki.newpagehdr": "세 페이지", "addon.mod_wiki.newpagetitle": "새 페이지 제목", "addon.mod_wiki.nocontent": "이 페이지에 내용이 없습니다.", @@ -588,6 +610,7 @@ "addon.mod_workshop.gradinggradecalculated": "계산완료된 자기평가 성적", "addon.mod_workshop.gradinggradeof": "자기평가 성적 ({{$a}}) ", "addon.mod_workshop.gradinggradeover": "평가 성적 덮어쓰기", + "addon.mod_workshop.modulenameplural": "상호평가", "addon.mod_workshop.nogradeyet": "아직 성적 없음", "addon.mod_workshop.notassessed": "아직 평가하지 않음", "addon.mod_workshop.notoverridden": "덮어쓰여지지 않음", @@ -901,6 +924,8 @@ "assets.mimetypes.text/rtf": "RTF 문서", "core.accounts": "계정", "core.add": "추가", + "core.ago": "{{$a}} 이전", + "core.all": "모두", "core.allparticipants": "모든 참가자", "core.android": "안드로이드", "core.answer": "대답", @@ -972,7 +997,6 @@ "core.courses.cannotretrievemorecategories": "{{$a}} 레벨보다 더 깊은 카테고리는 검색 할 수 없습니다.", "core.courses.categories": "강좌 범주", "core.courses.confirmselfenrol": "이 강좌에 자신을 등록 하시겠습니까?", - "core.courses.courseoverview": "강좌 개요", "core.courses.courses": "강좌", "core.courses.downloadcourses": "다운로드된 강좌", "core.courses.enrolme": "등록하기", @@ -983,8 +1007,8 @@ "core.courses.filtermycourses": "내 강좌 필터링", "core.courses.frontpage": "시작 페이지", "core.courses.mycourses": "내 강좌", + "core.courses.mymoodle": "내 공부방", "core.courses.nocourses": "볼 수 있는 강좌 정보가 없습니다.", - "core.courses.nocoursesoverview": "강좌 없음", "core.courses.nocoursesyet": "검색된 결과가 없습니다.", "core.courses.nosearchresults": "결과 없음", "core.courses.notenroled": "이 강좌에 등록되지 않았습니다.", @@ -1115,7 +1139,7 @@ "core.login.createaccount": "새 계정 만들기", "core.login.createuserandpass": "아이디와 비밀번호 생성", "core.login.credentialsdescription": "로그인하려면 사용자 이름과 비밀번호를 입력하십시오.", - "core.login.emailconfirmsent": "

이메일은 {{$a}}

귀하의 주소로 발송되었습니다. 이메일에는 등록을 완료하기 위한 간단한 안내가 포함되어 있습니다.

문제가 계속되면 사이트 관리자에게 문의하십시오.

", + "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을 확인하십시오.", @@ -1128,7 +1152,7 @@ "core.login.helpmelogin": "

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

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

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

>", "core.login.instructions": "안내문", "core.login.invalidaccount": "로그인 세부 정보를 확인하거나 사이트 관리자에게 사이트 구성을 확인하십시오.", - "core.login.invaliddate": "잘못된 날짜", + "core.login.invaliddate": "유효하지 않은 날짜", "core.login.invalidemail": "쓸 수 없는 이메일 주소", "core.login.invalidmoodleversion": "잘못된 무들 버전입니다. 최소 버전은 2.4입니다.", "core.login.invalidsite": "사이트 URL이 유효하지 않습니다.", @@ -1146,6 +1170,7 @@ "core.login.missingfirstname": "빠짐: 성", "core.login.missinglastname": "빠짐: 이름", "core.login.mobileservicesnotenabled": "사이트에서 모바일 액세스를 사용하도록 설정하지 않았습니다. 활성화해야 한다고 생각되면 사이트 관리자에게 문의하십시오.", + "core.login.mustconfirm": "로그인 계정을 확인하세요.", "core.login.newaccount": "새 계정", "core.login.newsitedescription": "무들 사이트의 URL을 입력하십시오. 이 앱으로 작동하도록 구성되지 않았을 수 있습니다.", "core.login.notloggedin": "로그인 해야 합니다.", @@ -1189,8 +1214,6 @@ "core.mainmenu.changesite": "사이트 변경", "core.mainmenu.help": "도움", "core.mainmenu.logout": "로그아웃", - "core.mainmenu.mycourses": "내 강좌", - "core.mainmenu.togglemenu": "토글 메뉴", "core.mainmenu.website": "웹 사이트", "core.maxsizeandattachments": "파일의 최대 크기: {{$a.size}}, 최대 첨부 파일 갯수: {{$a.attachments}}", "core.min": "분", @@ -1272,8 +1295,10 @@ "core.question.requiresgrading": "채점 필요", "core.redirectingtosite": "사이트로 리디렉션됩니다.", "core.refresh": "새로고침", + "core.remove": "삭제", "core.required": "필수사항", "core.requireduserdatamissing": "이 사용자에게는 필요한 프로필 데이터가 없습니다. 사이트에 데이터를 입력하고 다시 시도하십시오.
{{$a}}", + "core.resources": "참고자료", "core.restore": "복구", "core.retry": "다시 시도", "core.search": "검색", @@ -1329,6 +1354,19 @@ "core.sizetb": "TB", "core.sorry": "죄송합니다.", "core.sortby": "정렬", + "core.strftimedate": "%Y년 %B %d일", + "core.strftimedatefullshort": "%y/%m/%d", + "core.strftimedateshort": "%B%d일", + "core.strftimedatetime": "%Y년 %B %d일, %p %I:%M", + "core.strftimedatetimeshort": "%Y/%m/%d %H:%M", + "core.strftimedaydate": "%Y년 %B %d일, %A", + "core.strftimedaydatetime": "%Y년 %B %d일, %A, %p %I:%M", + "core.strftimedayshort": "%B %d일, %A", + "core.strftimedaytime": "%a,%H:%M", + "core.strftimemonthyear": "%Y년 %B", + "core.strftimerecent": "%b %d일, %H:%M", + "core.strftimerecentfull": "%Y년 %b %d일, %a, %p %I:%M", + "core.strftimetime": "%p %I:%M", "core.submit": "제출", "core.success": "성공", "core.tablet": "태블릿", diff --git a/src/assets/lang/lt.json b/src/assets/lang/lt.json index b9f770c3c..3903b65dd 100644 --- a/src/assets/lang/lt.json +++ b/src/assets/lang/lt.json @@ -1,4 +1,5 @@ { + "addon.badges.alignment": "Kompetencija", "addon.badges.badgedetails": "Pasiekimo detalės", "addon.badges.badges": "Pasiekimai", "addon.badges.contact": "Kontaktas", @@ -7,9 +8,38 @@ "addon.badges.expirydate": "Galiojimo laikas", "addon.badges.issuancedetails": "Pasiekimo galiojimas", "addon.badges.issuerdetails": "Suteikėjo detalesnė informacija", + "addon.badges.issueremail": "El. paštas", "addon.badges.issuername": "Suteikėjo vardas", + "addon.badges.issuerurl": "Suteikėjo URL", + "addon.badges.language": "Kalba", "addon.badges.nobadges": "Nėra sukurtų pasiekimų.", "addon.badges.recipientdetails": "Informacija apie gavėją", + "addon.badges.version": "Versija", + "addon.badges.warnexpired": "(Šis pasiekimas baigė galioti!)", + "addon.block_activitymodules.pluginname": "Veiklos", + "addon.block_myoverview.all": "Visi", + "addon.block_myoverview.favourites": "Pažymėta žvaigždute", + "addon.block_myoverview.future": "Būsimi", + "addon.block_myoverview.hiddencourses": "Paslėptas", + "addon.block_myoverview.inprogress": "Vykstantys", + "addon.block_myoverview.morecourses": "Daugiau kursų", + "addon.block_myoverview.nocourses": "Nėra kursų", + "addon.block_myoverview.past": "Pasibaigę", + "addon.block_myoverview.pluginname": "Kursų apžvalga", + "addon.block_myoverview.title": "Kurso pavadinimas", + "addon.block_recentlyaccessedcourses.nocourses": "Nėra paskutinių kursų", + "addon.block_recentlyaccessedcourses.pluginname": "Neseniai aplankyti kursai", + "addon.block_recentlyaccesseditems.noitems": "Nėra paskutinių elementų", + "addon.block_sitemainmenu.pluginname": "Pagrindinis meniu", + "addon.block_starredcourses.nocourses": "Nėra pažymėtų kursų", + "addon.block_starredcourses.pluginname": "Pažymėti kursai", + "addon.block_timeline.next30days": "Sekančios 30 dienų", + "addon.block_timeline.next3months": "Sekantys 3 mėnesiai", + "addon.block_timeline.next6months": "Sekantys 6 mėnesiai", + "addon.block_timeline.next7days": "Sekančios 7 dienos", + "addon.block_timeline.pluginname": "Laiko juosta", + "addon.block_timeline.sortbycourses": "Rikiuoti pagal kursus", + "addon.block_timeline.sortbydates": "Rikiuoti pagal datas", "addon.calendar.calendar": "Kalendorius", "addon.calendar.calendarevents": "Renginių kalendorius", "addon.calendar.errorloadevent": "Klaida įkeliant renginį.", @@ -66,22 +96,22 @@ "addon.competency.xcompetenciesproficientoutofyincourse": "Įgijote {{$a.x}} iš {{$a.y}} kompetenciją šiame kurse.", "addon.coursecompletion.complete": "Visas", "addon.coursecompletion.completecourse": "Užbaigti kursus", - "addon.coursecompletion.completed": "Užbaigta", - "addon.coursecompletion.completiondate": "Užbaigimo data", + "addon.coursecompletion.completed": "Baigtas", + "addon.coursecompletion.completiondate": "Baigtumo data", "addon.coursecompletion.completionmenuitem": "Užbaigimas", "addon.coursecompletion.couldnotloadreport": "Nepavyko įkelti kursų baigimo ataskaitos, prašome pabandyti vėliau.", - "addon.coursecompletion.coursecompletion": "Kursų užbaigimas", + "addon.coursecompletion.coursecompletion": "Kurso baigimas", "addon.coursecompletion.criteria": "Kriterijai", "addon.coursecompletion.criteriagroup": "Kriterijų grupė", - "addon.coursecompletion.criteriarequiredall": "Visi žemiau esantys kriterijai yra privalomi", - "addon.coursecompletion.criteriarequiredany": "Kiekvienas kriterijus, esantis žemiau, yra privalomas", + "addon.coursecompletion.criteriarequiredall": "Visi žemiau pateikti kriterijai yra būtini", + "addon.coursecompletion.criteriarequiredany": "Bet kuris žemiau pateiktas kriterijus yra būtinas", "addon.coursecompletion.inprogress": "Nebaigta", - "addon.coursecompletion.manualselfcompletion": "Savarankiško mokymosi vadovas", - "addon.coursecompletion.notyetstarted": "Nepradėta", + "addon.coursecompletion.manualselfcompletion": "Savas užbaigimas neautomatiniu būdu", + "addon.coursecompletion.notyetstarted": "Dar nepradėta", "addon.coursecompletion.pending": "Laukiama", - "addon.coursecompletion.required": "Privaloma", - "addon.coursecompletion.requiredcriteria": "Privalomi kriterijai", - "addon.coursecompletion.requirement": "Būtina sąlyga", + "addon.coursecompletion.required": "Būtina", + "addon.coursecompletion.requiredcriteria": "Būtini kriterijai", + "addon.coursecompletion.requirement": "Reikalavimas", "addon.coursecompletion.status": "Būsena", "addon.coursecompletion.viewcoursereport": "Peržiūrėti kursų ataskaitą", "addon.files.couldnotloadfiles": "Negalima užkrauti failų sąrašo.", @@ -92,11 +122,13 @@ "addon.files.sitefiles": "Svetainės failai", "addon.messageoutput_airnotifier.processorsettingsdesc": "Prietaisų konfigūravimas", "addon.messages.addcontact": "Įtraukti kontaktą", - "addon.messages.blockcontact": "Blokuoti kontaktą", + "addon.messages.addtoyourcontacts": "Įtraukti į kontaktus", "addon.messages.blocknoncontacts": "Neleisti neįtrauktiems į kontaktų sąrašą asmenims siųsti man žinutes", + "addon.messages.contactblocked": "Užblokuotas kontaktas", "addon.messages.contactlistempty": "Kontaktų sąrašas tuščias", "addon.messages.contactname": "Kontaktas", "addon.messages.contacts": "Kontaktai", + "addon.messages.deleteallconfirm": "Ar tikrai norite ištrinti visą pokalbį?", "addon.messages.errordeletemessage": "Klaida trinant žinutes.", "addon.messages.errorwhileretrievingcontacts": "Klaida nuskaitant kontaktus iš serverio.", "addon.messages.errorwhileretrievingdiscussions": "Klaida nuskaitant diskusijas iš serverio.", @@ -106,16 +138,20 @@ "addon.messages.messagepreferences": "Žinučių nuostatos", "addon.messages.messages": "Žinutės", "addon.messages.newmessage": "Nauja žinutė", - "addon.messages.nomessages": "Nėra žinučių", + "addon.messages.nofavourites": "Nėra pažymėtų žvaigždute žinučių", + "addon.messages.nomessagesfound": "Nerasta žinučių", + "addon.messages.noncontacts": "Neįtraukti į kontaktus", "addon.messages.nousersfound": "Vartotojas nerastas", "addon.messages.removecontact": "Pašalinti kontaktą", + "addon.messages.removefromyourcontacts": "Pašalinti iš kontaktų", + "addon.messages.searchcombined": "Ieškoti žmonių ir žinučių", "addon.messages.type_blocked": "Užblokuota", "addon.messages.type_offline": "Neprisjungęs", "addon.messages.type_online": "Prisijungęs", "addon.messages.type_search": "Paieška", "addon.messages.type_strangers": "Kita", - "addon.messages.unblockcontact": "Atblokuoti kontaktą", "addon.messages.warningmessagenotsent": "Žinutė {{user}} vartotojui neišsiųsta. {{error}}", + "addon.messages.you": "Jūs:", "addon.mod_assign.acceptsubmissionstatement": "Priimkite pateiktą pranešimą.", "addon.mod_assign.addattempt": "Leisti dar vieną bandymą", "addon.mod_assign.addnewattempt": "Pridėti naują bandymą", @@ -153,6 +189,7 @@ "addon.mod_assign.graded": "Įvertinta", "addon.mod_assign.gradedby": "Įvertino", "addon.mod_assign.gradedon": "Įvertinta", + "addon.mod_assign.gradelocked": "Šis įvertis yra užrakintas arba pakeistas įverčių knygelėje.", "addon.mod_assign.gradeoutof": "Įvertinimas (iš galimų {{$a}})", "addon.mod_assign.gradingstatus": "Vertinimo būsena", "addon.mod_assign.groupsubmissionsettings": "Grupinio pateikimo nustatymai", @@ -166,6 +203,8 @@ "addon.mod_assign.markingworkflowstatereadyforrelease": "Paruošta pateikimui", "addon.mod_assign.markingworkflowstatereadyforreview": "Vertinimas baigtas", "addon.mod_assign.markingworkflowstatereleased": "Pateikta", + "addon.mod_assign.modulenameplural": "Užduotis", + "addon.mod_assign.multipleteams": "Daugiau nei vienos grupės narys", "addon.mod_assign.noattempt": "Nebandyta", "addon.mod_assign.nomoresubmissionsaccepted": "Tik besimokantieji, kuriems pratęstas įkėlimas vis dar gali įkelti savo darbus", "addon.mod_assign.noonlinesubmissions": "Ši užduotis nereikalauja nieko įkelti į svetainę", @@ -217,6 +256,7 @@ "addon.mod_assign_submission_file.pluginname": "Failų pateikimai", "addon.mod_assign_submission_onlinetext.pluginname": "Internetinio teksto pateikimai", "addon.mod_book.errorchapter": "Knygos skyriaus skaitymo klaida.", + "addon.mod_book.modulenameplural": "Knygos", "addon.mod_chat.beep": "Pyp", "addon.mod_chat.currentusers": "Dabartiniai naudotojai", "addon.mod_chat.enterchat": "Spustelėkite čia, kad prisijungtumėte prie pokalbio dabar", @@ -229,6 +269,7 @@ "addon.mod_chat.messagebeepsyou": "{{$a}} ką tik pyptelėjo jums!", "addon.mod_chat.messageenter": "{{$a}} ką tik prisijungė prie šio pokalbio", "addon.mod_chat.messageexit": "{{$a}} ką tik paliko šį pokalbį", + "addon.mod_chat.modulenameplural": "Pokalbiai", "addon.mod_chat.mustbeonlinetosendmessages": "Norėdamas išsiųsti žinutę, turite būti prisijungęs.", "addon.mod_chat.nomessages": "Žinučių dar nėra", "addon.mod_chat.send": "Siųsti", @@ -239,6 +280,7 @@ "addon.mod_choice.errorgetchoice": "Klaida gaunant alternatyvius duomenis.", "addon.mod_choice.expired": "Atsiprašome, veikla uždaryta {{$a}} ir nebegalima", "addon.mod_choice.full": "(Visas)", + "addon.mod_choice.modulenameplural": "Pasirinkimai", "addon.mod_choice.noresultsviewable": "Rezultatai dabar nematomi.", "addon.mod_choice.notopenyet": "Apgailestaujame, ši veikla negalima iki {{$a}}", "addon.mod_choice.numberofuser": "Atsakymų skaičius", @@ -274,8 +316,10 @@ "addon.mod_data.errormustsupplyvalue": "Privalote pateikti reikšmę čia.", "addon.mod_data.expired": "Apgailestaujame, ši veikla uždaryta {{$a}} ir nebegalima", "addon.mod_data.fields": "Laukai", + "addon.mod_data.foundrecords": "Rasta įrašų: {{$a.num}}/{{$a.max}} (Nustatyti filtrus iš naujo)", "addon.mod_data.latlongboth": "Abu, platuma ir ilguma, yra privalomi.", "addon.mod_data.menuchoose": "Pasirinkite...", + "addon.mod_data.modulenameplural": "Duomenų bazės", "addon.mod_data.more": "Daugiau", "addon.mod_data.nomatch": "Nerasta atitinkančių įrašų!", "addon.mod_data.norecords": "Duomenų bazėje nėra įrašų", @@ -305,6 +349,7 @@ "addon.mod_feedback.feedbackopen": "Atidaryti atsiliepimą", "addon.mod_feedback.mapcourses": "Susieti atsiliepimą su kursais", "addon.mod_feedback.mode": "Režimas", + "addon.mod_feedback.modulenameplural": "Atsiliepimas", "addon.mod_feedback.next_page": "Kitas puslapis", "addon.mod_feedback.non_anonymous": "Naudotojo vardas bus užregistruotas ir rodomas su atsakymais", "addon.mod_feedback.non_anonymous_entries": "Neanoniminiai įrašai ({{$a}})", @@ -325,6 +370,7 @@ "addon.mod_feedback.started": "Pradėjo", "addon.mod_feedback.this_feedback_is_already_submitted": "Jau užbaigėte šią veiklą.", "addon.mod_folder.emptyfilelist": "Nėra ką rodyti.", + "addon.mod_folder.modulenameplural": "Aplankai", "addon.mod_forum.addanewdiscussion": "Įtraukti naują diskusijų temą", "addon.mod_forum.addanewquestion": "Įtraukti naują klausimą", "addon.mod_forum.addanewtopic": "Įtraukti naują temą", @@ -347,6 +393,7 @@ "addon.mod_forum.modeflatnewestfirst": "Rodyti atsakymus standartiškai, pirmiausia rodant naujausią", "addon.mod_forum.modeflatoldestfirst": "Rodyti atsakymus standartiškai, pirmiausia rodant seniausią", "addon.mod_forum.modenested": "Rodyti atsakymus įdėtuoju formatu", + "addon.mod_forum.modulenameplural": "Forumai", "addon.mod_forum.numdiscussions": "Pokalbių {{numdiscussions}}", "addon.mod_forum.numreplies": "Atsakymų {{numreplies}}", "addon.mod_forum.posttoforum": "Skelbti forume", @@ -379,9 +426,11 @@ "addon.mod_glossary.fillfields": "Sąvokos ir apibrėžimo laukus būtina užpildyti.", "addon.mod_glossary.fullmatch": "Sieti tik visiškai atitinkančią žodžio formą", "addon.mod_glossary.linking": "Automatinis susiejimas", + "addon.mod_glossary.modulenameplural": "Žodynai", "addon.mod_glossary.noentriesfound": "Įrašų nėra.", "addon.mod_glossary.searchquery": "Paieškos eilutė", "addon.mod_imscp.deploymenterror": "Turinio paketo klaida!", + "addon.mod_imscp.modulenameplural": "IMS turinio paketai", "addon.mod_imscp.showmoduledescription": "Apibūdinimas", "addon.mod_lesson.answer": "Atsakyti", "addon.mod_lesson.attempt": "Bandymas: {{$a}}", @@ -422,6 +471,7 @@ "addon.mod_lesson.lowtime": "Trumpa trukmė", "addon.mod_lesson.maximumnumberofattemptsreached": "Pasiektas maksimalus bandymų skaičius. Perkeliama į kitą puslapį.", "addon.mod_lesson.modattemptsnoteacher": "Besimokančiųjų peržiūrą gali naudoti tik besimokantieji.", + "addon.mod_lesson.modulenameplural": "Pamokos", "addon.mod_lesson.noanswer": "Nepateiktas atsakymas. Grįžkite atgal ir pateikite atsakymą.", "addon.mod_lesson.nolessonattempts": "Šioje pamokoje dar nebuvo bandymų.", "addon.mod_lesson.nolessonattemptsgroup": "{{$a}} grupės nariai/ių šioje pamokoje nieko nebandė atlikti.", @@ -462,7 +512,9 @@ "addon.mod_lti.errorgetlti": "Klaida gaunant modulio duomenis.", "addon.mod_lti.errorinvalidlaunchurl": "Pateikras URL negalioja.", "addon.mod_lti.launchactivity": "Pateikti veiksmus", + "addon.mod_lti.modulenameplural": "Išoriniai įrankiai", "addon.mod_page.errorwhileloadingthepage": "Klaiuda užkraunant puslapį.", + "addon.mod_page.modulenameplural": "Puslapiai", "addon.mod_quiz.attemptfirst": "Pirmasis bandymas", "addon.mod_quiz.attemptlast": "Paskutinis bandymas", "addon.mod_quiz.attemptnumber": "Bandymas", @@ -497,6 +549,7 @@ "addon.mod_quiz.grademethod": "Vertinimo metodas", "addon.mod_quiz.gradesofar": "{{$a.method}}: {{$a.mygrade}} / {{$a.quizgrade}}.", "addon.mod_quiz.marks": "Balai", + "addon.mod_quiz.modulenameplural": "Testai", "addon.mod_quiz.mustbesubmittedby": "Atsakymą turi pateikti {{$a}}.", "addon.mod_quiz.noquestions": "Dar neįtraukta jokių klausimų", "addon.mod_quiz.noreviewattempt": "Jūs negalite peržiūrėti šio bandymo.", @@ -541,6 +594,7 @@ "addon.mod_quiz.yourfinalgradeis": "Jūsų galutinis šio testo įvertis yra {{$a}}.", "addon.mod_resource.errorwhileloadingthecontent": "Klaida užkraunant turinį.", "addon.mod_resource.modifieddate": "Atnaujinta {{$a}}", + "addon.mod_resource.modulenameplural": "Failai", "addon.mod_resource.openthefile": "Atidaryti failą", "addon.mod_resource.uploadeddate": "Įkelta {{$a}}", "addon.mod_scorm.asset": "Turtas", @@ -577,6 +631,7 @@ "addon.mod_scorm.incomplete": "Neužbaigta", "addon.mod_scorm.lastattempt": "Paskutinis bandymas", "addon.mod_scorm.mode": "Režimas", + "addon.mod_scorm.modulenameplural": "SCORM paketai", "addon.mod_scorm.newattempt": "Pradėti naują bandymą", "addon.mod_scorm.noattemptsallowed": "Leidžiamų bandymų skaičius", "addon.mod_scorm.noattemptsmade": "Išnaudotų bandymų skaičius", @@ -596,10 +651,12 @@ "addon.mod_survey.errorgetsurvey": "Klaida gaunant apklausos duomenis.", "addon.mod_survey.ifoundthat": "Sužinojau", "addon.mod_survey.ipreferthat": "Teikiu pirmenybę", + "addon.mod_survey.modulenameplural": "Apklausos", "addon.mod_survey.responses": "Atsakymai", "addon.mod_survey.results": "Rezultatai", "addon.mod_survey.surveycompletednograph": "Jūs baigėte šią apklausą.", "addon.mod_url.accessurl": "URL prieiga", + "addon.mod_url.modulenameplural": "URL", "addon.mod_url.pointingtourl": "URL šaltinis nurodo", "addon.mod_wiki.cannoteditpage": "Jūs negalite redaguoti šio puslapio.", "addon.mod_wiki.createpage": "Kurti puslapį", @@ -608,6 +665,7 @@ "addon.mod_wiki.errornowikiavailable": "Nėra Wiki turinio.", "addon.mod_wiki.gowikihome": "Eiti į Wiki", "addon.mod_wiki.map": "Susieti", + "addon.mod_wiki.modulenameplural": "Vikiai", "addon.mod_wiki.newpagehdr": "Naujas puslapis", "addon.mod_wiki.newpagetitle": "Naujo puslapio pavadinimas", "addon.mod_wiki.nocontent": "Nėra šio puslapio turinio", @@ -645,6 +703,7 @@ "addon.mod_workshop.gradinggradecalculated": "Apskaičiuotas įvertis už įvertinimą", "addon.mod_workshop.gradinggradeof": "Įvertis už įvertinimą (iš {{$a}})", "addon.mod_workshop.gradinggradeover": "Pakeisti įvertį už įvertinimą", + "addon.mod_workshop.modulenameplural": "Seminarai", "addon.mod_workshop.nogradeyet": "Dar nėra įverčio", "addon.mod_workshop.notassessed": "Dar neįvertinta", "addon.mod_workshop.notoverridden": "Nepakeistas", @@ -755,7 +814,7 @@ "assets.countries.CV": "Žaliasis Kyšulys", "assets.countries.CX": "Kalėdų Sala", "assets.countries.CY": "Kipras", - "assets.countries.CZ": "Čekijos Respublika", + "assets.countries.CZ": "Čekija", "assets.countries.DE": "Vokietija", "assets.countries.DJ": "Džibutis", "assets.countries.DK": "Danija", @@ -835,7 +894,7 @@ "assets.countries.LT": "Lietuva", "assets.countries.LU": "Liuksemburgas", "assets.countries.LV": "Latvija", - "assets.countries.LY": "Libijos Arabų Džamahirija", + "assets.countries.LY": "Libija", "assets.countries.MA": "Marokas", "assets.countries.MC": "Monakas", "assets.countries.MD": "Moldovos Respublika", @@ -959,11 +1018,11 @@ "assets.mimetypes.application/vnd.oasis.opendocument.text": "OpenDocument Teksto dokumentas", "assets.mimetypes.application/vnd.oasis.opendocument.text-template": "OpenDocument Teksto šablonas", "assets.mimetypes.application/vnd.oasis.opendocument.text-web": "OpenDocument Tinklalapio šablonas", - "assets.mimetypes.application/vnd.openxmlformats-officedocument.presentationml.presentation": "PowerPoint pateiktis", - "assets.mimetypes.application/vnd.openxmlformats-officedocument.presentationml.slideshow": "„Powerpoint“ prezentacija", - "assets.mimetypes.application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "„Excel“ skaičiuoklė", - "assets.mimetypes.application/vnd.openxmlformats-officedocument.spreadsheetml.template": "Excel šablonas", - "assets.mimetypes.application/vnd.openxmlformats-officedocument.wordprocessingml.document": "Word dokumentas", + "assets.mimetypes.application/vnd.openxmlformats-officedocument.presentationml.presentation": "PowerPoint 2007 pateiktis", + "assets.mimetypes.application/vnd.openxmlformats-officedocument.presentationml.slideshow": "„Powerpoint“ 2007 prezentacija", + "assets.mimetypes.application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "„Excel“ 2007 skaičiuoklė", + "assets.mimetypes.application/vnd.openxmlformats-officedocument.spreadsheetml.template": "Excel 2007 šablonas", + "assets.mimetypes.application/vnd.openxmlformats-officedocument.wordprocessingml.document": "Word 2007 dokumentas", "assets.mimetypes.application/x-iwork-keynote-sffkey": "„iWork Keynote“ pateiktis", "assets.mimetypes.application/x-iwork-numbers-sffnumbers": "„iWork Numbers“ skaičiuoklė", "assets.mimetypes.application/x-iwork-pages-sffpages": "„iWork Pages“ dokumentas", @@ -1002,6 +1061,8 @@ "core.accounts": "Paskyros", "core.add": "Įtraukti", "core.agelocationverification": "Amžiaus ir vietovės patvirtinimas", + "core.ago": "Prieš {{$a}}", + "core.all": "Viskas", "core.allparticipants": "Visi dalyviai", "core.android": "Android", "core.answer": "Atsakymas", @@ -1064,11 +1125,11 @@ "core.course.sections": "Dalys", "core.course.useactivityonbrowser": "Galite naudoti savo naršyklėje.", "core.coursedetails": "Kurso informacija", + "core.courses.addtofavourites": "Pažymėti šį kursą", "core.courses.allowguests": "Į šiuos kursus leidžiama įeiti svečiui", "core.courses.availablecourses": "Kursai", "core.courses.categories": "Kursų kategorijos", "core.courses.confirmselfenrol": "Ar Jūs tikrai norite įsirašyti į šiuos mokymo kursus?", - "core.courses.courseoverview": "Kursų apžvalga", "core.courses.courses": "Kursai", "core.courses.enrolme": "Įrašyti mane", "core.courses.errorloadcourses": "Klaida kraunant kursus.", @@ -1076,28 +1137,23 @@ "core.courses.errorselfenrol": "Klaida įrašant.", "core.courses.filtermycourses": "Filtruoti mano kursus", "core.courses.frontpage": "Pirmas puslapis", - "core.courses.future": "Būsimi", - "core.courses.inprogress": "Vykstantys", - "core.courses.morecourses": "Daugiau kursų", "core.courses.mycourses": "Mano kursai", + "core.courses.mymoodle": "Mano pagrindinis", "core.courses.nocourses": "Nėra rodytinos kursų informacijos", - "core.courses.nocoursesfuture": "Nėra būsimų kursų", - "core.courses.nocoursesinprogress": "Nėra vykstančių kursų", - "core.courses.nocoursesoverview": "Nėra kursų", - "core.courses.nocoursespast": "Nėra pasibaigusių kursų", "core.courses.nocoursesyet": "Nėra šios kategorijos kursų", "core.courses.nosearchresults": "Nėra rezultatų", "core.courses.notenroled": "Nesate įsiregistravęs kaip šių kursų besimokantysis", "core.courses.notenrollable": "Negalite įrašyti savęs į šį kursą.", "core.courses.password": "Įrašymo raktas", - "core.courses.past": "Pasibaigę", "core.courses.paymentrequired": "Už šiuos kursus reikia mokėti.", "core.courses.paypalaccepted": "„PayPal“ mokėjimai priimami", + "core.courses.removefromfavourites": "Nebežymėti šio kurso", "core.courses.search": "Ieškoti", "core.courses.searchcourses": "Ieškoti kursų", "core.courses.searchcoursesadvice": "Jūs galite naudoti kursų paieškos mygtuką ieškant kursų su svečio prieiga arba įrašyti save į kursus, kurie yra prieinami.", "core.courses.selfenrolment": "Savarankiška registracija", "core.courses.sendpaymentbutton": "Siųsti mokėjimą per „PayPal“", + "core.courses.show": "Rodyti šį kursą", "core.courses.totalcoursesearchresults": "Visi kursai: {{$a}}", "core.currentdevice": "Dabartinis prietaisas", "core.datastoredoffline": "Duomenys saugomi įrenginyje, nes šiuo metu negalima išsiųsti. Bus vėlaiu išsiųsti automatiškai.", @@ -1134,6 +1190,7 @@ "core.errorrenamefile": "Klaida pervadinant failą. Bandykite dar kartą.", "core.errorsync": "Klaida sinchronizuojant. Bandykite dar kartą.", "core.errorsyncblocked": "Dėl tebevykstančių procesų {{$a}} negali būti sinchronizuojama dabar. Bandykite dar kartą. Jei nepavyksta, bandykite iš naujo paleisti programą.", + "core.favourites": "Pažymėta žvaigždute", "core.filename": "Failo pavadinimas", "core.filenameexist": "Failas tokiu pavadinimu jau yra: {{$a}}", "core.fileuploader.addfiletext": "Pridėti failą", @@ -1213,7 +1270,7 @@ "core.login.createaccount": "Kurti naują mano paskyrą", "core.login.createuserandpass": "Pasirinkite savo naudotojo vardą ir slaptažodį", "core.login.credentialsdescription": "Prisijungti naudojant vartotojo vardą ir slaptažodį.", - "core.login.emailconfirmsent": "

Informacija išsiųsta Jūsų nurodytu adresu {{$a}}

Tai padės sėkmingai užbaigti registraciją.

Jeigu nepavyksta, susisiekite su svetainės administratoriumi.

", + "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.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", @@ -1226,7 +1283,7 @@ "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", "core.login.invalidaccount": "Prašome patikrinti prisijungimo duomenis arba paprašyti administratoriaus patikrinti svetainės nustatymus.", - "core.login.invaliddate": "Netinkama data", + "core.login.invaliddate": "Neteisinga data", "core.login.invalidemail": "Neteisingas el. pašto adresas", "core.login.invalidmoodleversion": "Negaliojanti Moodle versija. Turi būt ne senesnė nei 2.4.", "core.login.invalidsite": "URL adresas netinkamas.", @@ -1243,6 +1300,7 @@ "core.login.missingfirstname": "Nėra pavadinimo", "core.login.missinglastname": "Nėra pavardės", "core.login.mobileservicesnotenabled": "Mobilios paslaugos neįgalintos. Susisiekite su Moodle svetainės administratoriumi, jei šios paslaugos reikalingos.", + "core.login.mustconfirm": "Turite patvirtinti savo paskyrą", "core.login.newaccount": "Nauja paskyra", "core.login.newsitedescription": "Įveskite Moodle svetainės URL adresą. Atkreipkite dėmesį, kad gali būti nesukonfigūruotas dirbti su šia programėle.", "core.login.notloggedin": "Turite prisijungti", @@ -1283,8 +1341,6 @@ "core.mainmenu.changesite": "Pakeisti svetainę", "core.mainmenu.help": "Žinynas", "core.mainmenu.logout": "Atsijungti", - "core.mainmenu.mycourses": "Mano kursai", - "core.mainmenu.togglemenu": "Perjungti", "core.mainmenu.website": "Interneto svetainė", "core.maxsizeandattachments": "Maksimalus naujo failo dydis: {{$a.size}}, maksimalus priedų skaičius: {{$a.attachments}}", "core.min": "min.", @@ -1314,7 +1370,7 @@ "core.mod_survey": "Apklausa", "core.mod_url": "Asmenys", "core.mod_wiki": "Vikis", - "core.mod_workshop": "Parametrai", + "core.mod_workshop": "Seminaras", "core.moduleintro": "Aprašas", "core.more": "daugiau", "core.mygroups": "Mano grupės", @@ -1341,6 +1397,7 @@ "core.openinbrowser": "Atidaryti naršyklėje", "core.othergroups": "Kitos grupės", "core.pagea": "{{$a}} puslapis", + "core.parentlanguage": "", "core.paymentinstant": "Naudokite toliau pateiktą mygtuką, kad sumokėtumėte ir būtumėte įregistruoti per kelias minutes.", "core.percentagenumber": "{{$a}}%", "core.phone": "Telefonas", @@ -1368,8 +1425,10 @@ "core.quotausage": "Dabar išnaudojama {{$a.used}} Jūsų turimo {{$a.total}} limito.", "core.redirectingtosite": "Būsite nukreiptas į svetainę.", "core.refresh": "Atnaujinti", + "core.remove": "Šalinti", "core.required": "Būtina", "core.requireduserdatamissing": "Trūkta vartotojo duomenų. Prašome užpildyti duomenis Moodle ir pabandyti dar kartą.
{{$a}}", + "core.resources": "Ištekliai", "core.restore": "Atkurti", "core.retry": "Bandykite dar kartą", "core.save": "Įrašyti", @@ -1453,6 +1512,21 @@ "core.sizetb": "TB", "core.sorry": "Atsiprašome...", "core.sortby": "Rūšiuoti pagal", + "core.strftimedate": "%Y %B %d", + "core.strftimedatefullshort": "%y/%m/%d", + "core.strftimedateshort": "%B %d", + "core.strftimedatetime": "%Y %B %d, %H:%M", + "core.strftimedatetimeshort": "%y/%m/%d, %H:%M", + "core.strftimedaydate": "%A, %Y %B %d", + "core.strftimedaydatetime": "%A, %Y %B %d, %H:%M", + "core.strftimedayshort": "%A, %B %d", + "core.strftimedaytime": "%a, %H:%M", + "core.strftimemonthyear": "%Y %B", + "core.strftimerecent": "%b %d, %H:%M", + "core.strftimerecentfull": "%a, %d %b %Y, %H:%M", + "core.strftimetime": "%H:%M", + "core.strftimetime12": "%H:%M", + "core.strftimetime24": "%H:%M", "core.submit": "Pateikti", "core.success": "Sėkmingai", "core.tablet": "Planšetė", @@ -1483,7 +1557,7 @@ "core.user.firstname": "Vardas", "core.user.interests": "Pomėgiai", "core.user.lastname": "Pavardė", - "core.user.manager": "Valdytojas", + "core.user.manager": "Tvarkytojas", "core.user.newpicture": "Naujas paveikslėlis", "core.user.noparticipants": "Nerasta šių kursų dalyvių", "core.user.participants": "Dalyviai", @@ -1491,7 +1565,7 @@ "core.user.phone2": "Mobilusis telefonas", "core.user.roles": "Vaidmenys", "core.user.student": "Besimokantysis", - "core.user.teacher": "Neredaguojantis mokytojas", + "core.user.teacher": "Dėstytojas be redagavimo teisės", "core.user.webpage": "Tinklalapis", "core.userdeleted": "Ši naudotojo paskyra buvo panaikinta", "core.userdetails": "Naudotojo informacija", diff --git a/src/assets/lang/mr.json b/src/assets/lang/mr.json index 7e8baf785..a7af23621 100644 --- a/src/assets/lang/mr.json +++ b/src/assets/lang/mr.json @@ -1,4 +1,6 @@ { + "addon.block_activitymodules.pluginname": "सर्व एक्टिव्हीटी", + "addon.block_sitemainmenu.pluginname": "मुख्य मेनू", "addon.calendar.calendar": "दिनदर्शिका", "addon.calendar.calendarevents": "दिनदर्शिका कार्यक्रम", "addon.calendar.defaultnotificationtime": "सूचना वेळ", @@ -14,23 +16,9 @@ "addon.competency.errornocompetenciesfound": "कोणतीही कौशल्यं आढळली नाहीत", "addon.competency.nocompetencies": "कोणतीही क्षमता नाहीत", "addon.coursecompletion.complete": "पूर्ण", - "addon.coursecompletion.completed": "पूर्ण केले", - "addon.coursecompletion.completiondate": "Completion date", "addon.coursecompletion.couldnotloadreport": "अभ्यासक्रम पूर्ण केल्याचे अहवाल लोड करणे शक्य नाही, कृपया नंतर पुन्हा प्रयत्न करा.", - "addon.coursecompletion.coursecompletion": "अभ्यासक्रम पूर्ण", - "addon.coursecompletion.criteria": "निकष", - "addon.coursecompletion.criteriagroup": "निकष गट", - "addon.coursecompletion.criteriarequiredall": "खालील सर्व निकष आवश्यक आहेत", - "addon.coursecompletion.criteriarequiredany": "खालीलपैकी कोणताही निकष आवश्यक आहेत", - "addon.coursecompletion.inprogress": "प्रगतीपथावर", - "addon.coursecompletion.manualselfcompletion": "स्वयं पूर्ण", - "addon.coursecompletion.notyetstarted": "स्वतःच्या हाताने पूर्ण", - "addon.coursecompletion.pending": "प्रलंबित", - "addon.coursecompletion.required": "आवश्यक", - "addon.coursecompletion.requiredcriteria": "आवश्यक निकष", - "addon.coursecompletion.requirement": "आवश्यकता", - "addon.coursecompletion.status": "स्थिती", - "addon.coursecompletion.viewcoursereport": "अभ्यासक्रम अहवाल पहा", + "addon.coursecompletion.required": "गरजेचे आहे.", + "addon.coursecompletion.status": "दर्जा", "addon.files.couldnotloadfiles": "फायलींची सूची लोड करणे शक्य नाही .", "addon.files.emptyfilelist": "दर्शविण्यासाठी कोणतीही फाईल नाहीत.", "addon.files.erroruploadnotworking": "दुर्दैवाने सध्या आपल्या साइटवर फायली अपलोड करणे शक्य नाही.", @@ -38,8 +26,6 @@ "addon.files.sitefiles": "साईटवरील फाईल्स", "addon.messageoutput_airnotifier.processorsettingsdesc": "डिव्हाइसेस कॉन्फिगर करा", "addon.messages.addcontact": "संपर्क भरा", - "addon.messages.blockcontact": "संपर्क थांबवा", - "addon.messages.blockcontactconfirm": "आपण या संपर्कातून संदेश प्राप्त करणे थांबवाल.", "addon.messages.blocknoncontacts": "माझ्या संपर्क यादीमध्ये नसलेल्या लोकांकडून येणारे सर्व नविन संदेश थांबवा", "addon.messages.contactlistempty": "संपर्क यादी रिक्त आहे", "addon.messages.contactname": "संपर्क नाव", @@ -52,7 +38,7 @@ "addon.messages.messagenotsent": "संदेश पाठविला गेला नाही, कृपया नंतर पुन्हा प्रयत्न करा.", "addon.messages.messages": "संदेश", "addon.messages.newmessages": "नवीन संदेश", - "addon.messages.nomessages": "प्रतीक्षा सुचीमध्ये संदेश नाहीत", + "addon.messages.nomessagesfound": "संदेश सापडले नाहीत", "addon.messages.nousersfound": "कोणतेही वापरकर्ते आढळले नाहीत", "addon.messages.removecontact": "संपर्क काढुन टाका", "addon.messages.removecontactconfirm": "आपल्या संपर्क यादीतून संपर्क काढला जाईल.", @@ -61,7 +47,6 @@ "addon.messages.type_online": "ऑनलाइन", "addon.messages.type_search": "शोध परिणाम", "addon.messages.type_strangers": "इतर", - "addon.messages.unblockcontact": "संपर्क सुरू करा", "addon.messages.warningmessagenotsent": "वापरकर्ता {{user}} ला संदेश पाठवू शकला नाही. {{error}}", "addon.mod_assign.acceptsubmissionstatement": "सबमिशन स्टेटमेंट स्वीकार करा.", "addon.mod_assign.cannoteditduetostatementsubmission": "आपण अनुप्रयोगामध्ये एक सबमिशन जोडू किंवा संपादित करू शकत नाही कारण आम्ही साइटवरून सबमिशन स्टेटमेंट पुनर्प्राप्त करू शकलो नाही.", @@ -86,11 +71,13 @@ "addon.mod_chat.errorwhilegettingchatusers": "गप्पा वापरकर्ते मिळविताना त्रुटी.", "addon.mod_chat.errorwhileretrievingmessages": "सर्व्हरवरून संदेश पुनर्प्राप्त करताना त्रुटी.", "addon.mod_chat.errorwhilesendingmessage": "संदेश पाठवताना त्रुटी.", + "addon.mod_chat.modulenameplural": "संभाषण", "addon.mod_chat.mustbeonlinetosendmessages": "आपल्याला संदेश पाठविण्यासाठी ऑनलाइन असणे आवश्यक आहे", "addon.mod_chat.nomessages": "आजुन पर्यत संदेश नाही", "addon.mod_choice.errorgetchoice": "निवड डेटा मिळवताना त्रुटी.", "addon.mod_choice.expired": "क्षमा करा,ही कार्यक्षमता बंद आहे", "addon.mod_choice.full": "पुर्णपणे", + "addon.mod_choice.modulenameplural": "निवडने", "addon.mod_choice.noresultsviewable": "निकाल उपलब्द नाही", "addon.mod_choice.notopenyet": "क्षमा करा,ही कार्यक्षमता आजुन पर्यत बंद आहे", "addon.mod_choice.removemychoice": "माझी निवड रद्द करा", @@ -114,6 +101,7 @@ "addon.mod_data.errordeleting": "नोंद हटविताना त्रुटी.", "addon.mod_data.fields": "क्षेत्रे", "addon.mod_data.menuchoose": "निवडा", + "addon.mod_data.modulenameplural": "डेटाबेसेस", "addon.mod_data.more": "आधिक", "addon.mod_data.nomatch": "जुळवणी सापडली नाही", "addon.mod_data.norecords": "नोंद डेटाबेस नाही", @@ -184,6 +172,7 @@ "addon.mod_lesson.lowtime": "कमी काळ", "addon.mod_lesson.maximumnumberofattemptsreached": "अधिकाधीक प्रयत्न केले-पुढच्या पानावर जात आहे.", "addon.mod_lesson.modattemptsnoteacher": "विद्यार्थ्यांच्या आढावा फक्त विद्यार्थ्यांसाठीच काम करतो.", + "addon.mod_lesson.modulenameplural": "पाठ", "addon.mod_lesson.noanswer": "उत्तर दीलेले नाही,कृपया पाठीमागे जाऊन उत्तर दाखल करा.", "addon.mod_lesson.nolessonattempts": "ह्या पाठासाठी प्रयत्न केले गेले नाहीत.", "addon.mod_lesson.notcompleted": "पुर्ण केलेले नाही", @@ -244,6 +233,7 @@ "addon.mod_quiz.gradehighest": "उच्चतम श्रेणी", "addon.mod_quiz.grademethod": "श्रेणीची पद्धत", "addon.mod_quiz.marks": "गुण", + "addon.mod_quiz.modulenameplural": "चाचणी परीक्षा", "addon.mod_quiz.noquestions": "आतापर्यंत कोणतेही प्रश्न भरलेले नाहीत", "addon.mod_quiz.opentoc": "नेव्हिगेशन पॉप उघडा", "addon.mod_quiz.overallfeedback": "एकूण प्रतीसाद", @@ -264,6 +254,7 @@ "addon.mod_quiz.warningdatadiscarded": "काही ऑफलाइन उत्तरे टाकण्यात आली कारण प्रश्न ऑनलाइन सुधारित करण्यात आले होते.", "addon.mod_quiz.warningdatadiscardedfromfinished": "प्रयत्न अपूर्ण आहे कारण काही ऑफलाइन उत्तरे काढून टाकण्यात आली. कृपया आपल्या उत्तरांचे पुनरावलोकन करा आणि पुन्हा प्रयत्न पुन्हा सबमिट करा.", "addon.mod_resource.errorwhileloadingthecontent": "सामग्री लोड करताना त्रुटी.", + "addon.mod_resource.modulenameplural": "साधने", "addon.mod_resource.openthefile": "फाईल उघडा", "addon.mod_scorm.asset": "मौल्यवान वस्तू", "addon.mod_scorm.assetlaunched": "मौल्यवान वस्तू बघीतल्या", @@ -295,6 +286,7 @@ "addon.mod_scorm.incomplete": "अपूर्ण", "addon.mod_scorm.lastattempt": "शेवटचा प्रयत्न", "addon.mod_scorm.mode": "पद्धती", + "addon.mod_scorm.modulenameplural": "SCORMs/AICCs", "addon.mod_scorm.newattempt": "नविन प्रयत्न सुरू करा", "addon.mod_scorm.normal": "सामान्य", "addon.mod_scorm.notattempted": "प्रयत्न न केलेले", @@ -316,6 +308,7 @@ "addon.mod_wiki.errorloadingpage": "पृष्ठ लोड करताना त्रुटी आली", "addon.mod_wiki.errornowikiavailable": "या विकीकडे अद्याप कोणतीही सामग्री नाही.", "addon.mod_wiki.gowikihome": "विकीच्या होमला जा", + "addon.mod_wiki.modulenameplural": "Wikis", "addon.mod_wiki.subwiki": "उपविकि", "addon.mod_wiki.titleshouldnotbeempty": "शीर्षक रिक्त असू नये", "addon.mod_wiki.viewpage": "पृष्ठ पहा", @@ -588,6 +581,7 @@ "assets.mimetypes.text/rtf": "RTFचे माहीतीपत्र", "core.accounts": "खाते", "core.add": "जतन करा", + "core.all": "सर्व", "core.allparticipants": "सर्व सहभागी", "core.android": "अँड्रॉइड", "core.answer": "उत्तर", @@ -639,7 +633,6 @@ "core.courses.cannotretrievemorecategories": "{{$a}} स्तरापेक्षा अधिक श्रेणी सुधारली जाऊ शकत नाहीत.", "core.courses.categories": "कोर्सचे गट", "core.courses.confirmselfenrol": "आपली खात्री आहे की आपण या अभ्यासक्रमात आपली नोंदणी करू इच्छिता?", - "core.courses.courseoverview": "कोर्सचा तपशील्", "core.courses.courses": "कोर्सेस्", "core.courses.enrolme": "मला नोंदवा", "core.courses.errorloadcategories": "श्रेण्या लोड करताना त्रुटी आली", @@ -649,6 +642,7 @@ "core.courses.filtermycourses": "माझे अभ्यासक्रम फिल्टर करा", "core.courses.frontpage": "पहीले पान", "core.courses.mycourses": "माझे कोर्सेस", + "core.courses.mymoodle": "माझ मूडल", "core.courses.nocourses": "कोर्सबद्दलची माहीती दाखविण्यासाठी उपलब्ध नाही", "core.courses.nocoursesyet": "ह्या वर्गासाठी कोर्सेस नाहीत", "core.courses.notenrollable": "आपण या अभ्यासक्रमात आपली नोंदणी करू शकत नाही", @@ -781,7 +775,7 @@ "core.login.helpmelogin": "

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

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

वाचण्यासाठी त्यांना विचारा साइट पत्ता फील्डमध्ये मूडलच्या डेमो साइट प्रकार teacher किंवा student मध्ये site address कनेक्ट बटण क्लिक करा . ", "core.login.instructions": "सूचना", "core.login.invalidaccount": "कृपया आपले लॉगिन तपशील तपासा किंवा साइट कॉन्फिगरेशन तपासण्यासाठी आपल्या साइट प्रशासकास विचारा.", - "core.login.invaliddate": "अवैध तारीख", + "core.login.invaliddate": "चुकीचा दिनांक", "core.login.invalidemail": "अयोग्य ई-मेल पत्ता", "core.login.invalidmoodleversion": "अवैध मूडल आवृत्ती आवश्यक किमान आवृत्ती 2.4 आहे.", "core.login.invalidsite": "साइट URL अवैध आहे", @@ -797,6 +791,7 @@ "core.login.missingfirstname": "नाव दिलेले नाही", "core.login.missinglastname": "आडनाव दिलेले नाही", "core.login.mobileservicesnotenabled": "मोबाइल सेवा आपल्या साइटवर सक्षम नाहीत. कृपया मोबाइल प्रवेश सक्षम असावा असे आपल्याला वाटत असल्यास आपल्या मूडल साइट प्रशासकाशी संपर्क साधा", + "core.login.mustconfirm": "तुम्हाला तुमच्या लॉग-ईनची खात्री करावी लागेल.", "core.login.newaccount": "नवीन", "core.login.newsitedescription": "कृपया आपल्या मूडल साइटची URL प्रविष्ट करा लक्षात ठेवा कदाचित या अॅप सह कार्य करण्यासाठी कॉन्फिगर केले जाऊ शकत नाही.", "core.login.notloggedin": "आपल्याला लॉग इन करणे आवश्यक आहे", @@ -835,8 +830,6 @@ "core.mainmenu.changesite": "साइट बदला", "core.mainmenu.help": "मदत", "core.mainmenu.logout": "लॉग-आउट", - "core.mainmenu.mycourses": "माझे कोर्सेस", - "core.mainmenu.togglemenu": "मेनू बदला", "core.mainmenu.website": "वेबसाइट", "core.min": "लहानान लहान", "core.mins": "मिनस", @@ -882,8 +875,10 @@ "core.question.questionmessage": "प्रश्न {{$a}}: {{$b}}", "core.redirectingtosite": "आपल्याला साइटवर पुनर्निर्देशित केले जाईल.", "core.refresh": "रिफ्रेश", + "core.remove": "काढून टाका.", "core.required": "गरजेचे आहे.", "core.requireduserdatamissing": "या वापरकर्त्याकडे काही आवश्यक प्रोफाइल डेटा नसतो. कृपया आपल्या मूडलमध्ये हा डेटा भरा आणि पुन्हा प्रयत्न करा.
{{$ a}}", + "core.resources": "साधने", "core.restore": "पुन्हा साठवून ठेवणे", "core.retry": "पुन्हा प्रयत्न करा", "core.search": "शोध", @@ -956,6 +951,18 @@ "core.sizetb": "TB", "core.sorry": "क्षमस्व ...", "core.sortby": "संकलन", + "core.strftimedate": "%d %B %Y", + "core.strftimedateshort": "%d %B", + "core.strftimedatetime": "%d %B %Y, %I:%M %p", + "core.strftimedatetimeshort": "%d/%m/%y, %H:%M", + "core.strftimedaydate": "%A, %d %B %Y", + "core.strftimedaydatetime": "%A, %d %B %Y, %I:%M %p", + "core.strftimedayshort": "%A, %d %B", + "core.strftimedaytime": "%a, %H:%M", + "core.strftimemonthyear": "%B %Y", + "core.strftimerecent": "%d %b, %H:%M", + "core.strftimerecentfull": "%a, %d %b %Y, %I:%M %p", + "core.strftimetime": "%I:%M %p", "core.submit": "सबमीट", "core.success": "यश", "core.tablet": "टॅब्लेट", @@ -995,7 +1002,7 @@ "core.user.roles": "रोल्स", "core.user.sendemail": "ईमेल", "core.user.student": "विद्यार्थी", - "core.user.teacher": "गैर-संपादन शिक्षक", + "core.user.teacher": "शिक्षक जे काहिच बदल करू शकत नाहित", "core.user.webpage": "वेब पान", "core.userdeleted": "ह्या युजरचे खाते काढून टाकण्यात आले आहे.", "core.users": "सर्व युजर", diff --git a/src/assets/lang/nl.json b/src/assets/lang/nl.json index 4928bae89..c3786f923 100644 --- a/src/assets/lang/nl.json +++ b/src/assets/lang/nl.json @@ -1,15 +1,61 @@ { + "addon.badges.alignment": "Competentie", "addon.badges.badgedetails": "Badgedetails", "addon.badges.badges": "Badges", + "addon.badges.bendorsement": "Goedkeuring", + "addon.badges.claimcomment": "Opmerking bij de goedkeuring", + "addon.badges.claimid": "URL claimen", "addon.badges.contact": "Contact", "addon.badges.dateawarded": "Uitgavedatum", "addon.badges.expired": "Verlopen", "addon.badges.expirydate": "Vervaldatum", + "addon.badges.imageauthoremail": "E-mail van afbeelding auteur", + "addon.badges.imageauthorname": "Naam van afbeelding auteur", + "addon.badges.imageauthorurl": "URL van afbeelding auteur", + "addon.badges.imagecaption": "Afbeeldingsbijschrift", "addon.badges.issuancedetails": "Badge verloopt", "addon.badges.issuerdetails": "Details uitgever", + "addon.badges.issueremail": "E-mail", "addon.badges.issuername": "Naam uitgever", + "addon.badges.issuerurl": "URL uitgever", + "addon.badges.language": "Taal", + "addon.badges.noalignment": "Deze badge heeft geen opgegeven competenties.", "addon.badges.nobadges": "Er zijn geen badges beschikbaar.", + "addon.badges.norelated": "Deze badge heeft geen bijbehorende badges.", "addon.badges.recipientdetails": "Details ontvanger", + "addon.badges.relatedbages": "Gerelateerde badges", + "addon.badges.version": "Versie", + "addon.badges.warnexpired": "(deze badge is verlopen!)", + "addon.block_activitymodules.pluginname": "Activiteiten", + "addon.block_myoverview.all": "Alle", + "addon.block_myoverview.favourites": "Met sterretje", + "addon.block_myoverview.future": "Toekomst", + "addon.block_myoverview.hiddencourses": "Verborgen", + "addon.block_myoverview.inprogress": "Actief", + "addon.block_myoverview.lastaccessed": "Laatst geopend", + "addon.block_myoverview.morecourses": "Meer cursussen", + "addon.block_myoverview.nocourses": "Geen cursussen", + "addon.block_myoverview.past": "Voltooid", + "addon.block_myoverview.pluginname": "Cursusoverzicht", + "addon.block_myoverview.title": "Cursusnaam", + "addon.block_recentlyaccessedcourses.nocourses": "Geen recente cursussen", + "addon.block_recentlyaccessedcourses.pluginname": "Onlangs geopende cursussen", + "addon.block_recentlyaccesseditems.noitems": "Geen recente items", + "addon.block_recentlyaccesseditems.pluginname": "Recent geopende items", + "addon.block_sitemainmenu.pluginname": "Hoofdmenu", + "addon.block_starredcourses.nocourses": "Geen cursussen met ster", + "addon.block_starredcourses.pluginname": "Cursussen met ster", + "addon.block_timeline.duedate": "Uiterste inleverdatum", + "addon.block_timeline.next30days": "Volgende 30 dagen", + "addon.block_timeline.next3months": "Volgende 3 maanden", + "addon.block_timeline.next6months": "Volgende 6 maanden", + "addon.block_timeline.next7days": "Volgende 7 dagen", + "addon.block_timeline.nocoursesinprogress": "Geen lopende cursussen", + "addon.block_timeline.noevents": "Geen geplande activiteiten verschuldigd", + "addon.block_timeline.overdue": "Achterstallig", + "addon.block_timeline.pluginname": "Tijdlijn", + "addon.block_timeline.sortbycourses": "Sorteren op cursussen", + "addon.block_timeline.sortbydates": "Sorteer op datums", "addon.calendar.calendar": "Kalender", "addon.calendar.calendarevents": "Kalendergebeurtenissen", "addon.calendar.defaultnotificationtime": "Standaard notificatietijd", @@ -47,7 +93,7 @@ "addon.competency.evidence_evidenceofpriorlearningunlinked": "Bewijs van leren '{{$a}}' link verwijderd", "addon.competency.evidence_manualoverride": "De competentiebeoordeling werd manueel ingesteld.", "addon.competency.evidence_manualoverrideincourse": "De competentiebeoordeling werd manueel ingesteld in cursus '{{$a}}'.", - "addon.competency.evidence_manualoverrideinplan": "De competentiebeoordeling werd manueel ingesteld in leerplan '{{$a}}'.", + "addon.competency.evidence_manualoverrideinplan": "De competentiebeoordeling werd manueel ingesteld in studieplan '{{$a}}'.", "addon.competency.learningplancompetencies": "Studieplan competenties", "addon.competency.learningplans": "Studieplannen", "addon.competency.myplans": "Mijn studieplannen", @@ -71,7 +117,7 @@ "addon.competency.usercompetencystatus_idle": "Niet aan het werk", "addon.competency.usercompetencystatus_inreview": "Wordt beoordeeld", "addon.competency.usercompetencystatus_waitingforreview": "Wacht op beoordeling", - "addon.competency.userplans": "Leerplan", + "addon.competency.userplans": "Studieplan", "addon.competency.xcompetenciesproficientoutofy": "{{$a.x}} van de {{$a.y}} competenties zijn bekwaam", "addon.competency.xcompetenciesproficientoutofyincourse": "Je bent bekwaam in {{$a.x}} van de {{$a.y}} competenties in deze cursus.", "addon.coursecompletion.complete": "Voltooi", @@ -80,16 +126,16 @@ "addon.coursecompletion.completiondate": "Voltooiingsdatum", "addon.coursecompletion.completionmenuitem": "Voltooien", "addon.coursecompletion.couldnotloadreport": "Kon het voltooiingsrapport van de cursus niet laden. Probeer later opnieuw.", - "addon.coursecompletion.coursecompletion": "Cursus voltooiing", + "addon.coursecompletion.coursecompletion": "Cursus voltooien", "addon.coursecompletion.criteria": "Criteria", "addon.coursecompletion.criteriagroup": "Criteria groep", - "addon.coursecompletion.criteriarequiredall": "Alle onderstaande criteria zijn vereist.", - "addon.coursecompletion.criteriarequiredany": "Alle onderstaande criteria zijn vereist.", + "addon.coursecompletion.criteriarequiredall": "Alle onderstaande criteria zijn vereist", + "addon.coursecompletion.criteriarequiredany": "Al onderstaande criteria zijn vereist", "addon.coursecompletion.inprogress": "Bezig", - "addon.coursecompletion.manualselfcompletion": "Handmatige zelf-voltooiing", - "addon.coursecompletion.notyetstarted": "Nog niet gestart", - "addon.coursecompletion.pending": "Nog bezig", - "addon.coursecompletion.required": "Vereist", + "addon.coursecompletion.manualselfcompletion": "Manueel voltooien", + "addon.coursecompletion.notyetstarted": "Nog niet begonnen", + "addon.coursecompletion.pending": "Bezig", + "addon.coursecompletion.required": "Verplicht", "addon.coursecompletion.requiredcriteria": "Vereiste criteria", "addon.coursecompletion.requirement": "Vereiste", "addon.coursecompletion.status": "Status", @@ -101,34 +147,80 @@ "addon.files.privatefiles": "Privébestanden", "addon.files.sitefiles": "Sitebestanden", "addon.messageoutput_airnotifier.processorsettingsdesc": "Apparaten configureren", + "addon.messages.acceptandaddcontact": "Accepteren en toevoegen aan contactpersonen", "addon.messages.addcontact": "Voeg contactpersoon toe", - "addon.messages.blockcontact": "Blokkeer contactpersoon", - "addon.messages.blockcontactconfirm": "Je zult geen berichten meer krijgen van deze contactpersoon.", + "addon.messages.addcontactconfirm": "Weet je zeker dat je {{$a}} aan je contacten wilt toevoegen?", + "addon.messages.addtofavourites": "Met ster aanduiden", + "addon.messages.addtoyourcontacts": "Voeg toe aan contactpersonen", "addon.messages.blocknoncontacts": "Blokkeer gebruikers die niet op mijn contactenlijst staan", + "addon.messages.blockuser": "Blokkeer gebruiker", + "addon.messages.blockuserconfirm": "Weet je zeker dat je {{$a}} wilt blokkeren?", + "addon.messages.contactableprivacy": "Accepteer berichten van:", + "addon.messages.contactableprivacy_coursemember": "Mijn contacten en iedereen in mijn cursussen", + "addon.messages.contactableprivacy_onlycontacts": "Alleen mijn contacten", + "addon.messages.contactableprivacy_site": "Iedereen op de site", + "addon.messages.contactblocked": "Blokkeer contactpersoon", "addon.messages.contactlistempty": "De contactenlijst is leeg", "addon.messages.contactname": "Naam", + "addon.messages.contactrequestsent": "Contactverzoek verzonden", "addon.messages.contacts": "Contactpersonen", + "addon.messages.decline": "Afwijzen", + "addon.messages.deleteallconfirm": "Weet je zeker, dat je dit gehele gesprek wilt verwijderen? Dit zal het niet verwijderen voor andere deelnemers van dit gesprek.", + "addon.messages.deleteconversation": "Verwijder gesprek", + "addon.messages.deletemessage": "Verwijder bericht", + "addon.messages.deletemessageconfirmation": "Weet je zeker dat je dit bericht wil verwijderen? Het zal alleen van je berichtengeschiedenis gewist worden en zichtbaar blijven voor de gebruiker naar wie je het gestuurd hebt.", "addon.messages.errordeletemessage": "Fout bij het verwijderen van het bericht.", "addon.messages.errorwhileretrievingcontacts": "Fout bij het ophalen van contacten van de server.", "addon.messages.errorwhileretrievingdiscussions": "Fout bij het ophalen van discussies van de server.", "addon.messages.errorwhileretrievingmessages": "Fout bij het ophalen van berichten van de server.", + "addon.messages.errorwhileretrievingusers": "Fout bij het ophalen van gebruikers van de server", + "addon.messages.groupconversations": "Groep", + "addon.messages.groupinfo": "Groepsinfo", + "addon.messages.individualconversations": "Privé", + "addon.messages.info": "Info", + "addon.messages.isnotinyourcontacts": "{{$a}} staat niet in je contacten", "addon.messages.message": "Bericht", "addon.messages.messagenotsent": "Het bericht is niet verzonden. Probeer het later opnieuw.", "addon.messages.messagepreferences": "Berichten voorkeuren", "addon.messages.messages": "Berichten", "addon.messages.newmessage": "Nieuw bericht", "addon.messages.newmessages": "Nieuwe berichten", - "addon.messages.nomessages": "Geen berichten", + "addon.messages.nocontactrequests": "Geen contactverzoeken", + "addon.messages.nocontactsgetstarted": "Geen contacten", + "addon.messages.nofavourites": "Geen als favoriet gemarkeerde berichten", + "addon.messages.nogroupconversations": "Geen groepsberichten", + "addon.messages.noindividualconversations": "Geen privé-berichten", + "addon.messages.nomessagesfound": "Geen berichten gevonden", + "addon.messages.noncontacts": "Geen contactpersoon", "addon.messages.nousersfound": "Geen gebruikers gevonden", + "addon.messages.numparticipants": "{{$a}} deelnemers", "addon.messages.removecontact": "Verwijder contactpersoon", - "addon.messages.removecontactconfirm": "Contact zal verwijderd worden van je contactenlijst.", + "addon.messages.removecontactconfirm": "Weet je zeker dat je {{$a}} uit je contacten wilt verwijderen?", + "addon.messages.removefromfavourites": "Verwijder ster", + "addon.messages.removefromyourcontacts": "Verwijder uit je contacten", + "addon.messages.requests": "Verzoeken", + "addon.messages.requirecontacttomessage": "Je moet {{$a}} vragen om je als contact toe te voegen om berichten te kunnen sturen.", + "addon.messages.searchcombined": "Zoek mensen en berichten", + "addon.messages.searchnocontactsfound": "Geen contacten gevonden", + "addon.messages.searchnomessagesfound": "Geen berichten gevonden", + "addon.messages.searchnononcontactsfound": "Niet gevonden buiten jouw lijst met contactpersonen", + "addon.messages.sendcontactrequest": "Verzend contact-verzoek", + "addon.messages.showdeletemessages": "Toon verwijderde berichten", "addon.messages.type_blocked": "Geblokkeerd", "addon.messages.type_offline": "Offline", "addon.messages.type_online": "Online", "addon.messages.type_search": "Zoekresultaten", "addon.messages.type_strangers": "Anderen", - "addon.messages.unblockcontact": "deblokkeer deze contactpersoon", + "addon.messages.unabletomessage": "Je kunt geen berichten sturen naar deze gebruiker", + "addon.messages.unblockuser": "Deblokkeer gebruiker", + "addon.messages.unblockuserconfirm": "Weet je zeker dat je {{$a}} wilt deblokkeren?", + "addon.messages.userwouldliketocontactyou": "{{$a}} wil graag contact met je opnemen", + "addon.messages.warningconversationmessagenotsent": "Kon bericht(en) niet verzenden naar conversatie {{conversation}}. {{error}}", "addon.messages.warningmessagenotsent": "Kon bericht(en) niet versturen naar gebruiker {{user}}. {{error}}", + "addon.messages.wouldliketocontactyou": "Zou graag contact met je opnemen", + "addon.messages.you": "Jij:", + "addon.messages.youhaveblockeduser": "Je hebt deze gebruiker in het verleden geblokkeerd", + "addon.messages.yourcontactrequestpending": "Je contactverzoek is in behandeling bij {{$a}}", "addon.mod_assign.acceptsubmissionstatement": "Accepteer de uploadvoorwaarden.", "addon.mod_assign.addattempt": "Nog een poging toestaan", "addon.mod_assign.addnewattempt": "Een nieuwe poging toevoegen", @@ -165,7 +257,9 @@ "addon.mod_assign.grade": "Beoordeling", "addon.mod_assign.graded": "Beoordeeld", "addon.mod_assign.gradedby": "Beoordeeld door", + "addon.mod_assign.gradedfollowupsubmit": "Beoordeeld - opvolgingsinzending ontvangen", "addon.mod_assign.gradedon": "Beoordeeld op", + "addon.mod_assign.gradelocked": "Dit cijfer is geblokkeerd of overschreven in de cijferlijst.", "addon.mod_assign.gradenotsynced": "Cijfer niet gesynchroniseerd", "addon.mod_assign.gradeoutof": "Cijfer op {{$a}}", "addon.mod_assign.gradingstatus": "Beoordelingsstatus", @@ -180,6 +274,7 @@ "addon.mod_assign.markingworkflowstatereadyforrelease": "Klaar om vrijgegeven te worden", "addon.mod_assign.markingworkflowstatereadyforreview": "Beoordeling klaar", "addon.mod_assign.markingworkflowstatereleased": "Vrijgegeven", + "addon.mod_assign.modulenameplural": "Opdrachten", "addon.mod_assign.multipleteams": "Lid van meer dan één groep", "addon.mod_assign.multipleteams_desc": "De opdracht vereist insturen in groep. Je bent lid van meer dan één groep. Om te kunnen insturen mag je slechts lid zijn van één groep. Neem contact op met je leraar om je groepslidmaatschap aan te passen.", "addon.mod_assign.noattempt": "Geen poging", @@ -234,6 +329,7 @@ "addon.mod_assign_submission_file.pluginname": "Bestanden insturen", "addon.mod_assign_submission_onlinetext.pluginname": "Online tekst ingestuurde opdrachten", "addon.mod_book.errorchapter": "Fout bij het lezen van het hoofdstuk", + "addon.mod_book.modulenameplural": "Boeken", "addon.mod_chat.beep": "Piep", "addon.mod_chat.currentusers": "Huidige gebruikers", "addon.mod_chat.enterchat": "Klik hier om de chat nu binnen te gaan", @@ -246,6 +342,7 @@ "addon.mod_chat.messagebeepsyou": "{{$a}} heeft je net gepiept!", "addon.mod_chat.messageenter": "{{$a}} is net deze chat binnen gekomen", "addon.mod_chat.messageexit": "{{$a}} heeft deze chat verlaten", + "addon.mod_chat.modulenameplural": "Chats", "addon.mod_chat.mustbeonlinetosendmessages": "Je moet online zijn om berichten te versturen.", "addon.mod_chat.nomessages": "Nog geen berichten", "addon.mod_chat.send": "Stuur", @@ -256,6 +353,7 @@ "addon.mod_choice.errorgetchoice": "Fout bij het ophalen van de keuzegegevens.", "addon.mod_choice.expired": "Sorry, deze activiteit is afgesloten op {{$a}} en is niet meer beschikbaar", "addon.mod_choice.full": "(volledig)", + "addon.mod_choice.modulenameplural": "Keuzes", "addon.mod_choice.noresultsviewable": "De resultaten zijn nu niet zichtbaar.", "addon.mod_choice.notopenyet": "Deze activiteit is niet beschikbaar tot {{$a}}", "addon.mod_choice.numberofuser": "Aantal antwoorden", @@ -293,8 +391,10 @@ "addon.mod_data.errormustsupplyvalue": "Je moet hier een waarde geven.", "addon.mod_data.expired": "Deze activiteit is gesloten op {{$a}} en is niet langer beschikbaar", "addon.mod_data.fields": "Velden", + "addon.mod_data.foundrecords": "Gevonden records: {{$a.num}}/{{$a.max}} (Reset filters)", "addon.mod_data.latlongboth": "Zowel de breedtegraad als de lengtegraad zijn vereist.", "addon.mod_data.menuchoose": "Kies...", + "addon.mod_data.modulenameplural": "Databanken", "addon.mod_data.more": "Meer", "addon.mod_data.nomatch": "Geen overeenkomende items gevonden", "addon.mod_data.norecords": "Geen items in de databank", @@ -326,6 +426,7 @@ "addon.mod_feedback.feedbackopen": "Antwoorden toestaan van", "addon.mod_feedback.mapcourses": "Koppel feedback aan cursussen", "addon.mod_feedback.mode": "Modus", + "addon.mod_feedback.modulenameplural": "Feedbackformulieren", "addon.mod_feedback.next_page": "Volgende pagina", "addon.mod_feedback.non_anonymous": "De gebruikersnamen zullen bewaard worden en samen met de antwoorden getoond worden", "addon.mod_feedback.non_anonymous_entries": "Niet-anonieme items ({{$a}})", @@ -346,6 +447,7 @@ "addon.mod_feedback.started": "Gestart", "addon.mod_feedback.this_feedback_is_already_submitted": "Je hebt dit feedbackformulier is al ingestuurd.", "addon.mod_folder.emptyfilelist": "Geen bestanden.", + "addon.mod_folder.modulenameplural": "Mappen", "addon.mod_forum.addanewdiscussion": "Voeg een nieuw discussieonderwerp toe", "addon.mod_forum.addanewquestion": "Voeg een nieuwe vraag toe", "addon.mod_forum.addanewtopic": "Voeg een nieuw onderwerp toe", @@ -368,6 +470,7 @@ "addon.mod_forum.modeflatnewestfirst": "Laat de antwoorden in één lijst zien, met de nieuwste eerst", "addon.mod_forum.modeflatoldestfirst": "Laat de antwoorden in één lijst zien, met de oudste eerst", "addon.mod_forum.modenested": "Laat de antwoorden 'genest' zien", + "addon.mod_forum.modulenameplural": "Forums", "addon.mod_forum.numdiscussions": "{{numdiscussions}} discussies", "addon.mod_forum.numreplies": "{{numreplies}} antwoorden", "addon.mod_forum.posttoforum": "Plaats op het forum", @@ -403,9 +506,11 @@ "addon.mod_glossary.fillfields": "Concept en definitie zijn verplichte velden.", "addon.mod_glossary.fullmatch": "Alleen volledige woorden gebruiken
(wanneer automatisch gelinkt)", "addon.mod_glossary.linking": "Automatisch linken", + "addon.mod_glossary.modulenameplural": "Woordenlijsten", "addon.mod_glossary.noentriesfound": "Geen items gevonden.", "addon.mod_glossary.searchquery": "Zoekopdracht", "addon.mod_imscp.deploymenterror": "Inhoud pakket fout!", + "addon.mod_imscp.modulenameplural": "IMS inhoudspakketten", "addon.mod_imscp.showmoduledescription": "Toon beschrijving", "addon.mod_lesson.answer": "Antwoord", "addon.mod_lesson.attempt": "Poging: {{$a}}", @@ -449,6 +554,7 @@ "addon.mod_lesson.lowtime": "kortste duur", "addon.mod_lesson.maximumnumberofattemptsreached": "Maximum aantal pogingen bereikt - we gaan verder naar de volgende pagina.", "addon.mod_lesson.modattemptsnoteacher": "Nalezen werkt alleen voor leerlingen", + "addon.mod_lesson.modulenameplural": "Lessen", "addon.mod_lesson.noanswer": "Eén of meerdere vragen zijn niet beantwoord. Ga terug en geef een antwoord.", "addon.mod_lesson.nolessonattempts": "Niemand heeft deze les gemaakt.", "addon.mod_lesson.nolessonattemptsgroup": "Er zijn geen pogingen gedaan door {{$a}} groepsleden voor deze les.", @@ -493,7 +599,9 @@ "addon.mod_lti.errorgetlti": "Fout bij het ophalen van moduledata.", "addon.mod_lti.errorinvalidlaunchurl": "De start-URL is niet geldig", "addon.mod_lti.launchactivity": "Start de activiteit", + "addon.mod_lti.modulenameplural": "Externe tools", "addon.mod_page.errorwhileloadingthepage": "Fout bij het laden van de pagina-inhoud.", + "addon.mod_page.modulenameplural": "Pagina's", "addon.mod_quiz.attemptfirst": "Eerste poging", "addon.mod_quiz.attemptlast": "Laatste poging", "addon.mod_quiz.attemptnumber": "Poging", @@ -528,6 +636,7 @@ "addon.mod_quiz.grademethod": "Beoordelingsmethode", "addon.mod_quiz.gradesofar": "{{$a.method}}: {{$a.mygrade}} / {{$a.quizgrade}}", "addon.mod_quiz.marks": "Punten", + "addon.mod_quiz.modulenameplural": "Testen", "addon.mod_quiz.mustbesubmittedby": "Deze poging moet ingestuurd worden door {{$a}}.", "addon.mod_quiz.noquestions": "Er zijn nog geen vragen toegevoegd", "addon.mod_quiz.noreviewattempt": "Je mag deze poging niet nakijken", @@ -572,6 +681,7 @@ "addon.mod_quiz.yourfinalgradeis": "Je eindcijfer voor deze test is {{$a}}", "addon.mod_resource.errorwhileloadingthecontent": "Fout bij het laden van de inhoud.", "addon.mod_resource.modifieddate": "Gewijzigd {{$a}}", + "addon.mod_resource.modulenameplural": "Bestanden", "addon.mod_resource.openthefile": "Open het bestand", "addon.mod_resource.uploadeddate": "Geüpload {{$a}}", "addon.mod_scorm.asset": "Set", @@ -608,6 +718,7 @@ "addon.mod_scorm.incomplete": "Onvolledig", "addon.mod_scorm.lastattempt": "Laatste voltooide poging", "addon.mod_scorm.mode": "Modus", + "addon.mod_scorm.modulenameplural": "SCORM-pakketten", "addon.mod_scorm.newattempt": "Begin een nieuwe poging", "addon.mod_scorm.noattemptsallowed": "Aantal toegelaten pogingen", "addon.mod_scorm.noattemptsmade": "Aantal pogingen die je gedaan hebt", @@ -627,10 +738,12 @@ "addon.mod_survey.errorgetsurvey": "Fout bij het ophalen van onderzoeksdata.", "addon.mod_survey.ifoundthat": "Ik vond dat", "addon.mod_survey.ipreferthat": "Mijn voorkeur zou zijn", + "addon.mod_survey.modulenameplural": "Onderzoeken", "addon.mod_survey.responses": "Antwoorden", "addon.mod_survey.results": "Resultaten", "addon.mod_survey.surveycompletednograph": "Je hebt dit onderzoek afgerond.", "addon.mod_url.accessurl": "Ga naar de URL", + "addon.mod_url.modulenameplural": "URL's", "addon.mod_url.pointingtourl": "URL waarnaar deze bron verwijst.", "addon.mod_wiki.cannoteditpage": "Je kunt deze pagina niet bewerken.", "addon.mod_wiki.createpage": "Maak pagina", @@ -639,6 +752,7 @@ "addon.mod_wiki.errornowikiavailable": "De wiki heeft nog geen inhoud", "addon.mod_wiki.gowikihome": "Ga naar de Wiki startpagina", "addon.mod_wiki.map": "Map", + "addon.mod_wiki.modulenameplural": "Wiki's", "addon.mod_wiki.newpagehdr": "Nieuwe pagina", "addon.mod_wiki.newpagetitle": "Nieuwe paginatitel", "addon.mod_wiki.nocontent": "Er is geen inhoud voor deze pagina", @@ -677,6 +791,7 @@ "addon.mod_workshop.gradinggradecalculated": "Berekend cijfer voor beoordeling", "addon.mod_workshop.gradinggradeof": "Cijfer voor beoordeling (van {{$a}})", "addon.mod_workshop.gradinggradeover": "Overschrijf cijfer voor beoordeling", + "addon.mod_workshop.modulenameplural": "Workshops", "addon.mod_workshop.nogradeyet": "Nog geen cijfer", "addon.mod_workshop.notassessed": "Nog niet beoordeeld", "addon.mod_workshop.notoverridden": "Niet overschreven", @@ -693,6 +808,7 @@ "addon.mod_workshop.submissiongrade": "Cijfer voor taak", "addon.mod_workshop.submissiongradeof": "Cijfer voor taak (op {{$a}}", "addon.mod_workshop.submissionrequiredcontent": "Je moet wat tekst of een bestand toevoegen.", + "addon.mod_workshop.submissionrequiredtitle": "Je moet een titel ingeven.", "addon.mod_workshop.submissionsreport": "Workshop insturingsrapport", "addon.mod_workshop.submissiontitle": "Titel", "addon.mod_workshop.switchphase10": "Schakel naar opstartfase", @@ -1041,6 +1157,8 @@ "core.accounts": "Gebruikers", "core.add": "Voeg toe", "core.agelocationverification": "Leeftijd- en locatieverificatie", + "core.ago": "{{$a}} geleden", + "core.all": "Alle", "core.allparticipants": "Alle deelnemers", "core.android": "Android", "core.answer": "Antwoord", @@ -1078,7 +1196,7 @@ "core.confirmdeletefile": "Wil je dit bestand echt verwijderen?", "core.confirmloss": "Ben je zeker? Alle wijzigingen zullen verloren gaan.", "core.confirmopeninbrowser": "Wil je het openen in je browser?", - "core.considereddigitalminor": "Je wordt beschouwd als een digitale minderjarige.", + "core.considereddigitalminor": "Je bent te jong om een account te maken op deze site.", "core.content": "Inhoud", "core.contenteditingsynced": "De inhoud die je aan het bewerken bent, is gesynchroniseerd.", "core.contentlinks.chooseaccount": "Kies account", @@ -1108,17 +1226,21 @@ "core.course.errorgetmodule": "Fout bij het ophalen van activiteitsgegevens.", "core.course.hiddenfromstudents": "Verborgen voor leerlingen", "core.course.hiddenoncoursepage": "Beschikbaar, maar niet getoond op de cursuspagina", + "core.course.manualcompletionnotsynced": "Manueel voltooien niet gesynchroniseerd.", "core.course.nocontentavailable": "Geen inhoud beschikbaar.", "core.course.overriddennotice": "Je totaalcijfer voor deze activiteit is manueel aangepast.", + "core.course.refreshcourse": "Cursus vernieuwen", "core.course.sections": "Secties", "core.course.useactivityonbrowser": "Je kunt de activiteit nog steeds via de browser van je apparaat gebruiken.", + "core.course.warningmanualcompletionmodified": "Het manueel voltooien van een activiteit werd gewijzigd op de site.", + "core.course.warningofflinemanualcompletiondeleted": "Sommige van de offline manueel aangeduide voltooiingen van cursus '{{name}}' zijn verwijderd. {{error}}", "core.coursedetails": "Cursusdetails", + "core.courses.addtofavourites": "Sterretje bij deze cursus", "core.courses.allowguests": "In deze cursus zijn gasten toegestaan", "core.courses.availablecourses": "Beschikbare cursussen", "core.courses.cannotretrievemorecategories": "Categorieën dieper dan niveau {{$a}} kunnen niet opgehaald worden.", "core.courses.categories": "Cursuscategorieën", "core.courses.confirmselfenrol": "Weet je zeker dat je je wil aanmelden in deze cursus?", - "core.courses.courseoverview": "Cursusoverzicht", "core.courses.courses": "Cursussen", "core.courses.downloadcourses": "Download cursussen", "core.courses.enrolme": "Meld me aan", @@ -1128,33 +1250,24 @@ "core.courses.errorselfenrol": "Er is een fout opgetreden tijdens het zelf aanmelden.", "core.courses.filtermycourses": "Filter mijn cursussen", "core.courses.frontpage": "Startpagina", - "core.courses.future": "Toekomst", - "core.courses.inprogress": "Actief", - "core.courses.morecourses": "Meer cursussen", + "core.courses.hidecourse": "Verberg", "core.courses.mycourses": "Mijn cursussen", - "core.courses.next30days": "Volgende 30 dagen", - "core.courses.next7days": "Volgende 7 dagen", + "core.courses.mymoodle": "Mijn startpagina", "core.courses.nocourses": "Geen cursusinformatie", - "core.courses.nocoursesfuture": "Geen toekomstige cursussen", - "core.courses.nocoursesinprogress": "Geen actieve cursussen", - "core.courses.nocoursesoverview": "Geen cursussen", - "core.courses.nocoursespast": "Geen voltooide cursussen", "core.courses.nocoursesyet": "Er zijn geen cursussen in deze categorie", - "core.courses.noevents": "Geen geplande activiteiten verschuldigd", "core.courses.nosearchresults": "Geen resultaten", "core.courses.notenroled": "Je bent niet aangemeld in deze cursus", "core.courses.notenrollable": "Je kunt jezelf niet aanmelden in deze cursus.", "core.courses.password": "Aanmeldingssleutel", - "core.courses.past": "Voltooid", "core.courses.paymentrequired": "Betaling vereist om deze cursus binnen te gaan.", "core.courses.paypalaccepted": "Paypal-betalingen aanvaard", + "core.courses.removefromfavourites": "Sterretje van deze cursus verwijderen", "core.courses.search": "Zoek", "core.courses.searchcourses": "Zoek cursussen", "core.courses.searchcoursesadvice": "Je kunt via de cursus zoekknop toegang krijgen als gast of jezelf aanmelden in cursussen die dit toestaan.", "core.courses.selfenrolment": "Zelf aanmelden", "core.courses.sendpaymentbutton": "Stuur betaling via Paypal", - "core.courses.sortbycourses": "Sorteren op cursussen", - "core.courses.sortbydates": "Sorteer op datums", + "core.courses.show": "Toon deze cursus", "core.courses.totalcoursesearchresults": "Totaal aantal cursusen: {{$a}}", "core.currentdevice": "Huidig apparaat", "core.datastoredoffline": "Gegevens die bewaard werden op het toestel konden niet verstuurd worden. Ze zullen later automatisch verzonden worden.", @@ -1174,7 +1287,7 @@ "core.dfmediumdate": "LLL", "core.dftimedate": "h[:]mm A", "core.digitalminor": "Digitale minderjarige.", - "core.digitalminor_desc": "Om een account aan te maken op deze site verzoeken wij je om je ouder / voogd de volgende persoon te laten contacteren.", + "core.digitalminor_desc": "Vraag je ouders / voogd om contact op te nemen met:", "core.discard": "Verwerpen", "core.dismiss": "Verwijderen", "core.done": "Voltooid", @@ -1191,6 +1304,7 @@ "core.errorinvalidform": "Het formulier bevat ongeldige gegevens. Zorg ervoor dat je alle vereiste velden hebt ingevuld en dat de gegevens geldig zijn.", "core.errorinvalidresponse": "Ongeldig antwoord gekregen. Contacteer je Moodle site-beheerder als het probleem zich blijft voordoen.", "core.errorloadingcontent": "Fout bij het laden van inhoud.", + "core.errorofflinedisabled": "Offline browsen is uitgeschakeld op je site. Je moet verbinding hebben met het internet om de app te kunnen gebruiken.", "core.erroropenfilenoapp": "Fout bij het openen van het bestand: geen enkele app gevonden om dit soort bestand te openen.", "core.erroropenfilenoextension": "Fout bij het openen van het bestand: het bestand heeft geen extentie.", "core.erroropenpopup": "Deze activiteit probeert een popup te openen. Dit wordt niet ondersteund in deze app.", @@ -1198,6 +1312,7 @@ "core.errorsync": "Er is een fout opgetreden tijdens het synchroniseren. Probeer opnieuw.", "core.errorsyncblocked": "Deze {{$a}} kan nu niet gesynchroniseerd worden omdat er nog een ander proces anders bezig is. Probeer later opnieuw. Als het probleem blijft aanhouden, probeer dan de app te herstarten.", "core.explanationdigitalminor": "Deze informatie is vereist om te bepalen of je leeftijd hoger is dan de digitale leeftijd die vereist is. Dit is de leeftijd waarop een persoon kan instemmen met de algemene voorwaarden en dat zijn gegevens legaal worden opgeslagen en verwerkt.", + "core.favourites": "Met ster", "core.filename": "Bestandsnaam", "core.filenameexist": "Bestandsnaam bestaat al: {{$a}}", "core.fileuploader.addfiletext": "Bestand toevoegen", @@ -1221,6 +1336,7 @@ "core.fileuploader.more": "Meer", "core.fileuploader.photoalbums": "Foto-albums", "core.fileuploader.readingfile": "Bestand lezen", + "core.fileuploader.readingfileperc": "Lezen bestand: {{$a}}%", "core.fileuploader.selectafile": "Selecteer een bestand", "core.fileuploader.uploadafile": "Bestand uploaden", "core.fileuploader.uploading": "Aan het uploaden", @@ -1279,17 +1395,20 @@ "core.login.createaccount": "Maak mijn nieuwe account aan", "core.login.createuserandpass": "Kies een gebruikersnaam en wachtwoord", "core.login.credentialsdescription": "Geef je gebruikersnaam en wachtwoord op om je aan te melden", - "core.login.emailconfirmsent": "

Er zou een e-mail gestuurd moeten zijn naar jouw adres {{$a}}

Het bericht bevat eenvoudige instructies om je registratie te voltooien.

Als je problemen blijft ondervinden, neem dan contact op met de sitebeheerder.

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

Als het goed is, is er een e-mail verzonden naar {{$a}}

\n

Daarin staan eenvoudige instructies voor het voltooien van de registratie.

\n

Indien je moeilijkheden blijft ondervinden, neem dan contact op met je sitebeheerder.

", + "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.", + "core.login.findyoursite": "Zoek je site", "core.login.firsttime": "Is dit de eerste keer dat je hier bent?", "core.login.forgotten": "Ben je je gebruikersnaam of wachtwoord vergeten?", "core.login.getanothercaptcha": "Vraag een andere CAPTCHA", "core.login.help": "Help", - "core.login.helpmelogin": "

Er zijn veel Moodle sites over de hele wereld. Deze app kan enkel verbinding maken met Moodle sites die expliciet mobiele toegang ingeschakeld hebben.

Als je geen verbinding met jouw Moodle site kunt maken, vraag dan aan de beheerder van jouw site om http://docs.moodle.org/en/Mobile_app te lezen en de toegang in te schakelen.

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

", + "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", "core.login.invalidaccount": "Controleer je logingegevens of vraag je site-beheerder om de site-configuratie te controleren.", "core.login.invaliddate": "Ongeldige datum", @@ -1300,6 +1419,9 @@ "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", @@ -1310,6 +1432,7 @@ "core.login.missingfirstname": "Voornaam ontbreekt", "core.login.missinglastname": "Achternaam ontbreekt", "core.login.mobileservicesnotenabled": "Mobiele diensten zijn niet geactiveerd voor jouw Moodle site. Neem contact op met de sitebeheerder als je denkt dat die zouden moeten ingeschakeld zijn.", + "core.login.mustconfirm": "Je moet je account bevestigen", "core.login.newaccount": "Nieuwe account", "core.login.newsitedescription": "Geef de URL van je Moodle site. Merk op dat die misschien niet geconfigureerd is om met deze app te werken.", "core.login.notloggedin": "Je moet ingelogd zijn.", @@ -1326,15 +1449,19 @@ "core.login.problemconnectingerrorcontinue": "Controleer of je het adres juist ingegeven hebt en probeer opnieuw.", "core.login.profileinvaliddata": "Ongeldige waarde", "core.login.recaptchachallengeimage": "reCAPTCHA challenge afbeelding", + "core.login.recaptchaexpired": "Verificatie verlopen. Beantwoord de beveiligingsvraag opnieuw.", + "core.login.recaptchaincorrect": "Het antwoord op de beveiligingsvraag is fout.", "core.login.reconnect": "Opnieuw verbinden", "core.login.reconnectdescription": "Je authenticatietoken is niet geldig of is verlopen. Je moet opnieuw verbinden met de site.", "core.login.reconnectssodescription": "Je authenticatietoken is niet geldig of is verlopen. Je moet opnieuw verbinden met de site. Je moet je op de site aanmelden via een browservenster.", + "core.login.resendemail": "E-mail opnieuw versturen", "core.login.searchby": "Zoeken op:", "core.login.security_question": "Beveiligingsvraag", "core.login.selectacountry": "Kies een land", "core.login.selectsite": "Selecteer je site:", "core.login.signupplugindisabled": "{{$a}} is niet geactiveerd", "core.login.siteaddress": "Site-adres", + "core.login.sitehasredirect": "Je site bevat minstens één HTTP redirect. De app kan geen redirects volgen. Dit kan het probleem zijn dat je app verhindert om je site te connecteren.", "core.login.siteinmaintenance": "Je Moodle site staat in onderhoudsmodus", "core.login.sitepolicynotagreederror": "Site-beleid niet goedgekeurd.", "core.login.siteurl": "Site URL", @@ -1353,8 +1480,6 @@ "core.mainmenu.changesite": "Naar andere site", "core.mainmenu.help": "Help", "core.mainmenu.logout": "Log uit", - "core.mainmenu.mycourses": "Mijn cursussen", - "core.mainmenu.togglemenu": "Menu schakelen", "core.mainmenu.website": "Website", "core.maxsizeandattachments": "Maximale grootte voor nieuwe bestanden: {{$a.size}}, maximum aantal bijlagen: {{$a.attachments}}", "core.min": "minuut", @@ -1389,6 +1514,7 @@ "core.more": "meer", "core.mygroups": "Mijn groepen", "core.name": "Naam", + "core.networkerroriframemsg": "Deze inhoud is niet beschikbaar offline. Maak verbinding met het internet en probeer opnieuw.", "core.networkerrormsg": "Er was een probleem met het verbinden met de site. Controleer je verbinding en probeer opnieuw.", "core.never": "Nooit", "core.next": "Volgende", @@ -1397,6 +1523,7 @@ "core.nograde": "Nog geen cijfer", "core.none": "Geen", "core.nopasswordchangeforced": "Je kunt niet verdergaan zonder je wachtwoord te veranderen.", + "core.nopermissionerror": "Sorry, maar je hebt nu niet het recht om dit te doen.", "core.nopermissions": "Sorry, maar je hebt nu niet het recht om dat te doen ({{$a}}).", "core.noresults": "Geen resultaten", "core.notapplicable": "n/a", @@ -1421,6 +1548,7 @@ "core.pulltorefresh": "Slepen om te verversen", "core.question.answer": "Antwoord", "core.question.answersaved": "Antwoord bewaard", + "core.question.cannotdeterminestatus": "Kan status niet bepalen", "core.question.certainty": "Zekerheid", "core.question.complete": "Volledig", "core.question.correct": "Juist", @@ -1441,8 +1569,10 @@ "core.quotausage": "Je hebt {{$a.used}} gebruikt van je totale limiet van {{$a.total}}.", "core.redirectingtosite": "Je wordt doorgestuurd naar de site.", "core.refresh": "Vernieuw", + "core.remove": "Verwijder", "core.required": "Verplicht", "core.requireduserdatamissing": "Er ontbreken vereiste gegevens in het profiel van deze gebruiker. Vul deze gegevens in op je Moodle-site en probeer opnieuw.
{{$a}}", + "core.resources": "Bronnen", "core.restore": "Terugzetten", "core.retry": "Probeer opnieuw", "core.save": "Bewaar", @@ -1459,6 +1589,7 @@ "core.settings.appready": "App klaar", "core.settings.cannotsyncoffline": "Kan offline niet synchroniseren.", "core.settings.cannotsyncwithoutwifi": "Kan nu niet synchroniseren omdat er volgens je huidige instellingen alleen over Wi-Fi gesynchroniseerd mag worden. Maak verbinding met een Wi-Fi-netwerk.", + "core.settings.compilationinfo": "Compilatie info", "core.settings.cordovadevicemodel": "Cordova device model", "core.settings.cordovadeviceosversion": "Cordova device OS versie", "core.settings.cordovadeviceplatform": "Cordova device platform", @@ -1466,6 +1597,7 @@ "core.settings.cordovaversion": "Cordova versie", "core.settings.currentlanguage": "Huidige taal", "core.settings.debugdisplay": "Toon meldingen voor foutopsporing", + "core.settings.debugdisplaydescription": "Indien ingeschakeld zal er indien mogelijk meer informatie over de fout getoond worden.", "core.settings.deletesitefiles": "Weet je zeker dat je deze site en gedownloade bestanden ervan wil verwijderen?", "core.settings.deletesitefilestitle": "Verwijder site-bestanden", "core.settings.deviceinfo": "Toestel info", @@ -1496,6 +1628,7 @@ "core.settings.privacypolicy": "Privacy-beleid", "core.settings.reportinbackground": "Fouten automatisch rapporteren", "core.settings.settings": "Instellingen", + "core.settings.showdownloadoptions": "Toon download-opties", "core.settings.sites": "Sites", "core.settings.spaceusage": "Gebruikte ruimte", "core.settings.synchronization": "Synchronisatie", @@ -1527,6 +1660,21 @@ "core.sizetb": "TB", "core.sorry": "Sorry...", "core.sortby": "Sorteer volgens", + "core.strftimedate": "%d %B %Y", + "core.strftimedatefullshort": "%d/%m/%y", + "core.strftimedateshort": "%d %B", + "core.strftimedatetime": "%d %B %Y, %H:%M %p", + "core.strftimedatetimeshort": "%d/%m/%Y %H:%M", + "core.strftimedaydate": "%A, %d %B %Y", + "core.strftimedaydatetime": "%A, %d %B %Y, %H:%M %p", + "core.strftimedayshort": "%A %d %B", + "core.strftimedaytime": "%a, %H:%M", + "core.strftimemonthyear": "%B %Y", + "core.strftimerecent": "%d %b, %H:%M", + "core.strftimerecentfull": "%a, %d %b %Y, %H:%M %p", + "core.strftimetime": "%H:%M %p", + "core.strftimetime12": "%I:%M %p", + "core.strftimetime24": "%H:%M", "core.submit": "Insturen", "core.success": "Succes", "core.tablet": "Tablet", @@ -1556,10 +1704,11 @@ "core.user.editingteacher": "Leraar", "core.user.email": "E-mailadres", "core.user.emailagain": "E-mail (nogmaals)", + "core.user.errorloaduser": "Fout bij het laden van de gebruiker.", "core.user.firstname": "Voornaam", "core.user.interests": "Interesses", "core.user.lastname": "Achternaam", - "core.user.manager": "Manager", + "core.user.manager": "Beheerder", "core.user.newpicture": "Nieuwe foto", "core.user.noparticipants": "Geen gebruikers gevonden in deze cursus", "core.user.participants": "Deelnemers", @@ -1575,6 +1724,9 @@ "core.usernotfullysetup": "Gebruiker niet volledig ingesteld", "core.users": "Gebruikers", "core.view": "Bekijken", + "core.viewcode": "Bekijk code", + "core.vieweditor": "Bekijk editor", + "core.viewembeddedcontent": "Bekijk ingebedde inhoud", "core.viewprofile": "Bekijk profiel", "core.warningofflinedatadeleted": "Offline data van {{component}} '{{name}}' is verwijderd. {{error}}", "core.whatisyourage": "Wat is je leeftijd?", diff --git a/src/assets/lang/no.json b/src/assets/lang/no.json index b52444979..5ebc858fd 100644 --- a/src/assets/lang/no.json +++ b/src/assets/lang/no.json @@ -1,15 +1,61 @@ { + "addon.badges.alignment": "Kompetanse", "addon.badges.badgedetails": "Utmerkelsesdetaljer", "addon.badges.badges": "Utmerkelser", + "addon.badges.bendorsement": "Anbefaling", + "addon.badges.claimcomment": "Anbefaling", + "addon.badges.claimid": "KravURL", "addon.badges.contact": "Kontakt", "addon.badges.dateawarded": "Dato tildelt", "addon.badges.expired": "Utløpt", "addon.badges.expirydate": "Utløpsdato", + "addon.badges.imageauthoremail": "Bildeforfatters epost", + "addon.badges.imageauthorname": "Bildeforfatters navn", + "addon.badges.imageauthorurl": "Bildeforfatterens hjemmeside", + "addon.badges.imagecaption": "Bildetekst", "addon.badges.issuancedetails": "Utløpsdato på utmerkelse", "addon.badges.issuerdetails": "Utstederdetaljer", + "addon.badges.issueremail": "Epost", "addon.badges.issuername": "Navn på utsteder", + "addon.badges.issuerurl": "Utsteders URL", + "addon.badges.language": "Språk", + "addon.badges.noalignment": "Utmerkelsen har ingen spesifiserte kompetanser.", "addon.badges.nobadges": "Det er ingen tilgjengelige utmerkelser.", + "addon.badges.norelated": "Utmerkelsen har ingen relaterte utmerkelser", "addon.badges.recipientdetails": "Mottakerdetaljer", + "addon.badges.relatedbages": "Relaterte anbefalinger", + "addon.badges.version": "Versjon", + "addon.badges.warnexpired": "(Denne utmerkelsen har gått ut på dato!)", + "addon.block_activitymodules.pluginname": "Aktiviteter", + "addon.block_myoverview.all": "Alle", + "addon.block_myoverview.favourites": "Favoritt", + "addon.block_myoverview.future": "Fremtidige", + "addon.block_myoverview.hiddencourses": "Skjult", + "addon.block_myoverview.inprogress": "Aktive", + "addon.block_myoverview.lastaccessed": "Sist besøkt", + "addon.block_myoverview.morecourses": "Flere kurs", + "addon.block_myoverview.nocourses": "Ingen kurs", + "addon.block_myoverview.past": "Avsluttede", + "addon.block_myoverview.pluginname": "Kursoversikt", + "addon.block_myoverview.title": "Kursnavn", + "addon.block_recentlyaccessedcourses.nocourses": "Ingen nylig besøkte kurs", + "addon.block_recentlyaccessedcourses.pluginname": "Nylig besøkte kurs", + "addon.block_recentlyaccesseditems.noitems": "Ingen nylige elementer", + "addon.block_recentlyaccesseditems.pluginname": "Nylig besøkte elementer", + "addon.block_sitemainmenu.pluginname": "Hovedmeny", + "addon.block_starredcourses.nocourses": "Ingen favoritter", + "addon.block_starredcourses.pluginname": "Favoritter", + "addon.block_timeline.duedate": "Frist", + "addon.block_timeline.next30days": "Neste 30 dager", + "addon.block_timeline.next3months": "Neste 3 måneder", + "addon.block_timeline.next6months": "Neste 6 måneder", + "addon.block_timeline.next7days": "Neste 7 dager", + "addon.block_timeline.nocoursesinprogress": "Ingen aktive kurs", + "addon.block_timeline.noevents": "Ingen forestående aktiviteter", + "addon.block_timeline.overdue": "Forsinket", + "addon.block_timeline.pluginname": "Tidslinje", + "addon.block_timeline.sortbycourses": "Sorter på kurs", + "addon.block_timeline.sortbydates": "Sorter på datoer", "addon.calendar.calendar": "Kalender", "addon.calendar.calendarevents": "Kalenderhendelser", "addon.calendar.defaultnotificationtime": "Standard varslingstid", @@ -77,21 +123,21 @@ "addon.coursecompletion.complete": "Fullfør", "addon.coursecompletion.completecourse": "Fullfør kurset", "addon.coursecompletion.completed": "Fullført", - "addon.coursecompletion.completiondate": "Fullført dato", + "addon.coursecompletion.completiondate": "Fullføringsdato", "addon.coursecompletion.completionmenuitem": "Fullføring", "addon.coursecompletion.couldnotloadreport": "Kunne ikke laste kursets avslutningsrapport. Prøv igjen senere.", - "addon.coursecompletion.coursecompletion": "Kursavslutning", - "addon.coursecompletion.criteria": "Kriterium", - "addon.coursecompletion.criteriagroup": "Kriteriagruppe", - "addon.coursecompletion.criteriarequiredall": "Alle kriterier under er påkrevd", - "addon.coursecompletion.criteriarequiredany": "Alle kriterier under er påkrevd", - "addon.coursecompletion.inprogress": "Under arbeid", - "addon.coursecompletion.manualselfcompletion": "Manuell fullføring", - "addon.coursecompletion.notyetstarted": "Ikke startet", - "addon.coursecompletion.pending": "Ventende", + "addon.coursecompletion.coursecompletion": "Kursfullføring", + "addon.coursecompletion.criteria": "Kriterie", + "addon.coursecompletion.criteriagroup": "Kriteriegruppe", + "addon.coursecompletion.criteriarequiredall": "Alle kriteriene under er obligatoriske", + "addon.coursecompletion.criteriarequiredany": "Ethvert kriterium under er obligatorisk", + "addon.coursecompletion.inprogress": "Pågår", + "addon.coursecompletion.manualselfcompletion": "Manuell egenregistrering av fullføring", + "addon.coursecompletion.notyetstarted": "Ikke startet ennå", + "addon.coursecompletion.pending": "Behandles", "addon.coursecompletion.required": "Obligatorisk", - "addon.coursecompletion.requiredcriteria": "Obligatorisk kriterium", - "addon.coursecompletion.requirement": "Krav", + "addon.coursecompletion.requiredcriteria": "Påkrevde kriterier", + "addon.coursecompletion.requirement": "Forutsetninger", "addon.coursecompletion.status": "Status", "addon.coursecompletion.viewcoursereport": "Vis kursrapport", "addon.files.couldnotloadfiles": "Liste med filer kunne ikke vises", @@ -101,34 +147,75 @@ "addon.files.privatefiles": "Private filer", "addon.files.sitefiles": "Nettstedfiler", "addon.messageoutput_airnotifier.processorsettingsdesc": "Konfigurer enheter", + "addon.messages.acceptandaddcontact": "Godta og legg til i kontakter", "addon.messages.addcontact": "Legg til kontakt", - "addon.messages.blockcontact": "Blokker kontakten", - "addon.messages.blockcontactconfirm": "Du vil slutte å motta meldinger fra denne kontakten", + "addon.messages.addcontactconfirm": "Er du sikker på at du ønsker å legge til {{$a}} i dine kontakter?", + "addon.messages.addtofavourites": "Favoritt", + "addon.messages.addtoyourcontacts": "Legg til dine kontakter", "addon.messages.blocknoncontacts": "Blokker alle beskjeder fra folk som ikke er på kontaklisten min", + "addon.messages.blockuser": "Blokker bruker", + "addon.messages.blockuserconfirm": "Er du sikker på at du ønsker å blokkere {{$a}}?", + "addon.messages.contactableprivacy": "Aksepter meldinger fra:", + "addon.messages.contactableprivacy_coursemember": "Mine kontakter og deltakere i mine kurs", + "addon.messages.contactableprivacy_onlycontacts": "Kun kontakter", + "addon.messages.contactableprivacy_site": "Alle registrert brukere", + "addon.messages.contactblocked": "Kontakt blokkert", "addon.messages.contactlistempty": "Kontaktlisten er tom", "addon.messages.contactname": "Navn på kontakt", + "addon.messages.contactrequestsent": "Kontaktforespørsel sendt", "addon.messages.contacts": "Kontakter", + "addon.messages.decline": "Avslå", + "addon.messages.deleteallconfirm": "Er du sikker på at du ønsker å slette hele denne samtalen? Den vil ikke slettes for andre deltakere i samtalen.", + "addon.messages.deleteconversation": "Slett samtale", "addon.messages.errordeletemessage": "Feil ved sletting av melding", "addon.messages.errorwhileretrievingcontacts": "Feil ved henting av kontakter fra server", "addon.messages.errorwhileretrievingdiscussions": "Feil ved henting av diskusjoner fra server", "addon.messages.errorwhileretrievingmessages": "Feil ved henting av meldinger fra server", + "addon.messages.groupconversations": "Gruppe", + "addon.messages.groupinfo": "Gruppeinfo", + "addon.messages.individualconversations": "Privat", + "addon.messages.info": "Info", + "addon.messages.isnotinyourcontacts": "{{$a}} er ikke i dine kontakter", "addon.messages.message": "Melding", "addon.messages.messagenotsent": "Meldingen ble ikke sendt. Prøv igjen senere", "addon.messages.messagepreferences": "Meldingspreferanser", "addon.messages.messages": "Meldinger", "addon.messages.newmessage": "Ny melding", "addon.messages.newmessages": "Nye meldinger", - "addon.messages.nomessages": "Ingen meldinger", + "addon.messages.nocontactrequests": "Ingen kontaktforespørsler", + "addon.messages.nocontactsgetstarted": "Ingen kontakter", + "addon.messages.nofavourites": "Ingen merkede samtaler", + "addon.messages.nogroupconversations": "Ingen gruppesamtaler", + "addon.messages.noindividualconversations": "Ingen private samtaler", + "addon.messages.nomessagesfound": "Ingen meldinger ble funnet", + "addon.messages.noncontacts": "Ikke-kontakter", "addon.messages.nousersfound": "Ingen brukere funnet", + "addon.messages.numparticipants": "{{$a}} deltakere", "addon.messages.removecontact": "Fjern kontakt", - "addon.messages.removecontactconfirm": "Kontakten vil bli fjernet fra kontaktlisten", + "addon.messages.removecontactconfirm": "Er du sikker på at du ønsker å fjerne {{$a}} fra dine kontakter?", + "addon.messages.removefromfavourites": "Fjern fra favoritter", + "addon.messages.removefromyourcontacts": "Fjern fra dine kontakter", + "addon.messages.requests": "Forespørsler", + "addon.messages.requirecontacttomessage": "Du må sende en kontaktforespørsel til {{$a}} for å kunne sende de en melding.", + "addon.messages.searchcombined": "Søk etter mennesker og meldinger", + "addon.messages.searchnocontactsfound": "Fant ingen kontakter", + "addon.messages.searchnomessagesfound": "Fant ingen meldinger", + "addon.messages.searchnononcontactsfound": "Ingen ikke-kontakter funnet.", + "addon.messages.sendcontactrequest": "Send kontaktforespørsel", "addon.messages.type_blocked": "Blokkert", "addon.messages.type_offline": "Offline", "addon.messages.type_online": "Online", "addon.messages.type_search": "Søkeresultat", "addon.messages.type_strangers": "Andre", - "addon.messages.unblockcontact": "Opphev blokkering av kontakt", + "addon.messages.unabletomessage": "Du kan ikke sende meldinger til denne brukeren.", + "addon.messages.unblockuser": "Avblokker bruker", + "addon.messages.unblockuserconfirm": "Er du sikker på at du ønsker å avblokkerer {{$a}}?", + "addon.messages.userwouldliketocontactyou": "{{$a}} ønsker å kontakte deg", "addon.messages.warningmessagenotsent": "Kunne ikke sende melding(er) til {{user}}. {{error}}", + "addon.messages.wouldliketocontactyou": "Ønsker å kontakte deg", + "addon.messages.you": "Du:", + "addon.messages.youhaveblockeduser": "Du har tidligere blokkert denne brukeren", + "addon.messages.yourcontactrequestpending": "Ventende kontaktforespørsel med {{$a}}", "addon.mod_assign.acceptsubmissionstatement": "Bekreft betingelsene", "addon.mod_assign.addattempt": "Tillat et nytt forsøk", "addon.mod_assign.addnewattempt": "Legg til et nytt forsøk", @@ -162,7 +249,9 @@ "addon.mod_assign.grade": "Karakter", "addon.mod_assign.graded": "Karaktersatt", "addon.mod_assign.gradedby": "Vurdert av", + "addon.mod_assign.gradedfollowupsubmit": "Vurdert - oppfølgingsinnlevering mottatt", "addon.mod_assign.gradedon": "Vurdert den", + "addon.mod_assign.gradelocked": "Denne karakteren er låst eller overstyrt i karakterboken.", "addon.mod_assign.gradeoutof": "Karakter av {{$a}} mulige.", "addon.mod_assign.gradingstatus": "Karaktersettingsstatus", "addon.mod_assign.groupsubmissionsettings": "Innstillinger for gruppeinnleveringer", @@ -176,6 +265,7 @@ "addon.mod_assign.markingworkflowstatereadyforrelease": "Klar for publisering", "addon.mod_assign.markingworkflowstatereadyforreview": "Karaktersetting fullført", "addon.mod_assign.markingworkflowstatereleased": "Publisert", + "addon.mod_assign.modulenameplural": "Innlevering", "addon.mod_assign.multipleteams": "Medlem av mer enn en gruppe", "addon.mod_assign.multipleteams_desc": "Denne innleveringen krever gruppeinnlevering. Du er medlem av mer enn en gruppe. For å kunne levere kan du kun være medlem av en enkelt gruppe. Vennligst kontakt læreren din for å endre gruppemedlemsskapene dine.", "addon.mod_assign.noattempt": "Ingen forsøk", @@ -225,6 +315,7 @@ "addon.mod_assign_submission_file.pluginname": "Filinnleveringer", "addon.mod_assign_submission_onlinetext.pluginname": "Online innlevering", "addon.mod_book.errorchapter": "Feil ved lesing av bokkapitlet", + "addon.mod_book.modulenameplural": "Bøker", "addon.mod_chat.beep": "tut", "addon.mod_chat.currentusers": "Nåværende brukere", "addon.mod_chat.enterchat": "Start praten", @@ -234,6 +325,7 @@ "addon.mod_chat.messagebeepsyou": "{{$a}} har tutet på deg", "addon.mod_chat.messageenter": "{{$a}} har akkurat kommet", "addon.mod_chat.messageexit": "{{$a}} har forlatt denne praten", + "addon.mod_chat.modulenameplural": "Praterom", "addon.mod_chat.mustbeonlinetosendmessages": "Du må være på nett for å sende meldinger.", "addon.mod_chat.nomessages": "Ingen beskjeder ennå", "addon.mod_chat.send": "Send", @@ -243,6 +335,7 @@ "addon.mod_choice.choiceoptions": "Valginnstillinger", "addon.mod_choice.expired": "Beklager, denne aktiviteten ble stengt {{$a}} og er ikke tilgjengelig lenger.", "addon.mod_choice.full": "(full)", + "addon.mod_choice.modulenameplural": "Galluper", "addon.mod_choice.noresultsviewable": "Resultatene er ikke synlige akkurat nå.", "addon.mod_choice.notopenyet": "Denne aktiviteten er ikke tilgjengelig før {{$a}}", "addon.mod_choice.numberofuser": "Antall svar", @@ -278,8 +371,10 @@ "addon.mod_data.errormustsupplyvalue": "Du må skrive inn noe her", "addon.mod_data.expired": "Beklager, denne aktiviteten ble stengt {{$a}} og er ikke lenger tilgjengelig", "addon.mod_data.fields": "Felter", + "addon.mod_data.foundrecords": "Funnet poster: {{$a.num}}/{{$a.max}} (Nullstill filter)", "addon.mod_data.latlongboth": "Både lengdegrad og breddegrad må oppgis", "addon.mod_data.menuchoose": "Velg...", + "addon.mod_data.modulenameplural": "Databaser", "addon.mod_data.more": "Mer", "addon.mod_data.nomatch": "Ingen treff blant oppføringene!", "addon.mod_data.norecords": "Ingen oppføringer i databasen", @@ -309,6 +404,7 @@ "addon.mod_feedback.feedbackopen": "Åpne undersøkelsen", "addon.mod_feedback.mapcourses": "Koble Feedback til kurs", "addon.mod_feedback.mode": "Modus", + "addon.mod_feedback.modulenameplural": "Feedback", "addon.mod_feedback.next_page": "Neste side", "addon.mod_feedback.non_anonymous": "Brukerens navn vil bli lagret og vist sammen med svaret.", "addon.mod_feedback.non_anonymous_entries": "Ingen anonyme svar ({{$a}})", @@ -329,6 +425,7 @@ "addon.mod_feedback.started": "Startet", "addon.mod_feedback.this_feedback_is_already_submitted": "Du har allerede fullført denne aktiviteten.", "addon.mod_folder.emptyfilelist": "Det er ingen filer å vise.", + "addon.mod_folder.modulenameplural": "Mapper", "addon.mod_forum.addanewdiscussion": "Skriv i dette forumet", "addon.mod_forum.addanewquestion": "Legg til et nytt spørsmål", "addon.mod_forum.addanewtopic": "Skriv i dette forumet", @@ -351,6 +448,7 @@ "addon.mod_forum.modeflatnewestfirst": "Vis svar flatt, nyeste innlegg først.", "addon.mod_forum.modeflatoldestfirst": "Vis svar flatt, eldste innlegg først.", "addon.mod_forum.modenested": "Vis svar nøstet (Overskrift og innhold)", + "addon.mod_forum.modulenameplural": "Forum", "addon.mod_forum.numdiscussions": "{{numdiscussions}} diskusjoner", "addon.mod_forum.numreplies": "{{numreplies}} svar", "addon.mod_forum.posttoforum": "Legg inn i forumet", @@ -386,9 +484,11 @@ "addon.mod_glossary.fillfields": "'Begrep' og 'Definisjon' er obligatoriske felter.", "addon.mod_glossary.fullmatch": "Bare hele ord blir lenket
(når det lenkes automatisk)", "addon.mod_glossary.linking": "Autolenking", + "addon.mod_glossary.modulenameplural": "Ordbøker", "addon.mod_glossary.noentriesfound": "Ingen oppføringer funnet", "addon.mod_glossary.searchquery": "Søkeord", "addon.mod_imscp.deploymenterror": "Feil i pakkearkivet!", + "addon.mod_imscp.modulenameplural": "IMS innholdspakker", "addon.mod_imscp.showmoduledescription": "Vis beskrivelse", "addon.mod_lesson.answer": "Svaralternativ", "addon.mod_lesson.attempt": "Forsøk: {{$a}}", @@ -429,6 +529,7 @@ "addon.mod_lesson.lowtime": "Svak tid", "addon.mod_lesson.maximumnumberofattemptsreached": "Maks antall forsøk er oppbrukt. Leksjonen fortsetter på neste side.", "addon.mod_lesson.modattemptsnoteacher": "Studenten kan bare lese andre studenters bidrag", + "addon.mod_lesson.modulenameplural": "Leksjoner", "addon.mod_lesson.noanswer": "Ingen svar gitt. Vennligst gå tilbake og avgi et svar.", "addon.mod_lesson.nolessonattempts": "Det finnes ingen forsøk for denne leksjonen", "addon.mod_lesson.nolessonattemptsgroup": "Ingen forsøk har blitt gjort av {{$a}} gruppemedlemmer i denne leksjonen.", @@ -466,6 +567,8 @@ "addon.mod_lesson.youranswer": "Ditt svar", "addon.mod_lesson.yourcurrentgradeisoutof": "Gjeldende karakter er {{$a.grade}} av {{$a.total}}", "addon.mod_lesson.youshouldview": "Du bør besvare minst: {{$a}}", + "addon.mod_lti.modulenameplural": "Eksterne verktøy", + "addon.mod_page.modulenameplural": "Sider", "addon.mod_quiz.attemptfirst": "Første forsøk", "addon.mod_quiz.attemptlast": "Siste forsøk", "addon.mod_quiz.attemptnumber": "Forsøk", @@ -487,6 +590,7 @@ "addon.mod_quiz.grademethod": "Karaktermetode", "addon.mod_quiz.gradesofar": "{{$a.method}}: {{$a.mygrade}} / {{$a.quizgrade}}.", "addon.mod_quiz.marks": "Poeng", + "addon.mod_quiz.modulenameplural": "Quizer", "addon.mod_quiz.mustbesubmittedby": "Dette forsøket må sendes innen: {{$a}}.", "addon.mod_quiz.noquestions": "Ingen spørsmål er lagt til enda", "addon.mod_quiz.noreviewattempt": "Du har ikke tillatelse til å se gjennom dette forsøket.", @@ -526,6 +630,7 @@ "addon.mod_quiz.timetaken": "Totalt brukt tid", "addon.mod_quiz.yourfinalgradeis": "Sluttkarakter på denne quizen ble {{$a}}.", "addon.mod_resource.modifieddate": "Siste endret {{$a}}", + "addon.mod_resource.modulenameplural": "Filer", "addon.mod_resource.uploadeddate": "Lastet opp {{$a}}", "addon.mod_scorm.asset": "Ressurs", "addon.mod_scorm.assetlaunched": "Ressurs - Sett", @@ -551,6 +656,7 @@ "addon.mod_scorm.incomplete": "Ufullstendig", "addon.mod_scorm.lastattempt": "Siste fullførte forsøk", "addon.mod_scorm.mode": "Modus", + "addon.mod_scorm.modulenameplural": "SCORM/AICC-pakker", "addon.mod_scorm.newattempt": "Begynn et nytt forsøk", "addon.mod_scorm.noattemptsallowed": "Maksimum antall forsøk", "addon.mod_scorm.noattemptsmade": "Antall forsøk du har gjort til nå", @@ -562,12 +668,15 @@ "addon.mod_scorm.suspended": "Avbrutt", "addon.mod_survey.ifoundthat": "Jeg fant at", "addon.mod_survey.ipreferthat": "Jeg vil foretrekke at", + "addon.mod_survey.modulenameplural": "Undersøkelser", "addon.mod_survey.responses": "Besvareslser", "addon.mod_survey.surveycompletednograph": "Du har fullført denne undersøkelsen", + "addon.mod_url.modulenameplural": "URL-er", "addon.mod_wiki.cannoteditpage": "Du kan ikke redigere denne siden", "addon.mod_wiki.createpage": "Lag side", "addon.mod_wiki.editingpage": "Endrer denne siden '{{$a}}'", "addon.mod_wiki.map": "Kart", + "addon.mod_wiki.modulenameplural": "Wikier", "addon.mod_wiki.newpagehdr": "Ny side", "addon.mod_wiki.newpagetitle": "Ny sidetittel", "addon.mod_wiki.nocontent": "Denne siden har ikke innhold", @@ -604,6 +713,7 @@ "addon.mod_workshop.gradinggradecalculated": "Beregnet karakter for vurdering", "addon.mod_workshop.gradinggradeof": "Karakter for vurdering (av {{$a}})", "addon.mod_workshop.gradinggradeover": "Overstyr karakter for vurdering", + "addon.mod_workshop.modulenameplural": "Workshoper", "addon.mod_workshop.nogradeyet": "Ingen karakter ennå", "addon.mod_workshop.notassessed": "Ingen vurdert ennå", "addon.mod_workshop.notoverridden": "Ikke overstyrt", @@ -963,6 +1073,8 @@ "core.accounts": "Kontoer", "core.add": "Legg til", "core.agelocationverification": "Bekreftelse av alder og sted", + "core.ago": "for {{$a}} siden", + "core.all": "Alle", "core.allparticipants": "Alle deltakere", "core.answer": "Svar", "core.answered": "Besvart", @@ -996,7 +1108,7 @@ "core.confirmdeletefile": "Er du sikker på at du vil slette denne fila?", "core.confirmloss": "Er du sikker? Du vil miste alle endringer.", "core.confirmopeninbrowser": "Vil du åpne den i nettleser?", - "core.considereddigitalminor": "Du anses som en mindreårig.", + "core.considereddigitalminor": "Du er ikke gammel nok til å lage en bruker på denne portalen.", "core.content": "Innhold", "core.contentlinks.chooseaccount": "Velg konto", "core.contentlinks.chooseaccounttoopenlink": "Velg en konto å åpne lenken med", @@ -1004,39 +1116,32 @@ "core.course": "Kurs", "core.course.contents": "Innhold", "core.course.coursesummary": "Kurssammendrag", + "core.course.downloadcourse": "Last ned kurs", "core.course.hiddenfromstudents": "Skjult for studenter", "core.course.hiddenoncoursepage": "Tilgjengelig, men ikke vist på kurssiden.", "core.course.overriddennotice": "Den endelige karakteren fra denne aktiviteten ble redigert manuelt.", "core.course.sections": "Kategorier", "core.coursedetails": "Kursdetaljer", + "core.courses.addtofavourites": "Merk som favoritt", "core.courses.allowguests": "Dette kurset tillater besøk av gjester", "core.courses.availablecourses": "Tilgjengelige kurs", "core.courses.categories": "Kurskategorier", - "core.courses.courseoverview": "Kursoversikt", "core.courses.courses": "Kurs", "core.courses.frontpage": "Hovedside", - "core.courses.future": "Fremtidige", - "core.courses.inprogress": "Aktive", - "core.courses.morecourses": "Flere kurs", + "core.courses.hidecourse": "Skjult for visning", "core.courses.mycourses": "Mine kurs", - "core.courses.next30days": "Neste 30 dager", - "core.courses.next7days": "Neste 7 dager", + "core.courses.mymoodle": "Min startside", "core.courses.nocourses": "Ingen kursinformasjon å vise.", - "core.courses.nocoursesfuture": "Ingen fremtidige kurs", - "core.courses.nocoursesinprogress": "Ingen aktive kurs", - "core.courses.nocoursesoverview": "Ingen kurs", - "core.courses.nocoursespast": "Ingen avsluttede kurs", "core.courses.nocoursesyet": "Ingen kurs i denne kategorien", "core.courses.nosearchresults": "Ingen resultater", "core.courses.notenroled": "Du er ikke påmeldt dette kurset", - "core.courses.past": "Avsluttede", "core.courses.paymentrequired": "Dette er et kurs som krever betaling", "core.courses.paypalaccepted": "PayPal betalingsformer som er godkjent", + "core.courses.removefromfavourites": "Fjern fra favoritter", "core.courses.search": "Søk", "core.courses.searchcourses": "Søk etter studiesider", "core.courses.sendpaymentbutton": "Send betaling via PayPal", - "core.courses.sortbycourses": "Sorter på kurs", - "core.courses.sortbydates": "Sorter på datoer", + "core.courses.show": "Vis kurs", "core.date": "Dato", "core.day": "dag", "core.days": "dager", @@ -1051,6 +1156,7 @@ "core.edit": "Rediger", "core.error": "Feil", "core.explanationdigitalminor": "Denne informasjonen er påkrevd for å bestemme om din aldere er over lavalderen for samtykke. Dette er alderen for når en person kan samtykke til vilkår og betingelser og at dataene deres på lovlig vis blir lagret og behandlet.", + "core.favourites": "Favoritter", "core.filename": "Filnavn", "core.fileuploader.addfiletext": "Legg til fil", "core.fileuploader.filesofthesetypes": "Aksepterte filtyper:", @@ -1093,12 +1199,15 @@ "core.login.cancel": "Avbryt", "core.login.createaccount": "Lag en ny brukerkonto", "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.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", "core.login.invalidemail": "Feil e-postadresse", "core.login.invalidurl": "Ugyldig URL angitt", "core.login.login": "Logg inn", @@ -1106,16 +1215,18 @@ "core.login.missingemail": "Mangler e-postadresse", "core.login.missingfirstname": "Mangler fornavn", "core.login.missinglastname": "Mangler etternavn", + "core.login.mustconfirm": "Du må godkjenne brukerkontoen din", "core.login.newaccount": "Ny brukerkonto", "core.login.password": "Passord", "core.login.passwordforgotten": "Glemt passord", - "core.login.passwordforgotteninstructions2": "For å tilbakestille passordet må du fylle inn ENTEN brukernavnet ELLER passordet ditt.\n\nDersom vi finner deg i databasen får du en e-post med instruksjoner om hvordan du skal få logget inn.\n\nFår du ingen e-post innen rimelig tid betyr det at e-informasjonen du skrev inn var feil. Har du flere e-postadresser bør du sjekke alle før du kontakter Support.", + "core.login.passwordforgotteninstructions2": "For å tilbakestille passordet må du fylle inn ENTEN brukernavnet ELLER epostadressen ditt.\n\nDersom vi finner deg i databasen får du en e-post med instruksjoner om hvordan du skal få logget inn.\n\nFår du ingen e-post innen rimelig tid betyr det at e-informasjonen du skrev inn var feil. Har du flere e-postadresser bør du sjekke alle før du kontakter Support.", "core.login.policyaccept": "Jeg forstår og godtar", "core.login.policyagree": "Du må godta avtalen for å bruke dette nettstedet. Godtar du den?", "core.login.policyagreement": "Avtale for bruk av portalen", "core.login.policyagreementclick": "Klikk her for å lese bruksavtalen", "core.login.potentialidps": "Klikk på knappen for å logge inn:", "core.login.profileinvaliddata": "Ugyldig verdi", + "core.login.resendemail": "Send epost på nytt", "core.login.security_question": "Sikkerhetsspørmål", "core.login.selectacountry": "Velg et land", "core.login.startsignup": "Registrer deg", @@ -1125,7 +1236,6 @@ "core.login.usernotaddederror": "Bruker ble ikke lagt til - ukjent feil", "core.mainmenu.help": "Hjelp", "core.mainmenu.logout": "Logg ut", - "core.mainmenu.mycourses": "Mine kurs", "core.maxsizeandattachments": "Maks størrelse for nye filer: {{$a.size}}, maks antall vedlegg: {{$a.attachments}}", "core.min": "min", "core.mins": "min", @@ -1197,7 +1307,9 @@ "core.question.requiresgrading": "Karaktersetting påkrevet", "core.quotausage": "Du har brukt {{$a.used}} av din totale grense på {{$a.limit}}", "core.refresh": "Oppdatér", + "core.remove": "Fjern", "core.required": "Obligatorisk", + "core.resources": "Ressurser", "core.restore": "Gjenoppretting", "core.save": "Lagre", "core.search": "Søk", @@ -1233,6 +1345,21 @@ "core.sizekb": "Kb", "core.sizemb": "Mb", "core.sortby": "Sorter etter", + "core.strftimedate": "%d %B %Y", + "core.strftimedatefullshort": "%d/%m/%y", + "core.strftimedateshort": "%d %B", + "core.strftimedatetime": "%d %B %Y, %H:%M", + "core.strftimedatetimeshort": "%d/%m/%Y %H:%M", + "core.strftimedaydate": "%A, %d %B %Y", + "core.strftimedaydatetime": "%A, %d %B %Y, %H:%M", + "core.strftimedayshort": "%A, %d %B", + "core.strftimedaytime": "%a, %H:%M", + "core.strftimemonthyear": "%B %Y", + "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.submit": "Send", "core.success": "Vellykket", "core.teachers": "Lærere", @@ -1248,17 +1375,21 @@ "core.user.country": "Land", "core.user.description": "Beskrivelse", "core.user.details": "Detaljer", + "core.user.editingteacher": "Lærer", "core.user.email": "E-postadresse", "core.user.emailagain": "E-post (igjen)", "core.user.firstname": "Fornavn", "core.user.interests": "Interesser", "core.user.lastname": "Etternavn", + "core.user.manager": "Superbruker", "core.user.newpicture": "Nytt bilde", "core.user.noparticipants": "Fant ingen deltakere for dette kurset", "core.user.participants": "Deltagere", "core.user.phone1": "Telefon", "core.user.phone2": "Mobiltelefon", "core.user.roles": "Roller", + "core.user.student": "Student", + "core.user.teacher": "Lærer uten editor-rettigheter", "core.user.webpage": "Nettside", "core.userdeleted": "Denne brukeren er slettet", "core.userdetails": "Brukerdetaljer", diff --git a/src/assets/lang/pl.json b/src/assets/lang/pl.json index 5d56e6ddc..2f36510d4 100644 --- a/src/assets/lang/pl.json +++ b/src/assets/lang/pl.json @@ -1,15 +1,61 @@ { + "addon.badges.alignment": "Kompetencja", "addon.badges.badgedetails": "Szczegóły odznaki", "addon.badges.badges": "Odznaki", + "addon.badges.bendorsement": "Adnotacja", + "addon.badges.claimcomment": "Komentarz do zatwierdzenia", + "addon.badges.claimid": "URL wniosku", "addon.badges.contact": "Kontakt", "addon.badges.dateawarded": "Data wydania", "addon.badges.expired": "Wygasła", "addon.badges.expirydate": "Data ważności", + "addon.badges.imageauthoremail": "E-mail autora grafiki", + "addon.badges.imageauthorname": "Imię i nazwisko autora grafiki", + "addon.badges.imageauthorurl": "Adres URL autora grafiki", + "addon.badges.imagecaption": "Podpis grafiki", "addon.badges.issuancedetails": "Wygaśnięcie odznaki", "addon.badges.issuerdetails": "Dane wystawcy", + "addon.badges.issueremail": "E-mail", "addon.badges.issuername": "Nazwa wydawcy", + "addon.badges.issuerurl": "URL wydawcy", + "addon.badges.language": "Język", + "addon.badges.noalignment": "Ta odznaka nie ma określonych kompetencji.", "addon.badges.nobadges": "Brak dostępnych odznak", + "addon.badges.norelated": "Ta odznaka nie ma żadnych powiązanych odznak.", "addon.badges.recipientdetails": "Dane odbiorcy", + "addon.badges.relatedbages": "Powiązane odznaki", + "addon.badges.version": "Wersja", + "addon.badges.warnexpired": "(Ta odznaka wygasła!)", + "addon.block_activitymodules.pluginname": "Aktywności", + "addon.block_myoverview.all": "Wszystkie", + "addon.block_myoverview.favourites": "Oznaczone gwiazdką", + "addon.block_myoverview.future": "Nadchodzące", + "addon.block_myoverview.hiddencourses": "Ukryte", + "addon.block_myoverview.inprogress": "Aktualne", + "addon.block_myoverview.lastaccessed": "Ostatnio przeglądane", + "addon.block_myoverview.morecourses": "Więcej kursów", + "addon.block_myoverview.nocourses": "Brak kursów", + "addon.block_myoverview.past": "Zakończone", + "addon.block_myoverview.pluginname": "Przegląd kursów", + "addon.block_myoverview.title": "Nazwa", + "addon.block_recentlyaccessedcourses.nocourses": "Brak ostatnich kursów", + "addon.block_recentlyaccessedcourses.pluginname": "Ostatnio przeglądane kursy", + "addon.block_recentlyaccesseditems.noitems": "Brak ostatnich elementów", + "addon.block_recentlyaccesseditems.pluginname": "Ostatnio przeglądane elementy", + "addon.block_sitemainmenu.pluginname": "Menu główne", + "addon.block_starredcourses.nocourses": "Brak kursów oznaczonych gwiazdką", + "addon.block_starredcourses.pluginname": "Kursy oznaczone gwiazdką", + "addon.block_timeline.duedate": "Termin oddania", + "addon.block_timeline.next30days": "Kolejne 30 dni", + "addon.block_timeline.next3months": "Kolejne 3 miesiące", + "addon.block_timeline.next6months": "Kolejne 6 miesięcy", + "addon.block_timeline.next7days": "Kolejne 7 dni", + "addon.block_timeline.nocoursesinprogress": "Brak aktualnych kursów", + "addon.block_timeline.noevents": "Brak aktywności z terminem oddania", + "addon.block_timeline.overdue": "Zaległe", + "addon.block_timeline.pluginname": "Oś czasu", + "addon.block_timeline.sortbycourses": "Sortuj wg kursów", + "addon.block_timeline.sortbydates": "Sortuj wg dat", "addon.calendar.calendar": "Kalendarz", "addon.calendar.calendarevents": "Wydarzenia w kalendarzu", "addon.calendar.eventendtime": "Data końcowa", @@ -51,21 +97,80 @@ "addon.competency.usercompetencystatus_waitingforreview": "Oczekuje na przegląd", "addon.competency.userplans": "Plany uczenia się", "addon.coursecompletion.completecourse": "Zaznacz kurs jako ukończony", + "addon.coursecompletion.completed": "Ukończony", + "addon.coursecompletion.completiondate": "Data ukończenia", "addon.coursecompletion.completionmenuitem": "Ukończenie", + "addon.coursecompletion.coursecompletion": "Ukończenie kursu", + "addon.coursecompletion.criteria": "Kryteria", + "addon.coursecompletion.criteriagroup": "Grupa kryteriów", + "addon.coursecompletion.criteriarequiredall": "Wszystkie poniższe kryteria są wymagane", + "addon.coursecompletion.criteriarequiredany": "Wszystkie poniższe kryteria są wymagane", + "addon.coursecompletion.inprogress": "W toku", + "addon.coursecompletion.manualselfcompletion": "Samodzielne oznaczenie ukończenia", + "addon.coursecompletion.notyetstarted": "Jeszcze nie rozpoczęto", + "addon.coursecompletion.pending": "Oczekujący", + "addon.coursecompletion.required": "Wymagane", + "addon.coursecompletion.requiredcriteria": "Wymagane kryteria", + "addon.coursecompletion.requirement": "Wymagania", + "addon.coursecompletion.status": "Status", + "addon.coursecompletion.viewcoursereport": "Zobacz raport kursu", "addon.files.files": "Pliki", "addon.files.privatefiles": "Prywatne pliki", "addon.files.sitefiles": "Pliki serwisu", + "addon.messages.acceptandaddcontact": "Zaakceptuj i dodaj do kontaktów", "addon.messages.addcontact": "Dodaj kontakt", - "addon.messages.blockcontact": "Blokuj kontakt", + "addon.messages.addcontactconfirm": "Czy na pewno chcesz dodać {{$a}} do swoich kontaktów?", + "addon.messages.addtofavourites": "Oznacz gwiazdką", + "addon.messages.addtoyourcontacts": "Dodaj do kontaktów", "addon.messages.blocknoncontacts": "Blokuj wiadomości od użytkowników spoza listy kontaktów", + "addon.messages.blockuser": "Zablokuj użytkownika", + "addon.messages.blockuserconfirm": "Czy na pewno chcesz zablokować {{$a}}?", + "addon.messages.contactableprivacy": "Akceptuj wiadomości od:", + "addon.messages.contactableprivacy_coursemember": "Moje kontakty i każdy, kto uczestniczy w moich kursach", + "addon.messages.contactableprivacy_onlycontacts": "Tylko moje kontakty", + "addon.messages.contactableprivacy_site": "Każdy na tej stronie", + "addon.messages.contactblocked": "Kontakt zablokowany", + "addon.messages.contactrequestsent": "Wysłano prośbę o kontakt", "addon.messages.contacts": "Kontakty", + "addon.messages.decline": "Odmów", + "addon.messages.deleteallconfirm": "Czy na pewno chcesz usunąć tę rozmowę? Nie spowoduje to usunięcia jej dla innych uczestników.", + "addon.messages.deleteconversation": "Usuń rozmowę", + "addon.messages.groupconversations": "Grupowe", + "addon.messages.groupinfo": "Informacje o grupie", + "addon.messages.individualconversations": "Prywatne", + "addon.messages.info": "Informacje", + "addon.messages.isnotinyourcontacts": "{{$a}} nie ma w Twoich kontaktach", "addon.messages.message": "Wiadomość", "addon.messages.messagepreferences": "Preferencje wiadomości", "addon.messages.messages": "Wiadomości", "addon.messages.newmessage": "Nowa wiadomość", - "addon.messages.nomessages": "Brak oczekujących wiadomości", + "addon.messages.nocontactrequests": "Brak próśb o kontakt", + "addon.messages.nocontactsgetstarted": "Brak kontaktów", + "addon.messages.nofavourites": "Brak wiadomości oznaczonych gwiazdką", + "addon.messages.nogroupconversations": "Brak rozmów grupowych", + "addon.messages.noindividualconversations": "Brak rozmów prywatnych", + "addon.messages.nomessagesfound": "Brak wiadomości", + "addon.messages.noncontacts": "Spoza kontaktów", + "addon.messages.numparticipants": "{{$a}} uczestników", "addon.messages.removecontact": "Usuń kontakt", - "addon.messages.unblockcontact": "Odblokuj kontakt", + "addon.messages.removecontactconfirm": "Czy na pewno chcesz usunąć {{$a}} ze swoich kontaktów?", + "addon.messages.removefromfavourites": "Usuń gwiazdkę", + "addon.messages.removefromyourcontacts": "Usuń z kontaktów", + "addon.messages.requests": "Prośby", + "addon.messages.requirecontacttomessage": "Musisz poprosić {{$a}}, aby dodał(a) Cię jako kontakt, żeby móc wysłać wiadomość.", + "addon.messages.searchcombined": "Szukaj osób i wiadomości", + "addon.messages.searchnocontactsfound": "Nie znaleziono kontaktów", + "addon.messages.searchnomessagesfound": "Nie znaleziono wiadomości", + "addon.messages.searchnononcontactsfound": "Nie znaleziono żadnych osób spoza kontaktów", + "addon.messages.sendcontactrequest": "Wyślij prośbę o kontakt", + "addon.messages.unabletomessage": "Nie możesz wysłać wiadomości do tego użytkownika", + "addon.messages.unblockuser": "Odblokuj użytkownika", + "addon.messages.unblockuserconfirm": "Czy na pewno chcesz odblokować {{$a}}?", + "addon.messages.userwouldliketocontactyou": "{{$a}} chciał(a)by się z Tobą skontaktować", + "addon.messages.wouldliketocontactyou": "Chciał(a)bym się z Tobą skontaktować", + "addon.messages.you": "Ty:", + "addon.messages.youhaveblockeduser": "W przeszłości zablokowałeś tego użytkownika", + "addon.messages.yourcontactrequestpending": "Twoja prośba o kontakt oczekuje na {{$a}}", "addon.mod_assign.addattempt": "Zezwalaj na inną próbę", "addon.mod_assign.addnewattempt": "Dodaj nową wersję pracy", "addon.mod_assign.addnewattemptfromprevious": "Dodaj nową wersję pracy na podstawie pracy przesłanej poprzednio", @@ -96,6 +201,7 @@ "addon.mod_assign.graded": "Ocenione", "addon.mod_assign.gradedby": "Ocenione przez", "addon.mod_assign.gradedon": "Ocenione dnia", + "addon.mod_assign.gradelocked": "Ta ocena jest zablokowana lub nadpisana w dzienniku ocen", "addon.mod_assign.gradeoutof": "Ocena z max. {{$a}}", "addon.mod_assign.gradingstatus": "Stan oceniania", "addon.mod_assign.groupsubmissionsettings": "Ustawienia zadań grupowych", @@ -109,6 +215,7 @@ "addon.mod_assign.markingworkflowstatereadyforrelease": "Gotowe do publikacji", "addon.mod_assign.markingworkflowstatereadyforreview": "Ocenianie zakończone", "addon.mod_assign.markingworkflowstatereleased": "Ocena opublikowana", + "addon.mod_assign.modulenameplural": "Zadania", "addon.mod_assign.multipleteams": "Członek więcej niż jednej grupy", "addon.mod_assign.multipleteams_desc": "To jest zadanie grupowe. Jesteś członkiem więcej niż jednej grupy. By móc przesłać zadanie musisz być członkiem tylko jednej grupy. Skontaktuj się z prowadzącym w celu zmiany przynależności do grup.", "addon.mod_assign.noattempt": "Nie próbowano", @@ -157,6 +264,7 @@ "addon.mod_assign_submission_file.pluginname": "Przesyłane pliki", "addon.mod_assign_submission_onlinetext.pluginname": "Tekst on-line", "addon.mod_book.errorchapter": "Błąd podczas odczytu książki", + "addon.mod_book.modulenameplural": "Książki", "addon.mod_chat.beep": "rozmawia", "addon.mod_chat.currentusers": "Bieżący użytkownicy", "addon.mod_chat.enterchat": "Naciśnij tu, aby wejść na czat", @@ -164,6 +272,7 @@ "addon.mod_chat.messagebeepsyou": "{{$a}} właśnie do Ciebie napisał", "addon.mod_chat.messageenter": "{{$a}} właśnie wszedł na czat", "addon.mod_chat.messageexit": "{{$a}} opuścił czat", + "addon.mod_chat.modulenameplural": "Czaty", "addon.mod_chat.nomessages": "Brak wiadomości", "addon.mod_chat.send": "Wyślij", "addon.mod_chat.sessionstart": "Sesja czatu rozpocznie się za: {$ a}}", @@ -172,6 +281,7 @@ "addon.mod_choice.choiceoptions": "Opcje wyboru odpowiedzi", "addon.mod_choice.expired": "Niestety ta aktywność została zamknięta {{$a}} i nie jest już dostępna.", "addon.mod_choice.full": "(pełne)", + "addon.mod_choice.modulenameplural": "Głosowania", "addon.mod_choice.noresultsviewable": "Nie ma wglądu w wyniki głosowania.", "addon.mod_choice.notopenyet": "Moduł nieczynny do {{$a}}", "addon.mod_choice.numberofuser": "Liczba użytkowników", @@ -199,8 +309,10 @@ "addon.mod_data.errormustsupplyvalue": "Musisz podać wartość tutaj.", "addon.mod_data.expired": "Niestety, ta aktywność została zamknięta {{$a}} i nie jest już dłużej dostępna", "addon.mod_data.fields": "Pola", + "addon.mod_data.foundrecords": "Znaleziono rekordy: {{$a.num}}/{{$a.max}} (Reset filtrów)", "addon.mod_data.latlongboth": "Wymagana jest zarówno szerokość i długość geograficzna.", "addon.mod_data.menuchoose": "Wybierz...", + "addon.mod_data.modulenameplural": "Bazy danych", "addon.mod_data.more": "Więcej", "addon.mod_data.nomatch": "Żaden wpis nie został znaleziony.", "addon.mod_data.norecords": "Brak wpisów w bazie danych", @@ -230,6 +342,7 @@ "addon.mod_feedback.feedbackopen": "Otwórz o", "addon.mod_feedback.mapcourses": "Skojarz formularz opinii z kursami", "addon.mod_feedback.mode": "Tryb", + "addon.mod_feedback.modulenameplural": "Opinie zwrotne", "addon.mod_feedback.next_page": "Następna strona", "addon.mod_feedback.non_anonymous": "Nazwa użytkownika będzie zapamiętywana i prezentowana z odpowiedziami", "addon.mod_feedback.non_anonymous_entries": "Pozycje nie anonimowe", @@ -249,6 +362,7 @@ "addon.mod_feedback.show_nonrespondents": "Pokaż kto nie udzielił opinii*", "addon.mod_feedback.started": "Rozpoczęto", "addon.mod_feedback.this_feedback_is_already_submitted": "Już zakończyłeś wypełnianie tej aktywności.", + "addon.mod_folder.modulenameplural": "Foldery", "addon.mod_forum.addanewdiscussion": "Dodaj nowy temat dyskusji", "addon.mod_forum.addanewquestion": "Dodaj nowe pytanie", "addon.mod_forum.addanewtopic": "Dodaj nowy temat", @@ -267,6 +381,7 @@ "addon.mod_forum.modeflatnewestfirst": "Wyświetl odpowiedzi płasko, z najnowszymi na początku", "addon.mod_forum.modeflatoldestfirst": "Wyświetl odpowiedzi płasko, z najdawniejszymi na początku", "addon.mod_forum.modenested": "Wyświetl odpowiedzi tematycznie", + "addon.mod_forum.modulenameplural": "Fora dyskusyjne", "addon.mod_forum.posttoforum": "Wyślij wpis na forum", "addon.mod_forum.re": "Odp:", "addon.mod_forum.reply": "Odpowiedz", @@ -285,7 +400,9 @@ "addon.mod_glossary.fillfields": "Pola: Termin i Definicja muszą zostać wypełnione", "addon.mod_glossary.fullmatch": "Szukaj tylko wyrazów tak jak zostały wpisane
(jeśli zostały automatycznie połączone)", "addon.mod_glossary.linking": "Automatyczne linkowanie", + "addon.mod_glossary.modulenameplural": "Słowniki pojęć", "addon.mod_imscp.deploymenterror": "Błąd pakietu treści!", + "addon.mod_imscp.modulenameplural": "Pakiety treści IMS", "addon.mod_lesson.answer": "Odpowiedź", "addon.mod_lesson.attempt": "Podejście: {{$a}}", "addon.mod_lesson.attemptheader": "Próba", @@ -325,6 +442,7 @@ "addon.mod_lesson.lowtime": "Najkrótszy czas", "addon.mod_lesson.maximumnumberofattemptsreached": "Osiągnięto maksymalną liczbę prób - przejdź do następnej strony", "addon.mod_lesson.modattemptsnoteacher": "Tylko student może zmieniać pracę.", + "addon.mod_lesson.modulenameplural": "Lekcje", "addon.mod_lesson.noanswer": "Brak odpowiedzi", "addon.mod_lesson.nolessonattempts": "Nikt jeszcze nie rozwiązał lekcji", "addon.mod_lesson.nolessonattemptsgroup": "Członkowie grupy {{$a}} nie podjęli prób w tej lekcji.", @@ -362,6 +480,8 @@ "addon.mod_lesson.youranswer": "Twoja odpowiedź", "addon.mod_lesson.yourcurrentgradeisoutof": "Masz obecnie {{$a.grade}} punktów z {{$a.total}}", "addon.mod_lesson.youshouldview": "Powinieneś odpowiedzieć na co najmniej: {{$a}}", + "addon.mod_lti.modulenameplural": "Narzędzia zewnętrzne", + "addon.mod_page.modulenameplural": "Strony", "addon.mod_quiz.attemptfirst": "Pierwsze podejście", "addon.mod_quiz.attemptlast": "Ostatnie podejście", "addon.mod_quiz.attemptnumber": "Próba", @@ -383,7 +503,8 @@ "addon.mod_quiz.grademethod": "Metoda oceniania", "addon.mod_quiz.gradesofar": "{{$a.method}}: {{$a.mygrade}} / {{$a.quizgrade}}", "addon.mod_quiz.marks": "Punkty", - "addon.mod_quiz.mustbesubmittedby": "Próba musi być wykonana przez {{$a}}.", + "addon.mod_quiz.modulenameplural": "Testy (Quizy)", + "addon.mod_quiz.mustbesubmittedby": "Próba musi być wykonana przed: {{$a}}.", "addon.mod_quiz.noquestions": "Nie dodano jeszcze żadnego pytania", "addon.mod_quiz.noreviewattempt": "Nie masz uprawnień do dokonania przeglądu tej próby.", "addon.mod_quiz.notyetgraded": "Jeszcze nie ocenione", @@ -421,6 +542,7 @@ "addon.mod_quiz.timetaken": "Wykorzystany czas", "addon.mod_quiz.yourfinalgradeis": "Twoja końcowa ocena za ten quiz wynosi {{$a}}", "addon.mod_resource.modifieddate": "Zmodyfikowane {{$a}}", + "addon.mod_resource.modulenameplural": "Pliki", "addon.mod_resource.uploadeddate": "Przesłano {{$a}}", "addon.mod_scorm.asset": "Zasób", "addon.mod_scorm.assetlaunched": "Zasób - obejrzany", @@ -446,6 +568,7 @@ "addon.mod_scorm.incomplete": "Niepełne", "addon.mod_scorm.lastattempt": "Ostatnia zakończona próba", "addon.mod_scorm.mode": "Tryb", + "addon.mod_scorm.modulenameplural": "Pakiety SCORM", "addon.mod_scorm.newattempt": "Rozpocznij nową próbę", "addon.mod_scorm.noattemptsallowed": "Liczba dozwolonych prób", "addon.mod_scorm.noattemptsmade": "Liczba wykonanych prób", @@ -457,12 +580,15 @@ "addon.mod_scorm.suspended": "Zawieszone", "addon.mod_survey.ifoundthat": "Stwierdziłem, że", "addon.mod_survey.ipreferthat": "Wolę to", + "addon.mod_survey.modulenameplural": "Ankiety", "addon.mod_survey.responses": "Odpowiedzi", "addon.mod_survey.surveycompletednograph": "Już wypełniłeś tą ankietę.", + "addon.mod_url.modulenameplural": "Adresy URL", "addon.mod_wiki.cannoteditpage": "Nie możesz edytować tej strony.", "addon.mod_wiki.createpage": "Utwórz stronę", "addon.mod_wiki.editingpage": "Edycja strony: '{{$a}}'", "addon.mod_wiki.map": "Mapa stron", + "addon.mod_wiki.modulenameplural": "Wiki", "addon.mod_wiki.newpagehdr": "Nowa strona", "addon.mod_wiki.newpagetitle": "Tytuł nowej strony", "addon.mod_wiki.nocontent": "Brak zawartości dla tej strony", @@ -496,6 +622,7 @@ "addon.mod_workshop.gradinggradecalculated": "Obliczona ocena za recenzje", "addon.mod_workshop.gradinggradeof": "Ocena za recenzje (z {{$a}})", "addon.mod_workshop.gradinggradeover": "Nadpisz ocenę za recenzje", + "addon.mod_workshop.modulenameplural": "Warsztaty", "addon.mod_workshop.nogradeyet": "Jeszcze nie ocenione", "addon.mod_workshop.notassessed": "Jeszcze nie ocenione", "addon.mod_workshop.notoverridden": "Nie nadpisuj", @@ -540,7 +667,7 @@ "addon.notes.publishstate": "Kontekst", "addon.notes.sitenotes": "Notatki strony głównej", "addon.notifications.markallread": "Oznacz wszystko jako przeczytane", - "addon.notifications.notificationpreferences": "Preferencje powiadomienia", + "addon.notifications.notificationpreferences": "Preferencje powiadomień", "addon.notifications.notifications": "Powiadomienia", "assets.countries.AD": "Andora", "assets.countries.AE": "Zjednoczone Emiraty Arabskie", @@ -829,6 +956,8 @@ "core.accounts": "Konta", "core.add": "Dodaj", "core.agelocationverification": "Weryfikacja wieku i lokalizacji", + "core.ago": "{{$a}} temu", + "core.all": "Wszystkie", "core.allparticipants": "Wszyscy uczestnicy", "core.answer": "Odpowiedz", "core.answered": "Udzielono odpowiedzi", @@ -855,35 +984,32 @@ "core.course": "Kurs", "core.course.contents": "Zawartość", "core.course.coursesummary": "Podsumowanie kursu", + "core.course.downloadcourse": "Pobierz kurs", "core.course.hiddenfromstudents": "Ukryte przed studentami", "core.course.hiddenoncoursepage": "Dostępne, lecz nie wyświetlane na stronie kursu.", "core.course.overriddennotice": "Twoja ocena końcowa z tej aktywności została ręcznie zmieniona", "core.course.sections": "Sekcje", "core.coursedetails": "Szczegóły kursu", + "core.courses.addtofavourites": "Oznacz kurs gwiazdką", "core.courses.allowguests": "W tym kursie mogą uczestniczyć również goście", "core.courses.availablecourses": "Dostępne kursy", "core.courses.categories": "Kategorie kursów", - "core.courses.courseoverview": "Podgląd kursu", "core.courses.courses": "Kursy", "core.courses.frontpage": "Strona główna", - "core.courses.future": "Nadchodzące", - "core.courses.inprogress": "Aktualne", - "core.courses.morecourses": "Więcej kursów", + "core.courses.hidecourse": "Ukryj z widoku", "core.courses.mycourses": "Moje kursy", + "core.courses.mymoodle": "Kokpit", "core.courses.nocourses": "Nie ma informacji do pokazania", - "core.courses.nocoursesfuture": "Brak nadchodzących kursów", - "core.courses.nocoursesinprogress": "Brak aktualnych kursów", - "core.courses.nocoursesoverview": "żadnego kursu", - "core.courses.nocoursespast": "Brak zakończonych kursów", "core.courses.nocoursesyet": "Brak kursów w tej kategorii", "core.courses.nosearchresults": "Brak wyników", "core.courses.notenroled": "Nie jesteś zapisany w tym kursie", - "core.courses.past": "Zakończone", "core.courses.paymentrequired": "Ten kurs wymaga opłaty", "core.courses.paypalaccepted": "Płatności PayPal są akceptowane", + "core.courses.removefromfavourites": "Usuń gwiazdkę z kursu", "core.courses.search": "Wyszukaj", "core.courses.searchcourses": "Przeszukaj kursy", "core.courses.sendpaymentbutton": "Wyślij płatności za pośrednictwem PayPal", + "core.courses.show": "Pokaż kurs", "core.date": "Data", "core.day": "dzień", "core.days": "dni", @@ -897,14 +1023,34 @@ "core.edit": "Modyfikuj", "core.error": "Błąd", "core.errordownloading": "Błąd pobierania pliku", + "core.favourites": "Oznaczone gwiazdką", "core.filename": "Nazwa pliku", "core.fileuploader.addfiletext": "Dodaj plik", - "core.fileuploader.errorcapturingaudio": "Błąd przechwytywania dźwięku", + "core.fileuploader.audio": "Dźwięk", + "core.fileuploader.camera": "Aparat", + "core.fileuploader.confirmuploadfile": "Zostanie przesłane {{size}}. Czy chcesz kontynuować?", + "core.fileuploader.confirmuploadunknownsize": "Nie udało się obliczyć rozmiaru przesyłanych plików. Are you sure you want to continue?", + "core.fileuploader.errorcapturingaudio": "Błąd podczas nagrywania dźwięku.", + "core.fileuploader.errorcapturingimage": "Błąd podczas wykonywania zdjęcia.", "core.fileuploader.errorcapturingvideo": "Błąd przechwytywania wideo", + "core.fileuploader.errorgettingimagealbum": "Błąd podczas pobierania zdjęcia z albumu.", + "core.fileuploader.errormustbeonlinetoupload": "Musisz mieć dostęp do sieci aby przesłać pliki.", + "core.fileuploader.errornoapp": "Nie zainstalowano aplikacji koniecznej do wykonania tej czynności.", + "core.fileuploader.errorreadingfile": "Błąd odczytu pliku.", + "core.fileuploader.errorwhileuploading": "Wystąpił błąd podczas przesyłania pliku.", + "core.fileuploader.file": "Plik", "core.fileuploader.filesofthesetypes": "Akceptowane typy plików:", - "core.fileuploader.fileuploaded": "Plik został przesłany", + "core.fileuploader.fileuploaded": "Plik został przesłany.", "core.fileuploader.invalidfiletype": "Typ pliku nie jest akceptowany: {{$a}}", + "core.fileuploader.maxbytesfile": "Plik {{$a.file}} jest zbyt duży. Maksymalny dozwolony rozmiar przesyłanego pliku to {{$a.size}}.", "core.fileuploader.more": "Więcej", + "core.fileuploader.photoalbums": "Galeria zdjęć", + "core.fileuploader.readingfile": "Odczytuję plik", + "core.fileuploader.selectafile": "Wybierz plik", + "core.fileuploader.uploadafile": "Prześlij plik", + "core.fileuploader.uploading": "Przesyłanie", + "core.fileuploader.uploadingperc": "Przesyłanie: {{$a}}%", + "core.fileuploader.video": "Wideo", "core.folder": "Folder", "core.forcepasswordchangenotice": "W celu kontynuacji musisz zmienić swoje hasło", "core.fulllistofcourses": "Wszystkie kursy", @@ -942,12 +1088,14 @@ "core.login.cancel": "Anuluj", "core.login.createaccount": "Utwórz moje nowe konto", "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.enterthewordsabove": "Wpisz słowa powyżej", "core.login.firsttime": "Czy jesteś w tym serwisie po raz pierwszy?", "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", "core.login.invalidemail": "Niewłaściwy adres e-mail", "core.login.invalidsite": "Adres URL witryny jest nieprawidłowy.", "core.login.invalidurl": "Określono niepoprawny URL", @@ -958,6 +1106,7 @@ "core.login.missingfirstname": "Pominięto imię", "core.login.missinglastname": "Pominięto nazwisko", "core.login.mobileservicesnotenabled": "Usługi mobilne nie są włączone w Twojej witrynie. Proszę, skontaktuj się z administratorem serwisu Moodle jeśli uważasz mobilny dostęp powinien być włączony.", + "core.login.mustconfirm": "Musisz potwierdzić swoją nazwę użytkownika", "core.login.newaccount": "Nowe konto", "core.login.password": "Hasło", "core.login.passwordforgotten": "Zapomniane hasło", @@ -980,8 +1129,6 @@ "core.mainmenu.appsettings": "Ustawienia aplikacji", "core.mainmenu.help": "Pomoc", "core.mainmenu.logout": "Wyloguj", - "core.mainmenu.mycourses": "Moje kursy", - "core.mainmenu.togglemenu": "Przełącz menu", "core.maxsizeandattachments": "Maksymalny rozmiar dla nowych plików: {{$a.size}}, maksimum załączników: {{$a.attachments}}", "core.min": "min", "core.mins": "min.", @@ -1035,7 +1182,7 @@ "core.paymentinstant": "Użyj poniższego przycisku, aby zapłacić i zapisać się na kurs w ciągu kilku minut!", "core.phone": "Telefon", "core.pictureof": "Obraz {{$a}}", - "core.previous": "Poprzedni", + "core.previous": "Wstecz", "core.proceed": "Kontynuuj", "core.question.answer": "Odpowiedź", "core.question.answersaved": "Odpowiedź zapisana", @@ -1053,7 +1200,9 @@ "core.question.requiresgrading": "Wymaga oceny", "core.quotausage": "Aktualnie wykorzystano {{$a.used}} z limitu {{$a.total}}.", "core.refresh": "Odśwież", + "core.remove": "Usuń", "core.required": "Wymagane", + "core.resources": "Zasoby", "core.restore": "Odtwórz", "core.save": "Zapisz", "core.search": "Wyszukaj", @@ -1090,7 +1239,20 @@ "core.sizegb": "GB", "core.sizekb": "KB", "core.sizemb": "MB", - "core.sortby": "Posortuj według", + "core.sortby": "Sortuj według", + "core.strftimedate": "%d %B %Y", + "core.strftimedatefullshort": "%d/%m/%y", + "core.strftimedateshort": "%d %B", + "core.strftimedatetime": "%d %B %Y, %H:%M %p", + "core.strftimedatetimeshort": "%d/%m/%Y, %H:%M", + "core.strftimedaydate": "%A, %d %B %Y", + "core.strftimedaydatetime": "%A, %d %B %Y, %H:%M %p", + "core.strftimedayshort": "%A, %d %B", + "core.strftimedaytime": "%a, %H:%M", + "core.strftimemonthyear": "%B %Y", + "core.strftimerecent": "%d %b, %H:%M", + "core.strftimerecentfull": "%a, %d %b %Y, %H:%M %p", + "core.strftimetime": "%H:%M %p", "core.submit": "Prześlij", "core.success": "Gotowe", "core.teachers": "Prowadzący", @@ -1106,17 +1268,21 @@ "core.user.country": "Kraj", "core.user.description": "Opis", "core.user.details": "Szczegóły", + "core.user.editingteacher": "Prowadzący", "core.user.email": "E-mail", "core.user.emailagain": "E-mail (jeszcze raz)", "core.user.firstname": "Imię", "core.user.interests": "Zainteresowania", "core.user.lastname": "Nazwisko", + "core.user.manager": "Menedżer", "core.user.newpicture": "Nowy obraz", "core.user.noparticipants": "Nie znaleziono uczestników w tym kursie", "core.user.participants": "Uczestnicy", "core.user.phone1": "Telefon", "core.user.phone2": "Tel. komórkowy", "core.user.roles": "Role", + "core.user.student": "Student", + "core.user.teacher": "Prowadzący bez praw edycji", "core.user.webpage": "Strona WWW", "core.userdeleted": "To konto użytkownika zostało usunięte", "core.userdetails": "Szczegóły użytkownika", diff --git a/src/assets/lang/pt-br.json b/src/assets/lang/pt-br.json index 2decc2f74..f5d4c229e 100644 --- a/src/assets/lang/pt-br.json +++ b/src/assets/lang/pt-br.json @@ -1,15 +1,58 @@ { + "addon.badges.alignment": "Competência", "addon.badges.badgedetails": "Detalhes do emblema", "addon.badges.badges": "Emblemas", + "addon.badges.bendorsement": "Endosso", + "addon.badges.claimcomment": "Comentário de endosso", + "addon.badges.claimid": "URL do crédito", "addon.badges.contact": "Contato", "addon.badges.dateawarded": "Data de emissão", "addon.badges.expired": "Expirado", "addon.badges.expirydate": "Data de validade", + "addon.badges.imageauthoremail": "E-mail do autor da imagem", + "addon.badges.imageauthorname": "Nome do autor da imagem", + "addon.badges.imageauthorurl": "URL do autor da imagem", + "addon.badges.imagecaption": "Legenda da imagem", "addon.badges.issuancedetails": "Expiração do emblema", "addon.badges.issuerdetails": "Detalhes do emissor", + "addon.badges.issueremail": "Email", "addon.badges.issuername": "Nome do emissor", + "addon.badges.issuerurl": "URL do emissor", + "addon.badges.language": "Idioma", + "addon.badges.noalignment": "Este emblema não possui nenhuma competência especificada.", "addon.badges.nobadges": "Não há emblemas disponíveis.", + "addon.badges.norelated": "Este emblema não possui nenhum emblema relacionado.", "addon.badges.recipientdetails": "Detalhes do usuário a receber o emblema", + "addon.badges.relatedbages": "Emblemas relacionados", + "addon.badges.version": "Versão", + "addon.badges.warnexpired": "(Este emblema expirou!)", + "addon.block_activitymodules.pluginname": "Atividades", + "addon.block_myoverview.all": "Todos", + "addon.block_myoverview.future": "Não iniciados", + "addon.block_myoverview.hiddencourses": "Oculto", + "addon.block_myoverview.inprogress": "Em andamento", + "addon.block_myoverview.morecourses": "Mais cursos", + "addon.block_myoverview.nocourses": "Não há cursos", + "addon.block_myoverview.past": "Encerrados", + "addon.block_myoverview.pluginname": "Resumo dos cursos", + "addon.block_myoverview.title": "Nome do curso", + "addon.block_recentlyaccessedcourses.nocourses": "Não há cursos com acesso recente", + "addon.block_recentlyaccessedcourses.pluginname": "Cursos com acesso recente", + "addon.block_recentlyaccesseditems.noitems": "Não há itens com acesso recente", + "addon.block_recentlyaccesseditems.pluginname": "Itens com acesso recente", + "addon.block_sitemainmenu.pluginname": "Menu Principal", + "addon.block_starredcourses.nocourses": "Não há cursos favoritos", + "addon.block_starredcourses.pluginname": "Cursos favoritos", + "addon.block_timeline.duedate": "Prazo", + "addon.block_timeline.next30days": "Próximos 30 dias", + "addon.block_timeline.next3months": "Próximos 3 meses", + "addon.block_timeline.next6months": "Próximos 6 meses", + "addon.block_timeline.next7days": "Próximos 7 dias", + "addon.block_timeline.noevents": "Nenhuma atividade com prazo", + "addon.block_timeline.overdue": "Atrasado", + "addon.block_timeline.pluginname": "Linha do tempo", + "addon.block_timeline.sortbycourses": "Ordenar por cursos", + "addon.block_timeline.sortbydates": "Ordenar por data", "addon.calendar.calendar": "Calendário", "addon.calendar.calendarevents": "Eventos do calendário", "addon.calendar.defaultnotificationtime": "Tempo de notificação padrão", @@ -76,24 +119,24 @@ "addon.competency.xcompetenciesproficientoutofyincourse": "Você é proficiente em {{$a.x}} de {{$a.y}} competências neste curso.", "addon.coursecompletion.complete": "Concluído", "addon.coursecompletion.completecourse": "Clique aqui para marcar o curso como completo", - "addon.coursecompletion.completed": "Completado", + "addon.coursecompletion.completed": "Concluído", "addon.coursecompletion.completiondate": "Data de conclusão", "addon.coursecompletion.completionmenuitem": "Conclusão", "addon.coursecompletion.couldnotloadreport": "Não foi possível carregar o relatório de conclusão do curso, por favor tente novamente mais tarde.", - "addon.coursecompletion.coursecompletion": "Conclusão do curso", + "addon.coursecompletion.coursecompletion": "Conclusão de curso", "addon.coursecompletion.criteria": "Critérios", "addon.coursecompletion.criteriagroup": "Grupo de critérios", "addon.coursecompletion.criteriarequiredall": "Todos os critérios abaixo são necessários", - "addon.coursecompletion.criteriarequiredany": "Quaisquer critérios abaixo são necessários", - "addon.coursecompletion.inprogress": "Em progresso", - "addon.coursecompletion.manualselfcompletion": "Auto conclusão manual", - "addon.coursecompletion.notyetstarted": "Ainda não começou", - "addon.coursecompletion.pending": "Pendente", - "addon.coursecompletion.required": "Necessário", - "addon.coursecompletion.requiredcriteria": "Critério necessário", - "addon.coursecompletion.requirement": "Exigência", - "addon.coursecompletion.status": "Condição", - "addon.coursecompletion.viewcoursereport": "Ver relatório de curso", + "addon.coursecompletion.criteriarequiredany": "Qualquer um dos critérios abaixo são necessários", + "addon.coursecompletion.inprogress": "Em andamento", + "addon.coursecompletion.manualselfcompletion": "Conclusão manual por si mesmo", + "addon.coursecompletion.notyetstarted": "Não iniciado ainda", + "addon.coursecompletion.pending": "Pendentes", + "addon.coursecompletion.required": "Necessários", + "addon.coursecompletion.requiredcriteria": "Critérios exigidos", + "addon.coursecompletion.requirement": "Requisito", + "addon.coursecompletion.status": "Status", + "addon.coursecompletion.viewcoursereport": "Ver relatório do curso", "addon.files.couldnotloadfiles": "A lista de arquivos não pode ser carregada.", "addon.files.emptyfilelist": "Não há arquivos para mostrar.", "addon.files.erroruploadnotworking": "Infelizmente é impossível enviar arquivos para o seu site.", @@ -101,34 +144,73 @@ "addon.files.privatefiles": "Arquivos privados", "addon.files.sitefiles": "Arquivos do site", "addon.messageoutput_airnotifier.processorsettingsdesc": "Configurar dispositivos", + "addon.messages.acceptandaddcontact": "Aceitar e adicionar aos contatos", "addon.messages.addcontact": "Acrescentar Contato", - "addon.messages.blockcontact": "Bloquear contato", - "addon.messages.blockcontactconfirm": "Você deixará de receber mensagens deste contato.", + "addon.messages.addcontactconfirm": "Tem certeza de que deseja adicionar {{$a}} aos seus contatos?", + "addon.messages.addtofavourites": "Favorito", + "addon.messages.addtoyourcontacts": "Adicionar aos contatos", "addon.messages.blocknoncontacts": "Bloquear todas as mensagens de quem não estiver na minha lista de contatos", + "addon.messages.blockuser": "Bloquear usuário", + "addon.messages.blockuserconfirm": "Tem certeza de que deseja bloquear {{$a}}?", + "addon.messages.contactableprivacy": "Aceitar mensagens de:", + "addon.messages.contactableprivacy_coursemember": "Meus contatos e qualquer pessoa em meus cursos", + "addon.messages.contactableprivacy_onlycontacts": "Apenas meus contatos", + "addon.messages.contactableprivacy_site": "Qualquer pessoa no site", + "addon.messages.contactblocked": "Contato bloqueado", "addon.messages.contactlistempty": "A lista de contatos está vaiza", "addon.messages.contactname": "Nome do contato", + "addon.messages.contactrequestsent": "Solicitação de contato enviado", "addon.messages.contacts": "Contatos", + "addon.messages.decline": "Recusar", + "addon.messages.deleteallconfirm": "Você tem certeza que deseja excluir toda essa conversa?", + "addon.messages.deleteconversation": "Apagar conversa", "addon.messages.errordeletemessage": "Erro enquanto excluía a mensagem.", "addon.messages.errorwhileretrievingcontacts": "Erro ao recuperar contatos a partir do servidor.", "addon.messages.errorwhileretrievingdiscussions": "Erro ao recuperar discussão do servidor.", "addon.messages.errorwhileretrievingmessages": "Erro ao recuperar as mensagens do servidor.", + "addon.messages.groupconversations": "Grupo", + "addon.messages.groupinfo": "Informação do Grupo", + "addon.messages.individualconversations": "Privado", + "addon.messages.info": "Info", + "addon.messages.isnotinyourcontacts": "{{$a}} não está nos seus contatos", "addon.messages.message": "Mensagem", "addon.messages.messagenotsent": "A mensagem não foi enviada. Por favor tente novamente mais tarde.", "addon.messages.messagepreferences": "Preferências de mensagens", "addon.messages.messages": "Mensagens", "addon.messages.newmessage": "Nova Mensagem", "addon.messages.newmessages": "Novas mensagens", - "addon.messages.nomessages": "Sem novas mensagens", + "addon.messages.nocontactsgetstarted": "Sem contatos", + "addon.messages.nofavourites": "Nenhuma mensagem favorita", + "addon.messages.nogroupconversations": "Nenhuma conversa em grupo", + "addon.messages.noindividualconversations": "Nenhuma conversa privada", + "addon.messages.nomessagesfound": "Não foram encontradas mensagens", + "addon.messages.noncontacts": "Não contatos", "addon.messages.nousersfound": "Nenhum usuário encontrado", + "addon.messages.numparticipants": "{{$a}} participantes", "addon.messages.removecontact": "Eliminar contato", - "addon.messages.removecontactconfirm": "O contato será removido da sua lista de contatos.", + "addon.messages.removecontactconfirm": "Tem certeza de que deseja remover {{$a}} dos seus contatos?", + "addon.messages.removefromfavourites": "Remover", + "addon.messages.removefromyourcontacts": "Excluir dos seus contatos", + "addon.messages.requests": "Solicitações", + "addon.messages.requirecontacttomessage": "Você precisa solicitar {{$a}} que te adicione como contato. Só assim você conseguirá enviar uma mensagem para ele.", + "addon.messages.searchcombined": "Pesquisar pessoas e mensagens", + "addon.messages.searchnocontactsfound": "Nenhum contato encontrado", + "addon.messages.searchnomessagesfound": "Nenhuma mensagem encontrada", + "addon.messages.searchnononcontactsfound": "Nenhum 'não contato' encontrado", "addon.messages.type_blocked": "Bloqueado", "addon.messages.type_offline": "Offline", "addon.messages.type_online": "Online", "addon.messages.type_search": "Resultados de busca", "addon.messages.type_strangers": "Outros", - "addon.messages.unblockcontact": "Desbloquear contato", + "addon.messages.unabletomessage": "Você não consegue enviar mensagens para esse usuário", + "addon.messages.unblockuser": "Desbloquear usuário", + "addon.messages.unblockuserconfirm": "Tem certeza de que deseja desbloquear {{$a}}?", + "addon.messages.userwouldliketocontactyou": "{{$a}} gostaria de entrar em contato com você", "addon.messages.warningmessagenotsent": "Não pode enviar mensagem(s) para o usuário {{user}}. {{error}}", + "addon.messages.wouldliketocontactyou": "Gostaria de entrar em contato com você", + "addon.messages.you": "Você:", + "addon.messages.youhaveblockeduser": "Você bloqueou este usuário no passado", + "addon.messages.yourcontactrequestpending": "Sua solicitação de contato está pendente com {{$a}}", "addon.mod_assign.acceptsubmissionstatement": "Por favor aceite a declaração do envio.", "addon.mod_assign.addattempt": "Permitir outra tentativa", "addon.mod_assign.addnewattempt": "Adicionar uma nova tentativa", @@ -166,6 +248,7 @@ "addon.mod_assign.graded": "Avaliado", "addon.mod_assign.gradedby": "Avaliado por", "addon.mod_assign.gradedon": "Avaliado em", + "addon.mod_assign.gradelocked": "Esta nota está bloqueada ou sobreposta no livro de notas.", "addon.mod_assign.gradenotsynced": "Avaliação não sincronizada", "addon.mod_assign.gradeoutof": "Nota até {{$a}}", "addon.mod_assign.gradingstatus": "Status da avaliação", @@ -180,6 +263,7 @@ "addon.mod_assign.markingworkflowstatereadyforrelease": "Pronto para publicação", "addon.mod_assign.markingworkflowstatereadyforreview": "Avaliação concluída", "addon.mod_assign.markingworkflowstatereleased": "Publicado", + "addon.mod_assign.modulenameplural": "Tarefas", "addon.mod_assign.multipleteams": "Membro de mais de um grupo", "addon.mod_assign.multipleteams_desc": "Esta atividade requer envio em grupos. Você é membro de mais de um grupo. Para poder realizar o envio você deve fazer parte de apenas um grupo. Por favor, contate seu professor para mudar seu grupo.", "addon.mod_assign.noattempt": "Nenhuma tentativa", @@ -234,6 +318,7 @@ "addon.mod_assign_submission_file.pluginname": "Envio de arquivos", "addon.mod_assign_submission_onlinetext.pluginname": "Envio de texto online", "addon.mod_book.errorchapter": "Erro ao ler capítulo de livro.", + "addon.mod_book.modulenameplural": "Livros", "addon.mod_chat.beep": "Bip", "addon.mod_chat.currentusers": "Usuários atuais", "addon.mod_chat.enterchat": "Clique aqui para entrar no chat agora", @@ -246,6 +331,7 @@ "addon.mod_chat.messagebeepsyou": "{{$a}} está bipando você!", "addon.mod_chat.messageenter": "{{$a}} entrou no chat", "addon.mod_chat.messageexit": "{{$a}} abandonou este chat", + "addon.mod_chat.modulenameplural": "Chats", "addon.mod_chat.mustbeonlinetosendmessages": "Você deve estar online para enviar mensagens", "addon.mod_chat.nomessages": "Nenhuma mensagem ainda", "addon.mod_chat.send": "Enviar", @@ -256,6 +342,7 @@ "addon.mod_choice.errorgetchoice": "Erro ao obter dados do escolha.", "addon.mod_choice.expired": "Esta atividade está encerrada desde {{$a}}", "addon.mod_choice.full": "(Cheio)", + "addon.mod_choice.modulenameplural": "Escolhas", "addon.mod_choice.noresultsviewable": "Não é possível visualizar os resultados neste momento.", "addon.mod_choice.notopenyet": "Esta atividade será acessível a partir de {{$a}}", "addon.mod_choice.numberofuser": "Número de respostas", @@ -289,8 +376,10 @@ "addon.mod_data.errormustsupplyvalue": "Você precisa fornecer um valor aqui.", "addon.mod_data.expired": "Sinto muito, mas esta atividade foi fechada em {{$a}} e não está mais disponível", "addon.mod_data.fields": "Campos", + "addon.mod_data.foundrecords": "Encontrados: {{$a.num}}/{{$a.max}} (Reconfigurar filtros)", "addon.mod_data.latlongboth": "Tanto a Latitude quanto a Longitude devem ser preenchidas.", "addon.mod_data.menuchoose": "Selecionar...", + "addon.mod_data.modulenameplural": "Bases de dados", "addon.mod_data.more": "Mais", "addon.mod_data.nomatch": "Nenhum item correspondente encontrado!", "addon.mod_data.norecords": "Nenhum item na base de dados", @@ -320,6 +409,7 @@ "addon.mod_feedback.feedbackopen": "Permitir respostas de", "addon.mod_feedback.mapcourses": "Mapear pesquisa para os cursos", "addon.mod_feedback.mode": "Modo", + "addon.mod_feedback.modulenameplural": "Pesquisa", "addon.mod_feedback.next_page": "Próxima página", "addon.mod_feedback.non_anonymous": "O nome do usuário será registrado e mostrado com as respostas", "addon.mod_feedback.non_anonymous_entries": "Entradas não anônimas ({{$a}})", @@ -340,6 +430,7 @@ "addon.mod_feedback.started": "iniciado", "addon.mod_feedback.this_feedback_is_already_submitted": "Você já terminou esta atividade.", "addon.mod_folder.emptyfilelist": "Não há arquivos para mostrar", + "addon.mod_folder.modulenameplural": "Pastas", "addon.mod_forum.addanewdiscussion": "Acrescentar um novo tópico de discussão", "addon.mod_forum.addanewquestion": "Acrescentar uma nova questão", "addon.mod_forum.addanewtopic": "Acrescentar um novo tópico", @@ -362,6 +453,7 @@ "addon.mod_forum.modeflatnewestfirst": "Mostrar respostas começando pela mais recente", "addon.mod_forum.modeflatoldestfirst": "Mostrar respostas começando pela mais antiga", "addon.mod_forum.modenested": "Mostrar respostas aninhadas", + "addon.mod_forum.modulenameplural": "Fóruns", "addon.mod_forum.numdiscussions": "{{numdiscussions}} discussões", "addon.mod_forum.numreplies": "{{numreplies}} respostas", "addon.mod_forum.posttoforum": "Enviar mensagem ao fórum", @@ -397,9 +489,11 @@ "addon.mod_glossary.fillfields": "Conceito e definição são campos obrigatórios.", "addon.mod_glossary.fullmatch": "Criar links apenas a partir de palavras inteiras", "addon.mod_glossary.linking": "Auto-link", + "addon.mod_glossary.modulenameplural": "Glossários", "addon.mod_glossary.noentriesfound": "Nenhuma entrada foi encontrada", "addon.mod_glossary.searchquery": "Consulta de pesquisa", "addon.mod_imscp.deploymenterror": "Erro no conte!", + "addon.mod_imscp.modulenameplural": "Conteúdo dos pacotes IMS", "addon.mod_imscp.showmoduledescription": "Mostrar descrição", "addon.mod_lesson.answer": "Resposta", "addon.mod_lesson.attempt": "Tentativa: {{$a}}", @@ -440,6 +534,7 @@ "addon.mod_lesson.lowtime": "Tempo breve", "addon.mod_lesson.maximumnumberofattemptsreached": "Número máximo de tentativas atingido - indo para a próxima página", "addon.mod_lesson.modattemptsnoteacher": "A revisão dos estudantes só é ativa para eles.", + "addon.mod_lesson.modulenameplural": "Lições", "addon.mod_lesson.noanswer": "Uma ou mais questões estão sem resposta. Por favor, volte e envie uma resposta.", "addon.mod_lesson.nolessonattempts": "Nenhuma tentativa feita nesta lição.", "addon.mod_lesson.nolessonattemptsgroup": "Não há tentativas feitas por membros do grupo {{$a}} nesta lição.", @@ -480,7 +575,9 @@ "addon.mod_lti.errorgetlti": "Erro ao obter os dados do módulo.", "addon.mod_lti.errorinvalidlaunchurl": "O URL de lançamento não é válido.", "addon.mod_lti.launchactivity": "Iniciar a atividade", + "addon.mod_lti.modulenameplural": "Ferramentas externas", "addon.mod_page.errorwhileloadingthepage": "Erro ao carregar conteúdo da página.", + "addon.mod_page.modulenameplural": "Páginas", "addon.mod_quiz.attemptfirst": "Primeira tentativa", "addon.mod_quiz.attemptlast": "Última tentativa", "addon.mod_quiz.attemptnumber": "Tentativa", @@ -515,6 +612,7 @@ "addon.mod_quiz.grademethod": "Método de avaliação", "addon.mod_quiz.gradesofar": "{{$a.method}}: {{$a.mygrade}} / {{$a.quizgrade}}.", "addon.mod_quiz.marks": "Notas", + "addon.mod_quiz.modulenameplural": "Questionários", "addon.mod_quiz.mustbesubmittedby": "Esta tentativa precisa ser enviada até {{$a}}.", "addon.mod_quiz.noquestions": "Ainda não foi inserida nenhuma pergunta", "addon.mod_quiz.noreviewattempt": "Você não tem permissão para revisar esta tentativa.", @@ -559,6 +657,7 @@ "addon.mod_quiz.yourfinalgradeis": "A sua nota final neste questionário é {{$a}}.", "addon.mod_resource.errorwhileloadingthecontent": "Erro ao carregar conteúdo.", "addon.mod_resource.modifieddate": "Modificado {{$a}}", + "addon.mod_resource.modulenameplural": "Arquivos", "addon.mod_resource.openthefile": "Abrir o arquivo", "addon.mod_resource.uploadeddate": "Carregado {{$a}}", "addon.mod_scorm.asset": "Recurso", @@ -595,6 +694,7 @@ "addon.mod_scorm.incomplete": "Incompleto", "addon.mod_scorm.lastattempt": "Última tentativa finalizada", "addon.mod_scorm.mode": "Modalidade", + "addon.mod_scorm.modulenameplural": "SCORMs/AICCs", "addon.mod_scorm.newattempt": "Começar uma nova tentativa", "addon.mod_scorm.noattemptsallowed": "Número de tentativas permitidas", "addon.mod_scorm.noattemptsmade": "Número de tentativas feitas", @@ -614,10 +714,12 @@ "addon.mod_survey.errorgetsurvey": "Erro ao obter dados da pesquisa.", "addon.mod_survey.ifoundthat": "Freqüência atual", "addon.mod_survey.ipreferthat": "Freqüência desejada", + "addon.mod_survey.modulenameplural": "Pesquisas de avaliação", "addon.mod_survey.responses": "Respostas", "addon.mod_survey.results": "Resultados", "addon.mod_survey.surveycompletednograph": "Você completou essa pesquisa.", "addon.mod_url.accessurl": "Acessar URL", + "addon.mod_url.modulenameplural": "URLs", "addon.mod_url.pointingtourl": "A URL desse recurso direciona para", "addon.mod_wiki.cannoteditpage": "Você não pode editar esta página.", "addon.mod_wiki.createpage": "Criar página", @@ -626,6 +728,7 @@ "addon.mod_wiki.errornowikiavailable": "Essa wiki não tem nenhum conteúdo ainda.", "addon.mod_wiki.gowikihome": "Ir para o ínicio da Wiki", "addon.mod_wiki.map": "Mapa", + "addon.mod_wiki.modulenameplural": "Wikis", "addon.mod_wiki.newpagehdr": "Nova página", "addon.mod_wiki.newpagetitle": "Novo título da página", "addon.mod_wiki.nocontent": "Não existe conteúdo para esta página", @@ -663,6 +766,7 @@ "addon.mod_workshop.gradinggradecalculated": "Nota calculada para avaliação", "addon.mod_workshop.gradinggradeof": "Nota para avaliação (de{{$a}})", "addon.mod_workshop.gradinggradeover": "Sobrescrever nota para avaliação", + "addon.mod_workshop.modulenameplural": "Laboratórios de Avaliação", "addon.mod_workshop.nogradeyet": "Nenhuma nota ainda", "addon.mod_workshop.notassessed": "Nada avaliado ainda", "addon.mod_workshop.notoverridden": "Não sobrescrito", @@ -1024,6 +1128,8 @@ "core.accounts": "Contas", "core.add": "Adicionar", "core.agelocationverification": "Verificação de idade e localização", + "core.ago": "{{$a}} atrás", + "core.all": "Todos", "core.allparticipants": "Todos os participantes", "core.android": "Android", "core.answer": "Resposta", @@ -1081,6 +1187,7 @@ "core.course.couldnotloadsectioncontent": "Não foi possível carregar o conteúdo da seção, por favor tente mais tarde.", "core.course.couldnotloadsections": "Não foi possível carregar a seção, por favor tente mais tarde.", "core.course.coursesummary": "Sumário do curso", + "core.course.downloadcourse": "Download do curso", "core.course.errordownloadingsection": "Erro ao baixar seção.", "core.course.errorgetmodule": "Erro ao obter os dados do módulo.", "core.course.hiddenfromstudents": "Oculto para estudantes", @@ -1095,7 +1202,6 @@ "core.courses.cannotretrievemorecategories": "Categorias mais profundas que o nível {{$a}} não podem ser recuperadas.", "core.courses.categories": "Categorias de Cursos", "core.courses.confirmselfenrol": "Tem certeza de que deseja inscrever-se neste curso?", - "core.courses.courseoverview": "Descrição do curso", "core.courses.courses": "Cursos", "core.courses.enrolme": "Inscreva-me", "core.courses.errorloadcategories": "Ocorreu um erro ao carregar categorias.", @@ -1104,21 +1210,14 @@ "core.courses.errorselfenrol": "Ocorreu um erro ao auto registo.", "core.courses.filtermycourses": "Filtrar meus cursos", "core.courses.frontpage": "Página principal", - "core.courses.future": "Não iniciados", - "core.courses.inprogress": "Em andamento", - "core.courses.morecourses": "Mais cursos", "core.courses.mycourses": "Meus cursos", + "core.courses.mymoodle": "Painel", "core.courses.nocourses": "Nenhuma informação disponível sobre o curso.", - "core.courses.nocoursesfuture": "Não há cursos não iniciados", - "core.courses.nocoursesinprogress": "Não há cursos em andamento", - "core.courses.nocoursesoverview": "Nenhum curso", - "core.courses.nocoursespast": "Não há cursos encerrados", "core.courses.nocoursesyet": "Nenhum curso nesta categoria", "core.courses.nosearchresults": "Nenhum resultado", "core.courses.notenroled": "Você não está inscrito como estudante neste curso", "core.courses.notenrollable": "Você não pode inscrever-se neste curso.", "core.courses.password": "Chave de inscrição", - "core.courses.past": "Encerrados", "core.courses.paymentrequired": "Este curso requer o pagamento da taxa de inscrição antes do acesso.", "core.courses.paypalaccepted": "Aceitamos pagamentos com PayPal", "core.courses.search": "Buscar", @@ -1126,6 +1225,7 @@ "core.courses.searchcoursesadvice": "Você pode usar o botão de busca para acessar os cursos como convidado ou matricular-se em cursos que permitem que ele.", "core.courses.selfenrolment": "Auto-inscrição", "core.courses.sendpaymentbutton": "Pagamento via Paypal", + "core.courses.show": "Mostrar este curso", "core.courses.totalcoursesearchresults": "Total de cursos: {{$a}}", "core.currentdevice": "Dispositivo atual", "core.datastoredoffline": "Os dados foram guardados no dispositivo porque não foi possível enviar agora. Os dados serão automaticamente enviados mais tarde.", @@ -1166,6 +1266,7 @@ "core.errorsync": "Um erro aconteceu quando estava sincronizando. Por favor tente novamente.", "core.errorsyncblocked": "Esse {{$a}} não pode ser sincronizado agora por causa de um processo em andamento. Por favor tente mais tarde. Se o problema persistir, tente reiniciar o aplicativo.", "core.explanationdigitalminor": "Essas informações são necessárias para determinar se sua idade é superior a idade digital de consentimento. Essa é a idade em que um indivíduo pode consentir que os termos e condições e seus dados sejam legalmente armazenados e processados.", + "core.favourites": "Favoritos", "core.filename": "Nome do arquivo", "core.filenameexist": "O nome do arquivo já existe: {{$a}}", "core.fileuploader.addfiletext": "Adicionar arquivo", @@ -1245,7 +1346,8 @@ "core.login.createaccount": "Cadastrar este novo usuário", "core.login.createuserandpass": "Escolha seu usuário e senha", "core.login.credentialsdescription": "Por favor, informe seu nome de usuário e senha para efetuar o login", - "core.login.emailconfirmsent": "

Um e-mail irá ser enviado para o seu endereço em {{$a}}

Ele irá conter instruções fáceis para completar seu registro.

Se você continua a ter dificuldades, contate o administrador do site.

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

Uma mensagem foi enviada para o seu endereço {{$a}}

Esta mensagem contém instruções para completar a sua inscrição.

Se você encontrar dificuldades contate o administrador.

", + "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", @@ -1258,7 +1360,7 @@ "core.login.helpmelogin": "

Existem milhares de sites usando Moodle no mundo. Este aplicativo somente pode conectar com um Moodle se este foi especialmente configurado para permitir o acesso móvel.

\nSe não conseguir se conectar com o seu Moodle, vai precisar entrar em contato com a administradora do seu Moodle e pedir ela acessar http://docs.moodle.org/en/Mobile_app

\n

Para testar o aplicativo em um site Moodle demo, digite teacher ou estudent no campo Usuário e clique no botão Adicionar.

", "core.login.instructions": "Instruções", "core.login.invalidaccount": "Por favor, verifique os detalhes de seu Usuário ou peça ao administrador do site para verificar a configuração do site.", - "core.login.invaliddate": "Data inválida", + "core.login.invaliddate": "Data não válida", "core.login.invalidemail": "Endereço de email inválido", "core.login.invalidmoodleversion": "Versão do Moodle inválida. A versão mínima requerida é:", "core.login.invalidsite": "A URL do siteé inválida.", @@ -1276,6 +1378,7 @@ "core.login.missingfirstname": "Está faltando o primeiro nome", "core.login.missinglastname": "Está faltando o sobrenome", "core.login.mobileservicesnotenabled": "Os serviços móveis não estão habilitados no seu Moodle. Por favor contate a administradora do seu Moodle se achar que o acesso móvel deveria estar habilitado", + "core.login.mustconfirm": "Você precisa confirmar o seu acesso", "core.login.newaccount": "Nova conta", "core.login.newsitedescription": "Por favor, digite a URL do seu site Moodle. Note que, pode ser que ele não esteja configurado para trabalhar com este app.", "core.login.notloggedin": "Você precisa estar logado.", @@ -1295,6 +1398,7 @@ "core.login.reconnect": "Reconectar", "core.login.reconnectdescription": "Seu token de autenticação é inválido ou expirou, você tem que reconectar com o site.", "core.login.reconnectssodescription": "Seu token de autenticação é inválido ou expirou, você tem que reconectar com o site. Você precisa efetuar login no site em uma janela do navegador.", + "core.login.resendemail": "Reenviar email", "core.login.security_question": "Pergunta de segurança", "core.login.selectacountry": "Selecione um país", "core.login.signupplugindisabled": "{{$a}} não está habilitado.", @@ -1317,8 +1421,6 @@ "core.mainmenu.changesite": "Mudar site", "core.mainmenu.help": "Ajuda", "core.mainmenu.logout": "Sair", - "core.mainmenu.mycourses": "Meus cursos", - "core.mainmenu.togglemenu": "Alternar menu", "core.mainmenu.website": "Site", "core.maxsizeandattachments": "Tamanho máximo para novos arquivos: {{$a.size}}, máximo de anexos: {{$a.attachments}}", "core.min": "minuto", @@ -1405,8 +1507,10 @@ "core.quotausage": "Você utilizou {{$a.used}} de seu limite de {{$a.total}}.", "core.redirectingtosite": "Você será redirecionado para o site.", "core.refresh": "Atualizar", + "core.remove": "Remover", "core.required": "Necessários", "core.requireduserdatamissing": "Este usuário não possui alguns dados de perfil exigidos. Por favor, preencha estes dados em seu Moodle e tente novamente.
{{$a}}", + "core.resources": "Recursos", "core.restore": "Restaurar", "core.retry": "Tentar novamente", "core.save": "Salvar", @@ -1491,6 +1595,21 @@ "core.sizetb": "TB", "core.sorry": "Desculpe...", "core.sortby": "Ordenar por", + "core.strftimedate": "%d %B %Y", + "core.strftimedatefullshort": "%d/%m/%y", + "core.strftimedateshort": "%d %B", + "core.strftimedatetime": "%d %B %Y, %H:%M %p", + "core.strftimedatetimeshort": "%d/%m/%Y %H:%M", + "core.strftimedaydate": "%A, %d %B %Y", + "core.strftimedaydatetime": "%A, %d %b %Y, %H:%M", + "core.strftimedayshort": "%A, %d %B", + "core.strftimedaytime": "%a, %H:%M", + "core.strftimemonthyear": "%B %Y", + "core.strftimerecent": "%d %b, %H:%M", + "core.strftimerecentfull": "%a, %d %b %Y, %H:%M %p", + "core.strftimetime": "%H:%M %p", + "core.strftimetime12": "%I:%M %p", + "core.strftimetime24": "%H:%M", "core.submit": "Enviar", "core.success": "Sucesso", "core.tablet": "Tablet", @@ -1532,7 +1651,7 @@ "core.user.roles": "Papéis", "core.user.sendemail": "Email", "core.user.student": "Estudante", - "core.user.teacher": "Professor não-editor", + "core.user.teacher": "Moderador", "core.user.webpage": "Página web", "core.userdeleted": "Esta conta de usuário foi cancelada", "core.userdetails": "Detalhes do usuário", diff --git a/src/assets/lang/pt.json b/src/assets/lang/pt.json index 452e5fb47..4346ed611 100644 --- a/src/assets/lang/pt.json +++ b/src/assets/lang/pt.json @@ -1,15 +1,61 @@ { + "addon.badges.alignment": "Competência", "addon.badges.badgedetails": "Detalhes da Medalha", "addon.badges.badges": "Medalhas", + "addon.badges.bendorsement": "Aval", + "addon.badges.claimcomment": "Comentário da acreditação", + "addon.badges.claimid": "URL da reivindicação", "addon.badges.contact": "Contacto", "addon.badges.dateawarded": "Data de emissão", "addon.badges.expired": "Expirada", "addon.badges.expirydate": "Data de validade", + "addon.badges.imageauthoremail": "E-mail do autor da imagem", + "addon.badges.imageauthorname": "E-mail do autor da imagem", + "addon.badges.imageauthorurl": "URL do autor da imagem", + "addon.badges.imagecaption": "Legenda da imagem", "addon.badges.issuancedetails": "Data de validade da Medalha", "addon.badges.issuerdetails": "Detalhes do emissor", + "addon.badges.issueremail": "E-mail", "addon.badges.issuername": "Nome do emissor", + "addon.badges.issuerurl": "URL do emissor", + "addon.badges.language": "Idioma", + "addon.badges.noalignment": "Nenhuma competência foi especificada para esta medalha.", "addon.badges.nobadges": "Não existem Medalhas disponíveis.", + "addon.badges.norelated": "Não existem medalhas relacionadas com esta medalha.", "addon.badges.recipientdetails": "Detalhes do condecorado", + "addon.badges.relatedbages": "Medalhas relacionadas", + "addon.badges.version": "Versão", + "addon.badges.warnexpired": "(A validade desta Medalha terminou!)", + "addon.block_activitymodules.pluginname": "Atividades", + "addon.block_myoverview.all": "Tudo", + "addon.block_myoverview.favourites": "Com estrela", + "addon.block_myoverview.future": "Próximos 30 dias", + "addon.block_myoverview.hiddencourses": "Oculta", + "addon.block_myoverview.inprogress": "Em progresso", + "addon.block_myoverview.lastaccessed": "Último acesso", + "addon.block_myoverview.morecourses": "Mais disciplinas", + "addon.block_myoverview.nocourses": "Sem disciplinas", + "addon.block_myoverview.past": "Histórico", + "addon.block_myoverview.pluginname": "Visão global", + "addon.block_myoverview.title": "Nome da disciplina", + "addon.block_recentlyaccessedcourses.nocourses": "Sem disciplinas recentes", + "addon.block_recentlyaccessedcourses.pluginname": "Disciplinas acedidas recentemente", + "addon.block_recentlyaccesseditems.noitems": "Sem itens recentes", + "addon.block_recentlyaccesseditems.pluginname": "Itens acedidos recentemente", + "addon.block_sitemainmenu.pluginname": "Menu principal", + "addon.block_starredcourses.nocourses": "Nenhuma disciplina com estrela", + "addon.block_starredcourses.pluginname": "Disciplinas com estrela", + "addon.block_timeline.duedate": "Data limite", + "addon.block_timeline.next30days": "Próximos 30 dias", + "addon.block_timeline.next3months": "Próximos 3 meses", + "addon.block_timeline.next6months": "Próximos 6 meses", + "addon.block_timeline.next7days": "Próximos 7 dias", + "addon.block_timeline.nocoursesinprogress": "Não existem disciplinas em progresso", + "addon.block_timeline.noevents": "Nenhuma atividade a terminar brevemente", + "addon.block_timeline.overdue": "Terminadas", + "addon.block_timeline.pluginname": "Cronograma", + "addon.block_timeline.sortbycourses": "Ordenar por disciplinas", + "addon.block_timeline.sortbydates": "Ordenar por datas", "addon.calendar.calendar": "Calendário", "addon.calendar.calendarevents": "Eventos do calendário", "addon.calendar.defaultnotificationtime": "Hora de notificação predefinida", @@ -83,14 +129,14 @@ "addon.coursecompletion.coursecompletion": "Conclusão da disciplina", "addon.coursecompletion.criteria": "Critérios", "addon.coursecompletion.criteriagroup": "Grupo de critérios", - "addon.coursecompletion.criteriarequiredall": "É exigido o cumprimento de todos os critérios abaixo.", - "addon.coursecompletion.criteriarequiredany": "É exigido o cumprimento de qualquer um dos critérios abaixo.", + "addon.coursecompletion.criteriarequiredall": "Todos os critérios abaixo são exigidos", + "addon.coursecompletion.criteriarequiredany": "Qualquer dos critérios abaixo é necessário", "addon.coursecompletion.inprogress": "Em progresso", "addon.coursecompletion.manualselfcompletion": "Conclusão manual pelo próprio", - "addon.coursecompletion.notyetstarted": "Não iniciou", + "addon.coursecompletion.notyetstarted": "Ainda não iniciou", "addon.coursecompletion.pending": "Pendente", - "addon.coursecompletion.required": "Exigido", - "addon.coursecompletion.requiredcriteria": "Critério exigido", + "addon.coursecompletion.required": "Obrigatório", + "addon.coursecompletion.requiredcriteria": "Critério obrigatório", "addon.coursecompletion.requirement": "Requisito", "addon.coursecompletion.status": "Estado", "addon.coursecompletion.viewcoursereport": "Ver relatório da disciplina", @@ -101,34 +147,80 @@ "addon.files.privatefiles": "Ficheiros privados", "addon.files.sitefiles": "Ficheiros do site", "addon.messageoutput_airnotifier.processorsettingsdesc": "Configurar dispositivos", + "addon.messages.acceptandaddcontact": "Aceitar e adicionar aos contactos", "addon.messages.addcontact": "Adicionar contacto", - "addon.messages.blockcontact": "Bloquear contacto", - "addon.messages.blockcontactconfirm": "Não irá receber mais mensagens deste contacto.", + "addon.messages.addcontactconfirm": "Tem a certeza de que pretende adicionar {{$a}} aos seus contactos?", + "addon.messages.addtofavourites": "Marcar com estrela", + "addon.messages.addtoyourcontacts": "Adicionar aos contactos", "addon.messages.blocknoncontacts": "Bloquear mensagens de pessoas que não estão na minha lista de contactos", + "addon.messages.blockuser": "Bloquear utilizador", + "addon.messages.blockuserconfirm": "Tem certeza de que pretende bloquear {{$a}}?", + "addon.messages.contactableprivacy": "Aceitar mensagens de:", + "addon.messages.contactableprivacy_coursemember": "Os Meus contactos e qualquer um das minhas disciplinas", + "addon.messages.contactableprivacy_onlycontacts": "Apenas os Meus contactos", + "addon.messages.contactableprivacy_site": "Qualquer utilizador do site", + "addon.messages.contactblocked": "Contacto bloqueado", "addon.messages.contactlistempty": "A lista de contactos está vazia", "addon.messages.contactname": "Nome do contacto", + "addon.messages.contactrequestsent": "Pedido de contacto enviado", "addon.messages.contacts": "Contactos", + "addon.messages.decline": "Rejeitar", + "addon.messages.deleteallconfirm": "Tem a certeza de que pretende apagar esta conversação por completo? Não será apagada para os outros participantes da conversação.", + "addon.messages.deleteconversation": "Apagar conversação", + "addon.messages.deletemessage": "Apagar mensagem", + "addon.messages.deletemessageconfirmation": "Tem a certeza de que pretende apagar esta mensagem? Apenas será excluída do seu histórico de mensagens. Poderá ser visualizada pelo utilizador que enviou ou recebeu a mensagem.", "addon.messages.errordeletemessage": "Erro ao eliminar a mensagem.", "addon.messages.errorwhileretrievingcontacts": "Erro ao obter contactos do servidor.", "addon.messages.errorwhileretrievingdiscussions": "Erro ao obter tópicos de discussão do servidor.", "addon.messages.errorwhileretrievingmessages": "Erro ao obter mensagens do servidor.", + "addon.messages.errorwhileretrievingusers": "Erro ao obter utilizadores do servidor.", + "addon.messages.groupconversations": "Grupo", + "addon.messages.groupinfo": "Informação do grupo", + "addon.messages.individualconversations": "Privada", + "addon.messages.info": "Informação", + "addon.messages.isnotinyourcontacts": "{{$a}} não pertence aos seus contactos", "addon.messages.message": "Mensagem", "addon.messages.messagenotsent": "A mensagem não foi enviada. Tente novamente mais tarde.", "addon.messages.messagepreferences": "Preferências das mensagens", "addon.messages.messages": "Mensagens", "addon.messages.newmessage": "Nova mensagem", "addon.messages.newmessages": "Novas mensagens", - "addon.messages.nomessages": "Sem mensagens", + "addon.messages.nocontactrequests": "Sem pedidos de contacto", + "addon.messages.nocontactsgetstarted": "Sem contactos", + "addon.messages.nofavourites": "Nenhuma mensagem com estrela", + "addon.messages.nogroupconversations": "Nenhuma conversação de grupo", + "addon.messages.noindividualconversations": "Nenhuma conversação privada", + "addon.messages.nomessagesfound": "Não foram encontradas mensagens", + "addon.messages.noncontacts": "Outros contactos", "addon.messages.nousersfound": "Nenhum utilizador encontrado", + "addon.messages.numparticipants": "{{$a}} participantes", "addon.messages.removecontact": "Remover contacto", - "addon.messages.removecontactconfirm": "O contacto será removido da sua lista de contactos.", + "addon.messages.removecontactconfirm": "Tem a certeza de que pretende remover {{$a}} dos seus contactos?", + "addon.messages.removefromfavourites": "Remover estrela", + "addon.messages.removefromyourcontacts": "Remover dos contactos", + "addon.messages.requests": "Pedidos", + "addon.messages.requirecontacttomessage": "Tem de pedir a {{$a}} que o adicione como um contacto para lhe(s) poder enviar mensagens.", + "addon.messages.searchcombined": "Procurar pessoas e mensagens", + "addon.messages.searchnocontactsfound": "Nenhum contacto encontrado", + "addon.messages.searchnomessagesfound": "Nenhuma mensagem encontrada", + "addon.messages.searchnononcontactsfound": "Nenhum contacto encontrado", + "addon.messages.sendcontactrequest": "Enviar pedido de contacto", + "addon.messages.showdeletemessages": "Mostrar o apagar mensagens", "addon.messages.type_blocked": "Bloqueado", "addon.messages.type_offline": "Offline", "addon.messages.type_online": "Online", "addon.messages.type_search": "Resultados da pesquisa", "addon.messages.type_strangers": "Outros", - "addon.messages.unblockcontact": "Desbloquear contacto", + "addon.messages.unabletomessage": "Não é permitido enviar mensagens para este utilizador", + "addon.messages.unblockuser": "Utilizador desbloqueado", + "addon.messages.unblockuserconfirm": "Tem a certeza de que pretende desbloquear {{$a}}?", + "addon.messages.userwouldliketocontactyou": "{{$a}} gostaria de entrar em contacto consigo", + "addon.messages.warningconversationmessagenotsent": "Não foi possível enviar mensagens para a conversação {{conversation}}. {{error}}", "addon.messages.warningmessagenotsent": "Não foi possível enviar mensagens ao utilizador {{user}}. {{error}}", + "addon.messages.wouldliketocontactyou": "Gostaria de entrar em contacto consigo", + "addon.messages.you": "Eu:", + "addon.messages.youhaveblockeduser": "Bloqueou este utilizador no passado", + "addon.messages.yourcontactrequestpending": "O seu pedido de contacto com {{$a}} está pendente", "addon.mod_assign.acceptsubmissionstatement": "Por favor, aceite a declaração de submissão.", "addon.mod_assign.addattempt": "Permitir nova tentativa", "addon.mod_assign.addnewattempt": "Adicionar nova tentativa", @@ -165,7 +257,9 @@ "addon.mod_assign.grade": "Nota", "addon.mod_assign.graded": "Avaliado", "addon.mod_assign.gradedby": "Avaliado por", + "addon.mod_assign.gradedfollowupsubmit": "Avaliada - seguir a submissão recebida", "addon.mod_assign.gradedon": "Avaliado em", + "addon.mod_assign.gradelocked": "Esta nota está bloqueada ou foi ajustada na Pauta.", "addon.mod_assign.gradenotsynced": "Nota não está sincronizada", "addon.mod_assign.gradeoutof": "Nota (de 0 a {{$a}})", "addon.mod_assign.gradingstatus": "Estado da avaliação", @@ -180,6 +274,7 @@ "addon.mod_assign.markingworkflowstatereadyforrelease": "Avaliação validada", "addon.mod_assign.markingworkflowstatereadyforreview": "Avaliação concluída", "addon.mod_assign.markingworkflowstatereleased": "Avaliação publicada", + "addon.mod_assign.modulenameplural": "Trabalhos", "addon.mod_assign.multipleteams": "Inscrito em mais do que um grupo", "addon.mod_assign.multipleteams_desc": "Este trabalho requer submissão em grupos. Você é membro de mais do que um grupo. Para conseguir submeter deve ser membro de apenas um grupo. Por favor, contacte o seu professor para alterar a sua participação nos grupos.", "addon.mod_assign.noattempt": "Nenhuma tentativa", @@ -234,6 +329,7 @@ "addon.mod_assign_submission_file.pluginname": "Submissão de ficheiros", "addon.mod_assign_submission_onlinetext.pluginname": "Submissões de texto online", "addon.mod_book.errorchapter": "Ocorreu um erro ao ler o capítulo do livro", + "addon.mod_book.modulenameplural": "Livros", "addon.mod_chat.beep": "Chamar", "addon.mod_chat.currentusers": "Utilizadores no chat", "addon.mod_chat.enterchat": "Clique aqui para entrar no chat", @@ -246,6 +342,7 @@ "addon.mod_chat.messagebeepsyou": "{{$a}} acabou de o chamar!", "addon.mod_chat.messageenter": "{{$a}} acabou de entrar neste chat", "addon.mod_chat.messageexit": "{{$a}} acabou de sair deste chat", + "addon.mod_chat.modulenameplural": "Chats", "addon.mod_chat.mustbeonlinetosendmessages": "Tem de estar online para poder enviar mensagens.", "addon.mod_chat.nomessages": "Ainda não há mensagens", "addon.mod_chat.send": "Enviar", @@ -256,6 +353,7 @@ "addon.mod_choice.errorgetchoice": "Erro ao obter os dados da sondagem.", "addon.mod_choice.expired": "Esta atividade terminou em {{$a}} e já não está disponível", "addon.mod_choice.full": "Completa", + "addon.mod_choice.modulenameplural": "Sondagens", "addon.mod_choice.noresultsviewable": "Os resultados da sondagem ainda não estão disponíveis.", "addon.mod_choice.notopenyet": "Lamentamos mas esta atividade só estará disponível em {{$a}}", "addon.mod_choice.numberofuser": "Número de respostas", @@ -293,8 +391,10 @@ "addon.mod_data.errormustsupplyvalue": "Indique aqui um valor.", "addon.mod_data.expired": "A atividade terminou em {{$a}} e não se encontra disponível", "addon.mod_data.fields": "Campos", + "addon.mod_data.foundrecords": "Registos encontrados: {{$a.num}}/{{$a.max}} (Nova pesquisa)", "addon.mod_data.latlongboth": "É necessário a latitude e a longitude.", "addon.mod_data.menuchoose": "Selecione...", + "addon.mod_data.modulenameplural": "Bases de dados", "addon.mod_data.more": "Mais", "addon.mod_data.nomatch": "Não foram encontrados registos correspondentes!", "addon.mod_data.norecords": "Não existem registos na base de dados", @@ -326,6 +426,7 @@ "addon.mod_feedback.feedbackopen": "Permitir respostas a partir de", "addon.mod_feedback.mapcourses": "Associar o inquérito a disciplinas", "addon.mod_feedback.mode": "Modo", + "addon.mod_feedback.modulenameplural": "Inquéritos", "addon.mod_feedback.next_page": "Página seguinte", "addon.mod_feedback.non_anonymous": "O nome do utilizador será registado e apresentado com as respostas", "addon.mod_feedback.non_anonymous_entries": "Respostas não anónimas ({{$a}})", @@ -346,6 +447,7 @@ "addon.mod_feedback.started": "Iniciado", "addon.mod_feedback.this_feedback_is_already_submitted": "Já concluiu esta atividade", "addon.mod_folder.emptyfilelist": "Não há ficheiros para mostrar.", + "addon.mod_folder.modulenameplural": "Pastas", "addon.mod_forum.addanewdiscussion": "Criar um novo tópico", "addon.mod_forum.addanewquestion": "Criar uma nova pergunta", "addon.mod_forum.addanewtopic": "Criar um novo tópico", @@ -368,6 +470,7 @@ "addon.mod_forum.modeflatnewestfirst": "Mostrar respostas por ordem, a começar pela mais recente", "addon.mod_forum.modeflatoldestfirst": "Mostrar respostas por ordem, a começar pela mais antiga", "addon.mod_forum.modenested": "Mostrar respostas em lista encadeada", + "addon.mod_forum.modulenameplural": "Fóruns", "addon.mod_forum.numdiscussions": "{{numdiscussions}} tópicos de discussão", "addon.mod_forum.numreplies": "{{numreplies}} respostas", "addon.mod_forum.posttoforum": "Submeter tópico", @@ -403,9 +506,11 @@ "addon.mod_glossary.fillfields": "Os campos termo e descrição são obrigatórios.", "addon.mod_glossary.fullmatch": "Localizar apenas Palavras inteiras", "addon.mod_glossary.linking": "Hiperligações automáticas", + "addon.mod_glossary.modulenameplural": "Glossários", "addon.mod_glossary.noentriesfound": "Não foi encontrado nenhum termo.", "addon.mod_glossary.searchquery": "Pesquisa", "addon.mod_imscp.deploymenterror": "Erro no pacote de conteúdo!", + "addon.mod_imscp.modulenameplural": "Pacotes IMS", "addon.mod_imscp.showmoduledescription": "Mostrar descrição", "addon.mod_lesson.answer": "Resposta", "addon.mod_lesson.attempt": "Tentativa: {{$a}}", @@ -449,6 +554,7 @@ "addon.mod_lesson.lowtime": "Tempo mínimo", "addon.mod_lesson.maximumnumberofattemptsreached": "Atingiu o número máximo de tentativas permitido - A lição vai avançar para a página seguinte", "addon.mod_lesson.modattemptsnoteacher": "A possibilidade de revisão só funciona para os alunos.", + "addon.mod_lesson.modulenameplural": "Lições", "addon.mod_lesson.noanswer": "Uma ou mais perguntas estão ainda por responder. Por favor, volte atrás e submeta uma resposta.", "addon.mod_lesson.nolessonattempts": "Não houve tentativas de realização desta lição.", "addon.mod_lesson.nolessonattemptsgroup": "Não foram realizadas tentativas por membros do grupo {{$a}} nesta lição.", @@ -493,7 +599,9 @@ "addon.mod_lti.errorgetlti": "Erro ao obter os dados do módulo.", "addon.mod_lti.errorinvalidlaunchurl": "O URL de lançamento não é válido.", "addon.mod_lti.launchactivity": "Iniciar a atividade", + "addon.mod_lti.modulenameplural": "Ferramentas externas", "addon.mod_page.errorwhileloadingthepage": "Erro ao carregar o conteúdo da página.", + "addon.mod_page.modulenameplural": "Páginas", "addon.mod_quiz.attemptfirst": "Primeira tentativa", "addon.mod_quiz.attemptlast": "Última tentativa", "addon.mod_quiz.attemptnumber": "Tentativa", @@ -528,6 +636,7 @@ "addon.mod_quiz.grademethod": "Método de avaliação", "addon.mod_quiz.gradesofar": "{{$a.method}}: {{$a.mygrade}} / {{$a.quizgrade}}.", "addon.mod_quiz.marks": "Nota", + "addon.mod_quiz.modulenameplural": "Testes", "addon.mod_quiz.mustbesubmittedby": "Esta tentativa deve ser submetida até {{$a}}.", "addon.mod_quiz.noquestions": "Ainda não foi adicionada nenhuma pergunta", "addon.mod_quiz.noreviewattempt": "Não pode rever esta tentativa", @@ -572,6 +681,7 @@ "addon.mod_quiz.yourfinalgradeis": "A sua nota final do teste é {{$a}}", "addon.mod_resource.errorwhileloadingthecontent": "Erro ao carregar o conteúdo.", "addon.mod_resource.modifieddate": "Modificado {{$a}}", + "addon.mod_resource.modulenameplural": "Ficheiros", "addon.mod_resource.openthefile": "Abrir o ficheiro", "addon.mod_resource.uploadeddate": "Carregado {{$a}}", "addon.mod_scorm.asset": "Recurso", @@ -608,6 +718,7 @@ "addon.mod_scorm.incomplete": "Incompleto", "addon.mod_scorm.lastattempt": "Última tentativa concluída", "addon.mod_scorm.mode": "Modo", + "addon.mod_scorm.modulenameplural": "Pacotes SCORM", "addon.mod_scorm.newattempt": "Iniciar uma nova tentativa", "addon.mod_scorm.noattemptsallowed": "Número de tentativas permitidas", "addon.mod_scorm.noattemptsmade": "Número de tentativas realizadas", @@ -627,10 +738,12 @@ "addon.mod_survey.errorgetsurvey": "Erro ao obter os dados do inquérito.", "addon.mod_survey.ifoundthat": "Achei isso", "addon.mod_survey.ipreferthat": "Prefiro isso", + "addon.mod_survey.modulenameplural": "Inquéritos predefinidos", "addon.mod_survey.responses": "Respostas", "addon.mod_survey.results": "Resultados", "addon.mod_survey.surveycompletednograph": "Completou o presente inquérito.", "addon.mod_url.accessurl": "Aceder ao URL", + "addon.mod_url.modulenameplural": "URLs", "addon.mod_url.pointingtourl": "O URL para onde aponta este recurso.", "addon.mod_wiki.cannoteditpage": "Não pode editar esta página.", "addon.mod_wiki.createpage": "Criar página", @@ -639,6 +752,7 @@ "addon.mod_wiki.errornowikiavailable": "Este Wiki ainda não possui conteúdo.", "addon.mod_wiki.gowikihome": "Ir para página principal do Wiki", "addon.mod_wiki.map": "Mapa", + "addon.mod_wiki.modulenameplural": "Wikis", "addon.mod_wiki.newpagehdr": "Nova página", "addon.mod_wiki.newpagetitle": "Novo título da página", "addon.mod_wiki.nocontent": "Não há nenhum conteúdo nesta página", @@ -677,6 +791,7 @@ "addon.mod_workshop.gradinggradecalculated": "Nota da avaliação", "addon.mod_workshop.gradinggradeof": "Nota da avaliação (em {{$a}})", "addon.mod_workshop.gradinggradeover": "Substituir nota da avaliação", + "addon.mod_workshop.modulenameplural": "Workshops", "addon.mod_workshop.nogradeyet": "Ainda não tem nota", "addon.mod_workshop.notassessed": "Ainda não foi avaliado", "addon.mod_workshop.notoverridden": "Não substituir", @@ -689,10 +804,11 @@ "addon.mod_workshop.receivedgrades": "Notas obtidas", "addon.mod_workshop.submissionattachment": "Anexo", "addon.mod_workshop.submissioncontent": "Conteúdo submetido", - "addon.mod_workshop.submissiondeleteconfirm": "Tem a certeza que pretende apagar a seguinte submissão?", + "addon.mod_workshop.submissiondeleteconfirm": "Tem a certeza de que pretende apagar a seguinte submissão?", "addon.mod_workshop.submissiongrade": "Nota máxima do trabalho", "addon.mod_workshop.submissiongradeof": "Nota do trabalho (em {{$a}})", "addon.mod_workshop.submissionrequiredcontent": "Tem de inserir texto ou adicionar um ficheiro", + "addon.mod_workshop.submissionrequiredtitle": "É necessário indicar um título.", "addon.mod_workshop.submissionsreport": "Relatório de submissões do workshop", "addon.mod_workshop.submissiontitle": "Titulo", "addon.mod_workshop.switchphase10": "Mudar para a fase de configuração", @@ -1041,6 +1157,8 @@ "core.accounts": "Contas", "core.add": "Adicionar", "core.agelocationverification": "Verificação de idade e localização", + "core.ago": "{{$a}} atrás", + "core.all": "Todos", "core.allparticipants": "Todos", "core.android": "Android", "core.answer": "Resposta", @@ -1078,7 +1196,7 @@ "core.confirmdeletefile": "Tem a certeza de que pretende apagar este ficheiro?", "core.confirmloss": "Tem a certeza absoluta? Todas as alterações serão perdidas.", "core.confirmopeninbrowser": "Pretende abrir a ligação no navegador de internet?", - "core.considereddigitalminor": "Com a sua idade é considerado uma criança para efeitos de consentimento.", + "core.considereddigitalminor": "Ainda não tem idade para criar uma conta neste site", "core.content": "Conteúdo", "core.contenteditingsynced": "O conteúdo que está a editar foi sincronizado.", "core.contentlinks.chooseaccount": "Escolha a conta", @@ -1101,24 +1219,28 @@ "core.course.contents": "Conteúdos", "core.course.couldnotloadsectioncontent": "Não foi possível carregar o conteúdo da secção. Por favor tente novamente mais tarde.", "core.course.couldnotloadsections": "Não foi possível carregar as secções. Por favor tente novamente mais tarde.", - "core.course.coursesummary": "Resumo", + "core.course.coursesummary": "Descrição da disciplina", "core.course.downloadcourse": "Descarregar disciplina", "core.course.errordownloadingcourse": "Erro ao descarregar a disciplina", "core.course.errordownloadingsection": "Erro ao descarregar a secção.", "core.course.errorgetmodule": "Erro ao obter os dados da atividade.", "core.course.hiddenfromstudents": "Oculto para os alunos", "core.course.hiddenoncoursepage": "Disponível mas não mostrar na página da disciplina", + "core.course.manualcompletionnotsynced": "Conclusão manual não sincronizada.", "core.course.nocontentavailable": "Nenhum conteúdo disponível neste momento.", "core.course.overriddennotice": "A sua nota final nesta atividade foi alterada manualmente na pauta.", + "core.course.refreshcourse": "Atualizar disciplina", "core.course.sections": "Secções", "core.course.useactivityonbrowser": "Ainda pode usá-lo no navegador do seu dispositivo.", + "core.course.warningmanualcompletionmodified": "A conclusão manual de uma atividade foi modificada no site.", + "core.course.warningofflinemanualcompletiondeleted": "Uma conclusão manual offline da disciplina '{{name}}' foi apagada. {{error}}", "core.coursedetails": "Detalhes da disciplina", + "core.courses.addtofavourites": "Marcar disciplina com estrela", "core.courses.allowguests": "Esta disciplina permite o acesso a visitantes", "core.courses.availablecourses": "Disciplinas disponíveis", "core.courses.cannotretrievemorecategories": "Categorias abaixo do nível {{$a}} não podem ser recuperadas.", "core.courses.categories": "Categorias de disciplinas", "core.courses.confirmselfenrol": "Tem certeza de que deseja inscrever-se nesta disciplina?", - "core.courses.courseoverview": "Sumário da disciplina", "core.courses.courses": "Disciplinas", "core.courses.downloadcourses": "Descarregar disciplinas", "core.courses.enrolme": "Inscreva-me", @@ -1128,28 +1250,24 @@ "core.courses.errorselfenrol": "Ocorreu um erro na autoinscrição.", "core.courses.filtermycourses": "Filtrar as minhas disciplinas", "core.courses.frontpage": "Página principal", - "core.courses.future": "Próximos 30 dias", - "core.courses.inprogress": "Em progresso", - "core.courses.morecourses": "Mais disciplinas", + "core.courses.hidecourse": "Ocultar da vista", "core.courses.mycourses": "As minhas disciplinas", + "core.courses.mymoodle": "Painel do utilizador", "core.courses.nocourses": "Sem informação das suas disciplinas para mostrar.", - "core.courses.nocoursesfuture": "Nenhuma disciplina a começar", - "core.courses.nocoursesinprogress": "Nenhuma disciplina a decorrer", - "core.courses.nocoursesoverview": "Em nenhuma disciplina", - "core.courses.nocoursespast": "Nenhuma disciplina terminada", "core.courses.nocoursesyet": "Nenhuma disciplina nesta categoria", "core.courses.nosearchresults": "Sem resultados", "core.courses.notenroled": "Não está inscrito como aluno nesta disciplina", "core.courses.notenrollable": "Não se pode inscrever nesta disciplina.", "core.courses.password": "Senha de inscrição", - "core.courses.past": "Passado", "core.courses.paymentrequired": "Esta disciplina exige uma taxa de inscrição.", "core.courses.paypalaccepted": "Tipos de pagamentos aceites", + "core.courses.removefromfavourites": "Desmarcar a estrela desta disciplina", "core.courses.search": "Procurar", "core.courses.searchcourses": "Procurar disciplinas", "core.courses.searchcoursesadvice": "Pode usar o botão Procurar disciplinas para encontrar disciplinas com acesso permitido a visitantes ou inscrever-se em disciplinas que o permitam.", "core.courses.selfenrolment": "Autoinscrição", "core.courses.sendpaymentbutton": "Realizar pagamento através de \"Paypal\"", + "core.courses.show": "Mostrar esta disciplina", "core.courses.totalcoursesearchresults": "Total de disciplinas: {{$a}}", "core.currentdevice": "Dispositivo atual", "core.datastoredoffline": "Dados armazenados no dispositivo por não ter sido possível enviar. Serão automaticamente enviados mais tarde.", @@ -1169,7 +1287,7 @@ "core.dfmediumdate": "LLL", "core.dftimedate": "hh[:]mm", "core.digitalminor": "Criança", - "core.digitalminor_desc": "Para criar uma conta neste site, peça aos seus pais/responsáveis que contactem a pessoa indicada a seguir.", + "core.digitalminor_desc": "Peça aos seus pais/responsáveis para contactarem:", "core.discard": "Descartar", "core.dismiss": "Dispensar", "core.done": "Concluído", @@ -1186,6 +1304,7 @@ "core.errorinvalidform": "O formulário contém dados inválidos. Por favor, verifique se todos os campos obrigatórios estão preenchidos e que os dados são válidos.", "core.errorinvalidresponse": "Foi recebida uma resposta inválida. Se o erro persistir, por favor, contacte o administrador do site.", "core.errorloadingcontent": "Erro ao carregar conteúdo.", + "core.errorofflinedisabled": "A navegação offline está desativada no site. Precisa de estar ligado à internet para usar a aplicação.", "core.erroropenfilenoapp": "Erro ao abrir o ficheiro: não foi encontrada nenhuma aplicação compatível com este tipo de ficheiro.", "core.erroropenfilenoextension": "Erro ao abrir o ficheiro: o ficheiro não possui uma extensão.", "core.erroropenpopup": "Esta atividade está a tentar abrir uma janela pop-up. Esta funcionalidade não é suportada na aplicação.", @@ -1193,6 +1312,7 @@ "core.errorsync": "Ocorreu um erro durante a sincronização. Por favor, tente novamente.", "core.errorsyncblocked": "Não é possível sincronizar agora este {{$a}} devido a outro processo já em andamento. Por favor, tente novamente mais tarde. Se o problema persistir, tente reiniciar a aplicação.", "core.explanationdigitalminor": "Esta informação é necessária para determinar se a sua idade excede a idade mínima de consentimento. Esta é a idade em que um indivíduo pode dar o seu consentimento aos termos e condições e ao armazenamento e tratamento dos seus dados pessoais.", + "core.favourites": "Com estrela", "core.filename": "Nome do ficheiro", "core.filenameexist": "O nome do ficheiro já existe: {{$a}}", "core.fileuploader.addfiletext": "Adicionar ficheiro", @@ -1216,6 +1336,7 @@ "core.fileuploader.more": "Mais", "core.fileuploader.photoalbums": "Álbuns de fotos", "core.fileuploader.readingfile": "A ler ficheiro", + "core.fileuploader.readingfileperc": "A ler ficheiro: {{$a}}%", "core.fileuploader.selectafile": "Selecione um ficheiro", "core.fileuploader.uploadafile": "Carregar um ficheiro", "core.fileuploader.uploading": "A carregar", @@ -1274,12 +1395,15 @@ "core.login.createaccount": "Criar a minha conta", "core.login.createuserandpass": "Escolha um nome de utilizador e senha", "core.login.credentialsdescription": "Por favor, digite o nome de utilizador e senha para entrar", - "core.login.emailconfirmsent": "

Um e-mail deve ter sido enviado para o seu endereço {{$a}}

. Contém instruções fáceis para concluir o seu registo. Se continuar a ter dificuldades, entre em contacto com o administrador do site.

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

Acaba de ser enviada uma mensagem para o seu endereço de e-mail{{$a}}, com instruções fáceis para completar a sua inscrição.

Se tiver alguma dificuldade em completar o registo, contacte o administrador do site.

", + "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.", + "core.login.findyoursite": "Procurar o site", "core.login.firsttime": "É a sua primeira visita aqui?", "core.login.forgotten": "Esqueceu-se do seu nome de utilizador ou da senha?", "core.login.getanothercaptcha": "Gerar outro CAPTCHA", @@ -1287,7 +1411,7 @@ "core.login.helpmelogin": "

Existem milhares de sites Moodle no mundo. Esta aplicação apenas consegue aceder a sites Moodle que tenham o acesso a dispositivos móveis ativo.

\nCaso não consiga aceder ao seu site Moodle através da aplicação, terá de entrar em contacto com o administrador do seu site e solicitar o acesso.http://docs.moodle.org/en/Mobile_app

Para testar a aplicação num site demo do Moodle digite teacher ou student no campo Site URL e clique no botão Ligar.

", "core.login.instructions": "Instruções", "core.login.invalidaccount": "Por favor, verifique os detalhes dos seus dados de utilizador ou peça ao administrador do site para verificar as configurações do site.", - "core.login.invaliddate": "Data inválida", + "core.login.invaliddate": "Data não válida", "core.login.invalidemail": "Endereço de e-mail inválido", "core.login.invalidmoodleversion": "A versão do Moodle é inválida. É necessária a versão 2.4 ou superior.", "core.login.invalidsite": "O URL do site é inválido.", @@ -1295,16 +1419,19 @@ "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.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": "A verificação do Moodle Mobile Additional Features teve um erro inesperado. 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", "core.login.loginbutton": "Iniciar sessão", "core.login.logininsiterequired": "Precisa de entrar no site através de uma janela de navegador.", "core.login.loginsteps": "Para ter acesso completo a este site, primeiro precisa de criar uma nova conta de utilizador.", - "core.login.missingemail": "Falta o e-mail", + "core.login.missingemail": "Falta o endereço de e-mail", "core.login.missingfirstname": "Falta o nome", "core.login.missinglastname": "Falta o apelido", "core.login.mobileservicesnotenabled": "O seu site não permite acesso através da Mobile App. Por favor, contacte o administrador se pretender que esta funcionalidade seja ativada.", + "core.login.mustconfirm": "Precisa confirmar a sua conta", "core.login.newaccount": "Nova conta", "core.login.newsitedescription": "Por favor, digite o URL do seu site Moodle. Note que o mesmo poderá não estar configurado para funcionar nesta aplicação.", "core.login.notloggedin": "Precisa de estar autenticado.", @@ -1316,20 +1443,24 @@ "core.login.policyagree": "Deverá aceitar este regulamento para poder proceder a utilizar este site. Aceita o regulamento?", "core.login.policyagreement": "Política do site", "core.login.policyagreementclick": "Link para a política do site", - "core.login.potentialidps": "Autenticar-se usando a sua conta em:", + "core.login.potentialidps": "Autenticar-se usando a sua conta:", "core.login.problemconnectingerror": "Existem alguns problemas na ligação a", "core.login.problemconnectingerrorcontinue": "Verifique se introduziu correctamente o endereço e tente novamente.", "core.login.profileinvaliddata": "Valor inválido", "core.login.recaptchachallengeimage": "Imagem reCAPTCHA", + "core.login.recaptchaexpired": "O prazo de verificação expirou. Responda à pergunta de segurança novamente.", + "core.login.recaptchaincorrect": "A resposta à pergunta de segurança está incorreta.", "core.login.reconnect": "Entrar novamente", "core.login.reconnectdescription": "O seu token de autenticação é inválido ou expirou. Tem de entrar novamente no site.", "core.login.reconnectssodescription": "O seu token de autenticação é inválido ou expirou. Tem de entrar novamente no site. Precisa de autenticar-se no site através de uma janela de navegador.", + "core.login.resendemail": "Reenviar e-mail", "core.login.searchby": "Procurar por:", "core.login.security_question": "Pergunta de segurança", "core.login.selectacountry": "Selecione um país", "core.login.selectsite": "Selecione o seu site:", "core.login.signupplugindisabled": "{{$a}} não está ativado.", "core.login.siteaddress": "Endereço do site", + "core.login.sitehasredirect": "O seu site contém pelo menos um redirecionamento de HTTP. A aplicação não pode seguir redirecionamentos. Esse pode ser o problema que impede a aplicação de se ligar ao seu site.", "core.login.siteinmaintenance": "O seu site está em modo de manutenção", "core.login.sitepolicynotagreederror": "Não concordou com as políticas do site.", "core.login.siteurl": "URL do site", @@ -1348,8 +1479,6 @@ "core.mainmenu.changesite": "Mudar de site", "core.mainmenu.help": "Ajuda", "core.mainmenu.logout": "Sair", - "core.mainmenu.mycourses": "As minhas disciplinas", - "core.mainmenu.togglemenu": "Alternar menu", "core.mainmenu.website": "Site", "core.maxsizeandattachments": "Tamanho máximo para novos ficheiros: {{$a.size}}, número máximo de anexos: {{$a.attachments}}", "core.min": "minuto", @@ -1384,6 +1513,7 @@ "core.more": "mais", "core.mygroups": "Os meus grupos", "core.name": "Designação", + "core.networkerroriframemsg": "Este conteúdo não está disponível offline. Por favor, ligue-se à internet e tente novamente.", "core.networkerrormsg": "Houve um problema ao ligar ao site. Por favor verifique sua ligação e tente novamente.", "core.never": "Nunca", "core.next": "Seguinte", @@ -1392,7 +1522,8 @@ "core.nograde": "Nenhuma nota", "core.none": "Nenhum", "core.nopasswordchangeforced": "Não pode prosseguir sem alterar sua senha.", - "core.nopermissions": "Atualmente, não tem permissões para realizar a operação {{$a}}", + "core.nopermissionerror": "Desculpe, mas não tem permissões para executar esta operação", + "core.nopermissions": "Atualmente, não tem permissões para realizar a operação '{{$a}}'", "core.noresults": "Sem resultados", "core.notapplicable": "n/a", "core.notice": "Aviso", @@ -1416,6 +1547,7 @@ "core.pulltorefresh": "Puxe para atualizar", "core.question.answer": "Resposta", "core.question.answersaved": "Resposta guardada", + "core.question.cannotdeterminestatus": "Não é possível determinar o estado", "core.question.certainty": "Certeza", "core.question.complete": "Respondida", "core.question.correct": "Correto", @@ -1436,8 +1568,10 @@ "core.quotausage": "Está atualmente a usar {{$a.used}} do máximo de {{$a.total}}.", "core.redirectingtosite": "Irá ser redirecionado para o site.", "core.refresh": "Atualizar", + "core.remove": "Remover", "core.required": "Obrigatório", "core.requireduserdatamissing": "Este utilizador não possui todos os dados de perfil obrigatórios. Por favor, preencha estes dados no seu site e tente novamente.
{{$a}}", + "core.resources": "Recursos", "core.restore": "Restaurar", "core.retry": "Tentar novamente", "core.save": "Guardar", @@ -1454,6 +1588,7 @@ "core.settings.appready": "Aplicação pronta", "core.settings.cannotsyncoffline": "Não é possível sincronizar offline.", "core.settings.cannotsyncwithoutwifi": "Não é possível sincronizar porque as configurações atuais só permitem sincronizar quando ligado a uma rede sem fios. Por favor, ligue-se a uma rede Wi-Fi.", + "core.settings.compilationinfo": "Informação da compilação", "core.settings.cordovadevicemodel": "Modelo do Cordova device", "core.settings.cordovadeviceosversion": "Versão OS do Cordova device", "core.settings.cordovadeviceplatform": "Plataforma Cordova device", @@ -1461,6 +1596,7 @@ "core.settings.cordovaversion": "Versão do Cordova", "core.settings.currentlanguage": "Idioma usado atualmente", "core.settings.debugdisplay": "Mostrar mensagens de erro", + "core.settings.debugdisplaydescription": "Se ativar esta opção, as janelas modais dos erros mostrarão mais informações sobre o erro, se possível.", "core.settings.deletesitefiles": "Tem a certeza que pretende apagar os ficheiros descarregados a partir do site '{{sitename}}'?", "core.settings.deletesitefilestitle": "Excluir os ficheiros do site", "core.settings.deviceinfo": "Informação do dispositivo", @@ -1491,6 +1627,7 @@ "core.settings.privacypolicy": "Politica de privacidade", "core.settings.reportinbackground": "Reportar erros automaticamente", "core.settings.settings": "Configurações", + "core.settings.showdownloadoptions": "Mostrar opções de descarga", "core.settings.sites": "Sites", "core.settings.spaceusage": "Utilização do espaço", "core.settings.synchronization": "Sincronização", @@ -1523,13 +1660,28 @@ "core.sorry": "Desculpe...", "core.sortby": "Ordenar por", "core.start": "Executar", + "core.strftimedate": "%d %B %Y", + "core.strftimedatefullshort": "%d/%m/%y", + "core.strftimedateshort": "%d %B", + "core.strftimedatetime": "%d %B %Y, %H:%M", + "core.strftimedatetimeshort": "%d/%m/%y, %H:%M", + "core.strftimedaydate": "%A, %d %B %Y", + "core.strftimedaydatetime": "%A, %d %B %Y, %H:%M", + "core.strftimedayshort": "%A, %d %B", + "core.strftimedaytime": "%a, %H:%M", + "core.strftimemonthyear": "%B %Y", + "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.submit": "Submeter", "core.success": "Operação realizada com sucesso!", "core.tablet": "Tablet", "core.teachers": "Professores", "core.thereisdatatosync": "Existem {{$a}} offline que têm de ser sincronizados.", "core.thisdirection": "ltr", - "core.time": "Hora", + "core.time": "Data/Hora", "core.timesup": "O tempo terminou!", "core.today": "Hoje", "core.tryagain": "Tente novamente", @@ -1541,7 +1693,7 @@ "core.unknown": "Desconhecido", "core.unlimited": "Ilimitado(a)", "core.unzipping": "A descomprimir", - "core.upgraderunning": "O site está em processo de atualização, por favor, tente novamente mais tarde.", + "core.upgraderunning": "O site está em processo de atualização. Por favor, tente novamente mais tarde.", "core.user.address": "Morada", "core.user.city": "Cidade/Estado", "core.user.contact": "Contacto", @@ -1552,6 +1704,7 @@ "core.user.editingteacher": "Professor", "core.user.email": "Endereço de e-mail", "core.user.emailagain": "E-mail (novamente)", + "core.user.errorloaduser": "Erro ao carregar o utilizador", "core.user.firstname": "Nome", "core.user.interests": "Interesses", "core.user.lastname": "Apelido", @@ -1571,6 +1724,9 @@ "core.usernotfullysetup": "O utilizador não está totalmente configurado", "core.users": "Utilizadores", "core.view": "Ver", + "core.viewcode": "Ver código", + "core.vieweditor": "Ver editor", + "core.viewembeddedcontent": "Ver conteúdo incorporado", "core.viewprofile": "Ver perfil", "core.warningofflinedatadeleted": "Os dados offline de {{component}} '{{name}}' foram apagados. {{error}}", "core.whatisyourage": "Qual é a sua idade?", diff --git a/src/assets/lang/ro.json b/src/assets/lang/ro.json index cc3e5242a..c410a4635 100644 --- a/src/assets/lang/ro.json +++ b/src/assets/lang/ro.json @@ -8,8 +8,32 @@ "addon.badges.issuancedetails": "Expirare ecuson", "addon.badges.issuerdetails": "Detalii emitent", "addon.badges.issuername": "Nume emitent", + "addon.badges.issuerurl": "URL emitent", "addon.badges.nobadges": "Nu există ecusoane disponibile", "addon.badges.recipientdetails": "Detalii recipient", + "addon.badges.warnexpired": "(Acest ecuson a expirat!)", + "addon.block_activitymodules.pluginname": "Activităţi", + "addon.block_myoverview.all": "Toate", + "addon.block_myoverview.future": "Viitoare", + "addon.block_myoverview.hiddencourses": "Ascuns", + "addon.block_myoverview.inprogress": "În desfășurare", + "addon.block_myoverview.lastaccessed": "Accesat ultima dată", + "addon.block_myoverview.morecourses": "Mai multe cursuri", + "addon.block_myoverview.nocourses": "Nu există cursuri", + "addon.block_myoverview.past": "În trecut", + "addon.block_myoverview.title": "Numele cursului", + "addon.block_recentlyaccessedcourses.nocourses": "Nu există cursuri recente", + "addon.block_recentlyaccessedcourses.pluginname": "Cursuri accesate recent", + "addon.block_sitemainmenu.pluginname": "Meniu principal", + "addon.block_starredcourses.nocourses": "Nu există cursuri importante", + "addon.block_starredcourses.pluginname": "Cursuri importante", + "addon.block_timeline.next30days": "Următoarele 30 de zile", + "addon.block_timeline.next3months": "Următoarele 3 luni", + "addon.block_timeline.next6months": "Următoarele 6 luni", + "addon.block_timeline.next7days": "Următoarele 7 zile", + "addon.block_timeline.pluginname": "Cronologie", + "addon.block_timeline.sortbycourses": "Sortează după cursuri", + "addon.block_timeline.sortbydates": "Sortează după dată", "addon.calendar.calendar": "Calendar", "addon.calendar.calendarevents": "Evenimente din calendar", "addon.calendar.errorloadevent": "Eroare la încărcarea unui eveniment.", @@ -46,32 +70,33 @@ "addon.competency.userplans": "Planuri de învățare", "addon.coursecompletion.complete": "Complet", "addon.coursecompletion.completecourse": "Curs complet", - "addon.coursecompletion.completed": "Complet", + "addon.coursecompletion.completed": "Finalizare", "addon.coursecompletion.completiondate": "Data limita până la completarea acțiunii", "addon.coursecompletion.completionmenuitem": "Finalizare", "addon.coursecompletion.couldnotloadreport": "Raportul cu privire la situația completării cursului nu se poate încărca, încercați mai târziu.", - "addon.coursecompletion.coursecompletion": "Completarea cursului", + "addon.coursecompletion.coursecompletion": "Completare curs", "addon.coursecompletion.criteria": "Criterii", - "addon.coursecompletion.criteriagroup": "Criterii pentru grup", - "addon.coursecompletion.criteriarequiredall": "Indeplinirea următoarelor criterii este obligatorie", - "addon.coursecompletion.criteriarequiredany": "Oricare din urmatoarele criterii sunt obligatorii", - "addon.coursecompletion.inprogress": "În progres", - "addon.coursecompletion.manualselfcompletion": "Autocompletare", - "addon.coursecompletion.notyetstarted": "Înca nu a început", - "addon.coursecompletion.pending": "În asteptare", + "addon.coursecompletion.criteriagroup": "Grup criterii", + "addon.coursecompletion.criteriarequiredall": "Toate criteriile de mai jos sunt necesare", + "addon.coursecompletion.criteriarequiredany": "Oricare dintre criteriile de mai jos sunt necesare", + "addon.coursecompletion.inprogress": "În curs", + "addon.coursecompletion.manualselfcompletion": "Auto-finalizare manuală", + "addon.coursecompletion.notyetstarted": "Nu a fost încă început", + "addon.coursecompletion.pending": "În așteptare", "addon.coursecompletion.required": "Obligatoriu", - "addon.coursecompletion.requiredcriteria": "Criterii obligatorii", - "addon.coursecompletion.requirement": "Cerințe", - "addon.coursecompletion.status": "Situație", - "addon.coursecompletion.viewcoursereport": "Vizualizați raportul despre curs", + "addon.coursecompletion.requiredcriteria": "Criteriu necesar", + "addon.coursecompletion.requirement": "Cerință", + "addon.coursecompletion.status": "Status", + "addon.coursecompletion.viewcoursereport": "Vezi raportul cursului", "addon.files.couldnotloadfiles": "Lista fișierelor nu a putut fi încărcată.", "addon.files.emptyfilelist": "Nu sunt fișiere disponibile.", "addon.files.files": "Fişiere", "addon.files.privatefiles": "Fișiere private", "addon.files.sitefiles": "Fişiere site", "addon.messages.addcontact": "Adaugă prieten", - "addon.messages.blockcontact": "Blochează această persoană", + "addon.messages.addtoyourcontacts": "Adaugă la contactele tale", "addon.messages.blocknoncontacts": "Blochează toate mesajele noi de la persoane care nu se află în lista mea de prieteni", + "addon.messages.contactblocked": "Contactul a fost blocat", "addon.messages.contactlistempty": "Lista de contacte este goală", "addon.messages.contactname": "Nume contact", "addon.messages.contacts": "Prieteni", @@ -83,15 +108,17 @@ "addon.messages.messagenotsent": "Mesajul nu a fost expediat, vă rugăm să încercați mai târziu.", "addon.messages.messages": "Mesaje", "addon.messages.newmessage": "Mesaj nou", - "addon.messages.nomessages": "Nu există mesaje în aşteptare", + "addon.messages.nomessagesfound": "Nu s-a găsit niciun mesaj", "addon.messages.nousersfound": "Nu au fost găsiți utilizatori", "addon.messages.removecontact": "Şterge prieten din listă", + "addon.messages.requests": "Solicitări", + "addon.messages.searchcombined": "Caută persoane și mesaje", "addon.messages.type_blocked": "Blocat", "addon.messages.type_offline": "Deconectat", "addon.messages.type_online": "Conectat", "addon.messages.type_search": "Caută rezultatele", "addon.messages.type_strangers": "Alții", - "addon.messages.unblockcontact": "Deblochează utilizator", + "addon.messages.you": "Dumneavoastră:", "addon.mod_assign.addattempt": "Permite altă încercare", "addon.mod_assign.addnewattempt": "Adaugă o nouă încercare", "addon.mod_assign.addsubmission": "Adaugă lucrare", @@ -117,6 +144,7 @@ "addon.mod_assign.graded": "Notat", "addon.mod_assign.gradedby": "Notat de", "addon.mod_assign.gradedon": "Notat în data de", + "addon.mod_assign.gradelocked": "Această notă este blocată sau suprascrisă în catalog.", "addon.mod_assign.gradeoutof": "O notă din {{$a}}", "addon.mod_assign.hiddenuser": "Participant", "addon.mod_assign.latesubmissionsaccepted": "Permis până în {{$a}}", @@ -126,6 +154,7 @@ "addon.mod_assign.markingworkflowstatereadyforrelease": "Gata de lansare", "addon.mod_assign.markingworkflowstatereadyforreview": "Notare completă", "addon.mod_assign.markingworkflowstatereleased": "Lansat", + "addon.mod_assign.modulenameplural": "Teme", "addon.mod_assign.multipleteams": "Membru al mai mult de un grup", "addon.mod_assign.noattempt": "Fără încercare", "addon.mod_assign.nosubmission": "Nu s-a primit răspuns la această sarcină de lucru", @@ -162,6 +191,7 @@ "addon.mod_assign_submission_comments.pluginname": "Comentarii la lucrare", "addon.mod_assign_submission_file.pluginname": "Fișier aplicare", "addon.mod_assign_submission_onlinetext.pluginname": "Trimiteri online ale textului", + "addon.mod_book.modulenameplural": "Cărți", "addon.mod_chat.beep": "beep", "addon.mod_chat.currentusers": "Utilizatori logaţi", "addon.mod_chat.enterchat": "Clic pentru a intra în conversaţie", @@ -174,6 +204,7 @@ "addon.mod_chat.messagebeepsyou": "{{$a}} v-a trimis o atenţionare!", "addon.mod_chat.messageenter": "{{$a}} a intrat în această cameră de discuţii", "addon.mod_chat.messageexit": "{{$a}} a ieşit din această cameră de discuţii", + "addon.mod_chat.modulenameplural": "Chats", "addon.mod_chat.mustbeonlinetosendmessages": "Trebuie să fiți conectat pentru a trimite mesaje", "addon.mod_chat.nomessages": "Nu a fost trimis încă niciun mesaj", "addon.mod_chat.send": "Trimis", @@ -181,6 +212,7 @@ "addon.mod_choice.errorgetchoice": "Eroare în obținerea de date pentru alegere", "addon.mod_choice.expired": "Ne pare rău, această activitate s-a închis la {{$a}} şi nu mai este disponibilă", "addon.mod_choice.full": "(plin)", + "addon.mod_choice.modulenameplural": "Alegeri", "addon.mod_choice.noresultsviewable": "În acest moment rezultatele nu pot fi vizualizate.", "addon.mod_choice.notopenyet": "Ne pare rău, această activitate nu este disponibilă mai devreme de {{$a}}", "addon.mod_choice.numberofuser": "Numărul de răspunsuri", @@ -209,8 +241,10 @@ "addon.mod_data.errormustsupplyvalue": "Trebuie să inserați o valoare aici.", "addon.mod_data.expired": "Ne pare rău, activitatea s-a închis în {{$a}} și nu mai este disponibilă", "addon.mod_data.fields": "Câmpuri", + "addon.mod_data.foundrecords": "Au fost găsite următoarele înregistrări: {{$a.num}}/{{$a.max}} (Reset filters)", "addon.mod_data.latlongboth": "Sunt necesare atât latitudinea, cât și longitudinea.", "addon.mod_data.menuchoose": "Alegeţi...", + "addon.mod_data.modulenameplural": "Baze de date", "addon.mod_data.more": "Detalii suplimentare", "addon.mod_data.nomatch": "Nu s-au găsit articole care să corespundă criteriilor selectate!", "addon.mod_data.norecords": "Nu s-au găsit articole în baza de date", @@ -237,6 +271,7 @@ "addon.mod_feedback.feedback_is_not_open": "Feedback-ul nu este disponibil", "addon.mod_feedback.feedbackopen": "Permite răspunsuri de la", "addon.mod_feedback.mode": "Mod", + "addon.mod_feedback.modulenameplural": "Feedback", "addon.mod_feedback.next_page": "Următoarea pagină", "addon.mod_feedback.not_selected": "Nu este selectat", "addon.mod_feedback.not_started": "neînceput", @@ -253,6 +288,7 @@ "addon.mod_feedback.started": "început", "addon.mod_feedback.this_feedback_is_already_submitted": "Ați completat deja această activitate.", "addon.mod_folder.emptyfilelist": "Nu sunt fișiere disponibile pentru vizualizare.", + "addon.mod_folder.modulenameplural": "Dosare", "addon.mod_forum.addanewdiscussion": "Adaugă o nouă intervenţie", "addon.mod_forum.addanewquestion": "Adaugă o întrebare", "addon.mod_forum.addanewtopic": "Adaugă temă", @@ -272,6 +308,7 @@ "addon.mod_forum.modeflatnewestfirst": "Arată replicile liniar, mai întâi cele recente", "addon.mod_forum.modeflatoldestfirst": "Arată replicile liniar, mai întâi cele vechi", "addon.mod_forum.modenested": "Arată replicile într-o formă de cuib", + "addon.mod_forum.modulenameplural": "Forumuri", "addon.mod_forum.numdiscussions": "{{numdiscussions}} discuții", "addon.mod_forum.numreplies": "{{numreplies}} răspunsuri", "addon.mod_forum.posttoforum": "Trimite intervenţie pe forum", @@ -302,8 +339,10 @@ "addon.mod_glossary.fillfields": "Câmpurile \"concept\" şi \"definiţie\" sunt obligatorii", "addon.mod_glossary.fullmatch": "Doar cuvinte întregi", "addon.mod_glossary.linking": "Auto-linking", + "addon.mod_glossary.modulenameplural": "Glosare", "addon.mod_glossary.noentriesfound": "Nu au fost găsite intrări.", "addon.mod_glossary.searchquery": "Căutare", + "addon.mod_imscp.modulenameplural": "Conținutul pachetelor IMS", "addon.mod_imscp.showmoduledescription": "Arată descrierea", "addon.mod_lesson.answer": "Răspuns", "addon.mod_lesson.attempt": "Încercarea cu numărul: {{$a}}", @@ -338,6 +377,7 @@ "addon.mod_lesson.loginfail": "Operaţiunea de logare nu a reuşit, vă rugăm mai încercaţi...", "addon.mod_lesson.lowscore": "Punctaj minim", "addon.mod_lesson.maximumnumberofattemptsreached": "Numărul maxim de încercări a fost epuizat - se trece la pagina următoare", + "addon.mod_lesson.modulenameplural": "Lecţii", "addon.mod_lesson.noanswer": "Nu aţi dat niciun răspuns. Vă rugăm mergeţi la pagina precedentă şi daţi un răspuns.", "addon.mod_lesson.nolessonattempts": "Nu s-a înregistrat nicio încercare de parcurgere a acestei lecţii", "addon.mod_lesson.notcompleted": "Nu a fost completat.", @@ -369,6 +409,7 @@ "addon.mod_lti.errorinvalidlaunchurl": "URLul lansat nu este valid.", "addon.mod_lti.launchactivity": "Lansați activitatea", "addon.mod_page.errorwhileloadingthepage": "A apărut o eroare la încărcarea paginii.", + "addon.mod_page.modulenameplural": "Pagini", "addon.mod_quiz.attemptfirst": "Prima încercare", "addon.mod_quiz.attemptlast": "Ultima încercare", "addon.mod_quiz.attemptnumber": "Încercare", @@ -386,6 +427,7 @@ "addon.mod_quiz.grademethod": "Metoda de notare", "addon.mod_quiz.gradesofar": "{{$a.method}}: {{$a.mygrade}} / {{$a.quizgrade}}.", "addon.mod_quiz.marks": "Puncte", + "addon.mod_quiz.modulenameplural": "Teste", "addon.mod_quiz.noquestions": "Nici o întrebare nu a fost adăugată încă", "addon.mod_quiz.notyetgraded": "Nu este notat încă", "addon.mod_quiz.outof": "{{$a.grade}} din maxim {{$a.maxgrade}} posibil", @@ -410,6 +452,7 @@ "addon.mod_quiz.yourfinalgradeis": "Nota ta finală la acest test este {{$a}}", "addon.mod_resource.errorwhileloadingthecontent": "A apărut o eroare la încărcarea conținutului.", "addon.mod_resource.modifieddate": "Modificat {{$a}}", + "addon.mod_resource.modulenameplural": "Fișiere", "addon.mod_resource.openthefile": "Deschideți fisierul", "addon.mod_resource.uploadeddate": "Încărcat {{$a}}", "addon.mod_scorm.asset": "Fişier curs", @@ -444,6 +487,7 @@ "addon.mod_scorm.incomplete": "Incomplet", "addon.mod_scorm.lastattempt": "Ultima încercare de parcurgere", "addon.mod_scorm.mode": "Mod", + "addon.mod_scorm.modulenameplural": "SCORMs/AICCs", "addon.mod_scorm.newattempt": "Începe o nouă încercare de parcurgere", "addon.mod_scorm.noattemptsallowed": "Numărul de încercări permise", "addon.mod_scorm.noattemptsmade": "Numărul de încercări pe care le-ați făcut", @@ -463,14 +507,17 @@ "addon.mod_survey.errorgetsurvey": "A apărut o eroare la primirea datelor sondajului.", "addon.mod_survey.ifoundthat": "Am descoperit că", "addon.mod_survey.ipreferthat": "Prefer ca", + "addon.mod_survey.modulenameplural": "Sondaje", "addon.mod_survey.responses": "Răspunsuri", "addon.mod_survey.results": "Rezultate", "addon.mod_survey.surveycompletednograph": "Ați completat acest sondaj.", "addon.mod_url.accessurl": "Accesați adresa URL", + "addon.mod_url.modulenameplural": "URL-uri", "addon.mod_url.pointingtourl": "Adresa URL a acestei resurse duce la", "addon.mod_wiki.cannoteditpage": "Nu puteți edita această pagină.", "addon.mod_wiki.createpage": "Creează pagină", "addon.mod_wiki.editingpage": "Se editează această pagină '{{$a}}'", + "addon.mod_wiki.modulenameplural": "Wikis", "addon.mod_wiki.newpagehdr": "Pagină nouă", "addon.mod_wiki.newpagetitle": "Un nou titlu de pagină", "addon.mod_wiki.nocontent": "Nu există conținut pentru această pagină", @@ -482,6 +529,7 @@ "addon.mod_workshop.conclusion": "Concluzii", "addon.mod_workshop.editsubmission": "Modifică propunere", "addon.mod_workshop.gradinggrade": "Grading Grade", + "addon.mod_workshop.modulenameplural": "Seminarii", "addon.mod_workshop.notoverridden": "Nu este suprascris", "addon.mod_workshop.reassess": "Re-evaluare", "addon.mod_workshop.receivedgrades": "Note primite", @@ -798,6 +846,8 @@ "core.accounts": "Conturi", "core.add": "Adaugă", "core.agelocationverification": "Verificarea vârstei și a locației", + "core.ago": "în urmă cu {{$a}}", + "core.all": "Toate", "core.allparticipants": "Toţi participanţii", "core.android": "Adroid", "core.answer": "Răspunde", @@ -826,6 +876,7 @@ "core.completion-alt-manual-y": "Completat: {{$a}}. Selectați pentru a-l seta ca fiind necompletat.", "core.confirmdeletefile": "Sunteți sigur că doriți să ștergeți acest fișier?", "core.confirmopeninbrowser": "Doriți să deschideți într-un browser?", + "core.considereddigitalminor": "Nu aveți vârsta necesară pentru a putea crea un cont pe acest site.", "core.content": "Conţinut", "core.contentlinks.chooseaccount": "Alegeți contul", "core.contentlinks.chooseaccounttoopenlink": "Alegeți contul pentru a putea deschide linkul.", @@ -853,7 +904,6 @@ "core.courses.availablecourses": "Cursuri disponibile", "core.courses.categories": "Categorii de cursuri", "core.courses.confirmselfenrol": "Sunteți sigur/ă că doriți să vă înregistrați la acest curs?", - "core.courses.courseoverview": "Situaţie curs", "core.courses.courses": "Cursuri", "core.courses.enrolme": "Înregistrează-mă", "core.courses.errorloadcourses": "A apărut o eroare la încărcarea cursurilor.", @@ -861,21 +911,14 @@ "core.courses.errorselfenrol": "A apărut o eroare în procesul de auto-înregistrare.", "core.courses.filtermycourses": "Filtrează cursurile", "core.courses.frontpage": "Pagina principală", - "core.courses.future": "Viitoare", - "core.courses.inprogress": "În desfășurare", - "core.courses.morecourses": "Mai multe cursuri", "core.courses.mycourses": "Cursurile mele", + "core.courses.mymoodle": "My Moodle", "core.courses.nocourses": "Nicio informație legată de cursuri este disponibilă", - "core.courses.nocoursesfuture": "Nu există cursuri viitoare", - "core.courses.nocoursesinprogress": "Nu există cursuri în desfășurare", - "core.courses.nocoursesoverview": "Niciun curs", - "core.courses.nocoursespast": "Nu există cursuri anterioare", "core.courses.nocoursesyet": "Nu s-au găsit cursuri în această categorie", "core.courses.nosearchresults": "Nu există rezultate", "core.courses.notenroled": "Nu sunteți înregistrat în acest curs", "core.courses.notenrollable": "Nu vă puteți auto-înregistra la acest curs.", "core.courses.password": "Parola de înregistrare", - "core.courses.past": "În trecut", "core.courses.paymentrequired": "Acest curs necesită plata unei taxe pentru acces.", "core.courses.paypalaccepted": "Se acceptă plăţi PayPal", "core.courses.search": "Caută", @@ -883,7 +926,7 @@ "core.courses.searchcoursesadvice": "Puteți folosi butonul pentru căutarea cursurilor pentru a cauta și accesa ca vizitator sau pentru a vă auto-înregistra la acele cursuri care permit aceste opțiuni.", "core.courses.selfenrolment": "Auto înscriere", "core.courses.sendpaymentbutton": "Trimite plată prin PayPal", - "core.courses.timeline": "Timeline", + "core.courses.show": "Afișați acest curs", "core.courses.totalcoursesearchresults": "Totalul cursurilor: {{$a}}", "core.date": "Dată", "core.day": "zi", @@ -978,6 +1021,8 @@ "core.login.createaccount": "Creează noul meu cont", "core.login.createuserandpass": "Alege un nume de utilizator şi o parolă", "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!", @@ -989,6 +1034,7 @@ "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", "core.login.invalidaccount": "Vă rugă să verificați datele de conectare sau rugați administratorul siteului sa verifice configurația siteului.", + "core.login.invaliddate": "Dată nevalidă", "core.login.invalidemail": "Adresă de email incorectă", "core.login.invalidmoodleversion": "Versiunea Moodle este invalidă. Versiunea minimă este 2.4", "core.login.invalidsite": "Adresa URL este invalidă.", @@ -1002,6 +1048,7 @@ "core.login.missingfirstname": "Lipseşte prenumele", "core.login.missinglastname": "Lipseşte numele de familie", "core.login.mobileservicesnotenabled": "Serviciile mobile nu sunt activate pe siteul dumneavoastră. Vă rugăm să contactați administratorul siteului dacă considerați necesară activarea accesului mobil.", + "core.login.mustconfirm": "Trebuie să confirmaţi autentificarea", "core.login.newaccount": "Cont nou", "core.login.newsitedescription": "Introduceți adresa URL a siteului dumneavoastră, dar există posibilitatea ca acesta să nu fie configurat pentru a funcționa cu această aplicație.", "core.login.notloggedin": "Trebuie să fiți logat.", @@ -1018,6 +1065,7 @@ "core.login.reconnect": "Reconectare", "core.login.reconnectdescription": "Tokenul dumneavoastră de autentificare este invalid sau a expirat, trebuie să vă reconectați pe site.", "core.login.reconnectssodescription": "Tokenul dumneavoastră de autentificare este invalid sau a expirat, trebuie să vă reconectați pe site. Logați-vă folosind un browser.", + "core.login.resendemail": "Retrimite email", "core.login.security_question": "Întrebare de securitate", "core.login.selectacountry": "Selectează o ţară", "core.login.siteaddress": "Adresa siteului", @@ -1036,8 +1084,6 @@ "core.mainmenu.changesite": "Schimbați siteul", "core.mainmenu.help": "Ajutor", "core.mainmenu.logout": "Ieşire", - "core.mainmenu.mycourses": "Cursurile mele", - "core.mainmenu.togglemenu": "Meniul pentru comutare", "core.mainmenu.website": "Website", "core.maxsizeandattachments": "Dimensiunea maximă pentru fișierele noi: {{$a.size}}, atașamente maxime: {{$a.attachments}}", "core.min": "min", @@ -1113,8 +1159,10 @@ "core.question.requiresgrading": "Trebuie să fie notată", "core.quotausage": "Ați folosit {{$a.used}} din limita de {{$a.total}}.", "core.refresh": "Reîncarcă", + "core.remove": "Şterge", "core.required": "Obligatoriu", "core.requireduserdatamissing": "Acest utilizator are unele date de profil obligatorii necompletate. Completați aceste date în contul din Moodle și încercați din nou.
{{$a}}", + "core.resources": "Resurse", "core.restore": "Restaurează", "core.save": "Salvează", "core.search": "Caută", @@ -1183,6 +1231,19 @@ "core.sizemb": "MB", "core.sizetb": "TB", "core.sortby": "Sortează după", + "core.strftimedate": "%d %B %Y", + "core.strftimedatefullshort": "%d/%m/%y", + "core.strftimedateshort": "%d %B", + "core.strftimedatetime": "%d %B %Y, %H:%M", + "core.strftimedatetimeshort": "%d/%m/%Y %H:%M", + "core.strftimedaydate": "%A, %d %B %Y", + "core.strftimedaydatetime": "%A, %d %B %Y, %H:%M", + "core.strftimedayshort": "%A, %d %B", + "core.strftimedaytime": "%a, %H:%M", + "core.strftimemonthyear": "%B %Y", + "core.strftimerecent": "%d %b, %H:%M", + "core.strftimerecentfull": "%a, %d %b %Y, %H:%M", + "core.strftimetime": "%H:%M", "core.submit": "Trimite", "core.success": "Succes", "core.tablet": "Tabletă", @@ -1210,21 +1271,22 @@ "core.user.firstname": "Prenume", "core.user.interests": "Interese", "core.user.lastname": "Nume", - "core.user.manager": "Manager", + "core.user.manager": "Administrator", "core.user.newpicture": "Imagine nouă", "core.user.noparticipants": "Nu există participanți la acest curs", "core.user.participants": "Participanţi", "core.user.phone1": "Telefon", "core.user.phone2": "Mobil", "core.user.roles": "Roluri", - "core.user.student": "Student", - "core.user.teacher": "Profesor asistent", + "core.user.student": "Cursant", + "core.user.teacher": "Profesor fără drepturi de editare", "core.user.webpage": "Pagină Web", "core.userdeleted": "Acest cont a fost şters", "core.userdetails": "Detalii utilizator", "core.users": "Utilizatori", "core.view": "Vizualizare", "core.viewprofile": "Vezi profilul", + "core.whatisyourage": "Ce vârstă aveți?", "core.wheredoyoulive": "În ce țară locuiți?", "core.whyisthisrequired": "De ce este solicitat acest lucru?", "core.windowsphone": "Telefon cu sistem de operare Windows", diff --git a/src/assets/lang/ru.json b/src/assets/lang/ru.json index 3dace7486..2ed05bf61 100644 --- a/src/assets/lang/ru.json +++ b/src/assets/lang/ru.json @@ -7,9 +7,33 @@ "addon.badges.expirydate": "Дата окончания срока действия", "addon.badges.issuancedetails": "Срок действия значка", "addon.badges.issuerdetails": "Сведения об эмитенте", + "addon.badges.issueremail": "Адрес эл. почты", "addon.badges.issuername": "Наименование эмитента", + "addon.badges.issuerurl": "URL-адрес эмитента", + "addon.badges.language": "Язык", "addon.badges.nobadges": "Нет доступных значков.", "addon.badges.recipientdetails": "Сведения о получателе", + "addon.badges.relatedbages": "Связанные значки", + "addon.badges.version": "Версия", + "addon.badges.warnexpired": "(Срок действия этого значка истек!)", + "addon.block_activitymodules.pluginname": "Элементы курса", + "addon.block_myoverview.all": "Все", + "addon.block_myoverview.favourites": "Помеченные", + "addon.block_myoverview.future": "Предстоящие", + "addon.block_myoverview.hiddencourses": "Скрытые", + "addon.block_myoverview.inprogress": "Текущие", + "addon.block_myoverview.lastaccessed": "Последнее посещение", + "addon.block_myoverview.morecourses": "Больше курсов", + "addon.block_myoverview.nocourses": "Нет курсов", + "addon.block_myoverview.past": "Прошедшие", + "addon.block_myoverview.pluginname": "Сводка по курсам", + "addon.block_myoverview.title": "Название курса", + "addon.block_sitemainmenu.pluginname": "Основное меню", + "addon.block_timeline.next30days": "Следующие 30 дней", + "addon.block_timeline.next3months": "Следующие 3 месяца", + "addon.block_timeline.next6months": "Следующие 6 месяцев", + "addon.block_timeline.next7days": "Следующие 7 дней", + "addon.block_timeline.pluginname": "Шкала времени", "addon.calendar.calendar": "Календарь", "addon.calendar.calendarevents": "События календаря", "addon.calendar.defaultnotificationtime": "Время уведомлений по умолчанию", @@ -76,24 +100,24 @@ "addon.competency.xcompetenciesproficientoutofyincourse": "Вы освоили {{$a.x}} из {{$a.y}} компетенций в этом курсе.", "addon.coursecompletion.complete": "Завершить", "addon.coursecompletion.completecourse": "Завершить курс", - "addon.coursecompletion.completed": "Завершен", + "addon.coursecompletion.completed": "Выполнено", "addon.coursecompletion.completiondate": "Дата завершения", "addon.coursecompletion.completionmenuitem": "Отслеживание выполнения", "addon.coursecompletion.couldnotloadreport": "Невозможно загрузить отчёт о завершении курса. Пожалуйста, попробуйте ещё раз позже.", "addon.coursecompletion.coursecompletion": "Завершение курса", "addon.coursecompletion.criteria": "Критерии", - "addon.coursecompletion.criteriagroup": "Критерии группы", - "addon.coursecompletion.criteriarequiredall": "Все приведенные ниже критерии являются обязательными.", - "addon.coursecompletion.criteriarequiredany": "Некоторые из нижеуказанных критериев обязательны.", - "addon.coursecompletion.inprogress": "В прогрессе", - "addon.coursecompletion.manualselfcompletion": "Ручное самостоятельное завершение", - "addon.coursecompletion.notyetstarted": "Еще не начался", - "addon.coursecompletion.pending": "На рассмотрении", - "addon.coursecompletion.required": "Необходимый", + "addon.coursecompletion.criteriagroup": "Группа критериев", + "addon.coursecompletion.criteriarequiredall": "Требуются соответствие всем указанным ниже критериям", + "addon.coursecompletion.criteriarequiredany": "Требуется соответствие любому из указанных ниже критериев", + "addon.coursecompletion.inprogress": "В процессе", + "addon.coursecompletion.manualselfcompletion": "Пользователь может сам поставить отметку о выполнении", + "addon.coursecompletion.notyetstarted": "Еще не началось", + "addon.coursecompletion.pending": "Ожидается", + "addon.coursecompletion.required": "Необходимо заполнить", "addon.coursecompletion.requiredcriteria": "Необходимые критерии", - "addon.coursecompletion.requirement": "Требование", + "addon.coursecompletion.requirement": "Необходимое условие", "addon.coursecompletion.status": "Статус", - "addon.coursecompletion.viewcoursereport": "Посмотреть отчет курса", + "addon.coursecompletion.viewcoursereport": "Просмотреть отчет по курсу", "addon.files.couldnotloadfiles": "Файлы из списка не могут быть загружены", "addon.files.emptyfilelist": "Нет файлов для отображения", "addon.files.erroruploadnotworking": "К сожалению, в данный момент невозможно загрузить файлы на ваш сайт.", @@ -101,34 +125,75 @@ "addon.files.privatefiles": "Личные файлы", "addon.files.sitefiles": "Файлы сайта", "addon.messageoutput_airnotifier.processorsettingsdesc": "Настроить устройства", + "addon.messages.acceptandaddcontact": "Принять и добавить в контакты", "addon.messages.addcontact": "Добавить собеседника", - "addon.messages.blockcontact": "Блокировать сообщения от этого человека", - "addon.messages.blockcontactconfirm": "Вы больше не будете получать сообщения от этого контакта.", + "addon.messages.addcontactconfirm": "Вы уверены, что хотите добавить {{$a}} к своим контактам?", + "addon.messages.addtofavourites": "Отметить", + "addon.messages.addtoyourcontacts": "Добавить в список контактов", "addon.messages.blocknoncontacts": "Не принимать сообщения от людей, которых нет в списке моих собеседников", + "addon.messages.blockuser": "Блокировать пользователя", + "addon.messages.blockuserconfirm": "Вы уверены, что хотите заблокировать {{$a}}?", + "addon.messages.contactableprivacy": "Принимать сообщения от:", + "addon.messages.contactableprivacy_coursemember": "Мои контакты и любой в моих курсах", + "addon.messages.contactableprivacy_onlycontacts": "Только мои контакты", + "addon.messages.contactableprivacy_site": "Любой на сайте", + "addon.messages.contactblocked": "Контакт заблокирован", "addon.messages.contactlistempty": "Список контактов пуст", "addon.messages.contactname": "Имя контакта", + "addon.messages.contactrequestsent": "Запрос на добавление в контакты отправлен", "addon.messages.contacts": "Собеседники", + "addon.messages.decline": "Отказаться", + "addon.messages.deleteallconfirm": "Вы уверены, что хотите удалить всю эту беседу ? Это не удалит её для других участников разговора.", + "addon.messages.deleteconversation": "Удалить беседу", "addon.messages.errordeletemessage": "Ошибка при удалении сообщения.", "addon.messages.errorwhileretrievingcontacts": "Ошибка при извлечении контактов с сервера.", "addon.messages.errorwhileretrievingdiscussions": "Ошибка при получении обсуждений с сервера.", "addon.messages.errorwhileretrievingmessages": "Ошибка при получении сообщений с сервера.", + "addon.messages.groupconversations": "Группа", + "addon.messages.groupinfo": "Информация о группе", + "addon.messages.individualconversations": "Личное", + "addon.messages.info": "Информация", + "addon.messages.isnotinyourcontacts": "{{$a}} нет в ваших контактах", "addon.messages.message": "Сообщение", "addon.messages.messagenotsent": "Сообщение не было отправлено. Пожалуйста, повторите попытку позже.", "addon.messages.messagepreferences": "Настройки сообщений", "addon.messages.messages": "Сообщения", "addon.messages.newmessage": "Новое сообщение", "addon.messages.newmessages": "Новые сообщения", - "addon.messages.nomessages": "Нет сообщений", + "addon.messages.nocontactrequests": "Нет запросов на добавление в контакты", + "addon.messages.nocontactsgetstarted": "Нет контактов", + "addon.messages.nofavourites": "Нет помеченных разговоров", + "addon.messages.nogroupconversations": "Нет групповых разговоров", + "addon.messages.noindividualconversations": "Нет личных разговоров", + "addon.messages.nomessagesfound": "Сообщений не найдено", + "addon.messages.noncontacts": "Собеседники отсутствуют", "addon.messages.nousersfound": "Пользователи не найдены", + "addon.messages.numparticipants": "Участники – {{$a}}", "addon.messages.removecontact": "Удалить собеседника из моего списка", - "addon.messages.removecontactconfirm": "Контакт будет удалён из вашего списка контактов.", + "addon.messages.removecontactconfirm": "Вы уверены, что хотите удалить {{$a}} из своих контактов?", + "addon.messages.removefromfavourites": "Снять отметку", + "addon.messages.removefromyourcontacts": "Удалить из контактов", + "addon.messages.requests": "Запросы", + "addon.messages.requirecontacttomessage": "Вам нужно попросить {{$a}} добавить вас в контакты, чтобы иметь возможность отправлять сообщения этому пользователю.", + "addon.messages.searchcombined": "Поиск пользователей и сообщений", + "addon.messages.searchnocontactsfound": "Контакты не найдены", + "addon.messages.searchnomessagesfound": "Сообщений не найдено", + "addon.messages.searchnononcontactsfound": "Контакты не найдены", + "addon.messages.sendcontactrequest": "Отправить запрос на добавление в контакты", "addon.messages.type_blocked": "Заблокировано", "addon.messages.type_offline": "Вне сайта", "addon.messages.type_online": "На сайте", "addon.messages.type_search": "Результаты поиска", "addon.messages.type_strangers": "Другие", - "addon.messages.unblockcontact": "Разблокировать сообщения от этого собеседника", + "addon.messages.unabletomessage": "Вы не можете отправить сообщение этому пользователю", + "addon.messages.unblockuser": "Разблокировать пользователя", + "addon.messages.unblockuserconfirm": "Вы уверены, что хотите разблокировать {{$a}}?", + "addon.messages.userwouldliketocontactyou": "{{$a}} хотел(а) бы с вами связаться", "addon.messages.warningmessagenotsent": "Не получилось отправить сообщение(я) пользователю {{user}}. {{error}}", + "addon.messages.wouldliketocontactyou": "Хотят связаться с вами", + "addon.messages.you": "Вы:", + "addon.messages.youhaveblockeduser": "Вы ранее заблокировали этого пользователя", + "addon.messages.yourcontactrequestpending": "Ваш запрос на добавление в контакты находится на рассмотрении с {{$a}}", "addon.mod_assign.acceptsubmissionstatement": "Пожалуйста, примите заявление к предоставляемому ответу.", "addon.mod_assign.addattempt": "Разрешить еще одну попытку", "addon.mod_assign.addnewattempt": "Добавить новую попытку", @@ -166,6 +231,7 @@ "addon.mod_assign.graded": "Оценено", "addon.mod_assign.gradedby": "Оценено", "addon.mod_assign.gradedon": "Оценено в", + "addon.mod_assign.gradelocked": "Эта оценка заблокирована или изменена в журнале оценок.", "addon.mod_assign.gradenotsynced": "Оценка не синхронизирована", "addon.mod_assign.gradeoutof": "Оценка из {{$a}}", "addon.mod_assign.gradingstatus": "Состояние оценивания", @@ -180,6 +246,7 @@ "addon.mod_assign.markingworkflowstatereadyforrelease": "Готово к публикации", "addon.mod_assign.markingworkflowstatereadyforreview": "Оценивание завершено", "addon.mod_assign.markingworkflowstatereleased": "Опубликовано", + "addon.mod_assign.modulenameplural": "Задания", "addon.mod_assign.multipleteams": "Член нескольких групп", "addon.mod_assign.multipleteams_desc": "Это задание требует представления в группах. Вы являетесь членом более чем одной группы. Чтобы представить ответ, вы должны быть участником только одной группы. Пожалуйста, свяжитесь со своим учителем, чтобы изменить ваше членство в группе.", "addon.mod_assign.noattempt": "Ни одной попытки", @@ -234,6 +301,7 @@ "addon.mod_assign_submission_file.pluginname": "Ответ в виде файла", "addon.mod_assign_submission_onlinetext.pluginname": "Ответ в виде текста", "addon.mod_book.errorchapter": "Ошибка чтения главы книги.", + "addon.mod_book.modulenameplural": "Книги", "addon.mod_chat.beep": "Сигнал", "addon.mod_chat.currentusers": "Текущие пользователи", "addon.mod_chat.enterchat": "Войти в чат", @@ -246,6 +314,7 @@ "addon.mod_chat.messagebeepsyou": "{{$a}} отправил Вам сигнал!", "addon.mod_chat.messageenter": "{{$a}} появился в чате", "addon.mod_chat.messageexit": "{{$a}} ушел из чата", + "addon.mod_chat.modulenameplural": "Чаты", "addon.mod_chat.mustbeonlinetosendmessages": "Вы должны быть в сети, чтобы отправлять сообщения.", "addon.mod_chat.nomessages": "Нет ни одного сообщения", "addon.mod_chat.send": "Отправить", @@ -256,6 +325,7 @@ "addon.mod_choice.errorgetchoice": "Ошибка при получении данных выбора", "addon.mod_choice.expired": "Извините, этот элемент курса закрыт {{$a}} и более недоступен", "addon.mod_choice.full": "(Заполнено)", + "addon.mod_choice.modulenameplural": "Опросы", "addon.mod_choice.noresultsviewable": "Вы не можете в данный момент просматривать результаты опроса.", "addon.mod_choice.notopenyet": "Извините, данное действие недоступно до {{$a}}", "addon.mod_choice.numberofuser": "Количество ответов на опрос", @@ -288,8 +358,10 @@ "addon.mod_data.errormustsupplyvalue": "Вы должны здесь указать значение.", "addon.mod_data.expired": "К сожалению, эта база данных закрыта {{$a}} и больше не доступна", "addon.mod_data.fields": "Поля", + "addon.mod_data.foundrecords": "Найдено записей: {{$a.num}}/{{$a.max}}. (Сбросить фильтры)", "addon.mod_data.latlongboth": "Необходимо задать и широту, и долготу.", "addon.mod_data.menuchoose": "Выбрать...", + "addon.mod_data.modulenameplural": "Базы данных", "addon.mod_data.more": "Просмотр записи", "addon.mod_data.nomatch": "Соответствующих записей не найдено!", "addon.mod_data.norecords": "Нет записей в базе данных", @@ -321,6 +393,7 @@ "addon.mod_feedback.feedbackopen": "Разрешить отвечать с", "addon.mod_feedback.mapcourses": "Сопоставление Обратной связи с курсами", "addon.mod_feedback.mode": "Режим", + "addon.mod_feedback.modulenameplural": "Обратная связь", "addon.mod_feedback.next_page": "Следующая страница", "addon.mod_feedback.non_anonymous": "Имя пользователя будет записано и показано с его ответами", "addon.mod_feedback.non_anonymous_entries": "Не анонимные записи ({{$a}})", @@ -341,6 +414,7 @@ "addon.mod_feedback.started": "Начало", "addon.mod_feedback.this_feedback_is_already_submitted": "Вы уже завершили этот учебный элемент", "addon.mod_folder.emptyfilelist": "Нет файлов для отображения", + "addon.mod_folder.modulenameplural": "Папки", "addon.mod_forum.addanewdiscussion": "Добавить тему для обсуждения", "addon.mod_forum.addanewquestion": "Добавить новый вопрос", "addon.mod_forum.addanewtopic": "Добавить новую тему", @@ -363,6 +437,7 @@ "addon.mod_forum.modeflatnewestfirst": "Плоско, впереди новые", "addon.mod_forum.modeflatoldestfirst": "Плоско, впереди старые", "addon.mod_forum.modenested": "Древовидно", + "addon.mod_forum.modulenameplural": "Форумы", "addon.mod_forum.numdiscussions": "Обсуждений - {{numdiscussions}}", "addon.mod_forum.numreplies": "Ответов - {{numreplies}}", "addon.mod_forum.posttoforum": "Отправить в форум", @@ -398,9 +473,11 @@ "addon.mod_glossary.fillfields": "Слово и определение - поля обязательные.", "addon.mod_glossary.fullmatch": "Определять соответствие только полным словам", "addon.mod_glossary.linking": "Автосвязывание", + "addon.mod_glossary.modulenameplural": "Глоссарии", "addon.mod_glossary.noentriesfound": "Записей не было найдено.", "addon.mod_glossary.searchquery": "Запрос на поиск", "addon.mod_imscp.deploymenterror": "Пакет содержимого IMS с ошибкой!", + "addon.mod_imscp.modulenameplural": "Пакеты IMS содержимого", "addon.mod_imscp.showmoduledescription": "Показать описание", "addon.mod_lesson.answer": "Ответ", "addon.mod_lesson.attempt": "Попытка: {{$a}}", @@ -444,6 +521,7 @@ "addon.mod_lesson.lowtime": "Наименьшее время", "addon.mod_lesson.maximumnumberofattemptsreached": "Достигнуто максимальное количество попыток - переходим к следующей странице", "addon.mod_lesson.modattemptsnoteacher": "Только студенты могут повторно проходить лекцию.", + "addon.mod_lesson.modulenameplural": "Лекции", "addon.mod_lesson.noanswer": "На один или несколько вопросов не получено ответа. Пожалуйста, вернитесь и представьте ответ.", "addon.mod_lesson.nolessonattempts": "Не было попыток пройти лекцию.", "addon.mod_lesson.nolessonattemptsgroup": "В этой лекции члены группы ({{$a}}) не сделали ни одной попытки.", @@ -488,7 +566,9 @@ "addon.mod_lti.errorgetlti": "Ошибка при получении данных модуля", "addon.mod_lti.errorinvalidlaunchurl": "Запущенный URL не действителен", "addon.mod_lti.launchactivity": "Запустить активность", + "addon.mod_lti.modulenameplural": "Внешние приложения", "addon.mod_page.errorwhileloadingthepage": "Ошибка при загрузке содержимого страницы", + "addon.mod_page.modulenameplural": "Страницы", "addon.mod_quiz.attemptfirst": "Первая попытка", "addon.mod_quiz.attemptlast": "Последняя попытка", "addon.mod_quiz.attemptnumber": "Попытка", @@ -523,6 +603,7 @@ "addon.mod_quiz.grademethod": "Метод оценивания", "addon.mod_quiz.gradesofar": "{{$a.method}}: {{$a.mygrade}} / {{$a.quizgrade}}.", "addon.mod_quiz.marks": "Баллы", + "addon.mod_quiz.modulenameplural": "Тесты", "addon.mod_quiz.mustbesubmittedby": "Эта попытка должна быть отправлена до {{$a}}.", "addon.mod_quiz.noquestions": "Пока не добавлено ни одного вопроса", "addon.mod_quiz.noreviewattempt": "Вам не разрешен просмотр этой попытки.", @@ -567,6 +648,7 @@ "addon.mod_quiz.yourfinalgradeis": "Ваша итоговая оценка за этот тест: {{$a}}", "addon.mod_resource.errorwhileloadingthecontent": "Ошибка при загрузке контента", "addon.mod_resource.modifieddate": "Изменено {{$a}}", + "addon.mod_resource.modulenameplural": "Файлы", "addon.mod_resource.openthefile": "Открыть файл", "addon.mod_resource.uploadeddate": "Загружено {{$a}}", "addon.mod_scorm.asset": "Элемент", @@ -603,6 +685,7 @@ "addon.mod_scorm.incomplete": "Не завершено", "addon.mod_scorm.lastattempt": "Завершена последняя попытка", "addon.mod_scorm.mode": "Режим", + "addon.mod_scorm.modulenameplural": "Пакеты SCORM", "addon.mod_scorm.newattempt": "Начать новую попытку", "addon.mod_scorm.noattemptsallowed": "Количество попыток", "addon.mod_scorm.noattemptsmade": "Выполнено попыток", @@ -622,10 +705,12 @@ "addon.mod_survey.errorgetsurvey": "Ошибка при получении данных опроса", "addon.mod_survey.ifoundthat": "Я узнал, что", "addon.mod_survey.ipreferthat": "Я предпочитаю", + "addon.mod_survey.modulenameplural": "Анкеты", "addon.mod_survey.responses": "Ответы", "addon.mod_survey.results": "Результаты", "addon.mod_survey.surveycompletednograph": "Вы уже ответили на эту анкету.", "addon.mod_url.accessurl": "Открыть URL-адрес", + "addon.mod_url.modulenameplural": "Гиперссылки", "addon.mod_url.pointingtourl": "URL, на который указывает ресурс.", "addon.mod_wiki.cannoteditpage": "Вы не можете редактировать эту страницу", "addon.mod_wiki.createpage": "Создать страницу", @@ -634,6 +719,7 @@ "addon.mod_wiki.errornowikiavailable": "В этом модуле WIKI пока нет контента.", "addon.mod_wiki.gowikihome": "Перейти на первую страницу wiki", "addon.mod_wiki.map": "Карта", + "addon.mod_wiki.modulenameplural": "Вики-страницы", "addon.mod_wiki.newpagehdr": "Новая страница", "addon.mod_wiki.newpagetitle": "Заголовок новой страницы", "addon.mod_wiki.nocontent": "Нет содержимого у этой страницы", @@ -672,6 +758,7 @@ "addon.mod_workshop.gradinggradecalculated": "Вычисленная оценка за оценивание", "addon.mod_workshop.gradinggradeof": "Баллы за оценивание (из {{$a}})", "addon.mod_workshop.gradinggradeover": "Переопределить баллы за оценивание", + "addon.mod_workshop.modulenameplural": "Семинары", "addon.mod_workshop.nogradeyet": "Еще не оценено", "addon.mod_workshop.notassessed": "Еще не оцененные", "addon.mod_workshop.notoverridden": "Не переопределено", @@ -993,11 +1080,11 @@ "assets.mimetypes.application/vnd.oasis.opendocument.text": "Текстовый документ OpenDocument", "assets.mimetypes.application/vnd.oasis.opendocument.text-template": "Шаблон текстового документа OpenDocument", "assets.mimetypes.application/vnd.oasis.opendocument.text-web": "Шаблон веб-страницы OpenDocument", - "assets.mimetypes.application/vnd.openxmlformats-officedocument.presentationml.presentation": "Презентация PowerPoint", - "assets.mimetypes.application/vnd.openxmlformats-officedocument.presentationml.slideshow": "Слайд-шоу PowerPoint", - "assets.mimetypes.application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "Таблица Excel", - "assets.mimetypes.application/vnd.openxmlformats-officedocument.spreadsheetml.template": "Шаблон Excel", - "assets.mimetypes.application/vnd.openxmlformats-officedocument.wordprocessingml.document": "Документ Word", + "assets.mimetypes.application/vnd.openxmlformats-officedocument.presentationml.presentation": "Презентация PowerPoint 2007", + "assets.mimetypes.application/vnd.openxmlformats-officedocument.presentationml.slideshow": "Слайд-шоу PowerPoint 2007", + "assets.mimetypes.application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": "Таблица Excel 2007", + "assets.mimetypes.application/vnd.openxmlformats-officedocument.spreadsheetml.template": "Шаблон Excel 2007", + "assets.mimetypes.application/vnd.openxmlformats-officedocument.wordprocessingml.document": "Документ Word 2007", "assets.mimetypes.application/x-iwork-keynote-sffkey": "Презентация iWork Keynote", "assets.mimetypes.application/x-iwork-numbers-sffnumbers": "Электронная таблица iWork Numbers", "assets.mimetypes.application/x-iwork-pages-sffpages": "Страницы документа iWork", @@ -1036,6 +1123,8 @@ "core.accounts": "Учетные записи", "core.add": "Добавить", "core.agelocationverification": "Проверка возраста и местонахождения", + "core.ago": "{{$a}} назад", + "core.all": "Все", "core.allparticipants": "Все участники", "core.android": "Android", "core.answer": "Ответ", @@ -1073,7 +1162,7 @@ "core.confirmdeletefile": "Вы уверены, что хотите удалить это файл?", "core.confirmloss": "Вы уверены? Все изменения будут удалены.", "core.confirmopeninbrowser": "Вы хотите открыть это в веб-браузере?", - "core.considereddigitalminor": "Вы считаетесь лицом, не достигшим цифрового совершеннолетия.", + "core.considereddigitalminor": "Вы слишком молоды, чтобы создать учетную запись на этом сайте.", "core.content": "Содержимое", "core.contenteditingsynced": "Содержимое, которое вы редактируете, было синхронизировано.", "core.contentlinks.chooseaccount": "Выберите учетную запись", @@ -1106,12 +1195,12 @@ "core.course.sections": "Разделы", "core.course.useactivityonbrowser": "Вы по прежнему можете пользоваться этим, используя веб-браузер своего устройства.", "core.coursedetails": "Информация о курсе", + "core.courses.addtofavourites": "Пометить этот курс", "core.courses.allowguests": "Курс доступен гостю", "core.courses.availablecourses": "Доступные курсы", "core.courses.cannotretrievemorecategories": "Категории глубже уровня {{$a}} не могут быть получены.", "core.courses.categories": "Категории курсов", "core.courses.confirmselfenrol": "Вы уверены, что хотите записать себя на этот курс?", - "core.courses.courseoverview": "Сводка по курсам", "core.courses.courses": "Курсы", "core.courses.enrolme": "Записать меня", "core.courses.errorloadcategories": "Во время загрузки категорий произошла ошибка.", @@ -1120,28 +1209,24 @@ "core.courses.errorselfenrol": "Во время самостоятельной записи произошла ошибка.", "core.courses.filtermycourses": "Поиск моих курсов", "core.courses.frontpage": "Главная страница", - "core.courses.future": "Предстоящие", - "core.courses.inprogress": "Текущие", - "core.courses.morecourses": "Больше курсов", + "core.courses.hidecourse": "Скрыть из вида", "core.courses.mycourses": "Мои курсы", + "core.courses.mymoodle": "Личный кабинет", "core.courses.nocourses": "Нет информации для отображения.", - "core.courses.nocoursesfuture": "Нет предстоящих курсов", - "core.courses.nocoursesinprogress": "Нет текущих курсов", - "core.courses.nocoursesoverview": "Нет курсов", - "core.courses.nocoursespast": "Нет прошедших курсов", "core.courses.nocoursesyet": "В этой категории нет курсов", "core.courses.nosearchresults": "Нет результатов", "core.courses.notenroled": "Вы не записаны на этот курс", "core.courses.notenrollable": "Вы не можете самостоятельно записаться на этот курс.", "core.courses.password": "Кодовое слово", - "core.courses.past": "Прошедшие", "core.courses.paymentrequired": "Для регистрации на этом курсе требуется оплата", "core.courses.paypalaccepted": "Принятые платежи PayPal", + "core.courses.removefromfavourites": "Снять отметку с курса", "core.courses.search": "Найти", "core.courses.searchcourses": "Поиск курса", "core.courses.searchcoursesadvice": "Вы можете использовать кнопку поиска по курсам, чтобы найти курсы для гостевого доступа к ним или записать себя на курсы, которые это разрешают.", "core.courses.selfenrolment": "Самостоятельная запись", "core.courses.sendpaymentbutton": "Оплатить через PayPal", + "core.courses.show": "Показать этот курс", "core.courses.totalcoursesearchresults": "Всего курсов: {{$a}}", "core.currentdevice": "Данное устройство", "core.datastoredoffline": "Данные сохранены на устройстве, потому что не могут быть отправлены. Они будут автоматически отправлены позже.", @@ -1161,7 +1246,7 @@ "core.dfmediumdate": "LLL", "core.dftimedate": "h[:]mm A", "core.digitalminor": "Цифровой возраст совершеннолетия", - "core.digitalminor_desc": "Чтобы создать учетную запись на этом сайте, пожалуйста, попросите родителя/опекуна связаться со следующим лицом.", + "core.digitalminor_desc": "Чтобы создать учетную запись на этом сайте, пожалуйста, попросите родителя/опекуна связаться со следующим лицом:", "core.discard": "Сбросить", "core.dismiss": "Распустить", "core.done": "Завершено", @@ -1185,6 +1270,7 @@ "core.errorsync": "При синхронизации возникла ошибка. Пожалуйста, попробуйте снова.", "core.errorsyncblocked": "В данный момент это {{$a}} не может быть синхронизировано по причине выполняющегося процесса. Пожалуйста, попытайтесь ещё раз позже. Если при последующих попытках ошибка не пропадёт, попробуйте перезапустить приложение.", "core.explanationdigitalminor": "Эта информация необходима для определения того, превышает ли ваш возраст цифровой возраст согласия. Это возраст, когда человек может соглашаться с условиями и его данные будут храниться и обрабатываться на законных основаниях.", + "core.favourites": "Помеченные", "core.filename": "Имя файла", "core.filenameexist": "Имя файла уже существует: {{$a}}", "core.fileuploader.addfiletext": "Добавить файл", @@ -1266,7 +1352,8 @@ "core.login.createaccount": "Сохранить", "core.login.createuserandpass": "Выберите имя пользователя и пароль", "core.login.credentialsdescription": "Пожалуйста, укажите Ваш логин и пароль для входа.", - "core.login.emailconfirmsent": "

Электронное письмо должно было быть отправлено вам на{{$a}}

Оно содержит простые инструкции по завершению вашей регистрации

Если вы продолжаете испытывать трудности , свяжитесь с администратором сайта

", + "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", @@ -1279,7 +1366,7 @@ "core.login.helpmelogin": "

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

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

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

", "core.login.instructions": "Инструкции", "core.login.invalidaccount": "Пожалуйста, проверьте свои регистрационные данные или обратитесь к администратору сайта, чтобы он проверил настройки сайта.", - "core.login.invaliddate": "Некорректная дата", + "core.login.invaliddate": "Неверная дата", "core.login.invalidemail": "Некорректный формат адреса электронной почты", "core.login.invalidmoodleversion": "Неверная версия Moodle. Минимальная требуемая версия - 2.4.", "core.login.invalidsite": "URL-адрес сайта недействителен.", @@ -1297,6 +1384,7 @@ "core.login.missingfirstname": "Заполните поле", "core.login.missinglastname": "Заполните поле", "core.login.mobileservicesnotenabled": "Мобильный доступ отключен на вашем сайте. Пожалуйста, обратитесь к администратору вашего сайта, если вы считаете что мобильный доступ должен быть включен.", + "core.login.mustconfirm": "Необходимо подтвердить учетную запись", "core.login.newaccount": "Новая учетная запись", "core.login.newsitedescription": "Пожалуйста, введите URL-адрес своего сайта Moodle. Учтите, что он может быть не настроен для работы с этим приложением.", "core.login.notloggedin": "Вы должны быть идентифицированы.", @@ -1316,6 +1404,7 @@ "core.login.reconnect": "Переподключение", "core.login.reconnectdescription": "Ваш ключ аутентификации недействителен или срок его действия истек. Вам придется заново подключиться к сайту.", "core.login.reconnectssodescription": "Ваш ключ аутентификации недействителен или срок его действия истек. Вам придется заново зайти на сайт в окне браузера.", + "core.login.resendemail": "Отправить письмо еще раз", "core.login.searchby": "Искать по:", "core.login.security_question": "Секретный вопрос", "core.login.selectacountry": "Выберите страну", @@ -1340,8 +1429,6 @@ "core.mainmenu.changesite": "Сменить сайт", "core.mainmenu.help": "Справка", "core.mainmenu.logout": "Выход", - "core.mainmenu.mycourses": "Мои курсы", - "core.mainmenu.togglemenu": "Переключить меню", "core.mainmenu.website": "Сайт", "core.maxsizeandattachments": "Максимальный размер новых файлов: {{$a.size}}, максимальное количество прикрепленных файлов: {{$a.attachments}}", "core.min": "мин.", @@ -1427,8 +1514,10 @@ "core.quotausage": "В настоящее время вы использовали {{$a.used}} из своего лимита {{$a.total}}.", "core.redirectingtosite": "Вы будете перенаправлены на сайт.", "core.refresh": "Обновить", + "core.remove": "Удалить", "core.required": "Необходимо заполнить", "core.requireduserdatamissing": "У этого пользователя не хватает необходимых данных профиля. Пожалуйста, введите данные на вашем сайте и попытайтесь снова.
{{$a}}", + "core.resources": "Ресурсы", "core.restore": "Восстановить", "core.retry": "Попробовать снова", "core.save": "Сохранить", @@ -1475,7 +1564,7 @@ "core.settings.locationhref": "URL компонента Web view", "core.settings.locked": "Заблокировано", "core.settings.loggedin": "На сайте", - "core.settings.loggedoff": "Вне сайта", + "core.settings.loggedoff": "Не в сети", "core.settings.navigatorlanguage": "Язык навигатора", "core.settings.navigatoruseragent": "userAgent навигатора", "core.settings.networkstatus": "Статус подключения к интернету", @@ -1513,6 +1602,21 @@ "core.sizetb": "Тб", "core.sorry": "Извините...", "core.sortby": "Сортировать по", + "core.strftimedate": "%d %B %Y", + "core.strftimedatefullshort": "%d/%m/%y", + "core.strftimedateshort": "%d %B", + "core.strftimedatetime": "%d %B %Y, %H:%M", + "core.strftimedatetimeshort": "%d/%m/%y, %H:%M", + "core.strftimedaydate": "%A %d %B %Y", + "core.strftimedaydatetime": "%A, %d %B %Y, %H:%M", + "core.strftimedayshort": "%A %d %B", + "core.strftimedaytime": "%a, %H:%M", + "core.strftimemonthyear": "%B %Y", + "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.submit": "Отправить", "core.success": "Успешно", "core.tablet": "Планшет", @@ -1554,7 +1658,7 @@ "core.user.roles": "Роли", "core.user.sendemail": "Электронная почта", "core.user.student": "Студент", - "core.user.teacher": "Учитель без права редактирования (ассистент)", + "core.user.teacher": "Ассистент (без права редактирования)", "core.user.webpage": "Веб-страница", "core.userdeleted": "Учетная запись пользователя была удалена", "core.userdetails": "Подробная информация о пользователе", diff --git a/src/assets/lang/sr-cr.json b/src/assets/lang/sr-cr.json index c00195acd..2d0af2b44 100644 --- a/src/assets/lang/sr-cr.json +++ b/src/assets/lang/sr-cr.json @@ -8,8 +8,18 @@ "addon.badges.issuancedetails": "Беџ истиче", "addon.badges.issuerdetails": "Подаци о издавачу", "addon.badges.issuername": "Име/назив издавача беџа", + "addon.badges.issuerurl": "URL адреса издавача", "addon.badges.nobadges": "Нема доступних беџева", "addon.badges.recipientdetails": "Детаљи о примаоцу", + "addon.badges.warnexpired": "(Овај беџ је истекао!)", + "addon.block_activitymodules.pluginname": "Активности", + "addon.block_myoverview.future": "Будући", + "addon.block_myoverview.inprogress": "У току", + "addon.block_myoverview.morecourses": "Више курсева", + "addon.block_myoverview.nocourses": "Нема курсева", + "addon.block_myoverview.past": "Прошли", + "addon.block_myoverview.pluginname": "Преглед курсева", + "addon.block_sitemainmenu.pluginname": "Главни мени", "addon.calendar.calendar": "Календар", "addon.calendar.calendarevents": "Догађаји у календару", "addon.calendar.defaultnotificationtime": "Подразумевано време за слање обавештења", @@ -87,11 +97,11 @@ "addon.coursecompletion.criteriarequiredany": "Било који од доле наведених критеријума је неопходан", "addon.coursecompletion.inprogress": "У току", "addon.coursecompletion.manualselfcompletion": "Ручни самостални завршетак", - "addon.coursecompletion.notyetstarted": "Још није започето", + "addon.coursecompletion.notyetstarted": "Није још почео", "addon.coursecompletion.pending": "На чекању", - "addon.coursecompletion.required": "Неопходно", - "addon.coursecompletion.requiredcriteria": "Неопходни критеријуми", - "addon.coursecompletion.requirement": "Услов", + "addon.coursecompletion.required": "Обавезно", + "addon.coursecompletion.requiredcriteria": "Обавезни критеријуми", + "addon.coursecompletion.requirement": "Захтев", "addon.coursecompletion.status": "Статус", "addon.coursecompletion.viewcoursereport": "Прикажи извештај са курса", "addon.files.couldnotloadfiles": "Списак датотека не може бити учитан.", @@ -102,12 +112,13 @@ "addon.files.sitefiles": "Датотеке сајта", "addon.messageoutput_airnotifier.processorsettingsdesc": "Конфигуриши уређаје", "addon.messages.addcontact": "Додај контакт", - "addon.messages.blockcontact": "Блокирај контакт", - "addon.messages.blockcontactconfirm": "Нећете више добијати поруке од ове особе.", + "addon.messages.addtoyourcontacts": "Додај међу своје контакте", "addon.messages.blocknoncontacts": "Блокирај све нове поруке од корисника који нису на мојој листи контаката", + "addon.messages.contactblocked": "Контак блокиран", "addon.messages.contactlistempty": "Листа контаката је празна", "addon.messages.contactname": "Име особе", "addon.messages.contacts": "Контакти", + "addon.messages.deleteallconfirm": "Да ли сте сигурни да желите да у потпуности обришете ову преписку?", "addon.messages.errordeletemessage": "Грешка приликом брисања поруке.", "addon.messages.errorwhileretrievingcontacts": "Грешка приликом преузимања контаката са сервера.", "addon.messages.errorwhileretrievingdiscussions": "Грешка приликом преузимања дискусија са сервера.", @@ -118,17 +129,20 @@ "addon.messages.messages": "Поруке", "addon.messages.newmessage": "Нова порука", "addon.messages.newmessages": "Нове поруке", - "addon.messages.nomessages": "Нема порука", + "addon.messages.nomessagesfound": "Није пронађена ниједна порука", + "addon.messages.noncontacts": "Ван списка контаката", "addon.messages.nousersfound": "Није пронађен ниједан корисник", "addon.messages.removecontact": "Обриши контакт", "addon.messages.removecontactconfirm": "Особа ће бити уклоњена са ваше листе контаката.", + "addon.messages.removefromyourcontacts": "Уклоните са списка ваших контаката", + "addon.messages.searchcombined": "Претражи људе и поруке", "addon.messages.type_blocked": "Блокиран", "addon.messages.type_offline": "Офлајн", "addon.messages.type_online": "Онлајн", "addon.messages.type_search": "Резултати претраге", "addon.messages.type_strangers": "Други", - "addon.messages.unblockcontact": "Одблокирај контакт", "addon.messages.warningmessagenotsent": "Није могуће послати поруку/е кориснику {{user}}. {{error}}", + "addon.messages.you": "Ви:", "addon.mod_assign.acceptsubmissionstatement": "Молимо вас да прихватите изјаву о предаји рада.", "addon.mod_assign.addattempt": "Дозволи други покушај", "addon.mod_assign.addnewattempt": "Додај нови покушај", @@ -166,6 +180,7 @@ "addon.mod_assign.graded": "Оцењено", "addon.mod_assign.gradedby": "Оценио/ла", "addon.mod_assign.gradedon": "Оцењено", + "addon.mod_assign.gradelocked": "Ова оцена је закључана или преписана у књизи оцена.", "addon.mod_assign.gradenotsynced": "Оцена није синхронизована", "addon.mod_assign.gradeoutof": "Оцена од {{$a}}", "addon.mod_assign.gradingstatus": "Статус оцењивања", @@ -180,6 +195,7 @@ "addon.mod_assign.markingworkflowstatereadyforrelease": "Спремно за објаву", "addon.mod_assign.markingworkflowstatereadyforreview": "Оцењивање завршено", "addon.mod_assign.markingworkflowstatereleased": "Објављено", + "addon.mod_assign.modulenameplural": "Задаци", "addon.mod_assign.multipleteams": "Члан више од једне групе", "addon.mod_assign.multipleteams_desc": "Овај задатак тражи групну предају рада. Члан сте више од једне групе. Да бисте могли да предате рад морате да будете члан само једне групе. Контактирајте свог предавача како би променио ваше чланство у групи.", "addon.mod_assign.noattempt": "Нема покушаја", @@ -234,6 +250,7 @@ "addon.mod_assign_submission_file.pluginname": "Предаја датотека", "addon.mod_assign_submission_onlinetext.pluginname": "Предаја онлајн текстова", "addon.mod_book.errorchapter": "Грешка приликом учитавања поглавља књиге.", + "addon.mod_book.modulenameplural": "Књиге", "addon.mod_chat.beep": "Звучни сигнал", "addon.mod_chat.currentusers": "Тренутни корисници", "addon.mod_chat.enterchat": "Кликните овде за улазак у причаоницу", @@ -246,6 +263,7 @@ "addon.mod_chat.messagebeepsyou": "{{$a}} вас је управо поздравио/ла звучним сигналом!", "addon.mod_chat.messageenter": "Учесник {{$a}} управо улази у причаоницу", "addon.mod_chat.messageexit": "Учесник {{$a}} управо напушта причаоницу", + "addon.mod_chat.modulenameplural": "Причаонице", "addon.mod_chat.mustbeonlinetosendmessages": "Морате бити онлајн како бисте слали поруке.", "addon.mod_chat.nomessages": "Још нема порука", "addon.mod_chat.send": "Пошаљи", @@ -256,6 +274,7 @@ "addon.mod_choice.errorgetchoice": "Грешка приликом преузимања података за 'Избор'", "addon.mod_choice.expired": "Нажалост, ова активност је затворена дана {{$a}} и није више доступна", "addon.mod_choice.full": "(Попуњено)", + "addon.mod_choice.modulenameplural": "Избори", "addon.mod_choice.noresultsviewable": "Резултати тренутно нису видљиви.", "addon.mod_choice.notopenyet": "Нажалост, ова активност није доступна до {{$a}}", "addon.mod_choice.numberofuser": "Број одговора", @@ -293,8 +312,10 @@ "addon.mod_data.errormustsupplyvalue": "Морате овде задати вредност.", "addon.mod_data.expired": "Нажалост, ова активност је затворена {$}} и више није доступна", "addon.mod_data.fields": "Поља", + "addon.mod_data.foundrecords": "Пронађени записи: {{$a.num}}/{{$a.max}}(Врати стара подешавања филтера)", "addon.mod_data.latlongboth": "Поља за географску ширина и дужина су обавезна.", "addon.mod_data.menuchoose": "Изаберите...", + "addon.mod_data.modulenameplural": "Базе података", "addon.mod_data.more": "Још", "addon.mod_data.nomatch": "Нема уноса који се поклапају!", "addon.mod_data.norecords": "Нема уноса у бази података", @@ -326,6 +347,7 @@ "addon.mod_feedback.feedbackopen": "Упитник доступан од", "addon.mod_feedback.mapcourses": "Повежи упитник са курсевима", "addon.mod_feedback.mode": "Врста упитника", + "addon.mod_feedback.modulenameplural": "Упитници (Feedback Activities)", "addon.mod_feedback.next_page": "Следећа страница", "addon.mod_feedback.non_anonymous": "Име корисника биће записано и приказано заједно са одговорима", "addon.mod_feedback.non_anonymous_entries": "Неанонимни одговори ({{$a}})", @@ -346,6 +368,7 @@ "addon.mod_feedback.started": "Започето", "addon.mod_feedback.this_feedback_is_already_submitted": "Већ сте попунили овај упитник.", "addon.mod_folder.emptyfilelist": "Нема датотека за приказ.", + "addon.mod_folder.modulenameplural": "Директоријуми", "addon.mod_forum.addanewdiscussion": "Додај нову тему за дискусију", "addon.mod_forum.addanewquestion": "Додај ново питање", "addon.mod_forum.addanewtopic": "Додај нову тему", @@ -368,6 +391,7 @@ "addon.mod_forum.modeflatnewestfirst": "Приказ одговора, почевши прво с најновијим", "addon.mod_forum.modeflatoldestfirst": "Приказ одговора, почевши прво с најстаријим", "addon.mod_forum.modenested": "Приказ одговора у угнежђеној форми", + "addon.mod_forum.modulenameplural": "Форуми", "addon.mod_forum.numdiscussions": "{{numdiscussions}} дискусије/а", "addon.mod_forum.numreplies": "{{numreplies}} одговора", "addon.mod_forum.posttoforum": "Пошаљи поруку на форум", @@ -403,9 +427,11 @@ "addon.mod_glossary.fillfields": "Појам и дефиниција су обавезна поља.", "addon.mod_glossary.fullmatch": "Ако се подударају искључиво целе речи", "addon.mod_glossary.linking": "Аутоматско повезивање", + "addon.mod_glossary.modulenameplural": "Речници", "addon.mod_glossary.noentriesfound": "Није пронађен ниједан појам.", "addon.mod_glossary.searchquery": "Упит за претрагу", "addon.mod_imscp.deploymenterror": "Грешка приликом учитавања пакета!", + "addon.mod_imscp.modulenameplural": "IMS пакети", "addon.mod_imscp.showmoduledescription": "Прикажи опис", "addon.mod_lesson.answer": "Одговор", "addon.mod_lesson.attempt": "Покушај: {{$a}}", @@ -449,6 +475,7 @@ "addon.mod_lesson.lowtime": "Најкраће време", "addon.mod_lesson.maximumnumberofattemptsreached": "Достигнут је максималан број покушаја - прелази се на следећу страницу", "addon.mod_lesson.modattemptsnoteacher": "Полазнички преглед функционише само за полазнике.", + "addon.mod_lesson.modulenameplural": "Лекције", "addon.mod_lesson.noanswer": "На једно или више питања није дат одговор. Молимо вратите се назад и дајте свој одговор.", "addon.mod_lesson.nolessonattempts": "Није било покушаја да се прође кроз ову лекцију.", "addon.mod_lesson.nolessonattemptsgroup": "Није било покушаја да се прође кроз ову лекцију од стране чланова групе {{$a}}.", @@ -493,7 +520,9 @@ "addon.mod_lti.errorgetlti": "Грешка приликом преузимања података модула.", "addon.mod_lti.errorinvalidlaunchurl": "Иницијална URL адреса није исправна.", "addon.mod_lti.launchactivity": "Покрени активност", + "addon.mod_lti.modulenameplural": "Екстерни алати", "addon.mod_page.errorwhileloadingthepage": "Грешка приликом учитавања садржаја странице.", + "addon.mod_page.modulenameplural": "Странице", "addon.mod_quiz.attemptfirst": "Први покушај", "addon.mod_quiz.attemptlast": "Последњи покушај", "addon.mod_quiz.attemptnumber": "Покушај", @@ -528,6 +557,7 @@ "addon.mod_quiz.grademethod": "Метод оцењивања", "addon.mod_quiz.gradesofar": "{{$a.method}}: {{$a.mygrade}} / {{$a.quizgrade}}.", "addon.mod_quiz.marks": "Оцене", + "addon.mod_quiz.modulenameplural": "Тестови", "addon.mod_quiz.mustbesubmittedby": "Овај покушај мора бити предат до {{$a}}.", "addon.mod_quiz.noquestions": "Ниједно питање још није додато", "addon.mod_quiz.noreviewattempt": "Није Вам дозвољено да прегледате овај покушај", @@ -572,6 +602,7 @@ "addon.mod_quiz.yourfinalgradeis": "Ваша коначна оцена на овом тесту је {{$a}}.", "addon.mod_resource.errorwhileloadingthecontent": "Грешка приликом учитавања садржаја.", "addon.mod_resource.modifieddate": "Измењено {{$a}}", + "addon.mod_resource.modulenameplural": "Датотеке", "addon.mod_resource.openthefile": "Отвори датотеку", "addon.mod_resource.uploadeddate": "Постављено {{$a}}", "addon.mod_scorm.asset": "Елемент", @@ -595,7 +626,7 @@ "addon.mod_scorm.errorpackagefile": "Извините, апликација подржава само ZIP архиве.", "addon.mod_scorm.errorsyncscorm": "Дошло је до грешке приликом синхронизације. Молимо, покушајте поново.", "addon.mod_scorm.exceededmaxattempts": "Достигли сте максималан број покушаја", - "addon.mod_scorm.failed": "Није успело", + "addon.mod_scorm.failed": "Неположен", "addon.mod_scorm.firstattempt": "Први покушај", "addon.mod_scorm.gradeaverage": "Просечна оцена", "addon.mod_scorm.gradeforattempt": "Оцена за покушај", @@ -608,6 +639,7 @@ "addon.mod_scorm.incomplete": "Непотпуно", "addon.mod_scorm.lastattempt": "Последњи завршени покушај", "addon.mod_scorm.mode": "Режим рада", + "addon.mod_scorm.modulenameplural": "SCORM пакети", "addon.mod_scorm.newattempt": "Почни нови покушај", "addon.mod_scorm.noattemptsallowed": "Број дозвољених покушаја", "addon.mod_scorm.noattemptsmade": "Број покушаја који сте имали", @@ -627,10 +659,12 @@ "addon.mod_survey.errorgetsurvey": "Грешка приликом преузимања података за 'Упитник' (Survey)", "addon.mod_survey.ifoundthat": "Открио/ла сам да", "addon.mod_survey.ipreferthat": "Преферирам", + "addon.mod_survey.modulenameplural": "Упитници (Surveys)", "addon.mod_survey.responses": "Одговори", "addon.mod_survey.results": "Резултати", "addon.mod_survey.surveycompletednograph": "Испунили сте анкету.", "addon.mod_url.accessurl": "Приступи URL адреси", + "addon.mod_url.modulenameplural": "URL адресе", "addon.mod_url.pointingtourl": "URL адреса са којом је овај ресурс повезан", "addon.mod_wiki.cannoteditpage": "Не можете да уређујете ову страницу.", "addon.mod_wiki.createpage": "Креирај страницу", @@ -639,6 +673,7 @@ "addon.mod_wiki.errornowikiavailable": "Овај вики још увек нема садржај.", "addon.mod_wiki.gowikihome": "Иди на почетну страницу викија", "addon.mod_wiki.map": "Мапа", + "addon.mod_wiki.modulenameplural": "Wikiji", "addon.mod_wiki.newpagehdr": "Нова страница", "addon.mod_wiki.newpagetitle": "Наслов нове странице", "addon.mod_wiki.nocontent": "На овој страници нема садржаја", @@ -676,6 +711,7 @@ "addon.mod_workshop.gradinggradecalculated": "Израчуната оцена за обављену процену", "addon.mod_workshop.gradinggradeof": "Оцена за евалуацију (од {{$a}})", "addon.mod_workshop.gradinggradeover": "Измени оцену за обављену процену", + "addon.mod_workshop.modulenameplural": "Радионице", "addon.mod_workshop.nogradeyet": "Још нема оцена", "addon.mod_workshop.notassessed": "Рад још није процењен", "addon.mod_workshop.notoverridden": "Није поништено", @@ -1038,6 +1074,8 @@ "core.accounts": "Налози", "core.add": "Додај", "core.agelocationverification": "Провера старости и места", + "core.ago": "{{$a}} пре", + "core.all": "Све", "core.allparticipants": "Сви учесници", "core.android": "Андроид", "core.answer": "Одговор", @@ -1075,7 +1113,7 @@ "core.confirmdeletefile": "Да ли сте сигурни да желите да обришете ову датотетеку?", "core.confirmloss": "Да ли сте сигурни? Све промене ће бити изгубљене.", "core.confirmopeninbrowser": "Да ли желите да отворите у веб читачу?", - "core.considereddigitalminor": "Сматрате се дигитално малолетним.", + "core.considereddigitalminor": "Превише сте млади да бисте могли да креирате налог на овом сајту.", "core.content": "Садржај", "core.contenteditingsynced": "Садржај који уређујете је синхронизован.", "core.contentlinks.chooseaccount": "Изабери налог", @@ -1099,6 +1137,7 @@ "core.course.couldnotloadsectioncontent": "Није могуће учитати садржај секције, покушајте поново касније.", "core.course.couldnotloadsections": "Није могуће учитати секције, покушајте поново касније.", "core.course.coursesummary": "Резиме курса", + "core.course.downloadcourse": "Преузми курс", "core.course.errordownloadingsection": "Грешка приликом преузимања секције.", "core.course.errorgetmodule": "Грешка приликом преузимања података модула.", "core.course.hiddenfromstudents": "Сакривено од полазника", @@ -1113,7 +1152,6 @@ "core.courses.cannotretrievemorecategories": "Категорије које су испод нивоа {{$a}} не могу се преузети.", "core.courses.categories": "Категорије курсева", "core.courses.confirmselfenrol": "Да ли сте сигурни да желите да се упишете на овај курс?", - "core.courses.courseoverview": "Преглед курсева", "core.courses.courses": "Курсеви", "core.courses.enrolme": "Упиши ме", "core.courses.errorloadcategories": "Дошло је до грешке приликом учитавања категорија.", @@ -1122,21 +1160,14 @@ "core.courses.errorselfenrol": "Дошло је до грешке приликом покушаја самосталног уписа.", "core.courses.filtermycourses": "Филтрирај моје курсеве", "core.courses.frontpage": "Насловна страница", - "core.courses.future": "Будући", - "core.courses.inprogress": "У току", - "core.courses.morecourses": "Више курсева", "core.courses.mycourses": "Моји курсеви", + "core.courses.mymoodle": "Контролни панел", "core.courses.nocourses": "Нема информација о курсу за приказ.", - "core.courses.nocoursesfuture": "Нема будућих курсева", - "core.courses.nocoursesinprogress": "Нема курсева који су току", - "core.courses.nocoursesoverview": "Нема курсева", - "core.courses.nocoursespast": "Нема прошлих курсева", "core.courses.nocoursesyet": "Нема курсева у овој категорији", "core.courses.nosearchresults": "Нема резултата", "core.courses.notenroled": "Нисте уписани на овај курс", "core.courses.notenrollable": "Не можете сами да се упишете на овај курс.", "core.courses.password": "Приступна лозинка курса", - "core.courses.past": "Прошли", "core.courses.paymentrequired": "Овај курс није бесплатан.", "core.courses.paypalaccepted": "Прихваћене су Paypal уплате", "core.courses.search": "Претрага", @@ -1162,7 +1193,7 @@ "core.dfmediumdate": "LLL", "core.dftimedate": "h[:]mm A", "core.digitalminor": "Дигитални малолетник", - "core.digitalminor_desc": "Да бисте креирали налог на овом сајту молимо нек ваш родитељ/старатељ контактира наведену особу.", + "core.digitalminor_desc": "Замолите свог родитеља/старатеља да контактира:", "core.discard": "Одбаци", "core.dismiss": "Обустави", "core.done": "Урађено", @@ -1267,7 +1298,7 @@ "core.login.createaccount": "Креирај мој нови кориснички налог", "core.login.createuserandpass": "Изаберите своје корисничко име и лозинку за приступ систему", "core.login.credentialsdescription": "За пријаву на систем унесите своје корисничко име и лозинку.", - "core.login.emailconfirmsent": "

Требало би да је послата порука на вашу е-адресу {{$a}}

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

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

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

Требало би да је послата е-порука на вашу адресу {{$a}}

\n

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

\n

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

", "core.login.emailnotmatch": "Адресе е-поште се не поклапају", "core.login.enterthewordsabove": "Унесите реч изнад", "core.login.erroraccesscontrolalloworigin": "Cross-Origin позив који покушавате да изведете је одбијен. Молимо, проверите https://docs.moodle.org/dev/Moodle_Mobile_development_using_Chrome_or_Chromium", @@ -1298,6 +1329,7 @@ "core.login.missingfirstname": "Недостаје име", "core.login.missinglastname": "Недостаје презиме", "core.login.mobileservicesnotenabled": "Мобилни сервиси нису омогућени на вашем сајту. Обратите се администратору вашег Moodle сајта ако мислите да мобилни приступ треба да буде омогућен.", + "core.login.mustconfirm": "Морате потврдити своју пријаву на систем", "core.login.newaccount": "Нови кориснички налог", "core.login.newsitedescription": "Унесите URL адресу вашег Moodle сајта. Имајте на уму да сајт можда није конфигурисан да ради са овом апликацијом.", "core.login.notloggedin": "Морате бити пријављени.", @@ -1339,8 +1371,6 @@ "core.mainmenu.changesite": "Промени сајт", "core.mainmenu.help": "Помоћ", "core.mainmenu.logout": "Одјава", - "core.mainmenu.mycourses": "Моји курсеви", - "core.mainmenu.togglemenu": "Укључи/искључи мени", "core.mainmenu.website": "Веб сајт", "core.maxsizeandattachments": "Максимална величина за нове датотеке: {{$a.size}}, максималан број прилога: {{$a.attachments}}", "core.min": "min", @@ -1427,8 +1457,10 @@ "core.quotausage": "Тренутно користите {{$a.used}} од максимално дозвољених {{$a.total}}.", "core.redirectingtosite": "Бићете преусмерени на сајт.", "core.refresh": "Освежи", + "core.remove": "Уклони", "core.required": "Обавезно", "core.requireduserdatamissing": "Овај корисник нема у свом профилу неке неопходне податке. Молимо вас, унесети ове податке у ваш Moodle и покушајте поново.
{{$a}}", + "core.resources": "Ресурси", "core.restore": "Рестаурирање резервнe копијe", "core.retry": "Покушај поново", "core.save": "Сачувај", @@ -1513,6 +1545,21 @@ "core.sizetb": "TB", "core.sorry": "Извините...", "core.sortby": "Сортирај по", + "core.strftimedate": "%d. %B %Y.", + "core.strftimedatefullshort": "%d/%m/%y", + "core.strftimedateshort": "%d. %B", + "core.strftimedatetime": "%d. %B %Y., %H:%M", + "core.strftimedatetimeshort": "%d/%m/%Y %H:%M", + "core.strftimedaydate": "%A, %d. %B %Y.", + "core.strftimedaydatetime": "%A, %d. %B %Y., %H:%M", + "core.strftimedayshort": "%A, %d. %B", + "core.strftimedaytime": "%a, %H:%M", + "core.strftimemonthyear": "%B %Y.", + "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.submit": "Проследи", "core.success": "Успешно", "core.tablet": "Таблет", diff --git a/src/assets/lang/sr-lt.json b/src/assets/lang/sr-lt.json index 63164fcbd..355a16d94 100644 --- a/src/assets/lang/sr-lt.json +++ b/src/assets/lang/sr-lt.json @@ -8,8 +8,18 @@ "addon.badges.issuancedetails": "Bedž ističe", "addon.badges.issuerdetails": "Podaci o izdavaču", "addon.badges.issuername": "Ime/naziv izdavača bedža", + "addon.badges.issuerurl": "URL adresa izdavača", "addon.badges.nobadges": "Nema dostupnih bedževa", "addon.badges.recipientdetails": "Detalji o primaocu", + "addon.badges.warnexpired": "(Ovaj bedž je istekao!)", + "addon.block_activitymodules.pluginname": "Aktivnosti", + "addon.block_myoverview.future": "Budući", + "addon.block_myoverview.inprogress": "U toku", + "addon.block_myoverview.morecourses": "Više kurseva", + "addon.block_myoverview.nocourses": "Nema kurseva", + "addon.block_myoverview.past": "Prošli", + "addon.block_myoverview.pluginname": "Pregled kurseva", + "addon.block_sitemainmenu.pluginname": "Glavni meni", "addon.calendar.calendar": "Kalendar", "addon.calendar.calendarevents": "Događaji u kalendaru", "addon.calendar.defaultnotificationtime": "Podrazumevano vreme za slanje obaveštenja", @@ -87,11 +97,11 @@ "addon.coursecompletion.criteriarequiredany": "Bilo koji od dole navedenih kriterijuma je neophodan", "addon.coursecompletion.inprogress": "U toku", "addon.coursecompletion.manualselfcompletion": "Ručni samostalni završetak", - "addon.coursecompletion.notyetstarted": "Još nije započeto", + "addon.coursecompletion.notyetstarted": "Nije još počeo", "addon.coursecompletion.pending": "Na čekanju", - "addon.coursecompletion.required": "Neophodno", - "addon.coursecompletion.requiredcriteria": "Neophodni kriterijumi", - "addon.coursecompletion.requirement": "Uslov", + "addon.coursecompletion.required": "Obavezno", + "addon.coursecompletion.requiredcriteria": "Obavezni kriterijumi", + "addon.coursecompletion.requirement": "Zahtev", "addon.coursecompletion.status": "Status", "addon.coursecompletion.viewcoursereport": "Prikaži izveštaj sa kursa", "addon.files.couldnotloadfiles": "Spisak datoteka ne može biti učitan.", @@ -102,12 +112,13 @@ "addon.files.sitefiles": "Datoteke sajta", "addon.messageoutput_airnotifier.processorsettingsdesc": "Komfiguriši uređaj", "addon.messages.addcontact": "Dodaj kontakt", - "addon.messages.blockcontact": "Blokiraj kontakt", - "addon.messages.blockcontactconfirm": "Nećete više dobijati poruke od ove osobe.", + "addon.messages.addtoyourcontacts": "Dodaj među svoje kontakte", "addon.messages.blocknoncontacts": "Blokiraj sve nove poruke od korisnika koji nisu na mojoj listi kontakata", + "addon.messages.contactblocked": "Kontakt blokiran", "addon.messages.contactlistempty": "Lista kontakata je prazna", "addon.messages.contactname": "Ime osobe", "addon.messages.contacts": "Kontakti", + "addon.messages.deleteallconfirm": "Da li ste sigurni da želite da u potpunosti obrišete ovu prepisku?", "addon.messages.errordeletemessage": "Greška prilikom brisanja poruke.", "addon.messages.errorwhileretrievingcontacts": "Greška prilikom preuzimanja kontakata sa servera.", "addon.messages.errorwhileretrievingdiscussions": "Greška prilikom preuzimanja diskusija sa servera.", @@ -118,17 +129,20 @@ "addon.messages.messages": "Poruke", "addon.messages.newmessage": "Nova poruka", "addon.messages.newmessages": "Nove poruke", - "addon.messages.nomessages": "Nema poruka", + "addon.messages.nomessagesfound": "Nije pronađena nijedna poruka", + "addon.messages.noncontacts": "Van spiska kontakata", "addon.messages.nousersfound": "Nije pronađen nijedan korisnik", "addon.messages.removecontact": "Obriši kontakt", "addon.messages.removecontactconfirm": "Osoba će biti uklonjena sa vaše liste kontakata.", + "addon.messages.removefromyourcontacts": "Uklonite sa spiska vaših kontakata", + "addon.messages.searchcombined": "Pretraži ljude i poruke", "addon.messages.type_blocked": "Blokiran", "addon.messages.type_offline": "Oflajn", "addon.messages.type_online": "Onlajn", "addon.messages.type_search": "Rezultati pretrage", "addon.messages.type_strangers": "Drugi", - "addon.messages.unblockcontact": "Odblokiraj kontakt", "addon.messages.warningmessagenotsent": "Nije moguće poslati poruku/e korisniku {{user}}. {{error}}", + "addon.messages.you": "Vi:", "addon.mod_assign.acceptsubmissionstatement": "Molimo vas da prihvatite izjavu o predaji rada.", "addon.mod_assign.addattempt": "Dozvoli drugi pokušaj", "addon.mod_assign.addnewattempt": "Dodaj novi pokušaj", @@ -166,6 +180,7 @@ "addon.mod_assign.graded": "Ocenjeno", "addon.mod_assign.gradedby": "Ocenio/la", "addon.mod_assign.gradedon": "Ocenjeno", + "addon.mod_assign.gradelocked": "Ова оцена је закључана или преписана у књизи оцена.", "addon.mod_assign.gradenotsynced": "Ocena nije sinhronizovana", "addon.mod_assign.gradeoutof": "Ocena od {{$a}}", "addon.mod_assign.gradingstatus": "Status ocenjivanja", @@ -180,6 +195,7 @@ "addon.mod_assign.markingworkflowstatereadyforrelease": "Spremno za objavu", "addon.mod_assign.markingworkflowstatereadyforreview": "Ocenjivanje završeno", "addon.mod_assign.markingworkflowstatereleased": "Objavljeno", + "addon.mod_assign.modulenameplural": "Zadaci", "addon.mod_assign.multipleteams": "Član više od jedne grupe", "addon.mod_assign.multipleteams_desc": "Ovaj zadatak traži grupnu predaju rada. Član ste više od jedne grupe. Da biste mogli da predate rad morate da budete član samo jedne grupe. Kontaktirajte svog predavača kako bi promenio vaše članstvo u grupi.", "addon.mod_assign.noattempt": "Nema pokušaja", @@ -234,6 +250,7 @@ "addon.mod_assign_submission_file.pluginname": "Predaja datoteka", "addon.mod_assign_submission_onlinetext.pluginname": "Predaja onlajn tekstova", "addon.mod_book.errorchapter": "Greška prilikom učitavanja poglavlja knjige.", + "addon.mod_book.modulenameplural": "Knjige", "addon.mod_chat.beep": "Zvučni signal", "addon.mod_chat.currentusers": "Trenutni korisnici", "addon.mod_chat.enterchat": "Kliknite ovde za ulazak u pričaonicu", @@ -246,6 +263,7 @@ "addon.mod_chat.messagebeepsyou": "{{$a}} vas je upravo pozdravio/la zvučnim signalom!", "addon.mod_chat.messageenter": "Učesnik {{$a}} upravo ulazi u pričaonicu", "addon.mod_chat.messageexit": "Učesnik {{$a}} upravo napušta pričaonicu", + "addon.mod_chat.modulenameplural": "Pričaonice", "addon.mod_chat.mustbeonlinetosendmessages": "Morate biti onlajn kako biste slali poruke.", "addon.mod_chat.nomessages": "Još nema poruka", "addon.mod_chat.send": "Pošalji", @@ -256,6 +274,7 @@ "addon.mod_choice.errorgetchoice": "Greška prilikom preuzimanja podataka za 'Izbor'", "addon.mod_choice.expired": "Nažalost, ova aktivnost je zatvorena dana {{$a}} i nije više dostupna", "addon.mod_choice.full": "(Popunjeno)", + "addon.mod_choice.modulenameplural": "Izbori", "addon.mod_choice.noresultsviewable": "Rezultati trenutno nisu vidljivi.", "addon.mod_choice.notopenyet": "Nažalost, ova aktivnost nije dostupna do {{$a}}", "addon.mod_choice.numberofuser": "Broj odgovora", @@ -293,8 +312,10 @@ "addon.mod_data.errormustsupplyvalue": "Morate ovde zadati vrednost.", "addon.mod_data.expired": "Nažalost, ova aktivnost je zatvorena {$}} i više nije dostupna", "addon.mod_data.fields": "Polja", + "addon.mod_data.foundrecords": "Pronađeni zapisi: {{$a.num}}/{{$a.max}}(Vrati stara podešavanja filtera)", "addon.mod_data.latlongboth": "Polja za geografsku širina i dužina su obavezna.", "addon.mod_data.menuchoose": "Izaberite...", + "addon.mod_data.modulenameplural": "Baze podataka", "addon.mod_data.more": "Još", "addon.mod_data.nomatch": "Nema unosa koji se poklapaju!", "addon.mod_data.norecords": "Nema unosa u bazi podataka", @@ -326,6 +347,7 @@ "addon.mod_feedback.feedbackopen": "Upitnik dostupan od", "addon.mod_feedback.mapcourses": "Poveži upitnik sa kursevima", "addon.mod_feedback.mode": "Vrsta upitnika", + "addon.mod_feedback.modulenameplural": "Upitnici (Feedback Activities)", "addon.mod_feedback.next_page": "Sledeća stranica", "addon.mod_feedback.non_anonymous": "Ime korisnika biće zapisano i prikazano zajedno sa odgovorima", "addon.mod_feedback.non_anonymous_entries": "Neanonimni odgovori ({{$a}})", @@ -346,6 +368,7 @@ "addon.mod_feedback.started": "Započeto", "addon.mod_feedback.this_feedback_is_already_submitted": "Već ste popunili ovaj upitnik.", "addon.mod_folder.emptyfilelist": "Nema datoteka za prikaz.", + "addon.mod_folder.modulenameplural": "Direktorijumi", "addon.mod_forum.addanewdiscussion": "Dodaj novu temu za diskusiju", "addon.mod_forum.addanewquestion": "Dodaj novo pitanje", "addon.mod_forum.addanewtopic": "Dodaj novu temu", @@ -368,6 +391,7 @@ "addon.mod_forum.modeflatnewestfirst": "Prikaz odgovora, počevši prvo s najnovijim", "addon.mod_forum.modeflatoldestfirst": "Prikaz odgovora, počevši prvo s najstarijim", "addon.mod_forum.modenested": "Prikaz odgovora u ugnežđenoj formi", + "addon.mod_forum.modulenameplural": "Forumi", "addon.mod_forum.numdiscussions": "{{numdiscussions}} diskusije/a", "addon.mod_forum.numreplies": "{{numreplies}} odgovora", "addon.mod_forum.posttoforum": "Pošalji poruku na forum", @@ -403,9 +427,11 @@ "addon.mod_glossary.fillfields": "Pojam i definicija su obavezna polja.", "addon.mod_glossary.fullmatch": "Ako se podudaraju isključivo cele reči", "addon.mod_glossary.linking": "Automatsko povezivanje", + "addon.mod_glossary.modulenameplural": "Rečnici", "addon.mod_glossary.noentriesfound": "Nije pronađen nijedan pojam.", "addon.mod_glossary.searchquery": "Upit za pretragu", "addon.mod_imscp.deploymenterror": "Greška prilikom učitavanja paketa!", + "addon.mod_imscp.modulenameplural": "IMS paketi", "addon.mod_imscp.showmoduledescription": "Prikaži opis", "addon.mod_lesson.answer": "Odgovor", "addon.mod_lesson.attempt": "Pokušaj: {{$a}}", @@ -449,6 +475,7 @@ "addon.mod_lesson.lowtime": "Najkraće vreme", "addon.mod_lesson.maximumnumberofattemptsreached": "Dostignut je maksimalan broj pokušaja - prelazi se na sledeću stranicu", "addon.mod_lesson.modattemptsnoteacher": "Polaznički pregled funkcioniše samo za polaznike", + "addon.mod_lesson.modulenameplural": "Lekcije", "addon.mod_lesson.noanswer": "Na jedno ili više pitanja nije dat odgovor. Molimo vratite se nazad i dajte svoj odgovor.", "addon.mod_lesson.nolessonattempts": "Nije bilo pokušaja da se prođe kroz ovu lekciju.", "addon.mod_lesson.nolessonattemptsgroup": "Nije bilo pokušaja da se prođe kroz ovu lekciju od strane članova grupe {{$a}}.", @@ -493,7 +520,9 @@ "addon.mod_lti.errorgetlti": "Greška prilikom preuzimanja podataka modula.", "addon.mod_lti.errorinvalidlaunchurl": "Inicijalna URL adresa nije ispravna.", "addon.mod_lti.launchactivity": "Pokreni aktivnost", + "addon.mod_lti.modulenameplural": "Eksterni alati", "addon.mod_page.errorwhileloadingthepage": "Greška prilikom učitavanja sadržaja stranice.", + "addon.mod_page.modulenameplural": "Stranice", "addon.mod_quiz.attemptfirst": "Prvi pokušaj", "addon.mod_quiz.attemptlast": "Poslednji pokušaj", "addon.mod_quiz.attemptnumber": "Pokušaj", @@ -528,6 +557,7 @@ "addon.mod_quiz.grademethod": "Metod ocenjivanja", "addon.mod_quiz.gradesofar": "{{$a.method}}: {{$a.mygrade}} / {{$a.quizgrade}}.", "addon.mod_quiz.marks": "Ocene", + "addon.mod_quiz.modulenameplural": "Testovi", "addon.mod_quiz.mustbesubmittedby": "Ovaj pokušaj mora biti predat do {{$a}}.", "addon.mod_quiz.noquestions": "Nijedno pitanje još nije dodato", "addon.mod_quiz.noreviewattempt": "Nije Vam dozvoljeno da pregledate ovaj pokušaj", @@ -572,6 +602,7 @@ "addon.mod_quiz.yourfinalgradeis": "Vaša konačna ocena na ovom testu je {{$a}}.", "addon.mod_resource.errorwhileloadingthecontent": "Greška prilikom učitavanja sadržaja.", "addon.mod_resource.modifieddate": "Izmenjeno {{$a}}", + "addon.mod_resource.modulenameplural": "Datoteke", "addon.mod_resource.openthefile": "Otvori datoteku", "addon.mod_resource.uploadeddate": "Postavljeno {{$a}}", "addon.mod_scorm.asset": "Element", @@ -595,7 +626,7 @@ "addon.mod_scorm.errorpackagefile": "Izvinite, aplikacija podržava samo ZIP arhive.", "addon.mod_scorm.errorsyncscorm": "Došlo je do greške prilikom sinhronizacije. Molimo, pokušajte ponovo.", "addon.mod_scorm.exceededmaxattempts": "Dostigli ste maksimalan broj pokušaja", - "addon.mod_scorm.failed": "Nije uspelo", + "addon.mod_scorm.failed": "Nepoložen", "addon.mod_scorm.firstattempt": "Prvi pokušaj", "addon.mod_scorm.gradeaverage": "Prosečna ocena", "addon.mod_scorm.gradeforattempt": "Ocena za pokušaj", @@ -608,6 +639,7 @@ "addon.mod_scorm.incomplete": "Nepotpuno", "addon.mod_scorm.lastattempt": "Poslednji završeni pokušaj", "addon.mod_scorm.mode": "Režim rada", + "addon.mod_scorm.modulenameplural": "SCORM paketi", "addon.mod_scorm.newattempt": "Počni novi pokušaj", "addon.mod_scorm.noattemptsallowed": "Broj dozvoljenih pokušaja", "addon.mod_scorm.noattemptsmade": "Broj pokušaja koji ste imali", @@ -627,10 +659,12 @@ "addon.mod_survey.errorgetsurvey": "Greška prilikom preuzimanja podataka za 'Upitnik' (Survey)", "addon.mod_survey.ifoundthat": "Otkrio/la sam da", "addon.mod_survey.ipreferthat": "Preferiram", + "addon.mod_survey.modulenameplural": "Upitnici (Surveys)", "addon.mod_survey.responses": "Odgovori", "addon.mod_survey.results": "Rezultati", "addon.mod_survey.surveycompletednograph": "Ispunili ste anketu.", "addon.mod_url.accessurl": "Pristupi URL adresi", + "addon.mod_url.modulenameplural": "URL adrese", "addon.mod_url.pointingtourl": "URL adresa sa kojom je ovaj resurs povezan", "addon.mod_wiki.cannoteditpage": "Ne možete da uređujete ovu stranicu.", "addon.mod_wiki.createpage": "Kreiraj stranicu", @@ -639,6 +673,7 @@ "addon.mod_wiki.errornowikiavailable": "Ovaj viki još uvek nema sadržaj.", "addon.mod_wiki.gowikihome": "Idi na početnu stranicu vikija", "addon.mod_wiki.map": "Mapa", + "addon.mod_wiki.modulenameplural": "Wikiji", "addon.mod_wiki.newpagehdr": "Nova stranica", "addon.mod_wiki.newpagetitle": "Naslov nove stranice", "addon.mod_wiki.nocontent": "Na ovoj stranici nema sadržaja", @@ -676,6 +711,7 @@ "addon.mod_workshop.gradinggradecalculated": "Izračunata ocena za obavljenu procenu", "addon.mod_workshop.gradinggradeof": "Ocena za evaluaciju (od {{$a}})", "addon.mod_workshop.gradinggradeover": "Izmeni ocenu za obavljenu procenu", + "addon.mod_workshop.modulenameplural": "Radionice", "addon.mod_workshop.nogradeyet": "Još nema ocena", "addon.mod_workshop.notassessed": "Rad još nije procenjen", "addon.mod_workshop.notoverridden": "Nije poništeno", @@ -1038,6 +1074,8 @@ "core.accounts": "Nalozi", "core.add": "Dodaj", "core.agelocationverification": "Provera starosti i mesta", + "core.ago": "{{$a}} pre", + "core.all": "Sve", "core.allparticipants": "Svi učesnici", "core.android": "Android", "core.answer": "Odgovor", @@ -1075,7 +1113,7 @@ "core.confirmdeletefile": "Da li ste sigurni da želite da obrišete ovu datoteteku?", "core.confirmloss": "Da li ste sigurni? Sve promene će biti izgubljene.", "core.confirmopeninbrowser": "Da li želite da otvorite u veb čitaču?", - "core.considereddigitalminor": "Smatrate se digitalno maloletnim.", + "core.considereddigitalminor": "Previše ste mladi da biste mogli da kreirate nalog na ovom sajtu.", "core.content": "Sadržaj", "core.contenteditingsynced": "Sadržaj koji uređujete je sinhronizovan.", "core.contentlinks.chooseaccount": "Izaberi nalog", @@ -1099,6 +1137,7 @@ "core.course.couldnotloadsectioncontent": "Nije moguće učitati sadržaj sekcije, pokušajte ponovo kasnije.", "core.course.couldnotloadsections": "Nije moguće učitati sekcije, pokušajte ponovo kasnije.", "core.course.coursesummary": "Rezime kursa", + "core.course.downloadcourse": "Preuzmi kurs", "core.course.errordownloadingsection": "Greška prilikom preuzimanja sekcije.", "core.course.errorgetmodule": "Greška prilikom preuzimanja podataka modula.", "core.course.hiddenfromstudents": "Sakriveno od polaznika", @@ -1113,7 +1152,6 @@ "core.courses.cannotretrievemorecategories": "Kategorije koje su ispod nivoa {{$a}} ne mogu se preuzeti.", "core.courses.categories": "Kategorije kurseva", "core.courses.confirmselfenrol": "Da li ste sigurni da želite da se upišete na ovaj kurs?", - "core.courses.courseoverview": "Pregled kurseva", "core.courses.courses": "Kursevi", "core.courses.enrolme": "Upiši me", "core.courses.errorloadcategories": "Došlo je do greške prilikom učitavanja kategorija.", @@ -1122,21 +1160,14 @@ "core.courses.errorselfenrol": "Došlo je do greške prilikom pokušaja samostalnog upisa.", "core.courses.filtermycourses": "Filtriraj moje kurseve", "core.courses.frontpage": "Naslovna stranica", - "core.courses.future": "Budući", - "core.courses.inprogress": "U toku", - "core.courses.morecourses": "Više kurseva", "core.courses.mycourses": "Moji kursevi", + "core.courses.mymoodle": "Kontrolni panel", "core.courses.nocourses": "Nema informacija o kursu koje bi se mogle prikazati.", - "core.courses.nocoursesfuture": "Nema budućih kurseva", - "core.courses.nocoursesinprogress": "Nema kurseva koji su toku", - "core.courses.nocoursesoverview": "Nema kursreva", - "core.courses.nocoursespast": "Nema prošlih kurseva", "core.courses.nocoursesyet": "Nema kurseva u ovoj kategoriji", "core.courses.nosearchresults": "Nema rezultata", "core.courses.notenroled": "Niste upisani na ovaj kurs", "core.courses.notenrollable": "Ne možete sami da se upišete na ovaj kurs.", "core.courses.password": "Pristupna lozinka kursa", - "core.courses.past": "Prošli", "core.courses.paymentrequired": "Ovaj kurs nije besplatan.", "core.courses.paypalaccepted": "Prihvaćene su Paypal uplate", "core.courses.search": "Pretraga", @@ -1162,7 +1193,7 @@ "core.dfmediumdate": "LLL", "core.dftimedate": "h[:]mm A", "core.digitalminor": "Digitalni maloletnik", - "core.digitalminor_desc": "Da biste kreirali nalog na ovom sajtu molimo nek vaš roditelj/staratelj kontaktira navedenu osobu.", + "core.digitalminor_desc": "Zamolite svog roditelja/staratelja da kontaktira:", "core.discard": "Odbaci", "core.dismiss": "Obustavi", "core.done": "Urađeno", @@ -1267,7 +1298,7 @@ "core.login.createaccount": "Kreiraj moj novi korisnički nalog", "core.login.createuserandpass": "Izaberite svoje korisničko ime i lozinku za pristup sistemu", "core.login.credentialsdescription": "Za prijavu na sistem unesite svoje korisničko ime i lozinku.", - "core.login.emailconfirmsent": "

Trebalo bi da je poslata poruka na vašu e-adresu {{$a}}

Poruka sadrži jednostavna uputstva o daljem postupku registracije.

Ako i dalje imate problema, obratite se administratoru sajta.

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

Trebalo bi da je poslata e-poruka na vašu adresu {{$a}}

\n

Poruka sadrži jednostavna uputstva o daljem postupku registracije.

\n

Ako i dalje imate problema, kontaktirajte administratora.

", "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", @@ -1298,6 +1329,7 @@ "core.login.missingfirstname": "Nedostaje ime", "core.login.missinglastname": "Nedostaje prezime", "core.login.mobileservicesnotenabled": "Mobilni servisi nisu omogućeni na vašem sajtu. Obratite se administratoru vašeg Moodle sajta ako mislite da mobilni pristup treba da bude omogućen.", + "core.login.mustconfirm": "Morate potvrditi svoju prijavu na sistem", "core.login.newaccount": "Novi korisnički nalog", "core.login.newsitedescription": "Unesite URL adresu vašeg Moodle sajta. Imajte na umu da sajt možda nije konfigurisan da radi sa ovom aplikacijom.", "core.login.notloggedin": "Morate biti prijavljeni.", @@ -1339,8 +1371,6 @@ "core.mainmenu.changesite": "Promeni sajt", "core.mainmenu.help": "Pomoć", "core.mainmenu.logout": "Odjava", - "core.mainmenu.mycourses": "Moji kursevi", - "core.mainmenu.togglemenu": "Uključi/isključi meni", "core.mainmenu.website": "Veb sajt", "core.maxsizeandattachments": "Maksimalna veličina za nove datoteke: {{$a.size}}, maksimalan broj priloga: {{$a.attachments}}", "core.min": "min", @@ -1427,8 +1457,10 @@ "core.quotausage": "Trenutno koristite {{$a.used}} od maksimalno dozvoljenih {{$a.total}}.", "core.redirectingtosite": "Bićete preusmereni na sajt.", "core.refresh": "Osveži", + "core.remove": "Ukloni", "core.required": "Obavezno", "core.requireduserdatamissing": "Ovaj korisnik nema u svom profilu neke neophodne podatke. Molimo vas, uneseti ove podatke u vaš Moodle i pokušajte ponovo.
{{$a}}", + "core.resources": "Resursi", "core.restore": "Restauriranje rezervne kopije", "core.retry": "Pokušaj ponovo", "core.save": "Sačuvaj", @@ -1513,6 +1545,21 @@ "core.sizetb": "TB", "core.sorry": "Izvinite...", "core.sortby": "Sortiraj po", + "core.strftimedate": "%d. %B %Y.", + "core.strftimedatefullshort": "%d/%m/%y", + "core.strftimedateshort": "%d. %B", + "core.strftimedatetime": "%d. %B %Y., %H:%M", + "core.strftimedatetimeshort": "%d/%m/%Y %H:%M", + "core.strftimedaydate": "%A, %d. %B %Y.", + "core.strftimedaydatetime": "%A, %d. %B %Y., %H:%M", + "core.strftimedayshort": "%A, %d. %B", + "core.strftimedaytime": "%a, %H:%M", + "core.strftimemonthyear": "%B %Y.", + "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.submit": "Prosledi", "core.success": "Uspešno", "core.tablet": "Tablet", diff --git a/src/assets/lang/sv.json b/src/assets/lang/sv.json index ac4b5fc1f..b303477dd 100644 --- a/src/assets/lang/sv.json +++ b/src/assets/lang/sv.json @@ -8,7 +8,17 @@ "addon.badges.issuancedetails": "Förfallande av märke", "addon.badges.issuerdetails": "Utfärdarens detaljer", "addon.badges.issuername": "Utfärdarens namn", + "addon.badges.issuerurl": "Utfärdarens URL", "addon.badges.nobadges": "Det finns inga märken tillgängliga.", + "addon.badges.warnexpired": "(Det här märket har förfallit!)", + "addon.block_activitymodules.pluginname": "Aktiviteter", + "addon.block_myoverview.future": "Kommande", + "addon.block_myoverview.inprogress": "Pågående", + "addon.block_myoverview.morecourses": "Fler kurser", + "addon.block_myoverview.nocourses": "Inga kurser", + "addon.block_myoverview.past": "Tidigare", + "addon.block_myoverview.pluginname": "Kursöversikt", + "addon.block_sitemainmenu.pluginname": "Huvudmeny", "addon.calendar.calendar": "Kalender", "addon.calendar.calendarevents": "Kalenderhändelser", "addon.calendar.errorloadevent": "Fel vid inläsning av händelse.", @@ -71,22 +81,22 @@ "addon.competency.xcompetenciesproficientoutofyincourse": "Du har uppnått {{$a.x}} av {{$a.y}} kompetenser i denna kurs..", "addon.coursecompletion.complete": "", "addon.coursecompletion.completecourse": "Fullföljd kurs", - "addon.coursecompletion.completed": "Fullföljt", + "addon.coursecompletion.completed": "Slutfört", "addon.coursecompletion.completiondate": "Datum för fullföljande", "addon.coursecompletion.completionmenuitem": "Fullföljning", "addon.coursecompletion.couldnotloadreport": "Det gick inte att läsa in rapporten för fullföljande av kursen, vänligen försök igen senare .", - "addon.coursecompletion.coursecompletion": "Fullföljande av kurs", - "addon.coursecompletion.criteria": "Villkor", - "addon.coursecompletion.criteriagroup": "Villkor grupp", - "addon.coursecompletion.criteriarequiredall": "Alla villkor nedan måste vara uppfyllda", - "addon.coursecompletion.criteriarequiredany": "Något villkor nedan måste vara uppfylld", - "addon.coursecompletion.inprogress": "Pågående", - "addon.coursecompletion.manualselfcompletion": "Manuell fullföljande av deltagare", - "addon.coursecompletion.notyetstarted": "Ännu inte börjat", + "addon.coursecompletion.coursecompletion": "Fullgörande av kurs", + "addon.coursecompletion.criteria": "Kriterier", + "addon.coursecompletion.criteriagroup": "Kriterier för grupp", + "addon.coursecompletion.criteriarequiredall": "Alla kriterier är obligatoriska", + "addon.coursecompletion.criteriarequiredany": "Alla kriterier nedan är obligatoriska", + "addon.coursecompletion.inprogress": "Pågår", + "addon.coursecompletion.manualselfcompletion": "Studenten markerar själv som fullföljd", + "addon.coursecompletion.notyetstarted": "Har ännu inte påbörjats", "addon.coursecompletion.pending": "Avvaktar", "addon.coursecompletion.required": "Obligatorisk", - "addon.coursecompletion.requiredcriteria": "Obligatorisk villkor", - "addon.coursecompletion.requirement": "Krav", + "addon.coursecompletion.requiredcriteria": "Obligatoriskt kriterium", + "addon.coursecompletion.requirement": "Förhandskrav", "addon.coursecompletion.status": "Status", "addon.coursecompletion.viewcoursereport": "Visa kursrapport", "addon.files.couldnotloadfiles": "Listan över filer kunde inte läsas in.", @@ -95,11 +105,17 @@ "addon.files.privatefiles": "Privata filer", "addon.files.sitefiles": "Webbplatsens filer", "addon.messages.addcontact": "Lägg till kontakt", - "addon.messages.blockcontact": "Blockera kontakt", + "addon.messages.addtoyourcontacts": "Lägg till dina kontakter", "addon.messages.blocknoncontacts": "Blockera alla nya meddelanden från personer som inte finns med på min kontaktlista.", + "addon.messages.contactableprivacy": "Acceptera meddelanden från:", + "addon.messages.contactableprivacy_coursemember": "Mina kontaker och alla i mina kurser", + "addon.messages.contactableprivacy_onlycontacts": "Bara mina kontakter", + "addon.messages.contactableprivacy_site": "Alla på denna webbplats", + "addon.messages.contactblocked": "Kontakt blockerad", "addon.messages.contactlistempty": "kontaktlista är tom", "addon.messages.contactname": "Kontakt namn", "addon.messages.contacts": "Kontakter", + "addon.messages.deleteallconfirm": "Vill du verkligen radera hela denna konversation?", "addon.messages.errordeletemessage": "Fel vid borttagning av meddelandet", "addon.messages.errorwhileretrievingcontacts": "Fel vid hämtning av kontakter från servern.", "addon.messages.errorwhileretrievingdiscussions": "Fel vid hämtning av diskussionerna från servern.", @@ -109,15 +125,18 @@ "addon.messages.messagepreferences": "Välj inställningar för meddelanden", "addon.messages.messages": "Meddelanden", "addon.messages.newmessage": "Nytt meddelande", - "addon.messages.nomessages": "Inga avvaktande meddelanden", + "addon.messages.nomessagesfound": "Det gick inte att hitta några nya meddelanden", + "addon.messages.noncontacts": "Icke-kontakter", "addon.messages.nousersfound": "Inga användare hittades", "addon.messages.removecontact": "Ta bort kontakt", + "addon.messages.removefromyourcontacts": "Ta bort från dina kontakter", + "addon.messages.searchcombined": "Sök personer och meddelanden", "addon.messages.type_blocked": "blockerad", "addon.messages.type_offline": "Offline", "addon.messages.type_online": "Online", "addon.messages.type_search": "Sökresultat", "addon.messages.type_strangers": "Andra", - "addon.messages.unblockcontact": "Ta bort blockering av användare", + "addon.messages.you": "Du:", "addon.mod_assign.addattempt": "Tillåt ett nytt försök", "addon.mod_assign.addnewattempt": "Lägg till ett nytt försök", "addon.mod_assign.addnewattemptfromprevious": "Lägg till ett nytt försök baserat på tidigare inlämning", @@ -148,6 +167,7 @@ "addon.mod_assign.graded": "Betygssatt", "addon.mod_assign.gradedby": "Betygssatt av", "addon.mod_assign.gradedon": "Betygssatt den", + "addon.mod_assign.gradelocked": "Detta betyg är låst eller skrevs över i betygsboken/betygsrapporten.", "addon.mod_assign.gradeoutof": "Betyg ur {{$a}}", "addon.mod_assign.gradingstatus": "Betygssättningsstatus", "addon.mod_assign.groupsubmissionsettings": "Gruppinlämning inställningar", @@ -161,6 +181,7 @@ "addon.mod_assign.markingworkflowstatereadyforrelease": "Redo för", "addon.mod_assign.markingworkflowstatereadyforreview": "Bedöming färdig", "addon.mod_assign.markingworkflowstatereleased": "Released", + "addon.mod_assign.modulenameplural": "Uppgifter", "addon.mod_assign.multipleteams": "Medlem av mer än en grupp", "addon.mod_assign.multipleteams_desc": "Denna inlämning är en gruppuppgift. Just nu är du medlem i flera grupper. För att kunna göra en inlämning måste du ingår i endast en grupp. V.g. be din lärare att ändra din grupptillhörighet.", "addon.mod_assign.noattempt": "Inga försök", @@ -210,6 +231,7 @@ "addon.mod_assign_submission_file.pluginname": "Filinlämningar", "addon.mod_assign_submission_onlinetext.pluginname": "Online textinlämning", "addon.mod_book.errorchapter": "Fel vid läsning av bokkapitel", + "addon.mod_book.modulenameplural": "Böcker", "addon.mod_chat.beep": "pipsignal", "addon.mod_chat.currentusers": "Aktuella användare", "addon.mod_chat.enterchat": "Klicka här för att gå in i direktsamtalet nu", @@ -222,6 +244,7 @@ "addon.mod_chat.messagebeepsyou": "{{$a}} har just skickat en pipsignal till Dig", "addon.mod_chat.messageenter": "{{$a}} har precis kommit in i detta direktsamtal", "addon.mod_chat.messageexit": "{{$a}} har lämnat det här direktsamtalet", + "addon.mod_chat.modulenameplural": "Direktsamtal", "addon.mod_chat.mustbeonlinetosendmessages": "Du måste vara online för att skicka meddelanden", "addon.mod_chat.nomessages": "Inga meddelanden än", "addon.mod_chat.send": "Skicka", @@ -232,6 +255,7 @@ "addon.mod_choice.errorgetchoice": "Fel vid hämtning av opinionsdata", "addon.mod_choice.expired": "Den här aktiviteten är stängd på {{$a}} och den är inte längre tillgänglig.", "addon.mod_choice.full": "(Full)", + "addon.mod_choice.modulenameplural": "Opinionsundersökningar", "addon.mod_choice.noresultsviewable": "Det går f.n. inte att visa resultaten.", "addon.mod_choice.notopenyet": "Den här aktiviteten är tyvärr inte tillgänglig förrän {{$a}}", "addon.mod_choice.numberofuser": "Antal användare", @@ -259,7 +283,9 @@ "addon.mod_data.entrieslefttoaddtoview": "Du måste lägga till {{$a.entrieslefttoview}} fler bidrag innan Du kan få se de andra deltagarnas bidrag.", "addon.mod_data.expired": "Den här aktiviteten stängdes tyvärr den {{$a}} och är inte längre tillgänglig.", "addon.mod_data.fields": "Fält", + "addon.mod_data.foundrecords": "Funna poster: {{$a.num}}/{{$a.max}} (Filter för återställning)", "addon.mod_data.menuchoose": "Välj...", + "addon.mod_data.modulenameplural": "Databaser", "addon.mod_data.more": "Fler", "addon.mod_data.nomatch": "Det gick inte att hitta några matchande bidrag!", "addon.mod_data.norecords": "Det finns inga bidrag i databasen", @@ -288,6 +314,7 @@ "addon.mod_feedback.feedbackopen": "Öppna denna Egen enkät vid den här tiden", "addon.mod_feedback.mapcourses": "\"Mappa\" Egen enkät till kurser", "addon.mod_feedback.mode": "Läge", + "addon.mod_feedback.modulenameplural": "Egen enkät", "addon.mod_feedback.next_page": "Nästa sida", "addon.mod_feedback.non_anonymous": "Inte-anonym", "addon.mod_feedback.non_anonymous_entries": "Inga anonyma bidrag", @@ -307,6 +334,7 @@ "addon.mod_feedback.started": "startad", "addon.mod_feedback.this_feedback_is_already_submitted": "Du har redan fullföljt den här Egen enkät", "addon.mod_folder.emptyfilelist": "Det finns inga filer att visa", + "addon.mod_folder.modulenameplural": "Mappar", "addon.mod_forum.addanewdiscussion": "Lägg till ett nytt diskussionsämne", "addon.mod_forum.addanewquestion": "Lägg till en ny fråga", "addon.mod_forum.addanewtopic": "Lägg till ett nytt ämne", @@ -329,6 +357,7 @@ "addon.mod_forum.modeflatnewestfirst": "Visa svarslista, med det senaste först", "addon.mod_forum.modeflatoldestfirst": "Visa svarslista, med det äldsta först", "addon.mod_forum.modenested": "Visa svar i nästlad form", + "addon.mod_forum.modulenameplural": "Forum", "addon.mod_forum.numdiscussions": "{{numdiscussions}} diskussioner", "addon.mod_forum.numreplies": "{{numreplies}} svar", "addon.mod_forum.posttoforum": "Publicera inlägget i forumet", @@ -359,9 +388,11 @@ "addon.mod_glossary.fillfields": "'Begrepp' och definition' är obligatoriska fält", "addon.mod_glossary.fullmatch": "Matcha hela ord bara
när de är automatiskt länkade", "addon.mod_glossary.linking": "Automatisk länkning", + "addon.mod_glossary.modulenameplural": "Ord- och begreppslistor", "addon.mod_glossary.noentriesfound": "Inga inlägg hittades", "addon.mod_glossary.searchquery": "", "addon.mod_imscp.deploymenterror": "Fel med innehållspaket!", + "addon.mod_imscp.modulenameplural": "Innehållspaket av typ IMS", "addon.mod_imscp.showmoduledescription": "Visa beskrivning", "addon.mod_lesson.answer": "Svar", "addon.mod_lesson.attempt": "Försök: {{$a}}", @@ -401,6 +432,7 @@ "addon.mod_lesson.lowtime": "Låg tid", "addon.mod_lesson.maximumnumberofattemptsreached": "Du har uppnått maximalt antal tillåtna försök - Du flyttas nu vidare till nästa sida.", "addon.mod_lesson.modattemptsnoteacher": "Upprepad visning för studenter/elever/deltagare/lärande fungerar bara för dem.", + "addon.mod_lesson.modulenameplural": "Lektioner", "addon.mod_lesson.noanswer": "Det finns inget angivet svar", "addon.mod_lesson.nolessonattempts": "Det har inte gjorts några försök att genomföra den här lektionen.", "addon.mod_lesson.notcompleted": "Inte avslutad", @@ -440,6 +472,7 @@ "addon.mod_lti.errorinvalidlaunchurl": "URL är ogiltig", "addon.mod_lti.launchactivity": "Starta aktiviteten", "addon.mod_page.errorwhileloadingthepage": "Fel vid laddning av sidans innehåll.", + "addon.mod_page.modulenameplural": "Sidor", "addon.mod_quiz.attemptfirst": "Första försök", "addon.mod_quiz.attemptlast": "Senaste försök", "addon.mod_quiz.attemptnumber": "Försök", @@ -461,6 +494,7 @@ "addon.mod_quiz.grademethod": "Betygsättningsmetod", "addon.mod_quiz.gradesofar": "{{$a.method}}: {{$a.mygrade}} / {{$a.quizgrade}}.", "addon.mod_quiz.marks": "Poäng", + "addon.mod_quiz.modulenameplural": "Test", "addon.mod_quiz.mustbesubmittedby": "Detta försöket måste skickas in av {{$a}}.", "addon.mod_quiz.noquestions": "Inga frågor har ännu lagts till", "addon.mod_quiz.noreviewattempt": "Du har inte rätt att granska det här försöket. ", @@ -500,6 +534,7 @@ "addon.mod_quiz.timetaken": "Tid för genomförande", "addon.mod_quiz.yourfinalgradeis": "Ditt slutgiltiga betyg för detta test är {{$a}}.", "addon.mod_resource.errorwhileloadingthecontent": "Fel vid laddning av innehåll.", + "addon.mod_resource.modulenameplural": "Filer", "addon.mod_resource.openthefile": "Öppna filen", "addon.mod_scorm.asset": "Tillgång", "addon.mod_scorm.assetlaunched": "Tillgång - visad", @@ -535,6 +570,7 @@ "addon.mod_scorm.incomplete": "Inte komplett", "addon.mod_scorm.lastattempt": "Senaste försöket", "addon.mod_scorm.mode": "Läge", + "addon.mod_scorm.modulenameplural": "Scormpaket (flera)", "addon.mod_scorm.newattempt": "Påbörja ett nytt försök", "addon.mod_scorm.noattemptsallowed": "Antal tillåtna försök", "addon.mod_scorm.noattemptsmade": "Antal försök som Du har genomfört", @@ -554,9 +590,11 @@ "addon.mod_survey.errorgetsurvey": "Fel vid hämtning av enkätdata", "addon.mod_survey.ifoundthat": "Jag har funnit att", "addon.mod_survey.ipreferthat": "Jag föredrar att", + "addon.mod_survey.modulenameplural": "Färdiga enkäter", "addon.mod_survey.responses": "Svar", "addon.mod_survey.results": "Resultat", "addon.mod_url.accessurl": "Gå till webbadressen", + "addon.mod_url.modulenameplural": "URLer", "addon.mod_url.pointingtourl": "Resursens pekar till följande webbadress", "addon.mod_wiki.cannoteditpage": "Du kan inte redigera den här sidan.", "addon.mod_wiki.createpage": "Skapa sida", @@ -565,6 +603,7 @@ "addon.mod_wiki.errornowikiavailable": "Det finns ingen Wiki tillgänglig som kan visas", "addon.mod_wiki.gowikihome": "Wiki hem", "addon.mod_wiki.map": "Karta", + "addon.mod_wiki.modulenameplural": "Wikis", "addon.mod_wiki.newpagehdr": "Ny sida", "addon.mod_wiki.newpagetitle": "Ny titel på sida", "addon.mod_wiki.nocontent": "Det finns inget innehåll för den här sidan", @@ -600,6 +639,7 @@ "addon.mod_workshop.gradinggradecalculated": "Beräknade betyg för bedömning", "addon.mod_workshop.gradinggradeof": "Betyg för bedömning (av {{$a}})", "addon.mod_workshop.gradinggradeover": "Åsidosätt betyg för bedömning", + "addon.mod_workshop.modulenameplural": "Workshops", "addon.mod_workshop.nogradeyet": "Inga betyg än", "addon.mod_workshop.notassessed": "Ännu inte bedömd", "addon.mod_workshop.notoverridden": "Ej överskriden", @@ -892,6 +932,8 @@ "assets.mimetypes.text/rtf": "RTF-dokument", "core.accounts": "Konton", "core.add": "Lägg till", + "core.ago": "För {{$a}} sedan", + "core.all": "Alla", "core.allparticipants": "Alla deltagare", "core.android": "Android", "core.answer": "Svar", @@ -944,7 +986,6 @@ "core.courses.availablecourses": "Tillgängliga kurser", "core.courses.categories": "Kurskategorier", "core.courses.confirmselfenrol": "Är du säker att du vill registrera dig i den här kursen ?", - "core.courses.courseoverview": "Översikt över kurser", "core.courses.courses": "Kurser", "core.courses.enrolme": "Registrera mig", "core.courses.errorloadcourses": "Ett fel uppstod vid inläsning av kursen", @@ -952,21 +993,14 @@ "core.courses.errorselfenrol": "Fel uppstod vid registrering", "core.courses.filtermycourses": "Filtrera mina kurser", "core.courses.frontpage": "Ingångssida", - "core.courses.future": "Kommande", - "core.courses.inprogress": "Pågående", - "core.courses.morecourses": "Fler kurser", "core.courses.mycourses": "Mina kurser", + "core.courses.mymoodle": "Mitt Moodle", "core.courses.nocourses": "Det finns ingen kursinformation att visa.", - "core.courses.nocoursesfuture": "Inga kommande kurser", - "core.courses.nocoursesinprogress": "Inga pågående kurser", - "core.courses.nocoursesoverview": "Inga kurser", - "core.courses.nocoursespast": "Inga tidigare kurser", "core.courses.nocoursesyet": "Inga kurser i denna kategori", "core.courses.nosearchresults": "Inga resultat", "core.courses.notenroled": "Du är inte registrerad i den här kursen", "core.courses.notenrollable": "Du kan inte självregistrera dig i kursen", "core.courses.password": "Kursnyckel", - "core.courses.past": "Tidigare", "core.courses.paymentrequired": "För att få tillgång till den här kursen måste Du betala först.", "core.courses.paypalaccepted": "Betalningar via Paypal accepterade", "core.courses.search": "Sök", @@ -1063,6 +1097,7 @@ "core.login.createaccount": "Skapa mitt nya konto", "core.login.createuserandpass": "Skapa ett nytt användarnamn och lösenord för att logga in med.", "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.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.", @@ -1073,6 +1108,7 @@ "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", "core.login.invalidaccount": "Kontrollera dina inloggningsuppgifter stämmer eller be webbplatsadministratören att kontrollera webbplatsens konfiguration", + "core.login.invaliddate": "Ogiltigt datum", "core.login.invalidemail": "Ogiltig e-postadress", "core.login.invalidmoodleversion": "Ogiltig Moodle version. Lägsta version som krävs är", "core.login.invalidsite": "Den webbadress är ogiltig.", @@ -1086,6 +1122,7 @@ "core.login.missingfirstname": "Förnamn saknas", "core.login.missinglastname": "Efternamn saknas", "core.login.mobileservicesnotenabled": "Mobila tjänster är inte aktiverade på din sida. Vänligen kontakta din Moodleadministrator om du tycker att mobil åtkomst ska aktiveras.", + "core.login.mustconfirm": "Du måste bekräfta Din inloggning.", "core.login.newaccount": "Nytt konto", "core.login.newsitedescription": "Ange webbadressen till din Moodle webbplats. Observera att den kan vara konfigurerad så att den inte fungerar med appen", "core.login.notloggedin": "Du måste vara inloggad.", @@ -1119,7 +1156,6 @@ "core.mainmenu.changesite": "Byt webbsida", "core.mainmenu.help": "Hjälp", "core.mainmenu.logout": "Logga ut", - "core.mainmenu.mycourses": "Mina kurser", "core.mainmenu.website": "Webbsida", "core.maxsizeandattachments": "Maximal storlek för nya filer: {{$a.size}}, max bilagor: {{$a.attachments}}", "core.min": "minut", @@ -1192,8 +1228,10 @@ "core.question.questionno": "Fråga {{$a}}", "core.question.requiresgrading": "Kräver rättning", "core.refresh": "Uppdatera", + "core.remove": "Ta bort", "core.required": "Obligatorisk", "core.requireduserdatamissing": "Den här användaren saknar vissa nödvändiga profildata. Vänligen fyll i uppgifterna i din Moodle och försök igen.
{{$a}}", + "core.resources": "Resurser", "core.restore": "Återställ", "core.search": "Sök", "core.searching": "Söker", @@ -1260,6 +1298,19 @@ "core.sizemb": "Mb", "core.sizetb": "Tb", "core.sortby": "Sortera enligt", + "core.strftimedate": "%d %B %Y", + "core.strftimedatefullshort": "%d/%m/%y", + "core.strftimedateshort": "%d %B", + "core.strftimedatetime": "%d %B %Y, %H:%M", + "core.strftimedatetimeshort": "%d/%m/%Y %H:%M", + "core.strftimedaydate": "%A, %d %B %Y", + "core.strftimedaydatetime": "%A, %d %B %Y, %H:%M", + "core.strftimedayshort": "%A, %d %B", + "core.strftimedaytime": "%a, %H:%M", + "core.strftimemonthyear": "%B %Y", + "core.strftimerecent": "%d %b, %H:%M", + "core.strftimerecentfull": "%a, %d %b %Y, %H:%M", + "core.strftimetime": "%H:%M", "core.submit": "Skicka", "core.success": "Framgång", "core.tablet": "Tablet", @@ -1280,21 +1331,21 @@ "core.user.country": "Land", "core.user.description": "Beskrivning", "core.user.detailsnotavailable": "Detaljerna till denna användare är inte tillgängliga för dig.", - "core.user.editingteacher": "Lärare", + "core.user.editingteacher": "Distanslärare/
handledare/
coach etc", "core.user.email": "E-postadress", "core.user.emailagain": "E-post (igen)", "core.user.firstname": "Förnamn", "core.user.interests": "Intressen", "core.user.lastname": "Efternamn", - "core.user.manager": "Manager", + "core.user.manager": "Administratör", "core.user.newpicture": "Ny bild", "core.user.noparticipants": "Inga deltagare hittades för denna kurs", "core.user.participants": "Deltagare", "core.user.phone1": "Telefon", "core.user.phone2": "Mobiltelefon", "core.user.roles": "Roller", - "core.user.student": "Student", - "core.user.teacher": "Icke editerande lärare", + "core.user.student": "Lärande", + "core.user.teacher": "Icke-redigerande lärare", "core.user.webpage": "Webbsida", "core.userdeleted": "Ditt användarkonto har tagits bort.", "core.userdetails": "Detaljer om användare", diff --git a/src/assets/lang/tg.json b/src/assets/lang/tg.json index e5660f03f..f8d54022b 100644 --- a/src/assets/lang/tg.json +++ b/src/assets/lang/tg.json @@ -1,5 +1,13 @@ { "addon.badges.badges": "Бейҷҳо", + "addon.block_activitymodules.pluginname": "Унсурҳои курс", + "addon.block_myoverview.future": "Оянда", + "addon.block_myoverview.inprogress": "Дар раванд", + "addon.block_myoverview.morecourses": "Курсҳои дигар", + "addon.block_myoverview.nocourses": "Курсҳо нестанд", + "addon.block_myoverview.past": "Гузашта", + "addon.block_myoverview.pluginname": "Азназаргузаронии курс", + "addon.block_sitemainmenu.pluginname": "Meнюи асосӣ", "addon.calendar.calendar": "Тақвим", "addon.calendar.calendarevents": "Чорабиниҳои тақвимӣ", "addon.calendar.eventendtime": "Вақти анҷомёбӣ", @@ -34,30 +42,46 @@ "addon.competency.usercompetencystatus_waitingforreview": "Мунтазири баррасист.", "addon.competency.userplans": "Нақшаҳои таълим", "addon.coursecompletion.completecourse": "Курс ба анҷом расонида шавад", + "addon.coursecompletion.completed": "Иҷро шуд", + "addon.coursecompletion.completiondate": "Рӯзи иҷроиш", "addon.coursecompletion.completionmenuitem": "Назорати иҷрокунӣ", - "addon.coursecompletion.inprogress": "Дар ҳоли иҷро", - "addon.coursecompletion.pending": "Дар ҳоли интизор", - "addon.coursecompletion.required": "Ҳатмӣ аст", - "addon.coursecompletion.requirement": "Талаб", - "addon.coursecompletion.status": "Вазъият", + "addon.coursecompletion.coursecompletion": "Хатми курс", + "addon.coursecompletion.criteria": "Меъёрҳо", + "addon.coursecompletion.criteriagroup": "Гурӯҳи меъёрҳо", + "addon.coursecompletion.criteriarequiredall": "Мувофиқат ба ҳамаи меъёрҳои дар зер зикршуда талаб карда мешавад", + "addon.coursecompletion.criteriarequiredany": "Мувофиқат ба ҳар яке аз меъёрҳои дар зер зикршуда талаб карда мешавад", + "addon.coursecompletion.inprogress": "Дар раванд", + "addon.coursecompletion.manualselfcompletion": "Истифодабаранда метавонад худаш оиди иҷрокунӣ қайд кунад", + "addon.coursecompletion.notyetstarted": "Ҳанӯз сар нашудааст", + "addon.coursecompletion.pending": "Интизор меравад", + "addon.coursecompletion.required": "Пур кардан лозим аст", + "addon.coursecompletion.requiredcriteria": "Меъёрҳои даркорӣ", + "addon.coursecompletion.requirement": "Талабот", + "addon.coursecompletion.status": "Статус", + "addon.coursecompletion.viewcoursereport": "Ҳисобот оиди курс аз назар гузаронда шавад", "addon.files.files": "Файлҳо", "addon.files.privatefiles": "Файлҳои шахсӣ", "addon.files.sitefiles": "Файлҳои сомона", "addon.messages.addcontact": "Ҳамсӯҳбат илова карда шавад", - "addon.messages.blockcontact": "Бастани тамос", + "addon.messages.addtoyourcontacts": "Ҳамсӯҳбатони худро илова кунед", "addon.messages.blocknoncontacts": "Мактубчаҳо аз касоне ки дар рӯйхати ҳамсӯҳбатони ман нестанд, қабул карда нашаванд", + "addon.messages.contactblocked": "Ҳамсӯҳбат масдуд карда шудааст", "addon.messages.contacts": "Ҳамсӯҳбатон", + "addon.messages.deleteallconfirm": "Оё шумо ин гуфтугузорро пурра тоза кардан мехоҳед?", "addon.messages.message": "Мактубча", "addon.messages.messagepreferences": "Хусусиятҳои паём", "addon.messages.messages": "Мактубчаҳо", "addon.messages.newmessage": "Паёми нав", "addon.messages.newmessages": "Паёмҳои нав", - "addon.messages.nomessages": "Мактубчаҳои нав нестанд", + "addon.messages.nomessagesfound": "Мактубчаҳо ёфт нашуданд", + "addon.messages.noncontacts": "Ғайри ҳамсӯҳбат", "addon.messages.removecontact": "Ҳамсӯҳбат аз рӯйхати ман нест карда шавад", + "addon.messages.removefromyourcontacts": "Аз ҳама ҳамсӯҳбатон тоза кардан", + "addon.messages.searchcombined": "Ҷустуҷӯи истифодабарандагон ва мактубчаҳо", "addon.messages.type_offline": "Офлайн", "addon.messages.type_online": "Онлайн", "addon.messages.type_search": "Натиҷаҳои ҷустуҷӯ", - "addon.messages.unblockcontact": "Мактубчаҳо аз ин ҳамсӯҳбат аз масдудкунӣ кушода шаванд", + "addon.messages.you": "Шумо:", "addon.mod_assign.addattempt": "Ба дигар кӯшиш иҷозат дода шавад", "addon.mod_assign.addnewattempt": "Кӯшиши нав илова карда шавад", "addon.mod_assign.addnewattemptfromprevious": "Илова кардани кӯшиши нав дар асоси пешниҳоди пешина", @@ -85,12 +109,14 @@ "addon.mod_assign.graded": "Баҳо дода шуд", "addon.mod_assign.gradedby": "Баҳо дода шуд аз ҷониби", "addon.mod_assign.gradedon": "Баҳо барои", + "addon.mod_assign.gradelocked": "Ин баҳо маҳкам ва ё сбекоршуда дар журнал ба ҳисоб меравад", "addon.mod_assign.gradingstatus": "Ҳолати баҳодиҳӣ", "addon.mod_assign.hiddenuser": "Иштирокчӣ", "addon.mod_assign.markingworkflowstateinmarking": "Дар раванди баҳогузорӣ аст", "addon.mod_assign.markingworkflowstateinreview": "Дар баррасӣ қарор дорад", "addon.mod_assign.markingworkflowstatenotmarked": "Қайд нашудааст", "addon.mod_assign.markingworkflowstatereadyforrelease": "Тайер барои татбиқ", + "addon.mod_assign.modulenameplural": "Супоришҳо", "addon.mod_assign.noattempt": "Кӯшиш нест", "addon.mod_assign.notgraded": "Баҳогузорӣ нашудааст", "addon.mod_assign.numberofdraftsubmissions": "Сиёҳнависҳо", @@ -124,6 +150,7 @@ "addon.mod_assign_feedback_file.pluginname": "Тақризи файл", "addon.mod_assign_submission_file.pluginname": "Пешниҳодҳои файл", "addon.mod_book.errorchapter": "Хато ҳангоми хондани боби китоб.", + "addon.mod_book.modulenameplural": "Китобҳо", "addon.mod_chat.beep": "сигнал", "addon.mod_chat.currentusers": "Истифодабарандагони ҳозира", "addon.mod_chat.enterchat": "Ба чат даромадан", @@ -131,12 +158,14 @@ "addon.mod_chat.messagebeepsyou": "{{$a}} ба Шумо сигнал фиристод!", "addon.mod_chat.messageenter": "{{$a}} дар чат намоён шуд", "addon.mod_chat.messageexit": "{{$a}} аз чат рафт", + "addon.mod_chat.modulenameplural": "Чатҳо", "addon.mod_chat.nomessages": "Ҳеҷ як мактубча нест", "addon.mod_chat.send": "Фиристода шавад", "addon.mod_chat.sessionstart": "Сессияи чат сар мешавад: {{$a}}", "addon.mod_chat.talk": "Гуфтугӯ", "addon.mod_choice.expired": "Бубахшед,ин фаъолият маҳкам карда шудааст {{$a}} ва акнун дастрас нест", "addon.mod_choice.full": "(Пур карда шудааст)", + "addon.mod_choice.modulenameplural": "Пурсишҳо", "addon.mod_choice.noresultsviewable": "Шумо ҳозир натиҷаҳои пурсишро аз назар гузаронда наметавонед.", "addon.mod_choice.notopenyet": "Бубахшед, ин амал дастрас нест то даме ки {{$a}}", "addon.mod_choice.removemychoice": "Интихоби ман нест карда шавад", @@ -154,7 +183,9 @@ "addon.mod_data.emptyaddform": "Шумо ҳеҷ як майдонро пур накардед", "addon.mod_data.entrieslefttoadd": "Шумо бояд боз {{$a.entriesleft}}илова кунед, то имкон дошта бошед, ки сабтҳои иштирокчиёни дигарро бинед", "addon.mod_data.fields": "Майдонҳо", + "addon.mod_data.foundrecords": "Сабтҳо ёфт шуданд: {{$a.num}}/{{$a.max}} (
Филтрҳо ба ҳолати пештара баргардонда шаванд)", "addon.mod_data.menuchoose": "Интихоб карда шавад...", + "addon.mod_data.modulenameplural": "Базаҳои маълумотҳо", "addon.mod_data.more": "Дида баромадани сабт", "addon.mod_data.nomatch": "Сабтҳои мувофиқаткунанда ёфт нашуданд!", "addon.mod_data.norecords": "Дар базаи маълумотҳо сабтҳо нетанд", @@ -166,9 +197,11 @@ "addon.mod_feedback.anonymous": "Махфӣ", "addon.mod_feedback.anonymous_entries": "Воридоти махфӣ ({{$a}})", "addon.mod_feedback.average": "Миёна", + "addon.mod_feedback.modulenameplural": "Алоқаи баргарданда", "addon.mod_feedback.preview": "Пешакӣ дида баромадан", "addon.mod_feedback.questions": "Саволҳо", "addon.mod_feedback.started": "саршавӣ", + "addon.mod_folder.modulenameplural": "Папкаҳо", "addon.mod_forum.addanewdiscussion": "Мавзӯъ барои муҳокима илова карда шавад", "addon.mod_forum.addanewquestion": "Саволи нав илова карда шавад", "addon.mod_forum.addanewtopic": "Мавзӯи нав илова карда шавад", @@ -185,6 +218,7 @@ "addon.mod_forum.modeflatnewestfirst": "Ҳамвор, дар пеш навҳо", "addon.mod_forum.modeflatoldestfirst": "Ҳамвор, дар пеш кӯҳнаҳо", "addon.mod_forum.modenested": "Чӯбмонанд", + "addon.mod_forum.modulenameplural": "Форумҳо", "addon.mod_forum.numreplies": "{{numreplies}} ҷавоб", "addon.mod_forum.posttoforum": "Ба форум фиристода шавад", "addon.mod_forum.re": "Re:", @@ -204,6 +238,8 @@ "addon.mod_glossary.entryusedynalink": "Ин сабт бояд ба табри автоматӣ пайваст шавад", "addon.mod_glossary.fillfields": "Калима ва маънидод - майдонҳои ҳатмӣ мебошанд.", "addon.mod_glossary.fullmatch": "Мувофиқат танҳо ба калимаҳои пурра муаяйн карда шавад", + "addon.mod_glossary.modulenameplural": "Глоссарийҳо", + "addon.mod_imscp.modulenameplural": "Пакетҳои IMS мӯҳтаво", "addon.mod_lesson.answer": "Ҷавоб", "addon.mod_lesson.attempt": "Кӯшиш: {{$a}}", "addon.mod_lesson.attemptsremaining": "Фақат{{$a}} кӯшишатон боқӣ мондааст", @@ -237,6 +273,7 @@ "addon.mod_lesson.lowtime": "Вақти камтарин", "addon.mod_lesson.maximumnumberofattemptsreached": "Шумо ба миқдори минималии кӯшишҳо расидед - ба саҳифаи навбатӣ мегузарем", "addon.mod_lesson.modattemptsnoteacher": "Фақат донишҷӯён метавонанд ҷавобҳои худро тағйир диҳанд.", + "addon.mod_lesson.modulenameplural": "Лексияҳо", "addon.mod_lesson.noanswer": "Ҷавоб ба даст наомадааст", "addon.mod_lesson.nolessonattempts": "Кӯшишҳои аз лексия гузаштан набуданд.", "addon.mod_lesson.notcompleted": "Ба анҷом расонда нашудааст", @@ -269,6 +306,7 @@ "addon.mod_lesson.youranswer": "Ҷавоби Шумо", "addon.mod_lesson.yourcurrentgradeisoutof": "Баҳои ҳозираи Шумо: {{$a.grade}} из {{$a.total}}", "addon.mod_lesson.youshouldview": "Шумо бояд дида бароед ақаллан: {{$a}}", + "addon.mod_page.modulenameplural": "Саҳифаҳо", "addon.mod_quiz.attemptfirst": "Кӯшиши якум", "addon.mod_quiz.attemptlast": "Кӯшиши охирин", "addon.mod_quiz.attemptnumber": "Кӯшиш", @@ -290,6 +328,7 @@ "addon.mod_quiz.grademethod": "Усули баҳодиҳӣ", "addon.mod_quiz.gradesofar": "{{$a.method}}: {{$a.mygrade}} / {{$a.quizgrade}}.", "addon.mod_quiz.marks": "Баллҳо", + "addon.mod_quiz.modulenameplural": "Тестҳо", "addon.mod_quiz.mustbesubmittedby": "Ин кӯшиш бояд то {{$a}} фиристода шавад.", "addon.mod_quiz.noquestions": "Ҳанӯз ҳеҷ як савол не илова карда нашудааст", "addon.mod_quiz.noreviewattempt": "Ба Шумо дида баромадани ин кӯшиш иҷозат дода нашудааст.", @@ -328,6 +367,7 @@ "addon.mod_quiz.timeleft": "Вақти боқимонда", "addon.mod_quiz.timetaken": "Вақт гузашт", "addon.mod_quiz.yourfinalgradeis": "Баҳои ҷамъбастии Шумо барои ин тест: {{$a}}", + "addon.mod_resource.modulenameplural": "Файлҳо", "addon.mod_scorm.attempts": "Кӯшишҳо", "addon.mod_scorm.averageattempt": "Миёнаи кӯшишҳо", "addon.mod_scorm.browse": "Пешакӣ дида баромадан", @@ -347,6 +387,7 @@ "addon.mod_scorm.incomplete": "Анҷом наёфтааст", "addon.mod_scorm.lastattempt": "Кӯшиши охирин", "addon.mod_scorm.mode": "Реҷа", + "addon.mod_scorm.modulenameplural": "Пакетҳои SCORM", "addon.mod_scorm.newattempt": "Кӯшиши нав сар карда шавад", "addon.mod_scorm.noattemptsallowed": "Миқдори кӯшишҳо", "addon.mod_scorm.noattemptsmade": "Кӯшишҳои иҷрошуда", @@ -357,11 +398,14 @@ "addon.mod_scorm.suspended": "Боздошта шудааст", "addon.mod_survey.ifoundthat": "Ман фаҳмидам, ки", "addon.mod_survey.ipreferthat": "Ман афзал медонам", + "addon.mod_survey.modulenameplural": "Анкетаҳо", "addon.mod_survey.responses": "Ҷавобҳо", "addon.mod_survey.results": "Натиҷаҳо", + "addon.mod_url.modulenameplural": "Гиперистинод", "addon.mod_wiki.createpage": "Саҳифа бунёд карда шавад", "addon.mod_wiki.editingpage": "Тафсир кардани саҳифаи \"{{$a}}\"", "addon.mod_wiki.map": "Харита", + "addon.mod_wiki.modulenameplural": "Wiki саҳифаҳо", "addon.mod_wiki.newpagetitle": "Сарлавҳаи саҳифаи нав", "addon.mod_wiki.nocontent": "Ин саҳифа мӯҳтаво надорад", "addon.mod_wiki.notingroup": "Берун аз гурӯҳ", @@ -391,6 +435,7 @@ "addon.mod_workshop.gradinggradecalculated": "Баҳои ҳисобкардашуда барои баҳодиҳӣ", "addon.mod_workshop.gradinggradeof": "Баллҳо барои баҳодиҳӣ (аз {{$a}})", "addon.mod_workshop.gradinggradeover": "Баллҳо барои баҳодиҳӣ аз нав муайян карда шаванд", + "addon.mod_workshop.modulenameplural": "Семинарҳо", "addon.mod_workshop.nogradeyet": "Боз баҳо дода нашудааст", "addon.mod_workshop.notassessed": "Ҳанӯз баҳо доданашуда", "addon.mod_workshop.notoverridden": "Аз нав муайян карда нашудааст", @@ -714,6 +759,8 @@ "core.accounts": "Сабтҳои баҳисобгирӣ", "core.add": "Илова карда шавад", "core.agelocationverification": "Тафтиши сол ва мавқеъ", + "core.ago": "{{$a}} ба ақиб", + "core.all": "Ҳама", "core.allparticipants": "Ҳамаи иштирокчиён", "core.answer": "Ҷавоб", "core.answered": "Ҷавоб дода шудааст", @@ -752,22 +799,14 @@ "core.courses.allowguests": "Курс ба меҳмон дастрас аст", "core.courses.availablecourses": "Курсҳои дастрас", "core.courses.categories": "Категорияҳои курсҳо", - "core.courses.courseoverview": "Баррасии курс", "core.courses.courses": "Курсҳо", "core.courses.frontpage": "Саҳифаи асосӣ", - "core.courses.future": "Оянда", - "core.courses.inprogress": "Дар раванд", - "core.courses.morecourses": "Курсҳои дигар", "core.courses.mycourses": "Курсҳои ман", + "core.courses.mymoodle": "Moodle -и ман", "core.courses.nocourses": "Барои инъикос кардан иттилоот нест.", - "core.courses.nocoursesfuture": "Курсҳои оянда нестанд", - "core.courses.nocoursesinprogress": "Ягон пешравӣ дар ин курсҳо нестанд", - "core.courses.nocoursesoverview": "Курсҳо нестанд", - "core.courses.nocoursespast": "Курсҳои гузашта, нестанд", "core.courses.nocoursesyet": "Дар ин категория курсҳо нестанд", "core.courses.nosearchresults": "Натиҷаҳо нестанд", "core.courses.notenroled": "Шумо ба ин курс номнависӣ карда нашудаед", - "core.courses.past": "Гузашта", "core.courses.paymentrequired": "Барои бақайдгирӣ дар ин курс пул пардохт намудан лозим аст", "core.courses.search": "Ёфта шавад", "core.courses.searchcourses": "Ҷустуҷӯи курс", @@ -829,6 +868,7 @@ "core.login.connecttomoodle": "Ба Moodle пайваст шавед", "core.login.createaccount": "Маҳфуз дошта шавад", "core.login.createuserandpass": "Номи истифодабаранда ва калимаи раҳкушоро интихоб намоед", + "core.login.emailconfirmsent": "Ба адреси почтаи электронии ({{$a}}) зикркардаи Шумо нома бо дастурҳои оддӣ барои анҷом додани бақайдгирӣ фиристода шудааст. Агар дар мавриди бақайдгирӣ мушкилот ба миён оянд, бо администратори сомона робита намоед.", "core.login.emailnotmatch": "Нишониҳои почтаи электронӣ мувофиқат намекунанд.", "core.login.enterthewordsabove": "Каломаҳоеро, ки дар боло мебинед, нависед", "core.login.firsttime": "Шумо бори аввал дар сомонаи мо ҳастед?", @@ -836,7 +876,7 @@ "core.login.getanothercaptcha": "Ба даст овардани CAPTCHA -и дигар (матн барои фарқ кунондани одамон ва компютерҳо)", "core.login.help": "Маълумотнома", "core.login.instructions": "Дастурҳо", - "core.login.invaliddate": "Санаи нодуруст", + "core.login.invaliddate": "Таърихи нодурусти рӯз", "core.login.invalidemail": "Формати нодурусти адреси почтаи электронӣ", "core.login.invalidtime": "Вақти нодуруст", "core.login.login": "Ворид шудан", @@ -845,6 +885,7 @@ "core.login.missingemail": "Майдонро пур кунед", "core.login.missingfirstname": "Майдонро пур кунед", "core.login.missinglastname": "Майдонро пур кунед", + "core.login.mustconfirm": "Сабти баҳисобгириро тасдиқ кунед", "core.login.newaccount": "Сабти баҳисобгирии нав", "core.login.notloggedin": "Шумо бояд ворид шавед.", "core.login.password": "Ниҳонвожа", @@ -873,7 +914,6 @@ "core.mainmenu.changesite": "Ивази сомона", "core.mainmenu.help": "Маълумотнома", "core.mainmenu.logout": "Баромадан", - "core.mainmenu.mycourses": "Курсҳои ман", "core.mainmenu.website": "Сомона", "core.maxsizeandattachments": "Андозаи максималии файлҳои нав: {{$a.size}}, миқдори максималии файлҳои вобастакардашуда: {{$a.attachments}}", "core.min": "Дақ.", @@ -939,7 +979,9 @@ "core.question.questionno": "Саволи {{$a}}", "core.question.requiresgrading": "Баҳодиҳӣ талаб карда мешавад", "core.refresh": "Навсозӣ карда шавад", + "core.remove": "Нест карда шавад", "core.required": "Пур кардан лозим аст", + "core.resources": "Захираҳо", "core.restore": "Барқарор карда шавад", "core.save": "Сабт намудан", "core.search": "Ёфта шавад", @@ -984,6 +1026,17 @@ "core.sizekb": "Кбайт", "core.sizemb": "Мбайт", "core.sortby": "Ба навъҳо ҷудо карда шавад аз рӯи", + "core.strftimedate": "%d %B %Y", + "core.strftimedateshort": "%d %B", + "core.strftimedatetime": "%d %B %Y, %H:%M", + "core.strftimedaydate": "%A %d %B %Y", + "core.strftimedaydatetime": "%A %d %B %Y, %H:%M", + "core.strftimedayshort": "%A %d %B", + "core.strftimedaytime": "%a, %H:%M", + "core.strftimemonthyear": "%B %Y", + "core.strftimerecent": "%d %b %H:%M", + "core.strftimerecentfull": "%a %d %b %Y, %H:%M", + "core.strftimetime": "%H:%M", "core.submit": "Равона карда шавад", "core.success": "Бо муваффақият", "core.teachers": "Муаллимон", @@ -1001,12 +1054,13 @@ "core.user.country": "Кишвар", "core.user.description": "Тавсиф", "core.user.details": "Тафсилот", + "core.user.editingteacher": "Муаллим", "core.user.email": "Адреси почтаи электронӣ", "core.user.emailagain": "Адреси почтаи электронӣ (бори дигар)", "core.user.firstname": "Ном", "core.user.interests": "Шавқмандиҳо", "core.user.lastname": "Фамилия", - "core.user.manager": "Мудир", + "core.user.manager": "Идоракунанда", "core.user.newpicture": "Тасвири нав", "core.user.noparticipants": "Барои ин курс иштирокчиён ёфт нашуданд.", "core.user.participants": "Иштирокчиён", @@ -1015,6 +1069,7 @@ "core.user.roles": "Нақшҳо", "core.user.sendemail": "Почтаи электронӣ", "core.user.student": "Донишҷӯ", + "core.user.teacher": "Ассистент (бе ҳуқуқи таҳрир)", "core.user.webpage": "Веб-саҳифа", "core.userdeleted": "Сабти баҳисобгирии истифодабаранда нест карда шудааст", "core.userdetails": "Иттилооти муфассал оиди истифодабаранда", diff --git a/src/assets/lang/tr.json b/src/assets/lang/tr.json index 6971a1669..e6ad96a4c 100644 --- a/src/assets/lang/tr.json +++ b/src/assets/lang/tr.json @@ -8,8 +8,18 @@ "addon.badges.issuancedetails": "Rozet sona erme", "addon.badges.issuerdetails": "çıkaran ayrıntıları", "addon.badges.issuername": "Çıkaranın adı", + "addon.badges.issuerurl": "Çıkaran URL", "addon.badges.nobadges": "Uygun nişan bulunmuyor.", "addon.badges.recipientdetails": "Alıcı bilgileri", + "addon.badges.warnexpired": "(Bu nişan zaman aşımına uğramış!)", + "addon.block_activitymodules.pluginname": "Etkinlikler", + "addon.block_myoverview.future": "Gelecek", + "addon.block_myoverview.inprogress": "Devam eden", + "addon.block_myoverview.morecourses": "Daha fazla ders", + "addon.block_myoverview.nocourses": "Ders yok", + "addon.block_myoverview.past": "Geçmiş", + "addon.block_myoverview.pluginname": "Derslere genel bakış", + "addon.block_sitemainmenu.pluginname": "Ana menü", "addon.calendar.calendar": "Takvim", "addon.calendar.eventendtime": "Bitiş süresi", "addon.calendar.eventstarttime": "Başlangıç zamanı", @@ -72,37 +82,50 @@ "addon.coursecompletion.complete": "Tamamla", "addon.coursecompletion.completecourse": "Dersi tamamla", "addon.coursecompletion.completed": "Tamamlandı", - "addon.coursecompletion.completiondate": "Tamamlanma tarihi", + "addon.coursecompletion.completiondate": "Tamamlama raporu", "addon.coursecompletion.completionmenuitem": "Tamamlama", - "addon.coursecompletion.coursecompletion": "Ders tamamlama", - "addon.coursecompletion.criteria": "Kriter", - "addon.coursecompletion.criteriagroup": "Kriter grup", + "addon.coursecompletion.coursecompletion": "Kurs tamamlama", + "addon.coursecompletion.criteria": "Ölçüt", + "addon.coursecompletion.criteriagroup": "Ölçüt Grubu", + "addon.coursecompletion.criteriarequiredall": "Aşağıdaki ölçütlerin tümü gereklidir", + "addon.coursecompletion.criteriarequiredany": "Aşağıdaki herhangi bir kriter gereklidir", + "addon.coursecompletion.inprogress": "Devam ediyor", + "addon.coursecompletion.manualselfcompletion": "Kendi kendine elle tamamlama", "addon.coursecompletion.notyetstarted": "Henüz başlamadı", + "addon.coursecompletion.pending": "Bekliyor", + "addon.coursecompletion.required": "Gerekli", + "addon.coursecompletion.requiredcriteria": "Gerekli Ölçüt", + "addon.coursecompletion.requirement": "Gereklilikler", "addon.coursecompletion.status": "Durum", - "addon.coursecompletion.viewcoursereport": "Ders raporunu incele", + "addon.coursecompletion.viewcoursereport": "Kurs raporunu görüntüle", "addon.files.emptyfilelist": "Gösterilecek dosya yok", "addon.files.files": "Dosyalar", "addon.files.privatefiles": "Kişisel dosyalar", "addon.files.sitefiles": "Site dosyaları", "addon.messages.addcontact": "Kişi ekle", - "addon.messages.blockcontact": "Kişiyi engelle", + "addon.messages.addtoyourcontacts": "İletişimlerinize ekleyin", "addon.messages.blocknoncontacts": "Tanımadığım kullanıcıları engelle", + "addon.messages.contactblocked": "İletişim engellendi", "addon.messages.contacts": "Kişiler", + "addon.messages.deleteallconfirm": "Tüm bu sohbeti silmek istediğinizden emin misiniz?", "addon.messages.message": "Mesaj", "addon.messages.messagenotsent": "İleti gönderilemedi. Lütfen daha sonra tekrar deneyin.", "addon.messages.messagepreferences": "İleti tercihleri", "addon.messages.messages": "Mesajlar", "addon.messages.newmessage": "Yeni ileti", "addon.messages.newmessages": "Yeni ileti", - "addon.messages.nomessages": "Yeni ileti yok", + "addon.messages.nomessagesfound": "Mesaj yok", + "addon.messages.noncontacts": "İletişim kurulamadı", "addon.messages.nousersfound": "Kullanıcı bulunamadı", "addon.messages.removecontact": "Kişiyi sil", + "addon.messages.removefromyourcontacts": "İletişimlerden kaldır", + "addon.messages.searchcombined": "İnsan ve mesajları ara", "addon.messages.type_blocked": "Engellendi", "addon.messages.type_offline": "Çevrimdışı", "addon.messages.type_online": "Çevrimiçi", "addon.messages.type_search": "Arama sonuçları", "addon.messages.type_strangers": "Diğer", - "addon.messages.unblockcontact": "Engeli kaldır", + "addon.messages.you": "Sen:", "addon.mod_assign.addattempt": "Yeniden denemeye izin ver", "addon.mod_assign.addnewattempt": "Yeni bir deneme ekle", "addon.mod_assign.addnewattemptfromprevious": "Önceki gönderim üzerinden yeni bir denemeye izin ver", @@ -133,6 +156,7 @@ "addon.mod_assign.graded": "Notlandırıldı", "addon.mod_assign.gradedby": "", "addon.mod_assign.gradedon": "Not verildi", + "addon.mod_assign.gradelocked": "Bu not kilitli veya not defterinde geçersiz kılınmıştır.", "addon.mod_assign.gradeoutof": "{{$a}} Dışarıdan notu", "addon.mod_assign.gradingstatus": "Puan durumu", "addon.mod_assign.groupsubmissionsettings": "Grup gönderimleri ayarları", @@ -146,6 +170,7 @@ "addon.mod_assign.markingworkflowstatereadyforrelease": "Yayımlamak için hazır", "addon.mod_assign.markingworkflowstatereadyforreview": "İşaretleme tamamlandı", "addon.mod_assign.markingworkflowstatereleased": "Yayımlandı", + "addon.mod_assign.modulenameplural": "Ödevler", "addon.mod_assign.multipleteams": "Birden fazla grubun üyesi", "addon.mod_assign.noattempt": "Deneme yok", "addon.mod_assign.nomoresubmissionsaccepted": "Artık gönderim kabul edilmiyor", @@ -193,6 +218,7 @@ "addon.mod_assign_submission_file.pluginname": "Dosya gönderimleri", "addon.mod_assign_submission_onlinetext.pluginname": "Çevrimiçi metin gönderimleri", "addon.mod_book.errorchapter": "Kitabın bölümünü okurken hata oluştu.", + "addon.mod_book.modulenameplural": "Kitaplar", "addon.mod_chat.beep": "biip", "addon.mod_chat.currentusers": "Aktif kullanıcılar", "addon.mod_chat.enterchat": "Şimdi sohbete katıl", @@ -200,6 +226,7 @@ "addon.mod_chat.messagebeepsyou": "{{$a}} size sesleniyor!", "addon.mod_chat.messageenter": "{{$a}} odaya girdi", "addon.mod_chat.messageexit": "{{$a}} odadan ayrıldı", + "addon.mod_chat.modulenameplural": "Sohbetler", "addon.mod_chat.nomessages": "Henüz mesaj yok", "addon.mod_chat.send": "Gönder", "addon.mod_chat.sessionstart": "Bir sonraki görüşme oturumu {{$a.date}}, ({{$a.fromnow}} şu andan itibaren)", @@ -208,6 +235,7 @@ "addon.mod_choice.choiceoptions": "Seçim seçenekleri", "addon.mod_choice.expired": "Üzgünüz, bu etkinlik {{$a}} tarihinde kapandı ve bu etkinliğe artık ulaşılamaz", "addon.mod_choice.full": "(Dolu)", + "addon.mod_choice.modulenameplural": "Anketler (Mini)", "addon.mod_choice.noresultsviewable": "Sonuçlar şu anda görüntülenmemektedir.", "addon.mod_choice.notopenyet": "Üzgünüz, bu etkinliğe {{$a}} tarihine kadar ulaşılamaz", "addon.mod_choice.numberofuser": "Yanıt sayısı", @@ -236,8 +264,10 @@ "addon.mod_data.errormustsupplyvalue": "Burada bir değer vermelisiniz.", "addon.mod_data.expired": "Maalesef, bu etkinlik {{$a}} tarihinde kapandı ve artık mevcut değil", "addon.mod_data.fields": "Alanlar", + "addon.mod_data.foundrecords": "Bulunan kayıtlar: {{$a.num}}/{{$a.max}} (Filtreleri temizle)", "addon.mod_data.latlongboth": "Enlem ve boylam gereklidir.", "addon.mod_data.menuchoose": "Seç...", + "addon.mod_data.modulenameplural": "Veritabanları", "addon.mod_data.more": "Dahası", "addon.mod_data.nomatch": "Eşleşen kayıt bulunamadı!", "addon.mod_data.norecords": "Veritabanında kayıt yok", @@ -267,6 +297,7 @@ "addon.mod_feedback.feedbackopen": "Şunlardan gelen cevaplara izin ver:", "addon.mod_feedback.mapcourses": "Geribildirimi derslere eşleştirin", "addon.mod_feedback.mode": "Mod", + "addon.mod_feedback.modulenameplural": "Anket (Geribildirim)", "addon.mod_feedback.next_page": "Sonraki sayfa", "addon.mod_feedback.non_anonymous": "Kullanıcıların adları kaydedilecek ve cevaplarıyla birlikte gösterilecek", "addon.mod_feedback.non_anonymous_entries": "anonim olmayan kayıtlar({{$a}})", @@ -287,6 +318,7 @@ "addon.mod_feedback.started": "Başlatıldı", "addon.mod_feedback.this_feedback_is_already_submitted": "Bu etkinliği zaten doldurdunuz.", "addon.mod_folder.emptyfilelist": "Gösterilecek dosya yok", + "addon.mod_folder.modulenameplural": "Klasörler", "addon.mod_forum.addanewdiscussion": "Yeni tartışma konusu ekle", "addon.mod_forum.addanewquestion": "Yeni soru ekle", "addon.mod_forum.addanewtopic": "Yeni konu ekle", @@ -307,6 +339,7 @@ "addon.mod_forum.modeflatnewestfirst": "Yanıtları yeniler önce olacak şekilde düz göster", "addon.mod_forum.modeflatoldestfirst": "Yanıtları eskiler önce olacak şekilde düz göster", "addon.mod_forum.modenested": "Yanıtları içiçe göster", + "addon.mod_forum.modulenameplural": "Forumlar", "addon.mod_forum.posttoforum": "Foruma gönder", "addon.mod_forum.re": "Ynt:", "addon.mod_forum.reply": "Yanıtla", @@ -327,8 +360,10 @@ "addon.mod_glossary.fillfields": "Kavram ve tanımlamalar gerekli alanlardır.", "addon.mod_glossary.fullmatch": "Sözcüklerin tamamını eşleştir", "addon.mod_glossary.linking": "Otomatik-linkle", + "addon.mod_glossary.modulenameplural": "Sözlükler", "addon.mod_glossary.noentriesfound": "Her hangi bir kayıt bulunamadı", "addon.mod_imscp.deploymenterror": "İçerik paketi hatası!", + "addon.mod_imscp.modulenameplural": "IMS İçerik paketleri", "addon.mod_imscp.showmoduledescription": "Açıklamayı göster", "addon.mod_lesson.answer": "Cevap", "addon.mod_lesson.attempt": "Uygulama: {{$a}}", @@ -369,6 +404,7 @@ "addon.mod_lesson.lowtime": "Düşük zaman", "addon.mod_lesson.maximumnumberofattemptsreached": "En fazla uygulama sayısına ulaşıldı - Sonraki sayfaya gidiliyor", "addon.mod_lesson.modattemptsnoteacher": "Öğrenci değerlendirmesi sadece öğrenciler için çalışır.", + "addon.mod_lesson.modulenameplural": "Dersler", "addon.mod_lesson.noanswer": "Bir veya daha fazla soruya cevap verilmedi. Lütfen geri dönün ve bir cevap verin.", "addon.mod_lesson.nolessonattempts": "Bu derste uygulama yapılmadı.", "addon.mod_lesson.nolessonattemptsgroup": "Bu derste {{$a}} grup üyeleri tarafından hiçbir deneme yapılmadı.", @@ -406,6 +442,7 @@ "addon.mod_lesson.youranswer": "Cevabınız", "addon.mod_lesson.yourcurrentgradeisoutof": "Şu anki notunuz {{$a.total}} üzerinden {{$a.grade}} dir.", "addon.mod_lesson.youshouldview": "En az cevaplamanız gereken: {{$a}}", + "addon.mod_page.modulenameplural": "Sayfalar", "addon.mod_quiz.attemptfirst": "İlk uygulama", "addon.mod_quiz.attemptlast": "Son uygulama", "addon.mod_quiz.attemptnumber": "Uygulama", @@ -427,6 +464,7 @@ "addon.mod_quiz.grademethod": "Notlandırma yöntemi", "addon.mod_quiz.gradesofar": "{{$a.method}}: {{$a.mygrade}} / {{$a.quizgrade}}.", "addon.mod_quiz.marks": "Puanlar", + "addon.mod_quiz.modulenameplural": "Sınavlar", "addon.mod_quiz.mustbesubmittedby": "Bu uygulama {{$a}} tarafından gönderilmelidir.", "addon.mod_quiz.noquestions": "Henüz bir soru eklenmemiş", "addon.mod_quiz.noreviewattempt": "Bu uygulamayı gözden geçirmek için izniniz yok.", @@ -466,6 +504,7 @@ "addon.mod_quiz.timetaken": "Geçen süre", "addon.mod_quiz.yourfinalgradeis": "Bu sınav için final notunuz: {{$a}}.", "addon.mod_resource.modifieddate": "Değiştirilmiş {{$a}}", + "addon.mod_resource.modulenameplural": "Kaynaklar", "addon.mod_resource.openthefile": "Dosyayı aç", "addon.mod_resource.uploadeddate": "{{$a}} öğesine yüklendi", "addon.mod_scorm.asset": "Öge", @@ -492,6 +531,7 @@ "addon.mod_scorm.incomplete": "Tamamlanmadı", "addon.mod_scorm.lastattempt": "Son uygulama", "addon.mod_scorm.mode": "Mod", + "addon.mod_scorm.modulenameplural": "SCORM/AICC", "addon.mod_scorm.newattempt": "Yeni bir uygulama başlat", "addon.mod_scorm.noattemptsallowed": "İzin verilen uygulama sayısı", "addon.mod_scorm.noattemptsmade": "Yaptığınız uygulama sayısı", @@ -503,12 +543,15 @@ "addon.mod_scorm.suspended": "Durduruldu", "addon.mod_survey.ifoundthat": "Gerçekte olan", "addon.mod_survey.ipreferthat": "İstediğim", + "addon.mod_survey.modulenameplural": "Anketler (Hazır ölçekli)", "addon.mod_survey.responses": "Yanıtlar", "addon.mod_survey.surveycompletednograph": "Bu anketi tamamladınız.", + "addon.mod_url.modulenameplural": "Adresler", "addon.mod_wiki.cannoteditpage": "Bu sayfayı düzenleyemezsiniz.", "addon.mod_wiki.createpage": "Sayfa oluştur", "addon.mod_wiki.editingpage": "Bu sayfayı düzenleme '{{$a}}'", "addon.mod_wiki.map": "Harita", + "addon.mod_wiki.modulenameplural": "Wikiler", "addon.mod_wiki.newpagehdr": "Yeni sayfa", "addon.mod_wiki.newpagetitle": "Yeni sayfa başlığı", "addon.mod_wiki.nocontent": "Bu sayfa için içerik yok", @@ -542,6 +585,7 @@ "addon.mod_workshop.gradinggradecalculated": "Değerlendirme için hesaplanan not", "addon.mod_workshop.gradinggradeof": "Değerlendirme için ({{$a}}) notu", "addon.mod_workshop.gradinggradeover": "Değerlendirme için notu iptal et", + "addon.mod_workshop.modulenameplural": "Çalıştaylar", "addon.mod_workshop.nogradeyet": "Henüz bir not yok", "addon.mod_workshop.notassessed": "Henüz değerlendirilmedi", "addon.mod_workshop.notoverridden": "Geçersiz kılınmadı", @@ -865,6 +909,8 @@ "assets.mimetypes.video": "Video dosyası ({{$a.EXT}})", "core.accounts": "Hesaplar", "core.add": "Ekle", + "core.ago": "{{$a}} önce", + "core.all": "Tümü", "core.allparticipants": "Bütün katılımcılar", "core.android": "Android", "core.answer": "Yanıt", @@ -904,22 +950,14 @@ "core.courses.allowguests": "Bu derse konuk olarak girilebilir", "core.courses.availablecourses": "Mevcut dersler", "core.courses.categories": "Ders Kategorileri", - "core.courses.courseoverview": "Derse genel bakış", "core.courses.courses": "Dersler", "core.courses.frontpage": "Ön sayfa", - "core.courses.future": "Gelecek", - "core.courses.inprogress": "Devam eden", - "core.courses.morecourses": "Daha fazla ders", "core.courses.mycourses": "Derslerim", + "core.courses.mymoodle": "Kontrol paneli", "core.courses.nocourses": "Gösterilecek ders bilgisi yok", - "core.courses.nocoursesfuture": "Gelecek ders yok", - "core.courses.nocoursesinprogress": "Devam eden ders yok", - "core.courses.nocoursesoverview": "Ders yok", - "core.courses.nocoursespast": "Geçmiş ders yok", "core.courses.nocoursesyet": "Bu kategoride ders yok", "core.courses.nosearchresults": "Sonuç yok", "core.courses.notenroled": "Bu derse kayıtlı değilsiniz", - "core.courses.past": "Geçmiş", "core.courses.paymentrequired": "Bu derse giriş için ödeme yapmanız gerekir.", "core.courses.paypalaccepted": "PayPal ödemeleri kabul edilir", "core.courses.search": "Ara", @@ -993,6 +1031,7 @@ "core.login.cancel": "İptal", "core.login.createaccount": "Yeni hesabımı oluştur", "core.login.createuserandpass": "Kullanıcı adınızı ve şifrenizi seçin", + "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.emailnotmatch": "E-postalar uyuymuyor", "core.login.enterthewordsabove": "Yukarıdaki sözcükleri yazın", "core.login.firsttime": "Buraya ilk defa mı geliyorsunuz?", @@ -1018,6 +1057,7 @@ "core.login.missingfirstname": "Adı eksik", "core.login.missinglastname": "Soyadı eksik", "core.login.mobileservicesnotenabled": "Mobil Hizmetler sitenizde etkin değildir. Mobil erişimin etkin olduğunu düşünüyorsanız, Lütfen Moodle site yöneticinize başvurun.", + "core.login.mustconfirm": "Girişinizi onaylamalısınız", "core.login.newaccount": "Yeni hesap", "core.login.newsitedescription": "Lütfen moodle sitenizin URL'sin giriniz. Sitenizin bu uygulama ile çalışacak şekilde yapılandırılmamış olabileceğini de göz önünde bulundurun.", "core.login.notloggedin": "Giriş yapmanız gerekiyor.", @@ -1051,7 +1091,6 @@ "core.mainmenu.changesite": "Siteyi değiştir", "core.mainmenu.help": "Yardım", "core.mainmenu.logout": "Çıkış yap", - "core.mainmenu.mycourses": "Derslerim", "core.mainmenu.website": "Websitesi", "core.maxsizeandattachments": "Yeni dosyalar için en büyük boyut: {{$a.size}}, en fazla ek: {{$a.attachments}}", "core.min": "dk", @@ -1060,7 +1099,7 @@ "core.mod_assignment": "Ödev (2.2)", "core.mod_book": "Kitap", "core.mod_chat": "Sohbet", - "core.mod_choice": "Anket", + "core.mod_choice": "Anket (Mini)", "core.mod_data": "Veritabanı", "core.mod_database": "Veritabanı", "core.mod_feedback": "Geribildirim", @@ -1076,7 +1115,7 @@ "core.mod_quiz": "Sınav", "core.mod_resource": "Kaynak", "core.mod_scorm": "SCORM paketi", - "core.mod_survey": "Anket", + "core.mod_survey": "Anket (Hazır ölçekli)", "core.mod_url": "URL", "core.mod_wiki": "Wiki", "core.mod_workshop": "Çalıştay", @@ -1121,7 +1160,9 @@ "core.question.questionno": "Soru {{$a}}", "core.question.requiresgrading": "Notlandırma gerekir", "core.refresh": "Yenile", + "core.remove": "Kaldır", "core.required": "Gerekli", + "core.resources": "Kaynaklar", "core.restore": "Geri yükle", "core.search": "Ara", "core.searching": "Aranıyor", @@ -1174,6 +1215,19 @@ "core.sizemb": "MB", "core.sorry": "Üzgünüz...", "core.sortby": "Sıralama ölçütü", + "core.strftimedate": "%d %B %Y", + "core.strftimedatefullshort": "%d/%m/%y", + "core.strftimedateshort": "%d %B", + "core.strftimedatetime": "%d %B %Y, %H:%M", + "core.strftimedatetimeshort": "%d/%m/%y, %H:%M", + "core.strftimedaydate": "%d %B %Y, %A", + "core.strftimedaydatetime": "%d %B %Y, %A, %H:%M", + "core.strftimedayshort": "%A, %d %B", + "core.strftimedaytime": "%a, %H:%M", + "core.strftimemonthyear": "%B %Y", + "core.strftimerecent": "%d %b, %H:%M", + "core.strftimerecentfull": "%d %b %Y, %a, %H:%M", + "core.strftimetime": "%H:%M", "core.submit": "Gönder", "core.success": "Başarı", "core.tablet": "Tablet", @@ -1193,7 +1247,7 @@ "core.user.description": "Açıklama", "core.user.details": "Ayrıntılar", "core.user.detailsnotavailable": "Bu kişiye ait detayları görünteleyemezsiniz.", - "core.user.editingteacher": "Öğretmen", + "core.user.editingteacher": "Eğitimci", "core.user.email": "E-posta adresi", "core.user.emailagain": "E-posta (tekrar)", "core.user.firstname": "Adı", @@ -1208,7 +1262,7 @@ "core.user.roles": "Roller", "core.user.sendemail": "Eposta", "core.user.student": "Öğrenci", - "core.user.teacher": "Düzenleme yetkisi olmayan öğretmen", + "core.user.teacher": "Düzenleme yapamayan eğitimci", "core.user.webpage": "Web sayfası", "core.userdeleted": "Bu kullanıcı hesabı silindi", "core.userdetails": "Kullanıcı ayrıntıları", diff --git a/src/assets/lang/uk.json b/src/assets/lang/uk.json index 18e717b69..8287ca6f4 100644 --- a/src/assets/lang/uk.json +++ b/src/assets/lang/uk.json @@ -8,8 +8,18 @@ "addon.badges.issuancedetails": "Відзнака не актуальна", "addon.badges.issuerdetails": "Деталі присудження", "addon.badges.issuername": "Ім’я видавця", + "addon.badges.issuerurl": "URL видавця", "addon.badges.nobadges": "Немає доступних відзнак.", "addon.badges.recipientdetails": "Деталі отримувача", + "addon.badges.warnexpired": "(Ця відзнака не актуальна!)", + "addon.block_activitymodules.pluginname": "Види діяльності", + "addon.block_myoverview.future": "Заплановані", + "addon.block_myoverview.inprogress": "В процесі", + "addon.block_myoverview.morecourses": "Більше курсів", + "addon.block_myoverview.nocourses": "Курси відсутні", + "addon.block_myoverview.past": "Минулі", + "addon.block_myoverview.pluginname": "Огляд курсу", + "addon.block_sitemainmenu.pluginname": "Головне меню", "addon.calendar.calendar": "Календар", "addon.calendar.calendarevents": "Події календаря", "addon.calendar.defaultnotificationtime": "Час сповіщень за-замовчуванням", @@ -70,21 +80,21 @@ "addon.competency.xcompetenciesproficientoutofyincourse": "Ви набули {{$a.x}} компетентностей з {{$a.y}} наявних у цьому курсі", "addon.coursecompletion.complete": "Завершити", "addon.coursecompletion.completecourse": "Проходження курсу", - "addon.coursecompletion.completed": "Завершено", - "addon.coursecompletion.completiondate": "Дата завершення", + "addon.coursecompletion.completed": "Виконано", + "addon.coursecompletion.completiondate": "Термін закінчення", "addon.coursecompletion.completionmenuitem": "Відслідковування виконання", "addon.coursecompletion.couldnotloadreport": "Не вдалося завантажити звіт про закінчення курсу, будь ласка, спробуйте ще раз пізніше.", "addon.coursecompletion.coursecompletion": "Завершення курсу", - "addon.coursecompletion.criteria": "Критерія", + "addon.coursecompletion.criteria": "Критерій", "addon.coursecompletion.criteriagroup": "Група критеріїв", - "addon.coursecompletion.criteriarequiredall": "Всі критерії нижче обов'язкові", - "addon.coursecompletion.criteriarequiredany": "Будь-яка критерія нижче обов'язкова", - "addon.coursecompletion.inprogress": "В процесі...", - "addon.coursecompletion.manualselfcompletion": "Самостійне завершення", - "addon.coursecompletion.notyetstarted": "Не розпочато", - "addon.coursecompletion.pending": "В очікуванні", - "addon.coursecompletion.required": "Необхідно", - "addon.coursecompletion.requiredcriteria": "Необхідна критерія", + "addon.coursecompletion.criteriarequiredall": "Потрібна відповідність всім вказаним критеріям", + "addon.coursecompletion.criteriarequiredany": "Потрібна відповідність будь-якому критерію", + "addon.coursecompletion.inprogress": "В процесі", + "addon.coursecompletion.manualselfcompletion": "Самореєстрація завершення", + "addon.coursecompletion.notyetstarted": "Ще не почато", + "addon.coursecompletion.pending": "Очікується", + "addon.coursecompletion.required": "Необхідні", + "addon.coursecompletion.requiredcriteria": "Необхідний критерій", "addon.coursecompletion.requirement": "Вимога", "addon.coursecompletion.status": "Статус", "addon.coursecompletion.viewcoursereport": "Переглянути звіт курсу", @@ -96,8 +106,6 @@ "addon.files.sitefiles": "Файли сайту", "addon.messageoutput_airnotifier.processorsettingsdesc": "Конфігурація пристроїв", "addon.messages.addcontact": "Додати контакт", - "addon.messages.blockcontact": "Заблокувати контакт", - "addon.messages.blockcontactconfirm": "Ви не будете отримувати повідомлення від цього контакту.", "addon.messages.blocknoncontacts": "Запобігти повідомленням, що не в моїх контактах", "addon.messages.contactlistempty": "Список контактів порожній", "addon.messages.contactname": "Ім'я контакту", @@ -110,16 +118,16 @@ "addon.messages.messagenotsent": "Повідомлення не було відправлено, будь ласка, спробуйте ще раз пізніше.", "addon.messages.messages": "Повідомлення", "addon.messages.newmessages": "Нові повідомлення", - "addon.messages.nomessages": "Немає нових повідомлень", + "addon.messages.nomessagesfound": "Повідомлень не знайдено", "addon.messages.nousersfound": "Користувачів не знайдено", "addon.messages.removecontact": "Видалити контакт", "addon.messages.removecontactconfirm": "Контакт буде видалено зі списку контактів.", + "addon.messages.searchcombined": "Шукати людей та повідомлення", "addon.messages.type_blocked": "Заблоковано", "addon.messages.type_offline": "Офлайн", "addon.messages.type_online": "Онлайн", "addon.messages.type_search": "Результати пошуку", "addon.messages.type_strangers": "Інші", - "addon.messages.unblockcontact": "Розблокувати контакт", "addon.messages.warningmessagenotsent": "Неможливо відправити повідомлення до {{user}}. {{error}}", "addon.mod_assign.acceptsubmissionstatement": "Будь ласка, прийміть заяву-згоду.", "addon.mod_assign.addattempt": "Дозволити іншу спробу", @@ -158,6 +166,7 @@ "addon.mod_assign.graded": "Оцінено", "addon.mod_assign.gradedby": "Оцінив", "addon.mod_assign.gradedon": "Оцінено на", + "addon.mod_assign.gradelocked": "Ця оцінка заблокована або змінена вручну в Журналі оцінок", "addon.mod_assign.gradenotsynced": "Оцінки не синхронізовані", "addon.mod_assign.gradeoutof": "Оцінка (макс. {{$a}})", "addon.mod_assign.gradingstatus": "Статус оцінення", @@ -172,6 +181,7 @@ "addon.mod_assign.markingworkflowstatereadyforrelease": "Готово до показу", "addon.mod_assign.markingworkflowstatereadyforreview": "Оцінено", "addon.mod_assign.markingworkflowstatereleased": "Показано", + "addon.mod_assign.modulenameplural": "Завдання", "addon.mod_assign.multipleteams": "Учасник кількох груп", "addon.mod_assign.noattempt": "Немає спроб", "addon.mod_assign.nomoresubmissionsaccepted": "Дозволено лише для учасників, яким продовжено термін виконання", @@ -224,6 +234,7 @@ "addon.mod_assign_submission_file.pluginname": "Завантаження файлу", "addon.mod_assign_submission_onlinetext.pluginname": "Відповідь текстом", "addon.mod_book.errorchapter": "Помилка читання розділу книги", + "addon.mod_book.modulenameplural": "Книги", "addon.mod_chat.beep": "сигнал", "addon.mod_chat.currentusers": "Поточні користувачі", "addon.mod_chat.enterchat": "Увійти в чат", @@ -236,6 +247,7 @@ "addon.mod_chat.messagebeepsyou": "{{$a}} відправив Вам сигнал!", "addon.mod_chat.messageenter": "{{$a}} з'явився в чаті", "addon.mod_chat.messageexit": "{{$a}} пішов із чату", + "addon.mod_chat.modulenameplural": "Чати", "addon.mod_chat.mustbeonlinetosendmessages": "Ви повинні бути онлайн для відправки повідомлення", "addon.mod_chat.nomessages": "Ще немає повідомлень", "addon.mod_chat.send": "Відіслати", @@ -246,6 +258,7 @@ "addon.mod_choice.errorgetchoice": "Помилка при отриманні даних виборки.", "addon.mod_choice.expired": "На жаль, ця діяльність закрита для {{$a}} та більше недоступна", "addon.mod_choice.full": "(Все)", + "addon.mod_choice.modulenameplural": "Вибори", "addon.mod_choice.noresultsviewable": "В даний час результати не можна побачити.", "addon.mod_choice.notopenyet": "Цей ресурс буде недоступний до {{$a}}", "addon.mod_choice.numberofuser": "Кількість користувачів", @@ -276,8 +289,10 @@ "addon.mod_data.errormustsupplyvalue": "Ви повинні тут вказати значення.", "addon.mod_data.expired": "На жаль, ця діяльність закрита на {{$a}} і більше не доступна", "addon.mod_data.fields": "Поля", + "addon.mod_data.foundrecords": "Знайдено записи: {{$a.num}}/{{$a.max}} (Перевстановити фільтр)", "addon.mod_data.latlongboth": "Широта і довгота є обов’язковими.", "addon.mod_data.menuchoose": "Вибрати...", + "addon.mod_data.modulenameplural": "Бази даних", "addon.mod_data.more": "Детальний перегляд...", "addon.mod_data.nomatch": "Жодного запису не знайдено!", "addon.mod_data.norecords": "Немає записів у базі даних", @@ -309,6 +324,7 @@ "addon.mod_feedback.feedbackopen": "Відкрити зворотний зв’язок до", "addon.mod_feedback.mapcourses": "Прив’язка зворотного зв’язку до курсів", "addon.mod_feedback.mode": "Режим", + "addon.mod_feedback.modulenameplural": "Зворотний зв’язок", "addon.mod_feedback.next_page": "Наступна сторінка", "addon.mod_feedback.non_anonymous": "Ім'я користувача буде записане та показане з відповідями", "addon.mod_feedback.non_anonymous_entries": "немає анонімних відповідей", @@ -329,6 +345,7 @@ "addon.mod_feedback.started": "почато", "addon.mod_feedback.this_feedback_is_already_submitted": "Ви вже пройшли цю діяльність", "addon.mod_folder.emptyfilelist": "Немає файлів для показу", + "addon.mod_folder.modulenameplural": "Теки", "addon.mod_forum.addanewdiscussion": "Додати тему для обговорення", "addon.mod_forum.addanewquestion": "Додати нове питання", "addon.mod_forum.addanewtopic": "Додати нову тему", @@ -350,6 +367,7 @@ "addon.mod_forum.modeflatnewestfirst": "Показувати відповіді простим списком, найновіші спочатку", "addon.mod_forum.modeflatoldestfirst": "Показувати відповіді простим списком, найстаріші спочатку", "addon.mod_forum.modenested": "Показувати відповіді у формі вкладених повідомлень", + "addon.mod_forum.modulenameplural": "Форуми", "addon.mod_forum.numdiscussions": "{{numdiscussions}} дискусій", "addon.mod_forum.numreplies": "{{numreplies}} відповідей", "addon.mod_forum.posttoforum": "Надіслати до форуму", @@ -385,9 +403,11 @@ "addon.mod_glossary.fillfields": "Поняття та визначення - поля обов’язкові.", "addon.mod_glossary.fullmatch": "Визначати відповідність тільки повним словам", "addon.mod_glossary.linking": "Автозв'язування", + "addon.mod_glossary.modulenameplural": "Глосарії", "addon.mod_glossary.noentriesfound": "Немає записів", "addon.mod_glossary.searchquery": "Пошуковий запит", "addon.mod_imscp.deploymenterror": "Помилка пакування вмісту!", + "addon.mod_imscp.modulenameplural": "IMS контент пакети", "addon.mod_imscp.showmoduledescription": "Показати опис", "addon.mod_lesson.answer": "Відповідь", "addon.mod_lesson.attempt": "Спроба: {{$a}}", @@ -430,6 +450,7 @@ "addon.mod_lesson.lowtime": "Менший час", "addon.mod_lesson.maximumnumberofattemptsreached": "Досягнуто максимальної кількості спроб - Перехід до наступної сторінки", "addon.mod_lesson.modattemptsnoteacher": "Перегляд студентів доступний лише для студентів.", + "addon.mod_lesson.modulenameplural": "Уроки", "addon.mod_lesson.noanswer": "Одне або більше питань залишилися без відповіді. Будь ласка, поверніться і дайте відповідь.", "addon.mod_lesson.nolessonattempts": "спроби проходження цього уроку не знайдено.", "addon.mod_lesson.nolessonattemptsgroup": "Студенти групи {{$a}} не зробили жодної спроби проходження уроку.", @@ -474,7 +495,9 @@ "addon.mod_lti.errorgetlti": "Помилка при отриманні даних модуля.", "addon.mod_lti.errorinvalidlaunchurl": "Цей URL не є дійсним.", "addon.mod_lti.launchactivity": "Запуск діяльності", + "addon.mod_lti.modulenameplural": "Зовнішні засоби", "addon.mod_page.errorwhileloadingthepage": "Помилка при завантаженні вмісту сторінки.", + "addon.mod_page.modulenameplural": "Сторінки", "addon.mod_quiz.attemptfirst": "Перша спроба", "addon.mod_quiz.attemptlast": "Остання спроба", "addon.mod_quiz.attemptnumber": "Спроба", @@ -509,6 +532,7 @@ "addon.mod_quiz.grademethod": "Метод оцінювання", "addon.mod_quiz.gradesofar": "{{$a.method}}: {{$a.mygrade}} / {{$a.quizgrade}}.", "addon.mod_quiz.marks": "Балів", + "addon.mod_quiz.modulenameplural": "Тести", "addon.mod_quiz.mustbesubmittedby": "Ця спроба має бути відправлена до {{$a}}.", "addon.mod_quiz.noquestions": "Жодного питання не було додано", "addon.mod_quiz.noreviewattempt": "Вам не дозволено переглядати цю спробу.", @@ -553,6 +577,7 @@ "addon.mod_quiz.yourfinalgradeis": "Ваша підсумкова оцінка за цей тест: {{$a}}", "addon.mod_resource.errorwhileloadingthecontent": "Помилка при завантаженні вмісту.", "addon.mod_resource.modifieddate": "Модифіковано {{$a}}", + "addon.mod_resource.modulenameplural": "Файли", "addon.mod_resource.openthefile": "Відкрити файл", "addon.mod_resource.uploadeddate": "Завантажено {{$a}}", "addon.mod_scorm.asset": "Актив", @@ -589,6 +614,7 @@ "addon.mod_scorm.incomplete": "Не завершено", "addon.mod_scorm.lastattempt": "Остання завершена спроба", "addon.mod_scorm.mode": "Режим", + "addon.mod_scorm.modulenameplural": "SCORM пакети", "addon.mod_scorm.newattempt": "Почати нову спробу", "addon.mod_scorm.noattemptsallowed": "Кількість дозволених спроб", "addon.mod_scorm.noattemptsmade": "Кількість спроб, які ви повінні зробити", @@ -608,10 +634,12 @@ "addon.mod_survey.errorgetsurvey": "Помилка при отриманні даних.", "addon.mod_survey.ifoundthat": "Я довідався, що", "addon.mod_survey.ipreferthat": "Я волію, щоб", + "addon.mod_survey.modulenameplural": "Обстеження", "addon.mod_survey.responses": "Відповіді", "addon.mod_survey.results": "Результати", "addon.mod_survey.surveycompletednograph": "Ви завершили цю анкету.", "addon.mod_url.accessurl": "Доступ до URL", + "addon.mod_url.modulenameplural": "URLs (веб-посилання)", "addon.mod_url.pointingtourl": "Точки URL цього ресурсу", "addon.mod_wiki.cannoteditpage": "Ви не можете редагувати цю сторінку", "addon.mod_wiki.createpage": "Створити сторінку", @@ -620,6 +648,7 @@ "addon.mod_wiki.errornowikiavailable": "Ця вікі ще не має ніякого змісту.", "addon.mod_wiki.gowikihome": "Перейти на Вікі головну", "addon.mod_wiki.map": "Мапа", + "addon.mod_wiki.modulenameplural": "Вікі", "addon.mod_wiki.newpagehdr": "Нова сторінка", "addon.mod_wiki.newpagetitle": "Заголовок нової сторінки", "addon.mod_wiki.nocontent": "Немає контенту для цієї сторінки", @@ -657,6 +686,7 @@ "addon.mod_workshop.gradinggradecalculated": "Розрахунок балів за оцінювання", "addon.mod_workshop.gradinggradeof": "Бали за оцінювання (з {{$a}})", "addon.mod_workshop.gradinggradeover": "Перевизначення балів за оцінювання", + "addon.mod_workshop.modulenameplural": "Семінари", "addon.mod_workshop.nogradeyet": "Ще не оцінено", "addon.mod_workshop.notassessed": "Ще не оцінювалося", "addon.mod_workshop.notoverridden": "Не перевизначено", @@ -974,6 +1004,8 @@ "core.accounts": "Облікові записи", "core.add": "Додати", "core.agelocationverification": "Перевірка віку та місцезнаходження", + "core.ago": "{{$a}} тому", + "core.all": "Вибрати все", "core.allparticipants": "Усі учасники", "core.android": "Android", "core.answer": "Відповідь", @@ -1040,7 +1072,6 @@ "core.courses.cannotretrievemorecategories": "Категорії глибші, ніж рівень {{$a}} не можуть бути відновлені.", "core.courses.categories": "Категорії курсів", "core.courses.confirmselfenrol": "Ви впевнені, що хочете зареєструвати себе в цьому курсі?", - "core.courses.courseoverview": "Огляд курсу", "core.courses.courses": "Курси", "core.courses.enrolme": "Відрахувати мене", "core.courses.errorloadcategories": "Сталася помилка під час завантаження категорій.", @@ -1049,21 +1080,14 @@ "core.courses.errorselfenrol": "Сталася помилка при самостійному зарахування.", "core.courses.filtermycourses": "Відфільтрувати мої курси", "core.courses.frontpage": "Головна сторінка", - "core.courses.future": "Заплановані", - "core.courses.inprogress": "В процесі", - "core.courses.morecourses": "Більше курсів", "core.courses.mycourses": "Мої курси", + "core.courses.mymoodle": "Інформаційна сторінка", "core.courses.nocourses": "Немає інформації курсу для показу.", - "core.courses.nocoursesfuture": "Немає запланових курсів", - "core.courses.nocoursesinprogress": "Курси в прогресі відсутні", - "core.courses.nocoursesoverview": "Курсів немає", - "core.courses.nocoursespast": "Пройдені курси відсутні", "core.courses.nocoursesyet": "У цій категорії немає курсів", "core.courses.nosearchresults": "Немає результатів", "core.courses.notenroled": "Ви не зареєстровані як студент цього курсу", "core.courses.notenrollable": "Ви не можете зареєструвати себе в цьому курсі.", "core.courses.password": "Ключ подачі заявок", - "core.courses.past": "Минулі", "core.courses.paymentrequired": "Цей курс потребує оплати за вхід.", "core.courses.paypalaccepted": "Налаштовувати варіанти зарахування PayPal", "core.courses.search": "Знайти", @@ -1188,7 +1212,7 @@ "core.login.createaccount": "Створити запис", "core.login.createuserandpass": "Створити користувача для входу в систему", "core.login.credentialsdescription": "Будь ласка, введіть Ваше ім'я користувача та пароль, щоб увійти в систему.", - "core.login.emailconfirmsent": "

Лист повинен бути відправлений на Вашу електронну адресу в {{$a}}

Він містить прості інструкції для завершення реєстрації.

Якщо ви продовжуєте зазнавати труднощів, зверніться до адміністратора сайту.

", + "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", @@ -1201,7 +1225,7 @@ "core.login.helpmelogin": "

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

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

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

", "core.login.instructions": "Інструкції", "core.login.invalidaccount": "Будь ласка, перевірте ваші реєстраційні дані, або зверніться до адміністратора сайту, щоб перевірити конфігурацію сайту.", - "core.login.invaliddate": "Невірна дата", + "core.login.invaliddate": "Неправильна дата", "core.login.invalidemail": "Неправильний формат для ел.пошти", "core.login.invalidmoodleversion": "Невірна версія Moodle. Мінімальна версія 2.4.", "core.login.invalidsite": "URL сайту недійсний.", @@ -1219,6 +1243,7 @@ "core.login.missingfirstname": "Не вказано ім'я", "core.login.missinglastname": "Не вказано прізвище", "core.login.mobileservicesnotenabled": "Мобільні послуги не включені в вашому сайті. Будь ласка, зверніться до адміністратора вашого Moodle сайт, якщо ви вважаєте, що мобільний доступ повинен бути включений.", + "core.login.mustconfirm": "Підтвердіть обліковий запис", "core.login.newaccount": "Новий обліковий запис", "core.login.newsitedescription": "Будь ласка, введіть адресу вашого сайту Moodle. Зверніть увагу, що це може бути не налаштоване для роботи з цим додатком.", "core.login.notloggedin": "Ви повинні увійти в систему.", @@ -1260,8 +1285,6 @@ "core.mainmenu.changesite": "Змінити сайт", "core.mainmenu.help": "Допомога", "core.mainmenu.logout": "Вихід", - "core.mainmenu.mycourses": "Мої курси", - "core.mainmenu.togglemenu": "Перемикання меню", "core.mainmenu.website": "Веб-сайт", "core.maxsizeandattachments": "Макс. обсяг для нових файлів: {{$a.size}}, макс. кількість прикріплених файлів: {{$a.attachments}}", "core.min": "хв", @@ -1346,8 +1369,10 @@ "core.quotausage": "Ви в даний час використовували {$ a-> used}} вашого ліміту {$ a-> total}}.", "core.redirectingtosite": "Ви будете перенаправлені на сайт.", "core.refresh": "Оновити", + "core.remove": "Видалити", "core.required": "Необхідні", "core.requireduserdatamissing": "Цей користувач не має деяких необхідних даних в профілі. Заповніть, будь ласка, ці дані у вашому профілі Moodle і спробуйте ще раз.
{{$a}}", + "core.resources": "Ресурси", "core.restore": "Відновлення", "core.retry": "Повторити", "core.save": "Зберегти", @@ -1431,6 +1456,19 @@ "core.sizetb": "TB", "core.sorry": "Вибачте...", "core.sortby": "Сортувати за", + "core.strftimedate": "%d %B %Y", + "core.strftimedatefullshort": "%d.%m.%y", + "core.strftimedateshort": "%d %B", + "core.strftimedatetime": "%d %B %Y, %I:%M %p", + "core.strftimedatetimeshort": "%d.%m.%y, %H:%M", + "core.strftimedaydate": "%A %d %B %Y", + "core.strftimedaydatetime": "%A %d %B %Y %I:%M %p", + "core.strftimedayshort": "%A, %d %B", + "core.strftimedaytime": "%a, %H:%M", + "core.strftimemonthyear": "%B %Y", + "core.strftimerecent": "%d %b %H:%M", + "core.strftimerecentfull": "%a %d %b %Y %I:%M %p", + "core.strftimetime": "%I:%M %p", "core.submit": "Прийняти", "core.success": "Успішно", "core.tablet": "Планшет", @@ -1457,7 +1495,7 @@ "core.user.description": "Опис", "core.user.details": "Деталі", "core.user.detailsnotavailable": "Деталі цього користувача вам не доступні", - "core.user.editingteacher": "Вчитель", + "core.user.editingteacher": "Викладач", "core.user.email": "Електронна пошта", "core.user.emailagain": "Електронна пошта (повторно)", "core.user.firstname": "Ім'я", @@ -1472,7 +1510,7 @@ "core.user.roles": "Ролі", "core.user.sendemail": "Email", "core.user.student": "Студент", - "core.user.teacher": "Учитель без прав редагування", + "core.user.teacher": "Асистент", "core.user.webpage": "Веб-сторінка", "core.userdeleted": "Реєстраційний запис користувача було вилучено", "core.userdetails": "Детально", diff --git a/src/assets/lang/zh-cn.json b/src/assets/lang/zh-cn.json index 15e9b3089..cfe28f355 100644 --- a/src/assets/lang/zh-cn.json +++ b/src/assets/lang/zh-cn.json @@ -8,8 +8,18 @@ "addon.badges.issuancedetails": "有效期", "addon.badges.issuerdetails": "授勋机构详情", "addon.badges.issuername": "授勋机构名称", + "addon.badges.issuerurl": "授勋机构 URL", "addon.badges.nobadges": "没有可用的勋章", "addon.badges.recipientdetails": "获得者详情", + "addon.badges.warnexpired": "(此勋章已过期!)", + "addon.block_activitymodules.pluginname": "活动", + "addon.block_myoverview.future": "未来", + "addon.block_myoverview.inprogress": "进行中", + "addon.block_myoverview.morecourses": "更多课程", + "addon.block_myoverview.nocourses": "没有课程", + "addon.block_myoverview.past": "过去", + "addon.block_myoverview.pluginname": "课程概览", + "addon.block_sitemainmenu.pluginname": "主菜单", "addon.calendar.calendar": "日程管理", "addon.calendar.calendarevents": "日历事件", "addon.calendar.defaultnotificationtime": "默认通知时间", @@ -75,23 +85,23 @@ "addon.coursecompletion.complete": "完成的", "addon.coursecompletion.completecourse": "完成课程", "addon.coursecompletion.completed": "已完成", - "addon.coursecompletion.completiondate": "完成日期", + "addon.coursecompletion.completiondate": "完成时间", "addon.coursecompletion.completionmenuitem": "学习进度", "addon.coursecompletion.couldnotloadreport": "无法载入课程进度报表,请稍后再试。", - "addon.coursecompletion.coursecompletion": "课程进度", - "addon.coursecompletion.criteria": "标准", - "addon.coursecompletion.criteriagroup": "标准组", - "addon.coursecompletion.criteriarequiredall": "下面的所有标准都是必需的。", - "addon.coursecompletion.criteriarequiredany": "下面的任何标准都是必需的。", - "addon.coursecompletion.inprogress": "进行中", + "addon.coursecompletion.coursecompletion": "课程进度跟踪", + "addon.coursecompletion.criteria": "条件", + "addon.coursecompletion.criteriagroup": "条件组", + "addon.coursecompletion.criteriarequiredall": "必须满足以下条件", + "addon.coursecompletion.criteriarequiredany": "必须满足下列任一条件", + "addon.coursecompletion.inprogress": "处理中", "addon.coursecompletion.manualselfcompletion": "手动自设完成", - "addon.coursecompletion.notyetstarted": "还没有开始", + "addon.coursecompletion.notyetstarted": "还未开始", "addon.coursecompletion.pending": "等待中", - "addon.coursecompletion.required": "要求的", - "addon.coursecompletion.requiredcriteria": "要求的标准", - "addon.coursecompletion.requirement": "要求", + "addon.coursecompletion.required": "必需的", + "addon.coursecompletion.requiredcriteria": "必备条件", + "addon.coursecompletion.requirement": "前提", "addon.coursecompletion.status": "状态", - "addon.coursecompletion.viewcoursereport": "查看课程报表", + "addon.coursecompletion.viewcoursereport": "查看课程报告", "addon.files.couldnotloadfiles": "文件列表不能加载。", "addon.files.emptyfilelist": "没有可显示的文件。", "addon.files.erroruploadnotworking": "很遗憾,目前无法将文件上传到您的站点。", @@ -100,12 +110,12 @@ "addon.files.sitefiles": "本站文件", "addon.messageoutput_airnotifier.processorsettingsdesc": "配置设备", "addon.messages.addcontact": "添加联系人", - "addon.messages.blockcontact": "屏蔽联系人", - "addon.messages.blockcontactconfirm": "您将不再收到此联系人的消息。", + "addon.messages.addtoyourcontacts": "添加到你的联系人", "addon.messages.blocknoncontacts": "禁止不在联系人中的用户给我发消息", "addon.messages.contactlistempty": "您的联系人名单是空的", "addon.messages.contactname": "联系人姓名", "addon.messages.contacts": "联系人", + "addon.messages.deleteallconfirm": "您确定需要删除所有对话内容吗?", "addon.messages.errordeletemessage": "删除消息时出错。", "addon.messages.errorwhileretrievingcontacts": "从服务器检索联系人时出错。", "addon.messages.errorwhileretrievingdiscussions": "从服务器检索讨论区时出错。", @@ -116,16 +126,18 @@ "addon.messages.messages": "消息", "addon.messages.newmessage": "新消息", "addon.messages.newmessages": "新消息", - "addon.messages.nomessages": "没有新消息", + "addon.messages.nomessagesfound": "没有消息", + "addon.messages.noncontacts": "不是联系人", "addon.messages.nousersfound": "没有找到用户", "addon.messages.removecontact": "删除联系人", "addon.messages.removecontactconfirm": "此联系人将从你的联系人列表中删除。", + "addon.messages.removefromyourcontacts": "从你的联系人中删除", + "addon.messages.searchcombined": "搜索用户和消息", "addon.messages.type_blocked": "已屏蔽", "addon.messages.type_offline": "离线", "addon.messages.type_online": "在线", "addon.messages.type_search": "搜索结果", "addon.messages.type_strangers": "其他人", - "addon.messages.unblockcontact": "不再阻拦联系人", "addon.messages.warningmessagenotsent": "不能向用户{{user}}发送消息。 {{error}}", "addon.mod_assign.acceptsubmissionstatement": "请接受提交声明。", "addon.mod_assign.addattempt": "允许重做", @@ -164,6 +176,7 @@ "addon.mod_assign.graded": "已评分", "addon.mod_assign.gradedby": "评分人", "addon.mod_assign.gradedon": "评分时间", + "addon.mod_assign.gradelocked": "该成绩在成绩册中被锁定或被覆盖", "addon.mod_assign.gradenotsynced": "成绩未同步", "addon.mod_assign.gradeoutof": "成绩(满分 {{$a}} )", "addon.mod_assign.gradingstatus": "评分状态", @@ -178,6 +191,7 @@ "addon.mod_assign.markingworkflowstatereadyforrelease": "已准备公布", "addon.mod_assign.markingworkflowstatereadyforreview": "评分已完成", "addon.mod_assign.markingworkflowstatereleased": "已经公布", + "addon.mod_assign.modulenameplural": "作业", "addon.mod_assign.multipleteams": "你同时属于不同群组的成员", "addon.mod_assign.noattempt": "没有提交作业", "addon.mod_assign.nomoresubmissionsaccepted": "只接受已被宽延期限的学生提交作业", @@ -230,6 +244,7 @@ "addon.mod_assign_submission_file.pluginname": "文件提交", "addon.mod_assign_submission_onlinetext.pluginname": "在线文本提交", "addon.mod_book.errorchapter": "读取图书章节发生错误。", + "addon.mod_book.modulenameplural": "图书", "addon.mod_chat.beep": "呼叫", "addon.mod_chat.currentusers": "当前用户", "addon.mod_chat.enterchat": "进入聊天室", @@ -242,6 +257,7 @@ "addon.mod_chat.messagebeepsyou": "{{$a}}刚刚呼叫您!", "addon.mod_chat.messageenter": "{{$a}}刚刚进入聊天室", "addon.mod_chat.messageexit": "{{$a}}已退出聊天室", + "addon.mod_chat.modulenameplural": "聊天", "addon.mod_chat.mustbeonlinetosendmessages": "您必须在线才能发送信息。", "addon.mod_chat.nomessages": "无消息", "addon.mod_chat.send": "发送", @@ -252,6 +268,7 @@ "addon.mod_choice.errorgetchoice": "获取投票数据时出错。", "addon.mod_choice.expired": "对不起,该活动已于 {{$a}} 关闭,不能再参加", "addon.mod_choice.full": "(已满)", + "addon.mod_choice.modulenameplural": "投票", "addon.mod_choice.noresultsviewable": "结果目前不能查看。", "addon.mod_choice.notopenyet": "对于起,此活动在 {{$a}} 之后才可用", "addon.mod_choice.numberofuser": "用户数", @@ -284,7 +301,9 @@ "addon.mod_data.errormustsupplyvalue": "这儿你必须提供一个值。", "addon.mod_data.expired": "对不起,这项活动截止于{{$a}},不再有效", "addon.mod_data.fields": "字段", + "addon.mod_data.foundrecords": "找到记录:{{$a.num}}/{{$a.max}} (重设过滤器)", "addon.mod_data.menuchoose": "选择...", + "addon.mod_data.modulenameplural": "数据库", "addon.mod_data.more": "更多", "addon.mod_data.nomatch": "未找到匹配的条目", "addon.mod_data.norecords": "数据库中无条目", @@ -316,6 +335,7 @@ "addon.mod_feedback.feedbackopen": "开放此反馈时间", "addon.mod_feedback.mapcourses": "将反馈映射到课程", "addon.mod_feedback.mode": "模式", + "addon.mod_feedback.modulenameplural": "反馈", "addon.mod_feedback.next_page": "下一页", "addon.mod_feedback.non_anonymous": "用户姓名会被记录,并和他们的反馈一起显示", "addon.mod_feedback.non_anonymous_entries": "非匿名条目", @@ -335,6 +355,7 @@ "addon.mod_feedback.started": "已经开始", "addon.mod_feedback.this_feedback_is_already_submitted": "您已经完成此活动。", "addon.mod_folder.emptyfilelist": "没有可显示的文件。", + "addon.mod_folder.modulenameplural": "文件夹", "addon.mod_forum.addanewdiscussion": "开启一个新话题", "addon.mod_forum.addanewquestion": "添加一个新问题", "addon.mod_forum.addanewtopic": "添加一个新话题", @@ -357,6 +378,7 @@ "addon.mod_forum.modeflatnewestfirst": "列表显示回帖内容,新帖在前", "addon.mod_forum.modeflatoldestfirst": "列表显示回帖内容,旧帖在前", "addon.mod_forum.modenested": "嵌套显示回帖内容", + "addon.mod_forum.modulenameplural": "讨论区", "addon.mod_forum.numdiscussions": "{{numdiscussions}} 讨论", "addon.mod_forum.numreplies": "{{numreplies}} 回复", "addon.mod_forum.posttoforum": "发到讨论区上", @@ -392,9 +414,11 @@ "addon.mod_glossary.fillfields": "词名和定义都是必须填写的项目。", "addon.mod_glossary.fullmatch": "整词匹配", "addon.mod_glossary.linking": "自动链接", + "addon.mod_glossary.modulenameplural": "词汇表", "addon.mod_glossary.noentriesfound": "没有发现条目。", "addon.mod_glossary.searchquery": "查询词条", "addon.mod_imscp.deploymenterror": "内容包有错!", + "addon.mod_imscp.modulenameplural": "IMS 内容包", "addon.mod_imscp.showmoduledescription": "显示描述", "addon.mod_lesson.answer": "答案", "addon.mod_lesson.attempt": "尝试:{{$a}}", @@ -438,6 +462,7 @@ "addon.mod_lesson.lowtime": "最短耗时", "addon.mod_lesson.maximumnumberofattemptsreached": "您已达到最大数量的尝试-请进入下一个页面", "addon.mod_lesson.modattemptsnoteacher": "学生复审只能在学生端使用。", + "addon.mod_lesson.modulenameplural": "程序教学", "addon.mod_lesson.noanswer": "未回答问题。请回退并提交一个答案。", "addon.mod_lesson.nolessonattempts": "还没有人尝试此程序教学。", "addon.mod_lesson.notcompleted": "没有完成", @@ -479,7 +504,9 @@ "addon.mod_lti.errorgetlti": "获取模块数据时出错。", "addon.mod_lti.errorinvalidlaunchurl": "启动URL是无效的。", "addon.mod_lti.launchactivity": "启动活动", + "addon.mod_lti.modulenameplural": "基本 lti", "addon.mod_page.errorwhileloadingthepage": "加载页面内容时出错。", + "addon.mod_page.modulenameplural": "网页", "addon.mod_quiz.attemptfirst": "第一次答题", "addon.mod_quiz.attemptlast": "最后一次答题", "addon.mod_quiz.attemptnumber": "试卷", @@ -514,6 +541,7 @@ "addon.mod_quiz.grademethod": "评分办法", "addon.mod_quiz.gradesofar": "{{$a.method}}: {{$a.mygrade}} / {{$a.quizgrade}}", "addon.mod_quiz.marks": "测验得分", + "addon.mod_quiz.modulenameplural": "测验", "addon.mod_quiz.mustbesubmittedby": "此试卷必须在 {{$a}} 以前提交。", "addon.mod_quiz.noquestions": "尚未添加试题", "addon.mod_quiz.noreviewattempt": "您无权回顾此试卷。", @@ -557,6 +585,7 @@ "addon.mod_quiz.warningdatadiscardedfromfinished": "尝试未完成,因为一些离线的答案被丢弃了。请检查你的答案,然后再提交尝试。", "addon.mod_quiz.yourfinalgradeis": "这个测验您的最后得分是 {{$a}}", "addon.mod_resource.errorwhileloadingthecontent": "加载内容时出错。", + "addon.mod_resource.modulenameplural": "文件", "addon.mod_resource.openthefile": "打开文件", "addon.mod_scorm.asset": "资产", "addon.mod_scorm.assetlaunched": "资产 - 已浏览", @@ -592,6 +621,7 @@ "addon.mod_scorm.incomplete": "不完整", "addon.mod_scorm.lastattempt": "末次完成的得分", "addon.mod_scorm.mode": "模式", + "addon.mod_scorm.modulenameplural": "SCORM 课件", "addon.mod_scorm.newattempt": "再学一次", "addon.mod_scorm.noattemptsallowed": "允许尝试的次数", "addon.mod_scorm.noattemptsmade": "您已经尝试的次数", @@ -611,9 +641,11 @@ "addon.mod_survey.errorgetsurvey": "获取问卷调查数据时出错。", "addon.mod_survey.ifoundthat": "我发现", "addon.mod_survey.ipreferthat": "我希望的是", + "addon.mod_survey.modulenameplural": "问卷调查", "addon.mod_survey.responses": "回复", "addon.mod_survey.results": "结果", "addon.mod_url.accessurl": "访问该网址", + "addon.mod_url.modulenameplural": "网页地址", "addon.mod_url.pointingtourl": "URL这个资源指向", "addon.mod_wiki.cannoteditpage": "您不能编辑此页面。", "addon.mod_wiki.createpage": "建立页面", @@ -622,6 +654,7 @@ "addon.mod_wiki.errornowikiavailable": "这个wiki还没有任何内容。", "addon.mod_wiki.gowikihome": "去 Wiki主页", "addon.mod_wiki.map": "地图", + "addon.mod_wiki.modulenameplural": "Wiki协作", "addon.mod_wiki.newpagehdr": "新建页面", "addon.mod_wiki.newpagetitle": "新页面标题", "addon.mod_wiki.nocontent": "此页面无内容", @@ -660,6 +693,7 @@ "addon.mod_workshop.gradinggradecalculated": "已计算的评价成绩", "addon.mod_workshop.gradinggradeof": "评价成绩(最高分 {{$a}})", "addon.mod_workshop.gradinggradeover": "覆盖评价成绩", + "addon.mod_workshop.modulenameplural": "互动评价", "addon.mod_workshop.nogradeyet": "还没有成绩", "addon.mod_workshop.notassessed": "还未评价", "addon.mod_workshop.notoverridden": "不能覆盖", @@ -993,6 +1027,8 @@ "core.accounts": "帐户", "core.add": "添加", "core.agelocationverification": "年龄和地区认证", + "core.ago": "{{$a}} 之前", + "core.all": "所有", "core.allparticipants": "所有成员", "core.android": "安卓", "core.answer": "回答", @@ -1066,7 +1102,6 @@ "core.courses.cannotretrievemorecategories": "不能检索到超过{{$a}}级的类别。", "core.courses.categories": "课程分类", "core.courses.confirmselfenrol": "你确定你想在这门课上报名吗?", - "core.courses.courseoverview": "课程总览", "core.courses.courses": "课程", "core.courses.downloadcourses": "下载课程", "core.courses.enrolme": "加入我", @@ -1076,21 +1111,14 @@ "core.courses.errorselfenrol": "自己报名时发生错误。", "core.courses.filtermycourses": "筛选我的课程", "core.courses.frontpage": "首页", - "core.courses.future": "未来", - "core.courses.inprogress": "进行中", - "core.courses.morecourses": "更多课程", "core.courses.mycourses": "我的课程", + "core.courses.mymoodle": "个人主页", "core.courses.nocourses": "没有可显示的课程信息。", - "core.courses.nocoursesfuture": "没有未来的课程", - "core.courses.nocoursesinprogress": "没有进行中的课程", - "core.courses.nocoursesoverview": "没有课程", - "core.courses.nocoursespast": "没有过去的课程", "core.courses.nocoursesyet": "此类中无课程", "core.courses.nosearchresults": "无结果", "core.courses.notenroled": "您没有加入此课程", "core.courses.notenrollable": "你不能在这门课上报名。", "core.courses.password": "选课密码", - "core.courses.past": "过去", "core.courses.paymentrequired": "此课程需要付费才能进入。", "core.courses.paypalaccepted": "Paypal 公认的付费方式", "core.courses.search": "搜索", @@ -1214,7 +1242,7 @@ "core.login.createaccount": "提交", "core.login.createuserandpass": "设定用户名和密码", "core.login.credentialsdescription": "请提供您的用户名和密码来登录。", - "core.login.emailconfirmsent": "

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

\n

它包含了完成你的注册的简单的说明。

\n

如果你仍然有困难,请联系网站管理员。

", + "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", @@ -1227,7 +1255,7 @@ "core.login.helpmelogin": "

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

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

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

", "core.login.instructions": "使用说明", "core.login.invalidaccount": "请检查您的登录信息,或请您的网站管理员检查网站设置。", - "core.login.invaliddate": "无效的日期", + "core.login.invaliddate": "无效日期", "core.login.invalidemail": "Email地址无效", "core.login.invalidmoodleversion": "无效的Moodle版本。最低版本要求是2.4。", "core.login.invalidsite": "网站URL是无效的。", @@ -1245,6 +1273,7 @@ "core.login.missingfirstname": "名没填", "core.login.missinglastname": "姓没填", "core.login.mobileservicesnotenabled": "您的站点没有启用移动服务,如果您觉得有必要开启移动接入功能,请与站点管理员联系。", + "core.login.mustconfirm": "您必须确认一下您的登录", "core.login.newaccount": "新帐号", "core.login.newsitedescription": "请输入您的Moodle站点的URL。注意,它可能不会被配置为在这个应用程序中工作。", "core.login.notloggedin": "您需要登录。", @@ -1288,8 +1317,6 @@ "core.mainmenu.changesite": "切换站点", "core.mainmenu.help": "帮助", "core.mainmenu.logout": "退出登录", - "core.mainmenu.mycourses": "我的课程", - "core.mainmenu.togglemenu": "切换菜单", "core.mainmenu.website": "网站", "core.maxsizeandattachments": "新文件的最大尺寸: {{$a.size}} ,最多附件:{{$a.attachments}}", "core.min": "分钟", @@ -1372,8 +1399,10 @@ "core.quotausage": "您当前已使用总配额 {{$a.total}} 中的 {{$a.used}}。", "core.redirectingtosite": "您将被重定向到站点。", "core.refresh": "刷新", + "core.remove": "免除", "core.required": "必需的", "core.requireduserdatamissing": "这个用户缺少一些必需的个人信息数据。请把这些数据填在你的Moodle上,再试一次。
{{$a}}", + "core.resources": "资源库", "core.restore": "恢复", "core.retry": "重试", "core.save": "保存", @@ -1456,6 +1485,19 @@ "core.sizemb": "MB", "core.sorry": "对不起…", "core.sortby": "排序", + "core.strftimedate": "%Y年%m月%d日", + "core.strftimedatefullshort": "%y/%m/%d", + "core.strftimedateshort": "%m月%d日", + "core.strftimedatetime": "%Y年%m月%d日 %H:%M", + "core.strftimedatetimeshort": "%Y年%m月%d日 %H:%M", + "core.strftimedaydate": "%Y年%m月%d日 %A", + "core.strftimedaydatetime": "%Y年%m月%d日 %A %H:%M", + "core.strftimedayshort": "%m月%d日 %A", + "core.strftimedaytime": "%A %H:%M", + "core.strftimemonthyear": "%Y年%m月", + "core.strftimerecent": "%m月%d日 %H:%M", + "core.strftimerecentfull": "%Y年%m月%d日 %A %H:%M", + "core.strftimetime": "%H:%M", "core.submit": "提交", "core.success": "成功", "core.tablet": "平板电脑", diff --git a/src/assets/lang/zh-tw.json b/src/assets/lang/zh-tw.json index 74bc4ad4e..e3a7f61e9 100644 --- a/src/assets/lang/zh-tw.json +++ b/src/assets/lang/zh-tw.json @@ -8,8 +8,26 @@ "addon.badges.issuancedetails": "獎章到期", "addon.badges.issuerdetails": "頒授者細節", "addon.badges.issuername": "頒授者的姓名", + "addon.badges.issuerurl": "頒授者的網址", "addon.badges.nobadges": "這裡沒有可用的獎章", "addon.badges.recipientdetails": "收件者細節", + "addon.badges.warnexpired": "(這一獎章已經過期失效!)", + "addon.block_activitymodules.pluginname": "活動", + "addon.block_myoverview.all": "全部", + "addon.block_myoverview.favourites": "標記星號的", + "addon.block_myoverview.future": "未來", + "addon.block_myoverview.hiddencourses": "隱藏", + "addon.block_myoverview.inprogress": "進行中", + "addon.block_myoverview.lastaccessed": "最後訪問的", + "addon.block_myoverview.morecourses": "更多課程", + "addon.block_myoverview.nocourses": "沒有課程", + "addon.block_myoverview.past": "過去", + "addon.block_myoverview.pluginname": "課程概觀", + "addon.block_myoverview.title": "課程名稱", + "addon.block_recentlyaccessedcourses.nocourses": "沒有最近的課程", + "addon.block_recentlyaccessedcourses.pluginname": "最近訪問的課程", + "addon.block_sitemainmenu.pluginname": "主選單", + "addon.block_starredcourses.pluginname": "加星號的課程", "addon.calendar.calendar": "行事曆", "addon.calendar.calendarevents": "行事曆", "addon.calendar.errorloadevent": "載入事件時出現錯誤", @@ -79,20 +97,20 @@ "addon.coursecompletion.completiondate": "完成日期", "addon.coursecompletion.completionmenuitem": "完成", "addon.coursecompletion.couldnotloadreport": "無法載入課程完成報表,請稍後再試.", - "addon.coursecompletion.coursecompletion": "課程完成度", - "addon.coursecompletion.criteria": "條件", - "addon.coursecompletion.criteriagroup": "條件群組", - "addon.coursecompletion.criteriarequiredall": "以下所有的條件都為必填", - "addon.coursecompletion.criteriarequiredany": "以下任何條件都為必須", + "addon.coursecompletion.coursecompletion": "課程完成進度", + "addon.coursecompletion.criteria": "規準", + "addon.coursecompletion.criteriagroup": "各種判斷條件", + "addon.coursecompletion.criteriarequiredall": "必須滿足以下所有條件", + "addon.coursecompletion.criteriarequiredany": "必須滿足以下任一條件", "addon.coursecompletion.inprogress": "處理中", "addon.coursecompletion.manualselfcompletion": "手動自我完成", - "addon.coursecompletion.notyetstarted": "還沒有開始", - "addon.coursecompletion.pending": "暫緩", + "addon.coursecompletion.notyetstarted": "尚未開始", + "addon.coursecompletion.pending": "等待中", "addon.coursecompletion.required": "必須的", - "addon.coursecompletion.requiredcriteria": "必須條件", - "addon.coursecompletion.requirement": "需要", + "addon.coursecompletion.requiredcriteria": "必要條件", + "addon.coursecompletion.requirement": "要求", "addon.coursecompletion.status": "狀態", - "addon.coursecompletion.viewcoursereport": "檢視課程報表", + "addon.coursecompletion.viewcoursereport": "查看課程報告", "addon.files.couldnotloadfiles": "這些檔案是不能被載入的", "addon.files.emptyfilelist": "沒有檔案可以顯示", "addon.files.erroruploadnotworking": "很抱歉, 目前無法將檔案上傳到您的網站.", @@ -101,11 +119,13 @@ "addon.files.sitefiles": "網站檔案", "addon.messageoutput_airnotifier.processorsettingsdesc": "設定裝置", "addon.messages.addcontact": "增加聯絡人", - "addon.messages.blockcontact": "封鎖聯絡人", + "addon.messages.addtoyourcontacts": "加入到你的聯絡人", "addon.messages.blocknoncontacts": "僅接收通訊錄中的使用者所發送的簡訊。(其他都拒收)", + "addon.messages.contactblocked": "聯絡人被鎖定", "addon.messages.contactlistempty": "聯絡人清單沒有資料", "addon.messages.contactname": "聯絡人名稱", "addon.messages.contacts": "通訊錄", + "addon.messages.deleteallconfirm": "你確定要刪除這整個對話?", "addon.messages.errordeletemessage": "刪除訊息時發生錯誤.", "addon.messages.errorwhileretrievingcontacts": "從伺服器存取聯絡人時出錯", "addon.messages.errorwhileretrievingdiscussions": "從伺服器存取討論區時出錯", @@ -115,16 +135,19 @@ "addon.messages.messagepreferences": "簡訊偏好", "addon.messages.messages": "簡訊", "addon.messages.newmessage": "新簡訊", - "addon.messages.nomessages": "沒有待處理的簡訊", + "addon.messages.nomessagesfound": "沒有找到簡訊", + "addon.messages.noncontacts": "沒有聯絡人", "addon.messages.nousersfound": "沒有使用者", "addon.messages.removecontact": "刪除聯絡人", + "addon.messages.removefromyourcontacts": "從你的聯絡人中移除", + "addon.messages.searchcombined": "搜尋人員和簡訊", "addon.messages.type_blocked": "已停止", "addon.messages.type_offline": "離線", "addon.messages.type_online": "上線", "addon.messages.type_search": "搜尋結果", "addon.messages.type_strangers": "其他", - "addon.messages.unblockcontact": "不再封鎖聯絡", "addon.messages.warningmessagenotsent": "無法傳送訊息給使用者 {{user}}. {{error}}", + "addon.messages.you": "你:", "addon.mod_assign.acceptsubmissionstatement": "請接受提交聲明.", "addon.mod_assign.addattempt": "允許另一次的繳交", "addon.mod_assign.addnewattempt": "新增一個繳交管道", @@ -162,6 +185,7 @@ "addon.mod_assign.graded": "已評分", "addon.mod_assign.gradedby": "已評分由", "addon.mod_assign.gradedon": "評分標準", + "addon.mod_assign.gradelocked": "這一分數在這成績簿中是被鎖定或覆蓋的", "addon.mod_assign.gradeoutof": "得分(配分{{$a}})", "addon.mod_assign.gradingstatus": "評分狀態", "addon.mod_assign.groupsubmissionsettings": "群組繳交作業設定", @@ -175,6 +199,7 @@ "addon.mod_assign.markingworkflowstatereadyforrelease": "已準備好公布", "addon.mod_assign.markingworkflowstatereadyforreview": "評分已完成", "addon.mod_assign.markingworkflowstatereleased": "已經公布", + "addon.mod_assign.modulenameplural": "作業", "addon.mod_assign.multipleteams": "你同時是屬於不同的群組的成員", "addon.mod_assign.multipleteams_desc": "這一作業要求以群組方式繳交作業。你同時屬於多個群組的成員,要提交作業你必須只屬於一個群組。請聯絡您的教師,為你更改群組。", "addon.mod_assign.noattempt": "沒有繳交作業", @@ -229,6 +254,7 @@ "addon.mod_assign_submission_file.pluginname": "提交檔案", "addon.mod_assign_submission_onlinetext.pluginname": "提交線上文字", "addon.mod_book.errorchapter": "讀取章節發生錯誤", + "addon.mod_book.modulenameplural": "電子書", "addon.mod_chat.beep": "呼叫", "addon.mod_chat.currentusers": "當前用戶", "addon.mod_chat.enterchat": "點選這裡進入聊天室", @@ -241,6 +267,7 @@ "addon.mod_chat.messagebeepsyou": "{{$a}} 呼叫您", "addon.mod_chat.messageenter": "{{$a}} 剛加入這次聊天", "addon.mod_chat.messageexit": "{{$a}} 已退出這次聊天", + "addon.mod_chat.modulenameplural": "聊天室", "addon.mod_chat.mustbeonlinetosendmessages": "您必須上線才能傳送訊息", "addon.mod_chat.nomessages": "尚無訊息", "addon.mod_chat.send": "傳送", @@ -251,6 +278,7 @@ "addon.mod_choice.errorgetchoice": "取得選項資料時出錯", "addon.mod_choice.expired": "抱歉,此項活動已經在{{$a}}關閉,不再開放使用。", "addon.mod_choice.full": "(已滿)", + "addon.mod_choice.modulenameplural": "票選活動", "addon.mod_choice.noresultsviewable": "目前無法檢視票選結果", "addon.mod_choice.notopenyet": "抱歉,這個活動在{{$a}}之前不能使用", "addon.mod_choice.numberofuser": "回應的人數", @@ -281,8 +309,10 @@ "addon.mod_data.errormustsupplyvalue": "這裡你必須提供一個數值。", "addon.mod_data.expired": "抱歉,這活動已經在 {{$a}}關閉,已經無法使用。", "addon.mod_data.fields": "欄位", + "addon.mod_data.foundrecords": "找到記錄資料: {{$a.num}}/{{$a.max}} ({{$a.reseturl}}
\">重設篩選器)", "addon.mod_data.latlongboth": "緯度和經度兩者都要填寫", "addon.mod_data.menuchoose": "選擇...", + "addon.mod_data.modulenameplural": "資料庫", "addon.mod_data.more": "更多", "addon.mod_data.nomatch": "找不到符合的資料!", "addon.mod_data.norecords": "資料庫中沒有資料", @@ -312,6 +342,7 @@ "addon.mod_feedback.feedbackopen": "開始填答時間", "addon.mod_feedback.mapcourses": "對應回饋單到課程", "addon.mod_feedback.mode": "模式", + "addon.mod_feedback.modulenameplural": "回饋單", "addon.mod_feedback.next_page": "下一頁", "addon.mod_feedback.non_anonymous": "用戶名稱和回應將被記錄", "addon.mod_feedback.non_anonymous_entries": "具名輸入({{$a}})", @@ -332,6 +363,7 @@ "addon.mod_feedback.started": "已經開始", "addon.mod_feedback.this_feedback_is_already_submitted": "您'已經完成這種活動。", "addon.mod_folder.emptyfilelist": "沒有檔案可以顯示", + "addon.mod_folder.modulenameplural": "資料夾", "addon.mod_forum.addanewdiscussion": "新增一個討論主題", "addon.mod_forum.addanewquestion": "新增一個問題", "addon.mod_forum.addanewtopic": "新增一個主題", @@ -354,6 +386,7 @@ "addon.mod_forum.modeflatnewestfirst": "以平舖方式呈現回應的貼文,最晚貼出的在前", "addon.mod_forum.modeflatoldestfirst": "以平舖方式呈現回應的貼文,最早貼出的在前", "addon.mod_forum.modenested": "以縮排方式呈現回應的貼文", + "addon.mod_forum.modulenameplural": "討論區", "addon.mod_forum.numdiscussions": "{{numdiscussions}}篇討論", "addon.mod_forum.numreplies": "{{numreplies}}個回覆", "addon.mod_forum.posttoforum": "貼文到討論區中", @@ -386,9 +419,11 @@ "addon.mod_glossary.fillfields": "概念和定義是必要的欄位", "addon.mod_glossary.fullmatch": "要完全符合整個文字", "addon.mod_glossary.linking": "自動連結", + "addon.mod_glossary.modulenameplural": "詞彙表", "addon.mod_glossary.noentriesfound": "未找到條目.", "addon.mod_glossary.searchquery": "搜索查詢", "addon.mod_imscp.deploymenterror": "內容包有錯誤!", + "addon.mod_imscp.modulenameplural": "IMS內容包", "addon.mod_imscp.showmoduledescription": "顯示說明", "addon.mod_lesson.answer": "答案", "addon.mod_lesson.attempt": "作答:{{$a}}", @@ -429,6 +464,7 @@ "addon.mod_lesson.lowtime": "最短時間", "addon.mod_lesson.maximumnumberofattemptsreached": "已達到作答次的上限 - 將進入下一頁", "addon.mod_lesson.modattemptsnoteacher": "學生回顧功能只開放給學生們。", + "addon.mod_lesson.modulenameplural": "編序學習", "addon.mod_lesson.noanswer": "有一個或以上的問題沒有回答。請回頭並提交一個答案。", "addon.mod_lesson.nolessonattempts": "這個課程還沒有人嘗試。", "addon.mod_lesson.nolessonattemptsgroup": "{{$a}}群組成員在這單元上還沒有人作答", @@ -469,7 +505,9 @@ "addon.mod_lti.errorgetlti": "取得模組資料時出錯", "addon.mod_lti.errorinvalidlaunchurl": "啟動網址無效", "addon.mod_lti.launchactivity": "啟動活動", + "addon.mod_lti.modulenameplural": "外部工具", "addon.mod_page.errorwhileloadingthepage": "載入頁面內容時出錯", + "addon.mod_page.modulenameplural": "頁面", "addon.mod_quiz.attemptfirst": "第一次作答", "addon.mod_quiz.attemptlast": "最後一次作答", "addon.mod_quiz.attemptnumber": "作答數", @@ -504,6 +542,7 @@ "addon.mod_quiz.grademethod": "評分方式", "addon.mod_quiz.gradesofar": "{{$a.method}}:{{$a.mygrade}} / {{$a.quizgrade}}。", "addon.mod_quiz.marks": "得分", + "addon.mod_quiz.modulenameplural": "測驗卷", "addon.mod_quiz.mustbesubmittedby": "這作答應該由{{$a}}提交", "addon.mod_quiz.noquestions": "尚未加入題目", "addon.mod_quiz.noreviewattempt": "你不被允許重新檢視作答結果", @@ -548,6 +587,7 @@ "addon.mod_quiz.yourfinalgradeis": "這個測驗您的最後成績是{{$a}}", "addon.mod_resource.errorwhileloadingthecontent": "載入內容時發生錯誤.", "addon.mod_resource.modifieddate": "修改於{{$a}}", + "addon.mod_resource.modulenameplural": "檔案", "addon.mod_resource.openthefile": "開啟檔案", "addon.mod_resource.uploadeddate": "上傳於{{$a}}", "addon.mod_scorm.asset": "資源", @@ -584,6 +624,7 @@ "addon.mod_scorm.incomplete": "不完整", "addon.mod_scorm.lastattempt": "最後完成的作答次", "addon.mod_scorm.mode": "模式", + "addon.mod_scorm.modulenameplural": "SCORM課程包", "addon.mod_scorm.newattempt": "開始一個新的作答次", "addon.mod_scorm.noattemptsallowed": "允許作答的次數", "addon.mod_scorm.noattemptsmade": "你已經作答的次數", @@ -603,10 +644,12 @@ "addon.mod_survey.errorgetsurvey": "取得調查資料時出錯", "addon.mod_survey.ifoundthat": "我發現", "addon.mod_survey.ipreferthat": "我希望的是", + "addon.mod_survey.modulenameplural": "問卷", "addon.mod_survey.responses": "回應", "addon.mod_survey.results": "結果", "addon.mod_survey.surveycompletednograph": "你已經完成這一份問卷。", "addon.mod_url.accessurl": "存取URL", + "addon.mod_url.modulenameplural": "網址", "addon.mod_url.pointingtourl": "此資源所指向的網址", "addon.mod_wiki.cannoteditpage": "你不能編輯這一頁", "addon.mod_wiki.createpage": "建立頁面", @@ -615,6 +658,7 @@ "addon.mod_wiki.errornowikiavailable": "這個維基詞條沒有內容", "addon.mod_wiki.gowikihome": "到維基百科", "addon.mod_wiki.map": "地圖", + "addon.mod_wiki.modulenameplural": "Wiki共筆", "addon.mod_wiki.newpagehdr": "新頁面", "addon.mod_wiki.newpagetitle": "新頁面標題", "addon.mod_wiki.nocontent": "在這一頁裡沒有內容", @@ -652,6 +696,7 @@ "addon.mod_workshop.gradinggradecalculated": "計算出的評鑑表現分數", "addon.mod_workshop.gradinggradeof": "評鑑表現分數(最高分 {{$a}})", "addon.mod_workshop.gradinggradeover": "覆蓋評價成績", + "addon.mod_workshop.modulenameplural": "工作坊", "addon.mod_workshop.nogradeyet": "還沒有分數", "addon.mod_workshop.notassessed": "還沒進行評價", "addon.mod_workshop.notoverridden": "不可覆蓋", @@ -1007,6 +1052,8 @@ "core.accounts": "帳戶", "core.add": "新增", "core.agelocationverification": "驗證年齡與所在位置", + "core.ago": "{{$a}}之前", + "core.all": "所有", "core.allparticipants": "所有參與者", "core.android": "安卓", "core.answer": "回答", @@ -1065,11 +1112,11 @@ "core.course.sections": "單元", "core.course.useactivityonbrowser": "你可以以瀏覽器一直使用", "core.coursedetails": "課程細節", + "core.courses.addtofavourites": "將課程標記星號", "core.courses.allowguests": "本課程允許無帳號者進入", "core.courses.availablecourses": "可使用的課程", "core.courses.categories": "課程類別", "core.courses.confirmselfenrol": "您確定要註冊本課程嗎?", - "core.courses.courseoverview": "課程概況", "core.courses.courses": "課程", "core.courses.enrolme": "註冊報名", "core.courses.errorloadcourses": "載入課程時出錯.", @@ -1077,28 +1124,24 @@ "core.courses.errorselfenrol": "自行註冊時出現錯誤.", "core.courses.filtermycourses": "過濾我的課程", "core.courses.frontpage": "首頁", - "core.courses.future": "未來", - "core.courses.inprogress": "進行中", - "core.courses.morecourses": "更多課程", + "core.courses.hidecourse": "隱藏不檢視", "core.courses.mycourses": "我的課程", + "core.courses.mymoodle": "儀表板", "core.courses.nocourses": "沒有課程資訊可以顯示", - "core.courses.nocoursesfuture": "沒有未來的課程", - "core.courses.nocoursesinprogress": "沒有進行中的課程", - "core.courses.nocoursesoverview": "沒有課程", - "core.courses.nocoursespast": "沒有過去的課程", "core.courses.nocoursesyet": "此類別中無課程", "core.courses.nosearchresults": "沒有結果", "core.courses.notenroled": "您沒有加入此課程", "core.courses.notenrollable": "您無法註冊本課程.", "core.courses.password": "註冊碼", - "core.courses.past": "過去", "core.courses.paymentrequired": "這個課程需要付費才能進入。", "core.courses.paypalaccepted": "PayPal 付款成功", + "core.courses.removefromfavourites": "取消課程星號", "core.courses.search": "搜尋", "core.courses.searchcourses": "搜尋課程", "core.courses.searchcoursesadvice": "您可以使用搜尋課程按鈕以訪客身份登入, 或在允許它的課程中註冊", "core.courses.selfenrolment": "自行註冊", "core.courses.sendpaymentbutton": "使用Paypal送出款項", + "core.courses.show": "顯示這個課程", "core.courses.totalcoursesearchresults": "課程總數: {{$a}}", "core.currentdevice": "目前的裝置", "core.datastoredoffline": "儲存在設備中的資料,因為它之前無法發送. 它稍後將自動發送.", @@ -1216,7 +1259,7 @@ "core.login.createaccount": "建立我的新帳號", "core.login.createuserandpass": "請選擇您的帳號名稱和密碼", "core.login.credentialsdescription": "請提供您的帳號和密碼以便登入", - "core.login.emailconfirmsent": "

電子郵件應該已經寄到您的地址 {{$ a}}

它包含簡單的說明, 可以完成註冊.

如果, 您仍然有困難, 請與網站管理員聯繫.", + "core.login.emailconfirmsent": "

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

\n

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

\n

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

", "core.login.emailnotmatch": "電子郵件內容不符", "core.login.enterthewordsabove": "輸入您在上圖中看到的字元", "core.login.erroraccesscontrolalloworigin": "您嘗試執行的Cross-Origin呼叫已被拒絕. 請檢查https://docs.moodle.org/dev/Moodle_Mobile_development_using_Chrome_or_Chromium", @@ -1229,7 +1272,7 @@ "core.login.helpmelogin": "

要登入前請確認:

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

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

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

", "core.login.instructions": "使用說明", "core.login.invalidaccount": "請檢查您的登人資料或請您的網站管理員檢查網站設定。", - "core.login.invaliddate": "無效的日期", + "core.login.invaliddate": "錯誤的日期", "core.login.invalidemail": "無效的電子郵件信箱", "core.login.invalidmoodleversion": "無效的Moodle版本。至少需要的版本為2.4", "core.login.invalidsite": "這網站網址無效.", @@ -1246,6 +1289,7 @@ "core.login.missingfirstname": "缺少名字資料", "core.login.missinglastname": "缺少姓氏資料", "core.login.mobileservicesnotenabled": "您的網站並未啟用行動服務. 如果您想使用此功能, 請連絡您的Moodle網站管理員.", + "core.login.mustconfirm": "您必須確認一下您的登入", "core.login.newaccount": "新帳號", "core.login.newsitedescription": "請輸入您的Moodle平台網址. 注意平台需要先設定好才能使用這個應用程式.", "core.login.notloggedin": "你必須先登入", @@ -1286,8 +1330,6 @@ "core.mainmenu.changesite": "更換網站", "core.mainmenu.help": "輔助說明", "core.mainmenu.logout": "登出", - "core.mainmenu.mycourses": "我的課程", - "core.mainmenu.togglemenu": "切換功能選單", "core.mainmenu.website": "網站", "core.maxsizeandattachments": "新檔案的最大容量: {{$a.size}} ,最多附件:{{$a.attachments}}", "core.min": "分鐘", @@ -1373,8 +1415,10 @@ "core.quotausage": "您可以使用的總容量有{{$a.total}} ,目前已經用掉 {{$a.used}} 。", "core.redirectingtosite": "您將被重定向到網站.", "core.refresh": "更新", + "core.remove": "移除", "core.required": "必須的", "core.requireduserdatamissing": "此使用者缺少一些必需的配置資料. 請在您的Moodle中填寫此數據, 然後重試.
{{$ a}}", + "core.resources": "資源", "core.restore": "還原", "core.retry": "重試", "core.save": "儲存", @@ -1458,6 +1502,19 @@ "core.sizetb": "TB", "core.sorry": "抱歉...", "core.sortby": "排序依據", + "core.strftimedate": "%Y年 %m月 %d日", + "core.strftimedatefullshort": "%y/%m/%d", + "core.strftimedateshort": "%m月 %d日", + "core.strftimedatetime": "%Y年%m月%d日,%H:%M", + "core.strftimedatetimeshort": "%Y/%m/%d %H:%M", + "core.strftimedaydate": "%Y年 %m月 %d日 %A", + "core.strftimedaydatetime": "%Y年 %m月 %d日(%a) %H:%M", + "core.strftimedayshort": "%m月 %d日 %A", + "core.strftimedaytime": "%a %H:%M", + "core.strftimemonthyear": "%Y年 %m月", + "core.strftimerecent": "%m月 %d日,%H:%M", + "core.strftimerecentfull": "%Y年%m月%d日(%a) %H:%M", + "core.strftimetime": "%p %I:%M", "core.submit": "送出", "core.success": "成功", "core.tablet": "平板", @@ -1488,7 +1545,7 @@ "core.user.firstname": "名字", "core.user.interests": "興趣", "core.user.lastname": "姓氏", - "core.user.manager": "管理者", + "core.user.manager": "管理員", "core.user.newpicture": "新照片", "core.user.noparticipants": "這一課程找不到參與者", "core.user.participants": "成員", @@ -1496,7 +1553,7 @@ "core.user.phone2": "手機", "core.user.roles": "角色", "core.user.student": "學生", - "core.user.teacher": "非編輯中的教師", + "core.user.teacher": "助理教師", "core.user.webpage": "網頁", "core.userdeleted": "該用戶帳號已被刪除", "core.userdetails": "用戶的詳細資料", diff --git a/src/core/login/lang/en.json b/src/core/login/lang/en.json index f01aac580..8264d9196 100644 --- a/src/core/login/lang/en.json +++ b/src/core/login/lang/en.json @@ -77,7 +77,7 @@ "selectsite": "Please select your site:", "signupplugindisabled": "{{$a}} is not enabled.", "siteaddress": "Site address", - "sitehasredirect": "Your site contains at least one HTTP redirect. The app cannot follow redirects and so cannot connect to your site.", + "sitehasredirect": "Your site contains at least one HTTP redirect. The app cannot follow redirects, this could be the issue that's preventing the app from connecting to your site.", "siteinmaintenance": "Your site is in maintenance mode", "sitepolicynotagreederror": "Site policy not agreed.", "siteurl": "Site URL", diff --git a/src/core/settings/lang/en.json b/src/core/settings/lang/en.json index caa4a9efa..2b95b657e 100644 --- a/src/core/settings/lang/en.json +++ b/src/core/settings/lang/en.json @@ -11,7 +11,7 @@ "cordovaversion": "Cordova version", "currentlanguage": "Current language", "debugdisplay": "Display debug messages", - "debugdisplaydescription": "If enabled, additional information about errors will be displayed.", + "debugdisplaydescription": "If enabled, error modals will display more data about the error if possible.", "deletesitefiles": "Are you sure that you want to delete the downloaded files from the site '{{sitename}}'?", "deletesitefilestitle": "Delete site files", "deviceinfo": "Device info", diff --git a/src/lang/en.json b/src/lang/en.json index a87389aec..bc10378ca 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -11,7 +11,7 @@ "areyousure": "Are you sure?", "back": "Back", "cancel": "Cancel", - "cannotconnect": "Cannot connect: Verify that you have typed the URL correctly.", + "cannotconnect": "Cannot connect: Verify that you have correctly typed the URL and that your site uses Moodle 2.4 or later.", "cannotdownloadfiles": "File downloading is disabled. Please contact your site administrator.", "captureaudio": "Record audio", "capturedimage": "Taken picture.", @@ -41,7 +41,7 @@ "confirmdeletefile": "Are you sure you want to delete this file?", "confirmloss": "Are you sure? All changes will be lost.", "confirmopeninbrowser": "Do you want to open it in a web browser?", - "considereddigitalminor": "You are considered to be a digital minor.", + "considereddigitalminor": "You are too young to create an account on this site.", "content": "Content", "contenteditingsynced": "The content you are editing has been synced.", "continue": "Continue", @@ -62,11 +62,11 @@ "dfdaymonthyear": "MM-DD-YYYY", "dfdayweekmonth": "ddd, D MMM", "dffulldate": "dddd, D MMMM YYYY h[:]mm A", - "dfmediumdate": "LLL", "dflastweekdate": "ddd", + "dfmediumdate": "LLL", "dftimedate": "h[:]mm A", "digitalminor": "Digital minor", - "digitalminor_desc": "To create an account on this site please have your parent/guardian contact the following person.", + "digitalminor_desc": "Please ask your parent/guardian to contact:", "discard": "Discard", "dismiss": "Dismiss", "done": "Done", @@ -91,7 +91,7 @@ "errorsync": "An error occurred while synchronising. Please try again.", "errorsyncblocked": "This {{$a}} cannot be synchronised right now because of an ongoing process. Please try again later. If the problem persists, try restarting the app.", "explanationdigitalminor": "This information is required to determine if your age is over the digital age of consent. This is the age when an individual can consent to terms and conditions and their data being legally stored and processed.", - "favourites": "Favourites", + "favourites": "Starred", "filename": "Filename", "filenameexist": "File name already exists: {{$a}}", "folder": "Folder", @@ -164,8 +164,8 @@ "nograde": "No grade", "none": "None", "nopasswordchangeforced": "You cannot proceed without changing your password.", - "nopermissionerror": "Sorry, but you do not currently have permissions to do that.", - "nopermissions": "Sorry, but you do not currently have permissions to do that ({{$a}})", + "nopermissionerror": "Sorry, but you do not currently have permissions to do that", + "nopermissions": "Sorry, but you do not currently have permissions to do that ({{$a}}).", "noresults": "No results", "notapplicable": "n/a", "notice": "Notice", From 2bd79fd95454f0c70311412e62d2861e927eed43 Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Wed, 23 Jan 2019 13:29:48 +0100 Subject: [PATCH 011/191] MOBILE-2429 feedback: Use responses of the last submission --- src/addon/mod/feedback/providers/feedback.ts | 31 ++++++++++---------- 1 file changed, 15 insertions(+), 16 deletions(-) diff --git a/src/addon/mod/feedback/providers/feedback.ts b/src/addon/mod/feedback/providers/feedback.ts index 3fcb0099e..6310b3deb 100644 --- a/src/addon/mod/feedback/providers/feedback.ts +++ b/src/addon/mod/feedback/providers/feedback.ts @@ -135,18 +135,6 @@ export class AddonModFeedbackProvider { */ protected fillValues(feedbackId: number, items: any[], offline: boolean, ignoreCache: boolean, siteId: string): Promise { return this.getCurrentValues(feedbackId, offline, ignoreCache, siteId).then((valuesArray) => { - if (valuesArray.length == 0) { - // Try sending empty values to get the last completed attempt values. - return this.processPageOnline(feedbackId, 0, {}, undefined, siteId).then(() => { - return this.getCurrentValues(feedbackId, offline, ignoreCache, siteId); - }).catch(() => { - // Ignore errors - }); - } - - return valuesArray; - - }).then((valuesArray) => { const values = {}; valuesArray.forEach((value) => { @@ -440,7 +428,7 @@ export class AddonModFeedbackProvider { } /** - * Returns the temporary completion record for the current user. + * Returns the temporary responses or responses of the last submission for the current user. * * @param {number} feedbackId Feedback ID. * @param {boolean} [offline=false] True if it should return cached data. Has priority over ignoreCache. @@ -465,11 +453,22 @@ export class AddonModFeedbackProvider { } return site.read('mod_feedback_get_unfinished_responses', params, preSets).then((response) => { - if (response && typeof response.responses != 'undefined') { - return response.responses; + if (!response || typeof response.responses == 'undefined') { + return Promise.reject(null); } - return Promise.reject(null); + if (response.responses.length == 0) { + // No unfinished responses, fetch responses of the last submission. + return site.read('mod_feedback_get_finished_responses', params, preSets).then((response) => { + if (!response || typeof response.responses == 'undefined') { + return Promise.reject(null); + } + + return response.responses; + }); + } + + return response.responses; }); }); } From ef2adcb7bab5dd54052fceb437aaa7dd2fd9c320 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 23 Jan 2019 15:29:29 +0100 Subject: [PATCH 012/191] MOBILE-2839 config: Bump version number --- config.xml | 2 +- desktop/assets/windows/AppXManifest.xml | 2 +- package.json | 2 +- src/config.json | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/config.xml b/config.xml index c0e695b8f..7160f2643 100644 --- a/config.xml +++ b/config.xml @@ -1,5 +1,5 @@ - + Moodle Moodle official app Moodle Mobile team diff --git a/desktop/assets/windows/AppXManifest.xml b/desktop/assets/windows/AppXManifest.xml index 61785fce3..f674c51dc 100644 --- a/desktop/assets/windows/AppXManifest.xml +++ b/desktop/assets/windows/AppXManifest.xml @@ -6,7 +6,7 @@ + Version="3.6.1.0" /> Moodle Desktop Moodle Pty Ltd. diff --git a/package.json b/package.json index 6116e057b..fb53f3a6d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "moodlemobile", - "version": "3.6.0", + "version": "3.6.1", "description": "The official app for Moodle.", "author": { "name": "Moodle Pty Ltd.", diff --git a/src/config.json b/src/config.json index e9e052309..56244a5a5 100644 --- a/src/config.json +++ b/src/config.json @@ -2,8 +2,8 @@ "app_id": "com.moodle.moodlemobile", "appname": "Moodle Mobile", "desktopappname": "Moodle Desktop", - "versioncode": 3600, - "versionname": "3.6.0", + "versioncode": 3610, + "versionname": "3.6.1-dev", "cache_expiration_time": 300000, "default_lang": "en", "languages": { From 9654fcfbbb761711013485e3eccad7e38434b3d0 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 23 Jan 2019 15:58:13 +0100 Subject: [PATCH 013/191] MOBILE-2839 config: Unlock plugin and libraries versions --- config.xml | 46 ++++++------- package-lock.json | 2 +- package.json | 168 +++++++++++++++++++++++----------------------- 3 files changed, 108 insertions(+), 108 deletions(-) diff --git a/config.xml b/config.xml index 7160f2643..eba7301fa 100644 --- a/config.xml +++ b/config.xml @@ -94,33 +94,33 @@ - - + + - - - - + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/package-lock.json b/package-lock.json index 9fa7a402d..595f96ad1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "moodlemobile", - "version": "3.6.0", + "version": "3.6.1", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index fb53f3a6d..0d89d571b 100644 --- a/package.json +++ b/package.json @@ -39,99 +39,99 @@ "windows.store": "electron-windows-store --input-directory .\\desktop\\dist\\win-unpacked --output-directory .\\desktop\\store --flatten true -a .\\resources\\desktop -m .\\desktop\\assets\\windows\\AppXManifest.xml --package-version 0.0.0.0 --package-name MoodleDesktop" }, "dependencies": { - "@angular/animations": "5.2.10", - "@angular/common": "5.2.10", - "@angular/compiler": "5.2.10", - "@angular/compiler-cli": "5.2.10", - "@angular/core": "5.2.10", - "@angular/forms": "5.2.10", - "@angular/http": "5.2.10", - "@angular/platform-browser": "5.2.10", - "@angular/platform-browser-dynamic": "5.2.10", - "@ionic-native/badge": "4.17.0", - "@ionic-native/camera": "4.17.0", - "@ionic-native/clipboard": "4.17.0", - "@ionic-native/core": "4.11.0", - "@ionic-native/device": "4.17.0", - "@ionic-native/file": "4.17.0", - "@ionic-native/file-opener": "4.17.0", - "@ionic-native/file-transfer": "4.17.0", - "@ionic-native/globalization": "4.17.0", - "@ionic-native/in-app-browser": "4.17.0", - "@ionic-native/keyboard": "4.17.0", + "@angular/animations": "^5.2.10", + "@angular/common": "^5.2.10", + "@angular/compiler": "^5.2.10", + "@angular/compiler-cli": "^5.2.10", + "@angular/core": "^5.2.10", + "@angular/forms": "^5.2.10", + "@angular/http": "^5.2.10", + "@angular/platform-browser": "^5.2.10", + "@angular/platform-browser-dynamic": "^5.2.10", + "@ionic-native/badge": "^4.17.0", + "@ionic-native/camera": "^4.17.0", + "@ionic-native/clipboard": "^4.17.0", + "@ionic-native/core": "^4.11.0", + "@ionic-native/device": "^4.17.0", + "@ionic-native/file": "^4.17.0", + "@ionic-native/file-opener": "^4.17.0", + "@ionic-native/file-transfer": "^4.17.0", + "@ionic-native/globalization": "^4.17.0", + "@ionic-native/in-app-browser": "^4.17.0", + "@ionic-native/keyboard": "^4.17.0", "@ionic-native/local-notifications": "4.5.2", - "@ionic-native/media-capture": "4.17.0", - "@ionic-native/network": "4.17.0", - "@ionic-native/push": "4.17.0", - "@ionic-native/screen-orientation": "4.17.0", - "@ionic-native/splash-screen": "4.17.0", - "@ionic-native/sqlite": "4.17.0", - "@ionic-native/status-bar": "4.17.0", - "@ionic-native/web-intent": "4.17.0", - "@ionic-native/zip": "4.17.0", - "@ngx-translate/core": "8.0.0", - "@ngx-translate/http-loader": "2.0.1", - "@types/cordova": "0.0.34", - "@types/cordova-plugin-file-transfer": "0.0.3", - "@types/cordova-plugin-globalization": "0.0.3", - "@types/cordova-plugin-network-information": "0.0.3", - "@types/node": "8.10.19", - "@types/promise.prototype.finally": "2.0.2", - "chart.js": "2.7.2", - "com-darryncampbell-cordova-plugin-intent": "1.1.1", + "@ionic-native/media-capture": "^4.17.0", + "@ionic-native/network": "^4.17.0", + "@ionic-native/push": "^4.17.0", + "@ionic-native/screen-orientation": "^4.17.0", + "@ionic-native/splash-screen": "^4.17.0", + "@ionic-native/sqlite": "^4.17.0", + "@ionic-native/status-bar": "^4.17.0", + "@ionic-native/web-intent": "^4.17.0", + "@ionic-native/zip": "^4.17.0", + "@ngx-translate/core": "^8.0.0", + "@ngx-translate/http-loader": "^2.0.1", + "@types/cordova": "^0.0.34", + "@types/cordova-plugin-file-transfer": "^0.0.3", + "@types/cordova-plugin-globalization": "^0.0.3", + "@types/cordova-plugin-network-information": "^0.0.3", + "@types/node": "^8.10.19", + "@types/promise.prototype.finally": "^2.0.2", + "chart.js": "^2.7.2", + "com-darryncampbell-cordova-plugin-intent": "^1.1.1", "cordova-android": "7.1.2", - "cordova-android-support-gradle-release": "2.0.1", - "cordova-clipboard": "1.2.1", + "cordova-android-support-gradle-release": "^2.0.1", + "cordova-clipboard": "^1.2.1", "cordova-ios": "4.5.5", "cordova-plugin-app-event": "1.2.1", - "cordova-plugin-badge": "0.8.8", - "cordova-plugin-camera": "4.0.3", - "cordova-plugin-customurlscheme": "4.3.0", - "cordova-plugin-device": "2.0.2", - "cordova-plugin-file": "6.0.1", - "cordova-plugin-file-opener2": "2.0.19", - "cordova-plugin-file-transfer": "1.7.1", - "cordova-plugin-globalization": "1.11.0", - "cordova-plugin-inappbrowser": "3.0.0", - "cordova-plugin-ionic-keyboard": "2.1.3", - "cordova-plugin-local-notifications-mm": "1.0.13", - "cordova-plugin-media-capture": "3.0.2", - "cordova-plugin-network-information": "2.0.1", - "cordova-plugin-screen-orientation": "3.0.1", - "cordova-plugin-splashscreen": "5.0.2", - "cordova-plugin-statusbar": "2.4.2", - "cordova-plugin-whitelist": "1.3.3", - "cordova-plugin-zip": "3.1.0", - "cordova-sqlite-storage": "2.6.0", - "es6-promise-plugin": "4.2.2", - "font-awesome": "4.7.0", + "cordova-plugin-badge": "^0.8.8", + "cordova-plugin-camera": "^4.0.3", + "cordova-plugin-customurlscheme": "^4.3.0", + "cordova-plugin-device": "^2.0.2", + "cordova-plugin-file": "^6.0.1", + "cordova-plugin-file-opener2": "^2.0.19", + "cordova-plugin-file-transfer": "^1.7.1", + "cordova-plugin-globalization": "^1.11.0", + "cordova-plugin-inappbrowser": "^3.0.0", + "cordova-plugin-ionic-keyboard": "^2.1.3", + "cordova-plugin-local-notifications-mm": "^1.0.13", + "cordova-plugin-media-capture": "^3.0.2", + "cordova-plugin-network-information": "^2.0.1", + "cordova-plugin-screen-orientation": "^3.0.1", + "cordova-plugin-splashscreen": "^5.0.2", + "cordova-plugin-statusbar": "^2.4.2", + "cordova-plugin-whitelist": "^1.3.3", + "cordova-plugin-zip": "^3.1.0", + "cordova-sqlite-storage": "^2.6.0", + "es6-promise-plugin": "^4.2.2", + "font-awesome": "^4.7.0", "ionic-angular": "3.9.2", - "ionicons": "3.0.0", - "jszip": "3.1.5", - "moment": "2.22.2", - "nl.kingsquare.cordova.background-audio": "1.0.1", + "ionicons": "^3.0.0", + "jszip": "^3.1.5", + "moment": "^2.22.2", + "nl.kingsquare.cordova.background-audio": "^1.0.1", "phonegap-plugin-push": "git+https://github.com/moodlemobile/phonegap-plugin-push.git#moodle", - "promise.prototype.finally": "3.1.0", - "rxjs": "5.5.11", - "sw-toolbox": "3.6.0", - "ts-md5": "1.2.4", - "web-animations-js": "2.3.1", - "zone.js": "0.8.26" + "promise.prototype.finally": "^3.1.0", + "rxjs": "^5.5.11", + "sw-toolbox": "^3.6.0", + "ts-md5": "^1.2.4", + "web-animations-js": "^2.3.1", + "zone.js": "^0.8.26" }, "devDependencies": { "@ionic/app-scripts": "3.1.9", - "electron-rebuild": "1.8.1", - "electron-builder-lib": "20.23.1", - "gulp": "4.0.0", - "gulp-clip-empty-files": "0.1.2", - "gulp-flatten": "0.4.0", - "gulp-rename": "1.3.0", - "gulp-slash": "1.1.3", - "gulp-util": "3.0.8", - "node-loader": "0.6.0", - "through": "2.3.8", - "typescript": "2.6.2", - "webpack-merge": "4.1.2" + "electron-rebuild": "^1.8.1", + "electron-builder-lib": "^20.23.1", + "gulp": "^4.0.0", + "gulp-clip-empty-files": "^0.1.2", + "gulp-flatten": "^0.4.0", + "gulp-rename": "^1.3.0", + "gulp-slash": "^1.1.3", + "gulp-util": "^3.0.8", + "node-loader": "^0.6.0", + "through": "^2.3.8", + "typescript": "^2.6.2", + "webpack-merge": "^4.1.2" }, "browser": { "electron": false From 2a8c0986c6150197e3c4e662c20e8b24d0944671 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 23 Jan 2019 16:05:05 +0100 Subject: [PATCH 014/191] MOBILE-2839 config: Copy config.json to www --- config/copy.config.js | 6 +++++- www/README.md | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/config/copy.config.js b/config/copy.config.js index 1da283e97..7f0f0686f 100644 --- a/config/copy.config.js +++ b/config/copy.config.js @@ -1,4 +1,4 @@ -// New copy task for font files +// New copy task for font files and config.json. module.exports = { // Override Ionic copyFonts task to exclude Roboto and Noto fonts. copyFonts: { @@ -8,5 +8,9 @@ module.exports = { copyFontAwesome: { src: ['{{ROOT}}/node_modules/font-awesome/fonts/**/*'], dest: '{{WWW}}/assets/fonts' + }, + copyConfig: { + src: ['{{ROOT}}/src/config.json'], + dest: '{{WWW}}/' } }; diff --git a/www/README.md b/www/README.md index 10422efc9..9f900647d 100644 --- a/www/README.md +++ b/www/README.md @@ -1 +1,3 @@ This folder only contains compiled code and assets. Please do not modify files in this folder, the app code is in the "src" folder. + +The config.json values are ignored by the app. This file is informative for Continous Integration processes used by Moodle HQ. From 0a13f9890d09f347f6d2a98b9db82ca1f8e55227 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 23 Jan 2019 13:01:59 +0100 Subject: [PATCH 015/191] MOBILE-2814 localnotif: Update cordova plugin and ionic native --- config.xml | 2 +- package-lock.json | 19 +++++++------------ package.json | 7 +++---- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/config.xml b/config.xml index eba7301fa..a5eb4c297 100644 --- a/config.xml +++ b/config.xml @@ -111,7 +111,7 @@ - + diff --git a/package-lock.json b/package-lock.json index 595f96ad1..0d1a7b001 100644 --- a/package-lock.json +++ b/package-lock.json @@ -161,9 +161,9 @@ "integrity": "sha512-2BHO1bV4mehWZNfdsWQ/uojxYFNvk4I6u0KYnNb61RiJRY83joCEw3oFkOMRGLZthPf6TN1cueZUIAGMHXA3nA==" }, "@ionic-native/local-notifications": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/@ionic-native/local-notifications/-/local-notifications-4.5.2.tgz", - "integrity": "sha512-/O2hNsWW6ixlAPY9Tw6wfIIUmNOPmd11DcxCTQ5vR8+oGPyYPj3IXkgUCI/U29Y3hDikSxdWTI19FtCxnzYKNA==" + "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==" }, "@ionic-native/media-capture": { "version": "4.17.0", @@ -2233,11 +2233,6 @@ } } }, - "cordova-plugin-app-event": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/cordova-plugin-app-event/-/cordova-plugin-app-event-1.2.1.tgz", - "integrity": "sha1-DuuxQTKqQ7suXAgamr29l8otgTI=" - }, "cordova-plugin-badge": { "version": "0.8.8", "resolved": "https://registry.npmjs.org/cordova-plugin-badge/-/cordova-plugin-badge-0.8.8.tgz", @@ -2288,10 +2283,10 @@ "resolved": "https://registry.npmjs.org/cordova-plugin-ionic-keyboard/-/cordova-plugin-ionic-keyboard-2.1.3.tgz", "integrity": "sha512-6ucQ6FdlLdBm8kJfFnzozmBTjru/0xekHP/dAhjoCZggkGRlgs8TsUJFkxa/bV+qi7Dlo50JjmpE4UMWAO+aOQ==" }, - "cordova-plugin-local-notifications-mm": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/cordova-plugin-local-notifications-mm/-/cordova-plugin-local-notifications-mm-1.0.13.tgz", - "integrity": "sha512-uZjYumhkvLH6tYU7cmR9Qx9ho8xPS2/lBGgKRsejVDtBFNnkSkkR3X/at2MuBe3ZJ7qAnJdFAN4rMY3yd+dG/g==" + "cordova-plugin-local-notification": { + "version": "0.9.0-beta.2", + "resolved": "https://registry.npmjs.org/cordova-plugin-local-notification/-/cordova-plugin-local-notification-0.9.0-beta.2.tgz", + "integrity": "sha512-63n77K1pt8dnbWnNR8QWETi9Glezi1bvNHvHWmGNIOv0xCb0phZnm+Ku49BQ+omwe8Z5voMvrA4I03SYPpv38w==" }, "cordova-plugin-media-capture": { "version": "3.0.2", diff --git a/package.json b/package.json index 0d89d571b..7b9a7e4ed 100644 --- a/package.json +++ b/package.json @@ -59,7 +59,7 @@ "@ionic-native/globalization": "^4.17.0", "@ionic-native/in-app-browser": "^4.17.0", "@ionic-native/keyboard": "^4.17.0", - "@ionic-native/local-notifications": "4.5.2", + "@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", @@ -83,7 +83,6 @@ "cordova-android-support-gradle-release": "^2.0.1", "cordova-clipboard": "^1.2.1", "cordova-ios": "4.5.5", - "cordova-plugin-app-event": "1.2.1", "cordova-plugin-badge": "^0.8.8", "cordova-plugin-camera": "^4.0.3", "cordova-plugin-customurlscheme": "^4.3.0", @@ -94,7 +93,7 @@ "cordova-plugin-globalization": "^1.11.0", "cordova-plugin-inappbrowser": "^3.0.0", "cordova-plugin-ionic-keyboard": "^2.1.3", - "cordova-plugin-local-notifications-mm": "^1.0.13", + "cordova-plugin-local-notification": "^0.9.0-beta.2", "cordova-plugin-media-capture": "^3.0.2", "cordova-plugin-network-information": "^2.0.1", "cordova-plugin-screen-orientation": "^3.0.1", @@ -159,7 +158,7 @@ "cordova-plugin-globalization": {}, "cordova-plugin-inappbrowser": {}, "cordova-plugin-ionic-keyboard": {}, - "cordova-plugin-local-notifications-mm": {}, + "cordova-plugin-local-notification": {}, "cordova-plugin-media-capture": {}, "cordova-plugin-network-information": {}, "cordova-plugin-screen-orientation": {}, From 3f84adca9a55322cf9c1c0376a7ea80a472f2bc3 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 23 Jan 2019 13:08:45 +0100 Subject: [PATCH 016/191] MOBILE-2814 localnotif: Adapt the code to the new API --- src/addon/calendar/providers/calendar.ts | 15 ++-- .../providers/pushnotifications.ts | 14 ++-- src/providers/local-notifications.ts | 75 +++++++++---------- upgrade.txt | 4 + 4 files changed, 48 insertions(+), 60 deletions(-) diff --git a/src/addon/calendar/providers/calendar.ts b/src/addon/calendar/providers/calendar.ts index b02765ed0..d59bd9ce8 100644 --- a/src/addon/calendar/providers/calendar.ts +++ b/src/addon/calendar/providers/calendar.ts @@ -13,7 +13,6 @@ // limitations under the License. import { Injectable } from '@angular/core'; -import { TranslateService } from '@ngx-translate/core'; import { CoreLoggerProvider } from '@providers/logger'; import { CoreSitesProvider } from '@providers/sites'; import { CoreSite } from '@classes/site'; @@ -23,6 +22,7 @@ import { CoreGroupsProvider } from '@providers/groups'; import { CoreConstants } from '@core/constants'; import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; import { CoreConfigProvider } from '@providers/config'; +import { ILocalNotification } from '@ionic-native/local-notifications'; /** * Service to handle calendar events. @@ -132,8 +132,7 @@ export class AddonCalendarProvider { constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private groupsProvider: CoreGroupsProvider, private coursesProvider: CoreCoursesProvider, private timeUtils: CoreTimeUtilsProvider, - private localNotificationsProvider: CoreLocalNotificationsProvider, private configProvider: CoreConfigProvider, - private translate: TranslateService) { + private localNotificationsProvider: CoreLocalNotificationsProvider, private configProvider: CoreConfigProvider) { this.logger = logger.getInstance('AddonCalendarProvider'); this.sitesProvider.createTablesFromSchema(this.tablesSchema); } @@ -526,16 +525,12 @@ export class AddonCalendarProvider { return Promise.resolve(); } - const dateTriggered = new Date((event.timestart - (time * 60)) * 1000), - notification = { + const notification: ILocalNotification = { id: event.id, title: event.name, text: this.timeUtils.userDate(event.timestart * 1000, 'core.strftimedaydatetime', true), - at: dateTriggered, - channelParams: { - channelID: 'notifications', - channelName: this.translate.instant('addon.notifications.notifications'), - importance: 4 // IMPORTANCE_HIGH + trigger: { + at: new Date((event.timestart - (time * 60)) * 1000) }, data: { eventid: event.id, diff --git a/src/addon/pushnotifications/providers/pushnotifications.ts b/src/addon/pushnotifications/providers/pushnotifications.ts index 0d6c03283..01ac2318c 100644 --- a/src/addon/pushnotifications/providers/pushnotifications.ts +++ b/src/addon/pushnotifications/providers/pushnotifications.ts @@ -13,7 +13,6 @@ // limitations under the License. import { Injectable, NgZone } from '@angular/core'; -import { TranslateService } from '@ngx-translate/core'; import { Badge } from '@ionic-native/badge'; import { Push, PushObject, PushOptions } from '@ionic-native/push'; import { Device } from '@ionic-native/device'; @@ -28,6 +27,7 @@ import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreConfigProvider } from '@providers/config'; import { CoreConstants } from '@core/constants'; import { CoreConfigConstants } from '../../../configconstants'; +import { ILocalNotification } from '@ionic-native/local-notifications'; /** * Service to handle push notifications. @@ -66,8 +66,7 @@ export class AddonPushNotificationsProvider { protected pushNotificationsDelegate: AddonPushNotificationsDelegate, protected sitesProvider: CoreSitesProvider, private badge: Badge, private localNotificationsProvider: CoreLocalNotificationsProvider, private utils: CoreUtilsProvider, private textUtils: CoreTextUtilsProvider, private push: Push, - private configProvider: CoreConfigProvider, private device: Device, private zone: NgZone, - private translate: TranslateService) { + private configProvider: CoreConfigProvider, private device: Device, private zone: NgZone) { this.logger = logger.getInstance('AddonPushNotificationsProvider'); this.appDB = appProvider.getDB(); this.appDB.createTablesFromSchema(this.tablesSchema); @@ -153,13 +152,10 @@ export class AddonPushNotificationsProvider { if (this.utils.isTrueOrOne(data.foreground)) { // If the app is in foreground when the notification is received, it's not shown. Let's show it ourselves. if (this.localNotificationsProvider.isAvailable()) { - const localNotif = { + const localNotif: ILocalNotification = { id: 1, - at: new Date(), - channelParams: { - channelID: 'notifications', - channelName: this.translate.instant('addon.notifications.notifications'), - importance: 4 // IMPORTANCE_HIGH + trigger: { + at: new Date() }, data: { notif: data.notif, diff --git a/src/providers/local-notifications.ts b/src/providers/local-notifications.ts index 85b5e30fc..5b64a5f64 100644 --- a/src/providers/local-notifications.ts +++ b/src/providers/local-notifications.ts @@ -24,24 +24,7 @@ import { CoreTextUtilsProvider } from './utils/text'; import { CoreUtilsProvider } from './utils/utils'; import { SQLiteDB } from '@classes/sqlitedb'; import { CoreConstants } from '@core/constants'; -import { Subject } from 'rxjs'; - -/** - * Local notification. - */ -export interface CoreILocalNotification extends ILocalNotification { - /** - * Number of milliseconds to turn the led on (Android only). - * @type {number} - */ - ledOnTime?: number; - - /** - * Number of milliseconds to turn the led off (Android only). - * @type {number} - */ - ledOffTime?: number; -} +import { Subject, Subscription } from 'rxjs'; /* * Generated class for the LocalNotificationsProvider provider. @@ -115,6 +98,8 @@ export class CoreLocalNotificationsProvider { ids: [], timeouts: [] }; + protected triggerSubscription: Subscription; + protected clickSubscription: Subscription; constructor(logger: CoreLoggerProvider, private localNotifications: LocalNotifications, private platform: Platform, private appProvider: CoreAppProvider, private utils: CoreUtilsProvider, private configProvider: CoreConfigProvider, @@ -126,16 +111,15 @@ export class CoreLocalNotificationsProvider { this.appDB.createTablesFromSchema(this.tablesSchema); platform.ready().then(() => { - localNotifications.on('trigger', (notification, state) => { + this.triggerSubscription = localNotifications.on('trigger').subscribe((notification: ILocalNotification) => { this.trigger(notification); }); - localNotifications.on('click', (notification, state) => { + this.clickSubscription = localNotifications.on('click').subscribe((notification: ILocalNotification) => { if (notification && notification.data) { this.logger.debug('Notification clicked: ', notification.data); - const data = textUtils.parseJSON(notification.data); - this.notifyClick(data); + this.notifyClick(notification.data); } }); }); @@ -175,7 +159,7 @@ export class CoreLocalNotificationsProvider { return Promise.reject(null); } - return this.localNotifications.getAllScheduled().then((scheduled) => { + return this.localNotifications.getScheduled().then((scheduled) => { const ids = []; scheduled.forEach((notif) => { @@ -291,12 +275,18 @@ export class CoreLocalNotificationsProvider { /** * Check if a notification has been triggered with the same trigger time. * - * @param {CoreILocalNotification} notification Notification to check. + * @param {ILocalNotification} notification Notification to check. * @return {Promise} Promise resolved with a boolean indicating if promise is triggered (true) or not. */ - isTriggered(notification: CoreILocalNotification): Promise { + isTriggered(notification: ILocalNotification): Promise { return this.appDB.getRecord(this.TRIGGERED_TABLE, { id: notification.id }).then((stored) => { - return stored.at === notification.at.getTime() / 1000; + let triggered = (notification.trigger && notification.trigger.at) || 0; + + if (typeof triggered != 'number') { + triggered = triggered.getTime(); + } + + return stored.at === triggered; }).catch(() => { return this.localNotifications.isTriggered(notification.id); }); @@ -426,12 +416,11 @@ export class CoreLocalNotificationsProvider { */ rescheduleAll(): Promise { // Get all the scheduled notifications. - return this.localNotifications.getAllScheduled().then((notifications) => { + return this.localNotifications.getScheduled().then((notifications) => { const promises = []; notifications.forEach((notification) => { // Convert some properties to the needed types. - notification.at = new Date(notification.at * 1000); notification.data = notification.data ? this.textUtils.parseJSON(notification.data, {}) : {}; promises.push(this.scheduleNotification(notification)); @@ -444,13 +433,13 @@ export class CoreLocalNotificationsProvider { /** * Schedule a local notification. * - * @param {CoreILocalNotification} notification Notification to schedule. Its ID should be lower than 10000000 and it should - * be unique inside its component and site. + * @param {ILocalNotification} notification Notification to schedule. Its ID should be lower than 10000000 and it should + * be unique inside its component and site. * @param {string} component Component triggering the notification. It is used to generate unique IDs. * @param {string} siteId Site ID. * @return {Promise} Promise resolved when the notification is scheduled. */ - schedule(notification: CoreILocalNotification, component: string, siteId: string): Promise { + schedule(notification: ILocalNotification, component: string, siteId: string): Promise { return this.getUniqueNotificationId(notification.id, component, siteId).then((uniqueId) => { notification.id = uniqueId; notification.data = notification.data || {}; @@ -460,9 +449,13 @@ export class CoreLocalNotificationsProvider { if (this.platform.is('android')) { notification.icon = notification.icon || 'res://icon'; notification.smallIcon = notification.smallIcon || 'res://icon'; - notification.led = notification.led || 'FF9900'; - notification.ledOnTime = notification.ledOnTime || 1000; - notification.ledOffTime = notification.ledOffTime || 1000; + + const led: any = notification.led || {}; + notification.led = { + color: led.color || 'FF9900', + on: led.on || 1000, + off: led.off || 1000 + }; } return this.scheduleNotification(notification); @@ -472,10 +465,10 @@ export class CoreLocalNotificationsProvider { /** * Helper function to schedule a notification object if it hasn't been triggered already. * - * @param {CoreILocalNotification} notification Notification to schedule. + * @param {ILocalNotification} notification Notification to schedule. * @return {Promise} Promise resolved when scheduled. */ - protected scheduleNotification(notification: CoreILocalNotification): Promise { + protected scheduleNotification(notification: ILocalNotification): Promise { // Check if the notification has been triggered already. return this.isTriggered(notification).then((triggered) => { // Cancel the current notification in case it gets scheduled twice. @@ -503,9 +496,9 @@ export class CoreLocalNotificationsProvider { * This function was used because local notifications weren't displayed when the app was in foreground in iOS10+, * but the issue was fixed in the plugin and this function is no longer used. * - * @param {CoreILocalNotification} notification Notification. + * @param {ILocalNotification} notification Notification. */ - showNotificationPopover(notification: CoreILocalNotification): void { + showNotificationPopover(notification: ILocalNotification): void { if (!notification || !notification.title || !notification.text) { // Invalid data. @@ -595,13 +588,13 @@ export class CoreLocalNotificationsProvider { * Function to call when a notification is triggered. Stores the notification so it's not scheduled again unless the * time is changed. * - * @param {CoreILocalNotification} notification Triggered notification. + * @param {ILocalNotification} notification Triggered notification. * @return {Promise} Promise resolved when stored, rejected otherwise. */ - trigger(notification: CoreILocalNotification): Promise { + trigger(notification: ILocalNotification): Promise { const entry = { id: notification.id, - at: parseInt(notification.at, 10) + at: notification.trigger && notification.trigger.at ? notification.trigger.at : Date.now() }; return this.appDB.insertRecord(this.TRIGGERED_TABLE, entry); diff --git a/upgrade.txt b/upgrade.txt index 695c38d0f..1e31823f2 100644 --- a/upgrade.txt +++ b/upgrade.txt @@ -1,6 +1,10 @@ This files describes API changes in the Moodle Mobile app, information provided here is intended especially for developers. +=== 3.6.1 === + +- The local notifications plugin was updated to its latest version. The new API has some breaking changes, so please check its documentation if you're using local notifications. Also, you need to run "npm install" to update the ionic-native library. + === 3.6.0 === - gulp was updated to v4. In order for gulp to work, you need to install gulp-cli: npm install -g gulp-cli From 5c85a3345ada9d82d8836d45b03c1b3aeabc482f Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 23 Jan 2019 13:20:25 +0100 Subject: [PATCH 017/191] MOBILE-2814 localnotif: Adapt emulator to the new API --- src/core/emulator/providers/helper.ts | 8 +- .../emulator/providers/local-notifications.ts | 552 +++++++++++++----- 2 files changed, 425 insertions(+), 135 deletions(-) diff --git a/src/core/emulator/providers/helper.ts b/src/core/emulator/providers/helper.ts index 53317da62..4c866ebc8 100644 --- a/src/core/emulator/providers/helper.ts +++ b/src/core/emulator/providers/helper.ts @@ -16,7 +16,7 @@ import { Injectable } from '@angular/core'; import { CoreFileProvider } from '@providers/file'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { File } from '@ionic-native/file'; -import { LocalNotifications } from '@ionic-native/local-notifications'; +import { LocalNotifications, ILocalNotification } from '@ionic-native/local-notifications'; import { CoreAppProvider } from '@providers/app'; import { CoreInitDelegate, CoreInitHandler } from '@providers/init'; import { CoreLoggerProvider } from '@providers/logger'; @@ -157,9 +157,11 @@ export class CoreEmulatorHelperProvider implements CoreInitHandler { // There is a new notification, show it. return getDataFn(notification).then((titleAndText) => { - const localNotif = { + const localNotif: ILocalNotification = { id: 1, - at: new Date(), + trigger: { + at: new Date() + }, title: titleAndText.title, text: titleAndText.text, data: { diff --git a/src/core/emulator/providers/local-notifications.ts b/src/core/emulator/providers/local-notifications.ts index d1685ec80..0936960e7 100644 --- a/src/core/emulator/providers/local-notifications.ts +++ b/src/core/emulator/providers/local-notifications.ts @@ -13,7 +13,7 @@ // limitations under the License. import { Injectable } from '@angular/core'; -import { LocalNotifications, ILocalNotification } from '@ionic-native/local-notifications'; +import { LocalNotifications, ILocalNotification, ILocalNotificationAction } from '@ionic-native/local-notifications'; import { CoreAppProvider } from '@providers/app'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreUtilsProvider } from '@providers/utils/utils'; @@ -21,6 +21,7 @@ import { SQLiteDB } from '@classes/sqlitedb'; import { CoreConstants } from '@core/constants'; import { CoreConfigConstants } from '../../../configconstants'; import * as moment from 'moment'; +import { Subject, Observable } from 'rxjs'; /** * Emulates the Cordova Globalization plugin in desktop apps and in browser. @@ -76,16 +77,40 @@ export class LocalNotificationsMock extends LocalNotifications { protected appDB: SQLiteDB; protected scheduled: { [i: number]: any } = {}; protected triggered: { [i: number]: any } = {}; - protected observers; + protected observers: {[event: string]: Subject}; protected defaults = { - text: '', - title: '', - sound: '', - badge: 0, - id: 0, - data: undefined, - every: undefined, - at: undefined + actions : [], + attachments : [], + autoClear : true, + badge : null, + channel : null, + clock : true, + color : null, + data : null, + defaults : 0, + foreground : null, + group : null, + groupSummary : false, + icon : null, + id : 0, + launch : true, + led : true, + lockscreen : true, + mediaSession : null, + number : 0, + priority : 0, + progressBar : false, + silent : false, + smallIcon : 'res://icon', + sound : true, + sticky : false, + summary : null, + text : '', + timeoutAfter : false, + title : '', + trigger : { type : 'calendar' }, + vibrate : false, + wakeup : true }; constructor(private appProvider: CoreAppProvider, private utils: CoreUtilsProvider, private textUtils: CoreTextUtilsProvider) { @@ -96,20 +121,32 @@ export class LocalNotificationsMock extends LocalNotifications { // Initialize observers. this.observers = { - schedule: [], - trigger: [], - click: [], - update: [], - clear: [], - clearall: [], - cancel: [], - cancelall: [] + schedule: new Subject(), + trigger: new Subject(), + click: new Subject(), + update: new Subject(), + clear: new Subject(), + clearall: new Subject(), + cancel: new Subject(), + cancelall: new Subject(), }; } /** - * Cancels single or multiple notifications - * @param notificationId {any} A single notification id, or an array of notification ids. + * Adds a group of actions. + * + * @param {any} groupId The id of the action group + * @param {ILocalNotificationAction[]} actions The actions of this group + * @returns {Promise} + */ + addActions(groupId: any, actions: ILocalNotificationAction[]): Promise { + return Promise.reject('Not supported in desktop apps.'); + } + + /** + * Cancels single or multiple notifications. + * + * @param {any} notificationId A single notification id, or an array of notification ids. * @returns {Promise} Returns a promise when the notification is canceled */ cancel(notificationId: any): Promise { @@ -135,7 +172,11 @@ export class LocalNotificationsMock extends LocalNotifications { */ cancelAll(): Promise { return this.cancel(Object.keys(this.scheduled)).then(() => { - this.triggerEvent('cancelall', 'foreground'); + this.fireEvent('cancelall', { + event: 'cancelall', + foreground: true, + queued: false + }); }); } @@ -162,7 +203,7 @@ export class LocalNotificationsMock extends LocalNotifications { this.removeNotification(id); if (!omitEvent) { - this.triggerEvent(eventName, notification, 'foreground'); + this.fireEvent(eventName, notification); } } @@ -181,7 +222,8 @@ export class LocalNotificationsMock extends LocalNotifications { // Clear the notifications. notificationId.forEach((id) => { // Cancel only the notifications that aren't repeating. - if (this.scheduled[id] && this.scheduled[id].notification && !this.scheduled[id].notification.every) { + if (this.scheduled[id] && this.scheduled[id].notification && + (!this.scheduled[id].notification.trigger || !this.scheduled[id].notification.trigger.every)) { promises.push(this.cancelNotification(id, false, 'clear')); } }); @@ -195,7 +237,11 @@ export class LocalNotificationsMock extends LocalNotifications { */ clearAll(): Promise { return this.clear(Object.keys(this.scheduled)).then(() => { - this.triggerEvent('clearall', 'foreground'); + this.fireEvent('clearall', { + event: 'clearall', + foreground: true, + queued: false + }); }); } @@ -225,44 +271,251 @@ export class LocalNotificationsMock extends LocalNotifications { */ protected convertProperties(notification: ILocalNotification): ILocalNotification { if (notification.id) { - if (isNaN(notification.id)) { - notification.id = this.defaults.id; - } else { - notification.id = Number(notification.id); - } + notification.id = this.parseToInt('id', notification); } if (notification.title) { notification.title = notification.title.toString(); } - if (notification.text) { - notification.text = notification.text.toString(); - } - if (notification.badge) { - if (isNaN(notification.badge)) { - notification.badge = this.defaults.badge; - } else { - notification.badge = Number(notification.badge); - } + notification.badge = this.parseToInt('badge', notification); } - if (notification.at) { - if (typeof notification.at == 'object') { - notification.at = notification.at.getTime(); - } + if (notification.defaults) { + notification.defaults = this.parseToInt('defaults', notification); + } - notification.at = Math.round(notification.at / 1000); + if (typeof notification.timeoutAfter === 'boolean') { + notification.timeoutAfter = notification.timeoutAfter ? 3600000 : null; + } + + if (notification.timeoutAfter) { + notification.timeoutAfter = this.parseToInt('timeoutAfter', notification); } if (typeof notification.data == 'object') { notification.data = JSON.stringify(notification.data); } + this.convertPriority(notification); + this.convertTrigger(notification); + this.convertActions(notification); + this.convertProgressBar(notification); + return notification; } + /** + * Parse a property to number, returning the default value if not valid. + * Code extracted from the Cordova plugin. + * + * @param {string} prop Name of property to convert. + * @param {any} notification Notification where to search the property. + * @return {number} Converted number or default value. + */ + protected parseToInt(prop: string, notification: any): number { + if (isNaN(notification[prop])) { + return this.defaults[prop]; + } else { + return Number(notification[prop]); + } + } + + /** + * Convert the priority of a notification. + * Code extracted from the Cordova plugin. + * + * @param {any} notification Notification. + * @return {any} Notification. + */ + protected convertPriority(notification: any): any { + let prio = notification.priority || notification.prio || 0; + + if (typeof prio === 'string') { + prio = { min: -2, low: -1, high: 1, max: 2 }[prio] || 0; + } + + if (notification.foreground === true) { + prio = Math.max(prio, 1); + } + + if (notification.foreground === false) { + prio = Math.min(prio, 0); + } + + notification.priority = prio; + + return notification; + } + + /** + * Convert the actions of a notification. + * Code extracted from the Cordova plugin. + * + * @param {any} notification Notification. + * @return {any} Notification. + */ + protected convertActions(notification: any): any { + const actions = []; + + if (!notification.actions || typeof notification.actions === 'string') { + return notification; + } + + for (let i = 0, len = notification.actions.length; i < len; i++) { + const action = notification.actions[i]; + + if (!action.id) { + // Ignore action, it has no ID. + continue; + } + + action.id = action.id.toString(); + + actions.push(action); + } + + notification.actions = actions; + + return notification; + } + + /** + * Convert the trigger of a notification. + * Code extracted from the Cordova plugin. + * + * @param {any} notification Notification. + * @return {any} Notification. + */ + protected convertTrigger(notification: any): any { + const trigger = notification.trigger || {}; + let date = this.getValueFor(trigger, 'at', 'firstAt', 'date'); + + const dateToNum = (date: any): number => { + const num = typeof date == 'object' ? date.getTime() : date; + + return Math.round(num); + }; + + if (!notification.trigger) { + return notification; + } + + if (!trigger.type) { + trigger.type = trigger.center ? 'location' : 'calendar'; + } + + const isCal = trigger.type == 'calendar'; + + if (isCal && !date) { + date = this.getValueFor(notification, 'at', 'firstAt', 'date'); + } + + if (isCal && !trigger.every && notification.every) { + trigger.every = notification.every; + } + + if (isCal && (trigger.in || trigger.every)) { + date = null; + } + + if (isCal && date) { + trigger.at = dateToNum(date); + } + + if (isCal && trigger.firstAt) { + trigger.firstAt = dateToNum(trigger.firstAt); + } + + if (isCal && trigger.before) { + trigger.before = dateToNum(trigger.before); + } + + if (isCal && trigger.after) { + trigger.after = dateToNum(trigger.after); + } + + if (!trigger.count) { + trigger.count = trigger.every ? 5 : 1; + } + + if (!isCal) { + trigger.notifyOnEntry = !!trigger.notifyOnEntry; + trigger.notifyOnExit = trigger.notifyOnExit === true; + trigger.radius = trigger.radius || 5; + trigger.single = !!trigger.single; + } + + if (!isCal || trigger.at) { + delete trigger.every; + } + + delete notification.every; + delete notification.at; + delete notification.firstAt; + delete notification.date; + + notification.trigger = trigger; + + return notification; + } + + /** + * Convert the progress bar of a notification. + * Code extracted from the Cordova plugin. + * + * @param {any} notification Notification. + * @return {any} Notification. + */ + protected convertProgressBar(notification: any): any { + let cfg = notification.progressBar; + + if (cfg === undefined) { + return notification; + } + + if (typeof cfg === 'boolean') { + cfg = notification.progressBar = { enabled: cfg }; + } + + if (typeof cfg.enabled !== 'boolean') { + cfg.enabled = !!(cfg.value || cfg.maxValue || cfg.indeterminate !== null); + } + + cfg.value = cfg.value || 0; + + cfg.enabled = !!cfg.enabled; + + if (cfg.enabled && notification.clock === true) { + notification.clock = 'chronometer'; + } + + return notification; + } + + /** + * Not an official interface, however its possible to manually fire events. + * + * @param {string} eventName The name of the event. Available events: schedule, trigger, click, update, clear, clearall, cancel, + * cancelall. Custom event names are possible for actions + * @param {any} args Optional arguments + */ + fireEvent(eventName: string, args: any): void { + if (this.observers[eventName]) { + this.observers[eventName].next(args); + } + } + + /** + * Fire queued events once the device is ready and all listeners are registered. + * + * @returns {Promise} + */ + fireQueuedEvents(): Promise { + return Promise.resolve(); + } + /** * Get a notification object. * @@ -282,12 +535,21 @@ export class LocalNotificationsMock extends LocalNotifications { return Promise.resolve(this.getNotifications(undefined, true, true)); } + /** + * Gets the (platform specific) default settings. + * + * @returns {Promise} An object with all default settings + */ + getDefaults(): Promise { + return Promise.resolve(this.defaults); + } + /** * Get all the notification ids. * * @returns {Promise>} */ - getAllIds(): Promise> { + getIds(): Promise> { let ids = this.utils.mergeArraysWithoutDuplicates(Object.keys(this.scheduled), Object.keys(this.triggered)); ids = ids.map((id) => { return Number(id); @@ -304,9 +566,13 @@ export class LocalNotificationsMock extends LocalNotifications { protected getAllNotifications(): Promise { return this.appDB.getAllRecords(this.DESKTOP_NOTIFS_TABLE).then((notifications) => { notifications.forEach((notification) => { - notification.at = new Date(notification.at); + notification.trigger = { + at: new Date(notification.at) + }; notification.data = this.textUtils.parseJSON(notification.data); notification.triggered = !!notification.triggered; + + this.mergeWithDefaults(notification); }); return notifications; @@ -318,7 +584,7 @@ export class LocalNotificationsMock extends LocalNotifications { * * @returns {Promise>} */ - getAllScheduled(): Promise> { + getScheduled(): Promise> { return Promise.resolve(this.getNotifications(undefined, true, false)); } @@ -327,14 +593,14 @@ export class LocalNotificationsMock extends LocalNotifications { * * @returns {Promise>} */ - getAllTriggered(): Promise> { + getTriggered(): Promise> { return Promise.resolve(this.getNotifications(undefined, false, true)); } /** * Get a set of notifications. If ids isn't specified, return all the notifications. * - * @param {Number[]} [ids] Ids of notifications to get. If not specified, get all notifications. + * @param {number[]} [ids] Ids of notifications to get. If not specified, get all notifications. * @param {boolean} [getScheduled] Get scheduled notifications. * @param {boolean} [getTriggered] Get triggered notifications. * @return {ILocalNotification[]} List of notifications. @@ -362,13 +628,19 @@ export class LocalNotificationsMock extends LocalNotifications { } /** - * Get a scheduled notification object. + * Get the trigger "at" in milliseconds. * - * @param {any} notificationId The id of the notification to ge. - * @returns {Promise} + * @param {ILocalNotification} notification Notification to get the trigger from. + * @return {number} Trigger time. */ - getScheduled(notificationId: any): Promise { - return Promise.resolve(this.getNotifications([Number(notificationId)], true, false)[0]); + protected getNotificationTriggerAt(notification: ILocalNotification): number { + const triggerAt = (notification.trigger && notification.trigger.at) || 0; + + if (typeof triggerAt != 'number') { + return triggerAt.getTime(); + } + + return triggerAt; } /** @@ -384,16 +656,6 @@ export class LocalNotificationsMock extends LocalNotifications { return Promise.resolve(ids); } - /** - * Get a triggered notification object. - * - * @param {any} notificationId The id of the notification to get. - * @returns {Promise} - */ - getTriggered(notificationId: any): Promise { - return Promise.resolve(this.getNotifications([Number(notificationId)], false, true)[0]); - } - /** * Get the ids of triggered notifications. * @@ -407,12 +669,28 @@ export class LocalNotificationsMock extends LocalNotifications { return Promise.resolve(ids); } + /** + * Get the type (triggered, scheduled) for the notification. + * + * @param {number} id The ID of the notification. + * @return {Promise} + */ + getType(id: number): Promise { + if (this.scheduled[id]) { + return Promise.resolve('scheduled'); + } else if (this.triggered[id]) { + return Promise.resolve('triggered'); + } else { + return Promise.resolve('unknown'); + } + } + /** * Given an object of options and a list of properties, return the first property that exists. * Code extracted from the Cordova plugin. * * @param {ILocalNotification} notification Notification. - * @param {any} ...args List of keys to check. + * @param {any[]} ...args List of keys to check. * @return {any} First value found. */ protected getValueFor(notification: ILocalNotification, ...args: any[]): any { @@ -424,6 +702,16 @@ export class LocalNotificationsMock extends LocalNotifications { } } + /** + * Checks if a group of actions is defined. + * + * @param {any} groupId The id of the action group + * @returns {Promise} Whether the group is defined. + */ + hasActions(groupId: any): Promise { + return Promise.resolve(false); + } + /** * Informs if the app has the permission to show notifications. * @@ -433,6 +721,19 @@ export class LocalNotificationsMock extends LocalNotifications { return Promise.resolve(true); } + /** + * Check if a notification has a given type. + * + * @param {number} id The ID of the notification. + * @param {string} type The type of the notification. + * @returns {Promise} Promise resolved with boolean: whether it has the type. + */ + hasType(id: number, type: string): Promise { + return this.getType(id).then((notifType) => { + return type == notifType; + }); + } + /** * Checks presence of a notification. * @@ -512,26 +813,22 @@ export class LocalNotificationsMock extends LocalNotifications { * @return {ILocalNotification} Treated notification. */ protected mergeWithDefaults(notification: ILocalNotification): ILocalNotification { - notification.at = this.getValueFor(notification, 'at', 'firstAt', 'date'); - notification.text = this.getValueFor(notification, 'text', 'message'); - notification.data = this.getValueFor(notification, 'data', 'json'); + const values = this.getDefaults(); - if (notification.at === undefined || notification.at === null) { - notification.at = new Date(); + if (values.hasOwnProperty('sticky')) { + notification.sticky = this.getValueFor(notification, 'sticky', 'ongoing'); } - for (const key in this.defaults) { - if (notification[key] === null || notification[key] === undefined) { - if (notification.hasOwnProperty(key) && ['data', 'sound'].indexOf(key) > -1) { - notification[key] = undefined; - } else { - notification[key] = this.defaults[key]; - } - } + if (notification.sticky && notification.autoClear !== true) { + notification.autoClear = false; } - for (const key in notification) { - if (!this.defaults.hasOwnProperty(key)) { + Object.assign(values, notification); + + for (const key in values) { + if (values[key] !== null) { + notification[key] = values[key]; + } else { delete notification[key]; } } @@ -545,7 +842,7 @@ export class LocalNotificationsMock extends LocalNotifications { * @param {ILocalNotification} notification Clicked notification. */ protected notificationClicked(notification: ILocalNotification): void { - this.triggerEvent('click', notification, 'foreground'); + this.fireEvent('click', notification); // Focus the app. require('electron').ipcRenderer.send('focusApp'); } @@ -553,20 +850,16 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Sets a callback for a specific event. * - * @param {string} eventName Name of the event. Events: schedule, trigger, click, update, clear, clearall, cancel, cancelall - * @param {any} callback Call back function. + * @param {string} eventName The name of the event. Events: schedule, trigger, click, update, clear, clearall, cancel, + * cancelall. Custom event names are possible for actions. + * @return {Observable} Observable */ - on(eventName: string, callback: any): void { - if (!this.observers[eventName] || typeof callback != 'function') { - // Event not supported, stop. - return; - } - this.observers[eventName].push(callback); + on(eventName: string): Observable { + return this.observers[eventName]; } /** * Parse a interval and convert it to a number of milliseconds (0 if not valid). - * Code extracted from the Cordova plugin. * * @param {string} every Interval to convert. * @return {number} Number of milliseconds of the interval- @@ -607,12 +900,13 @@ export class LocalNotificationsMock extends LocalNotifications { } /** - * Register permission to show notifications if not already granted. + * Removes a group of actions. * - * @returns {Promise} + * @param {any} groupId The id of the action group + * @returns {Promise} */ - registerPermission(): Promise { - return Promise.resolve(true); + removeActions(groupId: any): Promise { + return Promise.reject('Not supported in desktop apps.'); } /** @@ -625,6 +919,15 @@ export class LocalNotificationsMock extends LocalNotifications { return this.appDB.deleteRecords(this.DESKTOP_NOTIFS_TABLE, { id: id }); } + /** + * Request permission to show notifications if not already granted. + * + * @returns {Promise} + */ + requestPermission(): Promise { + return Promise.resolve(true); + } + /** * Schedules a single or multiple notifications. * @@ -656,13 +959,15 @@ export class LocalNotificationsMock extends LocalNotifications { }; this.storeNotification(notification, false); - if (Math.abs(moment().diff(notification.at * 1000, 'days')) > 15) { + const triggerAt = this.getNotificationTriggerAt(notification); + + if (Math.abs(moment().diff(triggerAt, 'days')) > 15) { // Notification should trigger more than 15 days from now, don't schedule it. return; } // Schedule the notification. - const toTriggerTime = notification.at * 1000 - Date.now(), + const toTriggerTime = triggerAt - Date.now(), trigger = (): void => { // Trigger the notification. this.triggerNotification(notification); @@ -672,10 +977,12 @@ export class LocalNotificationsMock extends LocalNotifications { this.storeNotification(notification, true); // Launch the trigger event. - this.triggerEvent('trigger', notification, 'foreground'); + this.fireEvent('trigger', notification); - if (notification.every && this.scheduled[notification.id] && !this.scheduled[notification.id].interval) { - const interval = this.parseInterval(notification.every); + if (notification.trigger.every && this.scheduled[notification.id] && + !this.scheduled[notification.id].interval) { + + const interval = this.parseInterval(notification.trigger.every); if (interval > 0) { this.scheduled[notification.id].interval = setInterval(trigger, interval); } @@ -685,10 +992,22 @@ export class LocalNotificationsMock extends LocalNotifications { this.scheduled[notification.id].timeout = setTimeout(trigger, toTriggerTime); // Launch the scheduled/update event. - this.triggerEvent(eventName, notification, 'foreground'); + this.fireEvent(eventName, notification); }); } + /** + * Overwrites the (platform specific) default settings. + * + * @param {any} defaults The defaults to set. + * @returns {Promise} + */ + setDefaults(defaults: any): Promise { + this.defaults = defaults; + + return Promise.resolve(); + } + /** * Store a notification in local DB. * @@ -702,7 +1021,7 @@ export class LocalNotificationsMock extends LocalNotifications { id : notification.id, title: notification.title, text: notification.text, - at: notification.at ? (typeof notification.at == 'object' ? notification.at.getTime() : notification.at) : 0, + at: this.getNotificationTriggerAt(notification), data: notification.data ? JSON.stringify(notification.data) : '{}', triggered: triggered ? 1 : 0 }; @@ -710,20 +1029,6 @@ export class LocalNotificationsMock extends LocalNotifications { return this.appDB.insertRecord(this.DESKTOP_NOTIFS_TABLE, entry); } - /** - * Trigger an event. - * - * @param {string} eventName Event name. - * @param {any[]} ...args List of parameters to pass. - */ - protected triggerEvent(eventName: string, ...args: any[]): void { - if (this.observers[eventName]) { - this.observers[eventName].forEach((callback) => { - callback.apply(null, args); - }); - } - } - /** * Trigger a notification, using the best method depending on the OS. * @@ -763,7 +1068,7 @@ export class LocalNotificationsMock extends LocalNotifications { } else { // Use Electron default notifications. const notifInstance = new Notification(notification.title, { - body: notification.text + body: notification.text }); // Listen for click events. @@ -773,23 +1078,6 @@ export class LocalNotificationsMock extends LocalNotifications { } } - /** - * Removes a callback of a specific event. - * - * @param {string} eventName Name of the event. Events: schedule, trigger, click, update, clear, clearall, cancel, cancelall - * @param {any} callback Call back function. - */ - un(eventName: string, callback: any): void { - if (this.observers[eventName] && this.observers[eventName].length) { - for (let i = 0; i < this.observers[eventName].length; i++) { - if (this.observers[eventName][i] == callback) { - this.observers[eventName].splice(i, 1); - break; - } - } - } - } - /** * Updates a previously scheduled notification. Must include the id in the options parameter. * From b4a0fe9a39cd180b171a6246d783a3a1fc9194d5 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 24 Jan 2019 09:26:34 +0100 Subject: [PATCH 018/191] MOBILE-2814 npm: Fix vulnerabilities --- package-lock.json | 777 +++++++++++++++++++++------------------------- package.json | 4 +- 2 files changed, 348 insertions(+), 433 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0d1a7b001..f5fe1237d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -211,35 +211,36 @@ "integrity": "sha512-tv3R0fvOsGRHQO8ILKElG2DAJESsMsRJqdZ7VkvzepXu2WAYYMNIK/YNNJESy9sQWfGruq9aj94d6p0NMOdtng==" }, "@ionic/app-scripts": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/@ionic/app-scripts/-/app-scripts-3.1.9.tgz", - "integrity": "sha512-Vf2t9X+Zu5Q+lAKNOM0cwePKul5z1qWhcpyaI/Br7/1vs/ERF+iL4gJUbfYDYC47FFPdxJmhsHrfhnv+RoXJ/A==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@ionic/app-scripts/-/app-scripts-3.2.1.tgz", + "integrity": "sha512-HEGTPTpcw/qYIP6tbeLV84YABOxeSBhd+92vbz63sS3yowNe8CZxTn3QjyMVVd+Wn00lmFpiuQUclSW5C6o0sg==", "dev": true, "requires": { "@angular-devkit/build-optimizer": "0.0.35", - "autoprefixer": "^7.1.6", - "chalk": "^2.3.0", + "autoprefixer": "^7.2.6", + "chalk": "^2.4.0", "chokidar": "^1.7.0", - "clean-css": "^4.1.9", + "clean-css": "^4.1.11", "cross-spawn": "^5.1.0", - "express": "^4.16.2", + "dotenv-webpack": "^1.5.7", + "express": "^4.16.3", "fs-extra": "^4.0.2", "glob": "^7.1.2", "json-loader": "^0.5.7", - "node-sass": "4.7.2", + "node-sass": "^4.10.0", "os-name": "^2.0.1", - "postcss": "^6.0.13", + "postcss": "^6.0.21", "proxy-middleware": "^0.15.0", "reflect-metadata": "^0.1.10", "rollup": "0.50.0", "rollup-plugin-commonjs": "8.2.6", "rollup-plugin-node-resolve": "3.0.0", "source-map": "^0.6.1", - "tiny-lr": "^1.0.5", + "tiny-lr": "^1.1.1", "tslint": "^5.8.0", "tslint-eslint-rules": "^4.1.1", "uglify-es": "3.2.2", - "webpack": "3.8.1", + "webpack": "3.12.0", "ws": "3.3.2", "xml2js": "^0.4.19" } @@ -312,9 +313,9 @@ } }, "acorn": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.6.2.tgz", - "integrity": "sha512-zUzo1E5dI2Ey8+82egfnttyMlMZ2y0D8xOCO3PNPPlYXpl8NZvF6Qk9L9BEtJs+43FqEmfBViDqc5d1ckRDguw==", + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz", + "integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==", "dev": true }, "acorn-dynamic-import": { @@ -347,9 +348,9 @@ } }, "ajv-keywords": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", - "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", + "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=", "dev": true }, "align-text": { @@ -668,9 +669,9 @@ } }, "assert-plus": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", - "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", "dev": true }, "assign-symbols": { @@ -767,15 +768,15 @@ } }, "aws-sign2": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", - "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", "dev": true }, "aws4": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", - "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz", + "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", "dev": true }, "babel-code-frame": { @@ -928,9 +929,9 @@ "dev": true }, "big.js": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", - "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", "dev": true }, "binary-extensions": { @@ -1005,30 +1006,21 @@ } }, "body-parser": { - "version": "1.18.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", - "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", "dev": true, "requires": { "bytes": "3.0.0", "content-type": "~1.0.4", "debug": "2.6.9", - "depd": "~1.1.1", - "http-errors": "~1.6.2", - "iconv-lite": "0.4.19", + "depd": "~1.1.2", + "http-errors": "~1.6.3", + "iconv-lite": "0.4.23", "on-finished": "~2.3.0", - "qs": "6.5.1", - "raw-body": "2.3.2", - "type-is": "~1.6.15" - } - }, - "boom": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", - "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", - "dev": true, - "requires": { - "hoek": "2.x.x" + "qs": "6.5.2", + "raw-body": "2.3.3", + "type-is": "~1.6.16" } }, "brace-expansion": { @@ -1057,9 +1049,9 @@ "dev": true }, "browser-resolve": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.2.tgz", - "integrity": "sha1-j/CbCixCFxihBRwmCzLkj0QpOM4=", + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.3.tgz", + "integrity": "sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==", "dev": true, "requires": { "resolve": "1.1.7" @@ -1099,14 +1091,15 @@ } }, "browserify-des": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.1.tgz", - "integrity": "sha512-zy0Cobe3hhgpiOM32Tj7KQ3Vl91m0njwsjzZQK1L+JDf11dzP9qIvjreVinsvXrgfjhStXwUWAEpB9D7Gwmayw==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", "dev": true, "requires": { "cipher-base": "^1.0.1", "des.js": "^1.0.0", - "inherits": "^2.0.1" + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" } }, "browserify-rsa": { @@ -1320,15 +1313,15 @@ } }, "caniuse-lite": { - "version": "1.0.30000852", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000852.tgz", - "integrity": "sha512-NOuitABlrRbIpjtC8HdDnHL9Fi+yH5phDoXlXT7Im++48kll2bUps9dWWdAnBwqT/oEsjobuOLnnJCBjVqadCw==", + "version": "1.0.30000930", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000930.tgz", + "integrity": "sha512-KD+pw9DderBLB8CGqBzYyFWpnrPVOEjsjargU/CvkNyg60od3cxSPTcTeMPhxJhDbkQPWvOz5BAyBzNl/St9vg==", "dev": true }, "caseless": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", - "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=", + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", "dev": true }, "center-align": { @@ -1446,20 +1439,12 @@ } }, "clean-css": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.1.11.tgz", - "integrity": "sha1-Ls3xRaujj1R0DybO/Q/z4D4SXWo=", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.1.tgz", + "integrity": "sha512-4ZxI6dy4lrY6FHzfiy1aEOXgu4LIsW2MhwG0VBKdcoGoH/XLFgaHSdLTGr4O8Be6A8r3MOphEiI8Gc1n0ecf3g==", "dev": true, "requires": { - "source-map": "0.5.x" - }, - "dependencies": { - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true - } + "source-map": "~0.6.0" } }, "cli-cursor": { @@ -1598,9 +1583,9 @@ } }, "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz", + "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==", "dev": true }, "compare-version": { @@ -2394,15 +2379,6 @@ "which": "^1.2.9" } }, - "cryptiles": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", - "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", - "dev": true, - "requires": { - "boom": "2.x.x" - } - }, "crypto-browserify": { "version": "3.12.0", "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", @@ -2674,12 +2650,30 @@ "integrity": "sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w==", "dev": true }, + "dotenv-defaults": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/dotenv-defaults/-/dotenv-defaults-1.0.2.tgz", + "integrity": "sha512-iXFvHtXl/hZPiFj++1hBg4lbKwGM+t/GlvELDnRtOFdjXyWP7mubkVr+eZGWG62kdsbulXAef6v/j6kiWc/xGA==", + "dev": true, + "requires": { + "dotenv": "^6.2.0" + } + }, "dotenv-expand": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-4.2.0.tgz", "integrity": "sha1-3vHxyl1gWdJKdm5YeULCEQbOEnU=", "dev": true }, + "dotenv-webpack": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/dotenv-webpack/-/dotenv-webpack-1.7.0.tgz", + "integrity": "sha512-wwNtOBW/6gLQSkb8p43y0Wts970A3xtNiG/mpwj9MLUhtPCQG6i+/DSXXoNN7fbPCU/vQ7JjwGmgOeGZSSZnsw==", + "dev": true, + "requires": { + "dotenv-defaults": "^1.0.2" + } + }, "duplexer2": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", @@ -2964,15 +2958,15 @@ } }, "electron-to-chromium": { - "version": "1.3.48", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.48.tgz", - "integrity": "sha1-07DYWTgUBE4JLs4hCPw6ya6kuQA=", + "version": "1.3.106", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.106.tgz", + "integrity": "sha512-eXX45p4q9CRxG0G8D3ZBZYSdN3DnrcZfrFvt6VUr1u7aKITEtRY/xwWzJ/UZcWXa7DMqPu/pYwuZ6Nm+bl0GmA==", "dev": true }, "elliptic": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.0.tgz", - "integrity": "sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8=", + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.4.1.tgz", + "integrity": "sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ==", "dev": true, "requires": { "bn.js": "^4.4.0", @@ -3228,9 +3222,9 @@ } }, "events": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", - "integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz", + "integrity": "sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA==", "dev": true }, "evp_bytestokey": { @@ -3284,14 +3278,14 @@ } }, "express": { - "version": "4.16.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz", - "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", + "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", "dev": true, "requires": { "accepts": "~1.3.5", "array-flatten": "1.1.1", - "body-parser": "1.18.2", + "body-parser": "1.18.3", "content-disposition": "0.5.2", "content-type": "~1.0.4", "cookie": "0.3.1", @@ -3308,10 +3302,10 @@ "on-finished": "~2.3.0", "parseurl": "~1.3.2", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.3", - "qs": "6.5.1", + "proxy-addr": "~2.0.4", + "qs": "6.5.2", "range-parser": "~1.2.0", - "safe-buffer": "5.1.1", + "safe-buffer": "5.1.2", "send": "0.16.2", "serve-static": "1.13.2", "setprototypeof": "1.1.0", @@ -3326,12 +3320,6 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=", "dev": true - }, - "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", - "dev": true } } }, @@ -3812,13 +3800,13 @@ "dev": true }, "form-data": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", - "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "dev": true, "requires": { "asynckit": "^0.4.0", - "combined-stream": "^1.0.5", + "combined-stream": "^1.0.6", "mime-types": "^2.1.12" } }, @@ -4397,21 +4385,6 @@ "globule": "^1.0.0" } }, - "generate-function": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.0.0.tgz", - "integrity": "sha1-aFj+fAlpt9TpCTM3ZHrHn2DfvnQ=", - "dev": true - }, - "generate-object-property": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", - "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", - "dev": true, - "requires": { - "is-property": "^1.0.0" - } - }, "get-caller-file": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", @@ -5160,40 +5133,37 @@ "dev": true }, "har-validator": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-2.0.6.tgz", - "integrity": "sha1-zcvAgYgmWtEZtqWnyKtw7s+10n0=", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", "dev": true, "requires": { - "chalk": "^1.1.1", - "commander": "^2.9.0", - "is-my-json-valid": "^2.12.4", - "pinkie-promise": "^2.0.0" + "ajv": "^6.5.5", + "har-schema": "^2.0.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": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "ajv": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.7.0.tgz", + "integrity": "sha512-RZXPviBTtfmtka9n9sy1N5M5b82CbxWIR6HIis4s3WQTXDJamc/0gpCWNGz6EWdWp4DOfjzJfhz/AS9zVPjjWg==", "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" + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true } } @@ -5313,25 +5283,13 @@ } }, "hash.js": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.4.tgz", - "integrity": "sha512-A6RlQvvZEtFS5fLU43IDu0QUmBy+fDO9VMdTXvufKwIkt/rFfvICAViCax5fbDO4zdNzaC3/27ZhKUok5bAJyw==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", "dev": true, "requires": { "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.0" - } - }, - "hawk": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", - "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", - "dev": true, - "requires": { - "boom": "2.x.x", - "cryptiles": "2.x.x", - "hoek": "2.x.x", - "sntp": "1.x.x" + "minimalistic-assert": "^1.0.1" } }, "hmac-drbg": { @@ -5345,12 +5303,6 @@ "minimalistic-crypto-utils": "^1.0.1" } }, - "hoek": { - "version": "2.16.3", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", - "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", - "dev": true - }, "homedir-polyfill": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz", @@ -5379,18 +5331,18 @@ } }, "http-parser-js": { - "version": "0.4.13", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.13.tgz", - "integrity": "sha1-O9bW/ebjFyyTNMOzO2wZPYD+ETc=", + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.0.tgz", + "integrity": "sha512-cZdEF7r4gfRIq7ezX9J0T+kQmJNOub71dWbgAXVHDct80TKP4MCETtZQ31xyv38UwgzkWPYF/Xc0ge55dW9Z9w==", "dev": true }, "http-signature": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", - "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "dev": true, "requires": { - "assert-plus": "^0.2.0", + "assert-plus": "^1.0.0", "jsprim": "^1.2.2", "sshpk": "^1.7.0" } @@ -5402,10 +5354,13 @@ "dev": true }, "iconv-lite": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", - "dev": true + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } }, "ieee754": { "version": "1.1.12", @@ -5483,9 +5438,9 @@ "integrity": "sha1-QLja9P16MRUL0AIWD2ZJbiKpjDw=" }, "ipaddr.js": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz", - "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs=", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", + "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=", "dev": true }, "is-absolute": { @@ -5637,25 +5592,6 @@ "integrity": "sha1-Mlj7afeMFNW4FdZkM2tM/7ZEFZE=", "dev": true }, - "is-my-ip-valid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", - "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", - "dev": true - }, - "is-my-json-valid": { - "version": "2.17.2", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz", - "integrity": "sha512-IBhBslgngMQN8DDSppmgDv7RNrlFotuuDsKcrCP3+HbFaVivIBU7u9oiiErw8sH4ynx3+gOGQ3q2otkgiSi6kg==", - "dev": true, - "requires": { - "generate-function": "^2.0.0", - "generate-object-property": "^1.1.0", - "is-my-ip-valid": "^1.0.0", - "jsonpointer": "^4.0.0", - "xtend": "^4.0.0" - } - }, "is-negated-glob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", @@ -5714,12 +5650,6 @@ "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=" }, - "is-property": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", - "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", - "dev": true - }, "is-regex": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", @@ -5816,9 +5746,9 @@ "dev": true }, "js-base64": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.4.5.tgz", - "integrity": "sha512-aUnNwqMOXw3yvErjMPSQu6qIIzUmT1e5KcU1OZxRDU1g/am6mzBvcrmLAYwzmB59BHPrh5/tKaiF4OPhqRWESQ==", + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.5.1.tgz", + "integrity": "sha512-M7kLczedRMYX4L8Mdh4MzyAMM9O5osx+4FcOQuTvr3A9F2D9S5JXheN0ewNbrvK2UatkTRhL5ejGmGSjNMiZuw==", "dev": true }, "js-tokens": { @@ -5878,10 +5808,13 @@ "dev": true }, "json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", - "dev": true + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", + "dev": true, + "requires": { + "minimist": "^1.2.0" + } }, "jsonfile": { "version": "4.0.0", @@ -5898,12 +5831,6 @@ "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", "dev": true }, - "jsonpointer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.0.1.tgz", - "integrity": "sha1-T9kss04OnbPInIYi7PUfm5eMbLk=", - "dev": true - }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", @@ -6049,9 +5976,9 @@ } }, "livereload-js": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.3.0.tgz", - "integrity": "sha512-j1R0/FeGa64Y+NmqfZhyoVRzcFlOZ8sNlKzHjh4VvLULFACZhn68XrX5DFg2FhMvSMJmROuFxRSa560ECWKBMg==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-2.4.0.tgz", + "integrity": "sha512-XPQH8Z2GDP/Hwz2PCDrh2mth4yFejwA1OZ/81Ti3LgKyhDcEjsSsqFWZojHG0va/duGd+WyosY7eXLDoOyqcPw==", "dev": true }, "load-json-file": { @@ -6068,20 +5995,20 @@ } }, "loader-runner": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.0.tgz", - "integrity": "sha1-9IKuqC1UPgeSFwDVpG7yb9rGuKI=", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", + "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", "dev": true }, "loader-utils": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", - "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.2.3.tgz", + "integrity": "sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA==", "dev": true, "requires": { - "big.js": "^3.1.3", + "big.js": "^5.2.2", "emojis-list": "^2.0.0", - "json5": "^0.5.0" + "json5": "^1.0.1" } }, "locate-path": { @@ -6277,9 +6204,9 @@ } }, "lru-cache": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", - "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", "dev": true, "requires": { "pseudomap": "^1.0.2", @@ -6633,13 +6560,14 @@ "integrity": "sha1-izqsWIuKZuSXXjzepn97sylgH6w=" }, "md5.js": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", - "integrity": "sha1-6b296UogpawYsENA/Fdk1bCdkB0=", + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", "dev": true, "requires": { "hash-base": "^3.0.0", - "inherits": "^2.0.1" + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" } }, "media-typer": { @@ -6734,18 +6662,18 @@ "dev": true }, "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==", "dev": true }, "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "version": "2.1.21", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", + "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", "dev": true, "requires": { - "mime-db": "~1.33.0" + "mime-db": "~1.37.0" } }, "mimic-fn": { @@ -6893,9 +6821,9 @@ "dev": true }, "neo-async": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.5.1.tgz", - "integrity": "sha512-3KL3fvuRkZ7s4IFOMfztb7zJp3QaVWnBeGoJlgB38XnCRPj/0tLzzLG5IB8NYOHbJ8g8UGrgZv44GLDk6CxTxA==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.0.tgz", + "integrity": "sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA==", "dev": true }, "next-tick": { @@ -7089,9 +7017,9 @@ } }, "node-libs-browser": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.1.0.tgz", - "integrity": "sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.0.tgz", + "integrity": "sha512-5MQunG/oyOaBdttrL40dA7bUfPORLRWMUJLQtMg7nluxUvk5XwnLdL9twQHFAjRx/y7mIMkLKT9++qPbbk6BZA==", "dev": true, "requires": { "assert": "^1.1.1", @@ -7101,7 +7029,7 @@ "constants-browserify": "^1.0.0", "crypto-browserify": "^3.11.0", "domain-browser": "^1.1.1", - "events": "^1.0.0", + "events": "^3.0.0", "https-browserify": "^1.0.0", "os-browserify": "^0.3.0", "path-browserify": "0.0.0", @@ -7115,7 +7043,7 @@ "timers-browserify": "^2.0.4", "tty-browserify": "0.0.0", "url": "^0.11.0", - "util": "^0.10.3", + "util": "^0.11.0", "vm-browserify": "0.0.4" } }, @@ -7126,9 +7054,9 @@ "dev": true }, "node-sass": { - "version": "4.7.2", - "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.7.2.tgz", - "integrity": "sha512-CaV+wLqZ7//Jdom5aUFCpGNoECd7BbNhjuwdsX/LkXBrHl8eb1Wjw4HvWqcFvhr5KuNgAk8i/myf/MQ1YYeroA==", + "version": "4.11.0", + "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-4.11.0.tgz", + "integrity": "sha512-bHUdHTphgQJZaF1LASx0kAviPH7sGlcyNhWade4eVIpFp6tsn7SV8xNMTbsQFpEV9VXpnwTTnNYlfsZXgGgmkA==", "dev": true, "requires": { "async-foreach": "^0.1.3", @@ -7143,10 +7071,10 @@ "lodash.mergewith": "^4.6.0", "meow": "^3.7.0", "mkdirp": "^0.5.1", - "nan": "^2.3.2", - "node-gyp": "^3.3.1", + "nan": "^2.10.0", + "node-gyp": "^3.8.0", "npmlog": "^4.0.0", - "request": "~2.79.0", + "request": "^2.88.0", "sass-graph": "^2.2.4", "stdout-stream": "^1.4.0", "true-case-path": "^1.0.2" @@ -7267,9 +7195,9 @@ "dev": true }, "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", "dev": true }, "object-assign": { @@ -7562,16 +7490,17 @@ "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==" }, "parse-asn1": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", - "integrity": "sha512-KPx7flKXg775zZpnp9SxJlz00gTd4BmJ2yJufSc44gMCRrRQ7NSzAcSJQfifuOLgW6bEi+ftrALtsgALeB2Adw==", + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.3.tgz", + "integrity": "sha512-VrPoetlz7B/FqjBLD2f5wBVZvsZVLnRUrxVLfRYhGXCODa/NWE4p3Wp+6+aV3ZPL3KM7/OZmxDIwwijD7yuucg==", "dev": true, "requires": { "asn1.js": "^4.0.0", "browserify-aes": "^1.0.0", "create-hash": "^1.1.0", "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3" + "pbkdf2": "^3.0.3", + "safe-buffer": "^5.1.1" } }, "parse-filepath": { @@ -7703,9 +7632,9 @@ } }, "pbkdf2": { - "version": "3.0.16", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.16.tgz", - "integrity": "sha512-y4CXP3thSxqf7c0qmOF+9UeOTrifiVTIM+u7NWlq+PRsHbr7r7dpCmvzrZxa96JJUNi0Y5w9VqG5ZNeCVMoDcA==", + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.17.tgz", + "integrity": "sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA==", "dev": true, "requires": { "create-hash": "^1.1.2", @@ -7821,9 +7750,9 @@ "dev": true }, "postcss": { - "version": "6.0.22", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.22.tgz", - "integrity": "sha512-Toc9lLoUASwGqxBSJGTVcOQiDqjK+Z2XlWBg+IgYwQMY9vA2f7iMpXVc1GpPcfTSyM5lkxNo0oDwDRO+wm7XHA==", + "version": "6.0.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.23.tgz", + "integrity": "sha512-soOk1h6J3VMTZtVeVpv15/Hpdl2cBLX3CAw4TAbkpTJiNPk9YP/zWcD1ND+xEtvyuuvKzbxliTOIyvkSeSJ6ag==", "dev": true, "requires": { "chalk": "^2.4.1", @@ -7832,9 +7761,9 @@ } }, "postcss-value-parser": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.0.tgz", - "integrity": "sha1-h/OPnxj3dKSrTIojL1xc6IcqnRU=", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz", + "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", "dev": true }, "preserve": { @@ -7870,13 +7799,13 @@ } }, "proxy-addr": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz", - "integrity": "sha512-jQTChiCJteusULxjBp8+jftSQE5Obdl3k4cnmLA6WXtK6XFuWRnvVL7aCiBqaLPM8c4ph0S4tKna8XvmIwEnXQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", + "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", "dev": true, "requires": { "forwarded": "~0.1.2", - "ipaddr.js": "1.6.0" + "ipaddr.js": "1.8.0" } }, "proxy-middleware": { @@ -7904,16 +7833,17 @@ "dev": true }, "public-encrypt": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.2.tgz", - "integrity": "sha512-4kJ5Esocg8X3h8YgJsKAuoesBgB7mqH3eowiDzMUPKiRDDE7E/BqqZD1hnTByIaAFiwAw246YEltSq7tdrOH0Q==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", "dev": true, "requires": { "bn.js": "^4.1.0", "browserify-rsa": "^4.0.0", "create-hash": "^1.1.0", "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1" + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" } }, "pump": { @@ -7944,9 +7874,9 @@ "dev": true }, "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", "dev": true }, "querystring": { @@ -8009,41 +7939,15 @@ "dev": true }, "raw-body": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", - "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", "dev": true, "requires": { "bytes": "3.0.0", - "http-errors": "1.6.2", - "iconv-lite": "0.4.19", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", "unpipe": "1.0.0" - }, - "dependencies": { - "depd": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", - "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=", - "dev": true - }, - "http-errors": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", - "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", - "dev": true, - "requires": { - "depd": "1.1.1", - "inherits": "2.0.3", - "setprototypeof": "1.0.3", - "statuses": ">= 1.3.1 < 2" - } - }, - "setprototypeof": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", - "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=", - "dev": true - } } }, "read-config-file": { @@ -8255,37 +8159,37 @@ } }, "request": { - "version": "2.79.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.79.0.tgz", - "integrity": "sha1-Tf5b9r6LjNw3/Pk+BLZVd3InEN4=", + "version": "2.88.0", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz", + "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==", "dev": true, "requires": { - "aws-sign2": "~0.6.0", - "aws4": "^1.2.1", - "caseless": "~0.11.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.0", + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", "forever-agent": "~0.6.1", - "form-data": "~2.1.1", - "har-validator": "~2.0.6", - "hawk": "~3.1.3", - "http-signature": "~1.1.0", + "form-data": "~2.3.2", + "har-validator": "~5.1.0", + "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.7", - "oauth-sign": "~0.8.1", - "qs": "~6.3.0", - "stringstream": "~0.0.4", - "tough-cookie": "~2.3.0", - "tunnel-agent": "~0.4.1", - "uuid": "^3.0.0" + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.4.3", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" }, "dependencies": { - "qs": { - "version": "6.3.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.3.2.tgz", - "integrity": "sha1-51vV9uJoEioqDgvaYwslUMFmUCw=", + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true } } @@ -8412,9 +8316,9 @@ } }, "rollup-pluginutils": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.3.0.tgz", - "integrity": "sha512-xB6hsRsjdJdIYWEyYUJy/3ki5g69wrf0luHPGNK3ZSocV6HLNfio59l3dZ3TL4xUwEKgROhFi9jOCt6c5gfUWw==", + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.3.3.tgz", + "integrity": "sha512-2XZwja7b6P5q4RZ5FhyX1+f46xi1Z3qBKigLRZ6VTZjwbN0K1IFGMlwm06Uu0Emcre2Z63l77nq/pzn+KxIEoA==", "dev": true, "requires": { "estree-walker": "^0.5.2", @@ -8752,19 +8656,10 @@ "kind-of": "^3.2.0" } }, - "sntp": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", - "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", - "dev": true, - "requires": { - "hoek": "2.x.x" - } - }, "source-list-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.0.tgz", - "integrity": "sha512-I2UmuJSRr/T8jisiROLU3A3ltr+swpniSmNPI4Ml3ZCX6tVnDsuZzK7F2hl5jTqbZBWCEKlj5HRQiPExXLgE8A==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", "dev": true }, "source-map": { @@ -8929,18 +8824,18 @@ "dev": true }, "stdout-stream": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.0.tgz", - "integrity": "sha1-osfIWH5U2UJ+qe2zrD8s1SLfN4s=", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz", + "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==", "dev": true, "requires": { "readable-stream": "^2.0.1" } }, "stream-browserify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", - "integrity": "sha1-ZiZu5fm9uZQKTkUUyvtDu3Hlyds=", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", + "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", "dev": true, "requires": { "inherits": "~2.0.1", @@ -8997,12 +8892,6 @@ "safe-buffer": "~5.1.0" } }, - "stringstream": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", - "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==", - "dev": true - }, "strip-ansi": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", @@ -9079,9 +8968,9 @@ "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=" }, "tapable": { - "version": "0.2.8", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.8.tgz", - "integrity": "sha1-mTcqXJmb8t8WCvwNdL7U9HlIzSI=", + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.9.tgz", + "integrity": "sha512-2wsvQ+4GwBvLPLWsNfLCDYGsW6xb7aeC6utq2Qh0PFwgEy7K7dsma9Jsmb2zSQj7GvYAyUGSntLtsv++GmgL1A==", "dev": true }, "tar": { @@ -9163,13 +9052,19 @@ }, "dependencies": { "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true } } }, @@ -9241,11 +9136,12 @@ } }, "tough-cookie": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", - "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz", + "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==", "dev": true, "requires": { + "psl": "^1.1.24", "punycode": "^1.4.1" } }, @@ -9256,27 +9152,12 @@ "dev": true }, "true-case-path": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.2.tgz", - "integrity": "sha1-fskRMJJHZsf1c74wIMNPj9/QDWI=", + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-1.0.3.tgz", + "integrity": "sha512-m6s2OdQe5wgpFMC+pAJ+q9djG82O2jcHPOI6RNg1yy9rCYR+WD6Nbpl32fDpfC56nirdRy+opFa/Vk7HYhqaew==", "dev": true, "requires": { - "glob": "^6.0.4" - }, - "dependencies": { - "glob": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", - "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", - "dev": true, - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } + "glob": "^7.1.2" } }, "truncate-utf8-bytes": { @@ -9310,9 +9191,9 @@ "integrity": "sha512-AVP5Xol3WivEr7hnssHDsaM+lVrVXWUvd1cfXTRkTj80b//6g2wIFEH6hZG0muGZRnHGrfttpdzRk3YlBkWjKw==" }, "tslint": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.10.0.tgz", - "integrity": "sha1-EeJrzLiK+gLdDZlWyuPUVAtfVMM=", + "version": "5.12.1", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.12.1.tgz", + "integrity": "sha512-sfodBHOucFg6egff8d1BvuofoOQ/nOeYNfbp7LDlKBcLNrL3lmS5zoiDGyOMdT7YsEXAwWpTdAHwOGOc8eRZAw==", "dev": true, "requires": { "babel-code-frame": "^6.22.0", @@ -9326,7 +9207,7 @@ "resolve": "^1.3.2", "semver": "^5.3.0", "tslib": "^1.8.0", - "tsutils": "^2.12.1" + "tsutils": "^2.27.2" } }, "tslint-eslint-rules": { @@ -9349,9 +9230,9 @@ } }, "tsutils": { - "version": "2.27.1", - "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.27.1.tgz", - "integrity": "sha512-AE/7uzp32MmaHvNNFES85hhUDHFdFZp6OAiZcd6y4ZKKIg6orJTm8keYWBhIhrJQH3a4LzNKat7ZPXZt5aTf6w==", + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", "dev": true, "requires": { "tslib": "^1.8.1" @@ -9364,10 +9245,13 @@ "dev": true }, "tunnel-agent": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.4.3.tgz", - "integrity": "sha1-Y3PbdpCf5XDgjXNYM2Xtgop07us=", - "dev": true + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dev": true, + "requires": { + "safe-buffer": "^5.0.1" + } }, "tweetnacl": { "version": "0.14.5", @@ -9691,9 +9575,9 @@ "dev": true }, "util": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", - "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", + "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", "dev": true, "requires": { "inherits": "2.0.3" @@ -9711,9 +9595,9 @@ "dev": true }, "uuid": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", + "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==", "dev": true }, "v8flags": { @@ -9898,23 +9782,24 @@ } }, "chokidar": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.3.tgz", - "integrity": "sha512-zW8iXYZtXMx4kux/nuZVXjkLP+CyIK5Al5FHnj1OgTKGZfp4Oy6/ymtMSKFv3GD8DviEmUPmJg9eFdJ/JzudMg==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", + "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", "dev": true, "requires": { "anymatch": "^2.0.0", "async-each": "^1.0.0", "braces": "^2.3.0", - "fsevents": "^1.1.2", + "fsevents": "^1.2.2", "glob-parent": "^3.1.0", "inherits": "^2.0.1", "is-binary-path": "^1.0.0", "is-glob": "^4.0.0", + "lodash.debounce": "^4.0.8", "normalize-path": "^2.1.1", "path-is-absolute": "^1.0.0", "readdirp": "^2.0.0", - "upath": "^1.0.0" + "upath": "^1.0.5" } }, "expand-brackets": { @@ -10194,15 +10079,15 @@ "integrity": "sha1-Om2bwVGWN3qQ+OKAP6UmIWWwRRA=" }, "webpack": { - "version": "3.8.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-3.8.1.tgz", - "integrity": "sha512-5ZXLWWsMqHKFr5y0N3Eo5IIisxeEeRAajNq4mELb/WELOR7srdbQk2N5XiyNy2A/AgvlR3AmeBCZJW8lHrolbw==", + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-3.12.0.tgz", + "integrity": "sha512-Sw7MdIIOv/nkzPzee4o0EdvCuPmxT98+vVpIvwtcwcF1Q4SDSNp92vwcKc4REe7NItH9f1S4ra9FuQ7yuYZ8bQ==", "dev": true, "requires": { "acorn": "^5.0.0", "acorn-dynamic-import": "^2.0.0", - "ajv": "^5.1.5", - "ajv-keywords": "^2.0.0", + "ajv": "^6.1.0", + "ajv-keywords": "^3.1.0", "async": "^2.1.2", "enhanced-resolve": "^3.4.0", "escope": "^3.6.0", @@ -10223,6 +10108,18 @@ "yargs": "^8.0.2" }, "dependencies": { + "ajv": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.7.0.tgz", + "integrity": "sha512-RZXPviBTtfmtka9n9sy1N5M5b82CbxWIR6HIis4s3WQTXDJamc/0gpCWNGz6EWdWp4DOfjzJfhz/AS9zVPjjWg==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, "ansi-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", @@ -10235,6 +10132,12 @@ "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", "dev": true }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", @@ -10256,6 +10159,18 @@ "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", "dev": true }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + }, "load-json-file": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", @@ -10397,9 +10312,9 @@ } }, "webpack-sources": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.1.0.tgz", - "integrity": "sha512-aqYp18kPphgoO5c/+NaUvEeACtZjMESmDChuD3NBciVpah3XpMEU9VAAtIaB1BsfJWWTSdv8Vv1m3T0aRk2dUw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.3.0.tgz", + "integrity": "sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA==", "dev": true, "requires": { "source-list-map": "^2.0.0", diff --git a/package.json b/package.json index 7b9a7e4ed..3d53b5171 100644 --- a/package.json +++ b/package.json @@ -118,9 +118,9 @@ "zone.js": "^0.8.26" }, "devDependencies": { - "@ionic/app-scripts": "3.1.9", - "electron-rebuild": "^1.8.1", + "@ionic/app-scripts": "^3.2.1", "electron-builder-lib": "^20.23.1", + "electron-rebuild": "^1.8.1", "gulp": "^4.0.0", "gulp-clip-empty-files": "^0.1.2", "gulp-flatten": "^0.4.0", From 4aa5d4a6a1c5825b2f57cba0e271c4de459df3dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 22 Jan 2019 11:52:07 +0100 Subject: [PATCH 019/191] MOBILE-2843 course: Improve show prefetch group of courses --- src/components/empty-box/empty-box.scss | 1 + src/core/course/providers/helper.ts | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/empty-box/empty-box.scss b/src/components/empty-box/empty-box.scss index 4eb024d88..5d265575c 100644 --- a/src/components/empty-box/empty-box.scss +++ b/src/components/empty-box/empty-box.scss @@ -29,6 +29,7 @@ ion-app.app-root core-empty-box { .icon { font-size: 120px; + width: auto; } img { height: 125px; diff --git a/src/core/course/providers/helper.ts b/src/core/course/providers/helper.ts index 3c1dcdb48..27a3dcfef 100644 --- a/src/core/course/providers/helper.ts +++ b/src/core/course/providers/helper.ts @@ -758,10 +758,11 @@ export class CoreCourseHelperProvider { * * @param {any[]} courses Courses array to get info from. * @param {any} prefetch Prefetch information. + * @param {number} [minCourses=2] Min course to show icon. * @return {Promise} Resolved with the prefetch information updated when done. */ - initPrefetchCoursesIcons(courses: any[], prefetch: any): Promise { - if (!courses || courses.length < 2) { + initPrefetchCoursesIcons(courses: any[], prefetch: any, minCourses: number = 2): Promise { + if (!courses || courses.length < minCourses) { // Not enough courses. prefetch.icon = ''; From 58746a9e449b17122be1741cb65ca2c27bc83b80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 24 Jan 2019 09:27:05 +0100 Subject: [PATCH 020/191] MOBILE-2843 ux: Remove border of the refresher --- src/app/app.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/app/app.scss b/src/app/app.scss index 508268db3..17b534f24 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -73,6 +73,10 @@ ion-app.app-root { } } + .has-refresher > .scroll-content { + border-top: 0 !important; + } + // Define an alternative way to set a heading in an item without using a heading tag. // This is done for accessibility reasons when a heading is semantically incorrect. .item .item-heading { From a6c84b25f200e3246d218f06c90ebfc260006bef Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 24 Jan 2019 14:36:08 +0100 Subject: [PATCH 021/191] MOBILE-2829 mac: Fix single instance in Mac apps --- desktop/assets/mac/parent.plist | 2 ++ 1 file changed, 2 insertions(+) diff --git a/desktop/assets/mac/parent.plist b/desktop/assets/mac/parent.plist index 9e39db337..0941fd051 100644 --- a/desktop/assets/mac/parent.plist +++ b/desktop/assets/mac/parent.plist @@ -16,5 +16,7 @@ com.apple.security.device.audio-input + LSMultipleInstancesProhibited + \ No newline at end of file From 6c94a675f0dbef0633bd1ad64aead78815bd3b62 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 24 Jan 2019 15:52:10 +0100 Subject: [PATCH 022/191] MOBILE-2841 messages: Fix scroll bottom in iOS devices --- src/addon/messages/pages/discussion/discussion.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/addon/messages/pages/discussion/discussion.ts b/src/addon/messages/pages/discussion/discussion.ts index b3151026e..25cdcab13 100644 --- a/src/addon/messages/pages/discussion/discussion.ts +++ b/src/addon/messages/pages/discussion/discussion.ts @@ -352,8 +352,9 @@ export class AddonMessagesDiscussionPage implements OnDestroy { } // Check if we are at the bottom to scroll it after render. - this.scrollBottom = this.domUtils.getScrollHeight(this.content) - this.domUtils.getScrollTop(this.content) === - this.domUtils.getContentHeight(this.content); + // 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.domUtils.getContentHeight(this.content)) < 5; if (this.messagesBeingSent > 0) { // Ignore polling due to a race condition. From 67680550ce16e1a8e78359e605bda630cfacd535 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 23 Jan 2019 16:37:06 +0100 Subject: [PATCH 023/191] MOBILE-2843 notes: Move add note to the notes page --- .../components/list/addon-notes-list.html | 14 ++++- src/addon/notes/components/list/list.ts | 55 ++++++++++++++++--- src/addon/notes/notes.module.ts | 3 +- src/addon/notes/pages/add/add.html | 12 ++-- src/addon/notes/pages/add/add.ts | 12 ++-- src/addon/notes/pages/list/list.html | 7 +++ src/addon/notes/pages/list/list.module.ts | 33 +++++++++++ src/addon/notes/pages/list/list.ts | 34 ++++++++++++ .../notes/providers/course-option-handler.ts | 2 +- src/addon/notes/providers/notes-offline.ts | 18 ++++++ src/addon/notes/providers/notes-sync.ts | 4 +- src/addon/notes/providers/notes.ts | 45 +++++++++++---- src/addon/notes/providers/user-handler.ts | 36 ++++++------ 13 files changed, 219 insertions(+), 56 deletions(-) create mode 100644 src/addon/notes/pages/list/list.html create mode 100644 src/addon/notes/pages/list/list.module.ts create mode 100644 src/addon/notes/pages/list/list.ts diff --git a/src/addon/notes/components/list/addon-notes-list.html b/src/addon/notes/components/list/addon-notes-list.html index 0285ddd90..2c069ca78 100644 --- a/src/addon/notes/components/list/addon-notes-list.html +++ b/src/addon/notes/components/list/addon-notes-list.html @@ -10,6 +10,10 @@ + + +

{{user.fullname}}

+
@@ -29,8 +33,8 @@ - -

{{note.userfullname}}

+ +

{{note.userfullname}}

{{note.lastmodified | coreDateDayOrTime}}

{{ 'core.notsent' | translate }}

@@ -39,5 +43,11 @@
+ + + + diff --git a/src/addon/notes/components/list/list.ts b/src/addon/notes/components/list/list.ts index b51247b3d..0150edbfd 100644 --- a/src/addon/notes/components/list/list.ts +++ b/src/addon/notes/components/list/list.ts @@ -13,11 +13,12 @@ // limitations under the License. import { Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; -import { Content } from 'ionic-angular'; +import { Content, ModalController } from 'ionic-angular'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreEventsProvider } from '@providers/events'; import { CoreSitesProvider } from '@providers/sites'; import { CoreTextUtilsProvider } from '@providers/utils/text'; +import { CoreUserProvider } from '@core/user/providers/user'; import { AddonNotesProvider } from '../../providers/notes'; import { AddonNotesSyncProvider } from '../../providers/notes-sync'; @@ -30,6 +31,7 @@ import { AddonNotesSyncProvider } from '../../providers/notes-sync'; }) export class AddonNotesListComponent implements OnInit, OnDestroy { @Input() courseId: number; + @Input() userId?: number; @ViewChild(Content) content: Content; @@ -41,10 +43,12 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { notes: any[]; hasOffline = false; notesLoaded = false; + user: any; constructor(private domUtils: CoreDomUtilsProvider, private textUtils: CoreTextUtilsProvider, - sitesProvider: CoreSitesProvider, eventsProvider: CoreEventsProvider, - private notesProvider: AddonNotesProvider, private notesSync: AddonNotesSyncProvider) { + sitesProvider: CoreSitesProvider, eventsProvider: CoreEventsProvider, private modalCtrl: ModalController, + private notesProvider: AddonNotesProvider, private notesSync: AddonNotesSyncProvider, + private userProvider: CoreUserProvider) { // Refresh data if notes are synchronized automatically. this.syncObserver = eventsProvider.on(AddonNotesSyncProvider.AUTO_SYNCED, (data) => { if (data.courseId == this.courseId) { @@ -67,7 +71,7 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { */ ngOnInit(): void { this.fetchNotes(true).then(() => { - this.notesProvider.logView(this.courseId).catch(() => { + this.notesProvider.logView(this.courseId, this.userId).catch(() => { // Ignore errors. }); }); @@ -86,14 +90,23 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { return promise.catch(() => { // Ignore errors. }).then(() => { - return this.notesProvider.getNotes(this.courseId).then((notes) => { + return this.notesProvider.getNotes(this.courseId, this.userId).then((notes) => { notes = notes[this.type + 'notes'] || []; this.hasOffline = notes.some((note) => note.offline); - return this.notesProvider.getNotesUserData(notes, this.courseId).then((notes) => { + if (this.userId) { this.notes = notes; - }); + + // Get the user profile to retrieve the user image. + return this.userProvider.getProfile(this.userId, this.courseId, true).then((user) => { + this.user = user; + }); + } else { + return this.notesProvider.getNotesUserData(notes, this.courseId).then((notes) => { + this.notes = notes; + }); + } }); }).catch((message) => { this.domUtils.showErrorModal(message); @@ -113,7 +126,7 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { refreshNotes(showErrors: boolean, refresher?: any): void { this.refreshIcon = 'spinner'; this.syncIcon = 'spinner'; - this.notesProvider.invalidateNotes(this.courseId).finally(() => { + this.notesProvider.invalidateNotes(this.courseId, this.userId).finally(() => { this.fetchNotes(true, showErrors).finally(() => { if (refresher) { refresher.complete(); @@ -130,12 +143,36 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { this.refreshIcon = 'spinner'; this.syncIcon = 'spinner'; this.fetchNotes(true).then(() => { - this.notesProvider.logView(this.courseId).catch(() => { + this.notesProvider.logView(this.courseId, this.userId).catch(() => { // Ignore errors. }); }); } + /** + * Add a new Note to user and course. + * @param {Event} e Event. + */ + addNote(e: Event): void { + e.preventDefault(); + e.stopPropagation(); + const modal = this.modalCtrl.create('AddonNotesAddPage', { userId: this.userId, courseId: this.courseId, type: this.type }); + modal.onDidDismiss((data) => { + if (data && data.sent && data.type) { + if (data.type != this.type) { + this.type = data.type; + this.notesLoaded = false; + } + + this.refreshNotes(true); + } else if (data && data.type && data.type != this.type) { + this.type = data.type; + this.typeChanged(); + } + }); + modal.present(); + } + /** * Tries to synchronize course notes. * diff --git a/src/addon/notes/notes.module.ts b/src/addon/notes/notes.module.ts index 425766440..002f60177 100644 --- a/src/addon/notes/notes.module.ts +++ b/src/addon/notes/notes.module.ts @@ -44,8 +44,7 @@ export const ADDON_NOTES_PROVIDERS: any[] = [ AddonNotesSyncProvider, AddonNotesCourseOptionHandler, AddonNotesSyncCronHandler, - AddonNotesUserHandler - ] + AddonNotesUserHandler ] }) export class AddonNotesModule { constructor(courseOptionsDelegate: CoreCourseOptionsDelegate, courseOptionHandler: AddonNotesCourseOptionHandler, diff --git a/src/addon/notes/pages/add/add.html b/src/addon/notes/pages/add/add.html index aaf3bfab8..b6df1d660 100644 --- a/src/addon/notes/pages/add/add.html +++ b/src/addon/notes/pages/add/add.html @@ -8,11 +8,11 @@ - +
{{ 'addon.notes.publishstate' | translate }} - + {{ 'addon.notes.personalnotes' | translate }} {{ 'addon.notes.coursenotes' | translate }} {{ 'addon.notes.sitenotes' | translate }} @@ -21,8 +21,10 @@ - +
+ +
diff --git a/src/addon/notes/pages/add/add.ts b/src/addon/notes/pages/add/add.ts index 92cc7e219..98c770c8f 100644 --- a/src/addon/notes/pages/add/add.ts +++ b/src/addon/notes/pages/add/add.ts @@ -29,7 +29,7 @@ import { AddonNotesProvider } from '../../providers/notes'; export class AddonNotesAddPage { userId: number; courseId: number; - publishState = 'personal'; + type = 'personal'; text = ''; processing = false; @@ -37,6 +37,7 @@ export class AddonNotesAddPage { private domUtils: CoreDomUtilsProvider, private notesProvider: AddonNotesProvider) { this.userId = params.get('userId'); this.courseId = params.get('courseId'); + this.type = params.get('type') || 'personal'; } /** @@ -52,10 +53,9 @@ export class AddonNotesAddPage { const loadingModal = this.domUtils.showModalLoading('core.sending', true); // Freeze the add note button. this.processing = true; - this.notesProvider.addNote(this.userId, this.courseId, this.publishState, this.text).then((sent) => { - this.viewCtrl.dismiss().finally(() => { - const message = sent ? 'addon.notes.eventnotecreated' : 'core.datastoredoffline'; - this.domUtils.showAlertTranslated('core.success', message); + this.notesProvider.addNote(this.userId, this.courseId, this.type, this.text).then((sent) => { + this.viewCtrl.dismiss({type: this.type, sent: true}).finally(() => { + this.domUtils.showToast(sent ? 'addon.notes.eventnotecreated' : 'core.datastoredoffline', true, 3000); }); }).catch((error) => { this.domUtils.showErrorModal(error); @@ -69,6 +69,6 @@ export class AddonNotesAddPage { * Close modal. */ closeModal(): void { - this.viewCtrl.dismiss(); + this.viewCtrl.dismiss({type: this.type}); } } diff --git a/src/addon/notes/pages/list/list.html b/src/addon/notes/pages/list/list.html new file mode 100644 index 000000000..f88fe2201 --- /dev/null +++ b/src/addon/notes/pages/list/list.html @@ -0,0 +1,7 @@ + + + {{ 'addon.notes.notes' | translate }} + + + + diff --git a/src/addon/notes/pages/list/list.module.ts b/src/addon/notes/pages/list/list.module.ts new file mode 100644 index 000000000..c983a9395 --- /dev/null +++ b/src/addon/notes/pages/list/list.module.ts @@ -0,0 +1,33 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreDirectivesModule } from '@directives/directives.module'; +import { AddonNotesListPage } from './list'; +import { AddonNotesComponentsModule } from '../../components/components.module'; + +@NgModule({ + declarations: [ + AddonNotesListPage + ], + imports: [ + CoreDirectivesModule, + AddonNotesComponentsModule, + IonicPageModule.forChild(AddonNotesListPage), + TranslateModule.forChild() + ] +}) +export class AddonNotesListPageModule {} diff --git a/src/addon/notes/pages/list/list.ts b/src/addon/notes/pages/list/list.ts new file mode 100644 index 000000000..499ca2304 --- /dev/null +++ b/src/addon/notes/pages/list/list.ts @@ -0,0 +1,34 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component } from '@angular/core'; +import { IonicPage, NavParams } from 'ionic-angular'; + +/** + * Page that displays a list of notes. + */ +@IonicPage({ segment: 'addon-notes-list-page' }) +@Component({ + selector: 'page-addon-notes-list-page', + templateUrl: 'list.html', +}) +export class AddonNotesListPage { + userId: number; + courseId: number; + + constructor(params: NavParams) { + this.userId = params.get('userId'); + this.courseId = params.get('courseId'); + } +} diff --git a/src/addon/notes/providers/course-option-handler.ts b/src/addon/notes/providers/course-option-handler.ts index a1431a8c3..f00761ae8 100644 --- a/src/addon/notes/providers/course-option-handler.ts +++ b/src/addon/notes/providers/course-option-handler.ts @@ -79,6 +79,6 @@ export class AddonNotesCourseOptionHandler implements CoreCourseOptionsHandler { * @return {Promise} Promise resolved when done. */ prefetch(course: any): Promise { - return this.notesProvider.getNotes(course.id, true); + return this.notesProvider.getNotes(course.id, undefined, true); } } diff --git a/src/addon/notes/providers/notes-offline.ts b/src/addon/notes/providers/notes-offline.ts index b75a69067..dd9fbf50b 100644 --- a/src/addon/notes/providers/notes-offline.ts +++ b/src/addon/notes/providers/notes-offline.ts @@ -118,6 +118,24 @@ export class AddonNotesOfflineProvider { }); } + /** + * Get offline notes for a certain course and user. + * + * @param {number} courseId Course ID. + * @param {number} [userId] User ID. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with notes. + */ + getNotesForCourseAndUser(courseId: number, userId?: number, siteId?: string): Promise { + if (!userId) { + return this.getNotesForCourse(courseId, siteId); + } + + return this.sitesProvider.getSite(siteId).then((site) => { + return site.getDb().getRecords(AddonNotesOfflineProvider.NOTES_TABLE, {userid: userId, courseid: courseId}); + }); + } + /** * Get offline notes for a certain course. * diff --git a/src/addon/notes/providers/notes-sync.ts b/src/addon/notes/providers/notes-sync.ts index e02baf438..376e5b673 100644 --- a/src/addon/notes/providers/notes-sync.ts +++ b/src/addon/notes/providers/notes-sync.ts @@ -154,8 +154,8 @@ export class AddonNotesSyncProvider extends CoreSyncBaseProvider { }); // Fetch the notes from server to be sure they're up to date. - return this.notesProvider.invalidateNotes(courseId, siteId).then(() => { - return this.notesProvider.getNotes(courseId, false, true, siteId); + return this.notesProvider.invalidateNotes(courseId, undefined, siteId).then(() => { + return this.notesProvider.getNotes(courseId, undefined, false, true, siteId); }).catch(() => { // Ignore errors. }); diff --git a/src/addon/notes/providers/notes.ts b/src/addon/notes/providers/notes.ts index 9e065ca01..8226e0139 100644 --- a/src/addon/notes/providers/notes.ts +++ b/src/addon/notes/providers/notes.ts @@ -104,7 +104,7 @@ export class AddonNotesProvider { } // A note was added, invalidate the course notes. - return this.invalidateNotes(courseId, siteId).catch(() => { + return this.invalidateNotes(courseId, undefined, siteId).catch(() => { // Ignore errors. }); }); @@ -184,37 +184,54 @@ export class AddonNotesProvider { * @return {Promise} Promise resolved with true if enabled, resolved with false or rejected otherwise. */ isPluginViewNotesEnabledForCourse(courseId: number, siteId?: string): Promise { - return this.utils.promiseWorks(this.getNotes(courseId, false, true, siteId)); + return this.utils.promiseWorks(this.getNotes(courseId, undefined, false, true, siteId)); + } + + /** + * Get prefix cache key for course notes. + * + * @param {number} courseId ID of the course to get the notes from. + * @return {string} Cache key. + */ + getNotesPrefixCacheKey(courseId: number): string { + return this.ROOT_CACHE_KEY + 'notes:' + courseId + ':'; } /** * Get the cache key for the get notes call. * * @param {number} courseId ID of the course to get the notes from. + * @param {number} [userId] ID of the user to get the notes from if requested. * @return {string} Cache key. */ - getNotesCacheKey(courseId: number): string { - return this.ROOT_CACHE_KEY + 'notes:' + courseId; + getNotesCacheKey(courseId: number, userId?: number): string { + return this.getNotesPrefixCacheKey(courseId) + (userId ? userId : ''); } /** * Get users notes for a certain site, course and personal notes. * * @param {number} courseId ID of the course to get the notes from. + * @param {number} [userId] ID of the user to get the notes from if requested. * @param {boolean} [ignoreCache] True when we should not get the value from the cache. * @param {boolean} [onlyOnline] True to return only online notes, false to return both online and offline. * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise to be resolved when the notes are retrieved. */ - getNotes(courseId: number, ignoreCache?: boolean, onlyOnline?: boolean, siteId?: string): Promise { + getNotes(courseId: number, userId?: number, ignoreCache?: boolean, onlyOnline?: boolean, siteId?: string): Promise { this.logger.debug('Get notes for course ' + courseId); return this.sitesProvider.getSite(siteId).then((site) => { const data = { courseid: courseId }; + + if (userId) { + data['userid'] = userId; + } + const preSets: CoreSiteWSPreSets = { - cacheKey: this.getNotesCacheKey(courseId) + cacheKey: this.getNotesCacheKey(courseId, userId) }; if (ignoreCache) { @@ -228,7 +245,7 @@ export class AddonNotesProvider { } // Get offline notes and add them to the list. - return this.notesOffline.getNotesForCourse(courseId, siteId).then((offlineNotes) => { + return this.notesOffline.getNotesForCourseAndUser(courseId, userId, siteId).then((offlineNotes) => { offlineNotes.forEach((note) => { const fieldName = note.publishstate + 'notes'; if (!notes[fieldName]) { @@ -272,12 +289,17 @@ export class AddonNotesProvider { * Invalidate get notes WS call. * * @param {number} courseId Course ID. + * @param {number} [userId] User ID if needed. * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when data is invalidated. */ - invalidateNotes(courseId: number, siteId?: string): Promise { + invalidateNotes(courseId: number, userId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.invalidateWsCacheForKey(this.getNotesCacheKey(courseId)); + if (userId) { + return site.invalidateWsCacheForKey(this.getNotesCacheKey(courseId, userId)); + } + + return site.invalidateWsCacheForKeyStartingWith(this.getNotesPrefixCacheKey(courseId)); }); } @@ -285,14 +307,15 @@ export class AddonNotesProvider { * Report notes as being viewed. * * @param {number} courseId ID of the course. + * @param {number} [userId] User ID if needed. * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the WS call is successful. */ - logView(courseId: number, siteId?: string): Promise { + logView(courseId: number, userId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const params = { courseid: courseId, - userid: 0 + userid: userId || 0 }; return site.write('core_notes_view_notes', params); diff --git a/src/addon/notes/providers/user-handler.ts b/src/addon/notes/providers/user-handler.ts index ffa1c367c..a5a409ded 100644 --- a/src/addon/notes/providers/user-handler.ts +++ b/src/addon/notes/providers/user-handler.ts @@ -13,10 +13,10 @@ // limitations under the License. import { Injectable } from '@angular/core'; -import { ModalController } from 'ionic-angular'; import { CoreEventsProvider } from '@providers/events'; import { CoreUserProvider } from '@core/user/providers/user'; import { CoreUserDelegate, CoreUserProfileHandler, CoreUserProfileHandlerData } from '@core/user/providers/user-delegate'; +import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; import { CoreSitesProvider } from '@providers/sites'; import { AddonNotesProvider } from './notes'; @@ -25,30 +25,30 @@ import { AddonNotesProvider } from './notes'; */ @Injectable() export class AddonNotesUserHandler implements CoreUserProfileHandler { - name = 'AddonNotes:addNote'; - priority = 200; - type = CoreUserDelegate.TYPE_COMMUNICATION; - addNoteEnabledCache = {}; + name = 'AddonNotes:notes'; + priority = 100; + type = CoreUserDelegate.TYPE_NEW_PAGE; + noteEnabledCache = {}; - constructor(private modalCtrl: ModalController, private sitesProvider: CoreSitesProvider, + constructor(private linkHelper: CoreContentLinksHelperProvider, private sitesProvider: CoreSitesProvider, private notesProvider: AddonNotesProvider, eventsProvider: CoreEventsProvider) { - eventsProvider.on(CoreEventsProvider.LOGOUT, this.clearAddNoteCache.bind(this)); + eventsProvider.on(CoreEventsProvider.LOGOUT, this.clearNoteCache.bind(this)); eventsProvider.on(CoreUserProvider.PROFILE_REFRESHED, (data) => { - this.clearAddNoteCache(data.courseId); + this.clearNoteCache(data.courseId); }); } /** - * Clear add note cache. + * Clear note cache. * If a courseId is specified, it will only delete the entry for that course. * * @param {number} [courseId] Course ID. */ - private clearAddNoteCache(courseId?: number): void { + private clearNoteCache(courseId?: number): void { if (courseId) { - delete this.addNoteEnabledCache[courseId]; + delete this.noteEnabledCache[courseId]; } else { - this.addNoteEnabledCache = {}; + this.noteEnabledCache = {}; } } @@ -75,12 +75,12 @@ export class AddonNotesUserHandler implements CoreUserProfileHandler { return Promise.resolve(false); } - if (typeof this.addNoteEnabledCache[courseId] != 'undefined') { - return this.addNoteEnabledCache[courseId]; + if (typeof this.noteEnabledCache[courseId] != 'undefined') { + return this.noteEnabledCache[courseId]; } return this.notesProvider.isPluginAddNoteEnabledForCourse(courseId).then((enabled) => { - this.addNoteEnabledCache[courseId] = enabled; + this.noteEnabledCache[courseId] = enabled; return enabled; }); @@ -94,13 +94,13 @@ export class AddonNotesUserHandler implements CoreUserProfileHandler { getDisplayData(user: any, courseId: number): CoreUserProfileHandlerData { return { icon: 'list', - title: 'addon.notes.addnewnote', + title: 'addon.notes.notes', class: 'addon-notes-handler', action: (event, navCtrl, user, courseId): void => { event.preventDefault(); event.stopPropagation(); - const modal = this.modalCtrl.create('AddonNotesAddPage', { userId: user.id, courseId }); - modal.present(); + // Always use redirect to make it the new history root (to avoid "loops" in history). + this.linkHelper.goInSite(navCtrl, 'AddonNotesListPage', { userId: user.id, courseId: courseId }); } }; } From e71e18148949985ce2327c9488b64da79a76b679 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 25 Jan 2019 09:40:28 +0100 Subject: [PATCH 024/191] MOBILE-2832 core: Make infinite scroll load more automatically --- .../infinite-loading/infinite-loading.ts | 31 ++++++++++++++++--- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/src/components/infinite-loading/infinite-loading.ts b/src/components/infinite-loading/infinite-loading.ts index 6b72837f0..a3f1f89df 100644 --- a/src/components/infinite-loading/infinite-loading.ts +++ b/src/components/infinite-loading/infinite-loading.ts @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, Input, Output, EventEmitter } from '@angular/core'; -import { InfiniteScroll } from 'ionic-angular'; +import { Component, Input, Output, EventEmitter, OnChanges, SimpleChange, Optional } from '@angular/core'; +import { InfiniteScroll, Content } from 'ionic-angular'; /** * Component to show a infinite loading trigger and spinner while more data is being loaded. @@ -25,7 +25,7 @@ import { InfiniteScroll } from 'ionic-angular'; selector: 'core-infinite-loading', templateUrl: 'core-infinite-loading.html', }) -export class CoreInfiniteLoadingComponent { +export class CoreInfiniteLoadingComponent implements OnChanges { @Input() enabled: boolean; @Input() error = false; @Input() position = 'bottom'; @@ -35,10 +35,26 @@ export class CoreInfiniteLoadingComponent { protected infiniteScroll: InfiniteScroll; - constructor() { + constructor(@Optional() private content: Content) { this.action = new EventEmitter(); } + /** + * Detect changes on input properties. + * + * @param {SimpleChange}} changes Changes. + */ + ngOnChanges(changes: {[name: string]: SimpleChange}): void { + if (changes.enabled && this.enabled) { + // Infinite scroll enabled. If the list doesn't fill the full height, infinite scroll isn't triggered automatically. + // Send a fake scroll event to make infinite scroll check if it should load more items. + setTimeout(() => { + const event: any = new Event('scroll'); + this.content.ionScroll.emit(event); + }); + } + } + /** * Load More items calling the action provided. * @@ -64,6 +80,13 @@ export class CoreInfiniteLoadingComponent { this.loadingMore = false; this.infiniteScroll && this.infiniteScroll.complete(); this.infiniteScroll = undefined; + + // More items loaded. If the list doesn't fill the full height, infinite scroll isn't triggered automatically. + // Send a fake scroll event to make infinite scroll check if it should load more items. + setTimeout(() => { + const event: any = new Event('scroll'); + this.content.ionScroll.emit(event); + }); } } From 336b50cc9281f1a827b9b4360ad26a5fb9acca9e Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 25 Jan 2019 11:30:46 +0100 Subject: [PATCH 025/191] MOBILE-2829 mac: Use inappbrowser for SSO in Mac --- desktop/assets/mac/parent.plist | 2 -- src/core/login/providers/helper.ts | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/desktop/assets/mac/parent.plist b/desktop/assets/mac/parent.plist index 0941fd051..9e39db337 100644 --- a/desktop/assets/mac/parent.plist +++ b/desktop/assets/mac/parent.plist @@ -16,7 +16,5 @@ com.apple.security.device.audio-input - LSMultipleInstancesProhibited - \ No newline at end of file diff --git a/src/core/login/providers/helper.ts b/src/core/login/providers/helper.ts index 9cebad238..5c51f6aae 100644 --- a/src/core/login/providers/helper.ts +++ b/src/core/login/providers/helper.ts @@ -561,8 +561,8 @@ export class CoreLoginHelperProvider { * @return {boolean} True if embedded browser, false othwerise. */ isSSOEmbeddedBrowser(code: number): boolean { - if (this.appProvider.isLinux()) { - // In Linux desktop apps, always use embedded browser. + if (this.appProvider.isLinux() || this.appProvider.isMac()) { + // In Linux and Mac desktop apps, always use embedded browser. return true; } From 05c552c3439e0d73ff5fcf0f1c992a7d76089048 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 25 Jan 2019 11:51:20 +0100 Subject: [PATCH 026/191] MOBILE-2844 mac: Use inappbrowser for oauth in Mac --- src/core/login/providers/helper.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/login/providers/helper.ts b/src/core/login/providers/helper.ts index 5c51f6aae..ff9d6c58d 100644 --- a/src/core/login/providers/helper.ts +++ b/src/core/login/providers/helper.ts @@ -647,8 +647,8 @@ export class CoreLoginHelperProvider { loginUrl += '&oauthsso=' + params.id; - if (this.appProvider.isLinux()) { - // In Linux desktop apps, always use embedded browser. + if (this.appProvider.isLinux() || this.appProvider.isMac()) { + // In Linux and Mac desktop apps, always use embedded browser. this.utils.openInApp(loginUrl); } else { // Always open it in browser because the user might have the session stored in there. From edcc26f2c523ecd1d7d21dffad4003ebfc3806c9 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Mon, 28 Jan 2019 09:55:32 +0100 Subject: [PATCH 027/191] MOBILE-2844 desktop: Display download button in SSO legacy site --- src/core/login/providers/helper.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/login/providers/helper.ts b/src/core/login/providers/helper.ts index ff9d6c58d..77d06b08c 100644 --- a/src/core/login/providers/helper.ts +++ b/src/core/login/providers/helper.ts @@ -179,10 +179,10 @@ export class CoreLoginHelperProvider { } else { this.goToSiteInitialPage(); } - }).catch((errorMessage) => { - if (errorMessage) { + }).catch((error) => { + if (error) { // An error occurred, display the error and logout the user. - this.domUtils.showErrorModal(errorMessage); + this.treatUserTokenError(siteData.siteUrl, error); this.sitesProvider.logout(); } }).finally(() => { From bf786ba596c1d79ea90fd137371a295c071ad98b Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Mon, 28 Jan 2019 10:26:30 +0100 Subject: [PATCH 028/191] MOBILE-2844 course: Display size instead of are you sure in prefetch --- src/providers/utils/dom.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/providers/utils/dom.ts b/src/providers/utils/dom.ts index 05f86a032..d026def15 100644 --- a/src/providers/utils/dom.ts +++ b/src/providers/utils/dom.ts @@ -140,13 +140,12 @@ export class CoreDomUtilsProvider { const readableSize = this.textUtils.bytesToSize(size.size, 2); return this.showConfirm(this.translate.instant('core.course.confirmpartialdownloadsize', { size: readableSize })); - } else if (size.size >= wifiThreshold || (this.appProvider.isNetworkAccessLimited() && size.size >= limitedThreshold)) { + } else if (alwaysConfirm || size.size >= wifiThreshold || + (this.appProvider.isNetworkAccessLimited() && size.size >= limitedThreshold)) { message = message || 'core.course.confirmdownload'; const readableSize = this.textUtils.bytesToSize(size.size, 2); return this.showConfirm(this.translate.instant(message, { size: readableSize })); - } else if (alwaysConfirm) { - return this.showConfirm(this.translate.instant('core.areyousure')); } return Promise.resolve(); From f52c6cae2d88a315a4021726f771fa5443714f79 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Mon, 28 Jan 2019 12:25:34 +0100 Subject: [PATCH 029/191] MOBILE-2811 quiz: Disable selects in review --- src/addon/qtype/match/component/addon-qtype-match.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/addon/qtype/match/component/addon-qtype-match.html b/src/addon/qtype/match/component/addon-qtype-match.html index b392ef3b4..d905fade1 100644 --- a/src/addon/qtype/match/component/addon-qtype-match.html +++ b/src/addon/qtype/match/component/addon-qtype-match.html @@ -10,7 +10,7 @@ - + {{option.label}} From d2fc0ed4200be16862bf7c6210508d5747093463 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Mon, 28 Jan 2019 11:10:33 +0100 Subject: [PATCH 030/191] MOBILE-2812 ux: Imrpove module icons sizes --- src/app/app.scss | 2 ++ src/core/grades/pages/grade/grade.html | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/app/app.scss b/src/app/app.scss index 17b534f24..b8b9ec993 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -134,6 +134,8 @@ ion-app.app-root { .core-module-icon { width: auto; + max-width: 24px; + max-height: 24px; } .core-button-spinner { diff --git a/src/core/grades/pages/grade/grade.html b/src/core/grades/pages/grade/grade.html index 232f3a302..7bb1b117d 100644 --- a/src/core/grades/pages/grade/grade.html +++ b/src/core/grades/pages/grade/grade.html @@ -13,13 +13,13 @@ - +

- +

From 9d320b5914a912ebd3c407af4168e63bfb6fed89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Mon, 28 Jan 2019 17:42:50 +0100 Subject: [PATCH 031/191] MOBILE-2812 quiz: Update styles to match web version --- .../index/addon-mod-quiz-index.html | 4 +- src/addon/mod/quiz/lang/en.json | 1 + src/addon/mod/quiz/pages/attempt/attempt.html | 20 ++++---- .../navigation-modal/navigation-modal.html | 4 ++ .../navigation-modal.module.ts | 2 + .../navigation-modal/navigation-modal.scss | 46 ++++++++++++++++--- src/addon/mod/quiz/pages/player/player.scss | 5 ++ src/addon/mod/quiz/pages/review/review.html | 34 +++++++------- src/addon/mod/quiz/pages/review/review.scss | 11 +++-- .../qtype/calculated/calculated.module.ts | 2 + .../component/addon-qtype-calculated.html | 12 +++-- .../calculated/component/calculated.scss | 5 ++ .../component/ddimageortext.scss | 30 +++--------- src/addon/qtype/ddmarker/classes/ddmarker.ts | 18 ++------ .../qtype/ddmarker/component/ddmarker.scss | 2 +- src/addon/qtype/ddwtos/component/ddwtos.scss | 30 +++--------- .../qtype/gapselect/component/gapselect.scss | 8 +++- .../match/component/addon-qtype-match.html | 13 ++++-- src/addon/qtype/match/component/match.scss | 10 ++++ src/addon/qtype/match/match.module.ts | 2 + .../component/addon-qtype-multianswer.html | 2 +- .../component/addon-qtype-multichoice.html | 22 +++++---- .../multichoice/component/multichoice.scss | 13 ++++++ .../qtype/multichoice/multichoice.module.ts | 2 + .../component/addon-qtype-shortanswer.html | 8 +++- .../qtype/shortanswer/shortanswer.module.ts | 2 + src/app/app.scss | 26 ++++++++++- src/assets/lang/en.json | 1 + .../classes/base-question-component.ts | 14 +++++- src/theme/variables.scss | 4 ++ 30 files changed, 225 insertions(+), 128 deletions(-) create mode 100644 src/addon/qtype/calculated/component/calculated.scss create mode 100644 src/addon/qtype/match/component/match.scss create mode 100644 src/addon/qtype/multichoice/component/multichoice.scss 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 5304251e7..6316b64cc 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 @@ -22,11 +22,11 @@

{{ rule }}

-

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

+

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

{{ quiz.gradeMethodReadable }}

-

{{ 'core.lastsync' | translate }}

+

{{ 'core.lastsync' | translate }}

{{ syncTime }}

diff --git a/src/addon/mod/quiz/lang/en.json b/src/addon/mod/quiz/lang/en.json index 99887b291..18a699dda 100644 --- a/src/addon/mod/quiz/lang/en.json +++ b/src/addon/mod/quiz/lang/en.json @@ -1,4 +1,5 @@ { + "answercolon": "Answer:", "attemptfirst": "First attempt", "attemptlast": "Last attempt", "attemptnumber": "Attempt", diff --git a/src/addon/mod/quiz/pages/attempt/attempt.html b/src/addon/mod/quiz/pages/attempt/attempt.html index bc95ab867..aa4dc3b5f 100644 --- a/src/addon/mod/quiz/pages/attempt/attempt.html +++ b/src/addon/mod/quiz/pages/attempt/attempt.html @@ -9,25 +9,25 @@ - -

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

+ +

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

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

{{ attempt.attempt }}

- -

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

+ +

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

{{ sentence }}

- -

{{ 'addon.mod_quiz.marks' | translate }} / {{ quiz.sumGradesFormatted }}

+ +

{{ 'addon.mod_quiz.marks' | translate }} / {{ quiz.sumGradesFormatted }}

{{ attempt.readableMark }}

- -

{{ 'addon.mod_quiz.grade' | translate }} / {{ quiz.gradeFormatted }}

+ +

{{ 'addon.mod_quiz.grade' | translate }} / {{ quiz.gradeFormatted }}

{{ attempt.readableGrade }}

- -

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

+ +

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

diff --git a/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.html b/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.html index 7c90fd6f1..ded960206 100644 --- a/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.html +++ b/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.html @@ -24,6 +24,10 @@ {{ 'core.question.questionno' | translate:{$a: question.number} }} {{ 'core.question.information' | translate }} + + + + diff --git a/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.module.ts b/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.module.ts index 04ed4ca98..1152efb6f 100644 --- a/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.module.ts +++ b/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.module.ts @@ -16,6 +16,7 @@ import { NgModule } from '@angular/core'; import { IonicPageModule } from 'ionic-angular'; import { AddonModQuizNavigationModalPage } from './navigation-modal'; import { TranslateModule } from '@ngx-translate/core'; +import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; @NgModule({ @@ -24,6 +25,7 @@ import { CoreDirectivesModule } from '@directives/directives.module'; ], imports: [ CoreDirectivesModule, + CoreComponentsModule, IonicPageModule.forChild(AddonModQuizNavigationModalPage), TranslateModule.forChild() ] diff --git a/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.scss b/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.scss index a7b609f48..f732e2949 100644 --- a/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.scss +++ b/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.scss @@ -14,15 +14,49 @@ ion-app.app-root page-addon-mod-quiz-navigation-modal { } } - .item.core-question-correct .item-inner { - @include push-arrow-color($core-question-correct-color); + .item.core-question-correct { + &.addon-mod_quiz-selected { + @include border-start(5px, solid, $core-question-correct-color); + } + + .item-inner { + @include push-arrow-color($core-question-correct-color); + border-bottom-color: $core-question-correct-color; + } } - .item.core-question-incorrect .item-inner { - @include push-arrow-color($core-question-incorrect-color); + .item.core-question-incorrect, + .item.core-question-notanswered { + &.addon-mod_quiz-selected { + @include border-start(5px, solid, $core-question-incorrect-color); + } + + .item-inner { + @include push-arrow-color($core-question-incorrect-color); + border-bottom-color: $core-question-incorrect-color; + } } - .item.core-question-answersaved .item-inner { - @include push-arrow-color($text-color); + .item.core-question-partiallycorrect { + &.addon-mod_quiz-selected { + @include border-start(5px, solid, $core-question-state-partial-text); + } + + .item-inner { + @include push-arrow-color($core-question-state-partial-text); + border-bottom-color: $core-question-state-partial-text; + } + } + + .item.core-question-requiresgrading, + .item.core-question-answersaved { + &.addon-mod_quiz-selected { + @include border-start(5px, solid, $text-color); + } + + .item-inner { + @include push-arrow-color($text-color); + border-bottom-color: $text-color; + } } } diff --git a/src/addon/mod/quiz/pages/player/player.scss b/src/addon/mod/quiz/pages/player/player.scss index 5766ea400..d7febf8dd 100644 --- a/src/addon/mod/quiz/pages/player/player.scss +++ b/src/addon/mod/quiz/pages/player/player.scss @@ -11,4 +11,9 @@ ion-app.app-root page-addon-mod-quiz-player { .toolbar-ios .bar-buttons-ios .bar-button { @include padding-horizontal($content-padding); } + + .core-question-container { + display: block; + padding-bottom: $content-padding; + } } \ No newline at end of file diff --git a/src/addon/mod/quiz/pages/review/review.html b/src/addon/mod/quiz/pages/review/review.html index be085af08..f7682f1b2 100644 --- a/src/addon/mod/quiz/pages/review/review.html +++ b/src/addon/mod/quiz/pages/review/review.html @@ -22,36 +22,36 @@

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

- -

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

+ +

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

{{ attempt.timestart * 1000 | coreFormatDate }}

- -

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

+ +

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

{{ attempt.readableState }}

- -

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

+ +

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

{{ attempt.timefinish * 1000 | coreFormatDate }}

- -

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

+ +

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

{{ attempt.timeTaken }}

- -

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

+ +

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

{{ attempt.overTime }}

- -

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

+ +

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

- -

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

+ +

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

{{ attempt.readableGrade }}

- -

{{ data.title }}

+ +

{{ data.title }}

@@ -70,7 +70,7 @@

{{ 'core.question.questionno' | translate:{$a: question.number} }}

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

-

{{question.status}}

+

{{question.status}}

diff --git a/src/addon/mod/quiz/pages/review/review.scss b/src/addon/mod/quiz/pages/review/review.scss index 48a0f092d..e6b221d31 100644 --- a/src/addon/mod/quiz/pages/review/review.scss +++ b/src/addon/mod/quiz/pages/review/review.scss @@ -1,11 +1,12 @@ ion-app.app-root page-addon-mod-quiz-review { .item-radio-disabled, .item-checkbox-disabled, - .text-input[disabled] { - opacity: 1; + .item-select-disabled, + .item-input-disabled { + opacity: 0.8; - .label, .radio, .checkbox { - opacity: 1; - } + .label, .radio, .checkbox, .select-disabled, .core-correct-icon { + opacity: 1; + } } } diff --git a/src/addon/qtype/calculated/calculated.module.ts b/src/addon/qtype/calculated/calculated.module.ts index fed580b35..a819b755e 100644 --- a/src/addon/qtype/calculated/calculated.module.ts +++ b/src/addon/qtype/calculated/calculated.module.ts @@ -16,6 +16,7 @@ import { NgModule } from '@angular/core'; import { IonicModule } from 'ionic-angular'; import { TranslateModule } from '@ngx-translate/core'; import { CoreQuestionDelegate } from '@core/question/providers/delegate'; +import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; import { AddonQtypeCalculatedHandler } from './providers/handler'; import { AddonQtypeCalculatedComponent } from './component/calculated'; @@ -27,6 +28,7 @@ import { AddonQtypeCalculatedComponent } from './component/calculated'; imports: [ IonicModule, TranslateModule.forChild(), + CoreComponentsModule, CoreDirectivesModule ], providers: [ diff --git a/src/addon/qtype/calculated/component/addon-qtype-calculated.html b/src/addon/qtype/calculated/component/addon-qtype-calculated.html index 144d02c03..e2ea63c89 100644 --- a/src/addon/qtype/calculated/component/addon-qtype-calculated.html +++ b/src/addon/qtype/calculated/component/addon-qtype-calculated.html @@ -8,8 +8,9 @@ - - + + {{ 'addon.mod_quiz.answercolon' | translate }} + @@ -18,7 +19,7 @@ - + @@ -27,6 +28,7 @@ + @@ -54,9 +56,9 @@
-

{{option.text}}

+
- +
diff --git a/src/addon/qtype/calculated/component/calculated.scss b/src/addon/qtype/calculated/component/calculated.scss new file mode 100644 index 000000000..eb53fdb3d --- /dev/null +++ b/src/addon/qtype/calculated/component/calculated.scss @@ -0,0 +1,5 @@ +ion-app.app-root addon-qtype-calculated { + ion-col .select-disabled { + @include margin(0, 20px, 0, 0); + } +} \ No newline at end of file diff --git a/src/addon/qtype/ddimageortext/component/ddimageortext.scss b/src/addon/qtype/ddimageortext/component/ddimageortext.scss index 48c69d82e..57681ec2e 100644 --- a/src/addon/qtype/ddimageortext/component/ddimageortext.scss +++ b/src/addon/qtype/ddimageortext/component/ddimageortext.scss @@ -29,30 +29,12 @@ addon-qtype-ddimageortext { zoom: 1; } - .group1 { - background-color: $white; - } - .group2 { - background-color: $blue-light; - } - .group3 { - background-color: #DCDCDC; - } - .group4 { - background-color: #D8BFD8; - } - .group5 { - background-color: #87CEFA; - } - .group6 { - background-color: #DAA520; - } - .group7 { - background-color: #FFD700; - } - .group8 { - background-color: #F0E68C; + @for $i from 0 to length($core-dd-question-colors) { + .group#{$i + 1} { + background: nth($core-dd-question-colors, $i + 1); + } } + .drag { border: 1px solid $gray-darker; cursor: pointer; @@ -96,6 +78,6 @@ addon-qtype-ddimageortext { } .drag.beingdragged { z-index: 3; - box-shadow: 3px 3px 4px $gray-darker; + box-shadow: $core-dd-question-selected-shadow; } } diff --git a/src/addon/qtype/ddmarker/classes/ddmarker.ts b/src/addon/qtype/ddmarker/classes/ddmarker.ts index 46cb4909f..f303d5c1b 100644 --- a/src/addon/qtype/ddmarker/classes/ddmarker.ts +++ b/src/addon/qtype/ddmarker/classes/ddmarker.ts @@ -290,18 +290,13 @@ export class AddonQtypeDdMarkerQuestion { * @param {string} shape Name of the shape of the drop zone (circle, rectangle, polygon). * @param {string} coords Coordinates of the shape. * @param {string} colour Colour of the shape. - * @param {boolean} link Whether the marker should have a link in it. */ - drawDropZone(dropZoneNo: number, markerText: string, shape: string, coords: string, colour: string, link: boolean): void { + drawDropZone(dropZoneNo: number, markerText: string, shape: string, coords: string, colour: string): void { let existingMarkerText: HTMLElement; const markerTexts = this.doc.markerTexts(); // Check if there is already a marker text for this drop zone. - if (link) { - existingMarkerText = markerTexts.querySelector('span.markertext' + dropZoneNo + ' a'); - } else { - existingMarkerText = markerTexts.querySelector('span.markertext' + dropZoneNo); - } + existingMarkerText = markerTexts.querySelector('span.markertext' + dropZoneNo); if (existingMarkerText) { // Marker text already exists. Update it or remove it if empty. @@ -316,12 +311,7 @@ export class AddonQtypeDdMarkerQuestion { span = document.createElement('span'); span.className = classNames; - - if (link) { - span.innerHTML = '' + markerText + ''; - } else { - span.innerHTML = markerText; - } + span.innerHTML = markerText; markerTexts.appendChild(span); } @@ -802,7 +792,7 @@ export class AddonQtypeDdMarkerQuestion { dropZone = this.dropZones[dropZoneNo], dzNo = Number(dropZoneNo); - this.drawDropZone(dzNo, dropZone.markertext, dropZone.shape, dropZone.coords, colourForDropZone, true); + this.drawDropZone(dzNo, dropZone.markertext, dropZone.shape, dropZone.coords, colourForDropZone); } } } diff --git a/src/addon/qtype/ddmarker/component/ddmarker.scss b/src/addon/qtype/ddmarker/component/ddmarker.scss index e53f1baec..d9197d122 100644 --- a/src/addon/qtype/ddmarker/component/ddmarker.scss +++ b/src/addon/qtype/ddmarker/component/ddmarker.scss @@ -33,7 +33,7 @@ addon-qtype-ddmarker { .dragitem.beingdragged .markertext { z-index: 5; - box-shadow: 3px 3px 4px $gray-darker; + box-shadow: $core-dd-question-selected-shadow; } .dragitems .draghome { margin: 10px; diff --git a/src/addon/qtype/ddwtos/component/ddwtos.scss b/src/addon/qtype/ddwtos/component/ddwtos.scss index 47ae4151e..a594f55fa 100644 --- a/src/addon/qtype/ddwtos/component/ddwtos.scss +++ b/src/addon/qtype/ddwtos/component/ddwtos.scss @@ -40,10 +40,11 @@ addon-qtype-ddwtos { .drag { z-index: 2; border-radius: 5px; + line-height: 25px; } .drag.selected { z-index: 3; - box-shadow: 3px 3px 4px $gray-darker; + box-shadow: $core-dd-question-selected-shadow; } .drop.selected { @@ -73,29 +74,10 @@ addon-qtype-ddwtos { background-color: $green-light; } - .group1 { - background-color: $white; - } - .group2 { - background-color: #DCDCDC; - } - .group3 { - background-color: $blue-light; - } - .group4 { - background-color: #D8BFD8; - } - .group5 { - background-color: #87CEFA; - } - .group6 { - background-color: #DAA520; - } - .group7 { - background-color: #FFD700; - } - .group8 { - background-color: #F0E68C; + @for $i from 0 to length($core-dd-question-colors) { + .group#{$i + 1} { + background: nth($core-dd-question-colors, $i + 1); + } } sub, sup { diff --git a/src/addon/qtype/gapselect/component/gapselect.scss b/src/addon/qtype/gapselect/component/gapselect.scss index b467d9351..5311db7c8 100644 --- a/src/addon/qtype/gapselect/component/gapselect.scss +++ b/src/addon/qtype/gapselect/component/gapselect.scss @@ -1,5 +1,5 @@ // Style gapselect content a bit. All these styles are copied from Moodle. -addon-qtype-gapselect { +ion-app.app-root addon-qtype-gapselect { p { margin: 0 0 .5em; } @@ -15,5 +15,11 @@ addon-qtype-gapselect { border-radius: 4px; margin-bottom: 10px; background: $gray-lighter; + + &.core-question-answer-correct, + &.core-question-answer-incorrect { + background-color: $gray-lighter; + color: $text-color; + } } } diff --git a/src/addon/qtype/match/component/addon-qtype-match.html b/src/addon/qtype/match/component/addon-qtype-match.html index d905fade1..95bed5ddc 100644 --- a/src/addon/qtype/match/component/addon-qtype-match.html +++ b/src/addon/qtype/match/component/addon-qtype-match.html @@ -1,19 +1,22 @@
-

+
- - + +

- + - + {{option.label}} + + + diff --git a/src/addon/qtype/match/component/match.scss b/src/addon/qtype/match/component/match.scss new file mode 100644 index 000000000..89e49a50b --- /dev/null +++ b/src/addon/qtype/match/component/match.scss @@ -0,0 +1,10 @@ +ion-app.app-root addon-qtype-match { + ion-col .select-disabled { + @include margin(0, 20px, 0, 0); + } + + .core-correct-icon { + bottom: 50%; + margin-bottom: -7px; + } +} diff --git a/src/addon/qtype/match/match.module.ts b/src/addon/qtype/match/match.module.ts index b4b56f334..f140418a4 100644 --- a/src/addon/qtype/match/match.module.ts +++ b/src/addon/qtype/match/match.module.ts @@ -16,6 +16,7 @@ import { NgModule } from '@angular/core'; import { IonicModule } from 'ionic-angular'; import { TranslateModule } from '@ngx-translate/core'; import { CoreQuestionDelegate } from '@core/question/providers/delegate'; +import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; import { AddonQtypeMatchHandler } from './providers/handler'; import { AddonQtypeMatchComponent } from './component/match'; @@ -27,6 +28,7 @@ import { AddonQtypeMatchComponent } from './component/match'; imports: [ IonicModule, TranslateModule.forChild(), + CoreComponentsModule, CoreDirectivesModule ], providers: [ diff --git a/src/addon/qtype/multianswer/component/addon-qtype-multianswer.html b/src/addon/qtype/multianswer/component/addon-qtype-multianswer.html index 899f1c34e..74bc91da4 100644 --- a/src/addon/qtype/multianswer/component/addon-qtype-multianswer.html +++ b/src/addon/qtype/multianswer/component/addon-qtype-multianswer.html @@ -1,5 +1,5 @@
-

+
diff --git a/src/addon/qtype/multichoice/component/addon-qtype-multichoice.html b/src/addon/qtype/multichoice/component/addon-qtype-multichoice.html index a39aaaa2e..c41b376b9 100644 --- a/src/addon/qtype/multichoice/component/addon-qtype-multichoice.html +++ b/src/addon/qtype/multichoice/component/addon-qtype-multichoice.html @@ -1,18 +1,20 @@
-

-

+ +
- - + + -

+
- + + + @@ -21,12 +23,14 @@
- + -

+
- + + +
diff --git a/src/addon/qtype/multichoice/component/multichoice.scss b/src/addon/qtype/multichoice/component/multichoice.scss new file mode 100644 index 000000000..6312d0e50 --- /dev/null +++ b/src/addon/qtype/multichoice/component/multichoice.scss @@ -0,0 +1,13 @@ +ion-app.app-root addon-qtype-multichoice { + .core-correct-icon { + bottom: 50%; + margin-bottom: -7px; + } + + .specificfeedback { + background-color: $core-question-feedback-color-bg; + color: $core-question-feedback-color; + display: inline; + padding: 0 .7em; + } +} diff --git a/src/addon/qtype/multichoice/multichoice.module.ts b/src/addon/qtype/multichoice/multichoice.module.ts index c3d591ba7..a0e36e755 100644 --- a/src/addon/qtype/multichoice/multichoice.module.ts +++ b/src/addon/qtype/multichoice/multichoice.module.ts @@ -17,6 +17,7 @@ import { IonicModule } from 'ionic-angular'; import { TranslateModule } from '@ngx-translate/core'; import { CoreQuestionDelegate } from '@core/question/providers/delegate'; import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreComponentsModule } from '@components/components.module'; import { AddonQtypeMultichoiceHandler } from './providers/handler'; import { AddonQtypeMultichoiceComponent } from './component/multichoice'; @@ -27,6 +28,7 @@ import { AddonQtypeMultichoiceComponent } from './component/multichoice'; imports: [ IonicModule, TranslateModule.forChild(), + CoreComponentsModule, CoreDirectivesModule ], providers: [ diff --git a/src/addon/qtype/shortanswer/component/addon-qtype-shortanswer.html b/src/addon/qtype/shortanswer/component/addon-qtype-shortanswer.html index 550e65863..6f31a2d39 100644 --- a/src/addon/qtype/shortanswer/component/addon-qtype-shortanswer.html +++ b/src/addon/qtype/shortanswer/component/addon-qtype-shortanswer.html @@ -2,6 +2,10 @@

- - + + {{ 'addon.mod_quiz.answercolon' | translate }} + + + +
diff --git a/src/addon/qtype/shortanswer/shortanswer.module.ts b/src/addon/qtype/shortanswer/shortanswer.module.ts index 58497545d..313a23b75 100644 --- a/src/addon/qtype/shortanswer/shortanswer.module.ts +++ b/src/addon/qtype/shortanswer/shortanswer.module.ts @@ -16,6 +16,7 @@ import { NgModule } from '@angular/core'; import { IonicModule } from 'ionic-angular'; import { TranslateModule } from '@ngx-translate/core'; import { CoreQuestionDelegate } from '@core/question/providers/delegate'; +import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; import { AddonQtypeShortAnswerHandler } from './providers/handler'; import { AddonQtypeShortAnswerComponent } from './component/shortanswer'; @@ -27,6 +28,7 @@ import { AddonQtypeShortAnswerComponent } from './component/shortanswer'; imports: [ IonicModule, TranslateModule.forChild(), + CoreComponentsModule, CoreDirectivesModule ], providers: [ diff --git a/src/app/app.scss b/src/app/app.scss index b8b9ec993..902622a0c 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -378,6 +378,21 @@ ion-app.app-root { .select-icon .select-icon-inner { color: $core-select-placeholder-color; } + + &.select-disabled, .select-icon .select-icon-inner { + color: $text-color; + } + @each $color-name, $color-base, $color-contrast in get-colors($colors) { + &.select-md-#{$color-name}, + &.select-ios-#{$color-name}, + &.select-wp-#{$color-name} { + color: $color-base; + + .select-icon .select-icon-inner { + color: $color-base; + } + } + } } ion-select.core-button-select, @@ -454,8 +469,16 @@ ion-app.app-root { // Question. // ------------------------- + .core-correct-icon { + padding: 0 ($content-padding / 2); + position: absolute; + @include position(null, 0, $content-padding / 2, null); + margin-top: 0; + margin-bottom: 0; + } .core-question-answer-correct, + .core-question-correct, .core-question-comment { color: $core-question-correct-color; background-color: $core-question-correct-color-bg; @@ -533,7 +556,8 @@ ion-app.app-root { .core-question-incorrect { background-color: $core-question-state-incorrect-color; } - .core-question-answersaved { + .core-question-answersaved, + .core-question-requiresgrading { color: $text-color; background-color: $core-question-saved-color-bg; } diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index 58ab6beb3..47566d043 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -602,6 +602,7 @@ "addon.mod_lti.modulenameplural": "External tools", "addon.mod_page.errorwhileloadingthepage": "Error while loading the page content.", "addon.mod_page.modulenameplural": "Pages", + "addon.mod_quiz.answercolon": "Answer:", "addon.mod_quiz.attemptfirst": "First attempt", "addon.mod_quiz.attemptlast": "Last attempt", "addon.mod_quiz.attemptnumber": "Attempt", diff --git a/src/core/question/classes/base-question-component.ts b/src/core/question/classes/base-question-component.ts index 771502142..2e78a2dfe 100644 --- a/src/core/question/classes/base-question-component.ts +++ b/src/core/question/classes/base-question-component.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Input, Output, EventEmitter, Injector } from '@angular/core'; +import { Input, Output, EventEmitter, Injector, ElementRef } from '@angular/core'; import { CoreLoggerProvider } from '@providers/logger'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreTextUtilsProvider } from '@providers/utils/text'; @@ -34,6 +34,7 @@ export class CoreQuestionBaseComponent { protected questionHelper: CoreQuestionHelperProvider; protected domUtils: CoreDomUtilsProvider; protected textUtils: CoreTextUtilsProvider; + protected realElement: HTMLElement; constructor(logger: CoreLoggerProvider, logName: string, protected injector: Injector) { this.logger = logger.getInstance(logName); @@ -42,6 +43,7 @@ export class CoreQuestionBaseComponent { this.questionHelper = injector.get(CoreQuestionHelperProvider); this.domUtils = injector.get(CoreDomUtilsProvider); this.textUtils = injector.get(CoreTextUtilsProvider); + this.realElement = injector.get(ElementRef).nativeElement; } /** @@ -172,6 +174,8 @@ export class CoreQuestionBaseComponent { return this.questionHelper.showComponentError(this.onAbort); } + this.realElement.classList.add('core-question-container'); + const element = this.domUtils.convertToElement(this.question.html); // Extract question text. @@ -291,12 +295,20 @@ export class CoreQuestionBaseComponent { // Check if question is marked as correct. if (input.classList.contains('incorrect')) { this.question.input.correctClass = 'core-question-incorrect'; + this.question.input.correctIcon = 'fa-remove'; + this.question.input.correctIconColor = 'danger'; } else if (input.classList.contains('correct')) { this.question.input.correctClass = 'core-question-correct'; + this.question.input.correctIcon = 'fa-check'; + this.question.input.correctIconColor = 'success'; } else if (input.classList.contains('partiallycorrect')) { this.question.input.correctClass = 'core-question-partiallycorrect'; + this.question.input.correctIcon = 'fa-check-square'; + this.question.input.correctIconColor = 'warning'; } else { this.question.input.correctClass = ''; + this.question.input.correctIcon = ''; + this.question.input.correctIconColor = ''; } } diff --git a/src/theme/variables.scss b/src/theme/variables.scss index f3fbdfd3e..da912fa66 100644 --- a/src/theme/variables.scss +++ b/src/theme/variables.scss @@ -279,8 +279,12 @@ $core-question-saved-color-bg: $gray-light !default; $core-question-state-correct-color: $green-light !default; $core-question-state-partial-color: $yellow-light !default; +$core-question-state-partial-text: $yellow !default; $core-question-state-incorrect-color: $red-light !default; +$core-dd-question-selected-shadow: 2px 2px 4px $gray-dark !default; +$core-dd-question-colors: $white, $blue-light, #DCDCDC, #D8BFD8, #87CEFA, #DAA520, #FFD700, #F0E68C !default; + // Mixins // ------------------------- @mixin core-transition($where: all, $time: 500ms) { From 97d4f6d4edfde231fe2e2c5cc2a0297b29d2ebb6 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 29 Jan 2019 13:03:31 +0100 Subject: [PATCH 032/191] MOBILE-2844 assign: Fix prefetch error if grader was -1 --- src/addon/mod/assign/providers/prefetch-handler.ts | 2 +- src/core/user/providers/user.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/addon/mod/assign/providers/prefetch-handler.ts b/src/addon/mod/assign/providers/prefetch-handler.ts index d7a86836a..93ecb3f68 100644 --- a/src/addon/mod/assign/providers/prefetch-handler.ts +++ b/src/addon/mod/assign/providers/prefetch-handler.ts @@ -392,7 +392,7 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan // Prefetch feedback. if (submission.feedback) { // Get profile and image of the grader. - if (submission.feedback.grade && submission.feedback.grade.grader) { + if (submission.feedback.grade && submission.feedback.grade.grader && submission.feedback.grade.grader != -1) { userIds.push(submission.feedback.grade.grader); } diff --git a/src/core/user/providers/user.ts b/src/core/user/providers/user.ts index 2a4d83d47..242c586d6 100644 --- a/src/core/user/providers/user.ts +++ b/src/core/user/providers/user.ts @@ -397,7 +397,7 @@ export class CoreUserProvider { userId = Number(userId); // Make sure it's a number. // Prevent repeats and errors. - if (!isNaN(userId) && !treated[userId]) { + if (!isNaN(userId) && !treated[userId] && userId > 0) { treated[userId] = true; promises.push(this.getProfile(userId, courseId, false, siteId).then((profile) => { From 48b5233a84e834d697c33a413741c2ea9162816c Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 30 Jan 2019 09:39:28 +0100 Subject: [PATCH 033/191] MOBILE-2114 scorm: Fix convert attempt to offline JS error --- src/addon/mod/scorm/providers/scorm-offline.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/addon/mod/scorm/providers/scorm-offline.ts b/src/addon/mod/scorm/providers/scorm-offline.ts index 910628d46..b8cdc1d43 100644 --- a/src/addon/mod/scorm/providers/scorm-offline.ts +++ b/src/addon/mod/scorm/providers/scorm-offline.ts @@ -430,6 +430,8 @@ export class AddonModScormOfflineProvider { * @return {{[scoId: number]: string}} Launch URLs indexed by SCO ID. */ protected getLaunchUrlsFromScos(scos: any[]): {[scoId: number]: string} { + scos = scos || []; + const response = {}; scos.forEach((sco) => { @@ -487,12 +489,15 @@ export class AddonModScormOfflineProvider { * * @param {number} scormId SCORM ID. * @param {number} attempt Attempt number. - * @param {any[]} scos SCOs returned by AddonModScormProvider.getScos. + * @param {any[]} scos SCOs returned by AddonModScormProvider.getScos. If not supplied, this function will only return the + * SCOs that have something stored and cmi.launch_data will be undefined. * @param {string} [siteId] Site ID. If not defined, current site. * @param {number} [userId] User ID. If not defined use site's current user. * @return {Promise} Promise resolved when the user data is retrieved. */ getScormUserData(scormId: number, attempt: number, scos: any[], siteId?: string, userId?: number): Promise { + scos = scos || []; + let fullName = '', userName = ''; From cba4f2271a0795134c217a2a9d91e5fc6ba6a04c Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 30 Jan 2019 10:02:28 +0100 Subject: [PATCH 034/191] MOBILE-2114 scorm: Prefetch after sending data --- src/addon/mod/scorm/components/index/index.ts | 36 ++++++-- src/addon/mod/scorm/providers/scorm-sync.ts | 39 ++++++-- src/addon/mod/scorm/providers/scorm.ts | 17 +++- .../providers/module-prefetch-delegate.ts | 89 ++++++++++++++++++- 4 files changed, 164 insertions(+), 17 deletions(-) diff --git a/src/addon/mod/scorm/components/index/index.ts b/src/addon/mod/scorm/components/index/index.ts index ec881b25d..de725b272 100644 --- a/src/addon/mod/scorm/components/index/index.ts +++ b/src/addon/mod/scorm/components/index/index.ts @@ -61,7 +61,8 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom protected lastAttempt: number; // Last attempt. protected lastIsOffline: boolean; // Whether the last attempt is offline. protected hasPlayed = false; // Whether the user has opened the player page. - protected syncDueToPlayerLeft = false; // Whether a sync was due to the user leaving the player. + protected dataSentObserver; // To detect data sent to server. + protected dataSent = false; // Whether some data was sent to server while playing the SCORM. constructor(injector: Injector, protected scormProvider: AddonModScormProvider, @Optional() protected content: Content, protected scormHelper: AddonModScormHelperProvider, protected scormOffline: AddonModScormOfflineProvider, @@ -330,13 +331,12 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom * @return {boolean} If suceed or not. */ protected hasSyncSucceed(result: any): boolean { - if (result.updated || this.syncDueToPlayerLeft) { - // Check completion status if something was sent or the user just left the player. - // If the user plays the SCORM in online we don't know if he sent data or not, so always check completion. + if (result.updated || this.dataSent) { + // Check completion status if something was sent. this.checkCompletion(); } - this.syncDueToPlayerLeft = false; + this.dataSent = false; return true; } @@ -349,11 +349,13 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom if (this.hasPlayed) { this.hasPlayed = false; - this.syncDueToPlayerLeft = true; this.scormOptions.newAttempt = false; // Uncheck new attempt. // Add a delay to make sure the player has started the last writing calls so we can detect conflicts. setTimeout(() => { + this.dataSentObserver && this.dataSentObserver.off(); // Stop listening for changes. + this.dataSentObserver = undefined; + // Refresh data. this.showLoadingAndRefresh(true, false); }, 500); @@ -368,6 +370,15 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom if (this.navCtrl.getActive().component.name == 'AddonModScormPlayerPage') { this.hasPlayed = true; + + // Detect if anything was sent to server. + this.dataSentObserver && this.dataSentObserver.off(); + + this.dataSentObserver = this.eventsProvider.on(AddonModScormProvider.DATA_SENT_EVENT, (data) => { + if (data.scormId === this.scorm.id) { + this.dataSent = true; + } + }, this.siteId); } } @@ -533,6 +544,17 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom * @return {Promise} Promise resolved when done. */ protected sync(): Promise { - return this.scormSync.syncScorm(this.scorm); + return this.scormSync.syncScorm(this.scorm).then((result) => { + if (!result.updated && this.dataSent) { + // The user sent data to server, but not in the sync process. Check if we need to fetch data. + return this.scormSync.prefetchAfterUpdate(this.module, this.courseId).catch(() => { + // Ignore errors. + }).then(() => { + return result; + }); + } + + return result; + }); } } diff --git a/src/addon/mod/scorm/providers/scorm-sync.ts b/src/addon/mod/scorm/providers/scorm-sync.ts index 9a5d06ae7..e9b5014fb 100644 --- a/src/addon/mod/scorm/providers/scorm-sync.ts +++ b/src/addon/mod/scorm/providers/scorm-sync.ts @@ -23,6 +23,7 @@ import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate'; import { CoreSyncBaseProvider } from '@classes/base-sync'; import { AddonModScormProvider, AddonModScormAttemptCountResult } from './scorm'; import { AddonModScormOfflineProvider } from './scorm-offline'; @@ -63,9 +64,10 @@ export class AddonModScormSyncProvider extends CoreSyncBaseProvider { constructor(loggerProvider: CoreLoggerProvider, sitesProvider: CoreSitesProvider, appProvider: CoreAppProvider, syncProvider: CoreSyncProvider, textUtils: CoreTextUtilsProvider, translate: TranslateService, - courseProvider: CoreCourseProvider, private eventsProvider: CoreEventsProvider, timeUtils: CoreTimeUtilsProvider, + private eventsProvider: CoreEventsProvider, timeUtils: CoreTimeUtilsProvider, private scormProvider: AddonModScormProvider, private scormOfflineProvider: AddonModScormOfflineProvider, - private prefetchHandler: AddonModScormPrefetchHandler, private utils: CoreUtilsProvider) { + private prefetchHandler: AddonModScormPrefetchHandler, private utils: CoreUtilsProvider, + private prefetchDelegate: CoreCourseModulePrefetchDelegate, private courseProvider: CoreCourseProvider) { super('AddonModScormSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, timeUtils); @@ -190,11 +192,11 @@ export class AddonModScormSyncProvider extends CoreSyncBaseProvider { let promise; if (updated) { - // Update the WS data. - promise = this.scormProvider.invalidateAllScormData(scorm.id, siteId).catch(() => { + // Update downloaded data. + promise = this.courseProvider.getModuleBasicInfoByInstance(scorm.id, 'scorm', siteId).then((module) => { + return this.prefetchAfterUpdate(module, scorm.course); + }).catch(() => { // Ignore errors. - }).then(() => { - return this.prefetchHandler.fetchWSData(scorm, siteId); }); } else { promise = Promise.resolve(); @@ -357,6 +359,31 @@ export class AddonModScormSyncProvider extends CoreSyncBaseProvider { }); } + /** + * Prefetch data after an update. It won't prefetch the data if the package file was updated. + * + * @param {any} module Module. + * @param {number} courseId Course ID. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when done. + */ + prefetchAfterUpdate(module: any, courseId: number, siteId?: string): Promise { + // Get the module updates to check if the package was updated or not. + return this.prefetchDelegate.getModuleUpdates(module, courseId, true, siteId).then((result) => { + + if (result && result.updates) { + // Only prefetch if the package file hasn't changed. + const fileChanged = !!result.updates.find((entry) => { + return entry.name == 'packagefiles'; + }); + + if (!fileChanged) { + return this.prefetchHandler.download(module, courseId); + } + } + }); + } + /** * Save a snapshot from a synchronization. * diff --git a/src/addon/mod/scorm/providers/scorm.ts b/src/addon/mod/scorm/providers/scorm.ts index 6c3a7a151..201bdeaee 100644 --- a/src/addon/mod/scorm/providers/scorm.ts +++ b/src/addon/mod/scorm/providers/scorm.ts @@ -14,6 +14,7 @@ import { Injectable } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; +import { CoreEventsProvider } from '@providers/events'; import { CoreFilepoolProvider } from '@providers/filepool'; import { CoreLoggerProvider } from '@providers/logger'; import { CoreSitesProvider } from '@providers/sites'; @@ -86,6 +87,7 @@ export class AddonModScormProvider { static LAUNCH_PREV_SCO_EVENT = 'addon_mod_scorm_launch_prev_sco'; static UPDATE_TOC_EVENT = 'addon_mod_scorm_update_toc'; static GO_OFFLINE_EVENT = 'addon_mod_scorm_go_offline'; + static DATA_SENT_EVENT = 'addon_mod_scorm_data_sent'; // Protected constants. protected VALID_STATUSES = ['notattempted', 'passed', 'completed', 'failed', 'incomplete', 'browsed', 'suspend']; @@ -110,7 +112,8 @@ export class AddonModScormProvider { constructor(logger: CoreLoggerProvider, private translate: TranslateService, private sitesProvider: CoreSitesProvider, private wsProvider: CoreWSProvider, private textUtils: CoreTextUtilsProvider, private utils: CoreUtilsProvider, private filepoolProvider: CoreFilepoolProvider, private scormOfflineProvider: AddonModScormOfflineProvider, - private timeUtils: CoreTimeUtilsProvider, private syncProvider: CoreSyncProvider) { + private timeUtils: CoreTimeUtilsProvider, private syncProvider: CoreSyncProvider, + private eventsProvider: CoreEventsProvider) { this.logger = logger.getInstance('AddonModScormProvider'); } @@ -1483,6 +1486,12 @@ export class AddonModScormProvider { return this.saveTracksOnline(scorm.id, scoId, attempt, tracks, siteId).then(() => { // Tracks have been saved, update cached user data. this.updateUserDataAfterSave(scorm.id, attempt, tracks, siteId); + + this.eventsProvider.trigger(AddonModScormProvider.DATA_SENT_EVENT, { + scormId: scorm.id, + scoId: scoId, + attempt: attempt + }, this.sitesProvider.getCurrentSiteId()); }); } } @@ -1546,6 +1555,12 @@ export class AddonModScormProvider { if (success) { // Tracks have been saved, update cached user data. this.updateUserDataAfterSave(scorm.id, attempt, tracks); + + this.eventsProvider.trigger(AddonModScormProvider.DATA_SENT_EVENT, { + scormId: scorm.id, + scoId: scoId, + attempt: attempt + }, this.sitesProvider.getCurrentSiteId()); } return success; diff --git a/src/core/course/providers/module-prefetch-delegate.ts b/src/core/course/providers/module-prefetch-delegate.ts index 17a8d9f3c..e69b04036 100644 --- a/src/core/course/providers/module-prefetch-delegate.ts +++ b/src/core/course/providers/module-prefetch-delegate.ts @@ -826,7 +826,7 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { } /** - * Get a module status and download time. It will only return the download time if the module is downloaded and not outdated. + * Get a module status and download time. It will only return the download time if the module is downloaded or outdated. * * @param {any} module Module. * @param {number} courseId Course ID the module belongs to. @@ -841,8 +841,8 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { const packageId = this.filepoolProvider.getPackageId(handler.component, module.id), status = this.statusCache.getValue(packageId, 'status'); - if (typeof status != 'undefined' && status != CoreConstants.DOWNLOADED) { - // Status is different than downloaded, just return the status. + if (typeof status != 'undefined' && status != CoreConstants.DOWNLOADED && status != CoreConstants.OUTDATED) { + // Module isn't downloaded, just return the status. return Promise.resolve({ status: status }); @@ -872,6 +872,75 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { }); } + /** + * Get updates for a certain module. + * It will only return the updates if the module can use check updates and it's downloaded or outdated. + * + * @param {any} module Module to check. + * @param {number} courseId Course the module belongs to. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with the updates. + */ + getModuleUpdates(module: any, courseId: number, ignoreCache?: boolean, siteId?: string): Promise { + + return this.sitesProvider.getSite(siteId).then((site) => { + // Get the status and download time of the module. + return this.getModuleStatusAndDownloadTime(module, courseId).then((data) => { + if (data.status != CoreConstants.DOWNLOADED && data.status != CoreConstants.OUTDATED) { + // Not downloaded, no updates. + return {}; + } + + // Module is downloaded. Check if it can check updates. + return this.canModuleUseCheckUpdates(module, courseId).then((canUse) => { + if (!canUse) { + // Can't use check updates, no updates. + return {}; + } + + const params = { + courseid: courseId, + tocheck: [ + { + contextlevel: 'module', + id: module.id, + since: data.downloadTime || 0 + } + ] + }, + preSets: CoreSiteWSPreSets = { + cacheKey: this.getModuleUpdatesCacheKey(courseId, module.id), + }; + + if (ignoreCache) { + preSets.getFromCache = false; + preSets.emergencyCache = false; + } + + return site.read('core_course_check_updates', params, preSets).then((response) => { + if (!response || !response.instances || !response.instances[0]) { + return Promise.reject(null); + } + + return response.instances[0]; + }); + }); + }); + }); + } + + /** + * Get cache key for module updates WS calls. + * + * @param {number} courseId Course ID. + * @param {number} moduleId Module ID. + * @return {string} Cache key. + */ + protected getModuleUpdatesCacheKey(courseId: number, moduleId: number): string { + return this.getCourseUpdatesCacheKey(courseId) + ':' + moduleId; + } + /** * Get a prefetch handler. * @@ -933,6 +1002,20 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { } } + /** + * Invalidate check updates WS call for a certain module. + * + * @param {number} courseId Course ID. + * @param {number} moduleId Module ID. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when data is invalidated. + */ + invalidateModuleUpdates(courseId: number, moduleId: number, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + return site.invalidateWsCacheForKey(this.getModuleUpdatesCacheKey(courseId, moduleId)); + }); + } + /** * Check if a list of modules is being downloaded. * From babe54fe7750df2f05eb65741cf8dc43048f6f4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 31 Jan 2019 11:52:25 +0100 Subject: [PATCH 035/191] MOBILE-2855 workshop: Show received grades on closed phase --- .../components/submission/addon-mod-workshop-submission.html | 2 +- src/addon/mod/workshop/pages/assessment/assessment.html | 2 +- src/addon/mod/workshop/pages/assessment/assessment.ts | 4 ++-- src/addon/mod/workshop/pages/submission/submission.ts | 5 +++-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/addon/mod/workshop/components/submission/addon-mod-workshop-submission.html b/src/addon/mod/workshop/components/submission/addon-mod-workshop-submission.html index 2637cc500..180f298c8 100644 --- a/src/addon/mod/workshop/components/submission/addon-mod-workshop-submission.html +++ b/src/addon/mod/workshop/components/submission/addon-mod-workshop-submission.html @@ -1,7 +1,7 @@
- +

{{submission.title}}

{{profile.fullname}}

diff --git a/src/addon/mod/workshop/pages/assessment/assessment.html b/src/addon/mod/workshop/pages/assessment/assessment.html index 4834ab4ca..ecc8b3fd3 100644 --- a/src/addon/mod/workshop/pages/assessment/assessment.html +++ b/src/addon/mod/workshop/pages/assessment/assessment.html @@ -36,7 +36,7 @@ - +

diff --git a/src/addon/mod/workshop/pages/assessment/assessment.ts b/src/addon/mod/workshop/pages/assessment/assessment.ts index 22c1b2bde..09b6a1e3f 100644 --- a/src/addon/mod/workshop/pages/assessment/assessment.ts +++ b/src/addon/mod/workshop/pages/assessment/assessment.ts @@ -164,8 +164,8 @@ export class AddonModWorkshopAssessmentPage implements OnInit, OnDestroy { if (this.evaluating || this.workshop.phase == AddonModWorkshopProvider.PHASE_CLOSED) { // Get all info of the assessment. - return this.workshopHelper.getReviewerAssessmentById(this.workshopId, this.assessmentId, this.profile.id) - .then((assessment) => { + return this.workshopHelper.getReviewerAssessmentById(this.workshopId, this.assessmentId, + this.profile && this.profile.id).then((assessment) => { let defaultGrade, promise; this.assessment = this.workshopHelper.realGradeValue(this.workshop, assessment); diff --git a/src/addon/mod/workshop/pages/submission/submission.ts b/src/addon/mod/workshop/pages/submission/submission.ts index 218bfe2c7..b058e1d05 100644 --- a/src/addon/mod/workshop/pages/submission/submission.ts +++ b/src/addon/mod/workshop/pages/submission/submission.ts @@ -202,7 +202,7 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy { this.workshop.phase < AddonModWorkshopProvider.PHASE_CLOSED && this.access.canoverridegrades; this.ownAssessment = false; - if (this.access.canviewallassessments) { + if (this.access.canviewallassessments || this.currentUserId == this.userId) { // Get new data, different that came from stateParams. promises.push(this.workshopProvider.getSubmissionAssessments(this.workshopId, this.submissionId) .then((subAssessments) => { @@ -291,7 +291,8 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy { this.feedbackForm.controls['text'].setValue(this.evaluate.text); }); })); - } else if (this.workshop.phase == AddonModWorkshopProvider.PHASE_CLOSED && submissionData.gradeoverby) { + } else if (this.workshop.phase == AddonModWorkshopProvider.PHASE_CLOSED && submissionData.gradeoverby && + this.evaluate && this.evaluate.text) { promises.push(this.userProvider.getProfile(submissionData.gradeoverby, this.courseId, true).then((profile) => { this.evaluateByProfile = profile; })); From 238487f509400402632cfeb41fe329b435046773 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 29 Jan 2019 16:07:53 +0100 Subject: [PATCH 036/191] MOBILE-2846 desktop: Build desktop linux build from Travis --- .travis.yml | 15 +++++++++++---- package.json | 2 +- scripts/aot.sh | 13 +++++++++---- scripts/linux.sh | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 55 insertions(+), 9 deletions(-) create mode 100755 scripts/linux.sh diff --git a/.travis.yml b/.travis.yml index 25bb9edc0..cedbc149d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,23 @@ sudo: required -dist: trusty +dist: xenial group: edge language: node_js -node_js: - - '8.10' +node_js: stable + +before_cache: + - rm -rf $HOME/.cache/electron-builder/wine + +cache: + directories: + - node_modules + - $HOME/.cache/electron + - $HOME/.cache/electron-builder before_script: - npm install -g @angular/cli - npm i npm@latest -g - gulp - - rm -Rf node_modules/electron-builder-squirrel-windows node_modules/electron-windows-notifications #Avoid electron fail script: - npm run build diff --git a/package.json b/package.json index 6116e057b..9150b17d9 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "preionic:build": "gulp", "postionic:build": "gulp copy-component-templates", "desktop.pack": "electron-builder --dir", - "desktop.dist": "electron-builder", + "desktop.dist": "electron-builder -p never", "windows.store": "electron-windows-store --input-directory .\\desktop\\dist\\win-unpacked --output-directory .\\desktop\\store --flatten true -a .\\resources\\desktop -m .\\desktop\\assets\\windows\\AppXManifest.xml --package-version 0.0.0.0 --package-name MoodleDesktop" }, "dependencies": { diff --git a/scripts/aot.sh b/scripts/aot.sh index 5246a8010..f10a70790 100755 --- a/scripts/aot.sh +++ b/scripts/aot.sh @@ -1,7 +1,7 @@ #!/bin/bash # Compile AOT. -if [ $TRAVIS_BRANCH == 'integration' ] || [ $TRAVIS_BRANCH == 'master' ] || [ -z $TRAVIS_BRANCH ] ; then +if [ $TRAVIS_BRANCH == 'integration' ] || [ $TRAVIS_BRANCH == 'master' ] || [ $TRAVIS_BRANCH == 'desktop' ] || [ -z $TRAVIS_BRANCH ] ; then cd scripts ./build_lang.sh cd .. @@ -38,9 +38,8 @@ fi # Copy to PGB git (only on a configured travis build). if [ ! -z $GIT_ORG ] && [ ! -z $GIT_TOKEN ] ; then gitfolder=${PWD##*/} - cd .. - git clone --depth 1 --no-single-branch https://github.com/$GIT_ORG/moodlemobile-phonegapbuild.git pgb - cd pgb + git clone --depth 1 --no-single-branch https://github.com/$GIT_ORG/moodlemobile-phonegapbuild.git ../pgb + pushd ../pgb git checkout $TRAVIS_BRANCH rm -Rf assets build index.html templates cp -Rf ../$gitfolder/www/* ./ @@ -48,4 +47,10 @@ if [ ! -z $GIT_ORG ] && [ ! -z $GIT_TOKEN ] ; then git add . git commit -m "Travis build: $TRAVIS_BUILD_NUMBER" git push https://$GIT_TOKEN@github.com/$GIT_ORG/moodlemobile-phonegapbuild.git + popd fi + +if [ ! -z $GIT_ORG_PRIVATE ] && [ ! -z $GIT_TOKEN ] && [ $TRAVIS_BRANCH == 'desktop' ] && [ $TRAVIS_OS_NAME == 'linux' ]; then + ./scripts/linux.sh +fi + diff --git a/scripts/linux.sh b/scripts/linux.sh new file mode 100755 index 000000000..ef76cd0a5 --- /dev/null +++ b/scripts/linux.sh @@ -0,0 +1,34 @@ +#!/bin/bash +# +# Script for generating the Desktop builds +# + +sudo apt-get install -y libnss3-dev + +npm install -g electron-builder electron + +electron-builder install-app-deps + +jq -s '.[0] + {"name": "moodledesktop"}' package.json > package_new.json +mv package_new.json package.json + +rm -Rf desktop/dist + +npm run desktop.dist -- -l --x64 --ia32 + +if [ ! -z $GIT_ORG_PRIVATE ] && [ ! -z $GIT_TOKEN ] ; then + git clone -q https://$GIT_TOKEN@github.com/moodlemobile/bma-apps-builds.git ../apps + + mv desktop/dist/*.AppImage ../apps + + cd ../apps + + chmod +x *.AppImage + mv *i386.AppImage linux-ia32.AppImage + mv Moodle*.AppImage linux-x64.AppImage + ls + + git add . + git commit -m "Linux desktop versions from Travis build $TRAVIS_BUILD_NUMBER" + git push +fi From 108fd65aa04244748a500c86b27ae2f03836f722 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 31 Jan 2019 10:38:30 +0100 Subject: [PATCH 037/191] MOBILE-2847 shortanswer: Handle inline answer boxes --- src/addon/mod/quiz/pages/review/review.scss | 3 ++- .../component/addon-qtype-shortanswer.html | 4 ++-- .../qtype/shortanswer/component/shortanswer.scss | 11 +++++++++++ .../question/classes/base-question-component.ts | 13 ++++++++++++- 4 files changed, 27 insertions(+), 4 deletions(-) create mode 100644 src/addon/qtype/shortanswer/component/shortanswer.scss diff --git a/src/addon/mod/quiz/pages/review/review.scss b/src/addon/mod/quiz/pages/review/review.scss index e6b221d31..47b755a75 100644 --- a/src/addon/mod/quiz/pages/review/review.scss +++ b/src/addon/mod/quiz/pages/review/review.scss @@ -2,7 +2,8 @@ ion-app.app-root page-addon-mod-quiz-review { .item-radio-disabled, .item-checkbox-disabled, .item-select-disabled, - .item-input-disabled { + .item-input-disabled, + .text-input[disabled] { opacity: 0.8; .label, .radio, .checkbox, .select-disabled, .core-correct-icon { diff --git a/src/addon/qtype/shortanswer/component/addon-qtype-shortanswer.html b/src/addon/qtype/shortanswer/component/addon-qtype-shortanswer.html index 6f31a2d39..b73d8d7be 100644 --- a/src/addon/qtype/shortanswer/component/addon-qtype-shortanswer.html +++ b/src/addon/qtype/shortanswer/component/addon-qtype-shortanswer.html @@ -1,8 +1,8 @@
- +

- + {{ 'addon.mod_quiz.answercolon' | translate }} diff --git a/src/addon/qtype/shortanswer/component/shortanswer.scss b/src/addon/qtype/shortanswer/component/shortanswer.scss new file mode 100644 index 000000000..a49c2e2c3 --- /dev/null +++ b/src/addon/qtype/shortanswer/component/shortanswer.scss @@ -0,0 +1,11 @@ +addon-qtype-shortanswer { + .addon-qtype-shortanswer-text input { + display: inline-block; + border: 1px solid #ccc; + padding: 4px 6px; + -webkit-border-radius: 4px; + -moz-border-radius: 4px; + border-radius: 4px; + margin-bottom: 10px; + } +} diff --git a/src/core/question/classes/base-question-component.ts b/src/core/question/classes/base-question-component.ts index 2e78a2dfe..1a1dfafa0 100644 --- a/src/core/question/classes/base-question-component.ts +++ b/src/core/question/classes/base-question-component.ts @@ -289,7 +289,8 @@ export class CoreQuestionBaseComponent { id: input.id, name: input.name, value: input.value, - readOnly: input.readOnly + readOnly: input.readOnly, + isInline: !!this.domUtils.closest(input, '.qtext') // The answer can be inside the question text. }; // Check if question is marked as correct. @@ -310,6 +311,16 @@ export class CoreQuestionBaseComponent { this.question.input.correctIcon = ''; this.question.input.correctIconColor = ''; } + + if (this.question.input.isInline) { + // Handle correct/incorrect classes and icons. + const content = questionEl.querySelector('.qtext'); + + this.questionHelper.replaceCorrectnessClasses(content); + this.questionHelper.treatCorrectnessIcons(content); + + this.question.text = content.innerHTML; + } } return questionEl; From 2e14b9bc8a1b9028b7137ea1d01b524917368a7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 31 Jan 2019 12:27:50 +0100 Subject: [PATCH 038/191] MOBILE-2847 quiz: Review shortanswer question styles --- src/addon/mod/quiz/pages/review/review.scss | 2 +- .../component/addon-qtype-shortanswer.html | 2 +- .../shortanswer/component/shortanswer.scss | 23 ++++++++++++------- 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/addon/mod/quiz/pages/review/review.scss b/src/addon/mod/quiz/pages/review/review.scss index 47b755a75..ca5b7d400 100644 --- a/src/addon/mod/quiz/pages/review/review.scss +++ b/src/addon/mod/quiz/pages/review/review.scss @@ -6,7 +6,7 @@ ion-app.app-root page-addon-mod-quiz-review { .text-input[disabled] { opacity: 0.8; - .label, .radio, .checkbox, .select-disabled, .core-correct-icon { + .label, .radio, .checkbox, .select-disabled, .core-correct-icon, .text-input[disabled] { opacity: 1; } } diff --git a/src/addon/qtype/shortanswer/component/addon-qtype-shortanswer.html b/src/addon/qtype/shortanswer/component/addon-qtype-shortanswer.html index b73d8d7be..42febebf0 100644 --- a/src/addon/qtype/shortanswer/component/addon-qtype-shortanswer.html +++ b/src/addon/qtype/shortanswer/component/addon-qtype-shortanswer.html @@ -1,6 +1,6 @@
-

+
{{ 'addon.mod_quiz.answercolon' | translate }} diff --git a/src/addon/qtype/shortanswer/component/shortanswer.scss b/src/addon/qtype/shortanswer/component/shortanswer.scss index a49c2e2c3..02c8bd099 100644 --- a/src/addon/qtype/shortanswer/component/shortanswer.scss +++ b/src/addon/qtype/shortanswer/component/shortanswer.scss @@ -1,11 +1,18 @@ addon-qtype-shortanswer { - .addon-qtype-shortanswer-text input { - display: inline-block; - border: 1px solid #ccc; - padding: 4px 6px; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; - margin-bottom: 10px; + .addon-qtype-shortanswer-text { + ion-label.label { + margin-bottom: 0; + } + + input { + @include placeholder($text-input-placeholder-color); + @include appearance(none); + @include border-radius(4px); + display: inline-block; + border: 1px solid $gray-dark; + padding: 6px 8px; + @include margin-horizontal(2px); + margin-bottom: 10px; + } } } From 5c2586b54fd8fb60ba0e7da9f37e0e7be881cabd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 31 Jan 2019 13:23:21 +0100 Subject: [PATCH 039/191] MOBILE-2847 quiz: Review multianswer question styles --- src/addon/mod/quiz/pages/review/review.scss | 5 +++-- .../qtype/gapselect/component/gapselect.scss | 6 ------ .../multianswer/component/multianswer.scss | 10 +++++----- src/app/app.scss | 18 ++++++++++++++++-- 4 files changed, 24 insertions(+), 15 deletions(-) diff --git a/src/addon/mod/quiz/pages/review/review.scss b/src/addon/mod/quiz/pages/review/review.scss index ca5b7d400..bb7509562 100644 --- a/src/addon/mod/quiz/pages/review/review.scss +++ b/src/addon/mod/quiz/pages/review/review.scss @@ -3,10 +3,11 @@ ion-app.app-root page-addon-mod-quiz-review { .item-checkbox-disabled, .item-select-disabled, .item-input-disabled, - .text-input[disabled] { + [disabled], + [readonly] { opacity: 0.8; - .label, .radio, .checkbox, .select-disabled, .core-correct-icon, .text-input[disabled] { + .label, .radio, .checkbox, .select-disabled, .core-correct-icon, [disabled], [readonly] { opacity: 1; } } diff --git a/src/addon/qtype/gapselect/component/gapselect.scss b/src/addon/qtype/gapselect/component/gapselect.scss index 5311db7c8..53ceeba30 100644 --- a/src/addon/qtype/gapselect/component/gapselect.scss +++ b/src/addon/qtype/gapselect/component/gapselect.scss @@ -15,11 +15,5 @@ ion-app.app-root addon-qtype-gapselect { border-radius: 4px; margin-bottom: 10px; background: $gray-lighter; - - &.core-question-answer-correct, - &.core-question-answer-incorrect { - background-color: $gray-lighter; - color: $text-color; - } } } diff --git a/src/addon/qtype/multianswer/component/multianswer.scss b/src/addon/qtype/multianswer/component/multianswer.scss index 5d116515e..f63e3e5ca 100644 --- a/src/addon/qtype/multianswer/component/multianswer.scss +++ b/src/addon/qtype/multianswer/component/multianswer.scss @@ -22,12 +22,12 @@ addon-qtype-multianswer { } input, select { + @include border-radius(4px); + display: inline-block; - border: 1px solid #ccc; - padding: 4px 6px; - -webkit-border-radius: 4px; - -moz-border-radius: 4px; - border-radius: 4px; + border: 1px solid $gray-dark; + padding: 6px 8px; + @include margin-horizontal(2px); margin-bottom: 10px; } diff --git a/src/app/app.scss b/src/app/app.scss index 902622a0c..04e9a7c77 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -477,7 +477,22 @@ ion-app.app-root { margin-bottom: 0; } - .core-question-answer-correct, + + .core-question-answer-correct { + color: $core-question-correct-color; + } + + .core-question-answer-incorrect { + color: $core-question-incorrect-color; + } + + input, select { + &.core-question-answer-correct, &.core-question-answer-incorrect { + background-color: $gray-lighter; + color: $text-color; + } + } + .core-question-correct, .core-question-comment { color: $core-question-correct-color; @@ -494,7 +509,6 @@ ion-app.app-root { } } - .core-question-answer-incorrect, .core-question-incorrect { color: $core-question-incorrect-color; background-color: $core-question-incorrect-color-bg; From 442c184ac03aa803df2b58ec380af7dd90d160d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 1 Feb 2019 11:23:31 +0100 Subject: [PATCH 040/191] MOBILE-2858 grades: Improve course grades preformat --- src/core/grades/providers/helper.ts | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/src/core/grades/providers/helper.ts b/src/core/grades/providers/helper.ts index 764d6ee7f..958c187e6 100644 --- a/src/core/grades/providers/helper.ts +++ b/src/core/grades/providers/helper.ts @@ -85,6 +85,10 @@ export class CoreGradesHelperProvider { let content = String(tableRow[name].content); if (name == 'itemname') { + row['id'] = parseInt(tableRow[name].id.split('_')[1], 10); + row['colspan'] = tableRow[name].colspan; + row['rowspan'] = (tableRow['leader'] && tableRow['leader'].rowspan) || 1; + this.setRowIcon(row, content); row['rowclass'] = tableRow[name].class.indexOf('leveleven') < 0 ? 'odd' : 'even'; row['rowclass'] += tableRow[name].class.indexOf('hidden') >= 0 ? ' hidden' : ''; @@ -92,10 +96,6 @@ export class CoreGradesHelperProvider { content = content.replace(/<\/span>/gi, '\n'); content = this.textUtils.cleanTags(content); - - row['id'] = parseInt(tableRow[name].id.split('_')[1], 10); - row['colspan'] = tableRow[name].colspan; - row['rowspan'] = (tableRow['leader'] && tableRow['leader'].rowspan) || 1; name = 'gradeitem'; } else { content = this.textUtils.replaceNewLines(content, '
'); @@ -439,9 +439,17 @@ export class CoreGradesHelperProvider { row['image'] = this.courseProvider.getModuleIconSrc(module[1], this.domUtils.convertToElement(text).querySelector('img').getAttribute('src')); } - } else if (text.indexOf('src=') > -1) { - const src = text.match(/src="([^"]*)"/); - row['image'] = src[1]; + } else { + if (row['rowspan']) { + row['itemtype'] = 'category'; + row['icon'] = 'fa-folder'; + } else if (text.indexOf('src=') > -1) { + const src = text.match(/src="([^"]*)"/); + row['image'] = src[1]; + } else if (text.indexOf(' -1) { + const src = text.match(/class="fa-([^ ]*)"/); + row['icon'] = src[1]; + } } return row; From e891fcb1a11ddf2a6ff76346dd425ee7c960b5e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 1 Feb 2019 11:51:36 +0100 Subject: [PATCH 041/191] MOBILE-2858 courses: Do not check course options on empty list --- src/core/courses/providers/courses.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/core/courses/providers/courses.ts b/src/core/courses/providers/courses.ts index 548069d07..d4e492aa8 100644 --- a/src/core/courses/providers/courses.ts +++ b/src/core/courses/providers/courses.ts @@ -555,6 +555,10 @@ export class CoreCoursesProvider { * @return {Promise} Promise resolved with administration options for each course. */ getUserAdministrationOptions(courseIds: number[], siteId?: string): Promise { + if (!courseIds || courseIds.length == 0) { + return Promise.resolve({}); + } + return this.sitesProvider.getSite(siteId).then((site) => { const params = { courseids: courseIds @@ -597,6 +601,10 @@ export class CoreCoursesProvider { * @return {Promise} Promise resolved with navigation options for each course. */ getUserNavigationOptions(courseIds: number[], siteId?: string): Promise { + if (!courseIds || courseIds.length == 0) { + return Promise.resolve({}); + } + return this.sitesProvider.getSite(siteId).then((site) => { const params = { courseids: courseIds From 2bf1575a54ae9d1120a048755440be7072dfc1fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 1 Feb 2019 16:03:46 +0100 Subject: [PATCH 042/191] MOBILE-2797 other: Add gitattributes file for eol settings --- .gitattributes | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..1003e5853 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,8 @@ +# This file has been retrieved from angular repository. + +# Auto detect text files and perform LF normalization +* text=auto + +# JS and TS files must always use LF for tools to work +*.js eol=lf +*.ts eol=lf From bd1fb5bd54374c22e15d44bb063b88f4847a34dc Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Mon, 4 Feb 2019 10:49:41 +0100 Subject: [PATCH 043/191] MOBILE-2825 core: Don't delete local files after upload --- src/core/fileuploader/providers/fileuploader.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/core/fileuploader/providers/fileuploader.ts b/src/core/fileuploader/providers/fileuploader.ts index 166982d34..753b4bf21 100644 --- a/src/core/fileuploader/providers/fileuploader.ts +++ b/src/core/fileuploader/providers/fileuploader.ts @@ -486,7 +486,10 @@ export class CoreFileUploaderProvider { } /** - * Upload a file to a draft area. If the file is an online file it will be downloaded and then re-uploaded. + * Upload a file to a draft area and return the draft ID. + * + * If the file is an online file it will be downloaded and then re-uploaded. + * If the file is a local file it will not be deleted from the device after upload. * * @param {any} file Online file or local FileEntry. * @param {number} [itemId] Draft ID to use. Undefined or 0 to create a new draft ID. @@ -502,7 +505,9 @@ export class CoreFileUploaderProvider { let promise, fileName; - if (file.filename && !file.name) { + const isOnline = file.filename && !file.name; + + if (isOnline) { // It's an online file. We need to download it and re-upload it. fileName = file.filename; promise = this.filepoolProvider.downloadUrl(siteId, file.url || file.fileurl, false, component, componentId, @@ -517,7 +522,7 @@ export class CoreFileUploaderProvider { return promise.then((fileEntry) => { // Now upload the file. - const options = this.getFileUploadOptions(fileEntry.toURL(), fileName, fileEntry.type, true, 'draft', itemId); + const options = this.getFileUploadOptions(fileEntry.toURL(), fileName, fileEntry.type, isOnline, 'draft', itemId); return this.uploadFile(fileEntry.toURL(), options, undefined, siteId).then((result) => { return result.itemid; @@ -527,7 +532,9 @@ export class CoreFileUploaderProvider { /** * Given a list of files (either online files or local files), upload them to a draft area and return the draft ID. + * * Online files will be downloaded and then re-uploaded. + * Local files are not deleted from the device after upload. * If there are no files to upload it will return a fake draft ID (1). * * @param {any[]} files List of files. From 6d55c50b013a0049d09983a121e579d7dafe075b Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Mon, 4 Feb 2019 10:51:42 +0100 Subject: [PATCH 044/191] MOBILE-2825 data: Delete offline files after sync --- src/addon/mod/data/providers/offline.ts | 49 ++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/src/addon/mod/data/providers/offline.ts b/src/addon/mod/data/providers/offline.ts index 76ca2556d..df54530ff 100644 --- a/src/addon/mod/data/providers/offline.ts +++ b/src/addon/mod/data/providers/offline.ts @@ -17,6 +17,7 @@ import { CoreLoggerProvider } from '@providers/logger'; import { CoreSitesProvider } from '@providers/sites'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreFileProvider } from '@providers/file'; +import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader'; /** * Service to handle Offline data. @@ -66,7 +67,7 @@ export class AddonModDataOfflineProvider { ]; constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private textUtils: CoreTextUtilsProvider, - private fileProvider: CoreFileProvider) { + private fileProvider: CoreFileProvider, private fileUploaderProvider: CoreFileUploaderProvider) { this.logger = logger.getInstance('AddonModDataOfflineProvider'); this.sitesProvider.createTablesFromSchema(this.tablesSchema); } @@ -102,8 +103,52 @@ export class AddonModDataOfflineProvider { */ deleteEntry(dataId: number, entryId: number, action: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().deleteRecords(AddonModDataOfflineProvider.DATA_ENTRY_TABLE, {dataid: dataId, entryid: entryId, + return this.deleteEntryFiles(dataId, entryId, action, site.id).then(() => { + return site.getDb().deleteRecords(AddonModDataOfflineProvider.DATA_ENTRY_TABLE, {dataid: dataId, entryid: entryId, action: action}); + }); + }); + } + + /** + * Delete entry offline files. + * + * @param {number} dataId Database ID. + * @param {number} entryId Database entry ID. + * @param {string} action Action to be done. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved if deleted, rejected if failure. + */ + protected deleteEntryFiles(dataId: number, entryId: number, action: string, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + return this.getEntry(dataId, entryId, action, site.id).then((entry) => { + if (!entry.fields) { + return; + } + + const promises = []; + + entry.fields.forEach((field) => { + const value = this.textUtils.parseJSON(field.value); + if (!value.offline) { + return; + } + + const promise = this.getEntryFieldFolder(dataId, entryId, field.fieldid, site.id).then((folderPath) => { + return this.fileUploaderProvider.getStoredFiles(folderPath); + }).then((files) => { + return this.fileUploaderProvider.clearTmpFiles(files); + }).catch(() => { + // Files not found, ignore. + }); + + promises.push(promise); + }); + + return Promise.all(promises); + }).catch(() => { + // Entry not found, ignore. + }); }); } From 7a2f5525146028c63669bb3c847b6ad1fb1cafa2 Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Mon, 4 Feb 2019 10:53:42 +0100 Subject: [PATCH 045/191] MOBILE-2825 assign: Delete temporary files after saving submission --- src/addon/mod/assign/pages/edit/edit.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/addon/mod/assign/pages/edit/edit.ts b/src/addon/mod/assign/pages/edit/edit.ts index e98d6abf0..3f2ba6c32 100644 --- a/src/addon/mod/assign/pages/edit/edit.ts +++ b/src/addon/mod/assign/pages/edit/edit.ts @@ -303,6 +303,9 @@ export class AddonModAssignEditPage implements OnInit, OnDestroy { } 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, From 0c428eb257f704ad9b15dac6aa972e6f25bda762 Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Mon, 4 Feb 2019 10:55:58 +0100 Subject: [PATCH 046/191] MOBILE-2825 glossary: Delete temporary files after saving entry --- src/addon/mod/glossary/pages/edit/edit.ts | 3 +++ src/addon/mod/glossary/providers/helper.ts | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/addon/mod/glossary/pages/edit/edit.ts b/src/addon/mod/glossary/pages/edit/edit.ts index f685feefd..8520727b6 100644 --- a/src/addon/mod/glossary/pages/edit/edit.ts +++ b/src/addon/mod/glossary/pages/edit/edit.ts @@ -232,6 +232,9 @@ export class AddonModGlossaryEditPage implements OnInit { attach, timecreated, undefined, this.entry, !this.attachments.length, !this.glossary.allowduplicatedentries); } }).then((entryId) => { + // Delete the local files from the tmp folder. + this.uploaderProvider.clearTmpFiles(this.attachments); + if (entryId) { // Data sent to server, delete stored files (if any). this.glossaryHelper.deleteStoredFiles(this.glossary.id, this.entry.concept, timecreated); diff --git a/src/addon/mod/glossary/providers/helper.ts b/src/addon/mod/glossary/providers/helper.ts index d4cf592b1..4ec569c47 100644 --- a/src/addon/mod/glossary/providers/helper.ts +++ b/src/addon/mod/glossary/providers/helper.ts @@ -29,7 +29,7 @@ export class AddonModGlossaryHelperProvider { private glossaryOffline: AddonModGlossaryOfflineProvider) {} /** - * Delete stored attachment files for a new discussion. + * Delete stored attachment files for a new entry. * * @param {number} glossaryId Glossary ID. * @param {string} entryName The name of the entry. From 5e098c981bb05d4a253ba08e49af48b9ea7ebf8a Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Mon, 4 Feb 2019 10:57:54 +0100 Subject: [PATCH 047/191] MOBILE-2825 workshop: Delete offline files after sync --- src/addon/mod/workshop/providers/sync.ts | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/addon/mod/workshop/providers/sync.ts b/src/addon/mod/workshop/providers/sync.ts index f6693960f..9b1fc1c02 100644 --- a/src/addon/mod/workshop/providers/sync.ts +++ b/src/addon/mod/workshop/providers/sync.ts @@ -353,7 +353,15 @@ export class AddonModWorkshopSyncProvider extends CoreSyncBaseProvider { result.updated = true; return this.workshopOffline.deleteSubmissionAction(action.workshopid, action.submissionid, action.action, - siteId); + siteId).then(() => { + // Delete stored files. + if (action.action == 'add' || action.action == 'update') { + const editing = action.action == 'update'; + + return this.workshopHelper.deleteSubmissionStoredFiles(action.workshopid, + action.submissionid, editing, siteId); + } + }); }); }); }); @@ -433,7 +441,9 @@ export class AddonModWorkshopSyncProvider extends CoreSyncBaseProvider { // Delete the offline data. result.updated = true; - return this.workshopOffline.deleteAssessment(workshop.id, assessmentId, siteId); + return this.workshopOffline.deleteAssessment(workshop.id, assessmentId, siteId).then(() => { + this.workshopHelper.deleteAssessmentStoredFiles(workshop.id, assessmentId, siteId); + }); }); }).then(() => { if (discardError) { From d3d796e54b8ce3d53499abc301edaaf252d4c182 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Mon, 4 Feb 2019 16:33:44 +0100 Subject: [PATCH 048/191] MOBILE-2781 assign: Support grader hidden --- .../components/submission/addon-mod-assign-submission.html | 6 ++++++ src/addon/mod/assign/components/submission/submission.ts | 4 +++- src/addon/mod/assign/providers/prefetch-handler.ts | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/addon/mod/assign/components/submission/addon-mod-assign-submission.html b/src/addon/mod/assign/components/submission/addon-mod-assign-submission.html index ea03450af..84e54a560 100644 --- a/src/addon/mod/assign/components/submission/addon-mod-assign-submission.html +++ b/src/addon/mod/assign/components/submission/addon-mod-assign-submission.html @@ -215,6 +215,12 @@

{{ feedback.gradeddate * 1000 | coreFormatDate }}

+ + +

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

+

{{ feedback.gradeddate * 1000 | coreFormatDate }}

+
+
diff --git a/src/addon/mod/assign/components/submission/submission.ts b/src/addon/mod/assign/components/submission/submission.ts index cffefe88d..5b65ab853 100644 --- a/src/addon/mod/assign/components/submission/submission.ts +++ b/src/addon/mod/assign/components/submission/submission.ts @@ -485,12 +485,14 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy { this.feedback = feedback; // If we have data about the grader, get its profile. - if (feedback.grade && feedback.grade.grader) { + if (feedback.grade && feedback.grade.grader > 0) { this.userProvider.getProfile(feedback.grade.grader, this.courseId).then((profile) => { this.grader = profile; }).catch(() => { // Ignore errors. }); + } else { + delete this.grader; } // Check if the grade uses advanced grading. diff --git a/src/addon/mod/assign/providers/prefetch-handler.ts b/src/addon/mod/assign/providers/prefetch-handler.ts index 93ecb3f68..7505ee97b 100644 --- a/src/addon/mod/assign/providers/prefetch-handler.ts +++ b/src/addon/mod/assign/providers/prefetch-handler.ts @@ -392,7 +392,7 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan // Prefetch feedback. if (submission.feedback) { // Get profile and image of the grader. - if (submission.feedback.grade && submission.feedback.grade.grader && submission.feedback.grade.grader != -1) { + if (submission.feedback.grade && submission.feedback.grade.grader > 0) { userIds.push(submission.feedback.grade.grader); } From ed914d6a19b6c416dabdfe37e341517586aa601a Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Tue, 5 Feb 2019 13:01:38 +0100 Subject: [PATCH 049/191] MOBILE-2188 data: Don't fetch all entries when browsing entry by entry --- .../mod/data/components/action/action.ts | 4 + src/addon/mod/data/components/index/index.ts | 24 ++++-- src/addon/mod/data/pages/entry/entry.html | 10 +-- src/addon/mod/data/pages/entry/entry.ts | 83 +++++++++++------- src/addon/mod/data/providers/helper.ts | 84 +------------------ .../mod/data/providers/show-link-handler.ts | 2 +- 6 files changed, 83 insertions(+), 124 deletions(-) diff --git a/src/addon/mod/data/components/action/action.ts b/src/addon/mod/data/components/action/action.ts index dab8a35d0..32be60ec3 100644 --- a/src/addon/mod/data/components/action/action.ts +++ b/src/addon/mod/data/components/action/action.ts @@ -30,6 +30,7 @@ export class AddonModDataActionComponent implements OnInit { @Input() action: string; // The field to render. @Input() entry?: any; // The value of the field. @Input() database: any; // Database object. + @Input() offset?: number; // Offset of the entry. siteId: string; rootUrl: string; @@ -67,6 +68,9 @@ export class AddonModDataActionComponent implements OnInit { switch (this.action) { case 'more': this.url = this.rootUrl + '/mod/data/view.php?d= ' + this.entry.dataid + '&rid=' + this.entry.id; + if (typeof this.offset == 'number') { + this.url += '&mode=single&page=' + this.offset; + } break; case 'edit': this.url = this.rootUrl + '/mod/data/edit.php?d= ' + this.entry.dataid + '&rid=' + this.entry.id; diff --git a/src/addon/mod/data/components/index/index.ts b/src/addon/mod/data/components/index/index.ts index 9e32c7afb..9da9180fe 100644 --- a/src/addon/mod/data/components/index/index.ts +++ b/src/addon/mod/data/components/index/index.ts @@ -47,7 +47,7 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp timeAvailableToReadable: string | boolean; isEmpty = false; groupInfo: CoreGroupInfo; - entries = {}; + entries = []; firstEntry = false; canAdd = false; canSearch = false; @@ -317,6 +317,8 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp }); return Promise.all(promises).then((entries) => { + this.entries = entries; + let entriesHTML = this.data.listtemplateheader || ''; // Get first entry from the whole list. @@ -326,12 +328,15 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp const template = this.data.listtemplate || this.dataHelper.getDefaultTemplate('list', this.fieldsArray); - entries.forEach((entry) => { - this.entries[entry.id] = entry; + const entriesById = {}; + entries.forEach((entry, index) => { + entriesById[entry.id] = entry; const actions = this.dataHelper.getActions(this.data, this.access, entry); + const offset = this.search.page * AddonModDataProvider.PER_PAGE + index; - entriesHTML += this.dataHelper.displayShowFields(template, this.fieldsArray, entry, 'list', actions); + entriesHTML += this.dataHelper.displayShowFields(template, this.fieldsArray, entry, offset, 'list', + actions); }); entriesHTML += this.data.listtemplatefooter || ''; @@ -340,7 +345,7 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp // Pass the input data to the component. this.jsData = { fields: this.fields, - entries: this.entries, + entries: entriesById, data: this.data, gotoEntry: this.gotoEntry.bind(this) }; @@ -440,9 +445,16 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp module: this.module, courseId: this.courseId, entryId: entryId, - group: this.selectedGroup + group: this.selectedGroup, + offset: null }; + // Try to find page number and offset of the entry. + const pageXOffset = this.entries.findIndex((entry) => entry.id == entryId); + if (pageXOffset >= 0) { + params.offset = this.search.page * AddonModDataProvider.PER_PAGE + pageXOffset; + } + this.navCtrl.push('AddonModDataEntryPage', params); } diff --git a/src/addon/mod/data/pages/entry/entry.html b/src/addon/mod/data/pages/entry/entry.html index c1d727795..a49acdf57 100644 --- a/src/addon/mod/data/pages/entry/entry.html +++ b/src/addon/mod/data/pages/entry/entry.html @@ -34,16 +34,16 @@ - + - - - - diff --git a/src/addon/mod/data/pages/entry/entry.ts b/src/addon/mod/data/pages/entry/entry.ts index 8ea967f72..16a35f39e 100644 --- a/src/addon/mod/data/pages/entry/entry.ts +++ b/src/addon/mod/data/pages/entry/entry.ts @@ -41,7 +41,7 @@ export class AddonModDataEntryPage implements OnDestroy { protected module: any; protected entryId: number; protected courseId: number; - protected page: number; + protected offset: number; protected syncObserver: any; // It will observe the sync auto event. protected entryChangedObserver: any; // It will observe the changed entry event. protected fields = {}; @@ -55,8 +55,8 @@ export class AddonModDataEntryPage implements OnDestroy { offlineActions = []; hasOffline = false; cssTemplate = ''; - previousId: number; - nextId: number; + previousOffset: number; + nextOffset: number; access: any; data: any; groupInfo: any; @@ -77,7 +77,7 @@ export class AddonModDataEntryPage implements OnDestroy { this.entryId = params.get('entryId') || null; this.courseId = params.get('courseId'); this.selectedGroup = params.get('group') || 0; - this.page = params.get('page') || null; + this.offset = params.get('offset'); this.siteId = sitesProvider.getCurrentSiteId(); @@ -133,7 +133,7 @@ export class AddonModDataEntryPage implements OnDestroy { this.data = data; this.cssClass = 'addon-data-entries-' + data.id; - return this.setEntryIdFromPage(data.id, this.page, this.selectedGroup).then(() => { + return this.setEntryIdFromOffset(data.id, this.offset, this.selectedGroup).then(() => { return this.dataProvider.getDatabaseAccessInformation(data.id); }); }).then((accessData) => { @@ -177,7 +177,7 @@ export class AddonModDataEntryPage implements OnDestroy { const actions = this.dataHelper.getActions(this.data, this.access, this.entry); const templte = this.data.singletemplate || this.dataHelper.getDefaultTemplate('single', fieldsArray); - this.entryRendered = this.dataHelper.displayShowFields(templte, fieldsArray, this.entry, 'show', actions); + this.entryRendered = this.dataHelper.displayShowFields(templte, fieldsArray, this.entry, this.offset, 'show', actions); this.showComments = actions.comments; const entries = {}; @@ -189,11 +189,6 @@ export class AddonModDataEntryPage implements OnDestroy { entries: entries, data: this.data }; - - return this.dataHelper.getPageInfoByEntry(this.data.id, this.entryId, this.selectedGroup).then((result) => { - this.previousId = result.previousId; - this.nextId = result.nextId; - }); }).catch((message) => { if (!refresh) { // Some call failed, retry without using cache since it might be a new activity. @@ -210,13 +205,13 @@ export class AddonModDataEntryPage implements OnDestroy { /** * Go to selected entry without changing state. * - * @param {number} entry Entry Id where to go. - * @return {Promise} Resolved when done. + * @param {number} offset Entry offset. + * @return {Promise} Resolved when done. */ - gotoEntry(entry: number): Promise { - this.entryId = entry; + gotoEntry(offset: number): Promise { + this.offset = offset; + this.entryId = null; this.entry = null; - this.page = null; this.entryLoaded = false; return this.fetchEntryData(); @@ -264,30 +259,56 @@ export class AddonModDataEntryPage implements OnDestroy { */ setGroup(groupId: number): Promise { this.selectedGroup = groupId; + this.offset = 0; + this.entry = null; + this.entryId = null; this.entryLoaded = false; - return this.setEntryIdFromPage(this.data.id, 0, this.selectedGroup).then(() => { - return this.fetchEntryData(); - }); + return this.fetchEntryData(); } /** - * Convenience function to translate page number to entry identifier. + * Convenience function to translate offset to entry identifier and set next/previous entries. * - * @param {number} dataId Data Id. - * @param {number} [pageNumber] Page number where to go - * @param {number} group Group Id to get the entry. - * @return {Promise} Resolved when done. + * @param {number} dataId Data Id. + * @param {number} [offset] Offset of the entry. + * @param {number} [groupId] Group Id to get the entry. + * @return {Promise} Resolved when done. */ - protected setEntryIdFromPage(dataId: number, pageNumber?: number, group?: number): Promise { - if (typeof pageNumber == 'number') { - return this.dataHelper.getPageInfoByPage(dataId, pageNumber, group).then((result) => { - this.entryId = result.entryId; - this.page = null; - }); + protected setEntryIdFromOffset(dataId: number, offset?: number, groupId?: number): Promise { + if (typeof offset != 'number') { + // Entry id passed as navigation parameter instead of the offset. + // We don't display next/previous buttons in this case. + this.nextOffset = null; + this.previousOffset = null; + + return Promise.resolve(); } - return Promise.resolve(); + const perPage = AddonModDataProvider.PER_PAGE; + const page = Math.floor(offset / perPage); + const pageOffset = offset % perPage; + + return this.dataProvider.getEntries(dataId, groupId, undefined, undefined, page, perPage).then((entries) => { + if (!entries || !entries.entries || !entries.entries.length || pageOffset >= entries.entries.length) { + return Promise.reject(null); + } + + this.entryId = entries.entries[pageOffset].id; + this.previousOffset = offset > 0 ? offset - 1 : null; + if (pageOffset + 1 < entries.entries.length) { + // Not the last entry on the page; + this.nextOffset = offset + 1; + } else if (entries.entries.length < perPage) { + // Last entry of the last page. + this.nextOffset = null; + } else { + // Last entry of the page, check if there are more pages. + return this.dataProvider.getEntries(dataId, groupId, undefined, undefined, page + 1, perPage).then((entries) => { + this.nextOffset = entries && entries.entries && entries.entries.length > 0 ? offset + 1 : null; + }); + } + }); } /** diff --git a/src/addon/mod/data/providers/helper.ts b/src/addon/mod/data/providers/helper.ts index 97f6ac5e2..a94879196 100644 --- a/src/addon/mod/data/providers/helper.ts +++ b/src/addon/mod/data/providers/helper.ts @@ -100,11 +100,12 @@ export class AddonModDataHelperProvider { * @param {string} template Template HMTL. * @param {any[]} fields Fields that defines every content in the entry. * @param {any} entry Entry. + * @param {number} offset Entry offset. * @param {string} mode Mode list or show. * @param {any} actions Actions that can be performed to the record. * @return {string} Generated HTML. */ - displayShowFields(template: string, fields: any[], entry: any, mode: string, actions: any): string { + displayShowFields(template: string, fields: any[], entry: any, offset: number, mode: string, actions: any): string { if (!template) { return ''; } @@ -135,7 +136,7 @@ export class AddonModDataHelperProvider { render = this.translate.instant('addon.mod_data.' + (entry.approved ? 'approved' : 'notapproved')); } else { render = ''; + ']" mode="' + mode + '" [database]="data" [offset]="' + offset + '">'; } template = template.replace(replace, render); } else { @@ -177,24 +178,6 @@ export class AddonModDataHelperProvider { }; } - /** - * Fetch all entries and return it's Id - * - * @param {number} dataId Data ID. - * @param {number} groupId Group ID. - * @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false. - * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. Current if not defined. - * @return {Promise} Resolved with an array of entry ID. - */ - getAllEntriesIds(dataId: number, groupId: number, forceCache: boolean = false, ignoreCache: boolean = false, siteId?: string): - Promise { - return this.dataProvider.fetchAllEntries(dataId, groupId, undefined, undefined, undefined, forceCache, ignoreCache, siteId) - .then((entries) => { - return entries.map((entry) => entry.id); - }); - } - /** * Returns the default template of a certain type. * @@ -398,67 +381,6 @@ export class AddonModDataHelperProvider { }); } - /** - * Get page info related to an entry. - * - * @param {number} dataId Data ID. - * @param {number} entryId Entry ID. - * @param {number} groupId Group ID. - * @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false. - * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. Current if not defined. - * @return {Promise} Containing page number, if has next and have following page. - */ - getPageInfoByEntry(dataId: number, entryId: number, groupId: number, forceCache: boolean = false, - ignoreCache: boolean = false, siteId?: string): Promise { - return this.getAllEntriesIds(dataId, groupId, forceCache, ignoreCache, siteId).then((entries) => { - const index = entries.findIndex((entry) => entry == entryId); - - if (index >= 0) { - return { - previousId: entries[index - 1] || false, - nextId: entries[index + 1] || false, - entryId: entryId, - page: index + 1, // Parsed to natural language. - numEntries: entries.length - }; - } - - return false; - }); - } - - /** - * Get page info related to an entry by page number. - * - * @param {number} dataId Data ID. - * @param {number} page Page number. - * @param {number} groupId Group ID. - * @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false. - * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). - * @param {string} [siteId] Site ID. Current if not defined. - * @return {Promise} Containing page number, if has next and have following page. - */ - getPageInfoByPage(dataId: number, page: number, groupId: number, forceCache: boolean = false, - ignoreCache: boolean = false, siteId?: string): Promise { - return this.getAllEntriesIds(dataId, groupId, forceCache, ignoreCache, siteId).then((entries) => { - const index = page - 1, - entryId = entries[index]; - - if (entryId) { - return { - previousId: entries[index - 1] || null, - nextId: entries[index + 1] || null, - entryId: entryId, - page: page, // Parsed to natural language. - numEntries: entries.length - }; - } - - return false; - }); - } - /** * Get a list of stored attachment files for a new entry. See $mmaModDataHelper#storeFiles. * diff --git a/src/addon/mod/data/providers/show-link-handler.ts b/src/addon/mod/data/providers/show-link-handler.ts index 1d30f33ea..3685df23e 100644 --- a/src/addon/mod/data/providers/show-link-handler.ts +++ b/src/addon/mod/data/providers/show-link-handler.ts @@ -65,7 +65,7 @@ export class AddonModDataShowLinkHandler extends CoreContentLinksHandlerBase { } if (params.mode && params.mode == 'single') { - pageParams['page'] = page || 1; + pageParams['offset'] = page || 0; } else if (rId) { pageParams['entryId'] = rId; } From 73f449245ed3c045f9caf54aedcbee8cf72c7dd7 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 5 Feb 2019 13:13:07 +0100 Subject: [PATCH 050/191] MOBILE-2818 siteplugins: Allow specifying the component supported --- src/core/siteplugins/providers/helper.ts | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/src/core/siteplugins/providers/helper.ts b/src/core/siteplugins/providers/helper.ts index c041de987..cc78d9509 100644 --- a/src/core/siteplugins/providers/helper.ts +++ b/src/core/siteplugins/providers/helper.ts @@ -564,7 +564,7 @@ export class CoreSitePluginsHelperProvider { delegate.registerHandler(handler); - return plugin.component; + return handlerSchema.moodlecomponent || plugin.component; }).catch((err) => { this.logger.error('Error executing main method', plugin.component, handlerSchema.method, err); }); @@ -583,7 +583,7 @@ export class CoreSitePluginsHelperProvider { return this.registerComponentInitHandler(plugin, handlerName, handlerSchema, this.assignFeedbackDelegate, (uniqueName: string, result: any) => { - const type = plugin.component.replace('assignfeedback_', ''), + const type = (handlerSchema.moodlecomponent || plugin.component).replace('assignfeedback_', ''), prefix = this.getPrefixForStrings(plugin.addon); return new CoreSitePluginsAssignFeedbackHandler(this.translate, uniqueName, type, prefix); @@ -603,7 +603,7 @@ export class CoreSitePluginsHelperProvider { return this.registerComponentInitHandler(plugin, handlerName, handlerSchema, this.assignSubmissionDelegate, (uniqueName: string, result: any) => { - const type = plugin.component.replace('assignsubmission_', ''), + const type = (handlerSchema.moodlecomponent || plugin.component).replace('assignsubmission_', ''), prefix = this.getPrefixForStrings(plugin.addon); return new CoreSitePluginsAssignSubmissionHandler(this.translate, uniqueName, type, prefix); @@ -623,7 +623,7 @@ export class CoreSitePluginsHelperProvider { // Create and register the handler. const uniqueName = this.sitePluginsProvider.getHandlerUniqueName(plugin, handlerName), - formatName = plugin.component.replace('format_', ''); + formatName = (handlerSchema.moodlecomponent || plugin.component).replace('format_', ''); this.courseFormatDelegate.registerHandler(new CoreSitePluginsCourseFormatHandler(uniqueName, formatName, handlerSchema)); return formatName; @@ -709,7 +709,7 @@ export class CoreSitePluginsHelperProvider { // Create and register the handler. const uniqueName = this.sitePluginsProvider.getHandlerUniqueName(plugin, handlerName), prefixedTitle = this.getPrefixedString(plugin.addon, handlerSchema.displaydata.title), - processorName = plugin.component.replace('message_', ''); + processorName = (handlerSchema.moodlecomponent || plugin.component).replace('message_', ''); this.messageOutputDelegate.registerHandler(new CoreSitePluginsMessageOutputHandler(uniqueName, processorName, prefixedTitle, plugin, handlerSchema, initResult)); @@ -738,7 +738,7 @@ export class CoreSitePluginsHelperProvider { // Create and register the handler. const uniqueName = this.sitePluginsProvider.getHandlerUniqueName(plugin, handlerName), - modName = plugin.component.replace('mod_', ''); + modName = (handlerSchema.moodlecomponent || plugin.component).replace('mod_', ''); this.moduleDelegate.registerHandler(new CoreSitePluginsModuleHandler(uniqueName, modName, handlerSchema, initResult)); @@ -765,7 +765,7 @@ export class CoreSitePluginsHelperProvider { return this.registerComponentInitHandler(plugin, handlerName, handlerSchema, this.questionDelegate, (uniqueName: string, result: any) => { - return new CoreSitePluginsQuestionHandler(uniqueName, plugin.component); + return new CoreSitePluginsQuestionHandler(uniqueName, handlerSchema.moodlecomponent || plugin.component); }); } @@ -782,7 +782,7 @@ export class CoreSitePluginsHelperProvider { return this.registerComponentInitHandler(plugin, handlerName, handlerSchema, this.questionBehaviourDelegate, (uniqueName: string, result: any) => { - const type = plugin.component.replace('qbehaviour_', ''); + const type = (handlerSchema.moodlecomponent || plugin.component).replace('qbehaviour_', ''); return new CoreSitePluginsQuestionBehaviourHandler(this.questionProvider, uniqueName, type, result.templates.length); }); @@ -801,7 +801,8 @@ export class CoreSitePluginsHelperProvider { return this.registerComponentInitHandler(plugin, handlerName, handlerSchema, this.accessRulesDelegate, (uniqueName: string, result: any) => { - return new CoreSitePluginsQuizAccessRuleHandler(uniqueName, plugin.component, result.templates.length); + return new CoreSitePluginsQuizAccessRuleHandler(uniqueName, handlerSchema.moodlecomponent || plugin.component, + result.templates.length); }); } @@ -876,7 +877,7 @@ export class CoreSitePluginsHelperProvider { return this.registerComponentInitHandler(plugin, handlerName, handlerSchema, this.profileFieldDelegate, (uniqueName: string, result: any) => { - const fieldType = plugin.component.replace('profilefield_', ''); + const fieldType = (handlerSchema.moodlecomponent || plugin.component).replace('profilefield_', ''); return new CoreSitePluginsUserProfileFieldHandler(uniqueName, fieldType); }); @@ -896,7 +897,7 @@ export class CoreSitePluginsHelperProvider { return this.registerComponentInitHandler(plugin, handlerName, handlerSchema, this.workshopAssessmentStrategyDelegate, (uniqueName: string, result: any) => { - const strategyName = plugin.component.replace('workshopform_', ''); + const strategyName = (handlerSchema.moodlecomponent || plugin.component).replace('workshopform_', ''); return new CoreSitePluginsWorkshopAssessmentStrategyHandler(uniqueName, strategyName); }); From 281ec5cb4079e227f723bf370ef93a792716d92a Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 6 Feb 2019 10:11:49 +0100 Subject: [PATCH 051/191] MOBILE-2859 config: Unset ionic pro_id --- ionic.config.json | 1 - 1 file changed, 1 deletion(-) diff --git a/ionic.config.json b/ionic.config.json index a866b2bd9..52b63fde9 100644 --- a/ionic.config.json +++ b/ionic.config.json @@ -6,6 +6,5 @@ }, "type": "ionic-angular", "watchPatterns": [], - "pro_id": "com.moodle.moodlemobile", "id": "com.moodle.moodlemobile" } From 226f4eee829ecc7c1c335d6d41e8fdffcb30bb41 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 6 Feb 2019 11:37:29 +0100 Subject: [PATCH 052/191] MOBILE-2799 core: Handle changes in external-content --- src/directives/external-content.ts | 104 ++++++++++++++++++----------- 1 file changed, 64 insertions(+), 40 deletions(-) diff --git a/src/directives/external-content.ts b/src/directives/external-content.ts index 2bc08fabc..019cd4657 100644 --- a/src/directives/external-content.ts +++ b/src/directives/external-content.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Directive, Input, AfterViewInit, ElementRef } from '@angular/core'; +import { Directive, Input, AfterViewInit, ElementRef, OnChanges, SimpleChange } from '@angular/core'; import { Platform } from 'ionic-angular'; import { CoreAppProvider } from '@providers/app'; import { CoreLoggerProvider } from '@providers/logger'; @@ -35,13 +35,18 @@ import { CoreUtilsProvider } from '@providers/utils/utils'; @Directive({ selector: '[core-external-content]' }) -export class CoreExternalContentDirective implements AfterViewInit { +export class CoreExternalContentDirective implements AfterViewInit, OnChanges { @Input() siteId?: string; // Site ID to use. @Input() component?: string; // Component to link the file to. @Input() componentId?: string | number; // Component ID to use in conjunction with the component. + @Input() src?: string; + @Input() href?: string; + @Input('target-src') targetSrc?: string; + @Input() poster?: string; protected element: HTMLElement; protected logger; + protected initialized = false; constructor(element: ElementRef, logger: CoreLoggerProvider, private filepoolProvider: CoreFilepoolProvider, private platform: Platform, private sitesProvider: CoreSitesProvider, private domUtils: CoreDomUtilsProvider, @@ -55,47 +60,21 @@ export class CoreExternalContentDirective implements AfterViewInit { * View has been initialized */ ngAfterViewInit(): void { - const currentSite = this.sitesProvider.getCurrentSite(), - siteId = this.siteId || (currentSite && currentSite.getId()), - tagName = this.element.tagName; - let targetAttr, - sourceAttr; + this.checkAndHandleExternalContent(); - // Always handle inline styles (if any). - this.handleInlineStyles(siteId).catch((error) => { - this.logger.error('Error treating inline styles.', this.element); - }); + this.initialized = true; + } - if (tagName === 'A') { - targetAttr = 'href'; - sourceAttr = 'href'; - - } else if (tagName === 'IMG') { - targetAttr = 'src'; - sourceAttr = 'src'; - - } else if (tagName === 'AUDIO' || tagName === 'VIDEO' || tagName === 'SOURCE' || tagName === 'TRACK') { - targetAttr = 'src'; - sourceAttr = 'target-src'; - - if (tagName === 'VIDEO') { - const poster = ( this.element).poster; - if (poster) { - // Handle poster. - this.handleExternalContent('poster', poster, siteId).catch(() => { - // Ignore errors. - }); - } - } - - } else { - return; + /** + * Listen to changes. + * + * * @param {{[name: string]: SimpleChange}} changes Changes. + */ + ngOnChanges(changes: { [name: string]: SimpleChange }): void { + if (changes && this.initialized) { + // If any of the inputs changes, handle the content again. + this.checkAndHandleExternalContent(); } - - const url = this.element.getAttribute(sourceAttr) || this.element.getAttribute(targetAttr); - this.handleExternalContent(targetAttr, url, siteId).catch(() => { - // Ignore errors. - }); } /** @@ -124,6 +103,51 @@ export class CoreExternalContentDirective implements AfterViewInit { this.element.parentNode.insertBefore(newSource, this.element); } + /** + * Get the URL that should be handled and, if valid, handle it. + */ + protected checkAndHandleExternalContent(): void { + const currentSite = this.sitesProvider.getCurrentSite(), + siteId = this.siteId || (currentSite && currentSite.getId()), + tagName = this.element.tagName; + let targetAttr, + url; + + // Always handle inline styles (if any). + this.handleInlineStyles(siteId).catch((error) => { + this.logger.error('Error treating inline styles.', this.element); + }); + + if (tagName === 'A') { + targetAttr = 'href'; + url = this.href; + + } else if (tagName === 'IMG') { + targetAttr = 'src'; + url = this.src; + + } else if (tagName === 'AUDIO' || tagName === 'VIDEO' || tagName === 'SOURCE' || tagName === 'TRACK') { + targetAttr = 'src'; + url = this.targetSrc || this.src; + + if (tagName === 'VIDEO') { + if (this.poster) { + // Handle poster. + this.handleExternalContent('poster', this.poster, siteId).catch(() => { + // Ignore errors. + }); + } + } + + } else { + return; + } + + this.handleExternalContent(targetAttr, url, siteId).catch(() => { + // Ignore errors. + }); + } + /** * Handle external content, setting the right URL. * From 552b2d5a4a12f8cba2ee42f073219d311eba18c7 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 6 Feb 2019 12:18:37 +0100 Subject: [PATCH 053/191] MOBILE-2799 user: Check user avatar changed when opening profile --- src/core/user/pages/profile/profile.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/core/user/pages/profile/profile.ts b/src/core/user/pages/profile/profile.ts index a6b506f63..efef99985 100644 --- a/src/core/user/pages/profile/profile.ts +++ b/src/core/user/pages/profile/profile.ts @@ -97,6 +97,14 @@ export class CoreUserProfilePage { fetchUser(): Promise { return this.userProvider.getProfile(this.userId, this.courseId).then((user) => { + if (this.userId == this.site.getUserId() && user.profileimageurl != this.site.getInfo().userpictureurl) { + // The current user image received is different than the one stored in site info. Assume the image was updated. + this.eventsProvider.trigger(CoreUserProvider.PROFILE_PICTURE_UPDATED, { + userId: this.userId, + picture: user.profileimageurl + }, this.site.getId()); + } + user.address = this.userHelper.formatAddress('', user.city, user.country); user.roles = this.userHelper.formatRoleList(user.roles); From 3bcfd0128288c0b4c02f86a1f95e6114d9dc9090 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 6 Feb 2019 16:05:21 +0100 Subject: [PATCH 054/191] MOBILE-2807 message: Add setting to configure send --- scripts/langindex.json | 5 +++ src/addon/messages/lang/en.json | 3 ++ .../messages/pages/settings/settings.html | 16 ++++++++ src/addon/messages/pages/settings/settings.ts | 28 +++++++++++++- src/assets/lang/en.json | 3 ++ .../core-send-message-form.html | 2 +- .../send-message-form/send-message-form.ts | 37 ++++++++++++++++++- src/core/constants.ts | 1 + src/providers/events.ts | 1 + 9 files changed, 92 insertions(+), 4 deletions(-) diff --git a/scripts/langindex.json b/scripts/langindex.json index 22b33e825..3993e76f7 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -214,6 +214,9 @@ "addon.messages.unabletomessage": "message", "addon.messages.unblockuser": "message", "addon.messages.unblockuserconfirm": "message", + "addon.messages.useentertosend": "message", + "addon.messages.useentertosenddescdesktop": "local_moodlemobileapp", + "addon.messages.useentertosenddescmac": "local_moodlemobileapp", "addon.messages.userwouldliketocontactyou": "message", "addon.messages.warningconversationmessagenotsent": "local_moodlemobileapp", "addon.messages.warningmessagenotsent": "local_moodlemobileapp", @@ -602,6 +605,7 @@ "addon.mod_lti.modulenameplural": "lti", "addon.mod_page.errorwhileloadingthepage": "local_moodlemobileapp", "addon.mod_page.modulenameplural": "page", + "addon.mod_quiz.answercolon": "qtype_numerical", "addon.mod_quiz.attemptfirst": "quiz", "addon.mod_quiz.attemptlast": "quiz", "addon.mod_quiz.attemptnumber": "quiz", @@ -1246,6 +1250,7 @@ "core.courses.enrolme": "local_moodlemobileapp", "core.courses.errorloadcategories": "local_moodlemobileapp", "core.courses.errorloadcourses": "local_moodlemobileapp", + "core.courses.errorloadplugins": "local_moodlemobileapp", "core.courses.errorsearching": "local_moodlemobileapp", "core.courses.errorselfenrol": "local_moodlemobileapp", "core.courses.filtermycourses": "local_moodlemobileapp", diff --git a/src/addon/messages/lang/en.json b/src/addon/messages/lang/en.json index 2ec162f66..75419bf5b 100644 --- a/src/addon/messages/lang/en.json +++ b/src/addon/messages/lang/en.json @@ -66,6 +66,9 @@ "unabletomessage": "You are unable to message this user", "unblockuser": "Unblock user", "unblockuserconfirm": "Are you sure you want to unblock {{$a}}?", + "useentertosend": "Use enter to send", + "useentertosenddescdesktop": "If disabled, you can use Ctrl+Enter to send the message.", + "useentertosenddescmac": "If disabled, you can use Cmd+Enter to send the message.", "userwouldliketocontactyou": "{{$a}} would like to contact you", "warningconversationmessagenotsent": "Couldn't send message(s) to conversation {{conversation}}. {{error}}", "warningmessagenotsent": "Couldn't send message(s) to user {{user}}. {{error}}", diff --git a/src/addon/messages/pages/settings/settings.html b/src/addon/messages/pages/settings/settings.html index 4048bb8f6..0172888d9 100644 --- a/src/addon/messages/pages/settings/settings.html +++ b/src/addon/messages/pages/settings/settings.html @@ -32,6 +32,7 @@ +
@@ -90,5 +91,20 @@
+ + + + + {{ 'core.settings.general' | translate }} + + +

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

+

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

+

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

+
+ +
+
+
diff --git a/src/addon/messages/pages/settings/settings.ts b/src/addon/messages/pages/settings/settings.ts index 7b524ff3c..d7e30cd01 100644 --- a/src/addon/messages/pages/settings/settings.ts +++ b/src/addon/messages/pages/settings/settings.ts @@ -16,8 +16,12 @@ import { Component, OnDestroy } from '@angular/core'; import { IonicPage } from 'ionic-angular'; import { AddonMessagesProvider } from '../../providers/messages'; import { CoreUserProvider } from '@core/user/providers/user'; -import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { CoreAppProvider } from '@providers/app'; +import { CoreConfigProvider } from '@providers/config'; +import { CoreEventsProvider } from '@providers/events'; import { CoreSitesProvider } from '@providers/sites'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { CoreConstants } from '@core/constants'; /** * Page that displays the messages settings page. @@ -39,16 +43,27 @@ export class AddonMessagesSettingsPage implements OnDestroy { courseMemberValue = AddonMessagesProvider.MESSAGE_PRIVACY_COURSEMEMBER; siteValue = AddonMessagesProvider.MESSAGE_PRIVACY_SITE; groupMessagingEnabled: boolean; + sendOnEnter: boolean; + isDesktop: boolean; + isMac: boolean; protected previousContactableValue: number | boolean; constructor(private messagesProvider: AddonMessagesProvider, private domUtils: CoreDomUtilsProvider, - private userProvider: CoreUserProvider, sitesProvider: CoreSitesProvider) { + private userProvider: CoreUserProvider, private sitesProvider: CoreSitesProvider, appProvider: CoreAppProvider, + private configProvider: CoreConfigProvider, private eventsProvider: CoreEventsProvider) { const currentSite = sitesProvider.getCurrentSite(); this.advancedContactable = currentSite && currentSite.isVersionGreaterEqualThan('3.6'); this.allowSiteMessaging = currentSite && currentSite.canUseAdvancedFeature('messagingallusers'); this.groupMessagingEnabled = this.messagesProvider.isGroupMessagingEnabled(); + + this.configProvider.get(CoreConstants.SETTINGS_SEND_ON_ENTER, !appProvider.isMobile()).then((sendOnEnter) => { + this.sendOnEnter = !!sendOnEnter; + }); + + this.isDesktop = !appProvider.isMobile(); + this.isMac = appProvider.isMac(); } /** @@ -233,6 +248,15 @@ export class AddonMessagesSettingsPage implements OnDestroy { }); } + sendOnEnterChanged(): void { + // Save the value. + this.configProvider.set(CoreConstants.SETTINGS_SEND_ON_ENTER, this.sendOnEnter ? 1 : 0); + + // Notify the app. + this.eventsProvider.trigger(CoreEventsProvider.SEND_ON_ENTER_CHANGED, {sendOnEnter: !!this.sendOnEnter}, + this.sitesProvider.getCurrentSiteId()); + } + /** * Page destroyed. */ diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index 47566d043..048521fcd 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -214,6 +214,9 @@ "addon.messages.unabletomessage": "You are unable to message this user", "addon.messages.unblockuser": "Unblock user", "addon.messages.unblockuserconfirm": "Are you sure you want to unblock {{$a}}?", + "addon.messages.useentertosend": "Use enter to send", + "addon.messages.useentertosenddescdesktop": "If disabled, you can use Ctrl+Enter to send the message.", + "addon.messages.useentertosenddescmac": "If disabled, you can use Cmd+Enter to send the message.", "addon.messages.userwouldliketocontactyou": "{{$a}} would like to contact you", "addon.messages.warningconversationmessagenotsent": "Couldn't send message(s) to conversation {{conversation}}. {{error}}", "addon.messages.warningmessagenotsent": "Couldn't send message(s) to user {{user}}. {{error}}", 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 aea20f503..f02a7ee2c 100644 --- a/src/components/send-message-form/core-send-message-form.html +++ b/src/components/send-message-form/core-send-message-form.html @@ -1,5 +1,5 @@ - +
diff --git a/src/core/course/components/format/format.ts b/src/core/course/components/format/format.ts index 0252248f5..10ffa511e 100644 --- a/src/core/course/components/format/format.ts +++ b/src/core/course/components/format/format.ts @@ -49,7 +49,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { @Input() initialSectionId?: number; // The section to load first (by ID). @Input() initialSectionNumber?: number; // The section to load first (by number). @Input() moduleId?: number; // The module ID to scroll to. Must be inside the initial selected section. - @Output() completionChanged?: EventEmitter; // Will emit an event when any module completion changes. + @Output() completionChanged?: EventEmitter; // Will emit an event when any module completion changes. @ViewChildren(CoreDynamicComponent) dynamicComponents: QueryList; @@ -461,4 +461,28 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { return section.uservisible !== false && !section.hiddenbynumsections && section.id != CoreCourseProvider.STEALTH_MODULES_SECTION_ID; } + + /** + * The completion of any of the modules have changed. + */ + onCompletionChange(completionData: any): void { + if (completionData.hasOwnProperty('valueused') && !completionData.valueused) { + // If the completion value is not used, the page won't be reloaded, so update the progress bar. + const completionModules = [] + .concat(...this.sections.filter((section) => section.hasOwnProperty('modules')) + .map((section) => section.modules)) + .map((module) => (module.completion > 0) ? 1 : module.completion) + .reduce((accumulator, currentValue) => accumulator + currentValue); + const moduleProgressPercent = 100 / completionModules; + // Use min/max here to avoid floating point rounding errors over/under-flowing the progress bar. + if (completionData.state === CoreCourseProvider.COMPLETION_COMPLETE) { + this.course.progress = Math.min(100, this.course.progress + moduleProgressPercent); + } else { + this.course.progress = Math.max(0, this.course.progress - moduleProgressPercent); + } + + } + // Emit a new event for other components. + this.completionChanged.emit(completionData); + } } diff --git a/src/core/course/components/module-completion/module-completion.ts b/src/core/course/components/module-completion/module-completion.ts index 9edcd0e88..73efcc86f 100644 --- a/src/core/course/components/module-completion/module-completion.ts +++ b/src/core/course/components/module-completion/module-completion.ts @@ -35,7 +35,7 @@ import { CoreCourseProvider } from '../../providers/course'; export class CoreCourseModuleCompletionComponent implements OnChanges { @Input() completion: any; // The completion status. @Input() moduleName?: string; // The name of the module this completion affects. - @Output() completionChanged?: EventEmitter; // Will emit an event when the completion changes. + @Output() completionChanged?: EventEmitter; // Will emit an event when the completion changes. completionImage: string; completionDescription: string; @@ -71,15 +71,23 @@ export class CoreCourseModuleCompletionComponent implements OnChanges { const modal = this.domUtils.showModalLoading(); - this.courseProvider.markCompletedManually(this.completion.cmid, this.completion.state === 1 ? 0 : 1, + this.completion.state = this.completion.state === 1 ? 0 : 1; + this.courseProvider.markCompletedManually(this.completion.cmid, this.completion.state, this.completion.courseId, this.completion.courseName).then((response) => { if (!response.status) { return Promise.reject(null); } - this.completionChanged.emit(); + if (this.completion.hasOwnProperty('valueused') && !this.completion.valueused) { + this.showStatus(); + if (response.offline) { + this.completion.offline = true; + } + } + this.completionChanged.emit(this.completion); }).catch((error) => { + this.completion.state = this.completion.state === 1 ? 0 : 1; this.domUtils.showErrorModalDefault(error, 'core.errorchangecompletion', true); }).finally(() => { modal.dismiss(); diff --git a/src/core/course/components/module/core-course-module.html b/src/core/course/components/module/core-course-module.html index 559a76c0b..eb405cbd6 100644 --- a/src/core/course/components/module/core-course-module.html +++ b/src/core/course/components/module/core-course-module.html @@ -7,7 +7,7 @@
- +
diff --git a/src/core/course/components/module/module.ts b/src/core/course/components/module/module.ts index 50aff0b44..ea0fb47bd 100644 --- a/src/core/course/components/module/module.ts +++ b/src/core/course/components/module/module.ts @@ -49,7 +49,7 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy { this.prefetchDelegate.getModuleStatus(this.module, this.courseId).then(this.showStatus.bind(this)); } } - @Output() completionChanged?: EventEmitter; // Will emit an event when the module completion changes. + @Output() completionChanged?: EventEmitter; // Will emit an event when the module completion changes. showDownload: boolean; // Whether to display the download button. showRefresh: boolean; // Whether to display the refresh button. diff --git a/src/core/course/pages/section/section.html b/src/core/course/pages/section/section.html index 3cb66aa0b..2f47b9f38 100644 --- a/src/core/course/pages/section/section.html +++ b/src/core/course/pages/section/section.html @@ -23,7 +23,7 @@ - + diff --git a/src/core/course/pages/section/section.ts b/src/core/course/pages/section/section.ts index e711e1e08..65adf6fb4 100644 --- a/src/core/course/pages/section/section.ts +++ b/src/core/course/pages/section/section.ts @@ -347,9 +347,12 @@ export class CoreCourseSectionPage implements OnDestroy { /** * The completion of any of the modules have changed. */ - onCompletionChange(): void { + onCompletionChange(completionData: any): void { + const shouldReload = !completionData.hasOwnProperty('valueused') || completionData.valueused; this.invalidateData().finally(() => { - this.refreshAfterCompletionChange(true); + if (shouldReload) { + this.refreshAfterCompletionChange(true); + } }); } From eda5e3e08a5a8a5dcfe9fb21032a3568ca5e1084 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 7 Feb 2019 13:25:58 +0100 Subject: [PATCH 056/191] MOBILE-2841 infinite: Don't send event if position is top --- src/components/infinite-loading/infinite-loading.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/infinite-loading/infinite-loading.ts b/src/components/infinite-loading/infinite-loading.ts index a3f1f89df..101e303d4 100644 --- a/src/components/infinite-loading/infinite-loading.ts +++ b/src/components/infinite-loading/infinite-loading.ts @@ -45,13 +45,13 @@ export class CoreInfiniteLoadingComponent implements OnChanges { * @param {SimpleChange}} changes Changes. */ ngOnChanges(changes: {[name: string]: SimpleChange}): void { - if (changes.enabled && this.enabled) { + if (changes.enabled && this.enabled && this.position == 'bottom') { // Infinite scroll enabled. If the list doesn't fill the full height, infinite scroll isn't triggered automatically. // Send a fake scroll event to make infinite scroll check if it should load more items. setTimeout(() => { const event: any = new Event('scroll'); this.content.ionScroll.emit(event); - }); + }, 400); } } From 2ec371bf0c1f91d00f01388f2dd94d7e001cb168 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 7 Feb 2019 14:00:34 +0100 Subject: [PATCH 057/191] MOBILE-2114 scorm: Fix offline data not detected on exit --- src/addon/mod/scorm/pages/player/player.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/addon/mod/scorm/pages/player/player.ts b/src/addon/mod/scorm/pages/player/player.ts index eafadea86..8fed9f121 100644 --- a/src/addon/mod/scorm/pages/player/player.ts +++ b/src/addon/mod/scorm/pages/player/player.ts @@ -442,7 +442,9 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy { this.tocObserver && this.tocObserver.off(); this.launchNextObserver && this.launchNextObserver.off(); this.launchPrevObserver && this.launchPrevObserver.off(); - this.goOfflineObserver && this.goOfflineObserver.off(); + setTimeout(() => { + this.goOfflineObserver && this.goOfflineObserver.off(); + }, 500); // Unblock the SCORM so it can be synced. this.syncProvider.unblockOperation(AddonModScormProvider.COMPONENT, this.scorm.id, 'player'); From 1976cba5aa652659875e9fd2d314e66d7a8fe7b3 Mon Sep 17 00:00:00 2001 From: Andrew Nicols Date: Mon, 21 Jan 2019 12:07:45 +0800 Subject: [PATCH 058/191] MOBILE-2837 Docker: Add a Dockerfile for easier testing and CI --- .dockerignore | 2 ++ Dockerfile | 27 +++++++++++++++++++++++++++ hooks/post_push | 18 ++++++++++++++++++ 3 files changed, 47 insertions(+) create mode 100644 .dockerignore create mode 100644 Dockerfile create mode 100644 hooks/post_push diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..210c47a90 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,2 @@ +node_modules +Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 000000000..d884d988f --- /dev/null +++ b/Dockerfile @@ -0,0 +1,27 @@ +# This image is based on the fat node 11 image. +# We require fat images as neither alpine, or slim, include git binaries. +FROM node:11 + +# Port 8100 for ionic dev server. +EXPOSE 8100 + +# Port 35729 is the live-reload server. +EXPOSE 35729 + +# Port 53703 is the Chrome dev logger port. +EXPOSE 53703 + +# MoodleMobile uses Cordova, Ionic, and Gulp. +RUN npm install -g cordova ionic gulp && rm -rf /root/.npm + +WORKDIR /app + +COPY . /app + +# The setup script will handle npm installation, cordova setup, and gulp setup. +RUN npm run setup && rm -rf /root/.npm + +# Provide a Healthcheck command for easier use in CI. +HEALTHCHECK --interval=10s --timeout=3s --start-period=30s CMD curl -f http://localhost:8100 || exit 1 + +CMD ["ionic", "serve", "-b"] diff --git a/hooks/post_push b/hooks/post_push new file mode 100644 index 000000000..047dc366e --- /dev/null +++ b/hooks/post_push @@ -0,0 +1,18 @@ +#!/bin/bash + +set -e + +if [ "integration" != "${SOURCE_BRANCH}" ] +then + # A space-separated list of additional tags to place on this image. + additionalTags=(latest) + + # Tag and push image for each additional tag + for tag in ${additionalTags[@]}; do + echo "Tagging {$IMAGE_NAME} as ${DOCKER_REPO}:${tag}" + docker tag $IMAGE_NAME ${DOCKER_REPO}:${tag} + + echo "Pushing ${DOCKER_REPO}:${tag}" + docker push ${DOCKER_REPO}:${tag} + done +fi From 56eaafdedad96ccbcd4e2db21b92cd33def6f24c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Mon, 4 Feb 2019 12:06:05 +0100 Subject: [PATCH 059/191] MOBILE-1979 course: Save offline log calls --- src/addon/mod/assign/providers/assign.ts | 24 +- src/addon/mod/book/providers/book.ts | 9 +- src/addon/mod/chat/providers/chat.ts | 15 +- src/addon/mod/choice/providers/choice.ts | 10 +- src/addon/mod/data/providers/data.ts | 9 +- src/addon/mod/feedback/providers/feedback.ts | 8 +- src/addon/mod/folder/providers/folder.ts | 8 +- .../mod/forum/pages/discussion/discussion.ts | 2 +- src/addon/mod/forum/providers/forum.ts | 17 +- src/addon/mod/glossary/pages/entry/entry.ts | 2 +- src/addon/mod/glossary/providers/glossary.ts | 15 +- src/addon/mod/imscp/providers/imscp.ts | 11 +- src/addon/mod/lesson/providers/lesson.ts | 12 +- src/addon/mod/lti/providers/lti.ts | 11 +- src/addon/mod/page/providers/page.ts | 9 +- src/addon/mod/quiz/pages/player/player.ts | 5 +- src/addon/mod/quiz/pages/review/review.ts | 2 +- src/addon/mod/quiz/providers/quiz-sync.ts | 4 +- src/addon/mod/quiz/providers/quiz.ts | 27 ++- src/addon/mod/resource/providers/resource.ts | 9 +- src/addon/mod/scorm/providers/scorm.ts | 19 +- src/addon/mod/survey/providers/survey.ts | 8 +- src/addon/mod/url/providers/url.ts | 9 +- src/addon/mod/wiki/components/index/index.ts | 4 +- src/addon/mod/wiki/providers/wiki.ts | 29 ++- .../workshop/pages/submission/submission.ts | 2 +- src/addon/mod/workshop/providers/workshop.ts | 31 ++- src/core/course/course.module.ts | 3 + src/core/course/providers/log-helper.ts | 225 ++++++++++++++++++ 29 files changed, 403 insertions(+), 136 deletions(-) create mode 100644 src/core/course/providers/log-helper.ts diff --git a/src/addon/mod/assign/providers/assign.ts b/src/addon/mod/assign/providers/assign.ts index c6c3bf8cf..69cce5081 100644 --- a/src/addon/mod/assign/providers/assign.ts +++ b/src/addon/mod/assign/providers/assign.ts @@ -23,6 +23,7 @@ import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCommentsProvider } from '@core/comments/providers/comments'; import { CoreUserProvider } from '@core/user/providers/user'; import { CoreGradesProvider } from '@core/grades/providers/grades'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { AddonModAssignSubmissionDelegate } from './submission-delegate'; import { AddonModAssignOfflineProvider } from './assign-offline'; import { CoreSiteWSPreSets } from '@classes/site'; @@ -68,7 +69,8 @@ export class AddonModAssignProvider { private timeUtils: CoreTimeUtilsProvider, private appProvider: CoreAppProvider, private utils: CoreUtilsProvider, private userProvider: CoreUserProvider, private submissionDelegate: AddonModAssignSubmissionDelegate, private gradesProvider: CoreGradesProvider, private filepoolProvider: CoreFilepoolProvider, - private assignOffline: AddonModAssignOfflineProvider, private commentsProvider: CoreCommentsProvider) { + private assignOffline: AddonModAssignOfflineProvider, private commentsProvider: CoreCommentsProvider, + private logHelper: CoreCourseLogHelperProvider) { this.logger = logger.getInstance('AddonModAssignProvider'); } @@ -976,13 +978,11 @@ export class AddonModAssignProvider { * @return {Promise} Promise resolved when the WS call is successful. */ logGradingView(assignId: number, siteId?: string): Promise { - return this.sitesProvider.getSite(siteId).then((site) => { - const params = { - assignid: assignId - }; + const params = { + assignid: assignId + }; - return site.write('mod_assign_view_grading_table', params); - }); + return this.logHelper.log('mod_assign_view_grading_table', params, AddonModAssignProvider.COMPONENT, assignId, siteId); } /** @@ -993,13 +993,11 @@ export class AddonModAssignProvider { * @return {Promise} Promise resolved when the WS call is successful. */ logView(assignId: number, siteId?: string): Promise { - return this.sitesProvider.getSite(siteId).then((site) => { - const params = { - assignid: assignId - }; + const params = { + assignid: assignId + }; - return site.write('mod_assign_view_assign', params); - }); + return this.logHelper.log('mod_assign_view_assign', params, AddonModAssignProvider.COMPONENT, assignId, siteId); } /** diff --git a/src/addon/mod/book/providers/book.ts b/src/addon/mod/book/providers/book.ts index 4f97a6e83..e322284c6 100644 --- a/src/addon/mod/book/providers/book.ts +++ b/src/addon/mod/book/providers/book.ts @@ -22,6 +22,7 @@ import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; /** * A book chapter inside the toc list. @@ -64,7 +65,8 @@ export class AddonModBookProvider { 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 utils: CoreUtilsProvider, private courseProvider: CoreCourseProvider, private domUtils: CoreDomUtilsProvider, + private logHelper: CoreCourseLogHelperProvider) { this.logger = logger.getInstance('AddonModBookProvider'); } @@ -378,14 +380,15 @@ export class AddonModBookProvider { * * @param {number} id Module ID. * @param {string} chapterId Chapter ID. + * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the WS call is successful. */ - logView(id: number, chapterId: string): Promise { + logView(id: number, chapterId: string, siteId?: string): Promise { const params = { bookid: id, chapterid: chapterId }; - return this.sitesProvider.getCurrentSite().write('mod_book_view_book', params); + return this.logHelper.log('mod_book_view_book', params, AddonModBookProvider.COMPONENT, id, siteId); } } diff --git a/src/addon/mod/chat/providers/chat.ts b/src/addon/mod/chat/providers/chat.ts index d4f6e8f3e..1138502da 100644 --- a/src/addon/mod/chat/providers/chat.ts +++ b/src/addon/mod/chat/providers/chat.ts @@ -15,6 +15,7 @@ import { Injectable } from '@angular/core'; import { CoreSitesProvider } from '@providers/sites'; import { CoreUserProvider } from '@core/user/providers/user'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; /** * Service that provides some features for chats. @@ -24,7 +25,8 @@ export class AddonModChatProvider { static COMPONENT = 'mmaModChat'; static POLL_INTERVAL = 4000; - constructor(private sitesProvider: CoreSitesProvider, private userProvider: CoreUserProvider) {} + constructor(private sitesProvider: CoreSitesProvider, private userProvider: CoreUserProvider, + private logHelper: CoreCourseLogHelperProvider) {} /** * Get a chat. @@ -77,15 +79,16 @@ export class AddonModChatProvider { /** * Report a chat as being viewed. * - * @param {number} chatId Chat instance ID. - * @return {Promise} Promise resolved when the WS call is executed. + * @param {number} id Chat instance ID. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the WS call is successful. */ - logView(chatId: number): Promise { + logView(id: number, siteId?: string): Promise { const params = { - chatid: chatId + chatid: id }; - return this.sitesProvider.getCurrentSite().write('mod_chat_view_chat', params); + return this.logHelper.log('mod_chat_view_chat', params, AddonModChatProvider.COMPONENT, id, siteId); } /** diff --git a/src/addon/mod/choice/providers/choice.ts b/src/addon/mod/choice/providers/choice.ts index b94fec312..8845aed49 100644 --- a/src/addon/mod/choice/providers/choice.ts +++ b/src/addon/mod/choice/providers/choice.ts @@ -17,6 +17,7 @@ import { CoreSitesProvider } from '@providers/sites'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreAppProvider } from '@providers/app'; import { CoreFilepoolProvider } from '@providers/filepool'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { AddonModChoiceOfflineProvider } from './offline'; /** @@ -38,7 +39,7 @@ export class AddonModChoiceProvider { constructor(private sitesProvider: CoreSitesProvider, private appProvider: CoreAppProvider, private filepoolProvider: CoreFilepoolProvider, private utils: CoreUtilsProvider, - private choiceOffline: AddonModChoiceOfflineProvider) {} + private choiceOffline: AddonModChoiceOfflineProvider, private logHelper: CoreCourseLogHelperProvider) {} /** * Check if results can be seen by a student. The student can see the results if: @@ -346,14 +347,15 @@ export class AddonModChoiceProvider { * Report the choice as being viewed. * * @param {string} id Choice ID. - * @return {Promise} Promise resolved when the WS call is successful. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the WS call is successful. */ - logView(id: string): Promise { + logView(id: number, siteId?: string): Promise { const params = { choiceid: id }; - return this.sitesProvider.getCurrentSite().write('mod_choice_view_choice', params); + return this.logHelper.log('mod_choice_view_choice', params, AddonModChoiceProvider.COMPONENT, id, siteId); } /** diff --git a/src/addon/mod/data/providers/data.ts b/src/addon/mod/data/providers/data.ts index ca9d5da62..8ab7f43e2 100644 --- a/src/addon/mod/data/providers/data.ts +++ b/src/addon/mod/data/providers/data.ts @@ -18,6 +18,7 @@ import { CoreLoggerProvider } from '@providers/logger'; import { CoreSitesProvider } from '@providers/sites'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreFilepoolProvider } from '@providers/filepool'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { AddonModDataOfflineProvider } from './offline'; import { AddonModDataFieldsDelegate } from './fields-delegate'; @@ -35,7 +36,8 @@ export class AddonModDataProvider { constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider, private filepoolProvider: CoreFilepoolProvider, private dataOffline: AddonModDataOfflineProvider, - private appProvider: CoreAppProvider, private fieldsDelegate: AddonModDataFieldsDelegate) { + private appProvider: CoreAppProvider, private fieldsDelegate: AddonModDataFieldsDelegate, + private logHelper: CoreCourseLogHelperProvider) { this.logger = logger.getInstance('AddonModDataProvider'); } @@ -846,14 +848,15 @@ export class AddonModDataProvider { * Report the database as being viewed. * * @param {number} id Module ID. + * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the WS call is successful. */ - logView(id: number): Promise { + logView(id: number, siteId?: string): Promise { const params = { databaseid: id }; - return this.sitesProvider.getCurrentSite().write('mod_data_view_database', params); + return this.logHelper.log('mod_data_view_database', params, AddonModDataProvider.COMPONENT, id, siteId); } /** diff --git a/src/addon/mod/feedback/providers/feedback.ts b/src/addon/mod/feedback/providers/feedback.ts index 3fcb0099e..a113791c5 100644 --- a/src/addon/mod/feedback/providers/feedback.ts +++ b/src/addon/mod/feedback/providers/feedback.ts @@ -18,6 +18,7 @@ import { CoreSitesProvider } from '@providers/sites'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreFilepoolProvider } from '@providers/filepool'; import { CoreAppProvider } from '@providers/app'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { AddonModFeedbackOfflineProvider } from './offline'; /** @@ -38,7 +39,7 @@ export class AddonModFeedbackProvider { constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider, private filepoolProvider: CoreFilepoolProvider, private feedbackOffline: AddonModFeedbackOfflineProvider, - private appProvider: CoreAppProvider) { + private appProvider: CoreAppProvider, private logHelper: CoreCourseLogHelperProvider) { this.logger = logger.getInstance('AddonModFeedbackProvider'); } @@ -1071,15 +1072,16 @@ export class AddonModFeedbackProvider { * * @param {number} id Module ID. * @param {boolean} [formViewed=false] True if form was viewed. + * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the WS call is successful. */ - logView(id: number, formViewed: boolean = false): Promise { + logView(id: number, formViewed: boolean = false, siteId?: string): Promise { const params = { feedbackid: id, moduleviewed: formViewed ? 1 : 0 }; - return this.sitesProvider.getCurrentSite().write('mod_feedback_view_feedback', params); + return this.logHelper.log('mod_feedback_view_feedback', params, AddonModFeedbackProvider.COMPONENT, id, siteId); } /** diff --git a/src/addon/mod/folder/providers/folder.ts b/src/addon/mod/folder/providers/folder.ts index 63c5767bd..f26369af8 100644 --- a/src/addon/mod/folder/providers/folder.ts +++ b/src/addon/mod/folder/providers/folder.ts @@ -17,6 +17,7 @@ import { CoreLoggerProvider } from '@providers/logger'; import { CoreSitesProvider } from '@providers/sites'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; /** * Service that provides some features for folder. @@ -29,7 +30,7 @@ export class AddonModFolderProvider { protected logger; constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private courseProvider: CoreCourseProvider, - private utils: CoreUtilsProvider) { + private utils: CoreUtilsProvider, private logHelper: CoreCourseLogHelperProvider) { this.logger = logger.getInstance('AddonModFolderProvider'); } @@ -132,13 +133,14 @@ export class AddonModFolderProvider { * Report a folder as being viewed. * * @param {number} id Module ID. + * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the WS call is successful. */ - logView(id: number): Promise { + logView(id: number, siteId?: string): Promise { const params = { folderid: id }; - return this.sitesProvider.getCurrentSite().write('mod_folder_view_folder', params); + return this.logHelper.log('mod_folder_view_folder', params, AddonModFolderProvider.COMPONENT, id, siteId); } } diff --git a/src/addon/mod/forum/pages/discussion/discussion.ts b/src/addon/mod/forum/pages/discussion/discussion.ts index 16d5537b2..7879e8470 100644 --- a/src/addon/mod/forum/pages/discussion/discussion.ts +++ b/src/addon/mod/forum/pages/discussion/discussion.ts @@ -299,7 +299,7 @@ export class AddonModForumDiscussionPage implements OnDestroy { if (forceMarkAsRead || (hasUnreadPosts && this.trackPosts)) { // // Add log in Moodle and mark unread posts as readed. - this.forumProvider.logDiscussionView(this.discussionId).catch(() => { + this.forumProvider.logDiscussionView(this.discussionId, this.forumId).catch(() => { // Ignore errors. }).finally(() => { // Trigger mark read posts. diff --git a/src/addon/mod/forum/providers/forum.ts b/src/addon/mod/forum/providers/forum.ts index f386ac855..a7eb0bbae 100644 --- a/src/addon/mod/forum/providers/forum.ts +++ b/src/addon/mod/forum/providers/forum.ts @@ -20,6 +20,7 @@ import { CoreGroupsProvider } from '@providers/groups'; import { CoreSitesProvider } from '@providers/sites'; import { CoreUserProvider } from '@core/user/providers/user'; import { CoreUtilsProvider } from '@providers/utils/utils'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { AddonModForumOfflineProvider } from './offline'; /** @@ -43,7 +44,8 @@ export class AddonModForumProvider { private userProvider: CoreUserProvider, private translate: TranslateService, private utils: CoreUtilsProvider, - private forumOffline: AddonModForumOfflineProvider) {} + private forumOffline: AddonModForumOfflineProvider, + private logHelper: CoreCourseLogHelperProvider) {} /** * Get cache key for can add discussion WS calls. @@ -596,28 +598,31 @@ export class AddonModForumProvider { * Report a forum as being viewed. * * @param {number} id Module ID. - * @return {Promise} Promise resolved when the WS call is successful. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the WS call is successful. */ - logView(id: number): Promise { + logView(id: number, siteId?: string): Promise { const params = { forumid: id }; - return this.sitesProvider.getCurrentSite().write('mod_forum_view_forum', params); + return this.logHelper.log('mod_forum_view_forum', params, AddonModForumProvider.COMPONENT, id, siteId); } /** * Report a forum discussion as being viewed. * * @param {number} id Discussion ID. + * @param {number} forumId Forum ID. + * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the WS call is successful. */ - logDiscussionView(id: number): Promise { + logDiscussionView(id: number, forumId: number, siteId?: string): Promise { const params = { discussionid: id }; - return this.sitesProvider.getCurrentSite().write('mod_forum_view_forum_discussion', params); + return this.logHelper.log('mod_forum_view_forum_discussion', params, AddonModForumProvider.COMPONENT, forumId, siteId); } /** diff --git a/src/addon/mod/glossary/pages/entry/entry.ts b/src/addon/mod/glossary/pages/entry/entry.ts index 0e2451be6..461f0350b 100644 --- a/src/addon/mod/glossary/pages/entry/entry.ts +++ b/src/addon/mod/glossary/pages/entry/entry.ts @@ -48,7 +48,7 @@ export class AddonModGlossaryEntryPage { */ ionViewDidLoad(): void { this.fetchEntry().then(() => { - this.glossaryProvider.logEntryView(this.entry.id).catch(() => { + this.glossaryProvider.logEntryView(this.entry.id, this.componentId).catch(() => { // Ignore errors. }); }).finally(() => { diff --git a/src/addon/mod/glossary/providers/glossary.ts b/src/addon/mod/glossary/providers/glossary.ts index 990f33cdd..da906180c 100644 --- a/src/addon/mod/glossary/providers/glossary.ts +++ b/src/addon/mod/glossary/providers/glossary.ts @@ -20,6 +20,7 @@ import { CoreFilepoolProvider } from '@providers/filepool'; import { CoreSitesProvider } from '@providers/sites'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreUtilsProvider } from '@providers/utils/utils'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { AddonModGlossaryOfflineProvider } from './offline'; /** @@ -43,7 +44,8 @@ export class AddonModGlossaryProvider { private translate: TranslateService, private textUtils: CoreTextUtilsProvider, private utils: CoreUtilsProvider, - private glossaryOffline: AddonModGlossaryOfflineProvider) {} + private glossaryOffline: AddonModGlossaryOfflineProvider, + private logHelper: CoreCourseLogHelperProvider) {} /** * Get the course glossary cache key. @@ -860,28 +862,31 @@ export class AddonModGlossaryProvider { * * @param {number} glossaryId Glossary ID. * @param {string} mode The mode in which the glossary was viewed. + * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the WS call is successful. */ - logView(glossaryId: number, mode: string): Promise { + logView(glossaryId: number, mode: string, siteId?: string): Promise { const params = { id: glossaryId, mode: mode }; - return this.sitesProvider.getCurrentSite().write('mod_glossary_view_glossary', params); + return this.logHelper.log('mod_glossary_view_glossary', params, AddonModGlossaryProvider.COMPONENT, glossaryId, siteId); } /** * Report a glossary entry as being viewed. * * @param {number} entryId Entry ID. + * @param {number} glossaryId Glossary ID. + * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the WS call is successful. */ - logEntryView(entryId: number): Promise { + logEntryView(entryId: number, glossaryId: number, siteId?: string): Promise { const params = { id: entryId }; - return this.sitesProvider.getCurrentSite().write('mod_glossary_view_entry', params); + return this.logHelper.log('mod_glossary_view_entry', params, AddonModGlossaryProvider.COMPONENT, glossaryId, siteId); } } diff --git a/src/addon/mod/imscp/providers/imscp.ts b/src/addon/mod/imscp/providers/imscp.ts index cb66631a7..5cc7b9258 100644 --- a/src/addon/mod/imscp/providers/imscp.ts +++ b/src/addon/mod/imscp/providers/imscp.ts @@ -19,6 +19,7 @@ import { CoreSitesProvider } from '@providers/sites'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; /** * Service that provides some features for IMSCP. @@ -31,7 +32,8 @@ export class AddonModImscpProvider { constructor(private appProvider: CoreAppProvider, private courseProvider: CoreCourseProvider, private filepoolProvider: CoreFilepoolProvider, private sitesProvider: CoreSitesProvider, - private textUtils: CoreTextUtilsProvider, private utils: CoreUtilsProvider) {} + private textUtils: CoreTextUtilsProvider, private utils: CoreUtilsProvider, + private logHelper: CoreCourseLogHelperProvider) {} /** * Get the IMSCP toc as an array. @@ -307,13 +309,14 @@ export class AddonModImscpProvider { * Report a IMSCP as being viewed. * * @param {string} id Module ID. - * @return {Promise} Promise resolved when the WS call is successful. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the WS call is successful. */ - logView(id: string): Promise { + logView(id: number, siteId?: string): Promise { const params = { imscpid: id }; - return this.sitesProvider.getCurrentSite().write('mod_imscp_view_imscp', params); + return this.logHelper.log('mod_imscp_view_imscp', params, AddonModImscpProvider.COMPONENT, id, siteId); } } diff --git a/src/addon/mod/lesson/providers/lesson.ts b/src/addon/mod/lesson/providers/lesson.ts index 6af1f19d1..c996f0c6a 100644 --- a/src/addon/mod/lesson/providers/lesson.ts +++ b/src/addon/mod/lesson/providers/lesson.ts @@ -20,6 +20,7 @@ import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreGradesProvider } from '@core/grades/providers/grades'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreSiteWSPreSets } from '@classes/site'; import { AddonModLessonOfflineProvider } from './lesson-offline'; @@ -179,7 +180,7 @@ export class AddonModLessonProvider { constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider, private translate: TranslateService, private textUtils: CoreTextUtilsProvider, private domUtils: CoreDomUtilsProvider, - private lessonOfflineProvider: AddonModLessonOfflineProvider) { + private lessonOfflineProvider: AddonModLessonOfflineProvider, private logHelper: CoreCourseLogHelperProvider) { this.logger = logger.getInstance('AddonModLessonProvider'); this.sitesProvider.createTableFromSchema(this.tablesSchema); @@ -2960,14 +2961,9 @@ export class AddonModLessonProvider { params.password = password; } - return site.write('mod_lesson_view_lesson', params).then((result) => { - if (!result.status) { - return Promise.reject(null); - } - - return result; - }); + return this.logHelper.log('mod_lesson_view_lesson', params, AddonModLessonProvider.COMPONENT, id, siteId); }); + } /** diff --git a/src/addon/mod/lti/providers/lti.ts b/src/addon/mod/lti/providers/lti.ts index 64f6f6e6a..44163818a 100644 --- a/src/addon/mod/lti/providers/lti.ts +++ b/src/addon/mod/lti/providers/lti.ts @@ -20,6 +20,7 @@ import { CoreSitesProvider } from '@providers/sites'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreUrlUtilsProvider } from '@providers/utils/url'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; export interface AddonModLtiParam { name: string; @@ -42,7 +43,8 @@ export class AddonModLtiProvider { private urlUtils: CoreUrlUtilsProvider, private utils: CoreUtilsProvider, private translate: TranslateService, - private appProvider: CoreAppProvider) {} + private appProvider: CoreAppProvider, + private logHelper: CoreCourseLogHelperProvider) {} /** * Delete launcher. @@ -211,13 +213,14 @@ export class AddonModLtiProvider { * Report the LTI as being viewed. * * @param {string} id LTI id. - * @return {Promise} Promise resolved when the WS call is successful. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the WS call is successful. */ - logView(id: string): Promise { + logView(id: number, siteId?: string): Promise { const params: any = { ltiid: id }; - return this.sitesProvider.getCurrentSite().write('mod_lti_view_lti', params); + return this.logHelper.log('mod_lti_view_lti', params, AddonModLtiProvider.COMPONENT, id, siteId); } } diff --git a/src/addon/mod/page/providers/page.ts b/src/addon/mod/page/providers/page.ts index 11647e68b..cfdce8b11 100644 --- a/src/addon/mod/page/providers/page.ts +++ b/src/addon/mod/page/providers/page.ts @@ -17,6 +17,7 @@ import { CoreLoggerProvider } from '@providers/logger'; import { CoreSitesProvider } from '@providers/sites'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreFilepoolProvider } from '@providers/filepool'; /** @@ -30,7 +31,8 @@ export class AddonModPageProvider { protected logger; constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private courseProvider: CoreCourseProvider, - private utils: CoreUtilsProvider, private filepoolProvider: CoreFilepoolProvider) { + private utils: CoreUtilsProvider, private filepoolProvider: CoreFilepoolProvider, + private logHelper: CoreCourseLogHelperProvider) { this.logger = logger.getInstance('AddonModPageProvider'); } @@ -146,13 +148,14 @@ export class AddonModPageProvider { * Report a page as being viewed. * * @param {number} id Module ID. + * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the WS call is successful. */ - logView(id: number): Promise { + logView(id: number, siteId?: string): Promise { const params = { pageid: id }; - return this.sitesProvider.getCurrentSite().write('mod_page_view_page', params); + return this.logHelper.log('mod_page_view_page', params, AddonModPageProvider.COMPONENT, id, siteId); } } diff --git a/src/addon/mod/quiz/pages/player/player.ts b/src/addon/mod/quiz/pages/player/player.ts index 5d91f73e6..feb27bb7f 100644 --- a/src/addon/mod/quiz/pages/player/player.ts +++ b/src/addon/mod/quiz/pages/player/player.ts @@ -428,7 +428,8 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { }); // Mark the page as viewed. We'll ignore errors in this call. - this.quizProvider.logViewAttempt(this.attempt.id, page, this.preflightData, this.offline).catch((error) => { + this.quizProvider.logViewAttempt(this.attempt.id, this.quizId, page, this.preflightData, this.offline) + .catch((error) => { // Ignore errors. }); @@ -455,7 +456,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { this.attempt.dueDateWarning = this.quizProvider.getAttemptDueDateWarning(this.quiz, this.attempt); // Log summary as viewed. - this.quizProvider.logViewAttemptSummary(this.attempt.id, this.preflightData).catch((error) => { + this.quizProvider.logViewAttemptSummary(this.attempt.id, this.preflightData, this.quizId).catch((error) => { // Ignore errors. }); }); diff --git a/src/addon/mod/quiz/pages/review/review.ts b/src/addon/mod/quiz/pages/review/review.ts index 805d9662b..03c894cf6 100644 --- a/src/addon/mod/quiz/pages/review/review.ts +++ b/src/addon/mod/quiz/pages/review/review.ts @@ -77,7 +77,7 @@ export class AddonModQuizReviewPage implements OnInit { */ ngOnInit(): void { this.fetchData().then(() => { - this.quizProvider.logViewAttemptReview(this.attemptId).catch((error) => { + this.quizProvider.logViewAttemptReview(this.attemptId, this.quizId).catch((error) => { // Ignore errors. }); }).finally(() => { diff --git a/src/addon/mod/quiz/providers/quiz-sync.ts b/src/addon/mod/quiz/providers/quiz-sync.ts index 9c27f22b5..4d00bf7fa 100644 --- a/src/addon/mod/quiz/providers/quiz-sync.ts +++ b/src/addon/mod/quiz/providers/quiz-sync.ts @@ -341,8 +341,8 @@ export class AddonModQuizSyncProvider extends CoreSyncBaseProvider { // Answers sent, now set the current page if the attempt isn't finished. if (!finish) { - return this.quizProvider.logViewAttempt(onlineAttempt.id, offlineAttempt.currentpage, preflightData, - false).catch(() => { + return this.quizProvider.logViewAttempt(onlineAttempt.id, quiz.id, offlineAttempt.currentpage, + preflightData, false).catch(() => { // Ignore errors. }); } diff --git a/src/addon/mod/quiz/providers/quiz.ts b/src/addon/mod/quiz/providers/quiz.ts index a3c8d87be..1777bafdf 100644 --- a/src/addon/mod/quiz/providers/quiz.ts +++ b/src/addon/mod/quiz/providers/quiz.ts @@ -24,6 +24,7 @@ import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreSiteWSPreSets } from '@classes/site'; import { CoreGradesHelperProvider } from '@core/grades/providers/helper'; import { CoreQuestionDelegate } from '@core/question/providers/delegate'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { AddonModQuizAccessRuleDelegate } from './access-rules-delegate'; import { AddonModQuizOfflineProvider } from './quiz-offline'; @@ -62,7 +63,7 @@ export class AddonModQuizProvider { private gradesHelper: CoreGradesHelperProvider, private questionDelegate: CoreQuestionDelegate, private filepoolProvider: CoreFilepoolProvider, private timeUtils: CoreTimeUtilsProvider, private accessRulesDelegate: AddonModQuizAccessRuleDelegate, private quizOfflineProvider: AddonModQuizOfflineProvider, - private domUtils: CoreDomUtilsProvider) { + private domUtils: CoreDomUtilsProvider, private logHelper: CoreCourseLogHelperProvider) { this.logger = logger.getInstance('AddonModQuizProvider'); } @@ -1523,9 +1524,12 @@ export class AddonModQuizProvider { * @param {number} [page=0] Page number. * @param {any} [preflightData] Preflight required data (like password). * @param {boolean} [offline] Whether attempt is offline. + * @param {number} quizId Quiz ID. + * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the WS call is successful. */ - logViewAttempt(attemptId: number, page: number = 0, preflightData: any = {}, offline?: boolean): Promise { + logViewAttempt(attemptId: number, quizId: number, page: number = 0, preflightData: any = {}, offline?: boolean, + siteId?: string): Promise { const params = { attemptid: attemptId, page: page, @@ -1533,7 +1537,7 @@ export class AddonModQuizProvider { }, promises = []; - promises.push(this.sitesProvider.getCurrentSite().write('mod_quiz_view_attempt', params)); + promises.push(this.logHelper.log('mod_quiz_view_attempt', params, AddonModQuizProvider.COMPONENT, quizId, siteId)); if (offline) { promises.push(this.quizOfflineProvider.setAttemptCurrentPage(attemptId, page)); } @@ -1545,14 +1549,16 @@ export class AddonModQuizProvider { * Report an attempt's review as being viewed. * * @param {number} attemptId Attempt ID. + * @param {number} quizId Quiz ID. + * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the WS call is successful. */ - logViewAttemptReview(attemptId: number): Promise { + logViewAttemptReview(attemptId: number, quizId: number, siteId?: string): Promise { const params = { attemptid: attemptId }; - return this.sitesProvider.getCurrentSite().write('mod_quiz_view_attempt_review', params); + return this.logHelper.log('mod_quiz_view_attempt_review', params, AddonModQuizProvider.COMPONENT, quizId, siteId); } /** @@ -1560,29 +1566,32 @@ export class AddonModQuizProvider { * * @param {number} attemptId Attempt ID. * @param {any} preflightData Preflight required data (like password). + * @param {number} quizId Quiz ID. + * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the WS call is successful. */ - logViewAttemptSummary(attemptId: number, preflightData: any): Promise { + logViewAttemptSummary(attemptId: number, preflightData: any, quizId: number, siteId?: string): Promise { const params = { attemptid: attemptId, preflightdata: this.utils.objectToArrayOfObjects(preflightData, 'name', 'value', true) }; - return this.sitesProvider.getCurrentSite().write('mod_quiz_view_attempt_summary', params); + return this.logHelper.log('mod_quiz_view_attempt_summary', params, AddonModQuizProvider.COMPONENT, quizId, siteId); } /** * Report a quiz as being viewed. * * @param {number} id Module ID. + * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the WS call is successful. */ - logViewQuiz(id: number): Promise { + logViewQuiz(id: number, siteId?: string): Promise { const params = { quizid: id }; - return this.sitesProvider.getCurrentSite().write('mod_quiz_view_quiz', params); + return this.logHelper.log('mod_quiz_view_quiz', params, AddonModQuizProvider.COMPONENT, id, siteId); } /** diff --git a/src/addon/mod/resource/providers/resource.ts b/src/addon/mod/resource/providers/resource.ts index cd09eabc6..725cbc473 100644 --- a/src/addon/mod/resource/providers/resource.ts +++ b/src/addon/mod/resource/providers/resource.ts @@ -17,6 +17,7 @@ import { CoreLoggerProvider } from '@providers/logger'; import { CoreSitesProvider } from '@providers/sites'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreFilepoolProvider } from '@providers/filepool'; /** @@ -30,7 +31,8 @@ export class AddonModResourceProvider { protected logger; constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private courseProvider: CoreCourseProvider, - private filepoolProvider: CoreFilepoolProvider, private utils: CoreUtilsProvider) { + private filepoolProvider: CoreFilepoolProvider, private utils: CoreUtilsProvider, + private logHelper: CoreCourseLogHelperProvider) { this.logger = logger.getInstance('AddonModResourceProvider'); } @@ -148,13 +150,14 @@ export class AddonModResourceProvider { * Report the resource as being viewed. * * @param {number} id Module ID. + * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the WS call is successful. */ - logView(id: number): Promise { + logView(id: number, siteId?: string): Promise { const params = { resourceid: id }; - return this.sitesProvider.getCurrentSite().write('mod_resource_view_resource', params); + return this.logHelper.log('mod_resource_view_resource', params, AddonModResourceProvider.COMPONENT, id, siteId); } } diff --git a/src/addon/mod/scorm/providers/scorm.ts b/src/addon/mod/scorm/providers/scorm.ts index 201bdeaee..0ee62a7f7 100644 --- a/src/addon/mod/scorm/providers/scorm.ts +++ b/src/addon/mod/scorm/providers/scorm.ts @@ -26,6 +26,7 @@ import { CoreUtilsProvider } from '@providers/utils/utils'; import { AddonModScormOfflineProvider } from './scorm-offline'; import { CoreSiteWSPreSets } from '@classes/site'; import { CoreConstants } from '@core/constants'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; /** * Result of getAttemptCount. @@ -113,7 +114,7 @@ export class AddonModScormProvider { private wsProvider: CoreWSProvider, private textUtils: CoreTextUtilsProvider, private utils: CoreUtilsProvider, private filepoolProvider: CoreFilepoolProvider, private scormOfflineProvider: AddonModScormOfflineProvider, private timeUtils: CoreTimeUtilsProvider, private syncProvider: CoreSyncProvider, - private eventsProvider: CoreEventsProvider) { + private eventsProvider: CoreEventsProvider, private logHelper: CoreCourseLogHelperProvider) { this.logger = logger.getInstance('AddonModScormProvider'); } @@ -1446,18 +1447,12 @@ export class AddonModScormProvider { * @return {Promise} Promise resolved when the WS call is successful. */ logView(id: number, siteId?: string): Promise { - return this.sitesProvider.getSite(siteId).then((site) => { - const params = { - scormid: id - }; + const params = { + scormid: id + }; - return site.write('mod_scorm_view_scorm', params).then((response) => { - if (!response || !response.status) { - return Promise.reject(null); - } - }); - }); - } + return this.logHelper.log('mod_scorm_view_scorm', params, AddonModScormProvider.COMPONENT, id, siteId); +} /** * Saves a SCORM tracking record. diff --git a/src/addon/mod/survey/providers/survey.ts b/src/addon/mod/survey/providers/survey.ts index a7ae0337d..75bd970d0 100644 --- a/src/addon/mod/survey/providers/survey.ts +++ b/src/addon/mod/survey/providers/survey.ts @@ -18,6 +18,7 @@ import { CoreSitesProvider } from '@providers/sites'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreAppProvider } from '@providers/app'; import { CoreFilepoolProvider } from '@providers/filepool'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { AddonModSurveyOfflineProvider } from './offline'; /** @@ -32,7 +33,7 @@ export class AddonModSurveyProvider { constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private appProvider: CoreAppProvider, private filepoolProvider: CoreFilepoolProvider, private utils: CoreUtilsProvider, - private surveyOffline: AddonModSurveyOfflineProvider) { + private surveyOffline: AddonModSurveyOfflineProvider, private logHelper: CoreCourseLogHelperProvider) { this.logger = logger.getInstance('AddonModSurveyProvider'); } @@ -197,14 +198,15 @@ export class AddonModSurveyProvider { * Report the survey as being viewed. * * @param {number} id Module ID. + * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the WS call is successful. */ - logView(id: number): Promise { + logView(id: number, siteId?: string): Promise { const params = { surveyid: id }; - return this.sitesProvider.getCurrentSite().write('mod_survey_view_survey', params); + return this.logHelper.log('mod_survey_view_survey', params, AddonModSurveyProvider.COMPONENT, id, siteId); } /** diff --git a/src/addon/mod/url/providers/url.ts b/src/addon/mod/url/providers/url.ts index c1642b6ca..e7e02b85a 100644 --- a/src/addon/mod/url/providers/url.ts +++ b/src/addon/mod/url/providers/url.ts @@ -18,6 +18,7 @@ import { CoreSitesProvider } from '@providers/sites'; import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreConstants } from '@core/constants'; /** @@ -31,7 +32,8 @@ export class AddonModUrlProvider { protected logger; constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private courseProvider: CoreCourseProvider, - private utils: CoreUtilsProvider, private mimeUtils: CoreMimetypeUtilsProvider) { + private utils: CoreUtilsProvider, private mimeUtils: CoreMimetypeUtilsProvider, + private logHelper: CoreCourseLogHelperProvider) { this.logger = logger.getInstance('AddonModUrlProvider'); } @@ -215,13 +217,14 @@ export class AddonModUrlProvider { * Report the url as being viewed. * * @param {number} id Module ID. + * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the WS call is successful. */ - logView(id: number): Promise { + logView(id: number, siteId?: string): Promise { const params = { urlid: id }; - return this.sitesProvider.getCurrentSite().write('mod_url_view_url', params); + return this.logHelper.log('mod_url_view_url', params, AddonModUrlProvider.COMPONENT, id, siteId); } } diff --git a/src/addon/mod/wiki/components/index/index.ts b/src/addon/mod/wiki/components/index/index.ts index b154c7f4e..ee4362456 100644 --- a/src/addon/mod/wiki/components/index/index.ts +++ b/src/addon/mod/wiki/components/index/index.ts @@ -110,7 +110,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp // Ignore errors. }); } else { - this.wikiProvider.logPageView(this.pageId).catch(() => { + this.wikiProvider.logPageView(this.pageId, this.wikiId).catch(() => { // Ignore errors. }); } @@ -341,7 +341,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp this.currentPage = data.pageId; this.showLoadingAndFetch(true, false).then(() => { - this.wikiProvider.logPageView(this.currentPage).catch(() => { + this.wikiProvider.logPageView(this.currentPage, this.wikiId).catch(() => { // Ignore errors. }); }); diff --git a/src/addon/mod/wiki/providers/wiki.ts b/src/addon/mod/wiki/providers/wiki.ts index ce98a5dff..42dcd8b1c 100644 --- a/src/addon/mod/wiki/providers/wiki.ts +++ b/src/addon/mod/wiki/providers/wiki.ts @@ -20,6 +20,7 @@ import { CoreSitesProvider } from '@providers/sites'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreAppProvider } from '@providers/app'; import { CoreFilepoolProvider } from '@providers/filepool'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { AddonModWikiOfflineProvider } from './wiki-offline'; import { CoreSiteWSPreSets } from '@classes/site'; @@ -70,7 +71,8 @@ export class AddonModWikiProvider { constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private appProvider: CoreAppProvider, private filepoolProvider: CoreFilepoolProvider, private utils: CoreUtilsProvider, private translate: TranslateService, - private wikiOffline: AddonModWikiOfflineProvider, eventsProvider: CoreEventsProvider) { + private wikiOffline: AddonModWikiOfflineProvider, eventsProvider: CoreEventsProvider, + private logHelper: CoreCourseLogHelperProvider) { this.logger = logger.getInstance('AddonModWikiProvider'); // Clear subwiki lists cache on logout. @@ -651,18 +653,17 @@ export class AddonModWikiProvider { /** * Report a wiki page as being viewed. * - * @param {string} id Page ID. + * @param {number} id Page ID. + * @param {number} wikiId Wiki ID. * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the WS call is successful. */ - logPageView(id: number, siteId?: string): Promise { - return this.sitesProvider.getSite(siteId).then((site) => { - const params = { - pageid: id - }; + logPageView(id: number, wikiId: number, siteId?: string): Promise { + const params = { + pageid: id + }; - return site.write('mod_wiki_view_page', params); - }); + return this.logHelper.log('mod_wiki_view_page', params, AddonModWikiProvider.COMPONENT, wikiId, siteId); } /** @@ -673,13 +674,11 @@ export class AddonModWikiProvider { * @return {Promise} Promise resolved when the WS call is successful. */ logView(id: number, siteId?: string): Promise { - return this.sitesProvider.getSite(siteId).then((site) => { - const params = { - wikiid: id - }; + const params = { + wikiid: id + }; - return site.write('mod_wiki_view_wiki', params); - }); + return this.logHelper.log('mod_wiki_view_wiki', params, AddonModWikiProvider.COMPONENT, id, siteId); } /** diff --git a/src/addon/mod/workshop/pages/submission/submission.ts b/src/addon/mod/workshop/pages/submission/submission.ts index b058e1d05..e4cb2cd5f 100644 --- a/src/addon/mod/workshop/pages/submission/submission.ts +++ b/src/addon/mod/workshop/pages/submission/submission.ts @@ -130,7 +130,7 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy { */ ngOnInit(): void { this.fetchSubmissionData().then(() => { - this.workshopProvider.logViewSubmission(this.submissionId).then(() => { + this.workshopProvider.logViewSubmission(this.submissionId, this.workshopId).then(() => { this.courseProvider.checkModuleCompletion(this.courseId, this.module.completiondata); }).catch(() => { // Ignore errors. diff --git a/src/addon/mod/workshop/providers/workshop.ts b/src/addon/mod/workshop/providers/workshop.ts index 8c22710ef..5d0e3165e 100644 --- a/src/addon/mod/workshop/providers/workshop.ts +++ b/src/addon/mod/workshop/providers/workshop.ts @@ -17,6 +17,7 @@ import { CoreAppProvider } from '@providers/app'; import { CoreFilepoolProvider } from '@providers/filepool'; import { CoreSitesProvider } from '@providers/sites'; import { CoreUtilsProvider } from '@providers/utils/utils'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { AddonModWorkshopOfflineProvider } from './offline'; /** @@ -49,7 +50,8 @@ export class AddonModWorkshopProvider { private filepoolProvider: CoreFilepoolProvider, private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider, - private workshopOffline: AddonModWorkshopOfflineProvider) {} + private workshopOffline: AddonModWorkshopOfflineProvider, + private logHelper: CoreCourseLogHelperProvider) {} /** * Get cache key for workshop data WS calls. @@ -1359,34 +1361,31 @@ export class AddonModWorkshopProvider { /** * Report the workshop as being viewed. * - * @param {string} id Workshop ID. + * @param {number} id Workshop ID. * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the WS call is successful. */ logView(id: number, siteId?: string): Promise { - return this.sitesProvider.getSite(siteId).then((site) => { - const params = { - workshopid: id - }; + const params = { + workshopid: id + }; - return site.write('mod_workshop_view_workshop', params); - }); + return this.logHelper.log('mod_workshop_view_workshop', params, AddonModWorkshopProvider.COMPONENT, id, siteId); } /** * Report the workshop submission as being viewed. * - * @param {string} id Submission ID. + * @param {number} id Submission ID. + * @param {number} workshopId Workshop ID. * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the WS call is successful. */ - logViewSubmission(id: number, siteId?: string): Promise { - return this.sitesProvider.getSite(siteId).then((site) => { - const params = { - submissionid: id - }; + logViewSubmission(id: number, workshopId: number, siteId?: string): Promise { + const params = { + submissionid: id + }; - return site.write('mod_workshop_view_submission', params); - }); + return this.logHelper.log('mod_workshop_view_submission', params, AddonModWorkshopProvider.COMPONENT, workshopId, siteId); } } diff --git a/src/core/course/course.module.ts b/src/core/course/course.module.ts index e6e01a772..5293eda63 100644 --- a/src/core/course/course.module.ts +++ b/src/core/course/course.module.ts @@ -18,6 +18,7 @@ import { CoreCronDelegate } from '@providers/cron'; import { CoreEventsProvider } from '@providers/events'; import { CoreCourseProvider } from './providers/course'; import { CoreCourseHelperProvider } from './providers/helper'; +import { CoreCourseLogHelperProvider } from './providers/log-helper'; import { CoreCourseFormatDelegate } from './providers/format-delegate'; import { CoreCourseModuleDelegate } from './providers/module-delegate'; import { CoreCourseOfflineProvider } from './providers/course-offline'; @@ -37,6 +38,7 @@ import { CoreCourseLogCronHandler } from './providers/log-cron-handler'; export const CORE_COURSE_PROVIDERS: any[] = [ CoreCourseProvider, CoreCourseHelperProvider, + CoreCourseLogHelperProvider, CoreCourseFormatDelegate, CoreCourseModuleDelegate, CoreCourseModulePrefetchDelegate, @@ -56,6 +58,7 @@ export const CORE_COURSE_PROVIDERS: any[] = [ providers: [ CoreCourseProvider, CoreCourseHelperProvider, + CoreCourseLogHelperProvider, CoreCourseFormatDelegate, CoreCourseModuleDelegate, CoreCourseModulePrefetchDelegate, diff --git a/src/core/course/providers/log-helper.ts b/src/core/course/providers/log-helper.ts new file mode 100644 index 000000000..0b5a01f50 --- /dev/null +++ b/src/core/course/providers/log-helper.ts @@ -0,0 +1,225 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreTextUtilsProvider } from '@providers/utils/text'; +import { CoreTimeUtilsProvider } from '@providers/utils/time'; +import { CoreUtilsProvider } from '@providers/utils/utils'; +import { CoreAppProvider } from '@providers/app'; + +/** + * Helper to manage logging to Moodle. + */ +@Injectable() +export class CoreCourseLogHelperProvider { + + // Variables for database. + static ACTIVITY_LOG_TABLE = 'course_activity_log'; + protected tablesSchema = [ + { + name: CoreCourseLogHelperProvider.ACTIVITY_LOG_TABLE, + columns: [ + { + name: 'component', + type: 'TEXT' + }, + { + name: 'componentid', + type: 'INTEGER' + }, + { + name: 'ws', + type: 'TEXT' + }, + { + name: 'data', + type: 'TEXT' + }, + { + name: 'time', + type: 'INTEGER' + } + ], + primaryKeys: ['component', 'componentid', 'ws', 'time'] + } + ]; + + constructor(protected sitesProvider: CoreSitesProvider, protected timeUtils: CoreTimeUtilsProvider, + protected textUtils: CoreTextUtilsProvider, protected utils: CoreUtilsProvider, + protected appProvider: CoreAppProvider) { + this.sitesProvider.createTablesFromSchema(this.tablesSchema); + } + + /** + * Delete the offline saved activity logs. + * + * @param {string} component Component name. + * @param {number} componentId Component ID. + * @param {string} siteId Site ID. If not defined, current site. + * @return {Promise} Promise resolved when deleted, rejected if failure. + */ + protected deleteLogs(component: string, componentId: number, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + + return site.getDb().deleteRecords(CoreCourseLogHelperProvider.ACTIVITY_LOG_TABLE, + {component: component, componentid: componentId}); + }); + } + + /** + * Delete the offline saved activity logs using call data. + * + * @param {string} ws WS name. + * @param {any} data Data to send to the WS. + * @param {string} siteId Site ID. If not defined, current site. + * @return {Promise} Promise resolved when deleted, rejected if failure. + */ + protected deleteWSLogs(ws: string, data: any, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + + return site.getDb().deleteRecords(CoreCourseLogHelperProvider.ACTIVITY_LOG_TABLE, + {ws: ws, data: JSON.stringify(data)}); + }); + } + + /** + * Get the offline saved activity logs. + * + * @param {string} component Component name. + * @param {number} componentId Component ID. + * @param {string} siteId Site ID. If not defined, current site. + * @return {Promise} Promise resolved with the list of offline logs. + */ + protected getLogs(component: string, componentId: number, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + + return site.getDb().getRecords(CoreCourseLogHelperProvider.ACTIVITY_LOG_TABLE, + {component: component, componentid: componentId}); + }); + } + + /** + * Perform log online. Data will be saved offline for syncing. + * + * @param {string} ws WS name. + * @param {any} data Data to send to the WS. + * @param {string} component Component name. + * @param {number} componentId Component ID. + * @param {string} siteId Site ID. If not defined, current site. + * @return {Promise} Promise resolved when done. + */ + log(ws: string, data: any, component: string, componentId: number, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + if (!this.appProvider.isOnline()) { + // App is offline, store the action. + return this.storeOffline(ws, data, component, componentId, site.getId()); + } + + return this.logOnline(ws, data, site.getId()).catch((error) => { + if (this.utils.isWebServiceError(error)) { + // The WebService has thrown an error, this means that responses cannot be submitted. + return Promise.reject(error); + } + + // Couldn't connect to server, store in offline. + return this.storeOffline(ws, data, component, componentId, site.getId()); + }); + }); + } + + /** + * Perform the log online. + * + * @param {string} ws WS name. + * @param {any} data Data to send to the WS. + * @param {string} siteId Site ID. If not defined, current site. + * @return {Promise} Promise resolved when log is successfully submitted. Rejected with object containing + * the error message (if any) and a boolean indicating if the error was returned by WS. + */ + protected logOnline(ws: string, data: any, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + return site.write(ws, data).then((response) => { + if (!response.status) { + return Promise.reject(this.utils.createFakeWSError('')); + } + + // Remove all the logs performed. + // TODO: Remove this lines when time is accepted in logs. + return this.deleteWSLogs(ws, data); + }).catch((error) => { + return Promise.reject(this.utils.createFakeWSError(error)); + }); + }); + } + + /** + * Save activity log for offline sync. + * + * @param {string} ws WS name. + * @param {any} data Data to send to the WS. + * @param {string} component Component name. + * @param {number} componentId Component ID. + * @param {string} siteId Site ID. If not defined, current site. + * @return {Promise} Resolved with the inserted rowId field. + */ + protected storeOffline(ws: string, data: any, component: string, componentId: number, siteId?: string): + Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + const log = { + ws: ws, + data: JSON.stringify(data), + component: component, + componentid: componentId, + time: this.timeUtils.timestamp() + }; + + return site.getDb().insertRecord(CoreCourseLogHelperProvider.ACTIVITY_LOG_TABLE, log); + }); + } + + /** + * Sync the offline saved activity logs. + * + * @param {string} component Component name. + * @param {number} componentId Component ID. + * @param {string} siteId Site ID. If not defined, current site. + * @return {Promise} Promise resolved when done. + */ + syncIfNeeded(component: string, componentId: number, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + const siteId = site.getId(); + + return this.getLogs(component, componentId, siteId).then((logs) => { + const done = []; + + // TODO: When time is accepted on log, do not discard same logs. + return Promise.all(logs.map((log) => { + // Just perform unique syncs. + const found = done.find((doneLog) => { + return log.ws == doneLog.ws && log.data == doneLog.data; + }); + + if (found) { + return Promise.resolve(); + } + + done.push(log); + + return this.logOnline(log.ws, this.textUtils.parseJSON(log.data), siteId); + })); + }); + }); + } +} From 91435072844637ba7371ddc5ea12f937a91859e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 7 Feb 2019 10:12:54 +0100 Subject: [PATCH 060/191] MOBILE-1979 sync: Sync activity logs --- src/addon/mod/assign/providers/assign-sync.ts | 7 +- src/addon/mod/choice/providers/sync.ts | 14 ++- src/addon/mod/data/providers/sync.ts | 14 ++- src/addon/mod/feedback/providers/sync.ts | 15 ++- .../mod/forum/pages/discussion/discussion.ts | 2 +- src/addon/mod/forum/providers/sync.ts | 15 ++- src/addon/mod/glossary/providers/sync.ts | 15 ++- src/addon/mod/lesson/providers/lesson-sync.ts | 11 +- src/addon/mod/quiz/pages/player/player.ts | 3 +- src/addon/mod/quiz/providers/quiz-sync.ts | 14 ++- src/addon/mod/quiz/providers/quiz.ts | 30 ++--- src/addon/mod/scorm/providers/scorm-sync.ts | 11 +- src/addon/mod/survey/providers/sync.ts | 14 ++- src/addon/mod/wiki/components/index/index.ts | 4 +- src/addon/mod/wiki/providers/wiki-sync.ts | 13 +- src/addon/mod/workshop/providers/sync.ts | 7 +- src/core/course/providers/log-helper.ts | 118 ++++++++++++++---- .../course/providers/sync-cron-handler.ts | 12 +- 18 files changed, 228 insertions(+), 91 deletions(-) diff --git a/src/addon/mod/assign/providers/assign-sync.ts b/src/addon/mod/assign/providers/assign-sync.ts index 96627a1cc..6283bd878 100644 --- a/src/addon/mod/assign/providers/assign-sync.ts +++ b/src/addon/mod/assign/providers/assign-sync.ts @@ -23,6 +23,7 @@ import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreGradesHelperProvider } from '@core/grades/providers/helper'; import { CoreSyncBaseProvider } from '@classes/base-sync'; import { AddonModAssignProvider } from './assign'; @@ -61,7 +62,8 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider { private courseProvider: CoreCourseProvider, private eventsProvider: CoreEventsProvider, private assignProvider: AddonModAssignProvider, private assignOfflineProvider: AddonModAssignOfflineProvider, private utils: CoreUtilsProvider, private submissionDelegate: AddonModAssignSubmissionDelegate, - private gradesHelper: CoreGradesHelperProvider, timeUtils: CoreTimeUtilsProvider) { + private gradesHelper: CoreGradesHelperProvider, timeUtils: CoreTimeUtilsProvider, + private logHelper: CoreCourseLogHelperProvider) { super('AddonModAssignSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, timeUtils); @@ -202,6 +204,9 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider { return []; })); + // Sync offline logs. + promises.push(this.logHelper.syncIfNeeded(AddonModAssignProvider.COMPONENT, assignId, siteId)); + syncPromise = Promise.all(promises).then((results) => { const submissions = results[0], grades = results[1]; diff --git a/src/addon/mod/choice/providers/sync.ts b/src/addon/mod/choice/providers/sync.ts index 01635c5d4..b260f1ba7 100644 --- a/src/addon/mod/choice/providers/sync.ts +++ b/src/addon/mod/choice/providers/sync.ts @@ -25,6 +25,7 @@ import { AddonModChoiceProvider } from './choice'; import { CoreEventsProvider } from '@providers/events'; import { TranslateService } from '@ngx-translate/core'; import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreSyncProvider } from '@providers/sync'; /** @@ -40,7 +41,8 @@ export class AddonModChoiceSyncProvider extends CoreSyncBaseProvider { protected appProvider: CoreAppProvider, private choiceOffline: AddonModChoiceOfflineProvider, private eventsProvider: CoreEventsProvider, private choiceProvider: AddonModChoiceProvider, translate: TranslateService, private utils: CoreUtilsProvider, protected textUtils: CoreTextUtilsProvider, - courseProvider: CoreCourseProvider, syncProvider: CoreSyncProvider, timeUtils: CoreTimeUtilsProvider) { + courseProvider: CoreCourseProvider, syncProvider: CoreSyncProvider, timeUtils: CoreTimeUtilsProvider, + private logHelper: CoreCourseLogHelperProvider) { super('AddonModChoiceSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, timeUtils); @@ -137,10 +139,12 @@ export class AddonModChoiceSyncProvider extends CoreSyncBaseProvider { updated: false }; - // Get offline responses to be sent. - const syncPromise = this.choiceOffline.getResponse(choiceId, siteId, userId).catch(() => { - // No offline data found, return empty object. - return {}; + // Sync offline logs. + const syncPromise = this.logHelper.syncIfNeeded(AddonModChoiceProvider.COMPONENT, choiceId, siteId).finally(() => { + return this.choiceOffline.getResponse(choiceId, siteId, userId).catch(() => { + // No offline data found, return empty object. + return {}; + }); }).then((data) => { if (!data.choiceid) { // Nothing to sync. diff --git a/src/addon/mod/data/providers/sync.ts b/src/addon/mod/data/providers/sync.ts index 6e2a90128..22485fa93 100644 --- a/src/addon/mod/data/providers/sync.ts +++ b/src/addon/mod/data/providers/sync.ts @@ -26,6 +26,7 @@ import { AddonModDataHelperProvider } from './helper'; import { CoreEventsProvider } from '@providers/events'; import { TranslateService } from '@ngx-translate/core'; import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreSyncProvider } from '@providers/sync'; /** @@ -42,7 +43,7 @@ export class AddonModDataSyncProvider extends CoreSyncBaseProvider { private eventsProvider: CoreEventsProvider, private dataProvider: AddonModDataProvider, protected translate: TranslateService, private utils: CoreUtilsProvider, courseProvider: CoreCourseProvider, syncProvider: CoreSyncProvider, protected textUtils: CoreTextUtilsProvider, timeUtils: CoreTimeUtilsProvider, - private dataHelper: AddonModDataHelperProvider) { + private dataHelper: AddonModDataHelperProvider, private logHelper: CoreCourseLogHelperProvider) { super('AddonModDataSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, timeUtils); @@ -149,10 +150,13 @@ export class AddonModDataSyncProvider extends CoreSyncBaseProvider { updated: false }; - // Get answers to be sent. - const syncPromise = this.dataOffline.getDatabaseEntries(dataId, siteId).catch(() => { - // No offline data found, return empty object. - return []; + // Sync offline logs. + const syncPromise = this.logHelper.syncIfNeeded(AddonModDataProvider.COMPONENT, dataId, siteId).finally(() => { + // Get answers to be sent. + return this.dataOffline.getDatabaseEntries(dataId, siteId).catch(() => { + // No offline data found, return empty object. + return []; + }); }).then((offlineActions) => { if (!offlineActions.length) { // Nothing to sync. diff --git a/src/addon/mod/feedback/providers/sync.ts b/src/addon/mod/feedback/providers/sync.ts index 4aca43570..e9154d724 100644 --- a/src/addon/mod/feedback/providers/sync.ts +++ b/src/addon/mod/feedback/providers/sync.ts @@ -26,6 +26,7 @@ import { CoreEventsProvider } from '@providers/events'; import { TranslateService } from '@ngx-translate/core'; import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreSyncProvider } from '@providers/sync'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; /** * Service to sync feedbacks. @@ -40,7 +41,8 @@ export class AddonModFeedbackSyncProvider extends CoreSyncBaseProvider { protected appProvider: CoreAppProvider, private feedbackOffline: AddonModFeedbackOfflineProvider, private eventsProvider: CoreEventsProvider, private feedbackProvider: AddonModFeedbackProvider, protected translate: TranslateService, private utils: CoreUtilsProvider, protected textUtils: CoreTextUtilsProvider, - courseProvider: CoreCourseProvider, syncProvider: CoreSyncProvider, timeUtils: CoreTimeUtilsProvider) { + courseProvider: CoreCourseProvider, syncProvider: CoreSyncProvider, timeUtils: CoreTimeUtilsProvider, + private logHelper: CoreCourseLogHelperProvider) { super('AddonModFeedbackSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, timeUtils); @@ -144,10 +146,13 @@ export class AddonModFeedbackSyncProvider extends CoreSyncBaseProvider { this.logger.debug(`Try to sync feedback '${feedbackId}' in site ${siteId}'`); - // Get offline responses to be sent. - const syncPromise = this.feedbackOffline.getFeedbackResponses(feedbackId, siteId).catch(() => { - // No offline data found, return empty array. - return []; + // Sync offline logs. + const syncPromise = this.logHelper.syncIfNeeded(AddonModFeedbackProvider.COMPONENT, feedbackId, siteId).finally(() => { + // Get offline responses to be sent. + return this.feedbackOffline.getFeedbackResponses(feedbackId, siteId).catch(() => { + // No offline data found, return empty array. + return []; + }); }).then((responses) => { if (!responses.length) { // Nothing to sync. diff --git a/src/addon/mod/forum/pages/discussion/discussion.ts b/src/addon/mod/forum/pages/discussion/discussion.ts index 7879e8470..ad3da6f2f 100644 --- a/src/addon/mod/forum/pages/discussion/discussion.ts +++ b/src/addon/mod/forum/pages/discussion/discussion.ts @@ -299,7 +299,7 @@ export class AddonModForumDiscussionPage implements OnDestroy { if (forceMarkAsRead || (hasUnreadPosts && this.trackPosts)) { // // Add log in Moodle and mark unread posts as readed. - this.forumProvider.logDiscussionView(this.discussionId, this.forumId).catch(() => { + this.forumProvider.logDiscussionView(this.discussionId, this.forumId || -1).catch(() => { // Ignore errors. }).finally(() => { // Trigger mark read posts. diff --git a/src/addon/mod/forum/providers/sync.ts b/src/addon/mod/forum/providers/sync.ts index 76fc0c4ae..ddb33ab61 100644 --- a/src/addon/mod/forum/providers/sync.ts +++ b/src/addon/mod/forum/providers/sync.ts @@ -16,6 +16,7 @@ import { Injectable } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { CoreSyncBaseProvider } from '@classes/base-sync'; import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader'; import { CoreAppProvider } from '@providers/app'; import { CoreLoggerProvider } from '@providers/logger'; @@ -53,7 +54,8 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider { private utils: CoreUtilsProvider, private forumProvider: AddonModForumProvider, private forumHelper: AddonModForumHelperProvider, - private forumOffline: AddonModForumOfflineProvider) { + private forumOffline: AddonModForumOfflineProvider, + private logHelper: CoreCourseLogHelperProvider) { super('AddonModForumSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, timeUtils); @@ -189,10 +191,13 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider { updated: false }; - // Get offline responses to be sent. - const syncPromise = this.forumOffline.getNewDiscussions(forumId, siteId, userId).catch(() => { - // No offline data found, return empty object. - return []; + // Sync offline logs. + const syncPromise = this.logHelper.syncIfNeeded(AddonModForumProvider.COMPONENT, forumId, siteId).finally(() => { + // Get offline responses to be sent. + return this.forumOffline.getNewDiscussions(forumId, siteId, userId).catch(() => { + // No offline data found, return empty object. + return []; + }); }).then((discussions) => { if (!discussions.length) { // Nothing to sync. diff --git a/src/addon/mod/glossary/providers/sync.ts b/src/addon/mod/glossary/providers/sync.ts index ab748ab9d..af76e67d8 100644 --- a/src/addon/mod/glossary/providers/sync.ts +++ b/src/addon/mod/glossary/providers/sync.ts @@ -16,6 +16,7 @@ import { Injectable } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { CoreSyncBaseProvider } from '@classes/base-sync'; import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader'; import { CoreAppProvider } from '@providers/app'; import { CoreLoggerProvider } from '@providers/logger'; @@ -52,7 +53,8 @@ export class AddonModGlossarySyncProvider extends CoreSyncBaseProvider { private utils: CoreUtilsProvider, private glossaryProvider: AddonModGlossaryProvider, private glossaryHelper: AddonModGlossaryHelperProvider, - private glossaryOffline: AddonModGlossaryOfflineProvider) { + private glossaryOffline: AddonModGlossaryOfflineProvider, + private logHelper: CoreCourseLogHelperProvider) { super('AddonModGlossarySyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, timeUtils); @@ -160,10 +162,13 @@ export class AddonModGlossarySyncProvider extends CoreSyncBaseProvider { updated: false }; - // Get offline responses to be sent. - const syncPromise = this.glossaryOffline.getGlossaryNewEntries(glossaryId, siteId, userId).catch(() => { - // No offline data found, return empty object. - return []; + // Sync offline logs. + const syncPromise = this.logHelper.syncIfNeeded(AddonModGlossaryProvider.COMPONENT, glossaryId, siteId).finally(() => { + // Get offline responses to be sent. + return this.glossaryOffline.getGlossaryNewEntries(glossaryId, siteId, userId).catch(() => { + // No offline data found, return empty object. + return []; + }); }).then((entries) => { if (!entries.length) { // Nothing to sync. diff --git a/src/addon/mod/lesson/providers/lesson-sync.ts b/src/addon/mod/lesson/providers/lesson-sync.ts index de7e2ef7c..69ce789f4 100644 --- a/src/addon/mod/lesson/providers/lesson-sync.ts +++ b/src/addon/mod/lesson/providers/lesson-sync.ts @@ -24,6 +24,7 @@ import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreUrlUtilsProvider } from '@providers/utils/url'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreSyncBaseProvider } from '@classes/base-sync'; import { AddonModLessonProvider } from './lesson'; import { AddonModLessonOfflineProvider } from './lesson-offline'; @@ -86,7 +87,8 @@ export class AddonModLessonSyncProvider extends CoreSyncBaseProvider { courseProvider: CoreCourseProvider, private eventsProvider: CoreEventsProvider, private lessonProvider: AddonModLessonProvider, private lessonOfflineProvider: AddonModLessonOfflineProvider, private prefetchHandler: AddonModLessonPrefetchHandler, timeUtils: CoreTimeUtilsProvider, - private utils: CoreUtilsProvider, private urlUtils: CoreUrlUtilsProvider) { + private utils: CoreUtilsProvider, private urlUtils: CoreUrlUtilsProvider, + private logHelper: CoreCourseLogHelperProvider) { super('AddonModLessonSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, timeUtils); @@ -263,8 +265,11 @@ export class AddonModLessonSyncProvider extends CoreSyncBaseProvider { this.logger.debug('Try to sync lesson ' + lessonId + ' in site ' + siteId); - // Try to synchronize the attempts first. - syncPromise = this.lessonOfflineProvider.getLessonAttempts(lessonId, siteId).then((attempts) => { + // Sync offline logs. + syncPromise = this.logHelper.syncIfNeeded(AddonModLessonProvider.COMPONENT, lessonId, siteId).finally(() => { + // Try to synchronize the attempts first. + return this.lessonOfflineProvider.getLessonAttempts(lessonId, siteId); + }).then((attempts) => { if (!attempts.length) { return; } else if (!this.appProvider.isOnline()) { diff --git a/src/addon/mod/quiz/pages/player/player.ts b/src/addon/mod/quiz/pages/player/player.ts index feb27bb7f..669445fbc 100644 --- a/src/addon/mod/quiz/pages/player/player.ts +++ b/src/addon/mod/quiz/pages/player/player.ts @@ -428,8 +428,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { }); // Mark the page as viewed. We'll ignore errors in this call. - this.quizProvider.logViewAttempt(this.attempt.id, this.quizId, page, this.preflightData, this.offline) - .catch((error) => { + this.quizProvider.logViewAttempt(this.attempt.id, page, this.preflightData, this.offline).catch((error) => { // Ignore errors. }); diff --git a/src/addon/mod/quiz/providers/quiz-sync.ts b/src/addon/mod/quiz/providers/quiz-sync.ts index 4d00bf7fa..a7ebbdbec 100644 --- a/src/addon/mod/quiz/providers/quiz-sync.ts +++ b/src/addon/mod/quiz/providers/quiz-sync.ts @@ -22,6 +22,7 @@ import { CoreSyncProvider } from '@providers/sync'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreQuestionProvider } from '@core/question/providers/question'; import { CoreQuestionDelegate } from '@core/question/providers/delegate'; import { CoreSyncBaseProvider } from '@classes/base-sync'; @@ -61,7 +62,7 @@ export class AddonModQuizSyncProvider extends CoreSyncBaseProvider { courseProvider: CoreCourseProvider, private eventsProvider: CoreEventsProvider, timeUtils: CoreTimeUtilsProvider, private quizProvider: AddonModQuizProvider, private quizOfflineProvider: AddonModQuizOfflineProvider, private prefetchHandler: AddonModQuizPrefetchHandler, private questionProvider: CoreQuestionProvider, - private questionDelegate: CoreQuestionDelegate) { + private questionDelegate: CoreQuestionDelegate, private logHelper: CoreCourseLogHelperProvider) { super('AddonModQuizSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, timeUtils); @@ -259,8 +260,11 @@ export class AddonModQuizSyncProvider extends CoreSyncBaseProvider { this.logger.debug('Try to sync quiz ' + quiz.id + ' in site ' + siteId); - // Get all the offline attempts for the quiz. - syncPromise = this.quizOfflineProvider.getQuizAttempts(quiz.id, siteId).then((attempts) => { + // Sync offline logs. + syncPromise = this.logHelper.syncIfNeeded(AddonModQuizProvider.COMPONENT, quiz.id, siteId).finally(() => { + // Get all the offline attempts for the quiz. + return this.quizOfflineProvider.getQuizAttempts(quiz.id, siteId); + }).then((attempts) => { // Should return 0 or 1 attempt. if (!attempts.length) { return this.finishSync(siteId, quiz, courseId, warnings); @@ -341,8 +345,8 @@ export class AddonModQuizSyncProvider extends CoreSyncBaseProvider { // Answers sent, now set the current page if the attempt isn't finished. if (!finish) { - return this.quizProvider.logViewAttempt(onlineAttempt.id, quiz.id, offlineAttempt.currentpage, - preflightData, false).catch(() => { + return this.quizProvider.logViewAttempt(onlineAttempt.id, offlineAttempt.currentpage, preflightData, + false).catch(() => { // Ignore errors. }); } diff --git a/src/addon/mod/quiz/providers/quiz.ts b/src/addon/mod/quiz/providers/quiz.ts index 1777bafdf..551675b22 100644 --- a/src/addon/mod/quiz/providers/quiz.ts +++ b/src/addon/mod/quiz/providers/quiz.ts @@ -1518,31 +1518,31 @@ export class AddonModQuizProvider { } /** - * Report an attempt as being viewed. + * Report an attempt as being viewed. It did not store logs offline because order of the log is important. * * @param {number} attemptId Attempt ID. * @param {number} [page=0] Page number. * @param {any} [preflightData] Preflight required data (like password). * @param {boolean} [offline] Whether attempt is offline. - * @param {number} quizId Quiz ID. * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the WS call is successful. */ - logViewAttempt(attemptId: number, quizId: number, page: number = 0, preflightData: any = {}, offline?: boolean, - siteId?: string): Promise { - const params = { - attemptid: attemptId, - page: page, - preflightdata: this.utils.objectToArrayOfObjects(preflightData, 'name', 'value', true) - }, - promises = []; + logViewAttempt(attemptId: number, page: number = 0, preflightData: any = {}, offline?: boolean, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + const params = { + attemptid: attemptId, + page: page, + preflightdata: this.utils.objectToArrayOfObjects(preflightData, 'name', 'value', true) + }, + promises = []; - promises.push(this.logHelper.log('mod_quiz_view_attempt', params, AddonModQuizProvider.COMPONENT, quizId, siteId)); - if (offline) { - promises.push(this.quizOfflineProvider.setAttemptCurrentPage(attemptId, page)); - } + promises.push(site.write('mod_quiz_view_attempt', params)); + if (offline) { + promises.push(this.quizOfflineProvider.setAttemptCurrentPage(attemptId, page, site.getId())); + } - return Promise.all(promises); + return Promise.all(promises); + }); } /** diff --git a/src/addon/mod/scorm/providers/scorm-sync.ts b/src/addon/mod/scorm/providers/scorm-sync.ts index e9b5014fb..1bf3ea4cb 100644 --- a/src/addon/mod/scorm/providers/scorm-sync.ts +++ b/src/addon/mod/scorm/providers/scorm-sync.ts @@ -23,6 +23,7 @@ import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate'; import { CoreSyncBaseProvider } from '@classes/base-sync'; import { AddonModScormProvider, AddonModScormAttemptCountResult } from './scorm'; @@ -67,7 +68,8 @@ export class AddonModScormSyncProvider extends CoreSyncBaseProvider { private eventsProvider: CoreEventsProvider, timeUtils: CoreTimeUtilsProvider, private scormProvider: AddonModScormProvider, private scormOfflineProvider: AddonModScormOfflineProvider, private prefetchHandler: AddonModScormPrefetchHandler, private utils: CoreUtilsProvider, - private prefetchDelegate: CoreCourseModulePrefetchDelegate, private courseProvider: CoreCourseProvider) { + private prefetchDelegate: CoreCourseModulePrefetchDelegate, private courseProvider: CoreCourseProvider, + private logHelper: CoreCourseLogHelperProvider) { super('AddonModScormSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, timeUtils); @@ -646,8 +648,11 @@ export class AddonModScormSyncProvider extends CoreSyncBaseProvider { this.logger.debug('Try to sync SCORM ' + scorm.id + ' in site ' + siteId); - // Get attempts data. We ignore cache for online attempts, so this call will fail if offline or server down. - syncPromise = this.scormProvider.getAttemptCount(scorm.id, false, true, siteId).then((attemptsData) => { + // Sync offline logs. + syncPromise = this.logHelper.syncIfNeeded(AddonModScormProvider.COMPONENT, scorm.id, siteId).finally(() => { + // Get attempts data. We ignore cache for online attempts, so this call will fail if offline or server down. + return this.scormProvider.getAttemptCount(scorm.id, false, true, siteId); + }).then((attemptsData) => { if (!attemptsData.offline || !attemptsData.offline.length) { // Nothing to sync. return this.finishSync(siteId, scorm, warnings, lastOnline, lastOnlineWasFinished); diff --git a/src/addon/mod/survey/providers/sync.ts b/src/addon/mod/survey/providers/sync.ts index 084e05eec..580c24da4 100644 --- a/src/addon/mod/survey/providers/sync.ts +++ b/src/addon/mod/survey/providers/sync.ts @@ -25,6 +25,7 @@ import { AddonModSurveyProvider } from './survey'; import { CoreEventsProvider } from '@providers/events'; import { TranslateService } from '@ngx-translate/core'; import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreSyncProvider } from '@providers/sync'; /** @@ -40,7 +41,7 @@ export class AddonModSurveySyncProvider extends CoreSyncBaseProvider { syncProvider: CoreSyncProvider, textUtils: CoreTextUtilsProvider, translate: TranslateService, courseProvider: CoreCourseProvider, private surveyOffline: AddonModSurveyOfflineProvider, private eventsProvider: CoreEventsProvider, private surveyProvider: AddonModSurveyProvider, - private utils: CoreUtilsProvider, timeUtils: CoreTimeUtilsProvider) { + private utils: CoreUtilsProvider, timeUtils: CoreTimeUtilsProvider, private logHelper: CoreCourseLogHelperProvider) { super('AddonModSurveySyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, timeUtils); @@ -141,10 +142,13 @@ export class AddonModSurveySyncProvider extends CoreSyncBaseProvider { answersSent: false }; - // Get answers to be sent. - const syncPromise = this.surveyOffline.getSurveyData(surveyId, siteId, userId).catch(() => { - // No offline data found, return empty object. - return {}; + // Sync offline logs. + const syncPromise = this.logHelper.syncIfNeeded(AddonModSurveyProvider.COMPONENT, surveyId, siteId).finally(() => { + // Get answers to be sent. + return this.surveyOffline.getSurveyData(surveyId, siteId, userId).catch(() => { + // No offline data found, return empty object. + return {}; + }); }).then((data) => { if (!data.answers || !data.answers.length) { // Nothing to sync. diff --git a/src/addon/mod/wiki/components/index/index.ts b/src/addon/mod/wiki/components/index/index.ts index ee4362456..25bc9af76 100644 --- a/src/addon/mod/wiki/components/index/index.ts +++ b/src/addon/mod/wiki/components/index/index.ts @@ -110,7 +110,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp // Ignore errors. }); } else { - this.wikiProvider.logPageView(this.pageId, this.wikiId).catch(() => { + this.wikiProvider.logPageView(this.pageId, this.wiki.id).catch(() => { // Ignore errors. }); } @@ -341,7 +341,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp this.currentPage = data.pageId; this.showLoadingAndFetch(true, false).then(() => { - this.wikiProvider.logPageView(this.currentPage, this.wikiId).catch(() => { + this.wikiProvider.logPageView(this.currentPage, this.wiki.id).catch(() => { // Ignore errors. }); }); diff --git a/src/addon/mod/wiki/providers/wiki-sync.ts b/src/addon/mod/wiki/providers/wiki-sync.ts index 5a2bdc441..cfd37e1a0 100644 --- a/src/addon/mod/wiki/providers/wiki-sync.ts +++ b/src/addon/mod/wiki/providers/wiki-sync.ts @@ -24,6 +24,7 @@ import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreSyncBaseProvider } from '@classes/base-sync'; import { AddonModWikiProvider } from './wiki'; import { AddonModWikiOfflineProvider } from './wiki-offline'; @@ -104,7 +105,8 @@ export class AddonModWikiSyncProvider extends CoreSyncBaseProvider { syncProvider: CoreSyncProvider, textUtils: CoreTextUtilsProvider, translate: TranslateService, courseProvider: CoreCourseProvider, private eventsProvider: CoreEventsProvider, private wikiProvider: AddonModWikiProvider, private wikiOfflineProvider: AddonModWikiOfflineProvider, - private utils: CoreUtilsProvider, private groupsProvider: CoreGroupsProvider, timeUtils: CoreTimeUtilsProvider) { + private utils: CoreUtilsProvider, private groupsProvider: CoreGroupsProvider, timeUtils: CoreTimeUtilsProvider, + private logHelper: CoreCourseLogHelperProvider) { super('AddonModWikiSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, timeUtils); @@ -336,8 +338,13 @@ export class AddonModWikiSyncProvider extends CoreSyncBaseProvider { syncWiki(wikiId: number, courseId?: number, cmId?: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); - // Sync is done at subwiki level, get all the subwikis. - return this.wikiProvider.getSubwikis(wikiId).then((subwikis) => { + // Sync offline logs. + return this.logHelper.syncIfNeeded(AddonModWikiProvider.COMPONENT, wikiId, siteId).catch(() => { + // Ignore errors. + }).then(() => { + // Sync is done at subwiki level, get all the subwikis. + return this.wikiProvider.getSubwikis(wikiId); + }).then((subwikis) => { const promises = [], result: AddonModWikiSyncWikiResult = { warnings: [], diff --git a/src/addon/mod/workshop/providers/sync.ts b/src/addon/mod/workshop/providers/sync.ts index 9b1fc1c02..74f7619cf 100644 --- a/src/addon/mod/workshop/providers/sync.ts +++ b/src/addon/mod/workshop/providers/sync.ts @@ -24,6 +24,7 @@ import { CoreSyncProvider } from '@providers/sync'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreUtilsProvider } from '@providers/utils/utils'; +import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { AddonModWorkshopProvider } from './workshop'; import { AddonModWorkshopHelperProvider } from './helper'; import { AddonModWorkshopOfflineProvider } from './offline'; @@ -51,7 +52,8 @@ export class AddonModWorkshopSyncProvider extends CoreSyncBaseProvider { private utils: CoreUtilsProvider, private workshopProvider: AddonModWorkshopProvider, private workshopHelper: AddonModWorkshopHelperProvider, - private workshopOffline: AddonModWorkshopOfflineProvider) { + private workshopOffline: AddonModWorkshopOfflineProvider, + private logHelper: CoreCourseLogHelperProvider) { super('AddonModWorkshopSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, timeUtils); @@ -172,6 +174,9 @@ export class AddonModWorkshopSyncProvider extends CoreSyncBaseProvider { return []; })); + // Sync offline logs. + syncPromises.push(this.logHelper.syncIfNeeded(AddonModWorkshopProvider.COMPONENT, workshopId, siteId)); + const result = { warnings: [], updated: false diff --git a/src/core/course/providers/log-helper.ts b/src/core/course/providers/log-helper.ts index 0b5a01f50..24bde0ed5 100644 --- a/src/core/course/providers/log-helper.ts +++ b/src/core/course/providers/log-helper.ts @@ -67,7 +67,7 @@ export class CoreCourseLogHelperProvider { * * @param {string} component Component name. * @param {number} componentId Component ID. - * @param {string} siteId Site ID. If not defined, current site. + * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when deleted, rejected if failure. */ protected deleteLogs(component: string, componentId: number, siteId?: string): Promise { @@ -78,19 +78,49 @@ export class CoreCourseLogHelperProvider { }); } + /** + * Delete a WS based log. + * + * @param {string} component Component name. + * @param {number} componentId Component ID. + * @param {string} ws WS name. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when deleted, rejected if failure. + */ + protected deleteWSLogsByComponent(component: string, componentId: number, ws: string, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + + return site.getDb().deleteRecords(CoreCourseLogHelperProvider.ACTIVITY_LOG_TABLE, + {component: component, componentid: componentId, ws: ws}); + }); + } + /** * Delete the offline saved activity logs using call data. * * @param {string} ws WS name. * @param {any} data Data to send to the WS. - * @param {string} siteId Site ID. If not defined, current site. + * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when deleted, rejected if failure. */ protected deleteWSLogs(ws: string, data: any, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { return site.getDb().deleteRecords(CoreCourseLogHelperProvider.ACTIVITY_LOG_TABLE, - {ws: ws, data: JSON.stringify(data)}); + {ws: ws, data: this.utils.sortAndStringify(data)}); + }); + } + + /** + * Get all the offline saved activity logs. + * + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with the list of offline logs. + */ + protected getAllLogs(siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + + return site.getDb().getAllRecords(CoreCourseLogHelperProvider.ACTIVITY_LOG_TABLE); }); } @@ -99,7 +129,7 @@ export class CoreCourseLogHelperProvider { * * @param {string} component Component name. * @param {number} componentId Component ID. - * @param {string} siteId Site ID. If not defined, current site. + * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with the list of offline logs. */ protected getLogs(component: string, componentId: number, siteId?: string): Promise { @@ -117,7 +147,7 @@ export class CoreCourseLogHelperProvider { * @param {any} data Data to send to the WS. * @param {string} component Component name. * @param {number} componentId Component ID. - * @param {string} siteId Site ID. If not defined, current site. + * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when done. */ log(ws: string, data: any, component: string, componentId: number, siteId?: string): Promise { @@ -142,10 +172,10 @@ export class CoreCourseLogHelperProvider { /** * Perform the log online. * - * @param {string} ws WS name. - * @param {any} data Data to send to the WS. - * @param {string} siteId Site ID. If not defined, current site. - * @return {Promise} Promise resolved when log is successfully submitted. Rejected with object containing + * @param {string} ws WS name. + * @param {any} data Data to send to the WS. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when log is successfully submitted. Rejected with object containing * the error message (if any) and a boolean indicating if the error was returned by WS. */ protected logOnline(ws: string, data: any, siteId?: string): Promise { @@ -158,8 +188,6 @@ export class CoreCourseLogHelperProvider { // Remove all the logs performed. // TODO: Remove this lines when time is accepted in logs. return this.deleteWSLogs(ws, data); - }).catch((error) => { - return Promise.reject(this.utils.createFakeWSError(error)); }); }); } @@ -171,7 +199,7 @@ export class CoreCourseLogHelperProvider { * @param {any} data Data to send to the WS. * @param {string} component Component name. * @param {number} componentId Component ID. - * @param {string} siteId Site ID. If not defined, current site. + * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Resolved with the inserted rowId field. */ protected storeOffline(ws: string, data: any, component: string, componentId: number, siteId?: string): @@ -179,7 +207,7 @@ export class CoreCourseLogHelperProvider { return this.sitesProvider.getSite(siteId).then((site) => { const log = { ws: ws, - data: JSON.stringify(data), + data: this.utils.sortAndStringify(data), component: component, componentid: componentId, time: this.timeUtils.timestamp() @@ -189,12 +217,43 @@ export class CoreCourseLogHelperProvider { }); } + /** + * Sync all the offline saved activity logs. + * + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when done. + */ + syncAll(siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + const siteId = site.getId(); + + return this.getAllLogs(siteId).then((logs) => { + const unique = []; + + // TODO: When time is accepted on log, do not discard same logs. + logs.forEach((log) => { + // Just perform unique syncs. + const found = unique.find((doneLog) => { + return log.component == doneLog.component && log.componentid == doneLog.componentid && + log.ws == doneLog.ws && log.data == doneLog.data; + }); + + if (!found) { + unique.push(log); + } + }); + + return this.syncLogs(unique, siteId); + }); + }); + } + /** * Sync the offline saved activity logs. * * @param {string} component Component name. * @param {number} componentId Component ID. - * @param {string} siteId Site ID. If not defined, current site. + * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when done. */ syncIfNeeded(component: string, componentId: number, siteId?: string): Promise { @@ -202,24 +261,37 @@ export class CoreCourseLogHelperProvider { const siteId = site.getId(); return this.getLogs(component, componentId, siteId).then((logs) => { - const done = []; + const unique = []; // TODO: When time is accepted on log, do not discard same logs. - return Promise.all(logs.map((log) => { + logs.forEach((log) => { // Just perform unique syncs. - const found = done.find((doneLog) => { + const found = unique.find((doneLog) => { return log.ws == doneLog.ws && log.data == doneLog.data; }); - if (found) { - return Promise.resolve(); + if (!found) { + unique.push(log); } + }); - done.push(log); - - return this.logOnline(log.ws, this.textUtils.parseJSON(log.data), siteId); - })); + return this.syncLogs(unique, siteId); }); }); } + + /** + * Sync and delete given logs. + * + * @param {any[]} logs Array of log objects. + * @param {string} siteId Site Id. + * @return {Promise} Promise resolved when done. + */ + protected syncLogs(logs: any[], siteId: string): Promise { + return Promise.all(logs.map((log) => { + return this.logOnline(log.ws, this.textUtils.parseJSON(log.data), siteId).then(() => { + return this.deleteWSLogsByComponent(log.component, log.componentid, log.ws); + }); + })); + } } diff --git a/src/core/course/providers/sync-cron-handler.ts b/src/core/course/providers/sync-cron-handler.ts index f7bd6cdba..4e85dd398 100644 --- a/src/core/course/providers/sync-cron-handler.ts +++ b/src/core/course/providers/sync-cron-handler.ts @@ -15,6 +15,7 @@ import { Injectable } from '@angular/core'; import { CoreCronHandler } from '@providers/cron'; import { CoreCourseSyncProvider } from './sync'; +import { CoreCourseLogHelperProvider } from './log-helper'; /** * Synchronization cron handler. @@ -23,7 +24,7 @@ import { CoreCourseSyncProvider } from './sync'; export class CoreCourseSyncCronHandler implements CoreCronHandler { name = 'CoreCourseSyncCronHandler'; - constructor(private courseSync: CoreCourseSyncProvider) {} + constructor(private courseSync: CoreCourseSyncProvider, private logHelper: CoreCourseLogHelperProvider) {} /** * Execute the process. @@ -33,7 +34,14 @@ export class CoreCourseSyncCronHandler implements CoreCronHandler { * @return {Promise} Promise resolved when done, rejected if failure. */ execute(siteId?: string): Promise { - return this.courseSync.syncAllCourses(siteId); + const promises = []; + // Sync activity logs even if the activity does not have sync handler. + // This will sync all the activity logs even if there's nothing else to sync and also recources. + promises.push(this.logHelper.syncAll(siteId)); + + promises.push(this.courseSync.syncAllCourses(siteId)); + + return Promise.all(promises); } /** From fb29050627fb243d07e57bd2e6704f3e81d196ae Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Wed, 30 Jan 2019 10:29:01 +0100 Subject: [PATCH 061/191] MOBILE-2842 core: Interfaces for DB schemas --- src/addon/calendar/providers/calendar.ts | 3 +- .../messages/providers/messages-offline.ts | 3 +- .../mod/assign/providers/assign-offline.ts | 3 +- src/addon/mod/choice/providers/offline.ts | 3 +- src/addon/mod/data/providers/offline.ts | 3 +- src/addon/mod/feedback/providers/offline.ts | 3 +- src/addon/mod/forum/providers/offline.ts | 3 +- src/addon/mod/glossary/providers/offline.ts | 3 +- .../mod/lesson/providers/lesson-offline.ts | 3 +- src/addon/mod/lesson/providers/lesson-sync.ts | 3 +- src/addon/mod/lesson/providers/lesson.ts | 3 +- .../accessrules/password/providers/handler.ts | 3 +- src/addon/mod/quiz/providers/quiz-offline.ts | 4 +- .../mod/scorm/providers/scorm-offline.ts | 4 +- src/addon/mod/survey/providers/offline.ts | 3 +- src/addon/mod/wiki/providers/wiki-offline.ts | 3 +- src/addon/mod/workshop/providers/offline.ts | 3 +- src/addon/notes/providers/notes-offline.ts | 3 +- .../providers/pushnotifications.ts | 3 +- src/classes/site.ts | 4 +- src/classes/sqlitedb.ts | 171 ++++++++++++++---- src/core/course/providers/course-offline.ts | 3 +- src/core/course/providers/course.ts | 3 +- .../providers/module-prefetch-delegate.ts | 3 +- src/core/emulator/providers/helper.ts | 3 +- .../emulator/providers/local-notifications.ts | 4 +- src/core/question/providers/question.ts | 3 +- src/core/sharedfiles/providers/sharedfiles.ts | 4 +- src/core/user/providers/user.ts | 3 +- src/providers/config.ts | 4 +- src/providers/cron.ts | 4 +- src/providers/filepool.ts | 6 +- src/providers/local-notifications.ts | 4 +- src/providers/sites.ts | 12 +- src/providers/sync.ts | 3 +- 35 files changed, 208 insertions(+), 85 deletions(-) diff --git a/src/addon/calendar/providers/calendar.ts b/src/addon/calendar/providers/calendar.ts index d59bd9ce8..fda684ad5 100644 --- a/src/addon/calendar/providers/calendar.ts +++ b/src/addon/calendar/providers/calendar.ts @@ -23,6 +23,7 @@ import { CoreConstants } from '@core/constants'; import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; import { CoreConfigProvider } from '@providers/config'; import { ILocalNotification } from '@ionic-native/local-notifications'; +import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Service to handle calendar events. @@ -38,7 +39,7 @@ export class AddonCalendarProvider { // Variables for database. static EVENTS_TABLE = 'addon_calendar_events'; - protected tablesSchema = [ + protected tablesSchema: SQLiteDBTableSchema[] = [ { name: AddonCalendarProvider.EVENTS_TABLE, columns: [ diff --git a/src/addon/messages/providers/messages-offline.ts b/src/addon/messages/providers/messages-offline.ts index f340c7e17..bca52ffd7 100644 --- a/src/addon/messages/providers/messages-offline.ts +++ b/src/addon/messages/providers/messages-offline.ts @@ -17,6 +17,7 @@ import { CoreLoggerProvider } from '@providers/logger'; import { CoreSitesProvider } from '@providers/sites'; import { CoreAppProvider } from '@providers/app'; import { CoreTextUtilsProvider } from '@providers/utils/text'; +import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Service to handle Offline messages. @@ -29,7 +30,7 @@ export class AddonMessagesOfflineProvider { // Variables for database. static MESSAGES_TABLE = 'addon_messages_offline_messages'; // When group messaging isn't available or a new conversation starts. static CONVERSATION_MESSAGES_TABLE = 'addon_messages_offline_conversation_messages'; // Conversation messages. - protected tablesSchema = [ + protected tablesSchema: SQLiteDBTableSchema[] = [ { name: AddonMessagesOfflineProvider.MESSAGES_TABLE, columns: [ diff --git a/src/addon/mod/assign/providers/assign-offline.ts b/src/addon/mod/assign/providers/assign-offline.ts index 6c54503b7..39b32baf6 100644 --- a/src/addon/mod/assign/providers/assign-offline.ts +++ b/src/addon/mod/assign/providers/assign-offline.ts @@ -18,6 +18,7 @@ import { CoreLoggerProvider } from '@providers/logger'; import { CoreSitesProvider } from '@providers/sites'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; +import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Service to handle offline assign. @@ -30,7 +31,7 @@ export class AddonModAssignOfflineProvider { // Variables for database. static SUBMISSIONS_TABLE = 'addon_mod_assign_submissions'; static SUBMISSIONS_GRADES_TABLE = 'addon_mod_assign_submissions_grading'; - protected tablesSchema = [ + protected tablesSchema: SQLiteDBTableSchema[] = [ { name: AddonModAssignOfflineProvider.SUBMISSIONS_TABLE, columns: [ diff --git a/src/addon/mod/choice/providers/offline.ts b/src/addon/mod/choice/providers/offline.ts index f17e1d2ed..dc8afd2fd 100644 --- a/src/addon/mod/choice/providers/offline.ts +++ b/src/addon/mod/choice/providers/offline.ts @@ -14,6 +14,7 @@ import { Injectable } from '@angular/core'; import { CoreSitesProvider } from '@providers/sites'; +import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Service to handle offline choices. @@ -23,7 +24,7 @@ export class AddonModChoiceOfflineProvider { // Variables for database. static CHOICE_TABLE = 'addon_mod_choice_responses'; - protected tablesSchema = [ + protected tablesSchema: SQLiteDBTableSchema[] = [ { name: AddonModChoiceOfflineProvider.CHOICE_TABLE, columns: [ diff --git a/src/addon/mod/data/providers/offline.ts b/src/addon/mod/data/providers/offline.ts index df54530ff..460b053cf 100644 --- a/src/addon/mod/data/providers/offline.ts +++ b/src/addon/mod/data/providers/offline.ts @@ -18,6 +18,7 @@ import { CoreSitesProvider } from '@providers/sites'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreFileProvider } from '@providers/file'; import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader'; +import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Service to handle Offline data. @@ -29,7 +30,7 @@ export class AddonModDataOfflineProvider { // Variables for database. static DATA_ENTRY_TABLE = 'addon_mod_data_entry'; - protected tablesSchema = [ + protected tablesSchema: SQLiteDBTableSchema[] = [ { name: AddonModDataOfflineProvider.DATA_ENTRY_TABLE, columns: [ diff --git a/src/addon/mod/feedback/providers/offline.ts b/src/addon/mod/feedback/providers/offline.ts index d8c26f1c4..80ea785f4 100644 --- a/src/addon/mod/feedback/providers/offline.ts +++ b/src/addon/mod/feedback/providers/offline.ts @@ -17,6 +17,7 @@ import { CoreLoggerProvider } from '@providers/logger'; import { CoreSitesProvider } from '@providers/sites'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; +import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Service to handle Offline feedback. @@ -28,7 +29,7 @@ export class AddonModFeedbackOfflineProvider { // Variables for database. static FEEDBACK_TABLE = 'addon_mod_feedback_answers'; - protected tablesSchema = [ + protected tablesSchema: SQLiteDBTableSchema[] = [ { name: AddonModFeedbackOfflineProvider.FEEDBACK_TABLE, columns: [ diff --git a/src/addon/mod/forum/providers/offline.ts b/src/addon/mod/forum/providers/offline.ts index 4ba0c0e86..4140897d4 100644 --- a/src/addon/mod/forum/providers/offline.ts +++ b/src/addon/mod/forum/providers/offline.ts @@ -16,6 +16,7 @@ import { Injectable } from '@angular/core'; import { CoreFileProvider } from '@providers/file'; import { CoreSitesProvider } from '@providers/sites'; import { CoreTextUtilsProvider } from '@providers/utils/text'; +import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Service to handle offline forum. @@ -27,7 +28,7 @@ export class AddonModForumOfflineProvider { static DISCUSSIONS_TABLE = 'addon_mod_forum_discussions'; static REPLIES_TABLE = 'addon_mod_forum_replies'; - protected tablesSchema = [ + protected tablesSchema: SQLiteDBTableSchema[] = [ { name: AddonModForumOfflineProvider.DISCUSSIONS_TABLE, columns: [ diff --git a/src/addon/mod/glossary/providers/offline.ts b/src/addon/mod/glossary/providers/offline.ts index 0adccdb0b..20b0ef53d 100644 --- a/src/addon/mod/glossary/providers/offline.ts +++ b/src/addon/mod/glossary/providers/offline.ts @@ -17,6 +17,7 @@ import { CoreFileProvider } from '@providers/file'; import { CoreSitesProvider } from '@providers/sites'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreTextUtilsProvider } from '@providers/utils/text'; +import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Service to handle offline glossary. @@ -27,7 +28,7 @@ export class AddonModGlossaryOfflineProvider { // Variables for database. static ENTRIES_TABLE = 'addon_mod_glossary_entrues'; - protected tablesSchema = [ + protected tablesSchema: SQLiteDBTableSchema[] = [ { name: AddonModGlossaryOfflineProvider.ENTRIES_TABLE, columns: [ diff --git a/src/addon/mod/lesson/providers/lesson-offline.ts b/src/addon/mod/lesson/providers/lesson-offline.ts index cf037fadb..32dcdc74c 100644 --- a/src/addon/mod/lesson/providers/lesson-offline.ts +++ b/src/addon/mod/lesson/providers/lesson-offline.ts @@ -18,6 +18,7 @@ import { CoreSitesProvider } from '@providers/sites'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreUtilsProvider } from '@providers/utils/utils'; +import { SQLiteDBTableSchema } from '@classes/sqlitedb'; import { AddonModLessonProvider } from './lesson'; /** @@ -31,7 +32,7 @@ export class AddonModLessonOfflineProvider { // Variables for database. We use lowercase in the names to match the WS responses. static RETAKES_TABLE = 'addon_mod_lesson_retakes'; static PAGE_ATTEMPTS_TABLE = 'addon_mod_lesson_page_attempts'; - protected tablesSchema = [ + protected tablesSchema: SQLiteDBTableSchema[] = [ { name: AddonModLessonOfflineProvider.RETAKES_TABLE, columns: [ diff --git a/src/addon/mod/lesson/providers/lesson-sync.ts b/src/addon/mod/lesson/providers/lesson-sync.ts index de7e2ef7c..1e250f951 100644 --- a/src/addon/mod/lesson/providers/lesson-sync.ts +++ b/src/addon/mod/lesson/providers/lesson-sync.ts @@ -25,6 +25,7 @@ import { CoreUrlUtilsProvider } from '@providers/utils/url'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreSyncBaseProvider } from '@classes/base-sync'; +import { SQLiteDBTableSchema } from '@classes/sqlitedb'; import { AddonModLessonProvider } from './lesson'; import { AddonModLessonOfflineProvider } from './lesson-offline'; import { AddonModLessonPrefetchHandler } from './prefetch-handler'; @@ -58,7 +59,7 @@ export class AddonModLessonSyncProvider extends CoreSyncBaseProvider { // Variables for database. static RETAKES_FINISHED_TABLE = 'addon_mod_lesson_retakes_finished_sync'; - protected tablesSchema = { + protected tablesSchema: SQLiteDBTableSchema = { name: AddonModLessonSyncProvider.RETAKES_FINISHED_TABLE, columns: [ { diff --git a/src/addon/mod/lesson/providers/lesson.ts b/src/addon/mod/lesson/providers/lesson.ts index 6af1f19d1..e2343fbcb 100644 --- a/src/addon/mod/lesson/providers/lesson.ts +++ b/src/addon/mod/lesson/providers/lesson.ts @@ -21,6 +21,7 @@ import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreGradesProvider } from '@core/grades/providers/grades'; import { CoreSiteWSPreSets } from '@classes/site'; +import { SQLiteDBTableSchema } from '@classes/sqlitedb'; import { AddonModLessonOfflineProvider } from './lesson-offline'; /** @@ -155,7 +156,7 @@ export class AddonModLessonProvider { // Variables for database. static PASSWORD_TABLE = 'addon_mod_lesson_password'; - protected tablesSchema = { + protected tablesSchema: SQLiteDBTableSchema = { name: AddonModLessonProvider.PASSWORD_TABLE, columns: [ { diff --git a/src/addon/mod/quiz/accessrules/password/providers/handler.ts b/src/addon/mod/quiz/accessrules/password/providers/handler.ts index eea3286b2..52b482eea 100644 --- a/src/addon/mod/quiz/accessrules/password/providers/handler.ts +++ b/src/addon/mod/quiz/accessrules/password/providers/handler.ts @@ -17,6 +17,7 @@ import { Injectable, Injector } from '@angular/core'; import { CoreSitesProvider } from '@providers/sites'; import { AddonModQuizAccessRuleHandler } from '../../../providers/access-rules-delegate'; import { AddonModQuizAccessPasswordComponent } from '../component/password'; +import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Handler to support password access rule. @@ -25,7 +26,7 @@ import { AddonModQuizAccessPasswordComponent } from '../component/password'; export class AddonModQuizAccessPasswordHandler implements AddonModQuizAccessRuleHandler { // Variables for database. static PASSWORD_TABLE = 'addon_mod_quiz_access_password'; - protected tableSchema = { + protected tableSchema: SQLiteDBTableSchema = { name: AddonModQuizAccessPasswordHandler.PASSWORD_TABLE, columns: [ { diff --git a/src/addon/mod/quiz/providers/quiz-offline.ts b/src/addon/mod/quiz/providers/quiz-offline.ts index 76f7a2631..5d21446d1 100644 --- a/src/addon/mod/quiz/providers/quiz-offline.ts +++ b/src/addon/mod/quiz/providers/quiz-offline.ts @@ -21,7 +21,7 @@ import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreQuestionProvider } from '@core/question/providers/question'; import { CoreQuestionBehaviourDelegate } from '@core/question/providers/behaviour-delegate'; import { AddonModQuizProvider } from './quiz'; -import { SQLiteDB } from '@classes/sqlitedb'; +import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Service to handle offline quiz. @@ -33,7 +33,7 @@ export class AddonModQuizOfflineProvider { // Variables for database. static ATTEMPTS_TABLE = 'addon_mod_quiz_attempts'; - protected tablesSchema = [ + protected tablesSchema: SQLiteDBTableSchema[] = [ { name: AddonModQuizOfflineProvider.ATTEMPTS_TABLE, columns: [ diff --git a/src/addon/mod/scorm/providers/scorm-offline.ts b/src/addon/mod/scorm/providers/scorm-offline.ts index b8cdc1d43..9bfdd2c79 100644 --- a/src/addon/mod/scorm/providers/scorm-offline.ts +++ b/src/addon/mod/scorm/providers/scorm-offline.ts @@ -21,7 +21,7 @@ import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreUserProvider } from '@core/user/providers/user'; import { AddonModScormProvider } from './scorm'; -import { SQLiteDB } from '@classes/sqlitedb'; +import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Service to handle offline SCORM. @@ -34,7 +34,7 @@ export class AddonModScormOfflineProvider { // Variables for database. static ATTEMPTS_TABLE = 'addon_mod_scorm_offline_attempts'; static TRACKS_TABLE = 'addon_mod_scorm_offline_scos_tracks'; - protected tablesSchema = [ + protected tablesSchema: SQLiteDBTableSchema[] = [ { name: AddonModScormOfflineProvider.ATTEMPTS_TABLE, columns: [ diff --git a/src/addon/mod/survey/providers/offline.ts b/src/addon/mod/survey/providers/offline.ts index 6e3ce13a4..61d04a315 100644 --- a/src/addon/mod/survey/providers/offline.ts +++ b/src/addon/mod/survey/providers/offline.ts @@ -16,6 +16,7 @@ import { Injectable } from '@angular/core'; import { CoreLoggerProvider } from '@providers/logger'; import { CoreSitesProvider } from '@providers/sites'; import { CoreTextUtilsProvider } from '@providers/utils/text'; +import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Service to handle Offline survey. @@ -27,7 +28,7 @@ export class AddonModSurveyOfflineProvider { // Variables for database. static SURVEY_TABLE = 'addon_mod_survey_answers'; - protected tablesSchema = [ + protected tablesSchema: SQLiteDBTableSchema[] = [ { name: AddonModSurveyOfflineProvider.SURVEY_TABLE, columns: [ diff --git a/src/addon/mod/wiki/providers/wiki-offline.ts b/src/addon/mod/wiki/providers/wiki-offline.ts index 6ae9935d4..d9407e2dc 100644 --- a/src/addon/mod/wiki/providers/wiki-offline.ts +++ b/src/addon/mod/wiki/providers/wiki-offline.ts @@ -15,6 +15,7 @@ import { Injectable } from '@angular/core'; import { CoreLoggerProvider } from '@providers/logger'; import { CoreSitesProvider } from '@providers/sites'; +import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Service to handle offline wiki. @@ -26,7 +27,7 @@ export class AddonModWikiOfflineProvider { // Variables for database. static NEW_PAGES_TABLE = 'addon_mod_wiki_new_pages_store'; - protected tablesSchema = [ + protected tablesSchema: SQLiteDBTableSchema[] = [ { name: AddonModWikiOfflineProvider.NEW_PAGES_TABLE, columns: [ diff --git a/src/addon/mod/workshop/providers/offline.ts b/src/addon/mod/workshop/providers/offline.ts index 63ed454a8..c5c8c78d8 100644 --- a/src/addon/mod/workshop/providers/offline.ts +++ b/src/addon/mod/workshop/providers/offline.ts @@ -17,6 +17,7 @@ import { CoreFileProvider } from '@providers/file'; import { CoreSitesProvider } from '@providers/sites'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; +import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Service to handle offline workshop. @@ -30,7 +31,7 @@ export class AddonModWorkshopOfflineProvider { static EVALUATE_SUBMISSIONS_TABLE = 'addon_mod_workshop_evaluate_submissions'; static EVALUATE_ASSESSMENTS_TABLE = 'addon_mod_workshop_evaluate_assessments'; - protected tablesSchema = [ + protected tablesSchema: SQLiteDBTableSchema[] = [ { name: AddonModWorkshopOfflineProvider.SUBMISSIONS_TABLE, columns: [ diff --git a/src/addon/notes/providers/notes-offline.ts b/src/addon/notes/providers/notes-offline.ts index dd9fbf50b..1572c6559 100644 --- a/src/addon/notes/providers/notes-offline.ts +++ b/src/addon/notes/providers/notes-offline.ts @@ -16,6 +16,7 @@ import { Injectable } from '@angular/core'; import { CoreLoggerProvider } from '@providers/logger'; import { CoreSitesProvider } from '@providers/sites'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; +import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Service to handle offline notes. @@ -26,7 +27,7 @@ export class AddonNotesOfflineProvider { // Variables for database. static NOTES_TABLE = 'addon_notes_offline_notes'; - protected tablesSchema = [ + protected tablesSchema: SQLiteDBTableSchema[] = [ { name: AddonNotesOfflineProvider.NOTES_TABLE, columns: [ diff --git a/src/addon/pushnotifications/providers/pushnotifications.ts b/src/addon/pushnotifications/providers/pushnotifications.ts index 01ac2318c..cc36a99a5 100644 --- a/src/addon/pushnotifications/providers/pushnotifications.ts +++ b/src/addon/pushnotifications/providers/pushnotifications.ts @@ -28,6 +28,7 @@ import { CoreConfigProvider } from '@providers/config'; import { CoreConstants } from '@core/constants'; import { CoreConfigConstants } from '../../../configconstants'; import { ILocalNotification } from '@ionic-native/local-notifications'; +import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Service to handle push notifications. @@ -41,7 +42,7 @@ export class AddonPushNotificationsProvider { // Variables for database. static BADGE_TABLE = 'addon_pushnotifications_badge'; - protected tablesSchema = [ + protected tablesSchema: SQLiteDBTableSchema[] = [ { name: AddonPushNotificationsProvider.BADGE_TABLE, columns: [ diff --git a/src/classes/site.ts b/src/classes/site.ts index 08b3512da..3c84bcd8a 100644 --- a/src/classes/site.ts +++ b/src/classes/site.ts @@ -15,7 +15,7 @@ import { Injector } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { HttpClient } from '@angular/common/http'; -import { SQLiteDB } from './sqlitedb'; +import { SQLiteDB, SQLiteDBTableSchema } from './sqlitedb'; import { CoreAppProvider } from '@providers/app'; import { CoreDbProvider } from '@providers/db'; import { CoreEventsProvider } from '@providers/events'; @@ -168,7 +168,7 @@ export class CoreSite { // Variables for the database. protected WS_CACHE_TABLE = 'wscache'; protected CONFIG_TABLE = 'core_site_config'; - protected tableSchemas = [ + protected tableSchemas: SQLiteDBTableSchema[] = [ { name: this.WS_CACHE_TABLE, columns: [ diff --git a/src/classes/sqlitedb.ts b/src/classes/sqlitedb.ts index 8e650767b..fcd319045 100644 --- a/src/classes/sqlitedb.ts +++ b/src/classes/sqlitedb.ts @@ -15,6 +15,129 @@ import { SQLite, SQLiteObject } from '@ionic-native/sqlite'; import { Platform } from 'ionic-angular'; +/** + * Schema of a table. + */ +export interface SQLiteDBTableSchema { + /** + * The table name. + * @type {string} + */ + name: string; + + /** + * The columns to create in the table. + * @type {SQLiteDBColumnSchema[]} + */ + columns: SQLiteDBColumnSchema[]; + + /** + * Names of columns that are primary key. Use it for compound primary keys. + * @type {string[]} + */ + primaryKeys?: string[]; + + /** + * List of sets of unique columns. E.g: [['section', 'title'], ['author', 'title']]. + * @type {string[][]} + */ + uniqueKeys?: string[][]; + + /** + * List of foreign keys. + * @type {SQLiteDBForeignKeySchema[]} + */ + foreignKeys?: SQLiteDBForeignKeySchema[]; + + /** + * Check constraint for the table. + * @type {string} + */ + tableCheck?: string; +} + +/** + * Schema of a column. + */ +export interface SQLiteDBColumnSchema { + /** + * Column's name. + * @type {string} + */ + name: string; + + /** + * Column's type. + * @type {string} + */ + type?: 'INTEGER' | 'REAL' | 'TEXT' | 'BLOB'; + + /** + * Whether the column is a primary key. Use it only if primary key is a single column. + * @type {boolean} + */ + primaryKey?: boolean; + + /** + * Whether it should be autoincremented. Only if primaryKey is true. + * @type {boolean} + */ + autoIncrement?: boolean; + + /** + * True if column shouldn't be null. + * @type {boolean} + */ + notNull?: boolean; + + /** + * WWhether the column is unique. + * @type {boolean} + */ + unique?: boolean; + + /** + * Check constraint for the column. + * @type {string} + */ + check?: string; + + /** + * Default value for the column. + * @type {string} + */ + default?: string; +} + +/** + * Schema of a foreign key. + */ +export interface SQLiteDBForeignKeySchema { + /** + * Columns to include in this foreign key. + * @type {string[]} + */ + columns: string[]; + + /** + * The external table referenced by this key. + * @type {string} + */ + table: string; + + /** + * List of referenced columns from the referenced table. + * @type {string[]} + */ + foreignColumns?: string[]; + + /** + * Text with the actions to apply to the foreign key. + * @type {string} + */ + actions?: string; +} + /** * Class to interact with the local database. * @@ -43,27 +166,15 @@ export class SQLiteDB { * Helper function to create a table if it doesn't exist. * * @param {string} name The table name. - * @param {any[]} columns The columns to create in the table. Each column can have: - * * {string} name Column's name. - * * {string} [type] Column's type. - * * {boolean} [primaryKey] If column is primary key. Use it only if primary key is a single column. - * * {boolean} [autoIncrement] Whether it should be autoincremented. Only if primaryKey is true. - * * {boolean} [notNull] True if column shouldn't be null. - * * {boolean} [unique] Whether the column is unique. - * * {string} [check] Check constraint for the column. - * * {string} [default] Default value for the column. + * @param {SQLiteDBColumnSchema[]} columns The columns to create in the table. * @param {string[]} [primaryKeys] Names of columns that are primary key. Use it for compound primary keys. * @param {string[][]} [uniqueKeys] List of sets of unique columns. E.g: [['section', 'title'], ['author', 'title']]. - * @param {any[]} [foreignKeys] List of foreign keys. Each key can have: - * * {string[]} columns Columns to include in this foreign key. - * * {string} table The external table referenced by this key. - * * {string[]} [foreignColumns] List of referenced columns from the referenced table. - * * {string} [actions] Text with the actions to apply to the foreign key. + * @param {SQLiteDBForeignKeySchema[]} [foreignKeys] List of foreign keys. * @param {string} [tableCheck] Check constraint for the table. * @return SQL query. */ - buildCreateTableSql(name: string, columns: any[], primaryKeys?: string[], uniqueKeys?: string[][], foreignKeys?: any[], - tableCheck?: string): string { + buildCreateTableSql(name: string, columns: SQLiteDBColumnSchema[], primaryKeys?: string[], uniqueKeys?: string[][], + foreignKeys?: SQLiteDBForeignKeySchema[], tableCheck?: string): string { const columnsSql = []; let sql = `CREATE TABLE IF NOT EXISTS ${name} (`; @@ -207,27 +318,15 @@ export class SQLiteDB { * Create a table if it doesn't exist. * * @param {string} name The table name. - * @param {any[]} columns The columns to create in the table. Each column can have: - * * {string} name Column's name. - * * {string} [type] Column's type. - * * {boolean} [primaryKey] If column is primary key. Use it only if primary key is a single column. - * * {boolean} [autoIncrement] Whether it should be autoincremented. Only if primaryKey is true. - * * {boolean} [notNull] True if column shouldn't be null. - * * {boolean} [unique] Whether the column is unique. - * * {string} [check] Check constraint for the column. - * * {string} [default] Default value for the column. + * @param {SQLiteDBColumnSchema[]} columns The columns to create in the table. * @param {string[]} [primaryKeys] Names of columns that are primary key. Use it for compound primary keys. * @param {string[][]} [uniqueKeys] List of sets of unique columns. E.g: [['section', 'title'], ['author', 'title']]. - * @param {any[]} [foreignKeys] List of foreign keys. Each key can have: - * * {string[]} columns Columns to include in this foreign key. - * * {string} table The external table referenced by this key. - * * {string[]} [foreignColumns] List of referenced columns from the referenced table. - * * {string} [actions] Text with the actions to apply to the foreign key. + * @param {SQLiteDBForeignKeySchema[]} [foreignKeys] List of foreign keys. * @param {string} [tableCheck] Check constraint for the table. * @return {Promise} Promise resolved when success. */ - createTable(name: string, columns: any[], primaryKeys?: string[], uniqueKeys?: string[][], foreignKeys?: any[], - tableCheck?: string): Promise { + createTable(name: string, columns: SQLiteDBColumnSchema[], primaryKeys?: string[], uniqueKeys?: string[][], + foreignKeys?: SQLiteDBForeignKeySchema[], tableCheck?: string): Promise { const sql = this.buildCreateTableSql(name, columns, primaryKeys, uniqueKeys, foreignKeys, tableCheck); return this.execute(sql); @@ -236,10 +335,10 @@ export class SQLiteDB { /** * Create a table if it doesn't exist from a schema. * - * @param {any} table Table schema. + * @param {SQLiteDBTableSchema} table Table schema. * @return {Promise} Promise resolved when success. */ - createTableFromSchema(table: any): Promise { + createTableFromSchema(table: SQLiteDBTableSchema): Promise { return this.createTable(table.name, table.columns, table.primaryKeys, table.uniqueKeys, table.foreignKeys, table.tableCheck); } @@ -247,10 +346,10 @@ export class SQLiteDB { /** * Create several tables if they don't exist from a list of schemas. * - * @param {any[]} tables List of table schema. + * @param {SQLiteDBTableSchema[]} tables List of table schema. * @return {Promise} Promise resolved when success. */ - createTablesFromSchema(tables: any[]): Promise { + createTablesFromSchema(tables: SQLiteDBTableSchema[]): Promise { const promises = []; tables.forEach((table) => { promises.push(this.createTableFromSchema(table)); diff --git a/src/core/course/providers/course-offline.ts b/src/core/course/providers/course-offline.ts index fadb65463..af28b5cbf 100644 --- a/src/core/course/providers/course-offline.ts +++ b/src/core/course/providers/course-offline.ts @@ -14,6 +14,7 @@ import { Injectable } from '@angular/core'; import { CoreSitesProvider } from '@providers/sites'; +import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Service to handle offline data for courses. @@ -23,7 +24,7 @@ export class CoreCourseOfflineProvider { // Variables for database. static MANUAL_COMPLETION_TABLE = 'course_manual_completion'; - protected tablesSchema = [ + protected tablesSchema: SQLiteDBTableSchema[] = [ { name: CoreCourseOfflineProvider.MANUAL_COMPLETION_TABLE, columns: [ diff --git a/src/core/course/providers/course.ts b/src/core/course/providers/course.ts index f69b5063a..59b7dfe15 100644 --- a/src/core/course/providers/course.ts +++ b/src/core/course/providers/course.ts @@ -23,6 +23,7 @@ import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreSiteWSPreSets, CoreSite } from '@classes/site'; import { CoreConstants } from '../../constants'; import { CoreCourseOfflineProvider } from './course-offline'; +import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Service that provides some features regarding a course. @@ -47,7 +48,7 @@ export class CoreCourseProvider { // Variables for database. protected COURSE_STATUS_TABLE = 'course_status'; - protected courseStatusTableSchema = { + protected courseStatusTableSchema: SQLiteDBTableSchema = { name: this.COURSE_STATUS_TABLE, columns: [ { diff --git a/src/core/course/providers/module-prefetch-delegate.ts b/src/core/course/providers/module-prefetch-delegate.ts index e69b04036..61e228345 100644 --- a/src/core/course/providers/module-prefetch-delegate.ts +++ b/src/core/course/providers/module-prefetch-delegate.ts @@ -27,6 +27,7 @@ import { CoreConstants } from '../../constants'; import { Md5 } from 'ts-md5/dist/md5'; import { Subject, BehaviorSubject, Subscription } from 'rxjs'; import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate'; +import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Progress of downloading a list of modules. @@ -202,7 +203,7 @@ export interface CoreCourseModulePrefetchHandler extends CoreDelegateHandler { export class CoreCourseModulePrefetchDelegate extends CoreDelegate { // Variables for database. protected CHECK_UPDATES_TIMES_TABLE = 'check_updates_times'; - protected checkUpdatesTableSchema = { + protected checkUpdatesTableSchema: SQLiteDBTableSchema = { name: this.CHECK_UPDATES_TIMES_TABLE, columns: [ { diff --git a/src/core/emulator/providers/helper.ts b/src/core/emulator/providers/helper.ts index 4c866ebc8..e3eb05776 100644 --- a/src/core/emulator/providers/helper.ts +++ b/src/core/emulator/providers/helper.ts @@ -26,6 +26,7 @@ import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { FileTransferErrorMock } from './file-transfer'; import { CoreEmulatorCaptureHelperProvider } from './capture-helper'; import { CoreConstants } from '../../constants'; +import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Helper service for the emulator feature. It also acts as an init handler. @@ -40,7 +41,7 @@ export class CoreEmulatorHelperProvider implements CoreInitHandler { // Variables for database. protected LAST_RECEIVED_NOTIFICATION_TABLE = 'core_emulator_last_received_notification'; - protected tablesSchema = [ + protected tablesSchema: SQLiteDBTableSchema[] = [ { name: this.LAST_RECEIVED_NOTIFICATION_TABLE, columns: [ diff --git a/src/core/emulator/providers/local-notifications.ts b/src/core/emulator/providers/local-notifications.ts index 0936960e7..f8e4f0f65 100644 --- a/src/core/emulator/providers/local-notifications.ts +++ b/src/core/emulator/providers/local-notifications.ts @@ -17,7 +17,7 @@ import { LocalNotifications, ILocalNotification, ILocalNotificationAction } from import { CoreAppProvider } from '@providers/app'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreUtilsProvider } from '@providers/utils/utils'; -import { SQLiteDB } from '@classes/sqlitedb'; +import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb'; import { CoreConstants } from '@core/constants'; import { CoreConfigConstants } from '../../../configconstants'; import * as moment from 'moment'; @@ -43,7 +43,7 @@ export class LocalNotificationsMock extends LocalNotifications { // Variables for database. protected DESKTOP_NOTIFS_TABLE = 'desktop_local_notifications'; - protected tableSchema = { + protected tableSchema: SQLiteDBTableSchema = { name: this.DESKTOP_NOTIFS_TABLE, columns: [ { diff --git a/src/core/question/providers/question.ts b/src/core/question/providers/question.ts index 31a132cae..4c1e56e0e 100644 --- a/src/core/question/providers/question.ts +++ b/src/core/question/providers/question.ts @@ -17,6 +17,7 @@ import { CoreLoggerProvider } from '@providers/logger'; import { CoreSitesProvider } from '@providers/sites'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreUtilsProvider } from '@providers/utils/utils'; +import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * An object to represent a question state. @@ -63,7 +64,7 @@ export class CoreQuestionProvider { // Variables for database. protected QUESTION_TABLE = 'questions'; protected QUESTION_ANSWERS_TABLE = 'question_answers'; - protected tablesSchema = [ + protected tablesSchema: SQLiteDBTableSchema[] = [ { name: this.QUESTION_TABLE, columns: [ diff --git a/src/core/sharedfiles/providers/sharedfiles.ts b/src/core/sharedfiles/providers/sharedfiles.ts index 407c572ae..1938c1ec4 100644 --- a/src/core/sharedfiles/providers/sharedfiles.ts +++ b/src/core/sharedfiles/providers/sharedfiles.ts @@ -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 } from '@classes/sqlitedb'; +import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Service to share files with the app. @@ -32,7 +32,7 @@ export class CoreSharedFilesProvider { // Variables for the database. protected SHARED_FILES_TABLE = 'shared_files'; - protected tableSchema = { + protected tableSchema: SQLiteDBTableSchema = { name: this.SHARED_FILES_TABLE, columns: [ { diff --git a/src/core/user/providers/user.ts b/src/core/user/providers/user.ts index 242c586d6..96c1fa0f5 100644 --- a/src/core/user/providers/user.ts +++ b/src/core/user/providers/user.ts @@ -18,6 +18,7 @@ import { CoreLoggerProvider } from '@providers/logger'; import { CoreSite } from '@classes/site'; import { CoreSitesProvider } from '@providers/sites'; import { CoreUtilsProvider } from '@providers/utils/utils'; +import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Service to provide user functionalities. @@ -31,7 +32,7 @@ export class CoreUserProvider { // Variables for database. protected USERS_TABLE = 'users'; - protected tablesSchema = [ + protected tablesSchema: SQLiteDBTableSchema[] = [ { name: this.USERS_TABLE, columns: [ diff --git a/src/providers/config.ts b/src/providers/config.ts index 05e9a6a0e..e368c073b 100644 --- a/src/providers/config.ts +++ b/src/providers/config.ts @@ -14,7 +14,7 @@ import { Injectable } from '@angular/core'; import { CoreAppProvider } from './app'; -import { SQLiteDB } from '@classes/sqlitedb'; +import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Factory to provide access to dynamic and permanent config and settings. @@ -24,7 +24,7 @@ import { SQLiteDB } from '@classes/sqlitedb'; export class CoreConfigProvider { protected appDB: SQLiteDB; protected TABLE_NAME = 'core_config'; - protected tableSchema = { + protected tableSchema: SQLiteDBTableSchema = { name: this.TABLE_NAME, columns: [ { diff --git a/src/providers/cron.ts b/src/providers/cron.ts index b82335476..99d12b95d 100644 --- a/src/providers/cron.ts +++ b/src/providers/cron.ts @@ -19,7 +19,7 @@ import { CoreConfigProvider } from './config'; import { CoreLoggerProvider } from './logger'; import { CoreUtilsProvider } from './utils/utils'; import { CoreConstants } from '@core/constants'; -import { SQLiteDB } from '@classes/sqlitedb'; +import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Interface that all cron handlers must implement. @@ -94,7 +94,7 @@ export class CoreCronDelegate { // Variables for database. protected CRON_TABLE = 'cron'; - protected tableSchema = { + protected tableSchema: SQLiteDBTableSchema = { name: this.CRON_TABLE, columns: [ { diff --git a/src/providers/filepool.ts b/src/providers/filepool.ts index e01deba92..08fbb85a6 100644 --- a/src/providers/filepool.ts +++ b/src/providers/filepool.ts @@ -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 } from '@classes/sqlitedb'; +import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb'; import { CoreConstants } from '@core/constants'; import { Md5 } from 'ts-md5/dist/md5'; @@ -254,7 +254,7 @@ 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 = [ + protected appTablesSchema: SQLiteDBTableSchema[] = [ { name: this.QUEUE_TABLE, columns: [ @@ -306,7 +306,7 @@ export class CoreFilepoolProvider { primaryKeys: ['siteId', 'fileId'] } ]; - protected sitesTablesSchema = [ + protected sitesTablesSchema: SQLiteDBTableSchema[] = [ { name: this.FILES_TABLE, columns: [ diff --git a/src/providers/local-notifications.ts b/src/providers/local-notifications.ts index 5b64a5f64..877ee7d48 100644 --- a/src/providers/local-notifications.ts +++ b/src/providers/local-notifications.ts @@ -22,7 +22,7 @@ import { CoreEventsProvider } from './events'; import { CoreLoggerProvider } from './logger'; import { CoreTextUtilsProvider } from './utils/text'; import { CoreUtilsProvider } from './utils/utils'; -import { SQLiteDB } from '@classes/sqlitedb'; +import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb'; import { CoreConstants } from '@core/constants'; import { Subject, Subscription } from 'rxjs'; @@ -38,7 +38,7 @@ 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 = [ + protected tablesSchema: SQLiteDBTableSchema[] = [ { name: this.SITES_TABLE, columns: [ diff --git a/src/providers/sites.ts b/src/providers/sites.ts index d1abf0bc9..226efe33a 100644 --- a/src/providers/sites.ts +++ b/src/providers/sites.ts @@ -25,7 +25,7 @@ import { CoreUtilsProvider } from './utils/utils'; import { CoreConstants } from '@core/constants'; import { CoreConfigConstants } from '../configconstants'; import { CoreSite } from '@classes/site'; -import { SQLiteDB } from '@classes/sqlitedb'; +import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb'; import { Md5 } from 'ts-md5/dist/md5'; /** @@ -143,7 +143,7 @@ export class CoreSitesProvider { // Variables for the database. protected SITES_TABLE = 'sites'; protected CURRENT_SITE_TABLE = 'current_site'; - protected appTablesSchema = [ + protected appTablesSchema: SQLiteDBTableSchema[] = [ { name: this.SITES_TABLE, columns: [ @@ -1251,18 +1251,18 @@ export class CoreSitesProvider { /** * Create a table in all the sites databases. * - * @param {any} table Table schema. + * @param {SQLiteDBTamableSchema} table Table schema. */ - createTableFromSchema(table: any): void { + createTableFromSchema(table: SQLiteDBTableSchema): void { this.createTablesFromSchema([table]); } /** * Create several tables in all the sites databases. * - * @param {any[]} tables List of tables schema. + * @param {SQLiteDBTamableSchema[]} tables List of tables schema. */ - createTablesFromSchema(tables: any[]): void { + createTablesFromSchema(tables: SQLiteDBTableSchema[]): void { // Add the tables to the list of schemas. This list is to create all the tables in new sites. this.siteTablesSchemas = this.siteTablesSchemas.concat(tables); diff --git a/src/providers/sync.ts b/src/providers/sync.ts index 510fa7be5..e1329d8e0 100644 --- a/src/providers/sync.ts +++ b/src/providers/sync.ts @@ -15,6 +15,7 @@ import { Injectable } from '@angular/core'; import { CoreEventsProvider } from './events'; import { CoreSitesProvider } from './sites'; +import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /* * Service that provides some features regarding synchronization. @@ -24,7 +25,7 @@ export class CoreSyncProvider { // Variables for the database. protected SYNC_TABLE = 'sync'; - protected tableSchema = { + protected tableSchema: SQLiteDBTableSchema = { name: this.SYNC_TABLE, columns: [ { From db46635262581b1896aa294f8fd851d00e3dbbac Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Wed, 30 Jan 2019 11:03:27 +0100 Subject: [PATCH 062/191] MOBILE-2842 core: New SQLiteDB methods --- src/classes/sqlitedb.ts | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/classes/sqlitedb.ts b/src/classes/sqlitedb.ts index fcd319045..9bc24272b 100644 --- a/src/classes/sqlitedb.ts +++ b/src/classes/sqlitedb.ts @@ -407,6 +407,16 @@ export class SQLiteDB { return this.execute(`DELETE FROM ${table} ${select}`, params); } + /** + * Drop a table if it exists. + * + * @param {string} name The table name. + * @return {Promise} Promise resolved when success. + */ + dropTable(name: string): Promise { + return this.execute(`DROP TABLE IF EXISTS ${name}`); + } + /** * Execute a SQL query. * IMPORTANT: Use this function only if you cannot use any of the other functions in this API. Please take into account that @@ -783,6 +793,23 @@ export class SQLiteDB { return this.executeBatch(statements); } + /** + * Insert multiple records into database from another table. + * + * @param {string} table The database table to be inserted into. + * @param {string} source The database table to get the records from. + * @param {object} [conditions] The conditions to build the where clause. Must not contain numeric indexes. + * @param {string} [fields='*'] A comma separated list of fields to return. + * @return {Promise} Promise resolved when done. + */ + insertRecordsFrom(table: string, source: string, conditions?: object, fields: string = '*'): Promise { + const selectAndParams = this.whereClause(conditions); + const select = selectAndParams[0] ? 'WHERE ' + selectAndParams[0] : ''; + const params = selectAndParams[1]; + + return this.execute(`INSERT INTO ${table} SELECT ${fields} FROM ${source} ${select}`, params); + } + /** * Ensures that limit params are numeric and positive integers, to be passed to the database. * We explicitly treat null, '' and -1 as 0 in order to provide compatibility with how limit @@ -875,6 +902,16 @@ export class SQLiteDB { }); } + /** + * Test whether a table exists.. + * + * @param {string} name The table name. + * @return {Promise} Promise resolved if exists, rejected otherwise. + */ + tableExists(name: string): Promise { + return this.recordExists('sqlite_master', {type: 'table', tbl_name: name}); + } + /** * Update one or more records in a table. * From d3246e69001eb1518b3b55a6deec63f75712666c Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Wed, 30 Jan 2019 11:05:24 +0100 Subject: [PATCH 063/191] MOBILE-2842 core: Site schema migration --- src/providers/sites.ts | 180 ++++++++++++++++++++++++++++++++++------- upgrade.txt | 1 + 2 files changed, 153 insertions(+), 28 deletions(-) diff --git a/src/providers/sites.ts b/src/providers/sites.ts index 226efe33a..cd156af0c 100644 --- a/src/providers/sites.ts +++ b/src/providers/sites.ts @@ -127,6 +127,41 @@ export interface CoreSiteBasicInfo { badge?: number; } +/** + * Site schema and migration function. + */ +export interface CoreSiteSchema { + /** + * Name of the schema. + * + * @type {string} + */ + name: string; + + /** + * Latest version of the schema (integer greater than 0). + * + * @type {number} + */ + version: number; + + /** + * Tables to create when installing or upgrading the schema. + */ + tables?: SQLiteDBTableSchema[]; + + /** + * Migrates the schema in a site to the latest version. + * + * Called when installing and upgrading the schema, after creating the defined tables. + * + * @param {SQLiteDB} db Site database. + * @param {number} oldVersion Old version of the schema or 0 if not installed. + * @return {Promise | void} Promise resolved when done. + */ + migrate?(db: SQLiteDB, oldVersion: number): Promise | void; +} + /* * Service to manage and interact with sites. * It allows creating tables in the databases of all sites. Each service or component should be responsible of creating @@ -143,6 +178,7 @@ 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, @@ -208,7 +244,26 @@ export class CoreSitesProvider { protected currentSite: CoreSite; protected sites: { [s: string]: CoreSite } = {}; protected appDB: SQLiteDB; - protected siteTablesSchemas = []; // Schemas for site tables. Other providers can add schemas in here. + protected siteSchemasMigration: { [siteId: string]: Promise } = {}; + + // Schemas for site tables. Other providers can add schemas in here. + protected siteSchemas: { [name: string]: CoreSiteSchema } = {}; + protected siteTablesSchemas: SQLiteDBTableSchema[] = [ + { + name: this.SCHEMA_VERSIONS_TABLE, + columns: [ + { + name: 'name', + type: 'TEXT', + primaryKey: true, + }, + { + name: 'version', + type: 'INTEGER' + } + ] + } + ]; constructor(logger: CoreLoggerProvider, private http: HttpClient, private sitesFactory: CoreSitesFactoryProvider, private appProvider: CoreAppProvider, private translate: TranslateService, private urlUtils: CoreUrlUtilsProvider, @@ -478,24 +533,23 @@ export class CoreSitesProvider { candidateSite.setId(siteId); candidateSite.setInfo(info); - // Try to get the site config. - return this.getSiteConfig(candidateSite).then((config) => { - candidateSite.setConfig(config); - // Add site to sites list. - this.addSite(siteId, siteUrl, token, info, privateToken, config); - // Turn candidate site into current site. - this.currentSite = candidateSite; - this.sites[siteId] = candidateSite; - // Store session. - this.login(siteId); - this.eventsProvider.trigger(CoreEventsProvider.SITE_ADDED, info, siteId); + // Create database tables before login and before any WS call. + return this.migrateSiteSchemas(candidateSite).then(() => { - if (this.siteTablesSchemas.length) { - // Create tables in the site's database. - candidateSite.getDb().createTablesFromSchema(this.siteTablesSchemas); - } + // Try to get the site config. + return this.getSiteConfig(candidateSite).then((config) => { + candidateSite.setConfig(config); + // Add site to sites list. + this.addSite(siteId, siteUrl, token, info, privateToken, config); + // Turn candidate site into current site. + this.currentSite = candidateSite; + this.sites[siteId] = candidateSite; + // Store session. + this.login(siteId); + this.eventsProvider.trigger(CoreEventsProvider.SITE_ADDED, info, siteId); - return siteId; + return siteId; + }); }); } else if (result == this.LEGACY_APP_VERSION) { let errorKey = 'core.login.legacymoodleversion', @@ -830,10 +884,10 @@ export class CoreSitesProvider { * Create a site from an entry of the sites list DB. The new site is added to the list of "cached" sites: this.sites. * * @param {any} entry Site list entry. - * @return {CoreSite} Created site. + * @return {Promise} Promised resolved with the created site. */ - makeSiteFromSiteListEntry(entry: any): CoreSite { - let site, + makeSiteFromSiteListEntry(entry: any): Promise { + let site: CoreSite, info = entry.info, config = entry.config; @@ -843,13 +897,13 @@ export class CoreSitesProvider { site = this.sitesFactory.makeSite(entry.id, entry.siteUrl, entry.token, info, entry.privateToken, config, entry.loggedOut == 1); - this.sites[entry.id] = site; - if (this.siteTablesSchemas.length) { - // Create tables in the site's database. - site.getDb().createTablesFromSchema(this.siteTablesSchemas); - } - return site; + return this.migrateSiteSchemas(site).then(() => { + // Set site after migrating schemas, or a call to getSite could get the site while tables are being created. + this.sites[entry.id] = site; + + return site; + }); } /** @@ -1178,9 +1232,11 @@ export class CoreSitesProvider { return this.appDB.getAllRecords(this.SITES_TABLE).then((siteEntries) => { const ids = []; + const promises = []; + siteEntries.forEach((site) => { if (!this.sites[site.id]) { - this.makeSiteFromSiteListEntry(site); + promises.push(this.makeSiteFromSiteListEntry(site)); } if (this.sites[site.id].containsUrl(url)) { @@ -1190,7 +1246,9 @@ export class CoreSitesProvider { } }); - return ids; + return Promise.all(promises).then(() => { + return ids; + }); }).catch(() => { // Shouldn't happen. return []; @@ -1294,4 +1352,70 @@ export class CoreSitesProvider { isLegacyMoodleByInfo(info: any): boolean { return this.isValidMoodleVersion(info) == this.LEGACY_APP_VERSION; } + + /** + * Register a site schema. + */ + registerSiteSchema(schema: CoreSiteSchema): void { + this.siteSchemas[schema.name] = schema; + } + + /** + * Install and upgrade all the registered schemas and tables. + * + * @param {CoreSite} site Site. + * @return {Promise} Promise resolved when done. + */ + migrateSiteSchemas(site: CoreSite): Promise { + const db = site.getDb(); + + if (this.siteSchemasMigration[site.id]) { + return this.siteSchemasMigration[site.id]; + } + + 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)); + } + + // Set installed version. + promise = promise.then(() => db.insertRecord(this.SCHEMA_VERSIONS_TABLE, {name, version: schema.version})); + + promises.push(promise); + } + + return Promise.all(promises); + }); + }); + + this.siteSchemasMigration[site.id] = promise; + + return promise.finally(() => { + delete this.siteSchemasMigration[site.id]; + }); + } } diff --git a/upgrade.txt b/upgrade.txt index 1e31823f2..bf90adbac 100644 --- a/upgrade.txt +++ b/upgrade.txt @@ -4,6 +4,7 @@ information provided here is intended especially for developers. === 3.6.1 === - The local notifications plugin was updated to its latest version. The new API has some breaking changes, so please check its documentation if you're using local notifications. Also, you need to run "npm install" to update the ionic-native library. +- New method CoreSitesProvider.registerSiteSchema allows to register table schemas and migration functions for site databases. Prefer this method over CoreSitesProvider.createTablesFromSchema: it supports schema migrations and it tracks the installed version of the schema, so it does not try to create the tables on every app boot. === 3.6.0 === From 9602746c9acc722f9999c23907e9accf1bb26b4f Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Wed, 30 Jan 2019 11:06:27 +0100 Subject: [PATCH 064/191] MOBILE-2842 data: Add "action" to primary key of the offline table --- src/addon/mod/data/providers/offline.ts | 97 +++++++++++++++---------- 1 file changed, 58 insertions(+), 39 deletions(-) diff --git a/src/addon/mod/data/providers/offline.ts b/src/addon/mod/data/providers/offline.ts index 460b053cf..c9ed26257 100644 --- a/src/addon/mod/data/providers/offline.ts +++ b/src/addon/mod/data/providers/offline.ts @@ -14,11 +14,11 @@ import { Injectable } from '@angular/core'; import { CoreLoggerProvider } from '@providers/logger'; -import { CoreSitesProvider } from '@providers/sites'; +import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreFileProvider } from '@providers/file'; import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader'; -import { SQLiteDBTableSchema } from '@classes/sqlitedb'; +import { SQLiteDB } from '@classes/sqlitedb'; /** * Service to handle Offline data. @@ -29,48 +29,67 @@ export class AddonModDataOfflineProvider { protected logger; // Variables for database. - static DATA_ENTRY_TABLE = 'addon_mod_data_entry'; - protected tablesSchema: SQLiteDBTableSchema[] = [ - { - name: AddonModDataOfflineProvider.DATA_ENTRY_TABLE, - columns: [ - { - name: 'dataid', - type: 'INTEGER' - }, - { - name: 'courseid', - type: 'INTEGER' - }, - { - name: 'groupid', - type: 'INTEGER' - }, - { - name: 'action', - type: 'TEXT' - }, - { - name: 'entryid', - type: 'INTEGER' - }, - { - name: 'fields', - type: 'TEXT' - }, - { - name: 'timemodified', - type: 'INTEGER' - } - ], - primaryKeys: ['dataid', 'entryid'] + static DATA_ENTRY_TABLE = 'addon_mod_data_entry_1'; + protected siteSchema: CoreSiteSchema = { + name: 'AddonModDataOfflineProvider', + version: 1, + tables: [ + { + name: AddonModDataOfflineProvider.DATA_ENTRY_TABLE, + columns: [ + { + name: 'dataid', + type: 'INTEGER' + }, + { + name: 'courseid', + type: 'INTEGER' + }, + { + name: 'groupid', + type: 'INTEGER' + }, + { + name: 'action', + type: 'TEXT' + }, + { + name: 'entryid', + type: 'INTEGER' + }, + { + name: 'fields', + type: 'TEXT' + }, + { + name: 'timemodified', + type: 'INTEGER' + } + ], + primaryKeys: ['dataid', 'entryid', 'action'] + } + ], + migrate(db: SQLiteDB, oldVersion: number): Promise | void { + if (oldVersion == 0) { + // Move the records from the old table. + const newTable = AddonModDataOfflineProvider.DATA_ENTRY_TABLE; + const oldTable = 'addon_mod_data_entry'; + + return db.tableExists(oldTable).then(() => { + return db.insertRecordsFrom(newTable, oldTable).then(() => { + return db.dropTable(oldTable); + }); + }).catch(() => { + // Old table does not exist, ignore. + }); + } } - ]; + }; constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private textUtils: CoreTextUtilsProvider, private fileProvider: CoreFileProvider, private fileUploaderProvider: CoreFileUploaderProvider) { this.logger = logger.getInstance('AddonModDataOfflineProvider'); - this.sitesProvider.createTablesFromSchema(this.tablesSchema); + this.sitesProvider.registerSiteSchema(this.siteSchema); } /** From 95326d4da0adf0f613411633c226c3e2ad99ba67 Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Fri, 1 Feb 2019 11:02:03 +0100 Subject: [PATCH 065/191] MOBILE-2842 core: Use site schemas for all modules --- src/addon/calendar/providers/calendar.ts | 187 ++++++------ .../messages/providers/messages-offline.ts | 117 ++++---- .../mod/assign/providers/assign-offline.ts | 197 +++++++------ src/addon/mod/choice/providers/offline.ts | 82 +++--- src/addon/mod/feedback/providers/offline.ts | 65 ++-- src/addon/mod/forum/providers/offline.ts | 189 ++++++------ src/addon/mod/glossary/providers/offline.ts | 97 +++--- .../mod/lesson/providers/lesson-offline.ts | 192 ++++++------ src/addon/mod/lesson/providers/lesson-sync.ts | 47 +-- src/addon/mod/lesson/providers/lesson.ts | 39 +-- .../accessrules/password/providers/handler.ts | 39 +-- src/addon/mod/quiz/providers/quiz-offline.ts | 98 ++++--- .../mod/scorm/providers/scorm-offline.ts | 174 +++++------ src/addon/mod/survey/providers/offline.ts | 73 ++--- src/addon/mod/wiki/providers/wiki-offline.ts | 111 +++---- src/addon/mod/workshop/providers/offline.ts | 277 +++++++++--------- src/addon/notes/providers/notes-offline.ts | 81 ++--- src/classes/site.ts | 74 ++--- src/core/course/providers/course-offline.ts | 65 ++-- src/core/course/providers/course.ts | 65 ++-- .../providers/module-prefetch-delegate.ts | 33 ++- src/core/emulator/providers/helper.ts | 49 ++-- src/core/question/providers/question.ts | 169 +++++------ src/core/user/providers/user.ts | 49 ++-- src/providers/filepool.ts | 226 +++++++------- src/providers/sites.ts | 45 +++ src/providers/sync.ts | 53 ++-- 27 files changed, 1495 insertions(+), 1398 deletions(-) diff --git a/src/addon/calendar/providers/calendar.ts b/src/addon/calendar/providers/calendar.ts index fda684ad5..ab5b3e301 100644 --- a/src/addon/calendar/providers/calendar.ts +++ b/src/addon/calendar/providers/calendar.ts @@ -14,7 +14,7 @@ import { Injectable } from '@angular/core'; import { CoreLoggerProvider } from '@providers/logger'; -import { CoreSitesProvider } from '@providers/sites'; +import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; import { CoreSite } from '@classes/site'; import { CoreCoursesProvider } from '@core/courses/providers/courses'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; @@ -23,7 +23,6 @@ import { CoreConstants } from '@core/constants'; import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; import { CoreConfigProvider } from '@providers/config'; import { ILocalNotification } from '@ionic-native/local-notifications'; -import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Service to handle calendar events. @@ -39,95 +38,99 @@ export class AddonCalendarProvider { // Variables for database. static EVENTS_TABLE = 'addon_calendar_events'; - protected tablesSchema: SQLiteDBTableSchema[] = [ - { - name: AddonCalendarProvider.EVENTS_TABLE, - columns: [ - { - name: 'id', - type: 'INTEGER', - primaryKey: true - }, - { - name: 'notificationtime', - type: 'INTEGER' - }, - { - name: 'name', - type: 'TEXT', - notNull: true - }, - { - name: 'description', - type: 'TEXT' - }, - { - name: 'format', - type: 'INTEGER' - }, - { - name: 'eventtype', - type: 'TEXT' - }, - { - name: 'courseid', - type: 'INTEGER' - }, - { - name: 'timestart', - type: 'INTEGER' - }, - { - name: 'timeduration', - type: 'INTEGER' - }, - { - name: 'categoryid', - type: 'INTEGER' - }, - { - name: 'groupid', - type: 'INTEGER' - }, - { - name: 'userid', - type: 'INTEGER' - }, - { - name: 'instance', - type: 'INTEGER' - }, - { - name: 'modulename', - type: 'TEXT' - }, - { - name: 'timemodified', - type: 'INTEGER' - }, - { - name: 'repeatid', - type: 'INTEGER' - }, - { - name: 'visible', - type: 'INTEGER' - }, - { - name: 'uuid', - type: 'TEXT' - }, - { - name: 'sequence', - type: 'INTEGER' - }, - { - name: 'subscriptionid', - type: 'INTEGER' - } - ] - } - ]; + protected siteSchema: CoreSiteSchema = { + name: 'AddonCalendarProvider', + version: 1, + tables: [ + { + name: AddonCalendarProvider.EVENTS_TABLE, + columns: [ + { + name: 'id', + type: 'INTEGER', + primaryKey: true + }, + { + name: 'notificationtime', + type: 'INTEGER' + }, + { + name: 'name', + type: 'TEXT', + notNull: true + }, + { + name: 'description', + type: 'TEXT' + }, + { + name: 'format', + type: 'INTEGER' + }, + { + name: 'eventtype', + type: 'TEXT' + }, + { + name: 'courseid', + type: 'INTEGER' + }, + { + name: 'timestart', + type: 'INTEGER' + }, + { + name: 'timeduration', + type: 'INTEGER' + }, + { + name: 'categoryid', + type: 'INTEGER' + }, + { + name: 'groupid', + type: 'INTEGER' + }, + { + name: 'userid', + type: 'INTEGER' + }, + { + name: 'instance', + type: 'INTEGER' + }, + { + name: 'modulename', + type: 'TEXT' + }, + { + name: 'timemodified', + type: 'INTEGER' + }, + { + name: 'repeatid', + type: 'INTEGER' + }, + { + name: 'visible', + type: 'INTEGER' + }, + { + name: 'uuid', + type: 'TEXT' + }, + { + name: 'sequence', + type: 'INTEGER' + }, + { + name: 'subscriptionid', + type: 'INTEGER' + } + ] + } + ] + }; protected logger; @@ -135,7 +138,7 @@ export class AddonCalendarProvider { private coursesProvider: CoreCoursesProvider, private timeUtils: CoreTimeUtilsProvider, private localNotificationsProvider: CoreLocalNotificationsProvider, private configProvider: CoreConfigProvider) { this.logger = logger.getInstance('AddonCalendarProvider'); - this.sitesProvider.createTablesFromSchema(this.tablesSchema); + this.sitesProvider.registerSiteSchema(this.siteSchema); } /** diff --git a/src/addon/messages/providers/messages-offline.ts b/src/addon/messages/providers/messages-offline.ts index bca52ffd7..38737c50e 100644 --- a/src/addon/messages/providers/messages-offline.ts +++ b/src/addon/messages/providers/messages-offline.ts @@ -14,10 +14,9 @@ import { Injectable } from '@angular/core'; import { CoreLoggerProvider } from '@providers/logger'; -import { CoreSitesProvider } from '@providers/sites'; +import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; import { CoreAppProvider } from '@providers/app'; import { CoreTextUtilsProvider } from '@providers/utils/text'; -import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Service to handle Offline messages. @@ -30,65 +29,69 @@ export class AddonMessagesOfflineProvider { // Variables for database. static MESSAGES_TABLE = 'addon_messages_offline_messages'; // When group messaging isn't available or a new conversation starts. static CONVERSATION_MESSAGES_TABLE = 'addon_messages_offline_conversation_messages'; // Conversation messages. - protected tablesSchema: SQLiteDBTableSchema[] = [ - { - name: AddonMessagesOfflineProvider.MESSAGES_TABLE, - columns: [ - { - name: 'touserid', - type: 'INTEGER' - }, - { - name: 'useridfrom', - type: 'INTEGER' - }, - { - name: 'smallmessage', - type: 'TEXT' - }, - { - name: 'timecreated', - type: 'INTEGER' - }, - { - name: 'deviceoffline', // If message was stored because device was offline. - type: 'INTEGER' - } - ], - primaryKeys: ['touserid', 'smallmessage', 'timecreated'] - }, - { - name: AddonMessagesOfflineProvider.CONVERSATION_MESSAGES_TABLE, - columns: [ - { - name: 'conversationid', - type: 'INTEGER' - }, - { - name: 'text', - type: 'TEXT' - }, - { - name: 'timecreated', - type: 'INTEGER' - }, - { - name: 'deviceoffline', // If message was stored because device was offline. - type: 'INTEGER' - }, - { - name: 'conversation', // Data about the conversation. - type: 'TEXT' - } - ], - primaryKeys: ['conversationid', 'text', 'timecreated'] - } - ]; + protected siteSchema: CoreSiteSchema = { + name: 'AddonMessagesOfflineProvider', + version: 1, + tables: [ + { + name: AddonMessagesOfflineProvider.MESSAGES_TABLE, + columns: [ + { + name: 'touserid', + type: 'INTEGER' + }, + { + name: 'useridfrom', + type: 'INTEGER' + }, + { + name: 'smallmessage', + type: 'TEXT' + }, + { + name: 'timecreated', + type: 'INTEGER' + }, + { + name: 'deviceoffline', // If message was stored because device was offline. + type: 'INTEGER' + } + ], + primaryKeys: ['touserid', 'smallmessage', 'timecreated'] + }, + { + name: AddonMessagesOfflineProvider.CONVERSATION_MESSAGES_TABLE, + columns: [ + { + name: 'conversationid', + type: 'INTEGER' + }, + { + name: 'text', + type: 'TEXT' + }, + { + name: 'timecreated', + type: 'INTEGER' + }, + { + name: 'deviceoffline', // If message was stored because device was offline. + type: 'INTEGER' + }, + { + name: 'conversation', // Data about the conversation. + type: 'TEXT' + } + ], + primaryKeys: ['conversationid', 'text', 'timecreated'] + } + ] + }; constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private appProvider: CoreAppProvider, private textUtils: CoreTextUtilsProvider) { this.logger = logger.getInstance('AddonMessagesOfflineProvider'); - this.sitesProvider.createTablesFromSchema(this.tablesSchema); + this.sitesProvider.registerSiteSchema(this.siteSchema); } /** diff --git a/src/addon/mod/assign/providers/assign-offline.ts b/src/addon/mod/assign/providers/assign-offline.ts index 39b32baf6..87d630837 100644 --- a/src/addon/mod/assign/providers/assign-offline.ts +++ b/src/addon/mod/assign/providers/assign-offline.ts @@ -15,10 +15,9 @@ import { Injectable } from '@angular/core'; import { CoreFileProvider } from '@providers/file'; import { CoreLoggerProvider } from '@providers/logger'; -import { CoreSitesProvider } from '@providers/sites'; +import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; -import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Service to handle offline assign. @@ -31,105 +30,109 @@ export class AddonModAssignOfflineProvider { // Variables for database. static SUBMISSIONS_TABLE = 'addon_mod_assign_submissions'; static SUBMISSIONS_GRADES_TABLE = 'addon_mod_assign_submissions_grading'; - protected tablesSchema: SQLiteDBTableSchema[] = [ - { - name: AddonModAssignOfflineProvider.SUBMISSIONS_TABLE, - columns: [ - { - name: 'assignid', - type: 'INTEGER' - }, - { - name: 'courseid', - type: 'INTEGER' - }, - { - name: 'userid', - type: 'INTEGER' - }, - { - name: 'plugindata', - type: 'TEXT' - }, - { - name: 'onlinetimemodified', - type: 'INTEGER' - }, - { - name: 'timecreated', - type: 'INTEGER' - }, - { - name: 'timemodified', - type: 'INTEGER' - }, - { - name: 'submitted', - type: 'INTEGER' - }, - { - name: 'submissionstatement', - type: 'INTEGER' - } - ], - primaryKeys: ['assignid', 'userid'] - }, - { - name: AddonModAssignOfflineProvider.SUBMISSIONS_GRADES_TABLE, - columns: [ - { - name: 'assignid', - type: 'INTEGER' - }, - { - name: 'courseid', - type: 'INTEGER' - }, - { - name: 'userid', - type: 'INTEGER' - }, - { - name: 'grade', - type: 'REAL' - }, - { - name: 'attemptnumber', - type: 'INTEGER' - }, - { - name: 'addattempt', - type: 'INTEGER' - }, - { - name: 'workflowstate', - type: 'TEXT' - }, - { - name: 'applytoall', - type: 'INTEGER' - }, - { - name: 'outcomes', - type: 'TEXT' - }, - { - name: 'plugindata', - type: 'TEXT' - }, - { - name: 'timemodified', - type: 'INTEGER' - } - ], - primaryKeys: ['assignid', 'userid'] - } - ]; + protected siteSchema: CoreSiteSchema = { + name: 'AddonModAssignOfflineProvider', + version: 1, + tables: [ + { + name: AddonModAssignOfflineProvider.SUBMISSIONS_TABLE, + columns: [ + { + name: 'assignid', + type: 'INTEGER' + }, + { + name: 'courseid', + type: 'INTEGER' + }, + { + name: 'userid', + type: 'INTEGER' + }, + { + name: 'plugindata', + type: 'TEXT' + }, + { + name: 'onlinetimemodified', + type: 'INTEGER' + }, + { + name: 'timecreated', + type: 'INTEGER' + }, + { + name: 'timemodified', + type: 'INTEGER' + }, + { + name: 'submitted', + type: 'INTEGER' + }, + { + name: 'submissionstatement', + type: 'INTEGER' + } + ], + primaryKeys: ['assignid', 'userid'] + }, + { + name: AddonModAssignOfflineProvider.SUBMISSIONS_GRADES_TABLE, + columns: [ + { + name: 'assignid', + type: 'INTEGER' + }, + { + name: 'courseid', + type: 'INTEGER' + }, + { + name: 'userid', + type: 'INTEGER' + }, + { + name: 'grade', + type: 'REAL' + }, + { + name: 'attemptnumber', + type: 'INTEGER' + }, + { + name: 'addattempt', + type: 'INTEGER' + }, + { + name: 'workflowstate', + type: 'TEXT' + }, + { + name: 'applytoall', + type: 'INTEGER' + }, + { + name: 'outcomes', + type: 'TEXT' + }, + { + name: 'plugindata', + type: 'TEXT' + }, + { + name: 'timemodified', + type: 'INTEGER' + } + ], + primaryKeys: ['assignid', 'userid'] + } + ] + }; constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private textUtils: CoreTextUtilsProvider, private fileProvider: CoreFileProvider, private timeUtils: CoreTimeUtilsProvider) { this.logger = logger.getInstance('AddonModAssignOfflineProvider'); - this.sitesProvider.createTablesFromSchema(this.tablesSchema); + this.sitesProvider.registerSiteSchema(this.siteSchema); } /** diff --git a/src/addon/mod/choice/providers/offline.ts b/src/addon/mod/choice/providers/offline.ts index dc8afd2fd..b4193f380 100644 --- a/src/addon/mod/choice/providers/offline.ts +++ b/src/addon/mod/choice/providers/offline.ts @@ -13,8 +13,7 @@ // limitations under the License. import { Injectable } from '@angular/core'; -import { CoreSitesProvider } from '@providers/sites'; -import { SQLiteDBTableSchema } from '@classes/sqlitedb'; +import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; /** * Service to handle offline choices. @@ -24,45 +23,50 @@ export class AddonModChoiceOfflineProvider { // Variables for database. static CHOICE_TABLE = 'addon_mod_choice_responses'; - protected tablesSchema: SQLiteDBTableSchema[] = [ - { - name: AddonModChoiceOfflineProvider.CHOICE_TABLE, - columns: [ - { - name: 'choiceid', - type: 'INTEGER' - }, - { - name: 'name', - type: 'TEXT' - }, - { - name: 'courseid', - type: 'INTEGER' - }, - { - name: 'userid', - type: 'INTEGER' - }, - { - name: 'responses', - type: 'TEXT' - }, - { - name: 'deleting', - type: 'INTEGER' - }, - { - name: 'timecreated', - type: 'INTEGER' - } - ], - primaryKeys: ['choiceid', 'userid'] - } - ]; + + protected siteSchema: CoreSiteSchema = { + name: 'AddonModChoiceOfflineProvider', + version: 1, + tables: [ + { + name: AddonModChoiceOfflineProvider.CHOICE_TABLE, + columns: [ + { + name: 'choiceid', + type: 'INTEGER' + }, + { + name: 'name', + type: 'TEXT' + }, + { + name: 'courseid', + type: 'INTEGER' + }, + { + name: 'userid', + type: 'INTEGER' + }, + { + name: 'responses', + type: 'TEXT' + }, + { + name: 'deleting', + type: 'INTEGER' + }, + { + name: 'timecreated', + type: 'INTEGER' + } + ], + primaryKeys: ['choiceid', 'userid'] + } + ] + }; constructor(private sitesProvider: CoreSitesProvider) { - this.sitesProvider.createTablesFromSchema(this.tablesSchema); + this.sitesProvider.registerSiteSchema(this.siteSchema); } /** diff --git a/src/addon/mod/feedback/providers/offline.ts b/src/addon/mod/feedback/providers/offline.ts index 80ea785f4..b9fd588ef 100644 --- a/src/addon/mod/feedback/providers/offline.ts +++ b/src/addon/mod/feedback/providers/offline.ts @@ -14,10 +14,9 @@ import { Injectable } from '@angular/core'; import { CoreLoggerProvider } from '@providers/logger'; -import { CoreSitesProvider } from '@providers/sites'; +import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; -import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Service to handle Offline feedback. @@ -29,39 +28,43 @@ export class AddonModFeedbackOfflineProvider { // Variables for database. static FEEDBACK_TABLE = 'addon_mod_feedback_answers'; - protected tablesSchema: SQLiteDBTableSchema[] = [ - { - name: AddonModFeedbackOfflineProvider.FEEDBACK_TABLE, - columns: [ - { - name: 'feedbackid', - type: 'INTEGER' - }, - { - name: 'page', - type: 'INTEGER' - }, - { - name: 'courseid', - type: 'INTEGER' - }, - { - name: 'responses', - type: 'TEXT' - }, - { - name: 'timemodified', - type: 'INTEGER' - } - ], - primaryKeys: ['feedbackid', 'page'] - } - ]; + protected siteSchema: CoreSiteSchema = { + name: 'AddonModFeedbackOfflineProvider', + version: 1, + tables: [ + { + name: AddonModFeedbackOfflineProvider.FEEDBACK_TABLE, + columns: [ + { + name: 'feedbackid', + type: 'INTEGER' + }, + { + name: 'page', + type: 'INTEGER' + }, + { + name: 'courseid', + type: 'INTEGER' + }, + { + name: 'responses', + type: 'TEXT' + }, + { + name: 'timemodified', + type: 'INTEGER' + } + ], + primaryKeys: ['feedbackid', 'page'] + } + ] + }; constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private textUtils: CoreTextUtilsProvider, private timeUtils: CoreTimeUtilsProvider) { this.logger = logger.getInstance('AddonModFeedbackOfflineProvider'); - this.sitesProvider.createTablesFromSchema(this.tablesSchema); + this.sitesProvider.registerSiteSchema(this.siteSchema); } /** diff --git a/src/addon/mod/forum/providers/offline.ts b/src/addon/mod/forum/providers/offline.ts index 4140897d4..a5223f786 100644 --- a/src/addon/mod/forum/providers/offline.ts +++ b/src/addon/mod/forum/providers/offline.ts @@ -14,9 +14,8 @@ import { Injectable } from '@angular/core'; import { CoreFileProvider } from '@providers/file'; -import { CoreSitesProvider } from '@providers/sites'; +import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; import { CoreTextUtilsProvider } from '@providers/utils/text'; -import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Service to handle offline forum. @@ -28,101 +27,105 @@ export class AddonModForumOfflineProvider { static DISCUSSIONS_TABLE = 'addon_mod_forum_discussions'; static REPLIES_TABLE = 'addon_mod_forum_replies'; - protected tablesSchema: SQLiteDBTableSchema[] = [ - { - name: AddonModForumOfflineProvider.DISCUSSIONS_TABLE, - columns: [ - { - name: 'forumid', - type: 'INTEGER', - }, - { - name: 'name', - type: 'TEXT', - }, - { - name: 'courseid', - type: 'INTEGER', - }, - { - name: 'subject', - type: 'TEXT', - }, - { - name: 'message', - type: 'TEXT', - }, - { - name: 'options', - type: 'TEXT', - }, - { - name: 'groupid', - type: 'INTEGER', - }, - { - name: 'userid', - type: 'INTEGER', - }, - { - name: 'timecreated', - type: 'INTEGER', - } - ], - primaryKeys: ['forumid', 'userid', 'timecreated'] - }, - { - name: AddonModForumOfflineProvider.REPLIES_TABLE, - columns: [ - { - name: 'postid', - type: 'INTEGER', - }, - { - name: 'discussionid', - type: 'INTEGER', - }, - { - name: 'forumid', - type: 'INTEGER', - }, - { - name: 'name', - type: 'TEXT', - }, - { - name: 'courseid', - type: 'INTEGER', - }, - { - name: 'subject', - type: 'TEXT', - }, - { - name: 'message', - type: 'TEXT', - }, - { - name: 'options', - type: 'TEXT', - }, - { - name: 'userid', - type: 'INTEGER', - }, - { - name: 'timecreated', - type: 'INTEGER', - } - ], - primaryKeys: ['postid', 'userid'] - } - ]; + protected siteSchema: CoreSiteSchema = { + name: 'AddonModForumOfflineProvider', + version: 1, + tables: [ + { + name: AddonModForumOfflineProvider.DISCUSSIONS_TABLE, + columns: [ + { + name: 'forumid', + type: 'INTEGER', + }, + { + name: 'name', + type: 'TEXT', + }, + { + name: 'courseid', + type: 'INTEGER', + }, + { + name: 'subject', + type: 'TEXT', + }, + { + name: 'message', + type: 'TEXT', + }, + { + name: 'options', + type: 'TEXT', + }, + { + name: 'groupid', + type: 'INTEGER', + }, + { + name: 'userid', + type: 'INTEGER', + }, + { + name: 'timecreated', + type: 'INTEGER', + } + ], + primaryKeys: ['forumid', 'userid', 'timecreated'] + }, + { + name: AddonModForumOfflineProvider.REPLIES_TABLE, + columns: [ + { + name: 'postid', + type: 'INTEGER', + }, + { + name: 'discussionid', + type: 'INTEGER', + }, + { + name: 'forumid', + type: 'INTEGER', + }, + { + name: 'name', + type: 'TEXT', + }, + { + name: 'courseid', + type: 'INTEGER', + }, + { + name: 'subject', + type: 'TEXT', + }, + { + name: 'message', + type: 'TEXT', + }, + { + name: 'options', + type: 'TEXT', + }, + { + name: 'userid', + type: 'INTEGER', + }, + { + name: 'timecreated', + type: 'INTEGER', + } + ], + primaryKeys: ['postid', 'userid'] + } + ] + }; constructor(private fileProvider: CoreFileProvider, private sitesProvider: CoreSitesProvider, private textUtils: CoreTextUtilsProvider) { - this.sitesProvider.createTablesFromSchema(this.tablesSchema); + this.sitesProvider.registerSiteSchema(this.siteSchema); } /** diff --git a/src/addon/mod/glossary/providers/offline.ts b/src/addon/mod/glossary/providers/offline.ts index 20b0ef53d..ffe5aef84 100644 --- a/src/addon/mod/glossary/providers/offline.ts +++ b/src/addon/mod/glossary/providers/offline.ts @@ -14,10 +14,9 @@ import { Injectable } from '@angular/core'; import { CoreFileProvider } from '@providers/file'; -import { CoreSitesProvider } from '@providers/sites'; +import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreTextUtilsProvider } from '@providers/utils/text'; -import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Service to handle offline glossary. @@ -28,56 +27,60 @@ export class AddonModGlossaryOfflineProvider { // Variables for database. static ENTRIES_TABLE = 'addon_mod_glossary_entrues'; - protected tablesSchema: SQLiteDBTableSchema[] = [ - { - name: AddonModGlossaryOfflineProvider.ENTRIES_TABLE, - columns: [ - { - name: 'glossaryid', - type: 'INTEGER', - }, - { - name: 'courseid', - type: 'INTEGER', - }, - { - name: 'concept', - type: 'TEXT', - }, - { - name: 'definition', - type: 'TEXT', - }, - { - name: 'definitionformat', - type: 'TEXT', - }, - { - name: 'userid', - type: 'INTEGER', - }, - { - name: 'timecreated', - type: 'INTEGER', - }, - { - name: 'options', - type: 'TEXT', - }, - { - name: 'attachments', - type: 'TEXT', - }, - ], - primaryKeys: ['glossaryid', 'concept', 'timecreated'] - } - ]; + protected siteSchema: CoreSiteSchema = { + name: 'AddonModGlossaryOfflineProvider', + version: 1, + tables: [ + { + name: AddonModGlossaryOfflineProvider.ENTRIES_TABLE, + columns: [ + { + name: 'glossaryid', + type: 'INTEGER', + }, + { + name: 'courseid', + type: 'INTEGER', + }, + { + name: 'concept', + type: 'TEXT', + }, + { + name: 'definition', + type: 'TEXT', + }, + { + name: 'definitionformat', + type: 'TEXT', + }, + { + name: 'userid', + type: 'INTEGER', + }, + { + name: 'timecreated', + type: 'INTEGER', + }, + { + name: 'options', + type: 'TEXT', + }, + { + name: 'attachments', + type: 'TEXT', + }, + ], + primaryKeys: ['glossaryid', 'concept', 'timecreated'] + } + ] + }; constructor(private fileProvider: CoreFileProvider, private sitesProvider: CoreSitesProvider, private textUtils: CoreTextUtilsProvider, private utils: CoreUtilsProvider) { - this.sitesProvider.createTablesFromSchema(this.tablesSchema); + this.sitesProvider.registerSiteSchema(this.siteSchema); } /** diff --git a/src/addon/mod/lesson/providers/lesson-offline.ts b/src/addon/mod/lesson/providers/lesson-offline.ts index 32dcdc74c..1d7882461 100644 --- a/src/addon/mod/lesson/providers/lesson-offline.ts +++ b/src/addon/mod/lesson/providers/lesson-offline.ts @@ -14,11 +14,10 @@ import { Injectable } from '@angular/core'; import { CoreLoggerProvider } from '@providers/logger'; -import { CoreSitesProvider } from '@providers/sites'; +import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreUtilsProvider } from '@providers/utils/utils'; -import { SQLiteDBTableSchema } from '@classes/sqlitedb'; import { AddonModLessonProvider } from './lesson'; /** @@ -32,103 +31,108 @@ export class AddonModLessonOfflineProvider { // Variables for database. We use lowercase in the names to match the WS responses. static RETAKES_TABLE = 'addon_mod_lesson_retakes'; static PAGE_ATTEMPTS_TABLE = 'addon_mod_lesson_page_attempts'; - protected tablesSchema: SQLiteDBTableSchema[] = [ - { - name: AddonModLessonOfflineProvider.RETAKES_TABLE, - columns: [ - { - name: 'lessonid', - type: 'INTEGER', - primaryKey: true // Only 1 offline retake per lesson. - }, - { - name: 'retake', // Retake number. - type: 'INTEGER', - notNull: true - }, - { - name: 'courseid', - type: 'INTEGER' - }, - { - name: 'finished', - type: 'INTEGER' - }, - { - name: 'outoftime', - type: 'INTEGER' - }, - { - name: 'timemodified', - type: 'INTEGER' - }, - { - name: 'lastquestionpage', - type: 'INTEGER' - }, - ] - }, - { - name: AddonModLessonOfflineProvider.PAGE_ATTEMPTS_TABLE, - columns: [ - { - name: 'lessonid', - type: 'INTEGER', - notNull: true - }, - { - name: 'retake', // Retake number. - type: 'INTEGER', - notNull: true - }, - { - name: 'pageid', - type: 'INTEGER', - notNull: true - }, - { - name: 'timemodified', - type: 'INTEGER', - notNull: true - }, - { - name: 'courseid', - type: 'INTEGER' - }, - { - name: 'data', - type: 'TEXT' - }, - { - name: 'type', - type: 'INTEGER' - }, - { - name: 'newpageid', - type: 'INTEGER' - }, - { - name: 'correct', - type: 'INTEGER' - }, - { - name: 'answerid', - type: 'INTEGER' - }, - { - name: 'useranswer', - type: 'TEXT' - }, - ], - primaryKeys: ['lessonid', 'retake', 'pageid', 'timemodified'] // A user can attempt several times per page and retake. - } - ]; + protected siteSchema: CoreSiteSchema = { + name: 'AddonModLessonOfflineProvider', + version: 1, + tables: [ + { + name: AddonModLessonOfflineProvider.RETAKES_TABLE, + columns: [ + { + name: 'lessonid', + type: 'INTEGER', + primaryKey: true // Only 1 offline retake per lesson. + }, + { + name: 'retake', // Retake number. + type: 'INTEGER', + notNull: true + }, + { + name: 'courseid', + type: 'INTEGER' + }, + { + name: 'finished', + type: 'INTEGER' + }, + { + name: 'outoftime', + type: 'INTEGER' + }, + { + name: 'timemodified', + type: 'INTEGER' + }, + { + name: 'lastquestionpage', + type: 'INTEGER' + }, + ] + }, + { + name: AddonModLessonOfflineProvider.PAGE_ATTEMPTS_TABLE, + columns: [ + { + name: 'lessonid', + type: 'INTEGER', + notNull: true + }, + { + name: 'retake', // Retake number. + type: 'INTEGER', + notNull: true + }, + { + name: 'pageid', + type: 'INTEGER', + notNull: true + }, + { + name: 'timemodified', + type: 'INTEGER', + notNull: true + }, + { + name: 'courseid', + type: 'INTEGER' + }, + { + name: 'data', + type: 'TEXT' + }, + { + name: 'type', + type: 'INTEGER' + }, + { + name: 'newpageid', + type: 'INTEGER' + }, + { + name: 'correct', + type: 'INTEGER' + }, + { + name: 'answerid', + type: 'INTEGER' + }, + { + name: 'useranswer', + type: 'TEXT' + }, + ], + // A user can attempt several times per page and retake. + primaryKeys: ['lessonid', 'retake', 'pageid', 'timemodified'] + } + ] + }; constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private timeUtils: CoreTimeUtilsProvider, private textUtils: CoreTextUtilsProvider, private utils: CoreUtilsProvider) { this.logger = logger.getInstance('AddonModLessonOfflineProvider'); - this.sitesProvider.createTablesFromSchema(this.tablesSchema); + this.sitesProvider.registerSiteSchema(this.siteSchema); } /** diff --git a/src/addon/mod/lesson/providers/lesson-sync.ts b/src/addon/mod/lesson/providers/lesson-sync.ts index 1e250f951..1dfafc0d4 100644 --- a/src/addon/mod/lesson/providers/lesson-sync.ts +++ b/src/addon/mod/lesson/providers/lesson-sync.ts @@ -17,7 +17,7 @@ import { TranslateService } from '@ngx-translate/core'; import { CoreAppProvider } from '@providers/app'; import { CoreEventsProvider } from '@providers/events'; import { CoreLoggerProvider } from '@providers/logger'; -import { CoreSitesProvider } from '@providers/sites'; +import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; import { CoreSyncProvider } from '@providers/sync'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; @@ -25,7 +25,6 @@ import { CoreUrlUtilsProvider } from '@providers/utils/url'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreSyncBaseProvider } from '@classes/base-sync'; -import { SQLiteDBTableSchema } from '@classes/sqlitedb'; import { AddonModLessonProvider } from './lesson'; import { AddonModLessonOfflineProvider } from './lesson-offline'; import { AddonModLessonPrefetchHandler } from './prefetch-handler'; @@ -59,25 +58,31 @@ export class AddonModLessonSyncProvider extends CoreSyncBaseProvider { // Variables for database. static RETAKES_FINISHED_TABLE = 'addon_mod_lesson_retakes_finished_sync'; - protected tablesSchema: SQLiteDBTableSchema = { - name: AddonModLessonSyncProvider.RETAKES_FINISHED_TABLE, - columns: [ + protected siteSchema: CoreSiteSchema = { + name: 'AddonModLessonSyncProvider', + version: 1, + tables: [ { - name: 'lessonid', - type: 'INTEGER', - primaryKey: true - }, - { - name: 'retake', - type: 'INTEGER' - }, - { - name: 'pageid', - type: 'INTEGER' - }, - { - name: 'timefinished', - type: 'INTEGER' + name: AddonModLessonSyncProvider.RETAKES_FINISHED_TABLE, + columns: [ + { + name: 'lessonid', + type: 'INTEGER', + primaryKey: true + }, + { + name: 'retake', + type: 'INTEGER' + }, + { + name: 'pageid', + type: 'INTEGER' + }, + { + name: 'timefinished', + type: 'INTEGER' + } + ] } ] }; @@ -94,7 +99,7 @@ export class AddonModLessonSyncProvider extends CoreSyncBaseProvider { this.componentTranslate = courseProvider.translateModuleName('lesson'); - this.sitesProvider.createTableFromSchema(this.tablesSchema); + this.sitesProvider.registerSiteSchema(this.siteSchema); } /** diff --git a/src/addon/mod/lesson/providers/lesson.ts b/src/addon/mod/lesson/providers/lesson.ts index e2343fbcb..3204147b5 100644 --- a/src/addon/mod/lesson/providers/lesson.ts +++ b/src/addon/mod/lesson/providers/lesson.ts @@ -15,13 +15,12 @@ import { Injectable } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { CoreLoggerProvider } from '@providers/logger'; -import { CoreSitesProvider } from '@providers/sites'; +import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreGradesProvider } from '@core/grades/providers/grades'; import { CoreSiteWSPreSets } from '@classes/site'; -import { SQLiteDBTableSchema } from '@classes/sqlitedb'; import { AddonModLessonOfflineProvider } from './lesson-offline'; /** @@ -156,21 +155,27 @@ export class AddonModLessonProvider { // Variables for database. static PASSWORD_TABLE = 'addon_mod_lesson_password'; - protected tablesSchema: SQLiteDBTableSchema = { - name: AddonModLessonProvider.PASSWORD_TABLE, - columns: [ + protected siteSchema: CoreSiteSchema = { + name: 'AddonModLessonProvider', + version: 1, + tables: [ { - name: 'lessonid', - type: 'INTEGER', - primaryKey: true - }, - { - name: 'password', - type: 'TEXT' - }, - { - name: 'timemodified', - type: 'INTEGER' + name: AddonModLessonProvider.PASSWORD_TABLE, + columns: [ + { + name: 'lessonid', + type: 'INTEGER', + primaryKey: true + }, + { + name: 'password', + type: 'TEXT' + }, + { + name: 'timemodified', + type: 'INTEGER' + } + ] } ] }; @@ -183,7 +188,7 @@ export class AddonModLessonProvider { private lessonOfflineProvider: AddonModLessonOfflineProvider) { this.logger = logger.getInstance('AddonModLessonProvider'); - this.sitesProvider.createTableFromSchema(this.tablesSchema); + this.sitesProvider.registerSiteSchema(this.siteSchema); } /** diff --git a/src/addon/mod/quiz/accessrules/password/providers/handler.ts b/src/addon/mod/quiz/accessrules/password/providers/handler.ts index 52b482eea..be42e7591 100644 --- a/src/addon/mod/quiz/accessrules/password/providers/handler.ts +++ b/src/addon/mod/quiz/accessrules/password/providers/handler.ts @@ -14,10 +14,9 @@ // limitations under the License. import { Injectable, Injector } from '@angular/core'; -import { CoreSitesProvider } from '@providers/sites'; +import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; import { AddonModQuizAccessRuleHandler } from '../../../providers/access-rules-delegate'; import { AddonModQuizAccessPasswordComponent } from '../component/password'; -import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Handler to support password access rule. @@ -26,21 +25,27 @@ import { SQLiteDBTableSchema } from '@classes/sqlitedb'; export class AddonModQuizAccessPasswordHandler implements AddonModQuizAccessRuleHandler { // Variables for database. static PASSWORD_TABLE = 'addon_mod_quiz_access_password'; - protected tableSchema: SQLiteDBTableSchema = { - name: AddonModQuizAccessPasswordHandler.PASSWORD_TABLE, - columns: [ + protected siteSchema: CoreSiteSchema = { + name: 'AddonModQuizAccessPasswordHandler', + version: 1, + tables: [ { - name: 'id', - type: 'INTEGER', - primaryKey: true - }, - { - name: 'password', - type: 'TEXT' - }, - { - name: 'timemodified', - type: 'INTEGER' + name: AddonModQuizAccessPasswordHandler.PASSWORD_TABLE, + columns: [ + { + name: 'id', + type: 'INTEGER', + primaryKey: true + }, + { + name: 'password', + type: 'TEXT' + }, + { + name: 'timemodified', + type: 'INTEGER' + } + ] } ] }; @@ -49,7 +54,7 @@ export class AddonModQuizAccessPasswordHandler implements AddonModQuizAccessRule ruleName = 'quizaccess_password'; constructor(private sitesProvider: CoreSitesProvider) { - this.sitesProvider.createTableFromSchema(this.tableSchema); + this.sitesProvider.registerSiteSchema(this.siteSchema); } /** diff --git a/src/addon/mod/quiz/providers/quiz-offline.ts b/src/addon/mod/quiz/providers/quiz-offline.ts index 5d21446d1..b7fe048d2 100644 --- a/src/addon/mod/quiz/providers/quiz-offline.ts +++ b/src/addon/mod/quiz/providers/quiz-offline.ts @@ -15,13 +15,13 @@ import { Injectable } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { CoreLoggerProvider } from '@providers/logger'; -import { CoreSitesProvider } from '@providers/sites'; +import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreQuestionProvider } from '@core/question/providers/question'; import { CoreQuestionBehaviourDelegate } from '@core/question/providers/behaviour-delegate'; import { AddonModQuizProvider } from './quiz'; -import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb'; +import { SQLiteDB } from '@classes/sqlitedb'; /** * Service to handle offline quiz. @@ -33,57 +33,61 @@ export class AddonModQuizOfflineProvider { // Variables for database. static ATTEMPTS_TABLE = 'addon_mod_quiz_attempts'; - protected tablesSchema: SQLiteDBTableSchema[] = [ - { - name: AddonModQuizOfflineProvider.ATTEMPTS_TABLE, - columns: [ - { - name: 'id', // Attempt ID. - type: 'INTEGER', - primaryKey: true - }, - { - name: 'attempt', // Attempt number. - type: 'INTEGER' - }, - { - name: 'courseid', - type: 'INTEGER' - }, - { - name: 'userid', - type: 'INTEGER' - }, - { - name: 'quizid', - type: 'INTEGER' - }, - { - name: 'currentpage', - type: 'INTEGER' - }, - { - name: 'timecreated', - type: 'INTEGER' - }, - { - name: 'timemodified', - type: 'INTEGER' - }, - { - name: 'finished', - type: 'INTEGER' - } - ] - } - ]; + protected siteSchema: CoreSiteSchema = { + name: 'AddonModQuizOfflineProvider', + version: 1, + tables: [ + { + name: AddonModQuizOfflineProvider.ATTEMPTS_TABLE, + columns: [ + { + name: 'id', // Attempt ID. + type: 'INTEGER', + primaryKey: true + }, + { + name: 'attempt', // Attempt number. + type: 'INTEGER' + }, + { + name: 'courseid', + type: 'INTEGER' + }, + { + name: 'userid', + type: 'INTEGER' + }, + { + name: 'quizid', + type: 'INTEGER' + }, + { + name: 'currentpage', + type: 'INTEGER' + }, + { + name: 'timecreated', + type: 'INTEGER' + }, + { + name: 'timemodified', + type: 'INTEGER' + }, + { + name: 'finished', + type: 'INTEGER' + } + ] + } + ] + }; constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private timeUtils: CoreTimeUtilsProvider, private questionProvider: CoreQuestionProvider, private translate: TranslateService, private utils: CoreUtilsProvider, private behaviourDelegate: CoreQuestionBehaviourDelegate) { this.logger = logger.getInstance('AddonModQuizOfflineProvider'); - this.sitesProvider.createTablesFromSchema(this.tablesSchema); + this.sitesProvider.registerSiteSchema(this.siteSchema); } /** diff --git a/src/addon/mod/scorm/providers/scorm-offline.ts b/src/addon/mod/scorm/providers/scorm-offline.ts index 9bfdd2c79..74af07260 100644 --- a/src/addon/mod/scorm/providers/scorm-offline.ts +++ b/src/addon/mod/scorm/providers/scorm-offline.ts @@ -14,14 +14,14 @@ import { Injectable } from '@angular/core'; import { CoreLoggerProvider } from '@providers/logger'; -import { CoreSitesProvider } from '@providers/sites'; +import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; import { CoreSyncProvider } from '@providers/sync'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreUserProvider } from '@core/user/providers/user'; import { AddonModScormProvider } from './scorm'; -import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb'; +import { SQLiteDB } from '@classes/sqlitedb'; /** * Service to handle offline SCORM. @@ -34,95 +34,99 @@ export class AddonModScormOfflineProvider { // Variables for database. static ATTEMPTS_TABLE = 'addon_mod_scorm_offline_attempts'; static TRACKS_TABLE = 'addon_mod_scorm_offline_scos_tracks'; - protected tablesSchema: SQLiteDBTableSchema[] = [ - { - name: AddonModScormOfflineProvider.ATTEMPTS_TABLE, - columns: [ - { - name: 'scormid', - type: 'INTEGER', - notNull: true - }, - { - name: 'attempt', // Attempt number. - type: 'INTEGER', - notNull: true - }, - { - name: 'userid', - type: 'INTEGER', - notNull: true - }, - { - name: 'courseid', - type: 'INTEGER' - }, - { - name: 'timecreated', - type: 'INTEGER' - }, - { - name: 'timemodified', - type: 'INTEGER' - }, - { - name: 'snapshot', - type: 'TEXT' - }, - ], - primaryKeys: ['scormid', 'userid', 'attempt'] - }, - { - name: AddonModScormOfflineProvider.TRACKS_TABLE, - columns: [ - { - name: 'scormid', - type: 'INTEGER', - notNull: true - }, - { - name: 'attempt', // Attempt number. - type: 'INTEGER', - notNull: true - }, - { - name: 'userid', - type: 'INTEGER', - notNull: true - }, - { - name: 'scoid', - type: 'INTEGER', - notNull: true - }, - { - name: 'element', - type: 'TEXT', - notNull: true - }, - { - name: 'value', - type: 'TEXT' - }, - { - name: 'timemodified', - type: 'INTEGER' - }, - { - name: 'synced', - type: 'INTEGER' - }, - ], - primaryKeys: ['scormid', 'userid', 'attempt', 'scoid', 'element'] - } - ]; + protected siteSchema: CoreSiteSchema = { + name: 'AddonModScormOfflineProvider', + version: 1, + tables: [ + { + name: AddonModScormOfflineProvider.ATTEMPTS_TABLE, + columns: [ + { + name: 'scormid', + type: 'INTEGER', + notNull: true + }, + { + name: 'attempt', // Attempt number. + type: 'INTEGER', + notNull: true + }, + { + name: 'userid', + type: 'INTEGER', + notNull: true + }, + { + name: 'courseid', + type: 'INTEGER' + }, + { + name: 'timecreated', + type: 'INTEGER' + }, + { + name: 'timemodified', + type: 'INTEGER' + }, + { + name: 'snapshot', + type: 'TEXT' + }, + ], + primaryKeys: ['scormid', 'userid', 'attempt'] + }, + { + name: AddonModScormOfflineProvider.TRACKS_TABLE, + columns: [ + { + name: 'scormid', + type: 'INTEGER', + notNull: true + }, + { + name: 'attempt', // Attempt number. + type: 'INTEGER', + notNull: true + }, + { + name: 'userid', + type: 'INTEGER', + notNull: true + }, + { + name: 'scoid', + type: 'INTEGER', + notNull: true + }, + { + name: 'element', + type: 'TEXT', + notNull: true + }, + { + name: 'value', + type: 'TEXT' + }, + { + name: 'timemodified', + type: 'INTEGER' + }, + { + name: 'synced', + type: 'INTEGER' + }, + ], + primaryKeys: ['scormid', 'userid', 'attempt', 'scoid', 'element'] + } + ] + }; constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private timeUtils: CoreTimeUtilsProvider, private syncProvider: CoreSyncProvider, private utils: CoreUtilsProvider, private textUtils: CoreTextUtilsProvider, private userProvider: CoreUserProvider) { this.logger = logger.getInstance('AddonModScormOfflineProvider'); - this.sitesProvider.createTablesFromSchema(this.tablesSchema); + this.sitesProvider.registerSiteSchema(this.siteSchema); } /** diff --git a/src/addon/mod/survey/providers/offline.ts b/src/addon/mod/survey/providers/offline.ts index 61d04a315..5e3185315 100644 --- a/src/addon/mod/survey/providers/offline.ts +++ b/src/addon/mod/survey/providers/offline.ts @@ -14,9 +14,8 @@ import { Injectable } from '@angular/core'; import { CoreLoggerProvider } from '@providers/logger'; -import { CoreSitesProvider } from '@providers/sites'; +import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; import { CoreTextUtilsProvider } from '@providers/utils/text'; -import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Service to handle Offline survey. @@ -28,42 +27,46 @@ export class AddonModSurveyOfflineProvider { // Variables for database. static SURVEY_TABLE = 'addon_mod_survey_answers'; - protected tablesSchema: SQLiteDBTableSchema[] = [ - { - name: AddonModSurveyOfflineProvider.SURVEY_TABLE, - columns: [ - { - name: 'surveyid', - type: 'INTEGER' - }, - { - name: 'name', - type: 'TEXT' - }, - { - name: 'courseid', - type: 'INTEGER' - }, - { - name: 'userid', - type: 'INTEGER' - }, - { - name: 'answers', - type: 'TEXT' - }, - { - name: 'timecreated', - type: 'INTEGER' - } - ], - primaryKeys: ['surveyid', 'userid'] - } - ]; + protected siteSchema: CoreSiteSchema = { + name: 'AddonModSurveyOfflineProvider', + version: 1, + tables: [ + { + name: AddonModSurveyOfflineProvider.SURVEY_TABLE, + columns: [ + { + name: 'surveyid', + type: 'INTEGER' + }, + { + name: 'name', + type: 'TEXT' + }, + { + name: 'courseid', + type: 'INTEGER' + }, + { + name: 'userid', + type: 'INTEGER' + }, + { + name: 'answers', + type: 'TEXT' + }, + { + name: 'timecreated', + type: 'INTEGER' + } + ], + primaryKeys: ['surveyid', 'userid'] + } + ] + }; constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private textUtils: CoreTextUtilsProvider) { this.logger = logger.getInstance('AddonModSurveyOfflineProvider'); - this.sitesProvider.createTablesFromSchema(this.tablesSchema); + this.sitesProvider.registerSiteSchema(this.siteSchema); } /** diff --git a/src/addon/mod/wiki/providers/wiki-offline.ts b/src/addon/mod/wiki/providers/wiki-offline.ts index d9407e2dc..003fa3b7a 100644 --- a/src/addon/mod/wiki/providers/wiki-offline.ts +++ b/src/addon/mod/wiki/providers/wiki-offline.ts @@ -14,8 +14,7 @@ import { Injectable } from '@angular/core'; import { CoreLoggerProvider } from '@providers/logger'; -import { CoreSitesProvider } from '@providers/sites'; -import { SQLiteDBTableSchema } from '@classes/sqlitedb'; +import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; /** * Service to handle offline wiki. @@ -27,62 +26,66 @@ export class AddonModWikiOfflineProvider { // Variables for database. static NEW_PAGES_TABLE = 'addon_mod_wiki_new_pages_store'; - protected tablesSchema: SQLiteDBTableSchema[] = [ - { - name: AddonModWikiOfflineProvider.NEW_PAGES_TABLE, - columns: [ + protected siteSchema: CoreSiteSchema = { + name: 'AddonModWikiOfflineProvider', + version: 1, + tables: [ { - name: 'wikiid', - type: 'INTEGER' - }, - { - name: 'subwikiid', - type: 'INTEGER' - }, - { - name: 'userid', - type: 'INTEGER' - }, - { - name: 'groupid', - type: 'INTEGER' - }, - { - name: 'title', - type: 'TEXT' - }, - { - name: 'cachedcontent', - type: 'TEXT' - }, - { - name: 'contentformat', - type: 'TEXT' - }, - { - name: 'courseid', - type: 'INTEGER' - }, - { - name: 'timecreated', - type: 'INTEGER' - }, - { - name: 'timemodified', - type: 'INTEGER' - }, - { - name: 'caneditpage', - type: 'INTEGER' - } - ], - primaryKeys: ['wikiid', 'subwikiid', 'userid', 'groupid', 'title'] - } - ]; + name: AddonModWikiOfflineProvider.NEW_PAGES_TABLE, + columns: [ + { + name: 'wikiid', + type: 'INTEGER' + }, + { + name: 'subwikiid', + type: 'INTEGER' + }, + { + name: 'userid', + type: 'INTEGER' + }, + { + name: 'groupid', + type: 'INTEGER' + }, + { + name: 'title', + type: 'TEXT' + }, + { + name: 'cachedcontent', + type: 'TEXT' + }, + { + name: 'contentformat', + type: 'TEXT' + }, + { + name: 'courseid', + type: 'INTEGER' + }, + { + name: 'timecreated', + type: 'INTEGER' + }, + { + name: 'timemodified', + type: 'INTEGER' + }, + { + name: 'caneditpage', + type: 'INTEGER' + } + ], + primaryKeys: ['wikiid', 'subwikiid', 'userid', 'groupid', 'title'] + } + ] + }; constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider) { this.logger = logger.getInstance('AddonModWikiOfflineProvider'); - this.sitesProvider.createTablesFromSchema(this.tablesSchema); + this.sitesProvider.registerSiteSchema(this.siteSchema); } /** diff --git a/src/addon/mod/workshop/providers/offline.ts b/src/addon/mod/workshop/providers/offline.ts index c5c8c78d8..dcd7f1adc 100644 --- a/src/addon/mod/workshop/providers/offline.ts +++ b/src/addon/mod/workshop/providers/offline.ts @@ -14,10 +14,9 @@ import { Injectable } from '@angular/core'; import { CoreFileProvider } from '@providers/file'; -import { CoreSitesProvider } from '@providers/sites'; +import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; -import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Service to handle offline workshop. @@ -31,146 +30,150 @@ export class AddonModWorkshopOfflineProvider { static EVALUATE_SUBMISSIONS_TABLE = 'addon_mod_workshop_evaluate_submissions'; static EVALUATE_ASSESSMENTS_TABLE = 'addon_mod_workshop_evaluate_assessments'; - protected tablesSchema: SQLiteDBTableSchema[] = [ - { - name: AddonModWorkshopOfflineProvider.SUBMISSIONS_TABLE, - columns: [ - { - name: 'workshopid', - type: 'INTEGER', - }, - { - name: 'submissionid', - type: 'INTEGER', - }, - { - name: 'action', - type: 'TEXT', - }, - { - name: 'courseid', - type: 'INTEGER', - }, - { - name: 'title', - type: 'TEXT', - }, - { - name: 'content', - type: 'TEXT', - }, - { - name: 'attachmentsid', - type: 'TEXT', - }, - { - name: 'timemodified', - type: 'INTEGER', - } - ], - primaryKeys: ['workshopid', 'submissionid', 'action'] - }, - { - name: AddonModWorkshopOfflineProvider.ASSESSMENTS_TABLE, - columns: [ - { - name: 'workshopid', - type: 'INTEGER', - }, - { - name: 'assessmentid', - type: 'INTEGER', - }, - { - name: 'courseid', - type: 'INTEGER', - }, - { - name: 'inputdata', - type: 'TEXT', - }, - { - name: 'timemodified', - type: 'INTEGER', - }, - ], - primaryKeys: ['workshopid', 'assessmentid'] - }, - { - name: AddonModWorkshopOfflineProvider.EVALUATE_SUBMISSIONS_TABLE, - columns: [ - { - name: 'workshopid', - type: 'INTEGER', - }, - { - name: 'submissionid', - type: 'INTEGER', - }, - { - name: 'courseid', - type: 'INTEGER', - }, - { - name: 'timemodified', - type: 'INTEGER', - }, - { - name: 'feedbacktext', - type: 'TEXT', - }, - { - name: 'published', - type: 'INTEGER', - }, - { - name: 'gradeover', - type: 'TEXT', - }, - ], - primaryKeys: ['workshopid', 'submissionid'] - }, - { - name: AddonModWorkshopOfflineProvider.EVALUATE_ASSESSMENTS_TABLE, - columns: [ - { - name: 'workshopid', - type: 'INTEGER', - }, - { - name: 'assessmentid', - type: 'INTEGER', - }, - { - name: 'courseid', - type: 'INTEGER', - }, - { - name: 'timemodified', - type: 'INTEGER', - }, - { - name: 'feedbacktext', - type: 'TEXT', - }, - { - name: 'weight', - type: 'INTEGER', - }, - { - name: 'gradinggradeover', - type: 'TEXT', - }, - ], - primaryKeys: ['workshopid', 'assessmentid'] - } - ]; + protected siteSchema: CoreSiteSchema = { + name: 'AddonModWorkshopOfflineProvider', + version: 1, + tables: [ + { + name: AddonModWorkshopOfflineProvider.SUBMISSIONS_TABLE, + columns: [ + { + name: 'workshopid', + type: 'INTEGER', + }, + { + name: 'submissionid', + type: 'INTEGER', + }, + { + name: 'action', + type: 'TEXT', + }, + { + name: 'courseid', + type: 'INTEGER', + }, + { + name: 'title', + type: 'TEXT', + }, + { + name: 'content', + type: 'TEXT', + }, + { + name: 'attachmentsid', + type: 'TEXT', + }, + { + name: 'timemodified', + type: 'INTEGER', + } + ], + primaryKeys: ['workshopid', 'submissionid', 'action'] + }, + { + name: AddonModWorkshopOfflineProvider.ASSESSMENTS_TABLE, + columns: [ + { + name: 'workshopid', + type: 'INTEGER', + }, + { + name: 'assessmentid', + type: 'INTEGER', + }, + { + name: 'courseid', + type: 'INTEGER', + }, + { + name: 'inputdata', + type: 'TEXT', + }, + { + name: 'timemodified', + type: 'INTEGER', + }, + ], + primaryKeys: ['workshopid', 'assessmentid'] + }, + { + name: AddonModWorkshopOfflineProvider.EVALUATE_SUBMISSIONS_TABLE, + columns: [ + { + name: 'workshopid', + type: 'INTEGER', + }, + { + name: 'submissionid', + type: 'INTEGER', + }, + { + name: 'courseid', + type: 'INTEGER', + }, + { + name: 'timemodified', + type: 'INTEGER', + }, + { + name: 'feedbacktext', + type: 'TEXT', + }, + { + name: 'published', + type: 'INTEGER', + }, + { + name: 'gradeover', + type: 'TEXT', + }, + ], + primaryKeys: ['workshopid', 'submissionid'] + }, + { + name: AddonModWorkshopOfflineProvider.EVALUATE_ASSESSMENTS_TABLE, + columns: [ + { + name: 'workshopid', + type: 'INTEGER', + }, + { + name: 'assessmentid', + type: 'INTEGER', + }, + { + name: 'courseid', + type: 'INTEGER', + }, + { + name: 'timemodified', + type: 'INTEGER', + }, + { + name: 'feedbacktext', + type: 'TEXT', + }, + { + name: 'weight', + type: 'INTEGER', + }, + { + name: 'gradinggradeover', + type: 'TEXT', + }, + ], + primaryKeys: ['workshopid', 'assessmentid'] + } + ] + }; constructor(private fileProvider: CoreFileProvider, private sitesProvider: CoreSitesProvider, private textUtils: CoreTextUtilsProvider, private timeUtils: CoreTimeUtilsProvider) { - this.sitesProvider.createTablesFromSchema(this.tablesSchema); + this.sitesProvider.registerSiteSchema(this.siteSchema); } /** diff --git a/src/addon/notes/providers/notes-offline.ts b/src/addon/notes/providers/notes-offline.ts index 1572c6559..486fa0111 100644 --- a/src/addon/notes/providers/notes-offline.ts +++ b/src/addon/notes/providers/notes-offline.ts @@ -14,9 +14,8 @@ import { Injectable } from '@angular/core'; import { CoreLoggerProvider } from '@providers/logger'; -import { CoreSitesProvider } from '@providers/sites'; +import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; -import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Service to handle offline notes. @@ -27,46 +26,50 @@ export class AddonNotesOfflineProvider { // Variables for database. static NOTES_TABLE = 'addon_notes_offline_notes'; - protected tablesSchema: SQLiteDBTableSchema[] = [ - { - name: AddonNotesOfflineProvider.NOTES_TABLE, - columns: [ - { - name: 'userid', - type: 'INTEGER' - }, - { - name: 'courseid', - type: 'INTEGER' - }, - { - name: 'publishstate', - type: 'TEXT', - }, - { - name: 'content', - type: 'TEXT' - }, - { - name: 'format', - type: 'INTEGER' - }, - { - name: 'created', - type: 'INTEGER' - }, - { - name: 'lastmodified', - type: 'INTEGER' - } - ], - primaryKeys: ['userid', 'content', 'created'] - } - ]; + protected siteSchema: CoreSiteSchema = { + name: 'AddonNotesOfflineProvider', + version: 1, + tables: [ + { + name: AddonNotesOfflineProvider.NOTES_TABLE, + columns: [ + { + name: 'userid', + type: 'INTEGER' + }, + { + name: 'courseid', + type: 'INTEGER' + }, + { + name: 'publishstate', + type: 'TEXT', + }, + { + name: 'content', + type: 'TEXT' + }, + { + name: 'format', + type: 'INTEGER' + }, + { + name: 'created', + type: 'INTEGER' + }, + { + name: 'lastmodified', + type: 'INTEGER' + } + ], + primaryKeys: ['userid', 'content', 'created'] + } + ] + }; constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private timeUtils: CoreTimeUtilsProvider) { this.logger = logger.getInstance('AddonNotesOfflineProvider'); - this.sitesProvider.createTablesFromSchema(this.tablesSchema); + this.sitesProvider.registerSiteSchema(this.siteSchema); } /** diff --git a/src/classes/site.ts b/src/classes/site.ts index 3c84bcd8a..36fd3d598 100644 --- a/src/classes/site.ts +++ b/src/classes/site.ts @@ -15,7 +15,7 @@ import { Injector } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { HttpClient } from '@angular/common/http'; -import { SQLiteDB, SQLiteDBTableSchema } from './sqlitedb'; +import { SQLiteDB } from './sqlitedb'; import { CoreAppProvider } from '@providers/app'; import { CoreDbProvider } from '@providers/db'; import { CoreEventsProvider } from '@providers/events'; @@ -166,47 +166,8 @@ export class CoreSite { protected wsProvider: CoreWSProvider; // Variables for the database. - protected WS_CACHE_TABLE = 'wscache'; - protected CONFIG_TABLE = 'core_site_config'; - protected tableSchemas: SQLiteDBTableSchema[] = [ - { - name: this.WS_CACHE_TABLE, - columns: [ - { - name: 'id', - type: 'TEXT', - primaryKey: true - }, - { - name: 'data', - type: 'TEXT' - }, - { - name: 'key', - type: 'TEXT' - }, - { - name: 'expirationTime', - type: 'INTEGER' - } - ] - }, - { - name: this.CONFIG_TABLE, - columns: [ - { - name: 'name', - type: 'TEXT', - unique: true, - notNull: true - }, - { - name: 'value' - } - ] - } - - ]; + static WS_CACHE_TABLE = 'wscache'; + static CONFIG_TABLE = 'core_site_config'; // Versions of Moodle releases. protected MOODLE_RELEASES = { @@ -267,7 +228,6 @@ export class CoreSite { */ initDB(): void { this.db = this.dbProvider.getDB('Site-' + this.id); - this.db.createTablesFromSchema(this.tableSchemas); } /** @@ -784,10 +744,10 @@ export class CoreSite { let promise; if (preSets.getCacheUsingCacheKey || (emergency && preSets.getEmergencyCacheUsingCacheKey)) { - promise = this.db.getRecords(this.WS_CACHE_TABLE, { key: preSets.cacheKey }).then((entries) => { + promise = this.db.getRecords(CoreSite.WS_CACHE_TABLE, { key: preSets.cacheKey }).then((entries) => { if (!entries.length) { // Cache key not found, get by params sent. - return this.db.getRecord(this.WS_CACHE_TABLE, { id: id }); + return this.db.getRecord(CoreSite.WS_CACHE_TABLE, { id: id }); } else if (entries.length > 1) { // More than one entry found. Search the one with same ID as this call. for (let i = 0, len = entries.length; i < len; i++) { @@ -801,13 +761,13 @@ export class CoreSite { return entries[0]; }); } else { - promise = this.db.getRecord(this.WS_CACHE_TABLE, { id: id }).catch(() => { + promise = this.db.getRecord(CoreSite.WS_CACHE_TABLE, { id: id }).catch(() => { // Entry not found, try to get it using the old ID. const oldId = this.getCacheOldId(method, originalData || {}); - return this.db.getRecord(this.WS_CACHE_TABLE, { id: oldId }).then((entry) => { + return this.db.getRecord(CoreSite.WS_CACHE_TABLE, { id: oldId }).then((entry) => { // Update the entry ID to use the new one. - this.db.updateRecords(this.WS_CACHE_TABLE, {id: id}, {id: oldId}); + this.db.updateRecords(CoreSite.WS_CACHE_TABLE, {id: id}, {id: oldId}); return entry; }); @@ -877,7 +837,7 @@ export class CoreSite { entry.key = preSets.cacheKey; } - return this.db.insertRecord(this.WS_CACHE_TABLE, entry); + return this.db.insertRecord(CoreSite.WS_CACHE_TABLE, entry); }); } @@ -898,10 +858,10 @@ export class CoreSite { const id = this.getCacheId(method, data); if (allCacheKey) { - return this.db.deleteRecords(this.WS_CACHE_TABLE, { key: preSets.cacheKey }); + return this.db.deleteRecords(CoreSite.WS_CACHE_TABLE, { key: preSets.cacheKey }); } - return this.db.deleteRecords(this.WS_CACHE_TABLE, { id: id }); + return this.db.deleteRecords(CoreSite.WS_CACHE_TABLE, { id: id }); } /* @@ -935,7 +895,7 @@ export class CoreSite { this.logger.debug('Invalidate all the cache for site: ' + this.id); - return this.db.updateRecords(this.WS_CACHE_TABLE, { expirationTime: 0 }); + return this.db.updateRecords(CoreSite.WS_CACHE_TABLE, { expirationTime: 0 }); } /** @@ -954,7 +914,7 @@ export class CoreSite { this.logger.debug('Invalidate cache for key: ' + key); - return this.db.updateRecords(this.WS_CACHE_TABLE, { expirationTime: 0 }, { key: key }); + return this.db.updateRecords(CoreSite.WS_CACHE_TABLE, { expirationTime: 0 }, { key: key }); } /** @@ -997,7 +957,7 @@ export class CoreSite { this.logger.debug('Invalidate cache for key starting with: ' + key); - const sql = 'UPDATE ' + this.WS_CACHE_TABLE + ' SET expirationTime=0 WHERE key LIKE ?'; + const sql = 'UPDATE ' + CoreSite.WS_CACHE_TABLE + ' SET expirationTime=0 WHERE key LIKE ?'; return this.db.execute(sql, [key + '%']); } @@ -1590,7 +1550,7 @@ export class CoreSite { * @return {Promise} Promise resolved when done. */ deleteSiteConfig(name: string): Promise { - return this.db.deleteRecords(this.CONFIG_TABLE, { name: name }); + return this.db.deleteRecords(CoreSite.CONFIG_TABLE, { name: name }); } /** @@ -1601,7 +1561,7 @@ export class CoreSite { * @return {Promise} Resolves upon success along with the config data. Reject on failure. */ getLocalSiteConfig(name: string, defaultValue?: any): Promise { - return this.db.getRecord(this.CONFIG_TABLE, { name: name }).then((entry) => { + return this.db.getRecord(CoreSite.CONFIG_TABLE, { name: name }).then((entry) => { return entry.value; }).catch((error) => { if (typeof defaultValue != 'undefined') { @@ -1620,6 +1580,6 @@ export class CoreSite { * @return {Promise} Promise resolved when done. */ setLocalSiteConfig(name: string, value: number | string): Promise { - return this.db.insertRecord(this.CONFIG_TABLE, { name: name, value: value }); + return this.db.insertRecord(CoreSite.CONFIG_TABLE, { name: name, value: value }); } } diff --git a/src/core/course/providers/course-offline.ts b/src/core/course/providers/course-offline.ts index af28b5cbf..4de3ac671 100644 --- a/src/core/course/providers/course-offline.ts +++ b/src/core/course/providers/course-offline.ts @@ -13,8 +13,7 @@ // limitations under the License. import { Injectable } from '@angular/core'; -import { CoreSitesProvider } from '@providers/sites'; -import { SQLiteDBTableSchema } from '@classes/sqlitedb'; +import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; /** * Service to handle offline data for courses. @@ -24,37 +23,41 @@ export class CoreCourseOfflineProvider { // Variables for database. static MANUAL_COMPLETION_TABLE = 'course_manual_completion'; - protected tablesSchema: SQLiteDBTableSchema[] = [ - { - name: CoreCourseOfflineProvider.MANUAL_COMPLETION_TABLE, - columns: [ - { - name: 'cmid', - type: 'INTEGER', - primaryKey: true - }, - { - name: 'completed', - type: 'INTEGER' - }, - { - name: 'courseid', - type: 'INTEGER' - }, - { - name: 'coursename', - type: 'TEXT' - }, - { - name: 'timecompleted', - type: 'INTEGER' - } - ] - } - ]; + protected siteSchema: CoreSiteSchema = { + name: 'CoreCourseOfflineProvider', + version: 1, + tables: [ + { + name: CoreCourseOfflineProvider.MANUAL_COMPLETION_TABLE, + columns: [ + { + name: 'cmid', + type: 'INTEGER', + primaryKey: true + }, + { + name: 'completed', + type: 'INTEGER' + }, + { + name: 'courseid', + type: 'INTEGER' + }, + { + name: 'coursename', + type: 'TEXT' + }, + { + name: 'timecompleted', + type: 'INTEGER' + } + ] + } + ] + }; constructor(private sitesProvider: CoreSitesProvider) { - this.sitesProvider.createTablesFromSchema(this.tablesSchema); + this.sitesProvider.registerSiteSchema(this.siteSchema); } /** diff --git a/src/core/course/providers/course.ts b/src/core/course/providers/course.ts index 59b7dfe15..f35b76067 100644 --- a/src/core/course/providers/course.ts +++ b/src/core/course/providers/course.ts @@ -17,13 +17,12 @@ import { TranslateService } from '@ngx-translate/core'; import { CoreAppProvider } from '@providers/app'; import { CoreEventsProvider } from '@providers/events'; import { CoreLoggerProvider } from '@providers/logger'; -import { CoreSitesProvider } from '@providers/sites'; +import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreSiteWSPreSets, CoreSite } from '@classes/site'; import { CoreConstants } from '../../constants'; import { CoreCourseOfflineProvider } from './course-offline'; -import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Service that provides some features regarding a course. @@ -48,34 +47,40 @@ export class CoreCourseProvider { // Variables for database. protected COURSE_STATUS_TABLE = 'course_status'; - protected courseStatusTableSchema: SQLiteDBTableSchema = { - name: this.COURSE_STATUS_TABLE, - columns: [ + protected siteSchema: CoreSiteSchema = { + name: 'CoreCourseProvider', + version: 1, + tables: [ { - name: 'id', - type: 'INTEGER', - primaryKey: true - }, - { - name: 'status', - type: 'TEXT', - notNull: true - }, - { - name: 'previous', - type: 'TEXT' - }, - { - name: 'updated', - type: 'INTEGER' - }, - { - name: 'downloadTime', - type: 'INTEGER' - }, - { - name: 'previousDownloadTime', - type: 'INTEGER' + name: this.COURSE_STATUS_TABLE, + columns: [ + { + name: 'id', + type: 'INTEGER', + primaryKey: true + }, + { + name: 'status', + type: 'TEXT', + notNull: true + }, + { + name: 'previous', + type: 'TEXT' + }, + { + name: 'updated', + type: 'INTEGER' + }, + { + name: 'downloadTime', + type: 'INTEGER' + }, + { + name: 'previousDownloadTime', + type: 'INTEGER' + } + ] } ] }; @@ -92,7 +97,7 @@ export class CoreCourseProvider { private courseOffline: CoreCourseOfflineProvider, private appProvider: CoreAppProvider) { this.logger = logger.getInstance('CoreCourseProvider'); - this.sitesProvider.createTableFromSchema(this.courseStatusTableSchema); + this.sitesProvider.registerSiteSchema(this.siteSchema); } /** diff --git a/src/core/course/providers/module-prefetch-delegate.ts b/src/core/course/providers/module-prefetch-delegate.ts index 61e228345..c381ac64e 100644 --- a/src/core/course/providers/module-prefetch-delegate.ts +++ b/src/core/course/providers/module-prefetch-delegate.ts @@ -17,7 +17,7 @@ import { CoreEventsProvider } from '@providers/events'; import { CoreFileProvider } from '@providers/file'; import { CoreFilepoolProvider } from '@providers/filepool'; import { CoreLoggerProvider } from '@providers/logger'; -import { CoreSitesProvider } from '@providers/sites'; +import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCourseProvider } from './course'; @@ -27,7 +27,6 @@ import { CoreConstants } from '../../constants'; import { Md5 } from 'ts-md5/dist/md5'; import { Subject, BehaviorSubject, Subscription } from 'rxjs'; import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate'; -import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Progress of downloading a list of modules. @@ -203,18 +202,24 @@ export interface CoreCourseModulePrefetchHandler extends CoreDelegateHandler { export class CoreCourseModulePrefetchDelegate extends CoreDelegate { // Variables for database. protected CHECK_UPDATES_TIMES_TABLE = 'check_updates_times'; - protected checkUpdatesTableSchema: SQLiteDBTableSchema = { - name: this.CHECK_UPDATES_TIMES_TABLE, - columns: [ + protected siteSchema: CoreSiteSchema = { + name: 'CoreCourseModulePrefetchDelegate', + version: 1, + tables: [ { - name: 'courseId', - type: 'INTEGER', - primaryKey: true - }, - { - name: 'time', - type: 'INTEGER', - notNull: true + name: this.CHECK_UPDATES_TIMES_TABLE, + columns: [ + { + name: 'courseId', + type: 'INTEGER', + primaryKey: true + }, + { + name: 'time', + type: 'INTEGER', + notNull: true + } + ] } ] }; @@ -243,7 +248,7 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { protected eventsProvider: CoreEventsProvider) { super('CoreCourseModulePrefetchDelegate', loggerProvider, sitesProvider, eventsProvider); - this.sitesProvider.createTableFromSchema(this.checkUpdatesTableSchema); + this.sitesProvider.registerSiteSchema(this.siteSchema); eventsProvider.on(CoreEventsProvider.LOGOUT, this.clearStatusCache.bind(this)); eventsProvider.on(CoreEventsProvider.PACKAGE_STATUS_CHANGED, (data) => { diff --git a/src/core/emulator/providers/helper.ts b/src/core/emulator/providers/helper.ts index e3eb05776..fc10a32a2 100644 --- a/src/core/emulator/providers/helper.ts +++ b/src/core/emulator/providers/helper.ts @@ -20,13 +20,12 @@ import { LocalNotifications, ILocalNotification } from '@ionic-native/local-noti import { CoreAppProvider } from '@providers/app'; import { CoreInitDelegate, CoreInitHandler } from '@providers/init'; import { CoreLoggerProvider } from '@providers/logger'; -import { CoreSitesProvider } from '@providers/sites'; +import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { FileTransferErrorMock } from './file-transfer'; import { CoreEmulatorCaptureHelperProvider } from './capture-helper'; import { CoreConstants } from '../../constants'; -import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Helper service for the emulator feature. It also acts as an init handler. @@ -41,33 +40,37 @@ export class CoreEmulatorHelperProvider implements CoreInitHandler { // Variables for database. protected LAST_RECEIVED_NOTIFICATION_TABLE = 'core_emulator_last_received_notification'; - protected tablesSchema: SQLiteDBTableSchema[] = [ - { - name: this.LAST_RECEIVED_NOTIFICATION_TABLE, - columns: [ - { - name: 'component', - type: 'TEXT' - }, - { - name: 'id', - type: 'INTEGER', - }, - { - name: 'timecreated', - type: 'INTEGER', - }, - ], - primaryKeys: ['component'] - } - ]; + protected siteSchema: CoreSiteSchema = { + name: 'CoreEmulatorHelperProvider', + version: 1, + tables: [ + { + name: this.LAST_RECEIVED_NOTIFICATION_TABLE, + columns: [ + { + name: 'component', + type: 'TEXT' + }, + { + name: 'id', + type: 'INTEGER', + }, + { + name: 'timecreated', + type: 'INTEGER', + }, + ], + primaryKeys: ['component'] + } + ] + }; constructor(private file: File, private fileProvider: CoreFileProvider, private utils: CoreUtilsProvider, logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private localNotif: LocalNotifications, private captureHelper: CoreEmulatorCaptureHelperProvider, private timeUtils: CoreTimeUtilsProvider, private appProvider: CoreAppProvider, private localNotifProvider: CoreLocalNotificationsProvider) { this.logger = logger.getInstance('CoreEmulatorHelper'); - sitesProvider.createTablesFromSchema(this.tablesSchema); + sitesProvider.registerSiteSchema(this.siteSchema); } /** diff --git a/src/core/question/providers/question.ts b/src/core/question/providers/question.ts index 4c1e56e0e..92d8bc55c 100644 --- a/src/core/question/providers/question.ts +++ b/src/core/question/providers/question.ts @@ -14,10 +14,9 @@ import { Injectable } from '@angular/core'; import { CoreLoggerProvider } from '@providers/logger'; -import { CoreSitesProvider } from '@providers/sites'; +import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreUtilsProvider } from '@providers/utils/utils'; -import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * An object to represent a question state. @@ -64,86 +63,90 @@ export class CoreQuestionProvider { // Variables for database. protected QUESTION_TABLE = 'questions'; protected QUESTION_ANSWERS_TABLE = 'question_answers'; - protected tablesSchema: SQLiteDBTableSchema[] = [ - { - name: this.QUESTION_TABLE, - columns: [ - { - name: 'component', - type: 'TEXT', - notNull: true - }, - { - name: 'attemptid', - type: 'INTEGER', - notNull: true - }, - { - name: 'slot', - type: 'INTEGER', - notNull: true - }, - { - name: 'componentid', - type: 'INTEGER' - }, - { - name: 'userid', - type: 'INTEGER' - }, - { - name: 'number', - type: 'INTEGER' - }, - { - name: 'state', - type: 'TEXT' - } - ], - primaryKeys: ['component', 'attemptid', 'slot'] - }, - { - name: this.QUESTION_ANSWERS_TABLE, - columns: [ - { - name: 'component', - type: 'TEXT', - notNull: true - }, - { - name: 'attemptid', - type: 'INTEGER', - notNull: true - }, - { - name: 'name', - type: 'TEXT', - notNull: true - }, - { - name: 'componentid', - type: 'INTEGER' - }, - { - name: 'userid', - type: 'INTEGER' - }, - { - name: 'questionslot', - type: 'INTEGER' - }, - { - name: 'value', - type: 'TEXT' - }, - { - name: 'timemodified', - type: 'INTEGER' - } - ], - primaryKeys: ['component', 'attemptid', 'name'] - } - ]; + protected siteSchema: CoreSiteSchema = { + name: 'CoreQuestionProvider', + version: 1, + tables: [ + { + name: this.QUESTION_TABLE, + columns: [ + { + name: 'component', + type: 'TEXT', + notNull: true + }, + { + name: 'attemptid', + type: 'INTEGER', + notNull: true + }, + { + name: 'slot', + type: 'INTEGER', + notNull: true + }, + { + name: 'componentid', + type: 'INTEGER' + }, + { + name: 'userid', + type: 'INTEGER' + }, + { + name: 'number', + type: 'INTEGER' + }, + { + name: 'state', + type: 'TEXT' + } + ], + primaryKeys: ['component', 'attemptid', 'slot'] + }, + { + name: this.QUESTION_ANSWERS_TABLE, + columns: [ + { + name: 'component', + type: 'TEXT', + notNull: true + }, + { + name: 'attemptid', + type: 'INTEGER', + notNull: true + }, + { + name: 'name', + type: 'TEXT', + notNull: true + }, + { + name: 'componentid', + type: 'INTEGER' + }, + { + name: 'userid', + type: 'INTEGER' + }, + { + name: 'questionslot', + type: 'INTEGER' + }, + { + name: 'value', + type: 'TEXT' + }, + { + name: 'timemodified', + type: 'INTEGER' + } + ], + primaryKeys: ['component', 'attemptid', 'name'] + } + ] + }; protected QUESTION_PREFIX_REGEX = /q\d+:(\d+)_/; protected STATES: {[name: string]: CoreQuestionState} = { @@ -245,7 +248,7 @@ export class CoreQuestionProvider { constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private timeUtils: CoreTimeUtilsProvider, private utils: CoreUtilsProvider) { this.logger = logger.getInstance('CoreQuestionProvider'); - this.sitesProvider.createTablesFromSchema(this.tablesSchema); + this.sitesProvider.registerSiteSchema(this.siteSchema); } /** diff --git a/src/core/user/providers/user.ts b/src/core/user/providers/user.ts index 96c1fa0f5..928fb7bff 100644 --- a/src/core/user/providers/user.ts +++ b/src/core/user/providers/user.ts @@ -16,9 +16,8 @@ import { Injectable } from '@angular/core'; import { CoreFilepoolProvider } from '@providers/filepool'; import { CoreLoggerProvider } from '@providers/logger'; import { CoreSite } from '@classes/site'; -import { CoreSitesProvider } from '@providers/sites'; +import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; import { CoreUtilsProvider } from '@providers/utils/utils'; -import { SQLiteDBTableSchema } from '@classes/sqlitedb'; /** * Service to provide user functionalities. @@ -32,33 +31,37 @@ export class CoreUserProvider { // Variables for database. protected USERS_TABLE = 'users'; - protected tablesSchema: SQLiteDBTableSchema[] = [ - { - name: this.USERS_TABLE, - columns: [ - { - name: 'id', - type: 'INTEGER', - primaryKey: true - }, - { - name: 'fullname', - type: 'TEXT' - }, - { - name: 'profileimageurl', - type: 'TEXT' - } - ] - } - ]; + protected siteSchema: CoreSiteSchema = { + name: 'CoreUserProvider', + version: 1, + tables: [ + { + name: this.USERS_TABLE, + columns: [ + { + name: 'id', + type: 'INTEGER', + primaryKey: true + }, + { + name: 'fullname', + type: 'TEXT' + }, + { + name: 'profileimageurl', + type: 'TEXT' + } + ] + } + ] + }; protected logger; constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider, private filepoolProvider: CoreFilepoolProvider) { this.logger = logger.getInstance('CoreUserProvider'); - this.sitesProvider.createTablesFromSchema(this.tablesSchema); + this.sitesProvider.registerSiteSchema(this.siteSchema); } /** diff --git a/src/providers/filepool.ts b/src/providers/filepool.ts index 08fbb85a6..d148c7f72 100644 --- a/src/providers/filepool.ts +++ b/src/providers/filepool.ts @@ -20,7 +20,7 @@ import { CoreFileProvider } from './file'; import { CoreInitDelegate } from './init'; import { CoreLoggerProvider } from './logger'; import { CorePluginFileDelegate } from './plugin-file-delegate'; -import { CoreSitesProvider } from './sites'; +import { CoreSitesProvider, CoreSiteSchema } from './sites'; import { CoreWSProvider } from './ws'; import { CoreDomUtilsProvider } from './utils/dom'; import { CoreMimetypeUtilsProvider } from './utils/mimetype'; @@ -306,115 +306,119 @@ export class CoreFilepoolProvider { primaryKeys: ['siteId', 'fileId'] } ]; - protected sitesTablesSchema: SQLiteDBTableSchema[] = [ - { - name: this.FILES_TABLE, - columns: [ - { - name: 'fileId', - type: 'TEXT', - primaryKey: true - }, - { - name: 'url', - type: 'TEXT', - notNull: true - }, - { - name: 'revision', - type: 'INTEGER' - }, - { - name: 'timemodified', - type: 'INTEGER' - }, - { - name: 'stale', - type: 'INTEGER' - }, - { - name: 'downloadTime', - type: 'INTEGER' - }, - { - name: 'isexternalfile', - type: 'INTEGER' - }, - { - name: 'repositorytype', - type: 'TEXT' - }, - { - name: 'path', - type: 'TEXT' - }, - { - name: 'extension', - type: 'TEXT' - } - ] - }, - { - name: this.LINKS_TABLE, - columns: [ - { - name: 'fileId', - type: 'TEXT' - }, - { - name: 'component', - type: 'TEXT' - }, - { - name: 'componentId', - type: 'TEXT' - } - ], - primaryKeys: ['fileId', 'component', 'componentId'] - }, - { - name: this.PACKAGES_TABLE, - columns: [ - { - name: 'id', - type: 'TEXT', - primaryKey: true - }, - { - name: 'component', - type: 'TEXT' - }, - { - name: 'componentId', - type: 'TEXT' - }, - { - name: 'status', - type: 'TEXT' - }, - { - name: 'previous', - type: 'TEXT' - }, - { - name: 'updated', - type: 'INTEGER' - }, - { - name: 'downloadTime', - type: 'INTEGER' - }, - { - name: 'previousDownloadTime', - type: 'INTEGER' - }, - { - name: 'extra', - type: 'TEXT' - } - ] - }, - ]; + protected siteSchma: CoreSiteSchema = { + name: 'CoreFilepoolProvider', + version: 1, + tables: [ + { + name: this.FILES_TABLE, + columns: [ + { + name: 'fileId', + type: 'TEXT', + primaryKey: true + }, + { + name: 'url', + type: 'TEXT', + notNull: true + }, + { + name: 'revision', + type: 'INTEGER' + }, + { + name: 'timemodified', + type: 'INTEGER' + }, + { + name: 'stale', + type: 'INTEGER' + }, + { + name: 'downloadTime', + type: 'INTEGER' + }, + { + name: 'isexternalfile', + type: 'INTEGER' + }, + { + name: 'repositorytype', + type: 'TEXT' + }, + { + name: 'path', + type: 'TEXT' + }, + { + name: 'extension', + type: 'TEXT' + } + ] + }, + { + name: this.LINKS_TABLE, + columns: [ + { + name: 'fileId', + type: 'TEXT' + }, + { + name: 'component', + type: 'TEXT' + }, + { + name: 'componentId', + type: 'TEXT' + } + ], + primaryKeys: ['fileId', 'component', 'componentId'] + }, + { + name: this.PACKAGES_TABLE, + columns: [ + { + name: 'id', + type: 'TEXT', + primaryKey: true + }, + { + name: 'component', + type: 'TEXT' + }, + { + name: 'componentId', + type: 'TEXT' + }, + { + name: 'status', + type: 'TEXT' + }, + { + name: 'previous', + type: 'TEXT' + }, + { + name: 'updated', + type: 'INTEGER' + }, + { + name: 'downloadTime', + type: 'INTEGER' + }, + { + name: 'previousDownloadTime', + type: 'INTEGER' + }, + { + name: 'extra', + type: 'TEXT' + } + ] + } + ] + }; protected logger; protected appDB: SQLiteDB; @@ -443,7 +447,7 @@ export class CoreFilepoolProvider { this.appDB = this.appProvider.getDB(); this.appDB.createTablesFromSchema(this.appTablesSchema); - this.sitesProvider.createTablesFromSchema(this.sitesTablesSchema); + this.sitesProvider.registerSiteSchema(this.siteSchma); initDelegate.ready().then(() => { // Waiting for the app to be ready to start processing the queue. diff --git a/src/providers/sites.ts b/src/providers/sites.ts index cd156af0c..7398ffcbe 100644 --- a/src/providers/sites.ts +++ b/src/providers/sites.ts @@ -265,6 +265,50 @@ export class CoreSitesProvider { } ]; + // Site schema for this provider. + protected siteSchema: CoreSiteSchema = { + name: 'CoreSitesProvider', + version: 1, + tables: [ + { + name: CoreSite.WS_CACHE_TABLE, + columns: [ + { + name: 'id', + type: 'TEXT', + primaryKey: true + }, + { + name: 'data', + type: 'TEXT' + }, + { + name: 'key', + type: 'TEXT' + }, + { + name: 'expirationTime', + type: 'INTEGER' + } + ] + }, + { + name: CoreSite.CONFIG_TABLE, + columns: [ + { + name: 'name', + type: 'TEXT', + unique: true, + notNull: true + }, + { + name: 'value' + } + ] + } + ] + }; + constructor(logger: CoreLoggerProvider, private http: HttpClient, private sitesFactory: CoreSitesFactoryProvider, private appProvider: CoreAppProvider, private translate: TranslateService, private urlUtils: CoreUrlUtilsProvider, private eventsProvider: CoreEventsProvider, private textUtils: CoreTextUtilsProvider, @@ -273,6 +317,7 @@ export class CoreSitesProvider { this.appDB = appProvider.getDB(); this.appDB.createTablesFromSchema(this.appTablesSchema); + this.registerSiteSchema(this.siteSchema); } /** diff --git a/src/providers/sync.ts b/src/providers/sync.ts index e1329d8e0..a248c2ad1 100644 --- a/src/providers/sync.ts +++ b/src/providers/sync.ts @@ -14,8 +14,7 @@ import { Injectable } from '@angular/core'; import { CoreEventsProvider } from './events'; -import { CoreSitesProvider } from './sites'; -import { SQLiteDBTableSchema } from '@classes/sqlitedb'; +import { CoreSitesProvider, CoreSiteSchema } from './sites'; /* * Service that provides some features regarding synchronization. @@ -25,36 +24,42 @@ export class CoreSyncProvider { // Variables for the database. protected SYNC_TABLE = 'sync'; - protected tableSchema: SQLiteDBTableSchema = { - name: this.SYNC_TABLE, - columns: [ + protected siteSchema: CoreSiteSchema = { + name: 'CoreSyncProvider', + version: 1, + tables: [ { - name: 'component', - type: 'TEXT', - notNull: true - }, - { - name: 'id', - type: 'TEXT', - notNull: true - }, - { - name: 'time', - type: 'INTEGER' - }, - { - name: 'warnings', - type: 'TEXT' + name: this.SYNC_TABLE, + columns: [ + { + name: 'component', + type: 'TEXT', + notNull: true + }, + { + name: 'id', + type: 'TEXT', + notNull: true + }, + { + name: 'time', + type: 'INTEGER' + }, + { + name: 'warnings', + type: 'TEXT' + } + ], + primaryKeys: ['component', 'id'] } - ], - primaryKeys: ['component', 'id'] + ] }; // Store blocked sync objects. protected blockedItems: { [siteId: string]: { [blockId: string]: { [operation: string]: boolean } } } = {}; constructor(eventsProvider: CoreEventsProvider, private sitesProvider: CoreSitesProvider) { - this.sitesProvider.createTableFromSchema(this.tableSchema); + this.sitesProvider.registerSiteSchema(this.siteSchema); // Unblock all blocks on logout. eventsProvider.on(CoreEventsProvider.LOGOUT, (data) => { From c9a536f1b5d48ee37d19740290d7df0db939c1af Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 8 Feb 2019 15:08:47 +0100 Subject: [PATCH 066/191] MOBILE-2861 core: Fix error when dismissing a loading modal twice --- src/providers/utils/dom.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/providers/utils/dom.ts b/src/providers/utils/dom.ts index d026def15..bf303e859 100644 --- a/src/providers/utils/dom.ts +++ b/src/providers/utils/dom.ts @@ -1160,8 +1160,21 @@ export class CoreDomUtilsProvider { } const loader = this.loadingCtrl.create({ - content: text - }); + content: text + }), + dismiss = loader.dismiss.bind(loader); + let isDismissed = false; + + // Override dismiss to prevent dismissing a modal twice (it can throw an error and cause problems). + loader.dismiss = (data, role, navOptions): Promise => { + if (isDismissed) { + return Promise.resolve(); + } + + isDismissed = true; + + return dismiss(data, role, navOptions); + }; loader.present(); From cda11ada95d44700bb176176316947686c66c191 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 8 Feb 2019 11:33:58 +0100 Subject: [PATCH 067/191] MOBILE-2785 assign: Fix submission not received the first time --- .../components/submission/submission.ts | 2 +- src/addon/mod/assign/providers/assign.ts | 27 +++++++++++ .../mod/assign/providers/prefetch-handler.ts | 48 +++++++++++++++---- 3 files changed, 67 insertions(+), 10 deletions(-) diff --git a/src/addon/mod/assign/components/submission/submission.ts b/src/addon/mod/assign/components/submission/submission.ts index cffefe88d..d50a97a63 100644 --- a/src/addon/mod/assign/components/submission/submission.ts +++ b/src/addon/mod/assign/components/submission/submission.ts @@ -408,7 +408,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy { return Promise.all(promises); }).then(() => { // Get submission status. - return this.assignProvider.getSubmissionStatus(this.assign.id, this.submitId, isBlind); + return this.assignProvider.getSubmissionStatusWithRetry(this.assign, this.submitId, isBlind); }).then((response) => { const promises = []; diff --git a/src/addon/mod/assign/providers/assign.ts b/src/addon/mod/assign/providers/assign.ts index c6c3bf8cf..33d3f0652 100644 --- a/src/addon/mod/assign/providers/assign.ts +++ b/src/addon/mod/assign/providers/assign.ts @@ -503,6 +503,33 @@ export class AddonModAssignProvider { }); } + /** + * Get information about an assignment submission status for a given user. + * If the data doesn't include the user submission, retry ignoring cache. + * + * @param {any} assign Assignment. + * @param {number} [userId] User id (empty for current user). + * @param {boolean} [isBlind] If blind marking is enabled or not. + * @param {number} [filter=true] True to filter WS response and rewrite URLs, false otherwise. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). + * @param {string} [siteId] Site id (empty for current site). + * @return {Promise} Promise always resolved with the user submission status. + */ + getSubmissionStatusWithRetry(assign: any, userId?: number, isBlind?: boolean, filter: boolean = true, ignoreCache?: boolean, + siteId?: string): Promise { + + return this.getSubmissionStatus(assign.id, userId, isBlind, filter, ignoreCache, siteId).then((response) => { + const userSubmission = this.getSubmissionObjectFromAttempt(assign, response.lastattempt); + + if (!userSubmission) { + // Try again, ignoring cache. + return this.getSubmissionStatus(assign.id, userId, isBlind, filter, true, siteId); + } + + return response; + }); + } + /** * Get cache key for get submission status data WS calls. * diff --git a/src/addon/mod/assign/providers/prefetch-handler.ts b/src/addon/mod/assign/providers/prefetch-handler.ts index 93ecb3f68..a83d6c240 100644 --- a/src/addon/mod/assign/providers/prefetch-handler.ts +++ b/src/addon/mod/assign/providers/prefetch-handler.ts @@ -156,7 +156,8 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan protected getSubmissionFiles(assign: any, submitId: number, blindMarking: boolean, siteId?: string) : Promise { - return this.assignProvider.getSubmissionStatus(assign.id, submitId, blindMarking, true, false, siteId).then((response) => { + return this.assignProvider.getSubmissionStatusWithRetry(assign, submitId, blindMarking, true, true, siteId) + .then((response) => { const promises = []; if (response.lastattempt) { @@ -200,6 +201,17 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan return this.assignProvider.invalidateContent(moduleId, courseId); } + /** + * Invalidate WS calls needed to determine module status. + * + * @param {any} module Module. + * @param {number} courseId Course ID the module belongs to. + * @return {Promise} Promise resolved when invalidated. + */ + invalidateModule(module: any, courseId: number): Promise { + return this.assignProvider.invalidateAssignmentData(courseId); + } + /** * Whether or not the handler is enabled on a site level. * @@ -252,10 +264,11 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan subPromises.push(this.courseHelper.getModuleCourseIdByInstance(assign.id, 'assign', siteId)); - // Get all files and fetch them. - subPromises.push(this.getFiles(module, courseId, single, siteId).then((files) => { - return this.filepoolProvider.addFilesToQueue(siteId, files, this.component, module.id); - })); + // Download intro files and attachments. Do not call getFiles because it'd call some WS twice. + let files = assign.introattachments || []; + files = files.concat(this.getIntroFilesFromInstance(module, assign)); + + subPromises.push(this.filepoolProvider.addFilesToQueue(siteId, files, this.component, module.id)); return Promise.all(subPromises); })); @@ -288,8 +301,8 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan const subPromises = []; submissions.forEach((submission) => { - subPromises.push(this.assignProvider.getSubmissionStatus(assign.id, submission.submitid, - !!submission.blindid, true, false, siteId).then((subm) => { + subPromises.push(this.assignProvider.getSubmissionStatusWithRetry(assign, submission.submitid, + !!submission.blindid, true, true, siteId).then((subm) => { return this.prefetchSubmission(assign, courseId, moduleId, subm, submission.submitid, siteId); }).catch((error) => { if (error && error.errorcode == 'nopermission') { @@ -308,7 +321,7 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan // Prefetch the submission of the current user even if it does not exist, this will be create it. if (!data.submissions || !data.submissions.find((subm) => subm.submitid == userId)) { - subPromises.push(this.assignProvider.getSubmissionStatus(assign.id, userId, false, true, false, siteId) + subPromises.push(this.assignProvider.getSubmissionStatusWithRetry(assign, userId, false, true, true, siteId) .then((subm) => { return this.prefetchSubmission(assign, courseId, moduleId, subm, userId, siteId); })); @@ -330,7 +343,7 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan } else { // Student. promises.push( - this.assignProvider.getSubmissionStatus(assign.id, userId, false, true, false, siteId).then((subm) => { + this.assignProvider.getSubmissionStatusWithRetry(assign, userId, false, true, true, siteId).then((subm) => { return this.prefetchSubmission(assign, courseId, moduleId, subm, userId, siteId); }).catch((error) => { // Ignore if the user can't view their own submission. @@ -378,7 +391,16 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan // Prefetch submission plugins data. if (userSubmission.plugins) { userSubmission.plugins.forEach((plugin) => { + // Prefetch the plugin WS data. promises.push(this.submissionDelegate.prefetch(assign, userSubmission, plugin, siteId)); + + // Prefetch the plugin files. + promises.push(this.submissionDelegate.getPluginFiles(assign, userSubmission, plugin, siteId) + .then((files) => { + return this.filepoolProvider.addFilesToQueue(siteId, files, this.component, module.id); + }).catch(() => { + // Ignore errors. + })); }); } @@ -403,7 +425,15 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan // Prefetch feedback plugins data. if (submission.feedback.plugins) { submission.feedback.plugins.forEach((plugin) => { + // Prefetch the plugin WS data. promises.push(this.feedbackDelegate.prefetch(assign, submission, plugin, siteId)); + + // Prefetch the plugin files. + promises.push(this.feedbackDelegate.getPluginFiles(assign, submission, plugin, siteId).then((files) => { + return this.filepoolProvider.addFilesToQueue(siteId, files, this.component, module.id); + }).catch(() => { + // Ignore errors. + })); }); } } From 0454a7dcf312b57dd0f23d78776226a648a2a1ce Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 8 Feb 2019 12:10:28 +0100 Subject: [PATCH 068/191] MOBILE-2785 assign: Ignore cache when prefetching --- src/addon/mod/assign/providers/assign-sync.ts | 2 +- src/addon/mod/assign/providers/assign.ts | 76 ++++++++++++++----- src/addon/mod/assign/providers/helper.ts | 8 +- .../mod/assign/providers/prefetch-handler.ts | 26 +++---- src/providers/groups.ts | 26 +++++-- 5 files changed, 97 insertions(+), 41 deletions(-) diff --git a/src/addon/mod/assign/providers/assign-sync.ts b/src/addon/mod/assign/providers/assign-sync.ts index 96627a1cc..561f2245a 100644 --- a/src/addon/mod/assign/providers/assign-sync.ts +++ b/src/addon/mod/assign/providers/assign-sync.ts @@ -216,7 +216,7 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider { courseId = submissions.length > 0 ? submissions[0].courseid : grades[0].courseid; - return this.assignProvider.getAssignmentById(courseId, assignId, siteId).then((assignData) => { + return this.assignProvider.getAssignmentById(courseId, assignId, false, siteId).then((assignData) => { assign = assignData; const promises = []; diff --git a/src/addon/mod/assign/providers/assign.ts b/src/addon/mod/assign/providers/assign.ts index 33d3f0652..7a16d3b11 100644 --- a/src/addon/mod/assign/providers/assign.ts +++ b/src/addon/mod/assign/providers/assign.ts @@ -118,11 +118,12 @@ export class AddonModAssignProvider { * * @param {number} courseId Course ID the assignment belongs to. * @param {number} cmId Assignment module ID. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with the assignment. */ - getAssignment(courseId: number, cmId: number, siteId?: string): Promise { - return this.getAssignmentByField(courseId, 'cmid', cmId, siteId); + getAssignment(courseId: number, cmId: number, ignoreCache?: boolean, siteId?: string): Promise { + return this.getAssignmentByField(courseId, 'cmid', cmId, ignoreCache, siteId); } /** @@ -131,19 +132,27 @@ export class AddonModAssignProvider { * @param {number} courseId Course ID. * @param {string} key Name of the property to check. * @param {any} value Value to search. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the assignment is retrieved. */ - protected getAssignmentByField(courseId: number, key: string, value: any, siteId?: string): Promise { + protected getAssignmentByField(courseId: number, key: string, value: any, ignoreCache?: boolean, siteId?: string) + : Promise { + return this.sitesProvider.getSite(siteId).then((site) => { const params = { courseids: [courseId], includenotenrolledcourses: 1 }, - preSets = { + preSets: CoreSiteWSPreSets = { cacheKey: this.getAssignmentCacheKey(courseId) }; + if (ignoreCache) { + preSets.getFromCache = false; + preSets.emergencyCache = false; + } + return site.read('mod_assign_get_assignments', params, preSets).catch(() => { // In 3.6 we added a new parameter includenotenrolledcourses that could cause offline data not to be found. // Retry again without the param to check if the request is already cached. @@ -172,11 +181,12 @@ export class AddonModAssignProvider { * * @param {number} courseId Course ID the assignment belongs to. * @param {number} cmId Assignment instance ID. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with the assignment. */ - getAssignmentById(courseId: number, id: number, siteId?: string): Promise { - return this.getAssignmentByField(courseId, 'id', id, siteId); + getAssignmentById(courseId: number, id: number, ignoreCache?: boolean, siteId?: string): Promise { + return this.getAssignmentByField(courseId, 'id', id, ignoreCache, siteId); } /** @@ -194,18 +204,24 @@ export class AddonModAssignProvider { * * @param {number} assignId Assignment Id. * @param {number} userId User Id to be blinded. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with the user blind id. */ - getAssignmentUserMappings(assignId: number, userId: number, siteId?: string): Promise { + getAssignmentUserMappings(assignId: number, userId: number, ignoreCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const params = { assignmentids: [assignId] }, - preSets = { + preSets: CoreSiteWSPreSets = { cacheKey: this.getAssignmentUserMappingsCacheKey(assignId) }; + if (ignoreCache) { + preSets.getFromCache = false; + preSets.emergencyCache = false; + } + return site.read('mod_assign_get_user_mappings', params, preSets).then((response) => { // Search the user. if (response.assignments && response.assignments.length) { @@ -248,18 +264,24 @@ export class AddonModAssignProvider { * Returns grade information from assign_grades for the requested assignment id * * @param {number} assignId Assignment Id. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Resolved with requested info when done. */ - getAssignmentGrades(assignId: number, siteId?: string): Promise { + getAssignmentGrades(assignId: number, ignoreCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const params = { assignmentids: [assignId] }, - preSets = { + preSets: CoreSiteWSPreSets = { cacheKey: this.getAssignmentGradesCacheKey(assignId) }; + if (ignoreCache) { + preSets.getFromCache = false; + preSets.emergencyCache = false; + } + return site.read('mod_assign_get_grades', params, preSets).then((response) => { // Search the assignment. if (response.assignments && response.assignments.length) { @@ -419,18 +441,26 @@ export class AddonModAssignProvider { * Get an assignment submissions. * * @param {number} assignId Assignment id. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise<{canviewsubmissions: boolean, submissions?: any[]}>} Promise resolved when done. */ - getSubmissions(assignId: number, siteId?: string): Promise<{canviewsubmissions: boolean, submissions?: any[]}> { + getSubmissions(assignId: number, ignoreCache?: boolean, siteId?: string) + : Promise<{canviewsubmissions: boolean, submissions?: any[]}> { + return this.sitesProvider.getSite(siteId).then((site) => { const params = { assignmentids: [assignId] }, - preSets = { + preSets: CoreSiteWSPreSets = { cacheKey: this.getSubmissionsCacheKey(assignId) }; + if (ignoreCache) { + preSets.getFromCache = false; + preSets.emergencyCache = false; + } + return site.read('mod_assign_get_submissions', params, preSets).then((response): any => { // Check if we can view submissions, with enough permissions. if (response.warnings.length > 0 && response.warnings[0].warningcode == 1) { @@ -523,7 +553,10 @@ export class AddonModAssignProvider { if (!userSubmission) { // Try again, ignoring cache. - return this.getSubmissionStatus(assign.id, userId, isBlind, filter, true, siteId); + return this.getSubmissionStatus(assign.id, userId, isBlind, filter, true, siteId).catch(() => { + // Error, return the first result even if it doesn't have the user submission. + return response; + }); } return response; @@ -578,11 +611,12 @@ export class AddonModAssignProvider { * @param {number} assignId ID of the assignment the submissions belong to. * @param {boolean} [blind] Whether the user data need to be blinded. * @param {any[]} [participants] List of participants in the assignment. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @param {string} [siteId] Site id (empty for current site). * @return {Promise} Promise always resolved. Resolve param is the formatted submissions. */ getSubmissionsUserData(submissions: any[], courseId: number, assignId: number, blind?: boolean, participants?: any[], - siteId?: string): Promise { + ignoreCache?: boolean, siteId?: string): Promise { const promises = [], subs = [], @@ -619,7 +653,7 @@ export class AddonModAssignProvider { // Blind but not blinded! (Moodle < 3.1.1, 3.2). delete submission.userid; - promise = this.getAssignmentUserMappings(assignId, submission.submitid, siteId).then((blindId) => { + promise = this.getAssignmentUserMappings(assignId, submission.submitid, ignoreCache, siteId).then((blindId) => { submission.blindid = blindId; }); } else if (!participant) { @@ -702,10 +736,11 @@ export class AddonModAssignProvider { * * @param {number} assignId Assignment id. * @param {number} [groupId] Group id. If not defined, 0. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with the list of participants and summary of submissions. */ - listParticipants(assignId: number, groupId?: number, siteId?: string): Promise { + listParticipants(assignId: number, groupId?: number, ignoreCache?: boolean, siteId?: string): Promise { groupId = groupId || 0; return this.sitesProvider.getSite(siteId).then((site) => { @@ -719,10 +754,15 @@ export class AddonModAssignProvider { groupid: groupId, filter: '' }, - preSets = { + preSets: CoreSiteWSPreSets = { cacheKey: this.listParticipantsCacheKey(assignId, groupId) }; + if (ignoreCache) { + preSets.getFromCache = false; + preSets.emergencyCache = false; + } + return site.read('mod_assign_list_participants', params, preSets); }); } @@ -812,7 +852,7 @@ export class AddonModAssignProvider { invalidateContent(moduleId: number, courseId: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); - return this.getAssignment(courseId, moduleId, siteId).then((assign) => { + return this.getAssignment(courseId, moduleId, false, siteId).then((assign) => { const promises = []; // Do not invalidate assignment data before getting assignment info, we need it! diff --git a/src/addon/mod/assign/providers/helper.ts b/src/addon/mod/assign/providers/helper.ts index d227634de..78478936d 100644 --- a/src/addon/mod/assign/providers/helper.ts +++ b/src/addon/mod/assign/providers/helper.ts @@ -150,14 +150,15 @@ export class AddonModAssignHelperProvider { * List the participants for a single assignment, with some summary info about their submissions. * * @param {any} assign Assignment object + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise { + getParticipants(assign: any, ignoreCache?: boolean, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); // Get the participants without specifying a group. - return this.assignProvider.listParticipants(assign.id, undefined, siteId).then((participants) => { + return this.assignProvider.listParticipants(assign.id, undefined, ignoreCache, siteId).then((participants) => { if (participants && participants.length > 0) { return participants; } @@ -168,7 +169,8 @@ export class AddonModAssignHelperProvider { participants = {}; userGroups.forEach((userGroup) => { - promises.push(this.assignProvider.listParticipants(assign.id, userGroup.id, siteId).then((parts) => { + promises.push(this.assignProvider.listParticipants(assign.id, userGroup.id, ignoreCache, siteId) + .then((parts) => { // Do not get repeated users. parts.forEach((participant) => { participants[participant.id] = participant; diff --git a/src/addon/mod/assign/providers/prefetch-handler.ts b/src/addon/mod/assign/providers/prefetch-handler.ts index a83d6c240..16f91940f 100644 --- a/src/addon/mod/assign/providers/prefetch-handler.ts +++ b/src/addon/mod/assign/providers/prefetch-handler.ts @@ -92,19 +92,19 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan siteId = siteId || this.sitesProvider.getCurrentSiteId(); - return this.assignProvider.getAssignment(courseId, module.id, siteId).then((assign) => { + return this.assignProvider.getAssignment(courseId, module.id, false, siteId).then((assign) => { // Get intro files and attachments. let files = assign.introattachments || []; files = files.concat(this.getIntroFilesFromInstance(module, assign)); // Now get the files in the submissions. - return this.assignProvider.getSubmissions(assign.id, siteId).then((data) => { + return this.assignProvider.getSubmissions(assign.id, false, siteId).then((data) => { const blindMarking = assign.blindmarking && !assign.revealidentities; if (data.canviewsubmissions) { // Teacher, get all submissions. return this.assignProvider.getSubmissionsUserData(data.submissions, courseId, assign.id, blindMarking, - undefined, siteId).then((submissions) => { + undefined, false, siteId).then((submissions) => { const promises = []; @@ -156,7 +156,7 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan protected getSubmissionFiles(assign: any, submitId: number, blindMarking: boolean, siteId?: string) : Promise { - return this.assignProvider.getSubmissionStatusWithRetry(assign, submitId, blindMarking, true, true, siteId) + return this.assignProvider.getSubmissionStatusWithRetry(assign, submitId, blindMarking, true, false, siteId) .then((response) => { const promises = []; @@ -250,12 +250,12 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan siteId = siteId || this.sitesProvider.getCurrentSiteId(); // Get assignment to retrieve all its submissions. - promises.push(this.assignProvider.getAssignment(courseId, module.id, siteId).then((assign) => { + promises.push(this.assignProvider.getAssignment(courseId, module.id, true, siteId).then((assign) => { const subPromises = [], blindMarking = assign.blindmarking && !assign.revealidentities; if (blindMarking) { - subPromises.push(this.assignProvider.getAssignmentUserMappings(assign.id, undefined, siteId).catch(() => { + subPromises.push(this.assignProvider.getAssignmentUserMappings(assign.id, undefined, true, siteId).catch(() => { // Ignore errors. })); } @@ -289,14 +289,14 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan protected prefetchSubmissions(assign: any, courseId: number, moduleId: number, userId: number, siteId: string): Promise { // Get submissions. - return this.assignProvider.getSubmissions(assign.id, siteId).then((data) => { + return this.assignProvider.getSubmissions(assign.id, true, siteId).then((data) => { const promises = [], blindMarking = assign.blindmarking && !assign.revealidentities; if (data.canviewsubmissions) { // Teacher. Do not send participants to getSubmissionsUserData to retrieve user profiles. promises.push(this.assignProvider.getSubmissionsUserData(data.submissions, courseId, assign.id, blindMarking, - undefined, siteId).then((submissions) => { + undefined, true, siteId).then((submissions) => { const subPromises = []; @@ -316,7 +316,7 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan if (!assign.markingworkflow) { // Get assignment grades only if workflow is not enabled to check grading date. - subPromises.push(this.assignProvider.getAssignmentGrades(assign.id, siteId)); + subPromises.push(this.assignProvider.getAssignmentGrades(assign.id, true, siteId)); } // Prefetch the submission of the current user even if it does not exist, this will be create it. @@ -331,7 +331,7 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan })); // Get list participants. - promises.push(this.assignHelper.getParticipants(assign, siteId).then((participants) => { + promises.push(this.assignHelper.getParticipants(assign, true, siteId).then((participants) => { participants.forEach((participant) => { if (participant.profileimageurl) { this.filepoolProvider.addToQueueByUrl(siteId, participant.profileimageurl); @@ -354,8 +354,8 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan ); } - promises.push(this.groupsProvider.activityHasGroups(assign.cmid)); - promises.push(this.groupsProvider.getActivityAllowedGroups(assign.cmid, undefined, siteId)); + promises.push(this.groupsProvider.activityHasGroups(assign.cmid, siteId, true)); + promises.push(this.groupsProvider.getActivityAllowedGroups(assign.cmid, undefined, siteId, true)); return Promise.all(promises); }); @@ -419,7 +419,7 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan } if (userId) { - promises.push(this.gradesHelper.getGradeModuleItems(courseId, moduleId, userId, undefined, siteId)); + promises.push(this.gradesHelper.getGradeModuleItems(courseId, moduleId, userId, undefined, siteId, true)); } // Prefetch feedback plugins data. diff --git a/src/providers/groups.ts b/src/providers/groups.ts index 8afc57ee6..437e6e03f 100644 --- a/src/providers/groups.ts +++ b/src/providers/groups.ts @@ -16,6 +16,7 @@ import { Injectable } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { CoreSitesProvider } from './sites'; import { CoreCoursesProvider } from '@core/courses/providers/courses'; +import { CoreSiteWSPreSets } from '@classes/site'; /** * Group info for an activity. @@ -59,10 +60,11 @@ export class CoreGroupsProvider { * * @param {number} cmId Course module ID. * @param {string} [siteId] Site ID. If not defined, current site. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @return {Promise} Promise resolved with true if the activity has groups, resolved with false otherwise. */ - activityHasGroups(cmId: number, siteId?: string): Promise { - return this.getActivityGroupMode(cmId, siteId).then((groupmode) => { + activityHasGroups(cmId: number, siteId?: string, ignoreCache?: boolean): Promise { + return this.getActivityGroupMode(cmId, siteId, ignoreCache).then((groupmode) => { return groupmode === CoreGroupsProvider.SEPARATEGROUPS || groupmode === CoreGroupsProvider.VISIBLEGROUPS; }).catch(() => { return false; @@ -75,9 +77,10 @@ export class CoreGroupsProvider { * @param {number} cmId Course module ID. * @param {number} [userId] User ID. If not defined, use current user. * @param {string} [siteId] Site ID. If not defined, current site. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @return {Promise} Promise resolved when the groups are retrieved. */ - getActivityAllowedGroups(cmId: number, userId?: number, siteId?: string): Promise { + getActivityAllowedGroups(cmId: number, userId?: number, siteId?: string, ignoreCache?: boolean): Promise { return this.sitesProvider.getSite(siteId).then((site) => { userId = userId || site.getUserId(); @@ -85,10 +88,15 @@ export class CoreGroupsProvider { cmid: cmId, userid: userId }, - preSets = { + preSets: CoreSiteWSPreSets = { cacheKey: this.getActivityAllowedGroupsCacheKey(cmId, userId) }; + if (ignoreCache) { + preSets.getFromCache = false; + preSets.emergencyCache = false; + } + return site.read('core_group_get_activity_allowed_groups', params, preSets).then((response) => { if (!response || !response.groups) { return Promise.reject(null); @@ -175,17 +183,23 @@ export class CoreGroupsProvider { * * @param {number} cmId Course module ID. * @param {string} [siteId] Site ID. If not defined, current site. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @return {Promise} Promise resolved when the group mode is retrieved. */ - getActivityGroupMode(cmId: number, siteId?: string): Promise { + getActivityGroupMode(cmId: number, siteId?: string, ignoreCache?: boolean): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const params = { cmid: cmId }, - preSets = { + preSets: CoreSiteWSPreSets = { cacheKey: this.getActivityGroupModeCacheKey(cmId) }; + if (ignoreCache) { + preSets.getFromCache = false; + preSets.emergencyCache = false; + } + return site.read('core_group_get_activity_groupmode', params, preSets).then((response) => { if (!response || typeof response.groupmode == 'undefined') { return Promise.reject(null); From 5f96d3d25971dd9044031f2293e039712563cee4 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 13 Feb 2019 09:29:53 +0100 Subject: [PATCH 069/191] MOBILE-2869 core: Support '*' as accepted file types --- src/components/attachments/attachments.ts | 4 +++- src/core/fileuploader/providers/fileuploader.ts | 9 ++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/components/attachments/attachments.ts b/src/components/attachments/attachments.ts index a44eb6bb3..03ada9d5d 100644 --- a/src/components/attachments/attachments.ts +++ b/src/components/attachments/attachments.ts @@ -77,7 +77,9 @@ export class CoreAttachmentsComponent implements OnInit { this.maxSubmissionsReadable = String(this.maxSubmissions); } - if (this.acceptedTypes && this.acceptedTypes.trim()) { + this.acceptedTypes = this.acceptedTypes && this.acceptedTypes.trim(); + + if (this.acceptedTypes && this.acceptedTypes != '*') { this.fileTypes = this.fileUploaderProvider.prepareFiletypeList(this.acceptedTypes); } } diff --git a/src/core/fileuploader/providers/fileuploader.ts b/src/core/fileuploader/providers/fileuploader.ts index 753b4bf21..a43b4d3b4 100644 --- a/src/core/fileuploader/providers/fileuploader.ts +++ b/src/core/fileuploader/providers/fileuploader.ts @@ -333,9 +333,16 @@ export class CoreFileUploaderProvider { * Parse filetypeList to get the list of allowed mimetypes and the data to render information. * * @param {string} filetypeList Formatted string list where the mimetypes can be checked. - * @return {{info: any[], mimetypes: string[]}} Mimetypes and the filetypes informations. + * @return {{info: any[], mimetypes: string[]}} Mimetypes and the filetypes informations. Undefined if all types supported. */ prepareFiletypeList(filetypeList: string): { info: any[], mimetypes: string[] } { + filetypeList = filetypeList && filetypeList.trim(); + + if (!filetypeList || filetypeList == '*') { + // All types supported, return undefined. + return undefined; + } + const filetypes = filetypeList.split(/[;, ]+/g), mimetypes = {}, // Use an object to prevent duplicates. typesInfo = []; From 6c2eeee6bbaca365b914e91c0a5ad7e8bd062034 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 14 Feb 2019 16:15:53 +0100 Subject: [PATCH 070/191] MOBILE-2858 grades: Add unkown type grade items --- src/core/grades/providers/helper.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/core/grades/providers/helper.ts b/src/core/grades/providers/helper.ts index 958c187e6..8f206340d 100644 --- a/src/core/grades/providers/helper.ts +++ b/src/core/grades/providers/helper.ts @@ -440,13 +440,15 @@ export class CoreGradesHelperProvider { this.domUtils.convertToElement(text).querySelector('img').getAttribute('src')); } } else { - if (row['rowspan']) { + if (row['rowspan'] && row['rowspan'] > 1) { row['itemtype'] = 'category'; row['icon'] = 'fa-folder'; } else if (text.indexOf('src=') > -1) { + row['itemtype'] = 'unknown'; const src = text.match(/src="([^"]*)"/); row['image'] = src[1]; } else if (text.indexOf(' -1) { + row['itemtype'] = 'unknown'; const src = text.match(/class="fa-([^ ]*)"/); row['icon'] = src[1]; } From d6f5b5738ca34d98ce4a1b122e6e519a2ba6dca0 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 31 Jan 2019 09:05:35 +0100 Subject: [PATCH 071/191] MOBILE-2827 icon: Use small icon for Android notifications --- config.xml | 4 ++++ ...droid_splash.js => 030_android_resources.js} | 0 .../android/icon/drawable-hdpi-smallicon.png | Bin 0 -> 15237 bytes .../android/icon/drawable-ldpi-smallicon.png | Bin 0 -> 14854 bytes .../android/icon/drawable-mdpi-smallicon.png | Bin 0 -> 14957 bytes .../android/icon/drawable-xhdpi-smallicon.png | Bin 0 -> 15436 bytes .../providers/pushnotifications.ts | 3 ++- src/providers/local-notifications.ts | 2 +- 8 files changed, 7 insertions(+), 2 deletions(-) rename hooks/after_prepare/{030_android_splash.js => 030_android_resources.js} (100%) create mode 100644 resources/android/icon/drawable-hdpi-smallicon.png create mode 100644 resources/android/icon/drawable-ldpi-smallicon.png create mode 100644 resources/android/icon/drawable-mdpi-smallicon.png create mode 100644 resources/android/icon/drawable-xhdpi-smallicon.png diff --git a/config.xml b/config.xml index a5eb4c297..4e212fa13 100644 --- a/config.xml +++ b/config.xml @@ -59,6 +59,10 @@ + + + + diff --git a/hooks/after_prepare/030_android_splash.js b/hooks/after_prepare/030_android_resources.js similarity index 100% rename from hooks/after_prepare/030_android_splash.js rename to hooks/after_prepare/030_android_resources.js diff --git a/resources/android/icon/drawable-hdpi-smallicon.png b/resources/android/icon/drawable-hdpi-smallicon.png new file mode 100644 index 0000000000000000000000000000000000000000..5262dcb0fb7308ba6d4429c6c32643c44a895d91 GIT binary patch literal 15237 zcmeI3eNYo;9>=%lq(b#tZ5>~x)Lf|6qG2})fn*J#@+Mpja=a9nL1oEqU?s`MWFvu6 ziryc>QR>aLpr`F=Y3tn0P`n;EaHl;GPdLWw33>ur>ea!iSG7evi;4=jn}qNXL!b7h ze?FN>^2_slzrWw^=XuHg^His$ZdeeqJOsnA1u4l1>B9H(;(K0@@Oc^Aw@>&Awk78} zgto85_hV_#U%iWAns7EVhtDx=B#oRUf}*(sCciv6$hd1xz7pjggHU zYnMq`I!2bQHV_6|JX6Fbm)V(&veZmtS+P+|%VPB*Iu|J_-2kDBDx$}}j zEH>jZsawJq$H>-;1*JKLG-*6%XQb+gD7jIg(nzDV5el_dql{cDRT7FQoY3NmNI9V( z2`xz|q~4cIA0k9LJ8dG<6Oz2;gkLeTBA&OAI9^gx5>cXx;OvFCLaWu{gc4UO<$^-) zD7EsGOKx?9d6G=!NnjjCJ8R=v&MFo2QU#ookCDm5LcW*xzAQFhA*;j7PN0apC>ySb zAn-sY+UWDxoOZMO(r6>jm>COWJ@Bp#(-4g*KCrqVz;RDL-CrGE}#1<@2weXlH_7RW;z zNfT$cP`sYCP=yR`vli;`=|({5n^$B!XXfm}Wifh{4iD4}xZHD%OtCt6%4%d%67)ht z1k2K7l$OzG2udY45hhw5si8D-jY6fBtF%m1B%z6>i72BRqQL;#A4#3S8J*&K@usGK zU~0hrNa{^?R@mz(GoTT#{)d(bkmiw6$*e<|p{2mg73QydH_KqQ&upFQFthGm$wt{7 zjJW1vWK)aIw~hIBDbeFzyCh{4_cOgwToMee!)LTkUzX45wXy&20DAHR_StKc6fxHC zT2P);Zq@8b9Gr1;>Jb_ zvq88r$~_7S9sWq$bY+h|M;ukN)2&xV658=I|3SWXEOpFUZZEQ zfIoHjSbAE8M+V_(1@}F!%p7LlV)&7R={x$76NBkc5s(HnM!2AeKmo!9(tySY7ZedF zK)65}&=}!@A_4^n7f1sdBV15Kpa9_lX+UFy3yKI7AY32~XpC?{5rG1P3#0*!5iTeq zP=IiOG@voU1w{l35H64gG)B0fh(H0t1=4`V2p1F)C_uPC8qgTwf+7M12p32L8Y5g# zM4$lS0%<^FgbRuY6d+t64QPyTK@ouhgbSnrjS(&=B2a*Efi$2o!UaVH3J@-k1~f*v zpol;L!UfWR#t0V_5hy^oKpM~(;esLp1qc^N0~&uPu8`>ifQ(f*^;aSs?^~3A{f=-n zP-;w0H(*%VN(`&~C5GLb6uyOHg-!y)uH|DGDI7Ok!u@9JsU!@Wb3P?ueWvT;jlq|H zpOy7&_vjh@RsNIx_cp3-hHI)?=F67TJ0*|Tmd$UN7q+-(z2SqRSB9lUtCt#_y~W!k z#l0JAs&?&Pdb{#3&LexSjQ{7-?HjAwLyvuWp}VQ{t9f^xJmC1}#p20>D+$2YDLovLnkY{MwkAsBfd?tWcw=VYtI}_ z-ZLyYKDPB%?7SlN*yTTb@a3ili>exeMq8gWcOCfi(CR~srZT3lD>FhO4GO-mywtI~ zb}Y2-LO8FeVc+R2?E5UXvOnm`;zq;$Old_|-m?pu*W@fYG19$ISCid5UQd5fQU2{) z6C<`~xPhHpeqNTExMFyF@2MR_`&%}jZYoH;&CglaeCA>Q8*z;XQ|jTS8ZMR$E=;7yj^#pM>Y4kj&+*el(vES%Ni}q#-8rk zmeF{Y+}(3m{`6?@gQ_D9?^SnXPqhE-zk}z4*#k+3RYjo|*3*XyvQQy_7slRaIq$}v(1LIuheP^HPWcgzU zjiu>>Cvwf-Fdf#@AOGpO*Z*~~e{TMr%8BmLJo$n|*;r5IUsamw4U)_c%W`qmbyINE z`{!B|?^vt9G=FjZrRLh;^IsPXua&3mxS1byZw|z2hyyVuajUP_l8j9Vz=H0@kxskuUbvaM|k>zOL$_Hh^A0m`!-TmS$7 literal 0 HcmV?d00001 diff --git a/resources/android/icon/drawable-ldpi-smallicon.png b/resources/android/icon/drawable-ldpi-smallicon.png new file mode 100644 index 0000000000000000000000000000000000000000..3360a685b4a7297ec51f609cef383072d0160007 GIT binary patch literal 14854 zcmeI3Z)_7~9LH~B5N1XgnLrSv-U1@f-d(q@?RhPMbz{?Y8)Lv06XbgLY&+K8mEM(g z+Y9~~-N4|V1T)E^325L2keEQ2Az%h7i6#pO35tlz2r+`PfG_-$41BKb+NWK|1BRDR zleWJ;&-eTLJ@}TPzfYYKjt8#KNM~4h%~Gv?{8H9zFFXZBb+o zy}{vU{o!)Zrc`xBz}l|bMyab^a?A7zZ@v)YVS*4aL`y6bRCPY)q2qaZ7@N%uZHY^a zb`QPMENE%+*IUZ92(UP8WmbtRaao*h8|QGl?4`>sc9tt+SU1C!T3L=~-8{=#k}uku z4P-f-uYqHYPnJb5#FU`n-D6@0G9VCl}_()k$Es{r;_!cb^5)H2s5?cWiR$B!o z-H0et>xwVef?5O)3wTQeCRa1!imx@kTGb6vl|Z%63ma^TBJ(Anv`liioz^ltkgcT> z2v}XRy~XNq1Vm?<6aX&H9*5}nqxK1@eVP<$&=_>i_sxQz{*y*CLV3K zC-P<*vkM4}`413OGN&TZnmI$`Yj)y{3g*Ga!_@#sCdOxh6PO8Y>B*mVjTuD!MM^0n&WJaVTX-rHO@~0l3%S0=@Wq@}pOzL(ebDC3|;Ry%n zD|*6-(R8E;N&_1cTv$Y?fZ#%DU}J&{iwG4ETqq4}OmJZlp#p*nrGbqJE-WHcKyaZn zura}fMT80nE|dl~Cb+POPyxY((!j<97ZwpJAh=K(*qGqLB0>cO7fJ&g6I@tCsDR)? zX<%c53yTO95L_q?Y)o)r5upNt3#EaL2`(%mR6uZ{G_Wzjg++u42riTcHYT{Rh)@B+ zh0?&r1Q!+&Dj>K}8rYcN!XiQi1Q$vJ8xveuM5ut^LTO-Qf(wfX6%bq~4QxzsVG*GM zf(xaAjUS3DKm7n8P~lU59q{qKXAdp!hK~kXq^br#MRhHrsO`Hb>YoSj`!|Y;vJ`b| z6GicT6g5Zt;Ek^%)tSwr2sZsm3D%0IBi-?iiH7ZpWC&5d2}FYECwzIJNf@!I|wg#~vGFP#1R=_@0@ z{?b0yJ*QBdxujs)l;U~!UsLlhx%M>opIac@m!iJQH)?!GD!)3|6ML@k=f8VT^bA}* zJZ4>5p%3g>HuvlX<8I+I18=v^9RxMQ&u`o`Wp_{CIs0C@X5@5bJf zjsyJeZA}j%7uk;k+hz`3KlaD!&9`P;8G0A=SADXs?K96e`|D)p^u_iX{iX9~_Dwx{ kVQ}%8q6eeSdlL2O!0yj4-8}K3`J`rbMXm4Hs!cop1C+l??EnA( literal 0 HcmV?d00001 diff --git a/resources/android/icon/drawable-mdpi-smallicon.png b/resources/android/icon/drawable-mdpi-smallicon.png new file mode 100644 index 0000000000000000000000000000000000000000..c8df8f94dc67798e25535b55b2715c3bdf0e0382 GIT binary patch literal 14957 zcmeI3eQXnD9LH}4c^NNJ5Vr7UEkYR5-d($H?a`Lu3T}apZH&-?25#4<+lBV7^sclc z@*;=^8O#t&mW&vIpkM$6VwfnxcvVpcAqW8yi2i{h2n3BIfb+SwYoB!;4;cP@nza4( zdA{G@@43(O();Jul$X6WFnf45MNtDwOI(%kJ6C)6?F~P#MjY+%t6#9BT7hlN+B>T> zylNvwStp299@XQX!3lD}$b01)V2lQWP)$+x$AR|4C=&^B_S|tNm-S!WT9a5>L)w1?NJUU2mqDWMFW0G;i3+GEH4LRt(n&AViI+s zLqA0;sPnkXb;WWB=q$zpgTR`tx@Kp&gY7p#+2S*eqID4Fc;u`rwyw>w!o<9GvsjHMNI>SBl>fi$C2sj3eG zx)LaII3$3ni{Yh=$#nIq=u1sbmrPnuk7EZ>u?L&h6Su{dLVSko@LIXz5a3lgR3*!P zXMB~FcOujk7i+7kP&eMq3!uF~HXXIw;lJ z=~N@4Os*@gSoX^yI4s~a+v!Zrh%2_%xKc?`c}W1JE+=d-ilUce^9wCCUXx$|78V%t zZPo&V)nXM4HpXlA)tC#tR+A|P(d|a<-BP<`A*}6}L~8F7rbg`DQqK&D@T}wgh(;Uz zla|Pkrdz2JQGqM89$C3?{l-qSSzuAu*3J&U7(11Md`JP>o^$9sH(l}=OP*4iC$@Jv zUeL}nr=V>K;I-3TT2sSHIukbW|4v{$KVt8>Mx+i%kJ?c2R54Z0Nfg<0(M&a2k?tsry}0kb%r|EZ1)+pYX=(#R|6awADac%PIqfdRgUXD+N}D+ zaeV=!y=YeZOnA}$bZ<4ri}t5`Q`Xs%s5;?Zj14ud8F6XnZ~SPO1#cCAv%q38TkLdt zbGoBki~_&MCBi+dXe-l{pYWu2WHeUyWJEd=#`t6*f9kQhjJLvD26(qZCvR7}PIGcI zJmnyDMNc^~nvN7fX<%c53yTO95L_q?Y)o)r5upNt3#EaL2`(%mR6uZ{G_Wzjg++u4 z2riTcHYT{Rh)@B+h0?&r1Q!+&Dj>K}8rYcN!XiQi1Q$vJ8xveuM5ut^LTO-Qf(wfX z6%bq~4QxzsVG*GMf(xaAjR`I+B2++dp){~D!G%SH3J5Ng1~w+Ru!v9r!G+Sm#sn7@ z5h@_KP#V~n;KCw81q2sL0~-@uSVX9R;6iC&V}c8d2o(@qC=F~(aA6Ul0)h*rfsF|+ zEFx4uaG^A?@gs3%ryc+V5`5|}0w3?Y9XPNDJ{qVKN-EtHwPY+styo1-_d4MB?-Uhg zDC+tGisG6mDo5Th|M)bD$~su;noYvx^NX@a3^pxW-)ubFa-!*<3-{KqGKef*D|GoPI>vF$;hMa%_5 zYyRk~gDRU_7j0_XFk^Mam6_DiqoXe$3%`Es&jGI0bjt|#R!-iwtSyxMDPwDDfi z2IjfD;znuIxTCcKvuV)JW15a{9^7{N+wuY?XZttPP9JQQ228(`S2XlY^FHf4mpqN{ zde-#+qUc`nulsJjWIwWP{PZE`gU@`%G$<9u=*oUO*FC%meyi8@zt{*W+BO%3$E@7H z>aVG@=C!_D+o6A(Y1}d(zuCO?(_JeToc)fO|B3Pb?DnDCzkmPwZGGADJ%{J^W+ ze`?Z>cji^JeK@x1!P)(yp~{jocX#B=>N$LPWXn&v5ASrCeFw%i4cuWla!)w9YzSR^ tZP({*C#%}`-~PD0V_e7Iy{`134(gSU${XFkXb*&zPAzloes#gh{{Sm{e)0eS literal 0 HcmV?d00001 diff --git a/resources/android/icon/drawable-xhdpi-smallicon.png b/resources/android/icon/drawable-xhdpi-smallicon.png new file mode 100644 index 0000000000000000000000000000000000000000..28081d2041c74e5da6a5c0a0dd5e12a530930351 GIT binary patch literal 15436 zcmeI3Yg7|w8po%VY70_b6;PL!7)w#yWHPxxGEHKEV7V9sj1`nBLo&dWBomWCBD#uN ztElMeZWR=5sb|H46>Aq%+)}obvfjH#PpM)H)?LL`WoMEYUJyF%o_?I+4E&jDPo*8D6PR!cR0N9W%`^pXZQJ zSt@=QQ;?UWjps#>R-7jjh6wbCM9vFU2oae=E|&g*Cl(9AS<`NLn*sM4?c?A~7r$3uuMFw#-anc7fS8 z)tTf;9u02OTL}wAkY*l}7fU0vDHWg36mq{@*JZM}3z=;$b~HuUj#*$tD1wJF8T4+C zCEIFr49%d2aU*WR&6JJyAw&8snIuKpGRa{|y1R!hOwWl{>#jZIcul4uOWUZ(r8EK; z(jlET-7*UfC*n3T+p5PSm(rIqb*QVSi1hx+8IXzT8FuWrofyW(^f+yfrQn<)C4H@E zgcZjq(yAj#quRO3;-4VoMMN;GDwH=(i|Gk7!*#k69&GBL@&0_c2BUB_?NtawVgVx4 zA)%;Pgvvr^h~%h91SD}P2j$R`1|oggP&uTiSPbR>l!J57JIH`h*e}WgYJofkJ(^Bh zO&FynOjrgETg(|sxW5rly4MvNK^jRbJuF-;QNlwt11`r}L!-?$3N!2RXpNd~5E6s| z6-h-BM63@L;POz3KuT|Lfm~|P3k+C@7*|N-@-)N=QL6>*Po>t7`fO&uxKbN_Wop3w zRO&faf;sVQG|_Nq04}kORrzYX*L&MGO+bYhmmlcN*2s&!AR?qke-N--YC-bFDzbDpX7%$C3nN_aqP|FGOnmyP&;C(xN6un%7&ClfdS+=g3)pu%Yy7 zpht!|W&u&cPqp<|cIuOuSv6)m^b)a%dBF(t>GFd8gT2WtUa)_#x8FL?Oi*gEtK6Vw zz{Q-u&ZA*2eXD@0Lu4|EObHKe9_)xG?6@&WL(qHJ#;i=K+~pbEF{CkhI3v*EGCC&< z_)&MvrL&d3WuWg?VE66Hz-e}GhTk~ozoOqbF_?}k0@7fOIb5uWKmmsfq`?|>vDBy5`G+1K} z7b_xAz~KUEu*MuNRz#qH!v)e{jX7Mbh(G~{3#7psbGTR$fdUQ}NP{)zaIqo+1spDr z25ZdWVnqZBI9wnN)|kV^iU<^NxIh}LF^7v45h&nrfizfS4i_sTP{82=X|To|E>=XK zfWrmSV2wFktcXAXhYO^^8gsZ<5rF~@7f6FO=5Vnh0tFl{kOph~v$#C_9{|M7^izL1 z^y7V{wMXmdM+15Ks6;IUwDGK8(tH{0Cy*)PnM=vhkxcOs` zT64opV6UA5&c zfAT&9OYTOFy%2q7im~>>FUT!n30zb*3m+L<{QmfTd&;)_Xzn!7Y`H}a^=#!@!vCB@CiJH^T?TXh2tjzlKUb)BO zSf5SHyDa(Lb=%rb`j%ByO|H3eEFAwRW7QcL>C|jlx?PdnuD9mLeUMlm*Ov0($%4Q` z^J5ki9BFEd8Fl~ij}_nCI3iIlPMA4woF?f0{LzPZ{Xm|1_tvS3aUF_l7e9u!Xy%^q zt(|kz^Tdwuo<;YFu-jAE0$CIFoMYnD9rmU)Gp7)K8jME$nPhImrpVo8p literal 0 HcmV?d00001 diff --git a/src/addon/pushnotifications/providers/pushnotifications.ts b/src/addon/pushnotifications/providers/pushnotifications.ts index 01ac2318c..637612df3 100644 --- a/src/addon/pushnotifications/providers/pushnotifications.ts +++ b/src/addon/pushnotifications/providers/pushnotifications.ts @@ -94,7 +94,8 @@ export class AddonPushNotificationsProvider { return { android: { senderID: CoreConfigConstants.gcmpn, - sound: !!soundEnabled + sound: !!soundEnabled, + icon: 'smallicon' }, ios: { alert: 'true', diff --git a/src/providers/local-notifications.ts b/src/providers/local-notifications.ts index 5b64a5f64..75e0b555e 100644 --- a/src/providers/local-notifications.ts +++ b/src/providers/local-notifications.ts @@ -448,7 +448,7 @@ export class CoreLocalNotificationsProvider { if (this.platform.is('android')) { notification.icon = notification.icon || 'res://icon'; - notification.smallIcon = notification.smallIcon || 'res://icon'; + notification.smallIcon = notification.smallIcon || 'res://smallicon'; const led: any = notification.led || {}; notification.led = { From 407e11b4d7d1811b5376c19d5bb3422f0d85aa1f Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 15 Feb 2019 11:25:15 +0100 Subject: [PATCH 072/191] MOBILE-2827 core: Use resource-file instead of a hook --- config.xml | 12 +++++ hooks/after_prepare/030_android_resources.js | 49 -------------------- 2 files changed, 12 insertions(+), 49 deletions(-) delete mode 100755 hooks/after_prepare/030_android_resources.js diff --git a/config.xml b/config.xml index 4e212fa13..cdc0b199d 100644 --- a/config.xml +++ b/config.xml @@ -63,6 +63,18 @@ + + + + + + + + + + + + diff --git a/hooks/after_prepare/030_android_resources.js b/hooks/after_prepare/030_android_resources.js deleted file mode 100755 index 26c1d64f3..000000000 --- a/hooks/after_prepare/030_android_resources.js +++ /dev/null @@ -1,49 +0,0 @@ -#!/usr/bin/env node - -// This hook copies Android splash screen files from dev directories into the appropriate platform specific location. -// The code was extracted from here: http://devgirl.org/2013/11/12/three-hooks-your-cordovaphonegap-project-needs/ - -var filesToCopy = [{ - 'resources/android/splash/drawable-land-hdpi-screen.png': 'platforms/android/app/src/main/res/drawable-land-hdpi/screen.png' - }, { - 'resources/android/splash/drawable-land-ldpi-screen.png': 'platforms/android/app/src/main/res/drawable-land-ldpi/screen.png' - }, { - 'resources/android/splash/drawable-land-mdpi-screen.png': 'platforms/android/app/src/main/res/drawable-land-mdpi/screen.png' - }, { - 'resources/android/splash/drawable-land-xhdpi-screen.png': 'platforms/android/app/src/main/res/drawable-land-xhdpi/screen.png' - }, { - 'resources/android/splash/drawable-land-xxhdpi-screen.png': 'platforms/android/app/src/main/res/drawable-land-xxhdpi/screen.png' - }, { - 'resources/android/splash/drawable-land-xxxhdpi-screen.png': 'platforms/android/app/src/main/res/drawable-land-xxxhdpi/screen.png' - }, { - 'resources/android/splash/drawable-port-hdpi-screen.png': 'platforms/android/app/src/main/res/drawable-port-hdpi/screen.png' - }, { - 'resources/android/splash/drawable-port-ldpi-screen.png': 'platforms/android/app/src/main/res/drawable-port-ldpi/screen.png' - }, { - 'resources/android/splash/drawable-port-mdpi-screen.png': 'platforms/android/app/src/main/res/drawable-port-mdpi/screen.png' - }, { - 'resources/android/splash/drawable-port-xhdpi-screen.png': 'platforms/android/app/src/main/res/drawable-port-xhdpi/screen.png' - }, { - 'resources/android/splash/drawable-port-xxhdpi-screen.png': 'platforms/android/app/src/main/res/drawable-port-xxhdpi/screen.png' - }, { - 'resources/android/splash/drawable-port-xxxhdpi-screen.png': 'platforms/android/app/src/main/res/drawable-port-xxxhdpi/screen.png' - } -]; - -var fs = require('fs'); -var path = require('path'); - -// no need to configure below -var rootDir = process.argv[2]; - -filesToCopy.forEach(function(obj) { - Object.keys(obj).forEach(function(key) { - var val = obj[key]; - var srcFile = path.join(rootDir, key); - var destFile = path.join(rootDir, val); - var destDir = path.dirname(destFile); - if (fs.existsSync(srcFile) && fs.existsSync(destDir)) { - fs.createReadStream(srcFile).pipe(fs.createWriteStream(destFile)); - } - }); -}); From 9baca2918b34bcf723f874c0044446ae2bf9ef50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 23 Jan 2019 11:56:35 +0100 Subject: [PATCH 073/191] MOBILE-2820 calendar: Manage reminder exact time --- src/addon/calendar/lang/en.json | 3 +- src/addon/calendar/pages/event/event.html | 36 +++++----- src/addon/calendar/pages/event/event.ts | 84 ++++++++++++++++------- src/addon/calendar/providers/calendar.ts | 12 +++- src/assets/lang/en.json | 3 +- 5 files changed, 92 insertions(+), 46 deletions(-) diff --git a/src/addon/calendar/lang/en.json b/src/addon/calendar/lang/en.json index 3342aaeeb..1fc6d2e34 100644 --- a/src/addon/calendar/lang/en.json +++ b/src/addon/calendar/lang/en.json @@ -8,7 +8,8 @@ "eventstarttime": "Start time", "gotoactivity": "Go to activity", "noevents": "There are no events", - "notifications": "Notifications", + "reminders": "Reminders", + "setnewreminder": "Set a new reminder", "typeclose": "Close event", "typecourse": "Course event", "typecategory": "Category event", diff --git a/src/addon/calendar/pages/event/event.html b/src/addon/calendar/pages/event/event.html index a9711f678..fa58fb49b 100644 --- a/src/addon/calendar/pages/event/event.html +++ b/src/addon/calendar/pages/event/event.html @@ -10,10 +10,10 @@ - + - - +

+

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

{{ event.timestart * 1000 | coreFormatDate }}

@@ -52,20 +52,24 @@
- + - {{ 'addon.calendar.notifications' | translate }} - - {{ 'core.defaultvalue' | translate :{$a: defaultTimeReadable} }} - {{ 'core.settings.disabled' | translate }} - {{ 600 | coreDuration }} - {{ 1800 | coreDuration }} - {{ 3600 | coreDuration }} - {{ 7200 | coreDuration }} - {{ 21600 | coreDuration }} - {{ 43200 | coreDuration }} - {{ 86400 | coreDuration }} - +

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

+
+ +

{{ 'core.defaultvalue' | translate :{$a: ((event.timestart - defaultTime) * 1000) | coreFormatDate } }}

+

{{ notificationTime * 1000 | coreFormatDate }}

+ +
+ + + {{ 'addon.calendar.setnewreminder' | translate }} + + + +
diff --git a/src/addon/calendar/pages/event/event.ts b/src/addon/calendar/pages/event/event.ts index e20a61877..6b7b1ee55 100644 --- a/src/addon/calendar/pages/event/event.ts +++ b/src/addon/calendar/pages/event/event.ts @@ -39,8 +39,12 @@ export class AddonCalendarEventPage { protected eventId; protected siteHomeId: number; eventLoaded: boolean; + notificationFormat: string; + notificationMin: string; + notificationMax: string; notificationTime: number; - defaultTimeReadable: string; + notificationTimeText: string; + timeToLoad: number; event: any = {}; title: string; courseName: string; @@ -58,24 +62,21 @@ export class AddonCalendarEventPage { private textUtils: CoreTextUtilsProvider, private timeUtils: CoreTimeUtilsProvider) { this.eventId = navParams.get('id'); - this.notificationsEnabled = localNotificationsProvider.isAvailable(); + this.notificationsEnabled = localNotificationsProvider.isAvailable() || true; this.siteHomeId = sitesProvider.getCurrentSite().getSiteHomeId(); if (this.notificationsEnabled) { this.calendarProvider.getEventNotificationTimeOption(this.eventId).then((notificationTime) => { - this.notificationTime = notificationTime; - this.loadNotificationTime(); + this.setNotificationTime(notificationTime); }); this.calendarProvider.getDefaultNotificationTime().then((defaultTime) => { this.defaultTime = defaultTime * 60; - this.loadNotificationTime(); - if (defaultTime === 0) { - // Disabled by default. - this.defaultTimeReadable = this.translate.instant('core.settings.disabled'); - } else { - this.defaultTimeReadable = timeUtils.formatTime(defaultTime * 60); - } + this.setNotificationTime(); }); + + // Calculate format to use. ion-datetime doesn't support escaping characters ([]), so we remove them. + this.notificationFormat = this.timeUtils.convertPHPToMoment(this.translate.instant('core.strftimedatetimeshort')) + .replace(/[\[\]]/g, ''); } } @@ -88,12 +89,6 @@ export class AddonCalendarEventPage { }); } - updateNotificationTime(): void { - if (!isNaN(this.notificationTime) && this.event && this.event.id) { - this.calendarProvider.updateNotificationTime(this.event, this.notificationTime); - } - } - /** * Fetches the event and updates the view. * @@ -117,7 +112,11 @@ export class AddonCalendarEventPage { this.event = event; this.currentTime = this.timeUtils.timestamp(); - this.loadNotificationTime(); + this.notificationMin = this.timeUtils.userDate(this.currentTime * 1000, 'YYYY-MM-DDTHH:mm:ss', false); + this.notificationMax = this.timeUtils.userDate((event.timestart + event.timeduration) * 1000, + 'YYYY-MM-DDTHH:mm:ss', false); + + this.setNotificationTime(); // Reset some of the calculated data. this.categoryPath = ''; @@ -187,15 +186,50 @@ export class AddonCalendarEventPage { } /** - * Loads notification time by discarding options not in the list. + * Add a notification time for this event. + * + * @param {Event} e Click event. */ - loadNotificationTime(): void { - if (typeof this.notificationTime != 'undefined') { - if (this.notificationTime > 0 && this.event.timestart - this.notificationTime * 60 < this.currentTime) { - this.notificationTime = 0; - } else if (this.notificationTime < 0 && this.event.timestart - this.defaultTime < this.currentTime) { - this.notificationTime = 0; + addNotificationTime(e: Event): void { + e.preventDefault(); + e.stopPropagation(); + + if (this.notificationTimeText && this.event && this.event.id) { + this.setNotificationTime(new Date(this.notificationTimeText).getTime() / 1000); + this.calendarProvider.updateNotificationTime(this.event, this.notificationTime); + } + } + + /** + * Cancel the current notification. + * + * @param {Event} e Click event. + */ + cancelNotification(e: Event): void { + e.preventDefault(); + e.stopPropagation(); + + this.calendarProvider.updateNotificationTime(this.event, 0); + this.notificationTime = 0; + } + + /** + * Loads notification time. + * + * @param {number} [timeToLoad] Time to load. If not set, just recalculate. + */ + setNotificationTime(timeToLoad?: number): void { + this.timeToLoad = typeof timeToLoad == 'undefined' ? this.timeToLoad : timeToLoad; + + if (typeof this.timeToLoad != 'undefined') { + if (this.timeToLoad < 0) { + this.notificationTime = this.event.timestart - this.defaultTime * 60; + } else if (this.timeToLoad == 0 || this.timeToLoad > 1440) { + this.notificationTime = this.timeToLoad; + } else { + this.notificationTime = this.event.timestart - this.timeToLoad * 60; } + this.notificationTimeText = new Date(this.notificationTime * 1000).toString(); } } diff --git a/src/addon/calendar/providers/calendar.ts b/src/addon/calendar/providers/calendar.ts index ab5b3e301..9bfd2b4f0 100644 --- a/src/addon/calendar/providers/calendar.ts +++ b/src/addon/calendar/providers/calendar.ts @@ -305,11 +305,12 @@ export class AddonCalendarProvider { * * @param {number} id Event ID. * @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site. - * @return {Promise} Promise with wvent notification time in minutes. 0 if disabled, -1 if default time. + * @return {Promise} Promise with event notification time in minutes. 0 if disabled, -1 if default time. */ getEventNotificationTimeOption(id: number, siteId?: string): Promise { return this.getEventFromLocalDb(id, siteId).then((e) => { - return e.notificationtime || -1; + console.error(e.notificationtime); + return e.notificationtime || 0; }).catch(() => { return -1; }); @@ -670,7 +671,12 @@ export class AddonCalendarProvider { return site.getDb().updateRecords(AddonCalendarProvider.EVENTS_TABLE, {notificationtime: time}, {id: event.id}) .then(() => { - return this.scheduleEventNotification(event, time); + if (time == 0) { + // No notification, cancel it. + return this.localNotificationsProvider.cancel(event.id, AddonCalendarProvider.COMPONENT, site.getId()); + } else { + return this.scheduleEventNotification(event, time); + } }); }); } diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index 048521fcd..f440f6e3a 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -65,7 +65,8 @@ "addon.calendar.eventstarttime": "Start time", "addon.calendar.gotoactivity": "Go to activity", "addon.calendar.noevents": "There are no events", - "addon.calendar.notifications": "Notifications", + "addon.calendar.reminders": "Reminders", + "addon.calendar.setnewreminder": "Set a new reminder", "addon.calendar.typecategory": "Category event", "addon.calendar.typeclose": "Close event", "addon.calendar.typecourse": "Course event", From 1d6b79f33a776d145ede78e1aa16d15cafc70a7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 8 Feb 2019 10:32:21 +0100 Subject: [PATCH 074/191] MOBILE-2820 calendar: Add multiple reminders to calendar events --- src/addon/calendar/calendar.module.ts | 2 +- src/addon/calendar/pages/event/event.html | 10 +- src/addon/calendar/pages/event/event.ts | 61 ++-- src/addon/calendar/providers/calendar.ts | 333 ++++++++++++++-------- src/providers/update-manager.ts | 18 +- src/providers/utils/time.ts | 10 + 6 files changed, 264 insertions(+), 170 deletions(-) diff --git a/src/addon/calendar/calendar.module.ts b/src/addon/calendar/calendar.module.ts index 85aa71eb2..3146e6e69 100644 --- a/src/addon/calendar/calendar.module.ts +++ b/src/addon/calendar/calendar.module.ts @@ -71,7 +71,7 @@ export class AddonCalendarModule { newName: AddonCalendarProvider.EVENTS_TABLE, filterFields: ['id', 'name', 'description', 'format', 'eventtype', 'courseid', 'timestart', 'timeduration', 'categoryid', 'groupid', 'userid', 'instance', 'modulename', 'timemodified', 'repeatid', 'visible', 'uuid', - 'sequence', 'subscriptionid', 'notificationtime'] + 'sequence', 'subscriptionid'] }); // Migrate the component name. diff --git a/src/addon/calendar/pages/event/event.html b/src/addon/calendar/pages/event/event.html index fa58fb49b..b69e67962 100644 --- a/src/addon/calendar/pages/event/event.html +++ b/src/addon/calendar/pages/event/event.html @@ -56,10 +56,10 @@

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

- -

{{ 'core.defaultvalue' | translate :{$a: ((event.timestart - defaultTime) * 1000) | coreFormatDate } }}

-

{{ notificationTime * 1000 | coreFormatDate }}

-
@@ -69,7 +69,7 @@ - + diff --git a/src/addon/calendar/pages/event/event.ts b/src/addon/calendar/pages/event/event.ts index 6b7b1ee55..dae20076d 100644 --- a/src/addon/calendar/pages/event/event.ts +++ b/src/addon/calendar/pages/event/event.ts @@ -42,9 +42,7 @@ export class AddonCalendarEventPage { notificationFormat: string; notificationMin: string; notificationMax: string; - notificationTime: number; notificationTimeText: string; - timeToLoad: number; event: any = {}; title: string; courseName: string; @@ -54,6 +52,7 @@ export class AddonCalendarEventPage { categoryPath = ''; currentTime: number; defaultTime: number; + reminders: any[]; constructor(private translate: TranslateService, private calendarProvider: AddonCalendarProvider, navParams: NavParams, private domUtils: CoreDomUtilsProvider, private coursesProvider: CoreCoursesProvider, @@ -62,16 +61,15 @@ export class AddonCalendarEventPage { private textUtils: CoreTextUtilsProvider, private timeUtils: CoreTimeUtilsProvider) { this.eventId = navParams.get('id'); - this.notificationsEnabled = localNotificationsProvider.isAvailable() || true; + this.notificationsEnabled = localNotificationsProvider.isAvailable(); this.siteHomeId = sitesProvider.getCurrentSite().getSiteHomeId(); if (this.notificationsEnabled) { - this.calendarProvider.getEventNotificationTimeOption(this.eventId).then((notificationTime) => { - this.setNotificationTime(notificationTime); + this.calendarProvider.getEventReminders(this.eventId).then((reminders) => { + this.reminders = reminders; }); this.calendarProvider.getDefaultNotificationTime().then((defaultTime) => { this.defaultTime = defaultTime * 60; - this.setNotificationTime(); }); // Calculate format to use. ion-datetime doesn't support escaping characters ([]), so we remove them. @@ -112,11 +110,9 @@ export class AddonCalendarEventPage { this.event = event; this.currentTime = this.timeUtils.timestamp(); - this.notificationMin = this.timeUtils.userDate(this.currentTime * 1000, 'YYYY-MM-DDTHH:mm:ss', false); + this.notificationMin = this.timeUtils.userDate(this.currentTime * 1000, 'YYYY-MM-DDTHH:mm', false); this.notificationMax = this.timeUtils.userDate((event.timestart + event.timeduration) * 1000, - 'YYYY-MM-DDTHH:mm:ss', false); - - this.setNotificationTime(); + 'YYYY-MM-DDTHH:mm', false); // Reset some of the calculated data. this.categoryPath = ''; @@ -186,7 +182,7 @@ export class AddonCalendarEventPage { } /** - * Add a notification time for this event. + * Add a reminder for this event. * * @param {Event} e Click event. */ @@ -195,42 +191,33 @@ export class AddonCalendarEventPage { e.stopPropagation(); if (this.notificationTimeText && this.event && this.event.id) { - this.setNotificationTime(new Date(this.notificationTimeText).getTime() / 1000); - this.calendarProvider.updateNotificationTime(this.event, this.notificationTime); + const notificationTime = this.timeUtils.convertToTimestamp(this.notificationTimeText); + + this.calendarProvider.addEventReminder(this.event, notificationTime).then(() => { + this.calendarProvider.getEventReminders(this.eventId).then((reminders) => { + this.reminders = reminders; + }); + + this.notificationTimeText = null; + }); } } /** - * Cancel the current notification. + * Cancel the selected notification. * + * @param {number} id Reminder ID. * @param {Event} e Click event. */ - cancelNotification(e: Event): void { + cancelNotification(id: number, e: Event): void { e.preventDefault(); e.stopPropagation(); - this.calendarProvider.updateNotificationTime(this.event, 0); - this.notificationTime = 0; - } - - /** - * Loads notification time. - * - * @param {number} [timeToLoad] Time to load. If not set, just recalculate. - */ - setNotificationTime(timeToLoad?: number): void { - this.timeToLoad = typeof timeToLoad == 'undefined' ? this.timeToLoad : timeToLoad; - - if (typeof this.timeToLoad != 'undefined') { - if (this.timeToLoad < 0) { - this.notificationTime = this.event.timestart - this.defaultTime * 60; - } else if (this.timeToLoad == 0 || this.timeToLoad > 1440) { - this.notificationTime = this.timeToLoad; - } else { - this.notificationTime = this.event.timestart - this.timeToLoad * 60; - } - this.notificationTimeText = new Date(this.notificationTime * 1000).toString(); - } + this.calendarProvider.deleteEventReminder(id).then(() => { + this.calendarProvider.getEventReminders(this.eventId).then((reminders) => { + this.reminders = reminders; + }); + }); } /** diff --git a/src/addon/calendar/providers/calendar.ts b/src/addon/calendar/providers/calendar.ts index 9bfd2b4f0..6d17d056a 100644 --- a/src/addon/calendar/providers/calendar.ts +++ b/src/addon/calendar/providers/calendar.ts @@ -23,6 +23,7 @@ import { CoreConstants } from '@core/constants'; import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; import { CoreConfigProvider } from '@providers/config'; import { ILocalNotification } from '@ionic-native/local-notifications'; +import { SQLiteDB } from '@classes/sqlitedb'; /** * Service to handle calendar events. @@ -37,10 +38,11 @@ export class AddonCalendarProvider { protected ROOT_CACHE_KEY = 'mmaCalendar:'; // Variables for database. - static EVENTS_TABLE = 'addon_calendar_events'; + static EVENTS_TABLE = 'addon_calendar_events_1'; + static REMINDERS_TABLE = 'addon_calendar_reminders'; protected siteSchema: CoreSiteSchema = { name: 'AddonCalendarProvider', - version: 1, + version: 2, tables: [ { name: AddonCalendarProvider.EVENTS_TABLE, @@ -50,10 +52,6 @@ export class AddonCalendarProvider { type: 'INTEGER', primaryKey: true }, - { - name: 'notificationtime', - type: 'INTEGER' - }, { name: 'name', type: 'TEXT', @@ -128,8 +126,77 @@ export class AddonCalendarProvider { type: 'INTEGER' } ] + }, + { + name: AddonCalendarProvider.REMINDERS_TABLE, + columns: [ + { + name: 'id', + type: 'INTEGER', + primaryKey: true + }, + { + name: 'eventid', + type: 'INTEGER' + }, + { + name: 'time', + type: 'INTEGER' + } + ], + uniqueKeys: [ + ['eventid', 'time'] + ] } - ] + ], + migrate(db: SQLiteDB, oldVersion: number): Promise | void { + if (oldVersion < 2) { + const newTable = AddonCalendarProvider.EVENTS_TABLE; + const oldTable = 'addon_calendar_events'; + + return db.tableExists(oldTable).then(() => { + return db.getAllRecords(oldTable).then((events) => { + const now = Math.round(Date.now() / 1000); + + return Promise.all(events.map((event) => { + if (event.notificationtime == 0) { + // No reminders. + return Promise.resolve(); + } + + let time; + + if (event.notificationtime == -1) { + time = -1; + } else { + time = event.timestart - event.notificationtime * 60; + + if (time < now) { + // Old reminder, just not add this. + return Promise.resolve(); + } + } + + const reminder = { + eventid: event.id, + time: time + }; + + return db.insertRecord(AddonCalendarProvider.REMINDERS_TABLE, reminder); + })).then(() => { + // Move the records from the old table. + return db.insertRecordsFrom(newTable, oldTable, undefined, 'id, name, description, format, eventtype,\ + courseid, timestart, timeduration, categoryid, groupid, userid, instance, modulename, timemodified,\ + repeatid, visible, uuid, sequence, subscriptionid'); + }).then(() => { + return db.dropTable(oldTable); + }); + }); + }).catch(() => { + // Old table does not exist, ignore. + }); + } + } }; protected logger; @@ -145,29 +212,42 @@ export class AddonCalendarProvider { * Removes expired events from local DB. * * @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site. - * @return {Promise} Promise resolved when done. + * @return {Promise} Promise resolved when done. */ - cleanExpiredEvents(siteId?: string): Promise { + cleanExpiredEvents(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - let promise; + return site.getDb().getRecordsSelect(AddonCalendarProvider.EVENTS_TABLE, 'timestart + timeduration < ?', + [this.timeUtils.timestamp()]).then((events) => { + return Promise.all(events.map((event) => { + return this.deleteEvent(event.id, siteId); + })); + }); + }); + } - // Cancel expired events notifications first. - if (this.localNotificationsProvider.isAvailable()) { - promise = site.getDb().getRecordsSelect(AddonCalendarProvider.EVENTS_TABLE, 'timestart < ?', - [this.timeUtils.timestamp()]).then((events) => { - events.forEach((event) => { - return this.localNotificationsProvider.cancel(event.id, AddonCalendarProvider.COMPONENT, site.getId()); - }); - }).catch(() => { - // Ignore errors. - }); - } else { - promise = Promise.resolve(); - } + /** + * Delete event cancelling all the reminders and notifications. + * + * @param {number} eventId Event ID. + * @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site. + * @return {Promise} Resolved when done. + */ + protected deleteEvent(eventId: number, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + siteId = site.getId(); - return promise.then(() => { - return site.getDb().deleteRecordsSelect(AddonCalendarProvider.EVENTS_TABLE, 'timestart < ?', - [this.timeUtils.timestamp()]); + const promises = []; + + promises.push(site.getDb().deleteRecords(AddonCalendarProvider.EVENTS_TABLE, {id: eventId})); + + promises.push(site.getDb().getRecords(AddonCalendarProvider.REMINDERS_TABLE, {eventid: eventId}).then((reminders) => { + return Promise.all(reminders.map((reminder) => { + return this.deleteEventReminder(reminder.id, siteId); + })); + })); + + return Promise.all(promises).catch(() => { + // Ignore errors. }); }); } @@ -282,37 +362,60 @@ export class AddonCalendarProvider { } /** - * Get event notification time. Always returns number of minutes (0 if disabled). + * Adds an event reminder and schedule a new notification. * - * @param {number} id Event ID. + * @param {any} event Event to update its notification time. + * @param {number} time New notification setting timestamp. * @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site. - * @return {Promise} Event notification time in minutes. 0 if disabled. + * @return {Promise} Promise resolved when the notification is updated. */ - getEventNotificationTime(id: number, siteId?: string): Promise { - siteId = siteId || this.sitesProvider.getCurrentSiteId(); + addEventReminder(event: any, time: number, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + siteId = site.getId(); - return this.getEventNotificationTimeOption(id, siteId).then((time: number) => { - if (time == -1) { - return this.getDefaultNotificationTime(siteId); + if (!this.sitesProvider.isLoggedIn()) { + // Not logged in, we can't get the site DB. User logged out or session expired while an operation was ongoing. + return Promise.reject(null); } - return time; + const reminder = { + eventid: event.id, + time: time + }; + + return site.getDb().insertRecord(AddonCalendarProvider.REMINDERS_TABLE, reminder).then((reminderId) => { + return this.scheduleEventNotification(event, reminderId, time, siteId); + }); }); } /** - * Get event notification time for options. Returns -1 for default time. + * Remove an event reminder and cancel the notification. + * + * @param {number} id Reminder ID. + * @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site. + * @return {Promise} Promise resolved when the notification is updated. + */ + deleteEventReminder(id: number, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + if (this.localNotificationsProvider.isAvailable()) { + this.localNotificationsProvider.cancel(id, AddonCalendarProvider.COMPONENT, site.getId()); + } + + return site.getDb().deleteRecords(AddonCalendarProvider.REMINDERS_TABLE, {id: id}); + }); + } + + /** + * Get a calendar reminders from local Db. * * @param {number} id Event ID. * @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site. - * @return {Promise} Promise with event notification time in minutes. 0 if disabled, -1 if default time. + * @return {Promise} Promise resolved when the event data is retrieved. */ - getEventNotificationTimeOption(id: number, siteId?: string): Promise { - return this.getEventFromLocalDb(id, siteId).then((e) => { - console.error(e.notificationtime); - return e.notificationtime || 0; - }).catch(() => { - return -1; + getEventReminders(id: number, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + return site.getDb().getRecords(AddonCalendarProvider.REMINDERS_TABLE, {eventid: id}, 'time ASC'); }); } @@ -511,34 +614,35 @@ export class AddonCalendarProvider { * @param {string} [siteId] Site ID the event belongs to. If not defined, use current site. * @return {Promise} Promise resolved when the notification is scheduled. */ - scheduleEventNotification(event: any, time: number, siteId?: string): Promise { + protected scheduleEventNotification(event: any, reminderId: number, time: number, siteId?: string): Promise { if (this.localNotificationsProvider.isAvailable()) { siteId = siteId || this.sitesProvider.getCurrentSiteId(); if (time === 0) { // Cancel if it was scheduled. - return this.localNotificationsProvider.cancel(event.id, AddonCalendarProvider.COMPONENT, siteId); + return this.localNotificationsProvider.cancel(reminderId, AddonCalendarProvider.COMPONENT, siteId); } // If time is -1, get event default time. const promise = time == -1 ? this.getDefaultNotificationTime(siteId) : Promise.resolve(time); return promise.then((time) => { - const timeEnd = (event.timestart + event.timeduration) * 1000; - if (timeEnd <= new Date().getTime()) { - // The event has finished already, don't schedule it. - return Promise.resolve(); + + if (time * 1000 <= new Date().getTime()) { + // This reminder is over, don't schedule. Cancel if it was scheduled. + return this.localNotificationsProvider.cancel(reminderId, AddonCalendarProvider.COMPONENT, siteId); } const notification: ILocalNotification = { - id: event.id, + id: reminderId, title: event.name, text: this.timeUtils.userDate(event.timestart * 1000, 'core.strftimedaydatetime', true), trigger: { - at: new Date((event.timestart - (time * 60)) * 1000) + at: new Date(time * 1000) }, data: { eventid: event.id, + reminderid: reminderId, siteid: siteId } }; @@ -561,18 +665,27 @@ export class AddonCalendarProvider { * @return {Promise} Promise resolved when all the notifications have been scheduled. */ scheduleEventsNotifications(events: any[], siteId?: string): Promise { - const promises = []; if (this.localNotificationsProvider.isAvailable()) { siteId = siteId || this.sitesProvider.getCurrentSiteId(); - events.forEach((e) => { - promises.push(this.getEventNotificationTime(e.id, siteId).then((time) => { - return this.scheduleEventNotification(e, time, siteId); - })); - }); + + return Promise.all(events.map((event) => { + const timeEnd = (event.timestart + event.timeduration) * 1000; + + if (timeEnd <= new Date().getTime()) { + // The event has finished already, don't schedule it. + return this.deleteEvent(event.id, siteId); + } + + return this.getEventReminders(event.id, siteId).then((reminders) => { + return Promise.all(reminders.map((reminder) => { + return this.scheduleEventNotification(event, reminder.id, reminder.time, siteId); + })); + }); + })); } - return Promise.all(promises); + return Promise.resolve([]); } /** @@ -599,7 +712,41 @@ export class AddonCalendarProvider { */ storeEventInLocalDb(event: any, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().insertRecord(AddonCalendarProvider.EVENTS_TABLE, event); + siteId = site.getId(); + + // If event does not exists on the DB, schedule the reminder. + return this.getEventFromLocalDb(event.id, site.id).catch(() => { + // Event does not exist. Check if any reminder exists first. + return this.getEventReminders(event.id, siteId).then((reminders) => { + if (reminders.length == 0) { + this.addEventReminder(event, -1, siteId); + } + }); + }).then(() => { + const eventRecord = { + id: event.id, + name: event.name, + description: event.description, + format: event.format, + eventtype: event.eventtype, + courseid: event.courseid, + timestart: event.timestart, + timeduration: event.timeduration, + categoryid: event.categoryid, + groupid: event.groupid, + userid: event.userid, + instance: event.instance, + modulename: event.modulename, + timemodified: event.timemodified, + repeatid: event.repeatid, + visible: event.visible, + uuid: event.uuid, + sequence: event.sequence, + subscriptionid: event.subscriptionid + }; + + return site.getDb().insertRecord(AddonCalendarProvider.EVENTS_TABLE, eventRecord); + }); }); } @@ -614,70 +761,10 @@ export class AddonCalendarProvider { return this.sitesProvider.getSite(siteId).then((site) => { siteId = site.getId(); - const promises = [], - db = site.getDb(); - - events.forEach((event) => { - // Don't override event notification time if the user configured it. - promises.push(this.getEventFromLocalDb(event.id, siteId).catch(() => { - // Event not stored, return empty object. - return {}; - }).then((e) => { - const eventRecord = { - id: event.id, - name: event.name, - description: event.description, - format: event.format, - eventtype: event.eventtype, - courseid: event.courseid, - timestart: event.timestart, - timeduration: event.timeduration, - categoryid: event.categoryid, - groupid: event.groupid, - userid: event.userid, - instance: event.instance, - modulename: event.modulename, - timemodified: event.timemodified, - repeatid: event.repeatid, - visible: event.visible, - uuid: event.uuid, - sequence: event.sequence, - subscriptionid: event.subscriptionid, - notificationtime: e.notificationtime || -1 - }; - - return db.insertRecord(AddonCalendarProvider.EVENTS_TABLE, eventRecord); - })); - }); - - return Promise.all(promises); - }); - } - - /** - * Updates an event notification time and schedule a new notification. - * - * @param {any} event Event to update its notification time. - * @param {number} time New notification setting time (in minutes). E.g. 10 means "notificate 10 minutes before start". - * @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site. - * @return {Promise} Promise resolved when the notification is updated. - */ - updateNotificationTime(event: any, time: number, siteId?: string): Promise { - return this.sitesProvider.getSite(siteId).then((site) => { - if (!this.sitesProvider.isLoggedIn()) { - // Not logged in, we can't get the site DB. User logged out or session expired while an operation was ongoing. - return Promise.reject(null); - } - - return site.getDb().updateRecords(AddonCalendarProvider.EVENTS_TABLE, {notificationtime: time}, {id: event.id}) - .then(() => { - if (time == 0) { - // No notification, cancel it. - return this.localNotificationsProvider.cancel(event.id, AddonCalendarProvider.COMPONENT, site.getId()); - } else { - return this.scheduleEventNotification(event, time); - } - }); + return Promise.all(events.map((event) => { + // If event does not exists on the DB, schedule the reminder. + return this.storeEventInLocalDb(event, siteId); + })); }); } } diff --git a/src/providers/update-manager.ts b/src/providers/update-manager.ts index b4017eb1f..37ed1c0e8 100644 --- a/src/providers/update-manager.ts +++ b/src/providers/update-manager.ts @@ -21,6 +21,7 @@ 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'; @@ -321,7 +322,7 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { constructor(logger: CoreLoggerProvider, private configProvider: CoreConfigProvider, private sitesProvider: CoreSitesProvider, private filepoolProvider: CoreFilepoolProvider, private notifProvider: CoreLocalNotificationsProvider, - private utils: CoreUtilsProvider, private appProvider: CoreAppProvider, + private utils: CoreUtilsProvider, private appProvider: CoreAppProvider, private timeUtils: CoreTimeUtilsProvider, private calendarProvider: AddonCalendarProvider) { this.logger = logger.getInstance('CoreUpdateManagerProvider'); } @@ -606,6 +607,8 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { return Promise.resolve(); } + const now = this.timeUtils.timestamp(); + return this.sitesProvider.getSitesIds().then((siteIds) => { const promises = []; @@ -615,9 +618,16 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { const eventPromises = []; events.forEach((event) => { - if (event.notificationtime == AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME) { - event.notificationtime = -1; - eventPromises.push(this.calendarProvider.storeEventInLocalDb(event, siteId)); + 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)); } }); diff --git a/src/providers/utils/time.ts b/src/providers/utils/time.ts index 00bb2360b..e0c5633e1 100644 --- a/src/providers/utils/time.ts +++ b/src/providers/utils/time.ts @@ -276,6 +276,16 @@ export class CoreTimeUtilsProvider { return moment(timestamp).format(format); } + /** + * Convert a text into user timezone timestamp. + * + * @param {number} date To convert to timestamp. + * @return {number} Converted timestamp. + */ + convertToTimestamp(date: string): number { + return moment(date).unix() - (moment().utcOffset() * 60); + } + /** * Return the localized ISO format (i.e DDMMYY) from the localized moment format. Useful for translations. * DO NOT USE this function for ion-datetime format. Moment escapes characters with [], but ion-datetime doesn't support it. From a6d429f756ac9481b2f652ccf00e38b3d9823b2b Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 15 Feb 2019 15:49:23 +0100 Subject: [PATCH 075/191] MOBILE-2875 core: Map remoteAddOn_ to sitePlugin_ --- src/providers/utils/text.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/providers/utils/text.ts b/src/providers/utils/text.ts index 668d3589d..5e7c9f711 100644 --- a/src/providers/utils/text.ts +++ b/src/providers/utils/text.ts @@ -67,6 +67,7 @@ export class CoreTextUtilsProvider { {old: /_mmaModUrl/g, new: '_AddonModUrl'}, {old: /_mmaModWiki/g, new: '_AddonModWiki'}, {old: /_mmaModWorkshop/g, new: '_AddonModWorkshop'}, + {old: /remoteAddOn_/g, new: 'sitePlugin_'}, ]; protected template = document.createElement('template'); // A template element to convert HTML to element. From b78bd6f0c7ad0d4355c9344b54acb1ea670b956b Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 15 Feb 2019 16:06:54 +0100 Subject: [PATCH 076/191] MOBILE-2864 core: Update Ionic to 3.9.3 --- package-lock.json | 1555 +++++++++++++++++++++++++++++++++++++++++++-- package.json | 4 +- 2 files changed, 1507 insertions(+), 52 deletions(-) diff --git a/package-lock.json b/package-lock.json index f5fe1237d..a72c22b50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -211,15 +211,15 @@ "integrity": "sha512-tv3R0fvOsGRHQO8ILKElG2DAJESsMsRJqdZ7VkvzepXu2WAYYMNIK/YNNJESy9sQWfGruq9aj94d6p0NMOdtng==" }, "@ionic/app-scripts": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@ionic/app-scripts/-/app-scripts-3.2.1.tgz", - "integrity": "sha512-HEGTPTpcw/qYIP6tbeLV84YABOxeSBhd+92vbz63sS3yowNe8CZxTn3QjyMVVd+Wn00lmFpiuQUclSW5C6o0sg==", + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@ionic/app-scripts/-/app-scripts-3.2.2.tgz", + "integrity": "sha512-ZpeAafEboO/xHnmgy61ZOsaMGpoer621YX3q09x+BA2sbcuqOEsMKV91BRZmghoiHlwBB9woxXRyAwuYIAASHw==", "dev": true, "requires": { "@angular-devkit/build-optimizer": "0.0.35", "autoprefixer": "^7.2.6", "chalk": "^2.4.0", - "chokidar": "^1.7.0", + "chokidar": "^2.0.4", "clean-css": "^4.1.11", "cross-spawn": "^5.1.0", "dotenv-webpack": "^1.5.7", @@ -243,6 +243,905 @@ "webpack": "3.12.0", "ws": "3.3.2", "xml2js": "^0.4.19" + }, + "dependencies": { + "anymatch": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "dev": true, + "requires": { + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } + } + }, + "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 + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "chokidar": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.1.tgz", + "integrity": "sha512-gfw3p2oQV2wEt+8VuMlNsPjCxDxvvgnm/kz+uATu805mWVF8IJN7uz9DN7iBz+RMJISmiVbCOBFs9qBGMjtPfQ==", + "dev": true, + "requires": { + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", + "is-binary-path": "^1.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", + "path-is-absolute": "^1.0.0", + "readdirp": "^2.2.1", + "upath": "^1.1.0" + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "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=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "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=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "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", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "dev": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "dev": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "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=", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "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=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "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==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + } } }, "@ngx-translate/core": { @@ -348,9 +1247,9 @@ } }, "ajv-keywords": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", - "integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.4.0.tgz", + "integrity": "sha512-aUjdRFISbuFOl0EIZc+9e4FfZp0bDZgAdOOf30bJmw8VM9v84SHyVyxDfbWxpGYbdZD/9XoKxfHVNmxPkhwyGw==", "dev": true }, "align-text": { @@ -681,12 +1580,12 @@ "dev": true }, "async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", - "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.2.tgz", + "integrity": "sha512-H1qVYh1MYhEEFLsP97cVKqCGo7KfCyTt6uEWqsTBr9SO84oK9Uwbyd/yCW+6rKJLHksBNUVWZDAjfS+Ccx0Bbg==", "dev": true, "requires": { - "lodash": "^4.17.10" + "lodash": "^4.17.11" } }, "async-done": { @@ -1313,9 +2212,9 @@ } }, "caniuse-lite": { - "version": "1.0.30000930", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000930.tgz", - "integrity": "sha512-KD+pw9DderBLB8CGqBzYyFWpnrPVOEjsjargU/CvkNyg60od3cxSPTcTeMPhxJhDbkQPWvOz5BAyBzNl/St9vg==", + "version": "1.0.30000938", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000938.tgz", + "integrity": "sha512-ekW8NQ3/FvokviDxhdKLZZAx7PptXNwxKgXtnR5y+PR3hckwuP3yJ1Ir+4/c97dsHNqtAyfKUGdw8P4EYzBNgw==", "dev": true }, "caseless": { @@ -2958,9 +3857,9 @@ } }, "electron-to-chromium": { - "version": "1.3.106", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.106.tgz", - "integrity": "sha512-eXX45p4q9CRxG0G8D3ZBZYSdN3DnrcZfrFvt6VUr1u7aKITEtRY/xwWzJ/UZcWXa7DMqPu/pYwuZ6Nm+bl0GmA==", + "version": "1.3.113", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.113.tgz", + "integrity": "sha512-De+lPAxEcpxvqPTyZAXELNpRZXABRxf+uL/rSykstQhzj/B0l1150G/ExIIxKc16lI89Hgz81J0BHAcbTqK49g==", "dev": true }, "elliptic": { @@ -5143,9 +6042,9 @@ }, "dependencies": { "ajv": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.7.0.tgz", - "integrity": "sha512-RZXPviBTtfmtka9n9sy1N5M5b82CbxWIR6HIis4s3WQTXDJamc/0gpCWNGz6EWdWp4DOfjzJfhz/AS9zVPjjWg==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.9.1.tgz", + "integrity": "sha512-XDN92U311aINL77ieWHmqCcNlwjoP5cHXDxIxbf2MaPYuCXOHS7gHH8jktxeK5omgd52XbSTX6a4Piwd1pQmzA==", "dev": true, "requires": { "fast-deep-equal": "^2.0.1", @@ -5428,9 +6327,9 @@ "dev": true }, "ionic-angular": { - "version": "3.9.2", - "resolved": "https://registry.npmjs.org/ionic-angular/-/ionic-angular-3.9.2.tgz", - "integrity": "sha512-BEZ6magY1i5GwM9ki/MOpszUz62+g518HsGICtw9TE1D4v9Eb6n/o7e+X0vtvpK4TdouFjQ8r5XA9VPAKW9/+Q==" + "version": "3.9.3", + "resolved": "https://registry.npmjs.org/ionic-angular/-/ionic-angular-3.9.3.tgz", + "integrity": "sha512-EOuz9nyu0lV4KbqN+HipXuhoMRMDfprZeckg7H1Z+9AZpK9d7VzaiweyRNG5iaLp+cGCzBIQCfWhwlCo2vyuUA==" }, "ionicons": { "version": "3.0.0", @@ -6030,9 +6929,9 @@ } }, "lodash": { - "version": "4.17.10", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", - "integrity": "sha512-UejweD1pDoXu+AD825lWwp4ZGtSwgnpZxb3JDViD7StjQz+Nb/6l093lx4OQ0foGWNRoc19mWy7BzL+UAK2iVg==", + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", "dev": true }, "lodash._basecopy": { @@ -6662,18 +7561,18 @@ "dev": true }, "mime-db": { - "version": "1.37.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", - "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==", + "version": "1.38.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz", + "integrity": "sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg==", "dev": true }, "mime-types": { - "version": "2.1.21", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", - "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz", + "integrity": "sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog==", "dev": true, "requires": { - "mime-db": "~1.37.0" + "mime-db": "~1.38.0" } }, "mimic-fn": { @@ -7490,9 +8389,9 @@ "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==" }, "parse-asn1": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.3.tgz", - "integrity": "sha512-VrPoetlz7B/FqjBLD2f5wBVZvsZVLnRUrxVLfRYhGXCODa/NWE4p3Wp+6+aV3ZPL3KM7/OZmxDIwwijD7yuucg==", + "version": "5.1.4", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.4.tgz", + "integrity": "sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw==", "dev": true, "requires": { "asn1.js": "^4.0.0", @@ -9738,6 +10637,17 @@ "requires": { "micromatch": "^3.1.4", "normalize-path": "^2.1.1" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } } }, "arr-diff": { @@ -9782,24 +10692,23 @@ } }, "chokidar": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.4.tgz", - "integrity": "sha512-z9n7yt9rOvIJrMhvDtDictKrkFHeihkNl6uWMmZlmL6tJtX9Cs+87oK+teBx+JIgzvbX3yZHT3eF8vpbDxHJXQ==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.1.tgz", + "integrity": "sha512-gfw3p2oQV2wEt+8VuMlNsPjCxDxvvgnm/kz+uATu805mWVF8IJN7uz9DN7iBz+RMJISmiVbCOBFs9qBGMjtPfQ==", "dev": true, "requires": { "anymatch": "^2.0.0", - "async-each": "^1.0.0", - "braces": "^2.3.0", - "fsevents": "^1.2.2", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", "glob-parent": "^3.1.0", - "inherits": "^2.0.1", + "inherits": "^2.0.3", "is-binary-path": "^1.0.0", "is-glob": "^4.0.0", - "lodash.debounce": "^4.0.8", - "normalize-path": "^2.1.1", + "normalize-path": "^3.0.0", "path-is-absolute": "^1.0.0", - "readdirp": "^2.0.0", - "upath": "^1.0.5" + "readdirp": "^2.2.1", + "upath": "^1.1.0" } }, "expand-brackets": { @@ -9953,6 +10862,535 @@ } } }, + "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", @@ -10070,6 +11508,23 @@ "snapdragon": "^0.8.1", "to-regex": "^3.0.2" } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } } } }, @@ -10109,9 +11564,9 @@ }, "dependencies": { "ajv": { - "version": "6.7.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.7.0.tgz", - "integrity": "sha512-RZXPviBTtfmtka9n9sy1N5M5b82CbxWIR6HIis4s3WQTXDJamc/0gpCWNGz6EWdWp4DOfjzJfhz/AS9zVPjjWg==", + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.9.1.tgz", + "integrity": "sha512-XDN92U311aINL77ieWHmqCcNlwjoP5cHXDxIxbf2MaPYuCXOHS7gHH8jktxeK5omgd52XbSTX6a4Piwd1pQmzA==", "dev": true, "requires": { "fast-deep-equal": "^2.0.1", diff --git a/package.json b/package.json index ba4b33046..87f697455 100644 --- a/package.json +++ b/package.json @@ -104,7 +104,7 @@ "cordova-sqlite-storage": "^2.6.0", "es6-promise-plugin": "^4.2.2", "font-awesome": "^4.7.0", - "ionic-angular": "3.9.2", + "ionic-angular": "3.9.3", "ionicons": "^3.0.0", "jszip": "^3.1.5", "moment": "^2.22.2", @@ -118,7 +118,7 @@ "zone.js": "^0.8.26" }, "devDependencies": { - "@ionic/app-scripts": "^3.2.1", + "@ionic/app-scripts": "^3.2.2", "electron-builder-lib": "^20.23.1", "electron-rebuild": "^1.8.1", "gulp": "^4.0.0", From 7f7d5a54dc8fd298d02602df26462dceb7da76d0 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Mon, 18 Feb 2019 10:22:00 +0100 Subject: [PATCH 077/191] MOBILE-2882 mac: Restore browser SSO in Mac app --- src/core/login/providers/helper.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/login/providers/helper.ts b/src/core/login/providers/helper.ts index 77d06b08c..bf8abb21b 100644 --- a/src/core/login/providers/helper.ts +++ b/src/core/login/providers/helper.ts @@ -561,8 +561,8 @@ export class CoreLoginHelperProvider { * @return {boolean} True if embedded browser, false othwerise. */ isSSOEmbeddedBrowser(code: number): boolean { - if (this.appProvider.isLinux() || this.appProvider.isMac()) { - // In Linux and Mac desktop apps, always use embedded browser. + if (this.appProvider.isLinux()) { + // In Linux desktop app, always use embedded browser. return true; } @@ -647,8 +647,8 @@ export class CoreLoginHelperProvider { loginUrl += '&oauthsso=' + params.id; - if (this.appProvider.isLinux() || this.appProvider.isMac()) { - // In Linux and Mac desktop apps, always use embedded browser. + if (this.appProvider.isLinux()) { + // In Linux desktop app, always use embedded browser. this.utils.openInApp(loginUrl); } else { // Always open it in browser because the user might have the session stored in there. From c8bb1647c6e4886efb99fd69e7118815a0761e12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 15 Feb 2019 14:16:24 +0100 Subject: [PATCH 078/191] MOBILE-2796 calendar: Fix default time issues --- scripts/langindex.json | 2 ++ src/addon/calendar/pages/event/event.html | 32 ++++++++++-------- src/addon/calendar/pages/event/event.ts | 10 +++++- src/addon/calendar/providers/calendar.ts | 41 ++++++++++++++--------- src/addon/mod/data/providers/offline.ts | 2 +- src/providers/sites.ts | 5 +-- 6 files changed, 58 insertions(+), 34 deletions(-) diff --git a/scripts/langindex.json b/scripts/langindex.json index 3993e76f7..390e3fa12 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -66,6 +66,8 @@ "addon.calendar.gotoactivity": "calendar", "addon.calendar.noevents": "local_moodlemobileapp", "addon.calendar.notifications": "local_moodlemobileapp", + "addon.calendar.reminders": "local_moodlemobileapp", + "addon.calendar.setnewreminder": "local_moodlemobileapp", "addon.calendar.typecategory": "calendar", "addon.calendar.typeclose": "calendar", "addon.calendar.typecourse": "calendar", diff --git a/src/addon/calendar/pages/event/event.html b/src/addon/calendar/pages/event/event.html index b69e67962..c00376085 100644 --- a/src/addon/calendar/pages/event/event.html +++ b/src/addon/calendar/pages/event/event.html @@ -56,21 +56,25 @@

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

- -

{{ 'core.defaultvalue' | translate :{$a: ((event.timestart - defaultTime) * 1000) | coreFormatDate } }}

-

{{ reminder.time * 1000 | coreFormatDate }}

- -
+ + +

{{ 'core.defaultvalue' | translate :{$a: ((event.timestart - defaultTime) * 1000) | coreFormatDate } }}

+

{{ reminder.time * 1000 | coreFormatDate }}

+ +
+
- - {{ 'addon.calendar.setnewreminder' | translate }} - - - - - + + + {{ 'addon.calendar.setnewreminder' | translate }} + + + + + + diff --git a/src/addon/calendar/pages/event/event.ts b/src/addon/calendar/pages/event/event.ts index dae20076d..568eac629 100644 --- a/src/addon/calendar/pages/event/event.ts +++ b/src/addon/calendar/pages/event/event.ts @@ -191,7 +191,15 @@ export class AddonCalendarEventPage { e.stopPropagation(); if (this.notificationTimeText && this.event && this.event.id) { - const notificationTime = this.timeUtils.convertToTimestamp(this.notificationTimeText); + let notificationTime = this.timeUtils.convertToTimestamp(this.notificationTimeText); + + const currentTime = this.timeUtils.timestamp(), + minute = Math.floor(currentTime / 60) * 60; + + // Check if the notification time is in the same minute as we are, so the notification is triggered. + if (notificationTime >= minute && notificationTime < minute + 60) { + notificationTime = currentTime + 1; + } this.calendarProvider.addEventReminder(this.event, notificationTime).then(() => { this.calendarProvider.getEventReminders(this.eventId).then((reminders) => { diff --git a/src/addon/calendar/providers/calendar.ts b/src/addon/calendar/providers/calendar.ts index 6d17d056a..14ad8d69b 100644 --- a/src/addon/calendar/providers/calendar.ts +++ b/src/addon/calendar/providers/calendar.ts @@ -38,7 +38,7 @@ export class AddonCalendarProvider { protected ROOT_CACHE_KEY = 'mmaCalendar:'; // Variables for database. - static EVENTS_TABLE = 'addon_calendar_events_1'; + static EVENTS_TABLE = 'addon_calendar_events_2'; static REMINDERS_TABLE = 'addon_calendar_reminders'; protected siteSchema: CoreSiteSchema = { name: 'AddonCalendarProvider', @@ -149,7 +149,7 @@ export class AddonCalendarProvider { ] } ], - migrate(db: SQLiteDB, oldVersion: number): Promise | void { + migrate(db: SQLiteDB, oldVersion: number, siteId: string): Promise | void { if (oldVersion < 2) { const newTable = AddonCalendarProvider.EVENTS_TABLE; const oldTable = 'addon_calendar_events'; @@ -182,6 +182,9 @@ export class AddonCalendarProvider { time: time }; + // Cancel old notification. + this.localNotificationsProvider.cancel(event.id, AddonCalendarProvider.COMPONENT, siteId); + return db.insertRecord(AddonCalendarProvider.REMINDERS_TABLE, reminder); })).then(() => { // Move the records from the old table. @@ -371,20 +374,13 @@ export class AddonCalendarProvider { */ addEventReminder(event: any, time: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - siteId = site.getId(); - - if (!this.sitesProvider.isLoggedIn()) { - // Not logged in, we can't get the site DB. User logged out or session expired while an operation was ongoing. - return Promise.reject(null); - } - const reminder = { eventid: event.id, time: time }; return site.getDb().insertRecord(AddonCalendarProvider.REMINDERS_TABLE, reminder).then((reminderId) => { - return this.scheduleEventNotification(event, reminderId, time, siteId); + return this.scheduleEventNotification(event, reminderId, time, site.getId()); }); }); } @@ -623,12 +619,25 @@ export class AddonCalendarProvider { return this.localNotificationsProvider.cancel(reminderId, AddonCalendarProvider.COMPONENT, siteId); } - // If time is -1, get event default time. - const promise = time == -1 ? this.getDefaultNotificationTime(siteId) : Promise.resolve(time); + let promise; + if (time == -1) { + // If time is -1, get event default time to calculate the notification time. + promise = this.getDefaultNotificationTime(siteId).then((time) => { + if (time == 0) { + // Default notification time is disabled, do not show. + return this.localNotificationsProvider.cancel(reminderId, AddonCalendarProvider.COMPONENT, siteId); + } + + return event.timestart - (time * 60); + }); + } else { + promise = Promise.resolve(time); + } return promise.then((time) => { + time = time * 1000; - if (time * 1000 <= new Date().getTime()) { + if (time <= new Date().getTime()) { // This reminder is over, don't schedule. Cancel if it was scheduled. return this.localNotificationsProvider.cancel(reminderId, AddonCalendarProvider.COMPONENT, siteId); } @@ -638,7 +647,7 @@ export class AddonCalendarProvider { title: event.name, text: this.timeUtils.userDate(event.timestart * 1000, 'core.strftimedaydatetime', true), trigger: { - at: new Date(time * 1000) + at: new Date(time) }, data: { eventid: event.id, @@ -714,7 +723,7 @@ export class AddonCalendarProvider { return this.sitesProvider.getSite(siteId).then((site) => { siteId = site.getId(); - // If event does not exists on the DB, schedule the reminder. + // If event does not exist on the DB, schedule the reminder. return this.getEventFromLocalDb(event.id, site.id).catch(() => { // Event does not exist. Check if any reminder exists first. return this.getEventReminders(event.id, siteId).then((reminders) => { @@ -762,7 +771,7 @@ export class AddonCalendarProvider { siteId = site.getId(); return Promise.all(events.map((event) => { - // If event does not exists on the DB, schedule the reminder. + // If event does not exist on the DB, schedule the reminder. return this.storeEventInLocalDb(event, siteId); })); }); diff --git a/src/addon/mod/data/providers/offline.ts b/src/addon/mod/data/providers/offline.ts index c9ed26257..0c773f978 100644 --- a/src/addon/mod/data/providers/offline.ts +++ b/src/addon/mod/data/providers/offline.ts @@ -69,7 +69,7 @@ export class AddonModDataOfflineProvider { primaryKeys: ['dataid', 'entryid', 'action'] } ], - migrate(db: SQLiteDB, oldVersion: number): Promise | void { + migrate(db: SQLiteDB, oldVersion: number, siteId: string): Promise | void { if (oldVersion == 0) { // Move the records from the old table. const newTable = AddonModDataOfflineProvider.DATA_ENTRY_TABLE; diff --git a/src/providers/sites.ts b/src/providers/sites.ts index 7398ffcbe..759b89372 100644 --- a/src/providers/sites.ts +++ b/src/providers/sites.ts @@ -157,9 +157,10 @@ export interface CoreSiteSchema { * * @param {SQLiteDB} db Site database. * @param {number} oldVersion Old version of the schema or 0 if not installed. + * @param {string} siteId Site Id to migrate. * @return {Promise | void} Promise resolved when done. */ - migrate?(db: SQLiteDB, oldVersion: number): Promise | void; + migrate?(db: SQLiteDB, oldVersion: number, siteId: string): Promise | void; } /* @@ -1444,7 +1445,7 @@ export class CoreSitesProvider { promise = promise.then(() => db.createTablesFromSchema(schema.tables)); } if (schema.migrate) { - promise = promise.then(() => schema.migrate(db, oldVersion)); + promise = promise.then(() => schema.migrate(db, oldVersion, site.id)); } // Set installed version. From f11892f93c4da51fa6f7dbac933178a241e5c193 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 14 Feb 2019 16:14:57 +0100 Subject: [PATCH 079/191] MOBILE-2386 blog: Add blog entries page --- scripts/langindex.json | 10 ++ src/addon/blog/blog.module.ts | 46 +++++ .../blog/components/components.module.ts | 47 +++++ .../entries/addon-blog-entries.html | 49 ++++++ src/addon/blog/components/entries/entries.ts | 163 ++++++++++++++++++ src/addon/blog/lang/en.json | 12 ++ src/addon/blog/pages/entries/entries.html | 7 + .../blog/pages/entries/entries.module.ts | 33 ++++ src/addon/blog/pages/entries/entries.ts | 43 +++++ src/addon/blog/providers/blog.ts | 113 ++++++++++++ .../blog/providers/course-option-handler.ts | 120 +++++++++++++ src/addon/blog/providers/mainmenu-handler.ts | 51 ++++++ src/addon/blog/providers/user-handler.ts | 71 ++++++++ .../index/addon-mod-assign-index.html | 1 + .../index/addon-mod-book-index.html | 1 + .../index/addon-mod-chat-index.html | 1 + src/addon/mod/chat/components/index/index.ts | 2 +- .../index/addon-mod-choice-index.html | 1 + .../index/addon-mod-data-index.html | 1 + .../index/addon-mod-feedback-index.html | 1 + .../mod/feedback/components/index/index.ts | 2 +- .../index/addon-mod-folder-index.html | 1 + .../index/addon-mod-forum-index.html | 1 + .../index/addon-mod-glossary-index.html | 1 + .../index/addon-mod-imscp-index.html | 1 + .../index/addon-mod-lesson-index.html | 1 + .../components/index/addon-mod-lti-index.html | 1 + .../index/addon-mod-page-index.html | 1 + .../index/addon-mod-quiz-index.html | 1 + .../index/addon-mod-resource-index.html | 1 + .../index/addon-mod-scorm-index.html | 1 + .../index/addon-mod-survey-index.html | 1 + .../components/index/addon-mod-url-index.html | 1 + .../index/addon-mod-wiki-index.html | 1 + .../index/addon-mod-workshop-index.html | 1 + .../mod/workshop/components/index/index.ts | 2 +- src/addon/notes/providers/user-handler.ts | 2 +- src/app/app.module.ts | 2 + src/app/app.scss | 4 +- src/assets/lang/en.json | 10 ++ .../course/classes/main-resource-component.ts | 23 +++ src/core/course/providers/helper.ts | 19 ++ 42 files changed, 846 insertions(+), 5 deletions(-) create mode 100644 src/addon/blog/blog.module.ts create mode 100644 src/addon/blog/components/components.module.ts create mode 100644 src/addon/blog/components/entries/addon-blog-entries.html create mode 100644 src/addon/blog/components/entries/entries.ts create mode 100644 src/addon/blog/lang/en.json create mode 100644 src/addon/blog/pages/entries/entries.html create mode 100644 src/addon/blog/pages/entries/entries.module.ts create mode 100644 src/addon/blog/pages/entries/entries.ts create mode 100644 src/addon/blog/providers/blog.ts create mode 100644 src/addon/blog/providers/course-option-handler.ts create mode 100644 src/addon/blog/providers/mainmenu-handler.ts create mode 100644 src/addon/blog/providers/user-handler.ts diff --git a/scripts/langindex.json b/scripts/langindex.json index 390e3fa12..6e9b7a740 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -56,6 +56,16 @@ "addon.block_timeline.pluginname": "block_timeline", "addon.block_timeline.sortbycourses": "block_timeline", "addon.block_timeline.sortbydates": "block_timeline", + "addon.blog.blog": "blog", + "addon.blog.blogentries": "blog", + "addon.blog.errorloadentries": "local_moodlemobileapp", + "addon.blog.linktooriginalentry": "blog", + "addon.blog.noentriesyet": "blog", + "addon.blog.publishtonoone": "blog", + "addon.blog.publishtosite": "blog", + "addon.blog.publishtoworld": "blog", + "addon.blog.showonlyyourentries": "local_moodlemobileapp", + "addon.blog.siteblogheading": "blog", "addon.calendar.calendar": "calendar", "addon.calendar.calendarevents": "local_moodlemobileapp", "addon.calendar.defaultnotificationtime": "local_moodlemobileapp", diff --git a/src/addon/blog/blog.module.ts b/src/addon/blog/blog.module.ts new file mode 100644 index 000000000..275a16873 --- /dev/null +++ b/src/addon/blog/blog.module.ts @@ -0,0 +1,46 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { CoreMainMenuDelegate } from '@core/mainmenu/providers/delegate'; +import { CoreUserDelegate } from '@core/user/providers/user-delegate'; +import { CoreCourseOptionsDelegate } from '@core/course/providers/options-delegate'; +import { AddonBlogProvider } from './providers/blog'; +import { AddonBlogMainMenuHandler } from './providers/mainmenu-handler'; +import { AddonBlogUserHandler } from './providers/user-handler'; +import { AddonBlogCourseOptionHandler } from './providers/course-option-handler'; +import { AddonBlogComponentsModule } from './components/components.module'; + +@NgModule({ + declarations: [ + ], + imports: [ + AddonBlogComponentsModule + ], + providers: [ + AddonBlogProvider, + AddonBlogMainMenuHandler, + AddonBlogUserHandler, + AddonBlogCourseOptionHandler + ] +}) +export class AddonBlogModule { + constructor(mainMenuDelegate: CoreMainMenuDelegate, menuHandler: AddonBlogMainMenuHandler, + userHandler: AddonBlogUserHandler, userDelegate: CoreUserDelegate, + courseOptionHandler: AddonBlogCourseOptionHandler, courseOptionsDelegate: CoreCourseOptionsDelegate) { + mainMenuDelegate.registerHandler(menuHandler); + userDelegate.registerHandler(userHandler); + courseOptionsDelegate.registerHandler(courseOptionHandler); + } +} diff --git a/src/addon/blog/components/components.module.ts b/src/addon/blog/components/components.module.ts new file mode 100644 index 000000000..0e56fcc3f --- /dev/null +++ b/src/addon/blog/components/components.module.ts @@ -0,0 +1,47 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { IonicModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreComponentsModule } from '@components/components.module'; +import { CoreDirectivesModule } from '@directives/directives.module'; +import { CorePipesModule } from '@pipes/pipes.module'; +import { CoreCommentsComponentsModule } from '@core/comments/components/components.module'; +import { AddonBlogEntriesComponent } from './entries/entries'; + +@NgModule({ + declarations: [ + AddonBlogEntriesComponent + ], + imports: [ + CommonModule, + IonicModule, + TranslateModule.forChild(), + CoreComponentsModule, + CoreDirectivesModule, + CorePipesModule, + CoreCommentsComponentsModule + ], + providers: [ + ], + exports: [ + AddonBlogEntriesComponent + ], + entryComponents: [ + AddonBlogEntriesComponent + ] +}) +export class AddonBlogComponentsModule {} diff --git a/src/addon/blog/components/entries/addon-blog-entries.html b/src/addon/blog/components/entries/addon-blog-entries.html new file mode 100644 index 000000000..9312a00cd --- /dev/null +++ b/src/addon/blog/components/entries/addon-blog-entries.html @@ -0,0 +1,49 @@ + + + + + +
+ + {{ 'addon.blog.showonlyyourentries' | translate }} + + +
+ + + + + +

+ + + {{ 'addon.blog.' + entry.publishTranslated | translate}} + +

+

+ + {{entry.created | coreDateDayOrTime}} + + {{entry.user && entry.user.fullname}} +

+
+ + + + + + + {{ 'addon.blog.linktooriginalentry' | translate }} + + + + + {{entry.lastmodified | coreTimeAgo}} + + + +
+
+ +
+
diff --git a/src/addon/blog/components/entries/entries.ts b/src/addon/blog/components/entries/entries.ts new file mode 100644 index 000000000..f946264a8 --- /dev/null +++ b/src/addon/blog/components/entries/entries.ts @@ -0,0 +1,163 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, Input, OnInit, ViewChild } from '@angular/core'; +import { Content } from 'ionic-angular'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreUserProvider } from '@core/user/providers/user'; +import { AddonBlogProvider } from '../../providers/blog'; + +/** + * Component that displays the blog entries. + */ +@Component({ + selector: 'addon-blog-entries', + templateUrl: 'addon-blog-entries.html', +}) +export class AddonBlogEntriesComponent implements OnInit { + @Input() userId?: number; + @Input() courseId?: number; + @Input() cmId?: number; + + protected filter = {}; + protected pageLoaded = 0; + + @ViewChild(Content) content: Content; + + loaded = false; + canLoadMore = false; + loadMoreError = false; + entries = []; + currentUserId: number; + showMyIssuesToggle = false; + onlyMyEntries = false; + component = AddonBlogProvider.COMPONENT; + + constructor(protected blogProvider: AddonBlogProvider, protected domUtils: CoreDomUtilsProvider, + protected userProvider: CoreUserProvider, sitesProvider: CoreSitesProvider) { + this.currentUserId = sitesProvider.getCurrentSiteUserId(); + } + + /** + * Component being initialized. + */ + ngOnInit(): void { + if (this.userId) { + this.filter['userid'] = this.userId; + } + + if (this.courseId) { + this.filter['courseid'] = this.courseId; + } + + if (this.cmId) { + this.filter['cmid'] = this.cmId; + } + + this.fetchEntries().then(() => { + this.blogProvider.logView(this.filter).catch(() => { + // Ignore errors. + }); + }); + } + + /** + * Fetch blog entries. + * + * @param {boolean} [refresh] Empty events array first. + * @return {Promise} Promise with the entries. + */ + private fetchEntries(refresh: boolean = false): Promise { + this.loadMoreError = false; + + if (refresh) { + this.pageLoaded = 0; + } + + return this.blogProvider.getEntries(this.filter, this.pageLoaded).then((result) => { + const promises = result.entries.map((entry) => { + switch (entry.publishstate) { + case 'draft': + entry.publishTranslated = 'publishtonoone'; + break; + case 'site': + entry.publishTranslated = 'publishtosite'; + break; + case 'public': + entry.publishTranslated = 'publishtoworld'; + break; + default: + entry.publishTranslated = 'privacy:unknown'; + break; + } + + return this.userProvider.getProfile(entry.userid, entry.courseid, true).then((user) => { + entry.user = user; + }).catch(() => { + // Ignore errors. + }); + }); + + if (refresh) { + this.showMyIssuesToggle = false; + this.entries = result.entries; + } else { + this.entries = this.entries.concat(result.entries); + } + + this.canLoadMore = result.totalentries > this.entries.length; + this.pageLoaded++; + + this.showMyIssuesToggle = !this.userId && (this.showMyIssuesToggle || this.entries.some((entry) => { + return entry.userid == this.currentUserId; + })); + + return Promise.all(promises); + }).catch((message) => { + this.domUtils.showErrorModalDefault(message, 'addon.blog.errorloadentries', true); + this.loadMoreError = true; // Set to prevent infinite calls with infinite-loading. + }).finally(() => { + this.loaded = true; + }); + } + + /** + * Function to load more entries. + * + * @param {any} [infiniteComplete] Infinite scroll complete function. Only used from core-infinite-loading. + * @return {Promise} Resolved when done. + */ + loadMore(infiniteComplete?: any): Promise { + return this.fetchEntries().finally(() => { + infiniteComplete && infiniteComplete(); + }); + } + + /** + * Refresh blog entries on PTR. + * + * @param {any} refresher Refresher instance. + */ + refresh(refresher?: any): void { + this.blogProvider.invalidateEntries(this.filter).finally(() => { + this.fetchEntries(true).finally(() => { + if (refresher) { + refresher.complete(); + } + }); + }); + } + +} diff --git a/src/addon/blog/lang/en.json b/src/addon/blog/lang/en.json new file mode 100644 index 000000000..6e183232f --- /dev/null +++ b/src/addon/blog/lang/en.json @@ -0,0 +1,12 @@ +{ + "blog": "Blog", + "blogentries": "Blog entries", + "errorloadentries": "Error loading blog entries.", + "linktooriginalentry": "Link to original blog entry", + "noentriesyet": "No visible entries here", + "publishtonoone": "Yourself (draft)", + "publishtosite": "Anyone on this site", + "publishtoworld": "Anyone in the world", + "showonlyyourentries": "Show only your entries", + "siteblogheading": "Site blog" +} \ No newline at end of file diff --git a/src/addon/blog/pages/entries/entries.html b/src/addon/blog/pages/entries/entries.html new file mode 100644 index 000000000..ff5f1b240 --- /dev/null +++ b/src/addon/blog/pages/entries/entries.html @@ -0,0 +1,7 @@ + + + {{ title | translate }} + + + + diff --git a/src/addon/blog/pages/entries/entries.module.ts b/src/addon/blog/pages/entries/entries.module.ts new file mode 100644 index 000000000..a9cb5564f --- /dev/null +++ b/src/addon/blog/pages/entries/entries.module.ts @@ -0,0 +1,33 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreDirectivesModule } from '@directives/directives.module'; +import { AddonBlogEntriesPage } from './entries'; +import { AddonBlogComponentsModule } from '../../components/components.module'; + +@NgModule({ + declarations: [ + AddonBlogEntriesPage, + ], + imports: [ + CoreDirectivesModule, + AddonBlogComponentsModule, + IonicPageModule.forChild(AddonBlogEntriesPage), + TranslateModule.forChild() + ], +}) +export class AddonBlogEntriesPageModule {} diff --git a/src/addon/blog/pages/entries/entries.ts b/src/addon/blog/pages/entries/entries.ts new file mode 100644 index 000000000..1a268268f --- /dev/null +++ b/src/addon/blog/pages/entries/entries.ts @@ -0,0 +1,43 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component } from '@angular/core'; +import { IonicPage, NavParams } from 'ionic-angular'; + +/** + * Page that displays the list of blog entries. + */ +@IonicPage({ segment: 'addon-blog-entries' }) +@Component({ + selector: 'page-addon-blog-entries', + templateUrl: 'entries.html', +}) +export class AddonBlogEntriesPage { + userId: number; + courseId: number; + cmId: number; + title: string; + + constructor(params: NavParams) { + this.userId = params.get('userId'); + this.courseId = params.get('courseId'); + this.cmId = params.get('cmId'); + + if (!this.userId && !this.courseId && !this.cmId) { + this.title = 'addon.blog.siteblogheading'; + } else { + this.title = 'addon.blog.blogentries'; + } + } +} diff --git a/src/addon/blog/providers/blog.ts b/src/addon/blog/providers/blog.ts new file mode 100644 index 000000000..adc15b1f2 --- /dev/null +++ b/src/addon/blog/providers/blog.ts @@ -0,0 +1,113 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { CoreLoggerProvider } from '@providers/logger'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreUtilsProvider } from '@providers/utils/utils'; + +/** + * Service to handle blog entries. + */ +@Injectable() +export class AddonBlogProvider { + static ENTRIES_PER_PAGE = 10; + static COMPONENT = 'blog'; + protected ROOT_CACHE_KEY = 'addonBlog:'; + protected logger; + + constructor(logger: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider, protected utils: CoreUtilsProvider) { + this.logger = logger.getInstance('AddonBlogProvider'); + } + + /** + * Returns whether or not the blog plugin is enabled for a certain site. + * + * This method is called quite often and thus should only perform a quick + * check, we should not be calling WS from here. + * + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with true if enabled, resolved with false or rejected otherwise. + */ + isPluginEnabled(siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + return site.wsAvailable('core_blog_get_entries') && + site.canUseAdvancedFeature('enableblogs'); + }); + } + + /** + * Get the cache key for the blog entries. + * + * @param {any} [filter] Filter to apply on search. + * @return {string} Cache key. + */ + getEntriesCacheKey(filter: any = {}): string { + return this.ROOT_CACHE_KEY + this.utils.sortAndStringify(filter); + } + + /** + * Get blog entries. + * + * @param {any} [filter] Filter to apply on search. + * @param {any} [page=0] Page of the blog entries to fetch. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise to be resolved when the entries are retrieved. + */ + getEntries(filter: any = {}, page: number = 0, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + const data = { + filters: this.utils.objectToArrayOfObjects(filter, 'name', 'value'), + page: page, + perpage: AddonBlogProvider.ENTRIES_PER_PAGE + }; + + const preSets = { + cacheKey: this.getEntriesCacheKey(filter) + }; + + return site.read('core_blog_get_entries', data, preSets); + }); + } + + /** + * Invalidate blog entries WS call. + * + * @param {any} [filter] Filter to apply on search + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when data is invalidated. + */ + invalidateEntries(filter: any = {}, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + return site.invalidateWsCacheForKey(this.getEntriesCacheKey(filter)); + }); + } + + /** + * Trigger the blog_entries_viewed event. + * + * @param {any} [filter] Filter to apply on search. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise to be resolved when done. + */ + logView(filter: any = {}, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + const data = { + filters: this.utils.objectToArrayOfObjects(filter, 'name', 'value') + }; + + return site.write('core_blog_view_entries', data); + }); + } +} diff --git a/src/addon/blog/providers/course-option-handler.ts b/src/addon/blog/providers/course-option-handler.ts new file mode 100644 index 000000000..9c28973fb --- /dev/null +++ b/src/addon/blog/providers/course-option-handler.ts @@ -0,0 +1,120 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable, Injector } from '@angular/core'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreFilepoolProvider } from '@providers/filepool'; +import { CoreCourseOptionsHandler, CoreCourseOptionsHandlerData } from '@core/course/providers/options-delegate'; +import { CoreCoursesProvider } from '@core/courses/providers/courses'; +import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseHelperProvider } from '@core/course/providers/helper'; +import { AddonBlogEntriesComponent } from '../components/entries/entries'; +import { AddonBlogProvider } from './blog'; + +/** + * Course nav handler. + */ +@Injectable() +export class AddonBlogCourseOptionHandler implements CoreCourseOptionsHandler { + name = 'AddonBlog'; + priority = 100; + + constructor(protected coursesProvider: CoreCoursesProvider, protected blogProvider: AddonBlogProvider, + protected courseHelper: CoreCourseHelperProvider, protected courseProvider: CoreCourseProvider, + protected sitesProvider: CoreSitesProvider, protected filepoolProvider: CoreFilepoolProvider) {} + + /** + * Should invalidate the data to determine if the handler is enabled for a certain course. + * + * @param {number} courseId The course ID. + * @param {any} [navOptions] Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. + * @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. + * @return {Promise} Promise resolved when done. + */ + invalidateEnabledForCourse(courseId: number, navOptions?: any, admOptions?: any): Promise { + return this.courseProvider.invalidateCourseBlocks(courseId); + } + + /** + * Check if the handler is enabled on a site level. + * + * @return {boolean} Whether or not the handler is enabled on a site level. + */ + isEnabled(): boolean | Promise { + return this.blogProvider.isPluginEnabled(); + } + + /** + * Whether or not the handler is enabled for a certain course. + * + * @param {number} courseId The course ID. + * @param {any} accessData Access type and data. Default, guest, ... + * @param {any} [navOptions] Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. + * @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. + * @return {boolean|Promise} True or promise resolved with true if enabled. + */ + isEnabledForCourse(courseId: number, accessData: any, navOptions?: any, admOptions?: any): boolean | Promise { + return this.courseHelper.hasABlockNamed(courseId, 'blog_menu').then((enabled) => { + if (enabled && navOptions && typeof navOptions.blogs != 'undefined') { + return navOptions.blogs; + } + + return enabled; + }); + } + + /** + * Returns the data needed to render the handler. + * + * @param {Injector} injector Injector. + * @param {number} courseId The course ID. + * @return {CoreCourseOptionsHandlerData|Promise} Data or promise resolved with the data. + */ + getDisplayData(injector: Injector, courseId: number): CoreCourseOptionsHandlerData | Promise { + return { + title: 'addon.blog.blog', + class: 'addon-blog-handler', + component: AddonBlogEntriesComponent + }; + } + + /** + * Called when a course is downloaded. It should prefetch all the data to be able to see the addon in offline. + * + * @param {any} course The course. + * @return {Promise} Promise resolved when done. + */ + prefetch(course: any): Promise { + const siteId = this.sitesProvider.getCurrentSiteId(); + + return this.blogProvider.getEntries({courseid: course.id}).then((result) => { + return result.entries.map((entry) => { + let files = []; + + if (entry.attachmentfiles && entry.attachmentfiles.length) { + files = entry.attachmentfiles; + } + if (entry.summaryfiles && entry.summaryfiles.length) { + files = files.concat(entry.summaryfiles); + } + + if (files.length > 0) { + return this.filepoolProvider.addFilesToQueue(siteId, files, entry.module, entry.id); + } + + return Promise.resolve(); + }); + }); + } +} diff --git a/src/addon/blog/providers/mainmenu-handler.ts b/src/addon/blog/providers/mainmenu-handler.ts new file mode 100644 index 000000000..e45bfd06f --- /dev/null +++ b/src/addon/blog/providers/mainmenu-handler.ts @@ -0,0 +1,51 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { AddonBlogProvider } from './blog'; +import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '@core/mainmenu/providers/delegate'; + +/** + * Handler to inject an option into main menu. + */ +@Injectable() +export class AddonBlogMainMenuHandler implements CoreMainMenuHandler { + name = 'AddonBlog'; + priority = 450; + + constructor(private blogProvider: AddonBlogProvider) { } + + /** + * Check if the handler is enabled on a site level. + * + * @return {boolean} Whether or not the handler is enabled on a site level. + */ + isEnabled(): boolean | Promise { + return this.blogProvider.isPluginEnabled(); + } + + /** + * Returns the data needed to render the handler. + * + * @return {CoreMainMenuHandlerData} Data needed to render the handler. + */ + getDisplayData(): CoreMainMenuHandlerData { + return { + icon: 'fa-newspaper-o', + title: 'addon.blog.siteblogheading', + page: 'AddonBlogEntriesPage', + class: 'addon-blog-handler' + }; + } +} diff --git a/src/addon/blog/providers/user-handler.ts b/src/addon/blog/providers/user-handler.ts new file mode 100644 index 000000000..039b9ed56 --- /dev/null +++ b/src/addon/blog/providers/user-handler.ts @@ -0,0 +1,71 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { CoreUserDelegate, CoreUserProfileHandler, CoreUserProfileHandlerData } from '@core/user/providers/user-delegate'; +import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; +import { AddonBlogProvider } from './blog'; + +/** + * Profile item handler. + */ +@Injectable() +export class AddonBlogUserHandler implements CoreUserProfileHandler { + name = 'AddonBlog:blogs'; + priority = 300; + type = CoreUserDelegate.TYPE_NEW_PAGE; + + constructor(protected linkHelper: CoreContentLinksHelperProvider, protected blogProvider: AddonBlogProvider) { + } + + /** + * Whether or not the handler is enabled on a site level. + * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + */ + isEnabled(): boolean | Promise { + return this.blogProvider.isPluginEnabled(); + } + + /** + * Check if handler is enabled for this user in this context. + * + * @param {any} user User to check. + * @param {number} courseId Course ID. + * @param {any} [navOptions] Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. + * @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. + * @return {boolean|Promise} Promise resolved with true if enabled, resolved with false otherwise. + */ + isEnabledForUser(user: any, courseId: number, navOptions?: any, admOptions?: any): boolean | Promise { + return true; + } + + /** + * Returns the data needed to render the handler. + * + * @return {CoreUserProfileHandlerData} Data needed to render the handler. + */ + getDisplayData(user: any, courseId: number): CoreUserProfileHandlerData { + return { + icon: 'fa-newspaper-o', + title: 'addon.blog.blogentries', + class: 'addon-blog-handler', + action: (event, navCtrl, user, courseId): void => { + event.preventDefault(); + event.stopPropagation(); + // Always use redirect to make it the new history root (to avoid "loops" in history). + this.linkHelper.goInSite(navCtrl, 'AddonBlogEntriesPage', { userId: user.id, courseId: courseId }); + } + }; + } +} 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 fd9fd8488..96a7e5865 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 @@ -3,6 +3,7 @@ + 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 32c49d009..51fa95154 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 @@ -6,6 +6,7 @@ + diff --git a/src/addon/mod/chat/components/index/addon-mod-chat-index.html b/src/addon/mod/chat/components/index/addon-mod-chat-index.html index 43432d53b..b2f0d53c3 100644 --- a/src/addon/mod/chat/components/index/addon-mod-chat-index.html +++ b/src/addon/mod/chat/components/index/addon-mod-chat-index.html @@ -3,6 +3,7 @@ + diff --git a/src/addon/mod/chat/components/index/index.ts b/src/addon/mod/chat/components/index/index.ts index 34f3bc248..55d60961f 100644 --- a/src/addon/mod/chat/components/index/index.ts +++ b/src/addon/mod/chat/components/index/index.ts @@ -35,7 +35,7 @@ export class AddonModChatIndexComponent extends CoreCourseModuleMainActivityComp protected title: string; constructor(injector: Injector, private chatProvider: AddonModChatProvider, private timeUtils: CoreTimeUtilsProvider, - private navCtrl: NavController) { + protected navCtrl: NavController) { super(injector); } diff --git a/src/addon/mod/choice/components/index/addon-mod-choice-index.html b/src/addon/mod/choice/components/index/addon-mod-choice-index.html index 58eeaefe7..59a85ab7b 100644 --- a/src/addon/mod/choice/components/index/addon-mod-choice-index.html +++ b/src/addon/mod/choice/components/index/addon-mod-choice-index.html @@ -3,6 +3,7 @@ + diff --git a/src/addon/mod/data/components/index/addon-mod-data-index.html b/src/addon/mod/data/components/index/addon-mod-data-index.html index 94b658e30..62d8c38a0 100644 --- a/src/addon/mod/data/components/index/addon-mod-data-index.html +++ b/src/addon/mod/data/components/index/addon-mod-data-index.html @@ -6,6 +6,7 @@ + diff --git a/src/addon/mod/feedback/components/index/addon-mod-feedback-index.html b/src/addon/mod/feedback/components/index/addon-mod-feedback-index.html index 10df6623c..9fef582e3 100644 --- a/src/addon/mod/feedback/components/index/addon-mod-feedback-index.html +++ b/src/addon/mod/feedback/components/index/addon-mod-feedback-index.html @@ -3,6 +3,7 @@ + diff --git a/src/addon/mod/feedback/components/index/index.ts b/src/addon/mod/feedback/components/index/index.ts index 1eb116187..6914e4b03 100644 --- a/src/addon/mod/feedback/components/index/index.ts +++ b/src/addon/mod/feedback/components/index/index.ts @@ -70,7 +70,7 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity constructor(injector: Injector, private feedbackProvider: AddonModFeedbackProvider, @Optional() content: Content, private feedbackOffline: AddonModFeedbackOfflineProvider, private groupsProvider: CoreGroupsProvider, - private feedbackSync: AddonModFeedbackSyncProvider, private navCtrl: NavController, + private feedbackSync: AddonModFeedbackSyncProvider, protected navCtrl: NavController, private feedbackHelper: AddonModFeedbackHelperProvider, private timeUtils: CoreTimeUtilsProvider) { super(injector, content); 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 48bc258e1..f9ccdcf31 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 @@ -3,6 +3,7 @@ + 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 bcbce5865..e4d9b273d 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 @@ -3,6 +3,7 @@ + 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 f57a4fd31..2b748a568 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 @@ -6,6 +6,7 @@ + diff --git a/src/addon/mod/imscp/components/index/addon-mod-imscp-index.html b/src/addon/mod/imscp/components/index/addon-mod-imscp-index.html index 3bd7cdebd..eab430fd2 100644 --- a/src/addon/mod/imscp/components/index/addon-mod-imscp-index.html +++ b/src/addon/mod/imscp/components/index/addon-mod-imscp-index.html @@ -6,6 +6,7 @@ + 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 a394f589b..61a19ce3c 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 @@ -3,6 +3,7 @@ + diff --git a/src/addon/mod/lti/components/index/addon-mod-lti-index.html b/src/addon/mod/lti/components/index/addon-mod-lti-index.html index 049830e41..492303fa3 100644 --- a/src/addon/mod/lti/components/index/addon-mod-lti-index.html +++ b/src/addon/mod/lti/components/index/addon-mod-lti-index.html @@ -3,6 +3,7 @@ + diff --git a/src/addon/mod/page/components/index/addon-mod-page-index.html b/src/addon/mod/page/components/index/addon-mod-page-index.html index 3919124c4..8c2c78a40 100644 --- a/src/addon/mod/page/components/index/addon-mod-page-index.html +++ b/src/addon/mod/page/components/index/addon-mod-page-index.html @@ -3,6 +3,7 @@ + diff --git a/src/addon/mod/quiz/components/index/addon-mod-quiz-index.html b/src/addon/mod/quiz/components/index/addon-mod-quiz-index.html index 6316b64cc..5113cd105 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 @@ -3,6 +3,7 @@ + diff --git a/src/addon/mod/resource/components/index/addon-mod-resource-index.html b/src/addon/mod/resource/components/index/addon-mod-resource-index.html index 5b4289d28..7a964f220 100644 --- a/src/addon/mod/resource/components/index/addon-mod-resource-index.html +++ b/src/addon/mod/resource/components/index/addon-mod-resource-index.html @@ -3,6 +3,7 @@ + diff --git a/src/addon/mod/scorm/components/index/addon-mod-scorm-index.html b/src/addon/mod/scorm/components/index/addon-mod-scorm-index.html index fdd0eee67..947e4be78 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 @@ -3,6 +3,7 @@ + diff --git a/src/addon/mod/survey/components/index/addon-mod-survey-index.html b/src/addon/mod/survey/components/index/addon-mod-survey-index.html index 4ad777c3a..01645b24b 100644 --- a/src/addon/mod/survey/components/index/addon-mod-survey-index.html +++ b/src/addon/mod/survey/components/index/addon-mod-survey-index.html @@ -3,6 +3,7 @@ + diff --git a/src/addon/mod/url/components/index/addon-mod-url-index.html b/src/addon/mod/url/components/index/addon-mod-url-index.html index 422095419..bf2d57265 100644 --- a/src/addon/mod/url/components/index/addon-mod-url-index.html +++ b/src/addon/mod/url/components/index/addon-mod-url-index.html @@ -3,6 +3,7 @@ + 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 976ae64c9..0a8a6c5b1 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 @@ -13,6 +13,7 @@ + diff --git a/src/addon/mod/workshop/components/index/addon-mod-workshop-index.html b/src/addon/mod/workshop/components/index/addon-mod-workshop-index.html index bd3b2b12f..f778b5f19 100644 --- a/src/addon/mod/workshop/components/index/addon-mod-workshop-index.html +++ b/src/addon/mod/workshop/components/index/addon-mod-workshop-index.html @@ -3,6 +3,7 @@ + diff --git a/src/addon/mod/workshop/components/index/index.ts b/src/addon/mod/workshop/components/index/index.ts index 419ef4d4a..496039184 100644 --- a/src/addon/mod/workshop/components/index/index.ts +++ b/src/addon/mod/workshop/components/index/index.ts @@ -67,7 +67,7 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity constructor(injector: Injector, private workshopProvider: AddonModWorkshopProvider, @Optional() content: Content, private workshopOffline: AddonModWorkshopOfflineProvider, private groupsProvider: CoreGroupsProvider, - private navCtrl: NavController, private modalCtrl: ModalController, private utils: CoreUtilsProvider, + protected navCtrl: NavController, private modalCtrl: ModalController, private utils: CoreUtilsProvider, platform: Platform, private workshopHelper: AddonModWorkshopHelperProvider, private workshopSync: AddonModWorkshopSyncProvider) { super(injector, content); diff --git a/src/addon/notes/providers/user-handler.ts b/src/addon/notes/providers/user-handler.ts index a5a409ded..a348ffb13 100644 --- a/src/addon/notes/providers/user-handler.ts +++ b/src/addon/notes/providers/user-handler.ts @@ -79,7 +79,7 @@ export class AddonNotesUserHandler implements CoreUserProfileHandler { return this.noteEnabledCache[courseId]; } - return this.notesProvider.isPluginAddNoteEnabledForCourse(courseId).then((enabled) => { + return this.notesProvider.isPluginViewNotesEnabledForCourse(courseId).then((enabled) => { this.noteEnabledCache[courseId] = enabled; return enabled; diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 83003549f..551e7a880 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -81,6 +81,7 @@ import { CoreBlockModule } from '@core/block/block.module'; // Addon modules. import { AddonBadgesModule } from '@addon/badges/badges.module'; +import { AddonBlogModule } from '@addon/blog/blog.module'; import { AddonCalendarModule } from '@addon/calendar/calendar.module'; import { AddonCompetencyModule } from '@addon/competency/competency.module'; import { AddonCourseCompletionModule } from '@addon/coursecompletion/coursecompletion.module'; @@ -198,6 +199,7 @@ export const CORE_PROVIDERS: any[] = [ CoreCommentsModule, CoreBlockModule, AddonBadgesModule, + AddonBlogModule, AddonCalendarModule, AddonCompetencyModule, AddonCourseCompletionModule, diff --git a/src/app/app.scss b/src/app/app.scss index 04e9a7c77..5fe61c0fa 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -990,7 +990,9 @@ details summary { pointer-events: auto; } -.icon.fa-graduation-cap{ +.icon.fa-graduation-cap, +.item > .icon.fa, +.item-inner > .icon.fa { font-size: 21px; width: 21px; line-height: 28px; diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index f440f6e3a..a82101823 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -56,6 +56,16 @@ "addon.block_timeline.pluginname": "Timeline", "addon.block_timeline.sortbycourses": "Sort by courses", "addon.block_timeline.sortbydates": "Sort by dates", + "addon.blog.blog": "Blog", + "addon.blog.blogentries": "Blog entries", + "addon.blog.errorloadentries": "Error loading blog entries.", + "addon.blog.linktooriginalentry": "Link to original blog entry", + "addon.blog.noentriesyet": "No visible entries here", + "addon.blog.publishtonoone": "Yourself (draft)", + "addon.blog.publishtosite": "Anyone on this site", + "addon.blog.publishtoworld": "Anyone in the world", + "addon.blog.showonlyyourentries": "Show only your entries", + "addon.blog.siteblogheading": "Site blog", "addon.calendar.calendar": "Calendar", "addon.calendar.calendarevents": "Calendar events", "addon.calendar.defaultnotificationtime": "Default notification time", diff --git a/src/core/course/classes/main-resource-component.ts b/src/core/course/classes/main-resource-component.ts index cf60439d7..8bc986812 100644 --- a/src/core/course/classes/main-resource-component.ts +++ b/src/core/course/classes/main-resource-component.ts @@ -13,6 +13,7 @@ // limitations under the License. import { OnInit, OnDestroy, Input, Output, EventEmitter, Injector } from '@angular/core'; +import { NavController } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; import { CoreLoggerProvider } from '@providers/logger'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; @@ -20,6 +21,8 @@ import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreCourseHelperProvider } from '@core/course/providers/helper'; import { CoreCourseModuleMainComponent, CoreCourseModuleDelegate } from '@core/course/providers/module-delegate'; import { CoreCourseSectionPage } from '@core/course/pages/section/section.ts'; +import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; +import { AddonBlogProvider } from '@addon/blog/providers/blog'; /** * Template class to easily create CoreCourseModuleMainComponent of resources (or activities without syncing). @@ -32,6 +35,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, loaded: boolean; // If the component has been loaded. component: string; // Component name. componentId: number; // Component ID. + blog: boolean; // If blog is avalaible. // Data for context menu. externalUrl: string; // External URL to open in browser. @@ -54,6 +58,9 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, protected domUtils: CoreDomUtilsProvider; protected moduleDelegate: CoreCourseModuleDelegate; protected courseSectionPage: CoreCourseSectionPage; + protected linkHelper: CoreContentLinksHelperProvider; + protected navCtrl: NavController; + protected blogProvider: AddonBlogProvider; protected logger; @@ -64,6 +71,9 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, this.domUtils = injector.get(CoreDomUtilsProvider); this.moduleDelegate = injector.get(CoreCourseModuleDelegate); this.courseSectionPage = injector.get(CoreCourseSectionPage, null); + this.linkHelper = injector.get(CoreContentLinksHelperProvider); + this.navCtrl = injector.get(NavController, null); + this.blogProvider = injector.get(AddonBlogProvider, null); this.dataRetrieved = new EventEmitter(); const loggerProvider = injector.get(CoreLoggerProvider); @@ -79,6 +89,9 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, this.externalUrl = this.module.url; this.loaded = false; this.refreshIcon = 'spinner'; + this.blogProvider.isPluginEnabled().then((enabled) => { + this.blog = enabled; + }); } /** @@ -216,6 +229,16 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, this.textUtils.expandText(this.translate.instant('core.description'), this.description, this.component, this.module.id); } + /** + * Go to blog posts. + * + * @param {any} event Event. + */ + gotoBlog(event: any): void { + // Always use redirect to make it the new history root (to avoid "loops" in history). + this.linkHelper.goInSite(this.navCtrl, 'AddonBlogEntriesPage', { cmId: this.module.id }); + } + /** * Prefetch the module. */ diff --git a/src/core/course/providers/helper.ts b/src/core/course/providers/helper.ts index 8e22ab42f..6f05e157f 100644 --- a/src/core/course/providers/helper.ts +++ b/src/core/course/providers/helper.ts @@ -753,6 +753,25 @@ export class CoreCourseHelperProvider { }); } + /** + * Check if the course has a block with that name. + * + * @param {number} courseId Course ID. + * @param {string} name Block name to search. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with true if the block exists or false otherwise. + * @since 3.3 + */ + hasABlockNamed(courseId: number, name: string, siteId?: string): Promise { + return this.courseProvider.getCourseBlocks(courseId, siteId).then((blocks) => { + return blocks.some((block) => { + return block.name == name; + }); + }).catch(() => { + return false; + }); + } + /** * Initialize the prefetch icon for selected courses. * From 3cf70c83f7a64a117e98673044957e4a12e5cc0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Mon, 18 Feb 2019 15:27:39 +0100 Subject: [PATCH 080/191] MOBILE-2386 course: Migrate logs database schema --- src/core/course/providers/log-helper.ts | 64 +++++++++++++------------ 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/src/core/course/providers/log-helper.ts b/src/core/course/providers/log-helper.ts index 24bde0ed5..02a9aa92b 100644 --- a/src/core/course/providers/log-helper.ts +++ b/src/core/course/providers/log-helper.ts @@ -13,7 +13,7 @@ // limitations under the License. import { Injectable } from '@angular/core'; -import { CoreSitesProvider } from '@providers/sites'; +import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreUtilsProvider } from '@providers/utils/utils'; @@ -27,39 +27,43 @@ export class CoreCourseLogHelperProvider { // Variables for database. static ACTIVITY_LOG_TABLE = 'course_activity_log'; - protected tablesSchema = [ - { - name: CoreCourseLogHelperProvider.ACTIVITY_LOG_TABLE, - columns: [ - { - name: 'component', - type: 'TEXT' - }, - { - name: 'componentid', - type: 'INTEGER' - }, - { - name: 'ws', - type: 'TEXT' - }, - { - name: 'data', - type: 'TEXT' - }, - { - name: 'time', - type: 'INTEGER' - } - ], - primaryKeys: ['component', 'componentid', 'ws', 'time'] - } - ]; + protected siteSchema: CoreSiteSchema = { + name: 'CoreCourseOfflineProvider', + version: 1, + tables: [ + { + name: CoreCourseLogHelperProvider.ACTIVITY_LOG_TABLE, + columns: [ + { + name: 'component', + type: 'TEXT' + }, + { + name: 'componentid', + type: 'INTEGER' + }, + { + name: 'ws', + type: 'TEXT' + }, + { + name: 'data', + type: 'TEXT' + }, + { + name: 'time', + type: 'INTEGER' + } + ], + primaryKeys: ['component', 'componentid', 'ws', 'time'] + } + ] + }; constructor(protected sitesProvider: CoreSitesProvider, protected timeUtils: CoreTimeUtilsProvider, protected textUtils: CoreTextUtilsProvider, protected utils: CoreUtilsProvider, protected appProvider: CoreAppProvider) { - this.sitesProvider.createTablesFromSchema(this.tablesSchema); + this.sitesProvider.registerSiteSchema(this.siteSchema); } /** From 6663fe0bb483458a5c955ce8510baa7cbcb62e83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 20 Feb 2019 13:37:47 +0100 Subject: [PATCH 081/191] MOBILE-2888 login: Make url optional in multi site selector --- src/core/login/pages/site/site.html | 4 ++-- src/core/login/pages/site/site.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/login/pages/site/site.html b/src/core/login/pages/site/site.html index 28007d119..1b7d34d97 100644 --- a/src/core/login/pages/site/site.html +++ b/src/core/login/pages/site/site.html @@ -36,13 +36,13 @@ - +

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

{{site.name}}

-

{{site.url}}

+

{{site.url}}

diff --git a/src/core/login/pages/site/site.ts b/src/core/login/pages/site/site.ts index 2386735f8..c4bd86fda 100644 --- a/src/core/login/pages/site/site.ts +++ b/src/core/login/pages/site/site.ts @@ -50,7 +50,7 @@ export class CoreLoginSitePage { this.fixedSites = this.loginHelper.getFixedSites(); this.fixedDisplay = CoreConfigConstants.multisitesdisplay; // Autoselect if not defined. - if (['list', 'select', 'buttons'].indexOf(this.fixedDisplay) < 0) { + if (['list', 'listnourl', 'select', 'buttons'].indexOf(this.fixedDisplay) < 0) { this.fixedDisplay = this.fixedSites.length > 8 ? 'list' : (this.fixedSites.length > 3 ? 'select' : 'buttons'); } this.filteredSites = this.fixedSites; From a9c39d93a08223d831bc6fff6c7721e47a2be5cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 20 Feb 2019 15:25:27 +0100 Subject: [PATCH 082/191] MOBILE-2888 login: Add a heading style --- src/core/login/pages/site-error/site-error.scss | 3 +++ src/core/login/pages/site/site.html | 3 +-- 2 files changed, 4 insertions(+), 2 deletions(-) create mode 100644 src/core/login/pages/site-error/site-error.scss diff --git a/src/core/login/pages/site-error/site-error.scss b/src/core/login/pages/site-error/site-error.scss new file mode 100644 index 000000000..510539e27 --- /dev/null +++ b/src/core/login/pages/site-error/site-error.scss @@ -0,0 +1,3 @@ + page-core-login-site-error button.button.button-block { + margin-bottom: 3rem; + } \ No newline at end of file diff --git a/src/core/login/pages/site/site.html b/src/core/login/pages/site/site.html index 1b7d34d97..9503d5335 100644 --- a/src/core/login/pages/site/site.html +++ b/src/core/login/pages/site/site.html @@ -37,8 +37,7 @@ -

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

- +

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

{{site.name}}

From e45ca59d91356ca99a7fbeacbfc9939b1b994b04 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 21 Feb 2019 11:28:59 +0100 Subject: [PATCH 083/191] MOBILE-2799 user: Update site info if avatar changed --- .../user-avatar/core-user-avatar.html | 4 +-- src/components/user-avatar/user-avatar.ts | 7 ++-- src/core/user/pages/profile/profile.ts | 34 ++++++++++++++----- 3 files changed, 32 insertions(+), 13 deletions(-) diff --git a/src/components/user-avatar/core-user-avatar.html b/src/components/user-avatar/core-user-avatar.html index 4eeffcd06..15b7a35f9 100644 --- a/src/components/user-avatar/core-user-avatar.html +++ b/src/components/user-avatar/core-user-avatar.html @@ -1,4 +1,4 @@ - - + + \ No newline at end of file diff --git a/src/components/user-avatar/user-avatar.ts b/src/components/user-avatar/user-avatar.ts index 65108f470..cb1243e1d 100644 --- a/src/components/user-avatar/user-avatar.ts +++ b/src/components/user-avatar/user-avatar.ts @@ -39,6 +39,8 @@ export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy { @Input() protected courseId?: number; @Input() checkOnline = false; // If want to check and show online status. + avatarUrl?: string; + // Variable to check if we consider this user online or not. // @TODO: Use setting when available (see MDL-63972) so we can use site setting. protected timetoshowusers = 300000; // Miliseconds default. @@ -52,7 +54,7 @@ export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy { this.pictureObs = eventsProvider.on(CoreUserProvider.PROFILE_PICTURE_UPDATED, (data) => { if (data.userId == this.userId) { - this.profileUrl = data.picture; + this.avatarUrl = data.picture; } }, this.sitesProvider.getCurrentSiteId()); } @@ -82,7 +84,7 @@ export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy { this.user.userpictureurl || this.user.profileimageurlsmall)); if (typeof profileUrl == 'string') { - this.profileUrl = profileUrl; + this.avatarUrl = profileUrl; } this.fullname = this.fullname || (this.user && (this.user.fullname || this.user.userfullname)); @@ -92,7 +94,6 @@ export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy { // If not available we cannot ensure the avatar is from the current user. this.myUser = this.userId && this.userId == this.currentUserId; - } /** diff --git a/src/core/user/pages/profile/profile.ts b/src/core/user/pages/profile/profile.ts index efef99985..957de53e7 100644 --- a/src/core/user/pages/profile/profile.ts +++ b/src/core/user/pages/profile/profile.ts @@ -97,20 +97,15 @@ export class CoreUserProfilePage { fetchUser(): Promise { return this.userProvider.getProfile(this.userId, this.courseId).then((user) => { - if (this.userId == this.site.getUserId() && user.profileimageurl != this.site.getInfo().userpictureurl) { - // The current user image received is different than the one stored in site info. Assume the image was updated. - this.eventsProvider.trigger(CoreUserProvider.PROFILE_PICTURE_UPDATED, { - userId: this.userId, - picture: user.profileimageurl - }, this.site.getId()); - } - user.address = this.userHelper.formatAddress('', user.city, user.country); user.roles = this.userHelper.formatRoleList(user.roles); this.user = user; this.title = user.fullname; + // If there's already a subscription, unsubscribe because we'll get a new one. + this.subscription && this.subscription.unsubscribe(); + this.subscription = this.userDelegate.getProfileHandlersFor(user, this.courseId).subscribe((handlers) => { this.actionHandlers = []; this.newPageHandlers = []; @@ -133,6 +128,29 @@ export class CoreUserProfilePage { this.isLoadingHandlers = !this.userDelegate.areHandlersLoaded(user.id); }); + if (this.userId == this.site.getUserId() && user.profileimageurl != this.site.getInfo().userpictureurl) { + // The current user image received is different than the one stored in site info. Assume the image was updated. + // Update the site info to get the right avatar in there. + return this.sitesProvider.updateSiteInfo(this.site.getId()).then(() => { + if (user.profileimageurl != this.site.getInfo().userpictureurl) { + // The image is still different, this means that the good one is the one in site info. + return this.refreshUser(); + } else { + // Now they're the same, send event to use the right avatar in the rest of the app. + this.eventsProvider.trigger(CoreUserProvider.PROFILE_PICTURE_UPDATED, { + userId: this.userId, + picture: user.profileimageurl + }, this.site.getId()); + } + }, () => { + // Cannot update site info. Assume the profile image is the right one. + this.eventsProvider.trigger(CoreUserProvider.PROFILE_PICTURE_UPDATED, { + userId: this.userId, + picture: user.profileimageurl + }, this.site.getId()); + }); + } + }).catch((error) => { // Error is null for deleted users, do not show the modal. if (error) { From b95f8075e7787207687671454b12ce3b0bb550bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Mon, 18 Feb 2019 15:26:52 +0100 Subject: [PATCH 084/191] MOBILE-2871 assign: Add group selector on submission list --- .../index/addon-mod-assign-index.html | 12 ++- .../mod/assign/components/index/index.ts | 43 +++++++-- .../components/submission/submission.ts | 5 +- src/addon/mod/assign/pages/edit/edit.ts | 6 +- .../submission-list/submission-list.html | 11 ++- .../pages/submission-list/submission-list.ts | 72 ++++++++++----- .../submission-review/submission-review.ts | 3 +- src/addon/mod/assign/providers/assign-sync.ts | 8 +- src/addon/mod/assign/providers/assign.ts | 85 ++++++++--------- src/addon/mod/assign/providers/helper.ts | 13 +-- .../mod/assign/providers/prefetch-handler.ts | 91 +++++++++++-------- src/app/app.scss | 2 +- 12 files changed, 218 insertions(+), 133 deletions(-) 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 96a7e5865..edf43166f 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 @@ -32,7 +32,15 @@
- + + + {{ 'core.groupsseparate' | translate }} + {{ 'core.groupsvisible' | translate }} + + {{groupOpt.name}} + + +

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

{{ timeRemaining }}

@@ -80,7 +88,7 @@ {{ 'addon.mod_assign.ungroupedusers' | translate }}
- + diff --git a/src/addon/mod/assign/components/index/index.ts b/src/addon/mod/assign/components/index/index.ts index 23d2ebda0..7cd42a015 100644 --- a/src/addon/mod/assign/components/index/index.ts +++ b/src/addon/mod/assign/components/index/index.ts @@ -14,7 +14,7 @@ import { Component, Optional, Injector, ViewChild } from '@angular/core'; import { Content, NavController } from 'ionic-angular'; -import { CoreGroupsProvider } from '@providers/groups'; +import { CoreGroupsProvider, CoreGroupInfo } from '@providers/groups'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main-activity-component'; import { AddonModAssignProvider } from '../../providers/assign'; @@ -45,6 +45,12 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo summary: any; // The summary. needsGradingAvalaible: boolean; // Whether we can see the submissions that need grading. + groupInfo: CoreGroupInfo = { + groups: [], + separateGroups: false, + visibleGroups: false + }; + // Status. submissionStatusSubmitted = AddonModAssignProvider.SUBMISSION_STATUS_SUBMITTED; submissionStatusDraft = AddonModAssignProvider.SUBMISSION_STATUS_DRAFT; @@ -193,15 +199,13 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo } // Check if groupmode is enabled to avoid showing wrong numbers. - return this.groupsProvider.activityHasGroups(this.assign.cmid).then((hasGroups) => { - this.showNumbers = !hasGroups; + return this.groupsProvider.getActivityGroupInfo(this.assign.cmid, false).then((groupInfo) => { + this.groupInfo = groupInfo; + this.showNumbers = groupInfo.groups.length == 0 || + this.sitesProvider.getCurrentSite().isVersionGreaterEqualThan('3.5'); - return this.assignProvider.getSubmissionStatus(this.assign.id).then((response) => { - this.summary = response.gradingsummary; - - this.needsGradingAvalaible = response.gradingsummary.submissionsneedgradingcount > 0 && - this.sitesProvider.getCurrentSite().isVersionGreaterEqualThan('3.2'); - }); + return this.setGroup(this.group || (groupInfo.groups && groupInfo.groups[0] && groupInfo.groups[0].id) || + 0); }); } @@ -222,6 +226,23 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo }); } + /** + * Set group to see the summary. + * + * @param {number} groupId Group ID. + * @return {Promise} Resolved when done. + */ + setGroup(groupId: number): Promise { + this.group = groupId; + + return this.assignProvider.getSubmissionStatus(this.assign.id, undefined, this.group).then((response) => { + this.summary = response.gradingsummary; + + this.needsGradingAvalaible = response.gradingsummary && response.gradingsummary.submissionsneedgradingcount > 0 && + this.sitesProvider.getCurrentSite().isVersionGreaterEqualThan('3.2'); + }); + } + /** * Go to view a list of submissions. * @@ -232,6 +253,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo if (typeof status == 'undefined') { this.navCtrl.push('AddonModAssignSubmissionListPage', { courseId: this.courseId, + groupId: this.group || 0, moduleId: this.module.id, moduleName: this.moduleName }); @@ -239,6 +261,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo this.navCtrl.push('AddonModAssignSubmissionListPage', { status: status, courseId: this.courseId, + groupId: this.group || 0, moduleId: this.module.id, moduleName: this.moduleName }); @@ -273,7 +296,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo promises.push(this.assignProvider.invalidateAllSubmissionData(this.assign.id)); if (this.canViewAllSubmissions) { - promises.push(this.assignProvider.invalidateSubmissionStatusData(this.assign.id)); + promises.push(this.assignProvider.invalidateSubmissionStatusData(this.assign.id, undefined, this.group)); } } diff --git a/src/addon/mod/assign/components/submission/submission.ts b/src/addon/mod/assign/components/submission/submission.ts index d50a97a63..371c1bcf2 100644 --- a/src/addon/mod/assign/components/submission/submission.ts +++ b/src/addon/mod/assign/components/submission/submission.ts @@ -338,7 +338,8 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy { promises.push(this.assignProvider.invalidateAssignmentData(this.courseId)); if (this.assign) { - promises.push(this.assignProvider.invalidateSubmissionStatusData(this.assign.id, this.submitId, !!this.blindId)); + promises.push(this.assignProvider.invalidateSubmissionStatusData(this.assign.id, this.submitId, undefined, + !!this.blindId)); promises.push(this.assignProvider.invalidateAssignmentUserMappingsData(this.assign.id)); promises.push(this.assignProvider.invalidateListParticipantsData(this.assign.id)); } @@ -408,7 +409,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy { return Promise.all(promises); }).then(() => { // Get submission status. - return this.assignProvider.getSubmissionStatusWithRetry(this.assign, this.submitId, isBlind); + return this.assignProvider.getSubmissionStatusWithRetry(this.assign, this.submitId, undefined, isBlind); }).then((response) => { const promises = []; diff --git a/src/addon/mod/assign/pages/edit/edit.ts b/src/addon/mod/assign/pages/edit/edit.ts index 3f2ba6c32..f6072f48f 100644 --- a/src/addon/mod/assign/pages/edit/edit.ts +++ b/src/addon/mod/assign/pages/edit/edit.ts @@ -121,9 +121,11 @@ export class AddonModAssignEditPage implements OnInit, OnDestroy { }).then(() => { // Get submission status. Ignore cache to get the latest data. - return this.assignProvider.getSubmissionStatus(this.assign.id, this.userId, this.isBlind, false, true).catch((err) => { + return this.assignProvider.getSubmissionStatus(this.assign.id, this.userId, undefined, this.isBlind, false, true) + .catch((err) => { // Cannot connect. Get cached data. - return this.assignProvider.getSubmissionStatus(this.assign.id, this.userId, this.isBlind).then((response) => { + return this.assignProvider.getSubmissionStatus(this.assign.id, this.userId, undefined, this.isBlind) + .then((response) => { const userSubmission = this.assignProvider.getSubmissionObjectFromAttempt(this.assign, response.lastattempt); // Check if the user can edit it in offline. diff --git a/src/addon/mod/assign/pages/submission-list/submission-list.html b/src/addon/mod/assign/pages/submission-list/submission-list.html index 8a0ff95ef..cd96434da 100644 --- a/src/addon/mod/assign/pages/submission-list/submission-list.html +++ b/src/addon/mod/assign/pages/submission-list/submission-list.html @@ -15,10 +15,17 @@ + + {{ 'core.groupsseparate' | translate }} + {{ 'core.groupsvisible' | translate }} + + {{groupOpt.name}} + + - - + +

{{submission.userfullname}}

{{ 'addon.mod_assign.hiddenuser' | translate }}{{submission.blindid}}

diff --git a/src/addon/mod/assign/pages/submission-list/submission-list.ts b/src/addon/mod/assign/pages/submission-list/submission-list.ts index d5103ccf3..8ce653613 100644 --- a/src/addon/mod/assign/pages/submission-list/submission-list.ts +++ b/src/addon/mod/assign/pages/submission-list/submission-list.ts @@ -18,6 +18,7 @@ import { TranslateService } from '@ngx-translate/core'; import { CoreEventsProvider } from '@providers/events'; import { CoreSitesProvider } from '@providers/sites'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { CoreGroupsProvider, CoreGroupInfo } from '@providers/groups'; import { AddonModAssignProvider } from '../../providers/assign'; import { AddonModAssignOfflineProvider } from '../../providers/assign-offline'; import { AddonModAssignHelperProvider } from '../../providers/helper'; @@ -40,19 +41,28 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy { loaded: boolean; // Whether data has been loaded. haveAllParticipants: boolean; // Whether all participants have been loaded. selectedSubmissionId: number; // Selected submission ID. + groupId = 0; // Group ID to show. + + groupInfo: CoreGroupInfo = { + groups: [], + separateGroups: false, + visibleGroups: false + }; protected moduleId: number; // Module ID the submission belongs to. protected courseId: number; // Course ID the assignment belongs to. protected selectedStatus: string; // The status to see. protected gradedObserver; // Observer to refresh data when a grade changes. + protected submissionsData: any; constructor(navParams: NavParams, sitesProvider: CoreSitesProvider, eventsProvider: CoreEventsProvider, protected domUtils: CoreDomUtilsProvider, protected translate: TranslateService, protected assignProvider: AddonModAssignProvider, protected assignOfflineProvider: AddonModAssignOfflineProvider, - protected assignHelper: AddonModAssignHelperProvider) { + protected assignHelper: AddonModAssignHelperProvider, protected groupsProvider: CoreGroupsProvider) { this.moduleId = navParams.get('moduleId'); this.courseId = navParams.get('courseId'); + this.groupId = navParams.get('groupId'); this.selectedStatus = navParams.get('status'); if (this.selectedStatus) { @@ -98,15 +108,11 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy { * @return {Promise} Promise resolved when done. */ protected fetchAssignment(): Promise { - let participants, - submissionsData, - grades; // Get assignment data. return this.assignProvider.getAssignment(this.courseId, this.moduleId).then((assign) => { this.title = assign.name || this.title; this.assign = assign; - this.haveAllParticipants = true; // Get assignment submissions. return this.assignProvider.getSubmissions(assign.id); @@ -116,15 +122,39 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy { return Promise.reject(null); } - submissionsData = data; + this.submissionsData = data; - // Get the participants. - return this.assignHelper.getParticipants(this.assign).then((parts) => { - this.haveAllParticipants = true; - participants = parts; - }).catch(() => { - this.haveAllParticipants = false; + // Check if groupmode is enabled to avoid showing wrong numbers. + return this.groupsProvider.getActivityGroupInfo(this.assign.cmid, false).then((groupInfo) => { + this.groupInfo = groupInfo; + + return this.setGroup(this.groupId || (groupInfo.groups && groupInfo.groups[0] && groupInfo.groups[0].id) || 0); }); + }).catch((error) => { + this.domUtils.showErrorModalDefault(error, 'Error getting assigment data.'); + }); + } + + /** + * Set group to see the summary. + * + * @param {number} groupId Group ID. + * @return {Promise} Resolved when done. + */ + setGroup(groupId: number): Promise { + let participants, + grades; + + this.groupId = groupId; + + this.haveAllParticipants = true; + + // Get the participants. + return this.assignHelper.getParticipants(this.assign, this.groupId).then((parts) => { + this.haveAllParticipants = true; + participants = parts; + }).catch(() => { + this.haveAllParticipants = false; }).then(() => { if (!this.assign.markingworkflow) { // Get assignment grades only if workflow is not enabled to check grading date. @@ -134,16 +164,16 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy { } }).then(() => { // We want to show the user data on each submission. - return this.assignProvider.getSubmissionsUserData(submissionsData.submissions, this.courseId, this.assign.id, + return this.assignProvider.getSubmissionsUserData(this.submissionsData.submissions, this.courseId, this.assign.id, this.assign.blindmarking && !this.assign.revealidentities, participants); }).then((submissions) => { // Filter the submissions to get only the ones with the right status and add some extra data. const getNeedGrading = this.selectedStatus == AddonModAssignProvider.NEED_GRADING, searchStatus = getNeedGrading ? AddonModAssignProvider.SUBMISSION_STATUS_SUBMITTED : this.selectedStatus, - promises = []; + promises = [], + showSubmissions = []; - this.submissions = []; submissions.forEach((submission) => { if (!searchStatus || searchStatus == submission.status) { promises.push(this.assignOfflineProvider.getSubmissionGrade(this.assign.id, submission.userid).catch(() => { @@ -203,15 +233,15 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy { submission.gradingStatusTranslationId = false; } - this.submissions.push(submission); + showSubmissions.push(submission); }); })); } }); - return Promise.all(promises); - }).catch((error) => { - this.domUtils.showErrorModalDefault(error, 'Error getting assigment data.'); + return Promise.all(promises).then(() => { + this.submissions = showSubmissions; + }); }); } @@ -221,12 +251,12 @@ export class AddonModAssignSubmissionListPage implements OnInit, OnDestroy { * @param {any} submission The submission to load. */ loadSubmission(submission: any): void { - if (this.selectedSubmissionId === submission.id && this.splitviewCtrl.isOn()) { + if (this.selectedSubmissionId === submission.submitid && this.splitviewCtrl.isOn()) { // Already selected. return; } - this.selectedSubmissionId = submission.id; + this.selectedSubmissionId = submission.submitid; this.splitviewCtrl.push('AddonModAssignSubmissionReviewPage', { courseId: this.courseId, diff --git a/src/addon/mod/assign/pages/submission-review/submission-review.ts b/src/addon/mod/assign/pages/submission-review/submission-review.ts index be53916dc..44b0ca4a9 100644 --- a/src/addon/mod/assign/pages/submission-review/submission-review.ts +++ b/src/addon/mod/assign/pages/submission-review/submission-review.ts @@ -132,7 +132,8 @@ export class AddonModAssignSubmissionReviewPage implements OnInit { if (this.assign) { promises.push(this.assignProvider.invalidateSubmissionData(this.assign.id)); promises.push(this.assignProvider.invalidateAssignmentUserMappingsData(this.assign.id)); - promises.push(this.assignProvider.invalidateSubmissionStatusData(this.assign.id, this.submitId, this.blindMarking)); + promises.push(this.assignProvider.invalidateSubmissionStatusData(this.assign.id, this.submitId, undefined, + this.blindMarking)); } return Promise.all(promises).finally(() => { diff --git a/src/addon/mod/assign/providers/assign-sync.ts b/src/addon/mod/assign/providers/assign-sync.ts index 6c1fdd33b..cff8c421a 100644 --- a/src/addon/mod/assign/providers/assign-sync.ts +++ b/src/addon/mod/assign/providers/assign-sync.ts @@ -275,7 +275,7 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider { let discardError, submission; - return this.assignProvider.getSubmissionStatus(assign.id, userId, false, true, true, siteId).then((status) => { + return this.assignProvider.getSubmissionStatus(assign.id, userId, undefined, false, true, true, siteId).then((status) => { const promises = []; submission = this.assignProvider.getSubmissionObjectFromAttempt(assign, status.lastattempt); @@ -310,7 +310,7 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider { } }).then(() => { // Submission data sent, update cached data. No need to block the user for this. - this.assignProvider.getSubmissionStatus(assign.id, userId, false, true, true, siteId); + this.assignProvider.getSubmissionStatus(assign.id, userId, undefined, false, true, true, siteId); }); }).catch((error) => { if (error && this.utils.isWebServiceError(error)) { @@ -364,7 +364,7 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider { const userId = offlineData.userid; let discardError; - return this.assignProvider.getSubmissionStatus(assign.id, userId, false, true, true, siteId).then((status) => { + return this.assignProvider.getSubmissionStatus(assign.id, userId, undefined, false, true, true, siteId).then((status) => { const timemodified = status.feedback && (status.feedback.gradeddate || status.feedback.grade.timemodified); if (timemodified > offlineData.timemodified) { @@ -405,7 +405,7 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider { offlineData.plugindata, siteId).then(() => { // Grades sent, update cached data. No need to block the user for this. - this.assignProvider.getSubmissionStatus(assign.id, userId, false, true, true, siteId); + this.assignProvider.getSubmissionStatus(assign.id, userId, undefined, false, true, true, siteId); }).catch((error) => { if (error && this.utils.isWebServiceError(error)) { // The WebService has thrown an error, this means it cannot be submitted. Discard the offline data. diff --git a/src/addon/mod/assign/providers/assign.ts b/src/addon/mod/assign/providers/assign.ts index cbaa0501a..72320b5f7 100644 --- a/src/addon/mod/assign/providers/assign.ts +++ b/src/addon/mod/assign/providers/assign.ts @@ -21,7 +21,6 @@ import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCommentsProvider } from '@core/comments/providers/comments'; -import { CoreUserProvider } from '@core/user/providers/user'; import { CoreGradesProvider } from '@core/grades/providers/grades'; import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { AddonModAssignSubmissionDelegate } from './submission-delegate'; @@ -67,7 +66,7 @@ export class AddonModAssignProvider { constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private textUtils: CoreTextUtilsProvider, private timeUtils: CoreTimeUtilsProvider, private appProvider: CoreAppProvider, private utils: CoreUtilsProvider, - private userProvider: CoreUserProvider, private submissionDelegate: AddonModAssignSubmissionDelegate, + private submissionDelegate: AddonModAssignSubmissionDelegate, private gradesProvider: CoreGradesProvider, private filepoolProvider: CoreFilepoolProvider, private assignOffline: AddonModAssignOfflineProvider, private commentsProvider: CoreCommentsProvider, private logHelper: CoreCourseLogHelperProvider) { @@ -495,31 +494,37 @@ export class AddonModAssignProvider { * Get information about an assignment submission status for a given user. * * @param {number} assignId Assignment instance id. - * @param {number} [userId] User id (empty for current user). + * @param {number} [userId] User Id (empty for current user). + * @param {number} [groupId] Group Id (empty for all participants). * @param {boolean} [isBlind] If blind marking is enabled or not. * @param {number} [filter=true] True to filter WS response and rewrite URLs, false otherwise. * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @param {string} [siteId] Site id (empty for current site). * @return {Promise} Promise always resolved with the user submission status. */ - getSubmissionStatus(assignId: number, userId?: number, isBlind?: boolean, filter: boolean = true, ignoreCache?: boolean, - siteId?: string): Promise { + getSubmissionStatus(assignId: number, userId?: number, groupId?: number, isBlind?: boolean, filter: boolean = true, + ignoreCache?: boolean, siteId?: string): Promise { userId = userId || 0; return this.sitesProvider.getSite(siteId).then((site) => { + groupId = site.isVersionGreaterEqualThan('3.5') ? groupId || 0 : 0; const params = { assignid: assignId, userid: userId }, preSets: CoreSiteWSPreSets = { - cacheKey: this.getSubmissionStatusCacheKey(assignId, userId, isBlind), + cacheKey: this.getSubmissionStatusCacheKey(assignId, userId, groupId, isBlind), getCacheUsingCacheKey: true, // We use the cache key to take isBlind into account. filter: filter, rewriteurls: filter }; + if (groupId) { + params['groupid'] = groupId; + } + if (ignoreCache) { preSets.getFromCache = false; preSets.emergencyCache = false; @@ -541,21 +546,22 @@ export class AddonModAssignProvider { * * @param {any} assign Assignment. * @param {number} [userId] User id (empty for current user). + * @param {number} [groupId] Group Id (empty for all participants). * @param {boolean} [isBlind] If blind marking is enabled or not. * @param {number} [filter=true] True to filter WS response and rewrite URLs, false otherwise. * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @param {string} [siteId] Site id (empty for current site). * @return {Promise} Promise always resolved with the user submission status. */ - getSubmissionStatusWithRetry(assign: any, userId?: number, isBlind?: boolean, filter: boolean = true, ignoreCache?: boolean, - siteId?: string): Promise { + getSubmissionStatusWithRetry(assign: any, userId?: number, groupId?: number, isBlind?: boolean, filter: boolean = true, + ignoreCache?: boolean, siteId?: string): Promise { - return this.getSubmissionStatus(assign.id, userId, isBlind, filter, ignoreCache, siteId).then((response) => { + return this.getSubmissionStatus(assign.id, userId, groupId, isBlind, filter, ignoreCache, siteId).then((response) => { const userSubmission = this.getSubmissionObjectFromAttempt(assign, response.lastattempt); if (!userSubmission) { // Try again, ignoring cache. - return this.getSubmissionStatus(assign.id, userId, isBlind, filter, true, siteId).catch(() => { + return this.getSubmissionStatus(assign.id, userId, groupId, isBlind, filter, true, siteId).catch(() => { // Error, return the first result even if it doesn't have the user submission. return response; }); @@ -570,16 +576,17 @@ export class AddonModAssignProvider { * * @param {number} assignId Assignment instance id. * @param {number} [userId] User id (empty for current user). + * @param {number} [groupId] Group Id (empty for all participants). * @param {number} [isBlind] If blind marking is enabled or not. * @return {string} Cache key. */ - protected getSubmissionStatusCacheKey(assignId: number, userId: number, isBlind?: boolean): string { + protected getSubmissionStatusCacheKey(assignId: number, userId: number, groupId?: number, isBlind?: boolean): string { if (!userId) { isBlind = false; userId = this.sitesProvider.getCurrentSiteUserId(); } - return this.getSubmissionsCacheKey(assignId) + ':' + userId + ':' + (isBlind ? 1 : 0); + return this.getSubmissionsCacheKey(assignId) + ':' + userId + ':' + (isBlind ? 1 : 0) + ':' + groupId; } /** @@ -624,6 +631,10 @@ export class AddonModAssignProvider { subs = [], hasParticipants = participants && participants.length > 0; + if (!hasParticipants) { + return Promise.resolve([]); + } + submissions.forEach((submission) => { submission.submitid = submission.userid > 0 ? submission.userid : submission.blindid; if (submission.submitid <= 0) { @@ -631,42 +642,30 @@ export class AddonModAssignProvider { } const participant = this.getParticipantFromUserId(participants, submission.submitid); - if (hasParticipants && !participant) { + if (!participant) { // Avoid permission denied error. Participant not found on list. return; } - if (participant) { - if (!blind) { - submission.userfullname = participant.fullname; - submission.userprofileimageurl = participant.profileimageurl; - } + if (!blind) { + submission.userfullname = participant.fullname; + submission.userprofileimageurl = participant.profileimageurl; + } - submission.manyGroups = !!participant.groups && participant.groups.length > 1; - if (participant.groupname) { - submission.groupid = participant.groupid; - submission.groupname = participant.groupname; - } + submission.manyGroups = !!participant.groups && participant.groups.length > 1; + if (participant.groupname) { + submission.groupid = participant.groupid; + submission.groupname = participant.groupname; } let promise; - if (submission.userid > 0) { - if (blind) { - // Blind but not blinded! (Moodle < 3.1.1, 3.2). - delete submission.userid; + if (submission.userid > 0 && blind) { + // Blind but not blinded! (Moodle < 3.1.1, 3.2). + delete submission.userid; - promise = this.getAssignmentUserMappings(assignId, submission.submitid, ignoreCache, siteId).then((blindId) => { - submission.blindid = blindId; - }); - } else if (!participant) { - // No blind, no participant. - promise = this.userProvider.getProfile(submission.userid, courseId, true).then((user) => { - submission.userfullname = user.fullname; - submission.userprofileimageurl = user.profileimageurl; - }).catch(() => { - // Error getting profile, resolve promise without adding any extra data. - }); - } + promise = this.getAssignmentUserMappings(assignId, submission.submitid, ignoreCache, siteId).then((blindId) => { + submission.blindid = blindId; + }); } promise = promise || Promise.resolve(); @@ -899,13 +898,15 @@ export class AddonModAssignProvider { * * @param {number} assignId Assignment instance id. * @param {number} [userId] User id (empty for current user). + * @param {number} [groupId] Group Id (empty for all participants). * @param {boolean} [isBlind] Whether blind marking is enabled or not. * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the data is invalidated. */ - invalidateSubmissionStatusData(assignId: number, userId?: number, isBlind?: boolean, siteId?: string): Promise { + invalidateSubmissionStatusData(assignId: number, userId?: number, groupId?: number, isBlind?: boolean, siteId?: string): + Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.invalidateWsCacheForKey(this.getSubmissionStatusCacheKey(assignId, userId, isBlind)); + return site.invalidateWsCacheForKey(this.getSubmissionStatusCacheKey(assignId, userId, groupId, isBlind)); }); } @@ -1087,7 +1088,7 @@ export class AddonModAssignProvider { } // We need more data to decide that. - return this.getSubmissionStatus(assignId, submission.submitid, submission.blindid).then((response) => { + return this.getSubmissionStatus(assignId, submission.submitid, undefined, submission.blindid).then((response) => { if (!response.feedback || !response.feedback.gradeddate) { // Not graded. return true; diff --git a/src/addon/mod/assign/providers/helper.ts b/src/addon/mod/assign/providers/helper.ts index 78478936d..6b64d5c1b 100644 --- a/src/addon/mod/assign/providers/helper.ts +++ b/src/addon/mod/assign/providers/helper.ts @@ -149,21 +149,22 @@ export class AddonModAssignHelperProvider { /** * List the participants for a single assignment, with some summary info about their submissions. * - * @param {any} assign Assignment object + * @param {any} assign Assignment object. + * @param {number} [groupId] Group Id. * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise { + getParticipants(assign: any, groupId?: number, ignoreCache?: boolean, siteId?: string): Promise { + groupId = groupId || 0; siteId = siteId || this.sitesProvider.getCurrentSiteId(); - // Get the participants without specifying a group. - return this.assignProvider.listParticipants(assign.id, undefined, ignoreCache, siteId).then((participants) => { - if (participants && participants.length > 0) { + return this.assignProvider.listParticipants(assign.id, groupId, ignoreCache, siteId).then((participants) => { + if (groupId || participants && participants.length > 0) { return participants; } - // If no participants returned, get participants by groups. + // If no participants returned and all groups specified, get participants by groups. return this.groupsProvider.getActivityAllowedGroupsIfEnabled(assign.cmid, undefined, siteId).then((userGroups) => { const promises = [], participants = {}; diff --git a/src/addon/mod/assign/providers/prefetch-handler.ts b/src/addon/mod/assign/providers/prefetch-handler.ts index 16f91940f..1bc206445 100644 --- a/src/addon/mod/assign/providers/prefetch-handler.ts +++ b/src/addon/mod/assign/providers/prefetch-handler.ts @@ -156,7 +156,7 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan protected getSubmissionFiles(assign: any, submitId: number, blindMarking: boolean, siteId?: string) : Promise { - return this.assignProvider.getSubmissionStatusWithRetry(assign, submitId, blindMarking, true, false, siteId) + return this.assignProvider.getSubmissionStatusWithRetry(assign, submitId, undefined, blindMarking, true, false, siteId) .then((response) => { const promises = []; @@ -287,7 +287,6 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan * @return {Promise} Promise resolved when prefetched, rejected otherwise. */ protected prefetchSubmissions(assign: any, courseId: number, moduleId: number, userId: number, siteId: string): Promise { - // Get submissions. return this.assignProvider.getSubmissions(assign.id, true, siteId).then((data) => { const promises = [], @@ -295,55 +294,67 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan if (data.canviewsubmissions) { // Teacher. Do not send participants to getSubmissionsUserData to retrieve user profiles. - promises.push(this.assignProvider.getSubmissionsUserData(data.submissions, courseId, assign.id, blindMarking, - undefined, true, siteId).then((submissions) => { + promises.push(this.groupsProvider.getActivityGroupInfo(assign.cmid, false, undefined, siteId).then((groupInfo) => { + const groupProms = []; + if (!groupInfo.groups || groupInfo.groups.length == 0) { + groupInfo.groups = [{id: 0}]; + } - const subPromises = []; + groupInfo.groups.forEach((group) => { + groupProms.push(this.assignProvider.getSubmissionsUserData(data.submissions, courseId, assign.id, + blindMarking, undefined, true, siteId).then((submissions) => { - submissions.forEach((submission) => { - subPromises.push(this.assignProvider.getSubmissionStatusWithRetry(assign, submission.submitid, - !!submission.blindid, true, true, siteId).then((subm) => { - return this.prefetchSubmission(assign, courseId, moduleId, subm, submission.submitid, siteId); - }).catch((error) => { - if (error && error.errorcode == 'nopermission') { - // The user does not have persmission to view this submission, ignore it. - return Promise.resolve(); + const subPromises = []; + + submissions.forEach((submission) => { + subPromises.push(this.assignProvider.getSubmissionStatusWithRetry(assign, submission.submitid, + group.id, !!submission.blindid, true, true, siteId).then((subm) => { + return this.prefetchSubmission(assign, courseId, moduleId, subm, submission.submitid, siteId); + }).catch((error) => { + if (error && error.errorcode == 'nopermission') { + // The user does not have persmission to view this submission, ignore it. + return Promise.resolve(); + } + + return Promise.reject(error); + })); + }); + + if (!assign.markingworkflow) { + // Get assignment grades only if workflow is not enabled to check grading date. + subPromises.push(this.assignProvider.getAssignmentGrades(assign.id, true, siteId)); } - return Promise.reject(error); + // Prefetch the submission of the current user even if it does not exist, this will be create it. + if (!data.submissions || !data.submissions.find((subm) => subm.submitid == userId)) { + subPromises.push(this.assignProvider.getSubmissionStatusWithRetry(assign, userId, group.id, + false, true, true, siteId).then((subm) => { + return this.prefetchSubmission(assign, courseId, moduleId, subm, userId, siteId); + })); + } + + return Promise.all(subPromises); + })); + + // Get list participants. + groupProms.push(this.assignHelper.getParticipants(assign, group.id, true, siteId).then((participants) => { + participants.forEach((participant) => { + if (participant.profileimageurl) { + this.filepoolProvider.addToQueueByUrl(siteId, participant.profileimageurl); + } + }); + }).catch(() => { + // Fail silently (Moodle < 3.2). })); }); - if (!assign.markingworkflow) { - // Get assignment grades only if workflow is not enabled to check grading date. - subPromises.push(this.assignProvider.getAssignmentGrades(assign.id, true, siteId)); - } - - // Prefetch the submission of the current user even if it does not exist, this will be create it. - if (!data.submissions || !data.submissions.find((subm) => subm.submitid == userId)) { - subPromises.push(this.assignProvider.getSubmissionStatusWithRetry(assign, userId, false, true, true, siteId) - .then((subm) => { - return this.prefetchSubmission(assign, courseId, moduleId, subm, userId, siteId); - })); - } - - return Promise.all(subPromises); - })); - - // Get list participants. - promises.push(this.assignHelper.getParticipants(assign, true, siteId).then((participants) => { - participants.forEach((participant) => { - if (participant.profileimageurl) { - this.filepoolProvider.addToQueueByUrl(siteId, participant.profileimageurl); - } - }); - }).catch(() => { - // Fail silently (Moodle < 3.2). + return Promise.all(groupProms); })); } else { // Student. promises.push( - this.assignProvider.getSubmissionStatusWithRetry(assign, userId, false, true, true, siteId).then((subm) => { + this.assignProvider.getSubmissionStatusWithRetry(assign, userId, undefined, false, true, true, siteId) + .then((subm) => { return this.prefetchSubmission(assign, courseId, moduleId, subm, userId, siteId); }).catch((error) => { // Ignore if the user can't view their own submission. diff --git a/src/app/app.scss b/src/app/app.scss index 5fe61c0fa..eb0aad801 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -645,7 +645,7 @@ ion-app.app-root { @include padding(null, null, null, 52px); position: relative; - ion-icon { + > ion-icon { color: $color-base; position: absolute; @include position(0, null, null, 16px) From a53066d9353303e536e170e6592cb2db3e392bbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 21 Feb 2019 12:49:07 +0100 Subject: [PATCH 085/191] MOBILE-2822 utils: Make word count aware of block tags --- src/providers/utils/text.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/providers/utils/text.ts b/src/providers/utils/text.ts index 5e7c9f711..9bef61b2f 100644 --- a/src/providers/utils/text.ts +++ b/src/providers/utils/text.ts @@ -214,10 +214,20 @@ export class CoreTextUtilsProvider { if (!text || typeof text != 'string') { return 0; } + const blockTags = ['address', 'article', 'aside', 'blockquote', 'br', ' details', 'dialog', 'dd', 'div', 'dl', 'dt', + 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hgroup', 'hr', + 'li', 'main', 'nav', 'ol', 'p', 'pre', 'section', 'table', 'ul']; // Clean HTML scripts and tags. text = text.replace(/]*>([\S\s]*?)<\/script>/gmi, ''); - text = text.replace(/<\/?(?!\!)[^>]*>/gi, ''); + // Replace block tags by space to get word count aware of line break and remove inline tags. + text = text.replace(/<(\/[ ]*)?([a-zA-Z0-9]+)[^>]*>/gi, (str, p1, match) => { + if (blockTags.indexOf(match) >= 0) { + return ' '; + } + + return ''; + }); // Decode HTML entities. text = this.decodeHTMLEntities(text); // Replace underscores (which are classed as word characters) with spaces. From 6c9c1e42893028943b5dccf4b29063ab60fa9830 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 21 Feb 2019 16:15:57 +0100 Subject: [PATCH 086/191] MOBILE-2876 workshop: Fix check more dimensions than given --- .../accumulative/providers/handler.ts | 28 ++++++++++--------- .../assessment/comments/providers/handler.ts | 18 ++++++------ .../assessment/numerrors/providers/handler.ts | 28 ++++++++++--------- .../assessment/rubric/providers/handler.ts | 20 +++++++------ 4 files changed, 51 insertions(+), 43 deletions(-) diff --git a/src/addon/mod/workshop/assessment/accumulative/providers/handler.ts b/src/addon/mod/workshop/assessment/accumulative/providers/handler.ts index 3a845746b..e361727b0 100644 --- a/src/addon/mod/workshop/assessment/accumulative/providers/handler.ts +++ b/src/addon/mod/workshop/assessment/accumulative/providers/handler.ts @@ -120,21 +120,23 @@ export class AddonModWorkshopAssessmentStrategyAccumulativeHandler implements Ad let hasErrors = false; form.fields.forEach((field, idx) => { - const grade = parseInt(currentValues[idx].grade, 10); - if (!isNaN(grade) && grade >= 0) { - data['grade__idx_' + idx] = grade; - } else { - errors['grade_' + idx] = this.translate.instant('addon.mod_workshop_assessment_accumulative.mustchoosegrade'); - hasErrors = true; - } + if (idx < form.dimenssionscount) { + const grade = parseInt(currentValues[idx].grade, 10); + if (!isNaN(grade) && grade >= 0) { + data['grade__idx_' + idx] = grade; + } else { + errors['grade_' + idx] = this.translate.instant('addon.mod_workshop_assessment_accumulative.mustchoosegrade'); + hasErrors = true; + } - if (currentValues[idx].peercomment) { - data['peercomment__idx_' + idx] = currentValues[idx].peercomment; - } + if (currentValues[idx].peercomment) { + data['peercomment__idx_' + idx] = currentValues[idx].peercomment; + } - data['gradeid__idx_' + idx] = parseInt(form.current[idx].gradeid, 10) || 0; - data['dimensionid__idx_' + idx] = parseInt(field.dimensionid, 10); - data['weight__idx_' + idx] = parseInt(field.weight, 10) || 0; + data['gradeid__idx_' + idx] = parseInt(form.current[idx].gradeid, 10) || 0; + data['dimensionid__idx_' + idx] = parseInt(field.dimensionid, 10); + data['weight__idx_' + idx] = parseInt(field.weight, 10) || 0; + } }); if (hasErrors) { diff --git a/src/addon/mod/workshop/assessment/comments/providers/handler.ts b/src/addon/mod/workshop/assessment/comments/providers/handler.ts index 339c16a88..132e0f38b 100644 --- a/src/addon/mod/workshop/assessment/comments/providers/handler.ts +++ b/src/addon/mod/workshop/assessment/comments/providers/handler.ts @@ -102,15 +102,17 @@ export class AddonModWorkshopAssessmentStrategyCommentsHandler implements AddonW let hasErrors = false; form.fields.forEach((field, idx) => { - if (currentValues[idx].peercomment) { - data['peercomment__idx_' + idx] = currentValues[idx].peercomment; - } else { - errors['peercomment_' + idx] = this.translate.instant('core.err_required'); - hasErrors = true; - } + if (idx < form.dimenssionscount) { + if (currentValues[idx].peercomment) { + data['peercomment__idx_' + idx] = currentValues[idx].peercomment; + } else { + errors['peercomment_' + idx] = this.translate.instant('core.err_required'); + hasErrors = true; + } - data['gradeid__idx_' + idx] = parseInt(form.current[idx].gradeid, 10) || 0; - data['dimensionid__idx_' + idx] = parseInt(field.dimensionid, 10); + data['gradeid__idx_' + idx] = parseInt(form.current[idx].gradeid, 10) || 0; + data['dimensionid__idx_' + idx] = parseInt(field.dimensionid, 10); + } }); if (hasErrors) { diff --git a/src/addon/mod/workshop/assessment/numerrors/providers/handler.ts b/src/addon/mod/workshop/assessment/numerrors/providers/handler.ts index 4eaaf2012..5e77dc7e0 100644 --- a/src/addon/mod/workshop/assessment/numerrors/providers/handler.ts +++ b/src/addon/mod/workshop/assessment/numerrors/providers/handler.ts @@ -106,21 +106,23 @@ export class AddonModWorkshopAssessmentStrategyNumErrorsHandler implements Addon let hasErrors = false; form.fields.forEach((field, idx) => { - const grade = parseInt(currentValues[idx].grade); - if (!isNaN(grade) && grade >= 0) { - data['grade__idx_' + idx] = grade; - } else { - errors['grade_' + idx] = this.translate.instant('core.required'); - hasErrors = true; - } + if (idx < form.dimenssionscount) { + const grade = parseInt(currentValues[idx].grade); + if (!isNaN(grade) && (grade == 1 || grade == -1)) { + data['grade__idx_' + idx] = grade; + } else { + errors['grade_' + idx] = this.translate.instant('core.required'); + hasErrors = true; + } - if (currentValues[idx].peercomment) { - data['peercomment__idx_' + idx] = currentValues[idx].peercomment; - } + if (currentValues[idx].peercomment) { + data['peercomment__idx_' + idx] = currentValues[idx].peercomment; + } - data['gradeid__idx_' + idx] = parseInt(form.current[idx].gradeid, 10) || 0; - data['dimensionid__idx_' + idx] = parseInt(field.dimensionid, 10); - data['weight__idx_' + idx] = parseInt(field.weight, 10) || 0; + data['gradeid__idx_' + idx] = parseInt(form.current[idx].gradeid, 10) || 0; + data['dimensionid__idx_' + idx] = parseInt(field.dimensionid, 10); + data['weight__idx_' + idx] = parseInt(field.weight, 10) || 0; + } }); if (hasErrors) { diff --git a/src/addon/mod/workshop/assessment/rubric/providers/handler.ts b/src/addon/mod/workshop/assessment/rubric/providers/handler.ts index bc5dbdb1f..d9dc5a38b 100644 --- a/src/addon/mod/workshop/assessment/rubric/providers/handler.ts +++ b/src/addon/mod/workshop/assessment/rubric/providers/handler.ts @@ -102,16 +102,18 @@ export class AddonModWorkshopAssessmentStrategyRubricHandler implements AddonWor let hasErrors = false; form.fields.forEach((field, idx) => { - const id = parseInt(currentValues[idx].chosenlevelid, 10); - if (!isNaN(id) && id >= 0) { - data['chosenlevelid__idx_' + idx] = id; - } else { - errors['chosenlevelid_' + idx] = this.translate.instant('addon.mod_workshop_assessment_rubric.mustchooseone'); - hasErrors = true; - } + if (idx < form.dimenssionscount) { + const id = parseInt(currentValues[idx].chosenlevelid, 10); + if (!isNaN(id) && id >= 0) { + data['chosenlevelid__idx_' + idx] = id; + } else { + errors['chosenlevelid_' + idx] = this.translate.instant('addon.mod_workshop_assessment_rubric.mustchooseone'); + hasErrors = true; + } - data['gradeid__idx_' + idx] = parseInt(form.current[idx].gradeid, 10) || 0; - data['dimensionid__idx_' + idx] = parseInt(field.dimensionid, 10); + data['gradeid__idx_' + idx] = parseInt(form.current[idx].gradeid, 10) || 0; + data['dimensionid__idx_' + idx] = parseInt(field.dimensionid, 10); + } }); if (hasErrors) { From 5cbce97ff9c2214e6acb47f0bccb0c9ac852a8db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 22 Feb 2019 10:07:20 +0100 Subject: [PATCH 087/191] MOBILE-1979 sync: Fix activity syncing --- src/addon/mod/choice/providers/sync.ts | 4 +++- src/addon/mod/data/providers/sync.ts | 4 +++- src/addon/mod/feedback/providers/sync.ts | 4 +++- src/addon/mod/forum/providers/sync.ts | 4 +++- src/addon/mod/glossary/providers/sync.ts | 4 +++- src/addon/mod/lesson/providers/lesson-sync.ts | 4 +++- src/addon/mod/quiz/providers/quiz-sync.ts | 4 +++- src/addon/mod/scorm/providers/scorm-sync.ts | 4 +++- src/addon/mod/survey/providers/sync.ts | 4 +++- 9 files changed, 27 insertions(+), 9 deletions(-) diff --git a/src/addon/mod/choice/providers/sync.ts b/src/addon/mod/choice/providers/sync.ts index b260f1ba7..be612049a 100644 --- a/src/addon/mod/choice/providers/sync.ts +++ b/src/addon/mod/choice/providers/sync.ts @@ -140,7 +140,9 @@ export class AddonModChoiceSyncProvider extends CoreSyncBaseProvider { }; // Sync offline logs. - const syncPromise = this.logHelper.syncIfNeeded(AddonModChoiceProvider.COMPONENT, choiceId, siteId).finally(() => { + const syncPromise = this.logHelper.syncIfNeeded(AddonModChoiceProvider.COMPONENT, choiceId, siteId).catch(() => { + // Ignore errors. + }).then(() => { return this.choiceOffline.getResponse(choiceId, siteId, userId).catch(() => { // No offline data found, return empty object. return {}; diff --git a/src/addon/mod/data/providers/sync.ts b/src/addon/mod/data/providers/sync.ts index 22485fa93..7cf764845 100644 --- a/src/addon/mod/data/providers/sync.ts +++ b/src/addon/mod/data/providers/sync.ts @@ -151,7 +151,9 @@ export class AddonModDataSyncProvider extends CoreSyncBaseProvider { }; // Sync offline logs. - const syncPromise = this.logHelper.syncIfNeeded(AddonModDataProvider.COMPONENT, dataId, siteId).finally(() => { + const syncPromise = this.logHelper.syncIfNeeded(AddonModDataProvider.COMPONENT, dataId, siteId).catch(() => { + // Ignore errors. + }).then(() => { // Get answers to be sent. return this.dataOffline.getDatabaseEntries(dataId, siteId).catch(() => { // No offline data found, return empty object. diff --git a/src/addon/mod/feedback/providers/sync.ts b/src/addon/mod/feedback/providers/sync.ts index e9154d724..0a9364b48 100644 --- a/src/addon/mod/feedback/providers/sync.ts +++ b/src/addon/mod/feedback/providers/sync.ts @@ -147,7 +147,9 @@ export class AddonModFeedbackSyncProvider extends CoreSyncBaseProvider { this.logger.debug(`Try to sync feedback '${feedbackId}' in site ${siteId}'`); // Sync offline logs. - const syncPromise = this.logHelper.syncIfNeeded(AddonModFeedbackProvider.COMPONENT, feedbackId, siteId).finally(() => { + const syncPromise = this.logHelper.syncIfNeeded(AddonModFeedbackProvider.COMPONENT, feedbackId, siteId).catch(() => { + // Ignore errors. + }).then(() => { // Get offline responses to be sent. return this.feedbackOffline.getFeedbackResponses(feedbackId, siteId).catch(() => { // No offline data found, return empty array. diff --git a/src/addon/mod/forum/providers/sync.ts b/src/addon/mod/forum/providers/sync.ts index ddb33ab61..b6fc0559a 100644 --- a/src/addon/mod/forum/providers/sync.ts +++ b/src/addon/mod/forum/providers/sync.ts @@ -192,7 +192,9 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider { }; // Sync offline logs. - const syncPromise = this.logHelper.syncIfNeeded(AddonModForumProvider.COMPONENT, forumId, siteId).finally(() => { + const syncPromise = this.logHelper.syncIfNeeded(AddonModForumProvider.COMPONENT, forumId, siteId).catch(() => { + // Ignore errors. + }).then(() => { // Get offline responses to be sent. return this.forumOffline.getNewDiscussions(forumId, siteId, userId).catch(() => { // No offline data found, return empty object. diff --git a/src/addon/mod/glossary/providers/sync.ts b/src/addon/mod/glossary/providers/sync.ts index af76e67d8..571745698 100644 --- a/src/addon/mod/glossary/providers/sync.ts +++ b/src/addon/mod/glossary/providers/sync.ts @@ -163,7 +163,9 @@ export class AddonModGlossarySyncProvider extends CoreSyncBaseProvider { }; // Sync offline logs. - const syncPromise = this.logHelper.syncIfNeeded(AddonModGlossaryProvider.COMPONENT, glossaryId, siteId).finally(() => { + const syncPromise = this.logHelper.syncIfNeeded(AddonModGlossaryProvider.COMPONENT, glossaryId, siteId).catch(() => { + // Ignore errors. + }).then(() => { // Get offline responses to be sent. return this.glossaryOffline.getGlossaryNewEntries(glossaryId, siteId, userId).catch(() => { // No offline data found, return empty object. diff --git a/src/addon/mod/lesson/providers/lesson-sync.ts b/src/addon/mod/lesson/providers/lesson-sync.ts index 7e34a3e76..68b741f94 100644 --- a/src/addon/mod/lesson/providers/lesson-sync.ts +++ b/src/addon/mod/lesson/providers/lesson-sync.ts @@ -272,7 +272,9 @@ export class AddonModLessonSyncProvider extends CoreSyncBaseProvider { this.logger.debug('Try to sync lesson ' + lessonId + ' in site ' + siteId); // Sync offline logs. - syncPromise = this.logHelper.syncIfNeeded(AddonModLessonProvider.COMPONENT, lessonId, siteId).finally(() => { + syncPromise = this.logHelper.syncIfNeeded(AddonModLessonProvider.COMPONENT, lessonId, siteId).catch(() => { + // Ignore errors. + }).then(() => { // Try to synchronize the attempts first. return this.lessonOfflineProvider.getLessonAttempts(lessonId, siteId); }).then((attempts) => { diff --git a/src/addon/mod/quiz/providers/quiz-sync.ts b/src/addon/mod/quiz/providers/quiz-sync.ts index a7ebbdbec..f70b29109 100644 --- a/src/addon/mod/quiz/providers/quiz-sync.ts +++ b/src/addon/mod/quiz/providers/quiz-sync.ts @@ -261,7 +261,9 @@ export class AddonModQuizSyncProvider extends CoreSyncBaseProvider { this.logger.debug('Try to sync quiz ' + quiz.id + ' in site ' + siteId); // Sync offline logs. - syncPromise = this.logHelper.syncIfNeeded(AddonModQuizProvider.COMPONENT, quiz.id, siteId).finally(() => { + syncPromise = this.logHelper.syncIfNeeded(AddonModQuizProvider.COMPONENT, quiz.id, siteId).catch(() => { + // Ignore errors. + }).then(() => { // Get all the offline attempts for the quiz. return this.quizOfflineProvider.getQuizAttempts(quiz.id, siteId); }).then((attempts) => { diff --git a/src/addon/mod/scorm/providers/scorm-sync.ts b/src/addon/mod/scorm/providers/scorm-sync.ts index 1bf3ea4cb..861dce5dc 100644 --- a/src/addon/mod/scorm/providers/scorm-sync.ts +++ b/src/addon/mod/scorm/providers/scorm-sync.ts @@ -649,7 +649,9 @@ export class AddonModScormSyncProvider extends CoreSyncBaseProvider { this.logger.debug('Try to sync SCORM ' + scorm.id + ' in site ' + siteId); // Sync offline logs. - syncPromise = this.logHelper.syncIfNeeded(AddonModScormProvider.COMPONENT, scorm.id, siteId).finally(() => { + syncPromise = this.logHelper.syncIfNeeded(AddonModScormProvider.COMPONENT, scorm.id, siteId).catch(() => { + // Ignore errors. + }).then(() => { // Get attempts data. We ignore cache for online attempts, so this call will fail if offline or server down. return this.scormProvider.getAttemptCount(scorm.id, false, true, siteId); }).then((attemptsData) => { diff --git a/src/addon/mod/survey/providers/sync.ts b/src/addon/mod/survey/providers/sync.ts index 580c24da4..2e301f5fa 100644 --- a/src/addon/mod/survey/providers/sync.ts +++ b/src/addon/mod/survey/providers/sync.ts @@ -143,7 +143,9 @@ export class AddonModSurveySyncProvider extends CoreSyncBaseProvider { }; // Sync offline logs. - const syncPromise = this.logHelper.syncIfNeeded(AddonModSurveyProvider.COMPONENT, surveyId, siteId).finally(() => { + const syncPromise = this.logHelper.syncIfNeeded(AddonModSurveyProvider.COMPONENT, surveyId, siteId).catch(() => { + // Ignore errors. + }).then(() => { // Get answers to be sent. return this.surveyOffline.getSurveyData(surveyId, siteId, userId).catch(() => { // No offline data found, return empty object. From 3b6bb84f95ee6dff1b6b4b76d7132b1ada661b22 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 22 Feb 2019 10:57:38 +0100 Subject: [PATCH 088/191] MOBILE-2527 core: Fix external-content in format-text --- src/directives/format-text.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/directives/format-text.ts b/src/directives/format-text.ts index 08b4876e8..d840c8a84 100644 --- a/src/directives/format-text.ts +++ b/src/directives/format-text.ts @@ -98,6 +98,10 @@ export class CoreFormatTextDirective implements OnChanges { extContent.component = this.component; extContent.componentId = this.componentId; extContent.siteId = this.siteId; + extContent.src = element.getAttribute('src'); + extContent.href = element.getAttribute('href'); + extContent.targetSrc = element.getAttribute('target-src'); + extContent.poster = element.getAttribute('poster'); extContent.ngAfterViewInit(); } From 5c176b93b8dcac2c1733ed5c284537099e1e4ca2 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 22 Feb 2019 13:06:08 +0100 Subject: [PATCH 089/191] MOBILE-2527 label: Prefetch label files when downloading course --- src/addon/mod/label/label.module.ts | 11 +- src/addon/mod/label/providers/label.ts | 171 ++++++++++++++++++ .../mod/label/providers/prefetch-handler.ts | 92 ++++++++++ .../course/classes/module-prefetch-handler.ts | 10 +- .../classes/resource-prefetch-handler.ts | 2 +- src/core/course/providers/helper.ts | 2 +- .../providers/module-prefetch-delegate.ts | 21 ++- 7 files changed, 303 insertions(+), 6 deletions(-) create mode 100644 src/addon/mod/label/providers/label.ts create mode 100644 src/addon/mod/label/providers/prefetch-handler.ts diff --git a/src/addon/mod/label/label.module.ts b/src/addon/mod/label/label.module.ts index 15be0e3cc..951242b36 100644 --- a/src/addon/mod/label/label.module.ts +++ b/src/addon/mod/label/label.module.ts @@ -13,10 +13,13 @@ // limitations under the License. import { NgModule } from '@angular/core'; +import { AddonModLabelProvider } from './providers/label'; import { AddonModLabelModuleHandler } from './providers/module-handler'; import { AddonModLabelLinkHandler } from './providers/link-handler'; +import { AddonModLabelPrefetchHandler } from './providers/prefetch-handler'; import { CoreCourseModuleDelegate } from '@core/course/providers/module-delegate'; import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate'; +import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate'; @NgModule({ declarations: [ @@ -24,14 +27,18 @@ import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate' imports: [ ], providers: [ + AddonModLabelProvider, AddonModLabelModuleHandler, - AddonModLabelLinkHandler + AddonModLabelLinkHandler, + AddonModLabelPrefetchHandler ] }) export class AddonModLabelModule { constructor(moduleDelegate: CoreCourseModuleDelegate, moduleHandler: AddonModLabelModuleHandler, - contentLinksDelegate: CoreContentLinksDelegate, linkHandler: AddonModLabelLinkHandler) { + contentLinksDelegate: CoreContentLinksDelegate, linkHandler: AddonModLabelLinkHandler, + prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModLabelPrefetchHandler) { moduleDelegate.registerHandler(moduleHandler); contentLinksDelegate.registerHandler(linkHandler); + prefetchDelegate.registerHandler(prefetchHandler); } } diff --git a/src/addon/mod/label/providers/label.ts b/src/addon/mod/label/providers/label.ts new file mode 100644 index 000000000..b40b11ac9 --- /dev/null +++ b/src/addon/mod/label/providers/label.ts @@ -0,0 +1,171 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreUtilsProvider } from '@providers/utils/utils'; +import { CoreFilepoolProvider } from '@providers/filepool'; +import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; + +/** + * Service that provides some features for labels. + */ +@Injectable() +export class AddonModLabelProvider { + static COMPONENT = 'mmaModLabel'; + + protected ROOT_CACHE_KEY = 'mmaModLabel:'; + + constructor(private sitesProvider: CoreSitesProvider, private filepoolProvider: CoreFilepoolProvider, + private utils: CoreUtilsProvider) {} + + /** + * Get cache key for label data WS calls. + * + * @param {number} courseId Course ID. + * @return {string} Cache key. + */ + protected getLabelDataCacheKey(courseId: number): string { + return this.ROOT_CACHE_KEY + 'label:' + courseId; + } + + /** + * Get a label with key=value. If more than one is found, only the first will be returned. + * + * @param {number} courseId Course ID. + * @param {string} key Name of the property to check. + * @param {any} value Value to search. + * @param {boolean} [forceCache] True to always get the value from cache, false otherwise. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). + * @param {string} [siteId] Site ID. If not provided, current site. + * @return {Promise} Promise resolved when the label is retrieved. + */ + protected getLabelByField(courseId: number, key: string, value: any, forceCache?: boolean, ignoreCache?: boolean, + siteId?: string): Promise { + + return this.sitesProvider.getSite(siteId).then((site) => { + const params = { + courseids: [courseId] + }, + preSets: CoreSiteWSPreSets = { + cacheKey: this.getLabelDataCacheKey(courseId) + }; + + if (forceCache) { + preSets.omitExpires = true; + } else if (ignoreCache) { + preSets.getFromCache = false; + preSets.emergencyCache = false; + } + + return site.read('mod_label_get_labels_by_courses', params, preSets).then((response) => { + if (response && response.labels) { + const currentLabel = response.labels.find((label) => label[key] == value); + if (currentLabel) { + return currentLabel; + } + } + + return Promise.reject(null); + }); + }); + } + + /** + * Get a label by course module ID. + * + * @param {number} courseId Course ID. + * @param {number} cmId Course module ID. + * @param {boolean} [forceCache] True to always get the value from cache, false otherwise. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the label is retrieved. + */ + getLabel(courseId: number, cmId: number, forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise { + return this.getLabelByField(courseId, 'coursemodule', cmId, forceCache, ignoreCache, siteId); + } + + /** + * Get a label by ID. + * + * @param {number} courseId Course ID. + * @param {number} labelId Label ID. + * @param {boolean} [forceCache] True to always get the value from cache, false otherwise. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the label is retrieved. + */ + getLabelById(courseId: number, labelId: number, forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise { + return this.getLabelByField(courseId, 'id', labelId, forceCache, ignoreCache, siteId); + } + + /** + * Invalidate label data. + * + * @param {number} courseId Course ID. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the data is invalidated. + */ + invalidateLabelData(courseId: number, siteId?: string): Promise { + return this.sitesProvider.getSite(null).then((site) => { + return site.invalidateWsCacheForKey(this.getLabelDataCacheKey(courseId)); + }); + } + + /** + * Invalidate the prefetched content. + * + * @param {number} moduleId The module ID. + * @param {number} courseId Course ID. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when data is invalidated. + */ + invalidateContent(moduleId: number, courseId: number, siteId?: string): Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + const promises = []; + + promises.push(this.invalidateLabelData(courseId, siteId)); + + promises.push(this.filepoolProvider.invalidateFilesByComponent(siteId, AddonModLabelProvider.COMPONENT, moduleId, true)); + + return this.utils.allPromises(promises); + } + + /** + * Check if the site has the WS to get label data. + * + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with boolean: whether it's available. + * @since 3.3 + */ + isGetLabelAvailable(siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + return site.wsAvailable('mod_label_get_labels_by_courses'); + }); + } + + /** + * Check if the site has the WS to get label data. + * + * @param {CoreSite} [site] Site. If not defined, current site. + * @return {boolean} Whether it's available. + * @since 3.3 + */ + isGetLabelAvailableForSite(site?: CoreSite): boolean { + site = site || this.sitesProvider.getCurrentSite(); + + return site.wsAvailable('mod_label_get_labels_by_courses'); + } +} diff --git a/src/addon/mod/label/providers/prefetch-handler.ts b/src/addon/mod/label/providers/prefetch-handler.ts new file mode 100644 index 000000000..3f86dec31 --- /dev/null +++ b/src/addon/mod/label/providers/prefetch-handler.ts @@ -0,0 +1,92 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { CoreAppProvider } from '@providers/app'; +import { CoreFilepoolProvider } from '@providers/filepool'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { CoreUtilsProvider } from '@providers/utils/utils'; +import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseResourcePrefetchHandlerBase } from '@core/course/classes/resource-prefetch-handler'; +import { AddonModLabelProvider } from './label'; + +/** + * Handler to prefetch labels. + */ +@Injectable() +export class AddonModLabelPrefetchHandler extends CoreCourseResourcePrefetchHandlerBase { + name = 'AddonModLabel'; + modName = 'label'; + component = AddonModLabelProvider.COMPONENT; + updatesNames = /^.*files$/; + skipListStatus = true; + + constructor(translate: TranslateService, appProvider: CoreAppProvider, utils: CoreUtilsProvider, + courseProvider: CoreCourseProvider, filepoolProvider: CoreFilepoolProvider, sitesProvider: CoreSitesProvider, + domUtils: CoreDomUtilsProvider, protected labelProvider: AddonModLabelProvider) { + + super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils); + } + + /** + * Returns module intro files. + * + * @param {any} module The module object returned by WS. + * @param {number} courseId Course ID. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). + * @return {Promise} Promise resolved with list of intro files. + */ + getIntroFiles(module: any, courseId: number, ignoreCache?: boolean): Promise { + let promise; + + if (this.labelProvider.isGetLabelAvailableForSite()) { + promise = this.labelProvider.getLabel(courseId, module.id, false, ignoreCache); + } else { + promise = Promise.resolve(); + } + + return promise.then((label) => { + return this.getIntroFilesFromInstance(module, label); + }); + } + + /** + * Invalidate the prefetched content. + * + * @param {number} moduleId The module ID. + * @param {number} courseId Course ID the module belongs to. + * @return {Promise} Promise resolved when the data is invalidated. + */ + invalidateContent(moduleId: number, courseId: number): Promise { + return this.labelProvider.invalidateContent(moduleId, courseId); + } + + /** + * Invalidate WS calls needed to determine module status. + * + * @param {any} module Module. + * @param {number} courseId Course ID the module belongs to. + * @return {Promise} Promise resolved when invalidated. + */ + invalidateModule(module: any, courseId: number): Promise { + const promises = []; + + promises.push(this.labelProvider.invalidateLabelData(courseId)); + promises.push(this.courseProvider.invalidateModule(module.id)); + + return this.utils.allPromises(promises); + } +} diff --git a/src/core/course/classes/module-prefetch-handler.ts b/src/core/course/classes/module-prefetch-handler.ts index 49ae7e6c4..e079055ed 100644 --- a/src/core/course/classes/module-prefetch-handler.ts +++ b/src/core/course/classes/module-prefetch-handler.ts @@ -52,6 +52,13 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref */ updatesNames = /^.*files$/; + /** + * If true, this module will be ignored when determining the status of a list of modules. The module will + * still be downloaded when downloading the section/course, it only affects whether the button should be displayed. + * @type {boolean} + */ + skipListStatus = false; + /** * List of download promises to prevent downloading the module twice at the same time. * @type {{[s: string]: {[s: string]: Promise}}} @@ -167,9 +174,10 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref * * @param {any} module The module object returned by WS. * @param {number} courseId Course ID. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @return {Promise} Promise resolved with list of intro files. */ - getIntroFiles(module: any, courseId: number): Promise { + getIntroFiles(module: any, courseId: number, ignoreCache?: boolean): Promise { return Promise.resolve(this.getIntroFilesFromInstance(module)); } diff --git a/src/core/course/classes/resource-prefetch-handler.ts b/src/core/course/classes/resource-prefetch-handler.ts index f2e51246b..f7e7386d3 100644 --- a/src/core/course/classes/resource-prefetch-handler.ts +++ b/src/core/course/classes/resource-prefetch-handler.ts @@ -67,7 +67,7 @@ export class CoreCourseResourcePrefetchHandlerBase extends CoreCourseModulePrefe return this.loadContents(module, courseId, true); }).then(() => { // Get the intro files. - return this.getIntroFiles(module, courseId); + return this.getIntroFiles(module, courseId, true); }).then((introFiles) => { const downloadFn = prefetch ? this.filepoolProvider.prefetchPackage.bind(this.filepoolProvider) : this.filepoolProvider.downloadPackage.bind(this.filepoolProvider), diff --git a/src/core/course/providers/helper.ts b/src/core/course/providers/helper.ts index 6f05e157f..9cf20c3c1 100644 --- a/src/core/course/providers/helper.ts +++ b/src/core/course/providers/helper.ts @@ -192,7 +192,7 @@ export class CoreCourseHelperProvider { } // Get the status of this section. - return this.prefetchDelegate.getModulesStatus(section.modules, courseId, section.id, refresh).then((result) => { + return this.prefetchDelegate.getModulesStatus(section.modules, courseId, section.id, refresh, true).then((result) => { // Check if it's being downloaded. const downloadId = this.getSectionDownloadId(section); if (this.prefetchDelegate.isBeingDownloaded(downloadId)) { diff --git a/src/core/course/providers/module-prefetch-delegate.ts b/src/core/course/providers/module-prefetch-delegate.ts index c381ac64e..5d43db704 100644 --- a/src/core/course/providers/module-prefetch-delegate.ts +++ b/src/core/course/providers/module-prefetch-delegate.ts @@ -56,6 +56,12 @@ export type CoreCourseModulesProgressFunction = (data: CoreCourseModulesProgress * Interface that all course prefetch handlers must implement. */ export interface CoreCourseModulePrefetchHandler extends CoreDelegateHandler { + /** + * Name of the handler. + * @type {string} + */ + name: string; + /** * Name of the module. It should match the "modname" of the module returned in core_course_get_contents. * @type {string} @@ -75,6 +81,13 @@ export interface CoreCourseModulePrefetchHandler extends CoreDelegateHandler { */ updatesNames?: RegExp; + /** + * If true, this module will be treated as not downloadable when determining the status of a list of modules. The module will + * still be downloaded when downloading the section/course, it only affects whether the button should be displayed. + * @type {boolean} + */ + skipListStatus: boolean; + /** * Get the download size of a module. * @@ -769,6 +782,7 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { * @param {number} courseId Course ID the modules belong to. * @param {number} [sectionId] ID of the section the modules belong to. * @param {boolean} [refresh] True if it should always check the DB (slower). + * @param {boolean} [onlyToDisplay] True if the status will only be used to determine which button should be displayed. * @return {Promise} Promise resolved with an object with the following properties: * - status (string) Status of the module. * - total (number) Number of modules. @@ -777,7 +791,7 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { * - CoreConstants.DOWNLOADING (any[]) Modules with state DOWNLOADING. * - CoreConstants.OUTDATED (any[]) Modules with state OUTDATED. */ - getModulesStatus(modules: any[], courseId: number, sectionId?: number, refresh?: boolean): any { + getModulesStatus(modules: any[], courseId: number, sectionId?: number, refresh?: boolean, onlyToDisplay?: boolean): any { const promises = [], result: any = { total: 0 @@ -800,6 +814,11 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { // Check if the module has a prefetch handler. const handler = this.getPrefetchHandlerFor(module); if (handler) { + if (onlyToDisplay && handler.skipListStatus) { + // Skip this module. + return; + } + const packageId = this.filepoolProvider.getPackageId(handler.component, module.id); promises.push(this.getModuleStatus(module, courseId, updates, refresh).then((modStatus) => { From 890a8105e9fee8c9040890318705a55dfac43f15 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 22 Feb 2019 15:09:01 +0100 Subject: [PATCH 090/191] MOBILE-2527 course: Prefetch section summary files --- src/core/course/providers/course.ts | 2 ++ src/core/course/providers/helper.ts | 32 ++++++++++++++++++++++++++--- 2 files changed, 31 insertions(+), 3 deletions(-) diff --git a/src/core/course/providers/course.ts b/src/core/course/providers/course.ts index f35b76067..e16318b09 100644 --- a/src/core/course/providers/course.ts +++ b/src/core/course/providers/course.ts @@ -43,6 +43,8 @@ export class CoreCourseProvider { static COMPLETION_COMPLETE_PASS = 2; static COMPLETION_COMPLETE_FAIL = 3; + static COMPONENT = 'CoreCourse'; + protected ROOT_CACHE_KEY = 'mmCourse:'; // Variables for database. diff --git a/src/core/course/providers/helper.ts b/src/core/course/providers/helper.ts index 9cf20c3c1..3a6b746ac 100644 --- a/src/core/course/providers/helper.ts +++ b/src/core/course/providers/helper.ts @@ -401,11 +401,15 @@ export class CoreCourseHelperProvider { * @return {Promise} Promise resolved if the user confirms or there's no need to confirm. */ confirmDownloadSizeSection(courseId: number, section?: any, sections?: any[], alwaysConfirm?: boolean): Promise { - let sizePromise; + let sizePromise, + haveEmbeddedFiles = false; // Calculate the size of the download. if (section && section.id != CoreCourseProvider.ALL_SECTIONS_ID) { sizePromise = this.prefetchDelegate.getDownloadSize(section.modules, courseId); + + // Check if the section has embedded files in the description. + haveEmbeddedFiles = this.domUtils.extractDownloadableFilesFromHtml(section.summary).length > 0; } else { const promises = [], results = { @@ -419,6 +423,11 @@ export class CoreCourseHelperProvider { results.total = results.total && sectionSize.total; results.size += sectionSize.size; })); + + // Check if the section has embedded files in the description. + if (!haveEmbeddedFiles && this.domUtils.extractDownloadableFilesFromHtml(s.summary).length > 0) { + haveEmbeddedFiles = true; + } } }); @@ -428,6 +437,10 @@ export class CoreCourseHelperProvider { } return sizePromise.then((size) => { + if (haveEmbeddedFiles) { + size.total = false; + } + // Show confirm modal if needed. return this.domUtils.confirmDownloadSize(size, undefined, undefined, undefined, undefined, alwaysConfirm); }); @@ -1272,10 +1285,12 @@ export class CoreCourseHelperProvider { return Promise.resolve(); } + const promises = []; + section.isDownloading = true; // Validate the section needs to be downloaded and calculate amount of modules that need to be downloaded. - return this.prefetchDelegate.getModulesStatus(section.modules, courseId, section.id).then((result) => { + promises.push(this.prefetchDelegate.getModulesStatus(section.modules, courseId, section.id).then((result) => { if (result.status == CoreConstants.DOWNLOADED || result.status == CoreConstants.NOT_DOWNLOADABLE) { // Section is downloaded or not downloadable, nothing to do. return; @@ -1286,7 +1301,18 @@ export class CoreCourseHelperProvider { section.isDownloading = false; return Promise.reject(error); - }); + })); + + // Download the files in the section description. + const introFiles = this.domUtils.extractDownloadableFilesFromHtmlAsFakeFileObjects(section.summary), + siteId = this.sitesProvider.getCurrentSiteId(); + + promises.push(this.filepoolProvider.addFilesToQueue(siteId, introFiles, CoreCourseProvider.COMPONENT, courseId) + .catch(() => { + // Ignore errors. + })); + + return Promise.all(promises); } /** From e987aeba9896d803af1d4c3d3c9c9d00c0d39751 Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Thu, 7 Feb 2019 17:18:19 +0100 Subject: [PATCH 091/191] MOBILE-1928 chat: Passed sessions --- scripts/langindex.json | 6 + src/addon/mod/chat/chat.module.ts | 5 + .../index/addon-mod-chat-index.html | 4 +- src/addon/mod/chat/components/index/index.ts | 12 + src/addon/mod/chat/lang/en.json | 7 +- .../session-messages/session-messages.html | 40 +++ .../session-messages.module.ts | 37 +++ .../session-messages/session-messages.scss | 9 + .../session-messages/session-messages.ts | 95 +++++++ .../mod/chat/pages/sessions/sessions.html | 45 ++++ .../chat/pages/sessions/sessions.module.ts | 37 +++ .../mod/chat/pages/sessions/sessions.scss | 8 + src/addon/mod/chat/pages/sessions/sessions.ts | 165 ++++++++++++ src/addon/mod/chat/providers/chat.ts | 253 ++++++++++++++++-- .../mod/chat/providers/module-handler.ts | 11 +- .../mod/chat/providers/prefetch-handler.ts | 185 +++++++++++++ src/assets/lang/en.json | 6 + src/lang/en.json | 1 + 18 files changed, 902 insertions(+), 24 deletions(-) create mode 100644 src/addon/mod/chat/pages/session-messages/session-messages.html create mode 100644 src/addon/mod/chat/pages/session-messages/session-messages.module.ts create mode 100644 src/addon/mod/chat/pages/session-messages/session-messages.scss create mode 100644 src/addon/mod/chat/pages/session-messages/session-messages.ts create mode 100644 src/addon/mod/chat/pages/sessions/sessions.html create mode 100644 src/addon/mod/chat/pages/sessions/sessions.module.ts create mode 100644 src/addon/mod/chat/pages/sessions/sessions.scss create mode 100644 src/addon/mod/chat/pages/sessions/sessions.ts create mode 100644 src/addon/mod/chat/providers/prefetch-handler.ts diff --git a/scripts/langindex.json b/scripts/langindex.json index 6e9b7a740..047c14c1c 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -346,6 +346,7 @@ "addon.mod_book.errorchapter": "book", "addon.mod_book.modulenameplural": "book", "addon.mod_chat.beep": "chat", + "addon.mod_chat.chatreport": "chat", "addon.mod_chat.currentusers": "chat", "addon.mod_chat.enterchat": "chat", "addon.mod_chat.entermessage": "chat", @@ -357,12 +358,16 @@ "addon.mod_chat.messagebeepsyou": "chat", "addon.mod_chat.messageenter": "chat", "addon.mod_chat.messageexit": "chat", + "addon.mod_chat.messages": "chat", "addon.mod_chat.modulenameplural": "chat", "addon.mod_chat.mustbeonlinetosendmessages": "local_moodlemobileapp", "addon.mod_chat.nomessages": "chat", + "addon.mod_chat.nosessionsfound": "local_moodlemobileapp", "addon.mod_chat.send": "chat", "addon.mod_chat.sessionstart": "chat", + "addon.mod_chat.showincompletesessions": "local_moodlemobileapp", "addon.mod_chat.talk": "chat", + "addon.mod_chat.viewreport": "chat", "addon.mod_choice.cannotsubmit": "choice", "addon.mod_choice.choiceoptions": "choice", "addon.mod_choice.errorgetchoice": "local_moodlemobileapp", @@ -1295,6 +1300,7 @@ "core.defaultvalue": "tool_usertours", "core.delete": "moodle", "core.deletedoffline": "local_moodlemobileapp", + "core.deleteduser": "local_moodlemobileapp", "core.deleting": "local_moodlemobileapp", "core.description": "moodle", "core.dfdaymonthyear": "local_moodlemobileapp", diff --git a/src/addon/mod/chat/chat.module.ts b/src/addon/mod/chat/chat.module.ts index 62ce5d80b..7d90566e0 100644 --- a/src/addon/mod/chat/chat.module.ts +++ b/src/addon/mod/chat/chat.module.ts @@ -15,11 +15,13 @@ import { NgModule } from '@angular/core'; import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate'; import { CoreCourseModuleDelegate } from '@core/course/providers/module-delegate'; +import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate'; import { AddonModChatComponentsModule } from './components/components.module'; import { AddonModChatProvider } from './providers/chat'; import { AddonModChatLinkHandler } from './providers/link-handler'; import { AddonModChatListLinkHandler } from './providers/list-link-handler'; import { AddonModChatModuleHandler } from './providers/module-handler'; +import { AddonModChatPrefetchHandler } from './providers/prefetch-handler'; // List of providers (without handlers). export const ADDON_MOD_CHAT_PROVIDERS: any[] = [ @@ -37,15 +39,18 @@ export const ADDON_MOD_CHAT_PROVIDERS: any[] = [ AddonModChatLinkHandler, AddonModChatListLinkHandler, AddonModChatModuleHandler, + AddonModChatPrefetchHandler ] }) export class AddonModChatModule { constructor(moduleDelegate: CoreCourseModuleDelegate, moduleHandler: AddonModChatModuleHandler, contentLinksDelegate: CoreContentLinksDelegate, linkHandler: AddonModChatLinkHandler, + prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModChatPrefetchHandler, listLinkHandler: AddonModChatListLinkHandler) { moduleDelegate.registerHandler(moduleHandler); contentLinksDelegate.registerHandler(linkHandler); contentLinksDelegate.registerHandler(listLinkHandler); + prefetchDelegate.registerHandler(prefetchHandler); } } diff --git a/src/addon/mod/chat/components/index/addon-mod-chat-index.html b/src/addon/mod/chat/components/index/addon-mod-chat-index.html index b2f0d53c3..f76b8f8f8 100644 --- a/src/addon/mod/chat/components/index/addon-mod-chat-index.html +++ b/src/addon/mod/chat/components/index/addon-mod-chat-index.html @@ -5,6 +5,7 @@ + @@ -17,7 +18,8 @@ {{ 'addon.mod_chat.sessionstart' | translate:{$a: chatInfo} }} -

+ diff --git a/src/addon/mod/chat/components/index/index.ts b/src/addon/mod/chat/components/index/index.ts index 55d60961f..a04cf11d0 100644 --- a/src/addon/mod/chat/components/index/index.ts +++ b/src/addon/mod/chat/components/index/index.ts @@ -33,6 +33,7 @@ export class AddonModChatIndexComponent extends CoreCourseModuleMainActivityComp chatInfo: any; protected title: string; + protected sessionsAvailable = false; constructor(injector: Injector, private chatProvider: AddonModChatProvider, private timeUtils: CoreTimeUtilsProvider, protected navCtrl: NavController) { @@ -83,6 +84,10 @@ export class AddonModChatIndexComponent extends CoreCourseModuleMainActivityComp // All data obtained, now fill the context menu. this.fillContextMenu(refresh); + + return this.chatProvider.areSessionsAvailable().then((available) => { + this.sessionsAvailable = available; + }); }); } @@ -93,4 +98,11 @@ export class AddonModChatIndexComponent extends CoreCourseModuleMainActivityComp const title = this.chat.name || this.moduleName; this.navCtrl.push('AddonModChatChatPage', {chatId: this.chat.id, courseId: this.courseId, title: title }); } + + /** + * View past sessions. + */ + viewSessions(): void { + this.navCtrl.push('AddonModChatSessionsPage', {courseId: this.courseId, chatId: this.chat.id, cmId: this.module.id}); + } } diff --git a/src/addon/mod/chat/lang/en.json b/src/addon/mod/chat/lang/en.json index 30f9613ca..4348b5d63 100644 --- a/src/addon/mod/chat/lang/en.json +++ b/src/addon/mod/chat/lang/en.json @@ -1,5 +1,6 @@ { "beep": "Beep", + "chatreport": "Chat sessions", "currentusers": "Current users", "enterchat": "Click here to enter the chat now", "entermessage": "Enter your message", @@ -11,10 +12,14 @@ "messagebeepsyou": "{{$a}} has just beeped you!", "messageenter": "{{$a}} has just entered this chat", "messageexit": "{{$a}} has left this chat", + "messages": "Messages", "modulenameplural": "Chats", "mustbeonlinetosendmessages": "You must be online to send messages.", "nomessages": "No messages yet", + "nosessionsfound": "No sessions found", "send": "Send", "sessionstart": "The next chat session will start on {{$a.date}}, ({{$a.fromnow}} from now)", - "talk": "Talk" + "showincompletesessions": "Show incomplete sessions", + "talk": "Talk", + "viewreport": "View past chat sessions" } \ No newline at end of file diff --git a/src/addon/mod/chat/pages/session-messages/session-messages.html b/src/addon/mod/chat/pages/session-messages/session-messages.html new file mode 100644 index 000000000..a315ebecf --- /dev/null +++ b/src/addon/mod/chat/pages/session-messages/session-messages.html @@ -0,0 +1,40 @@ + + + {{ 'addon.mod_chat.messages' | translate }} + + + + + + + +
+
+ + {{ message.timestamp * 1000 | coreFormatDate:"strftimedayshort" }} + +
+ +
+ + {{ message.timestamp * 1000 | coreFormatDate:"strftimetime" }} {{ 'addon.mod_chat.messageenter' | translate:{$a: message.userfullname} }} + +
+ +
+ + {{ message.timestamp * 1000 | coreFormatDate:"strftimetime" }} {{ 'addon.mod_chat.messageexit' | translate:{$a: message.userfullname} }} + +
+ + + +

+

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

+ +

+ +
+
+
+
diff --git a/src/addon/mod/chat/pages/session-messages/session-messages.module.ts b/src/addon/mod/chat/pages/session-messages/session-messages.module.ts new file mode 100644 index 000000000..816b70999 --- /dev/null +++ b/src/addon/mod/chat/pages/session-messages/session-messages.module.ts @@ -0,0 +1,37 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreComponentsModule } from '@components/components.module'; +import { CoreDirectivesModule } from '@directives/directives.module'; +import { CorePipesModule } from '@pipes/pipes.module'; +import { AddonModChatComponentsModule } from '../../components/components.module'; +import { AddonModChatSessionMessagesPage } from './session-messages'; + +@NgModule({ + declarations: [ + AddonModChatSessionMessagesPage, + ], + imports: [ + CoreComponentsModule, + CoreDirectivesModule, + CorePipesModule, + AddonModChatComponentsModule, + IonicPageModule.forChild(AddonModChatSessionMessagesPage), + TranslateModule.forChild() + ], +}) +export class AddonModChatSessionMessagesPageModule {} diff --git a/src/addon/mod/chat/pages/session-messages/session-messages.scss b/src/addon/mod/chat/pages/session-messages/session-messages.scss new file mode 100644 index 000000000..a8d1e96e8 --- /dev/null +++ b/src/addon/mod/chat/pages/session-messages/session-messages.scss @@ -0,0 +1,9 @@ +ion-app.app-root page-addon-mod-chat-session-messages { + .addon-mod-chat-notice { + margin-top: 10px; + margin-bottom: 10px; + } + .addon-mod-chat-message { + align-items: flex-start; + } +} diff --git a/src/addon/mod/chat/pages/session-messages/session-messages.ts b/src/addon/mod/chat/pages/session-messages/session-messages.ts new file mode 100644 index 000000000..91fe8874b --- /dev/null +++ b/src/addon/mod/chat/pages/session-messages/session-messages.ts @@ -0,0 +1,95 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component } from '@angular/core'; +import { IonicPage, NavParams } from 'ionic-angular'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { AddonModChatProvider } from '../../providers/chat'; +import * as moment from 'moment'; + +/** + * Page that displays list of chat session messages. + */ +@IonicPage({ segment: 'addon-mod-chat-session-messages' }) +@Component({ + selector: 'page-addon-mod-chat-session-messages', + templateUrl: 'session-messages.html', +}) +export class AddonModChatSessionMessagesPage { + + protected courseId: number; + protected chatId: number; + protected sessionStart: number; + protected sessionEnd: number; + protected groupId: number; + protected loaded = false; + protected messages = []; + + constructor(navParams: NavParams, private domUtils: CoreDomUtilsProvider, private chatProvider: AddonModChatProvider) { + this.courseId = navParams.get('courseId'); + this.chatId = navParams.get('chatId'); + this.groupId = navParams.get('groupId'); + this.sessionStart = navParams.get('sessionStart'); + this.sessionEnd = navParams.get('sessionEnd'); + + this.fetchMessages(); + } + + /** + * Fetch session messages. + * + * @return {Promise} Promise resolved when done. + */ + protected fetchMessages(): Promise { + return this.chatProvider.getSessionMessages(this.chatId, this.sessionStart, this.sessionEnd, this.groupId) + .then((messages) => { + return this.chatProvider.getMessagesUserData(messages, this.courseId).then((messages) => { + this.messages = messages; + }); + }).catch((error) => { + this.domUtils.showErrorModalDefault(error, 'core.errorloadingcontent', true); + }).finally(() => { + this.loaded = true; + }); + } + + /** + * Refresh session messages. + * + * @param {any} refresher Refresher. + */ + refreshMessages(refresher: any): void { + this.chatProvider.invalidateSessionMessages(this.chatId, this.sessionStart, this.groupId).finally(() => { + this.fetchMessages().finally(() => { + refresher.complete(); + }); + }); + } + + /** + * Check if the date should be displayed between messages (when the day changes at midnight for example). + * + * @param {any} message New message object. + * @param {any} prevMessage Previous message object. + * @return {boolean} True if messages are from diferent days, false othetwise. + */ + showDate(message: any, prevMessage: any): boolean { + if (!prevMessage) { + return true; + } + + // Check if day has changed. + return !moment(message.timestamp * 1000).isSame(prevMessage.timestamp * 1000, 'day'); + } +} diff --git a/src/addon/mod/chat/pages/sessions/sessions.html b/src/addon/mod/chat/pages/sessions/sessions.html new file mode 100644 index 000000000..b78df54bc --- /dev/null +++ b/src/addon/mod/chat/pages/sessions/sessions.html @@ -0,0 +1,45 @@ + + + {{ 'addon.mod_chat.chatreport' | translate }} + + + + + + + + + + {{ 'core.groupsseparate' | translate }} + {{ 'core.groupsvisible' | translate }} + + {{groupOpt.name}} + + + + {{ 'addon.mod_chat.showincompletesessions' | translate }} + + + + +

{{ session.sessionstart * 1000 | coreFormatDate }}

+

{{ session.duration | coreDuration }}

+
+ +

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

+
+
+ +
+
+ + +
+
+
diff --git a/src/addon/mod/chat/pages/sessions/sessions.module.ts b/src/addon/mod/chat/pages/sessions/sessions.module.ts new file mode 100644 index 000000000..23e5cdc1e --- /dev/null +++ b/src/addon/mod/chat/pages/sessions/sessions.module.ts @@ -0,0 +1,37 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreComponentsModule } from '@components/components.module'; +import { CoreDirectivesModule } from '@directives/directives.module'; +import { CorePipesModule } from '@pipes/pipes.module'; +import { AddonModChatComponentsModule } from '../../components/components.module'; +import { AddonModChatSessionsPage } from './sessions'; + +@NgModule({ + declarations: [ + AddonModChatSessionsPage, + ], + imports: [ + CoreComponentsModule, + CoreDirectivesModule, + CorePipesModule, + AddonModChatComponentsModule, + IonicPageModule.forChild(AddonModChatSessionsPage), + TranslateModule.forChild() + ], +}) +export class AddonModChatSessionsPageModule {} diff --git a/src/addon/mod/chat/pages/sessions/sessions.scss b/src/addon/mod/chat/pages/sessions/sessions.scss new file mode 100644 index 000000000..066605cdc --- /dev/null +++ b/src/addon/mod/chat/pages/sessions/sessions.scss @@ -0,0 +1,8 @@ +ion-app.app-root page-addon-mod-chat-sessions { + .addon-mod-chat-session-show-more .card-content{ + padding-bottom: 0; + } + .addon-mod-chat-session-selected { + border-top: 5px solid $core-splitview-selected; + } +} diff --git a/src/addon/mod/chat/pages/sessions/sessions.ts b/src/addon/mod/chat/pages/sessions/sessions.ts new file mode 100644 index 000000000..59e43b6e2 --- /dev/null +++ b/src/addon/mod/chat/pages/sessions/sessions.ts @@ -0,0 +1,165 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, ViewChild } from '@angular/core'; +import { IonicPage, NavParams } from 'ionic-angular'; +import { TranslateService } from '@ngx-translate/core'; +import { CoreSplitViewComponent } from '@components/split-view/split-view'; +import { CoreUserProvider } from '@core/user/providers/user'; +import { CoreGroupsProvider, CoreGroupInfo } from '@providers/groups'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { CoreUtilsProvider } from '@providers/utils/utils'; +import { AddonModChatProvider } from '../../providers/chat'; + +/** + * Page that displays list of chat sessions. + */ +@IonicPage({ segment: 'addon-mod-chat-sessions' }) +@Component({ + selector: 'page-addon-mod-chat-sessions', + templateUrl: 'sessions.html', +}) +export class AddonModChatSessionsPage { + + @ViewChild(CoreSplitViewComponent) splitviewCtrl: CoreSplitViewComponent; + + protected courseId: number; + protected cmId: number; + protected chatId: number; + protected loaded = false; + protected showAll = false; + protected groupId = 0; + protected groupInfo: CoreGroupInfo; + protected sessions = []; + protected selectedSessionStart: number; + protected selectedSessionGroupId: number; + + constructor(navParams: NavParams, private chatProvider: AddonModChatProvider, private domUtils: CoreDomUtilsProvider, + private userProvider: CoreUserProvider, private groupsProvider: CoreGroupsProvider, + private translate: TranslateService, private utils: CoreUtilsProvider) { + this.courseId = navParams.get('courseId'); + this.cmId = navParams.get('cmId'); + this.chatId = navParams.get('chatId'); + + this.fetchSessions().then(() => { + if (this.splitviewCtrl.isOn() && this.sessions.length > 0) { + this.openSession(this.sessions[0]); + } + }); + } + + /** + * Fetch chat sessions. + * + * @param {number} [showLoading] Display a loading modal. + * @return {Promise} Promise resolved when done. + */ + fetchSessions(showLoading?: boolean): Promise { + const modal = showLoading ? this.domUtils.showModalLoading() : null; + + return this.groupsProvider.getActivityGroupInfo(this.cmId, false).then((groupInfo) => { + this.groupInfo = groupInfo; + + if (groupInfo.groups && groupInfo.groups.length > 0) { + if (!groupInfo.groups.find((group) => group.id === this.groupId)) { + this.groupId = groupInfo.groups[0].id; + } + } else { + this.groupId = 0; + } + + return this.chatProvider.getSessions(this.chatId, this.groupId, this.showAll); + }).then((sessions) => { + // Fetch user profiles. + const promises = []; + + sessions.forEach((session) => { + session.duration = session.sessionend - session.sessionstart; + session.sessionusers.forEach((sessionUser) => { + if (!sessionUser.userfullname) { + // The WS does not return the user name, fetch user profile. + promises.push(this.userProvider.getProfile(sessionUser.userid, this.courseId, true).then((user) => { + sessionUser.userfullname = user.fullname; + }).catch(() => { + // Error getting profile, most probably the user is deleted. + sessionUser.userfullname = this.translate.instant('core.deleteduser', {$a: sessionUser.userid}); + })); + } + }); + + // If session has more than 4 users we display a "Show more" link. + session.allsessionusers = session.sessionusers; + if (session.sessionusers.length > 4) { + session.sessionusers = session.allsessionusers.slice(0, 3); + } + }); + + return Promise.all(promises).then(() => { + this.sessions = sessions; + }); + }).catch((error) => { + this.domUtils.showErrorModalDefault(error, 'core.errorloadingcontent', true); + }).finally(() => { + this.loaded = true; + modal && modal.dismiss(); + }); + } + + /** + * Refresh chat sessions. + * + * @param {any} refresher Refresher. + */ + refreshSessions(refresher: any): void { + const promises = [ + this.groupsProvider.invalidateActivityGroupInfo(this.cmId), + this.chatProvider.invalidateSessions(this.chatId, this.groupId, this.showAll) + ]; + + this.utils.allPromises(promises).finally(() => { + this.fetchSessions().finally(() => { + refresher.complete(); + }); + }); + } + + /** + * Navigate to a session. + * + * @param {any} session Chat session. + */ + openSession(session: any): void { + this.selectedSessionStart = session.sessionstart; + this.selectedSessionGroupId = this.groupId; + const params = { + courseId: this.courseId, + chatId: this.chatId, + groupId: this.groupId, + sessionStart: session.sessionstart, + sessionEnd: session.sessionend + }; + this.splitviewCtrl.push('AddonModChatSessionMessagesPage', params); + } + + /** + * Show more session users. + * + * @param {any} session Chat session. + * @param {Event} $event The event. + */ + showMoreUsers(session: any, $event: Event): void { + session.sessionusers = session.allsessionusers; + $event.stopPropagation(); + } +} diff --git a/src/addon/mod/chat/providers/chat.ts b/src/addon/mod/chat/providers/chat.ts index 1138502da..16e6f7cd1 100644 --- a/src/addon/mod/chat/providers/chat.ts +++ b/src/addon/mod/chat/providers/chat.ts @@ -13,9 +13,12 @@ // limitations under the License. import { Injectable } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; import { CoreSitesProvider } from '@providers/sites'; import { CoreUserProvider } from '@core/user/providers/user'; import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; +import { CoreUtilsProvider } from '@providers/utils/utils'; +import { CoreSiteWSPreSets } from '@classes/site'; /** * Service that provides some features for chats. @@ -25,34 +28,38 @@ export class AddonModChatProvider { static COMPONENT = 'mmaModChat'; static POLL_INTERVAL = 4000; + protected ROOT_CACHE_KEY = 'AddonModChat:'; + constructor(private sitesProvider: CoreSitesProvider, private userProvider: CoreUserProvider, - private logHelper: CoreCourseLogHelperProvider) {} + private logHelper: CoreCourseLogHelperProvider, protected utils: CoreUtilsProvider, private translate: TranslateService) {} /** * Get a chat. * - * @param {number} courseId Course ID. - * @param {number} cmId Course module ID. - * @param {boolean} [refresh=false] True when we should not get the value from the cache. + * @param {number} courseId Course ID. + * @param {number} cmId Course module ID. + * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the chat is retrieved. */ - getChat(courseId: number, cmId: number, refresh: boolean = false): Promise { - const params = { - courseids: [courseId] - }; - const preSets = { - getFromCache: refresh ? false : undefined, - }; + getChat(courseId: number, cmId: number, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + const params = { + courseids: [courseId] + }; + const preSets: CoreSiteWSPreSets = { + cacheKey: this.getChatsCacheKey(courseId) + }; - return this.sitesProvider.getCurrentSite().read('mod_chat_get_chats_by_courses', params, preSets).then((response) => { - if (response.chats) { - const chat = response.chats.find((chat) => chat.coursemodule == cmId); - if (chat) { - return chat; + return site.read('mod_chat_get_chats_by_courses', params, preSets).then((response) => { + if (response.chats) { + const chat = response.chats.find((chat) => chat.coursemodule == cmId); + if (chat) { + return chat; + } } - } - return Promise.reject(null); + return Promise.reject(null); + }); }); } @@ -146,8 +153,8 @@ export class AddonModChatProvider { message.userfullname = user.fullname; message.userprofileimageurl = user.profileimageurl; }).catch(() => { - // Error getting profile. Set default data. - message.userfullname = message.userid; + // Error getting profile, most probably the user is deleted. + message.userfullname = this.translate.instant('core.deleteduser', {$a: message.userid}); }); }); @@ -172,4 +179,210 @@ export class AddonModChatProvider { return this.sitesProvider.getCurrentSite().read('mod_chat_get_chat_users', params, preSets); } + + /** + * Return whether WS for passed sessions are available. + * + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with a boolean. + */ + areSessionsAvailable(siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + return site.wsAvailable('mod_chat_get_sessions') && site.wsAvailable('mod_chat_get_session_messages'); + }); + } + + /** + * Get chat sessions. + * + * @param {number} chatId Chat ID. + * @param {number} [groupId=0] Group ID, 0 means that the function will determine the user group. + * @param {boolean} [showAll=false] Whether to include incomplete sessions or not. + * @param {boolean} [ignoreCache=false] True if it should ignore cached data (it will always fail in offline or server down). + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with the list of sessions. + * @since 3.5 + */ + getSessions(chatId: number, groupId: number = 0, showAll: boolean = false, ignoreCache: boolean = false, siteId?: string): + Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + const params = { + chatid: chatId, + groupid: groupId, + showall: showAll ? 1 : 0 + }; + const preSets: CoreSiteWSPreSets = { + cacheKey: this.getSessionsCacheKey(chatId, groupId, showAll), + }; + if (ignoreCache) { + preSets.getFromCache = false; + preSets.emergencyCache = false; + } + + return site.read('mod_chat_get_sessions', params, preSets).then((response) => { + if (!response || !response.sessions) { + return Promise.reject(null); + } + + return response.sessions; + }); + }); + } + + /** + * Get chat session messages. + * + * @param {number} chatId Chat ID. + * @param {number} sessionStart Session start time. + * @param {number} sessionEnd Session end time. + * @param {number} [groupId=0] Group ID, 0 means that the function will determine the user group. + * @param {boolean} [ignoreCache=false] True if it should ignore cached data (it will always fail in offline or server down). + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with the list of messages. + * @since 3.5 + */ + getSessionMessages(chatId: number, sessionStart: number, sessionEnd: number, groupId: number = 0, ignoreCache: boolean = false, + siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + const params = { + chatid: chatId, + sessionstart: sessionStart, + sessionend: sessionEnd, + groupid: groupId + }; + const preSets: CoreSiteWSPreSets = { + cacheKey: this.getSessionMessagesCacheKey(chatId, sessionStart, groupId) + }; + if (ignoreCache) { + preSets.getFromCache = false; + preSets.emergencyCache = false; + } + + return site.read('mod_chat_get_session_messages', params, preSets).then((response) => { + if (!response || !response.messages) { + return Promise.reject(null); + } + + return response.messages; + }); + }); + } + + /** + * Invalidate chats. + * + * @param {number} courseId Course ID. + * @return {Promise} Promise resolved when the data is invalidated. + */ + invalidateChats(courseId: number): Promise { + const site = this.sitesProvider.getCurrentSite(); + + return site.invalidateWsCacheForKey(this.getChatsCacheKey(courseId)); + } + + /** + * Invalidate chat sessions. + * + * @param {number} chatId Chat ID. + * @param {number} [groupId=0] Group ID, 0 means that the function will determine the user group. + * @param {boolean} [showAll=false] Whether to include incomplete sessions or not. + * @return {Promise} Promise resolved when the data is invalidated. + */ + invalidateSessions(chatId: number, groupId: number = 0, showAll: boolean = false): Promise { + const site = this.sitesProvider.getCurrentSite(); + + return site.invalidateWsCacheForKey(this.getSessionsCacheKey(chatId, groupId, showAll)); + } + + /** + * Invalidate all chat sessions. + * + * @param {number} chatId Chat ID. + * @return {Promise} Promise resolved when the data is invalidated. + */ + invalidateAllSessions(chatId: number): Promise { + const site = this.sitesProvider.getCurrentSite(); + + return site.invalidateWsCacheForKeyStartingWith(this.getSessionsCacheKeyPrefix(chatId)); + } + + /** + * Invalidate chat session messages. + * + * @param {number} chatId Chat ID. + * @param {number} sessionStart Session start time. + * @param {number} [groupId=0] Group ID, 0 means that the function will determine the user group. + * @return {Promise} Promise resolved when the data is invalidated. + */ + invalidateSessionMessages(chatId: number, sessionStart: number, groupId: number = 0): Promise { + const site = this.sitesProvider.getCurrentSite(); + + return site.invalidateWsCacheForKey(this.getSessionMessagesCacheKey(chatId, sessionStart, groupId)); + } + + /** + * Invalidate all chat session messages. + * + * @param {number} chatId Chat ID. + * @return {Promise} Promise resolved when the data is invalidated. + */ + invalidateAllSessionMessages(chatId: number): Promise { + const site = this.sitesProvider.getCurrentSite(); + + return site.invalidateWsCacheForKeyStartingWith(this.getSessionMessagesCacheKeyPrefix(chatId)); + } + + /** + * Get cache key for chats WS call. + * + * @param {number} courseId Course ID. + * @return {string} Cache key. + */ + protected getChatsCacheKey(courseId: number): string { + return this.ROOT_CACHE_KEY + 'chats:' + courseId; + } + + /** + * Get cache key for sessions WS call. + * + * @param {number} chatId Chat ID. + * @param {number} groupId Goup ID, 0 means that the function will determine the user group. + * @param {boolean} showAll Whether to include incomplete sessions or not. + * @return {string} Cache key. + */ + protected getSessionsCacheKey(chatId: number, groupId: number, showAll: boolean): string { + return this.getSessionsCacheKeyPrefix(chatId) + groupId + ':' + (showAll ? 1 : 0); + } + + /** + * Get cache key prefix for sessions WS call. + * + * @param {number} chatId Chat ID. + * @return {string} Cache key prefix. + */ + protected getSessionsCacheKeyPrefix(chatId: number): string { + return this.ROOT_CACHE_KEY + 'sessions:' + chatId + ':'; + } + + /** + * Get cache key for session messages WS call. + * + * @param {number} chatId Chat ID. + * @param {number} sessionStart Session start time. + * @param {number} groupId Group ID, 0 means that the function will determine the user group. + * @return {string} Cache key. + */ + protected getSessionMessagesCacheKey(chatId: number, sessionStart: number, groupId: number): string { + return this.getSessionMessagesCacheKeyPrefix(chatId) + sessionStart + ':' + groupId; + } + + /** + * Get cache key prefix for session messages WS call. + * + * @param {number} chatId Chat ID. + * @return {string} Cache key prefix. + */ + protected getSessionMessagesCacheKeyPrefix(chatId: number): string { + return this.ROOT_CACHE_KEY + 'sessionsMessages:' + chatId + ':'; + } } diff --git a/src/addon/mod/chat/providers/module-handler.ts b/src/addon/mod/chat/providers/module-handler.ts index 4ef57c7b3..f85c2be5e 100644 --- a/src/addon/mod/chat/providers/module-handler.ts +++ b/src/addon/mod/chat/providers/module-handler.ts @@ -18,6 +18,7 @@ import { AddonModChatIndexComponent } from '../components/index/index'; import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@core/course/providers/module-delegate'; import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreConstants } from '@core/constants'; +import { AddonModChatProvider } from './chat'; /** * Handler to support chat modules. @@ -38,7 +39,7 @@ export class AddonModChatModuleHandler implements CoreCourseModuleHandler { [CoreConstants.FEATURE_SHOW_DESCRIPTION]: true }; - constructor(private courseProvider: CoreCourseProvider) { } + constructor(private courseProvider: CoreCourseProvider, private chatProvider: AddonModChatProvider) { } /** * Check if the handler is enabled on a site level. @@ -58,7 +59,7 @@ export class AddonModChatModuleHandler implements CoreCourseModuleHandler { * @return {CoreCourseModuleHandlerData} Data to render the module. */ getData(module: any, courseId: number, sectionId: number): CoreCourseModuleHandlerData { - return { + const data: CoreCourseModuleHandlerData = { icon: this.courseProvider.getModuleIconSrc(this.modName, module.modicon), title: module.name, class: 'addon-mod_chat-handler', @@ -70,6 +71,12 @@ export class AddonModChatModuleHandler implements CoreCourseModuleHandler { navCtrl.push('AddonModChatIndexPage', pageParams, options); } }; + + this.chatProvider.areSessionsAvailable().then((available) => { + data.showDownloadButton = available; + }); + + return data; } /** diff --git a/src/addon/mod/chat/providers/prefetch-handler.ts b/src/addon/mod/chat/providers/prefetch-handler.ts new file mode 100644 index 000000000..c9f54a62a --- /dev/null +++ b/src/addon/mod/chat/providers/prefetch-handler.ts @@ -0,0 +1,185 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { CoreAppProvider } from '@providers/app'; +import { CoreFilepoolProvider } from '@providers/filepool'; +import { CoreGroupsProvider, CoreGroupInfo } from '@providers/groups'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { CoreUtilsProvider } from '@providers/utils/utils'; +import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreCourseActivityPrefetchHandlerBase } from '@core/course/classes/activity-prefetch-handler'; +import { CoreUserProvider } from '@core/user/providers/user'; +import { AddonModChatProvider } from './chat'; + +/** + * Handler to prefetch chats. + */ +@Injectable() +export class AddonModChatPrefetchHandler extends CoreCourseActivityPrefetchHandlerBase { + name = 'AddonModChat'; + modName = 'chat'; + component = AddonModChatProvider.COMPONENT; + + constructor(translate: TranslateService, + appProvider: CoreAppProvider, + utils: CoreUtilsProvider, + courseProvider: CoreCourseProvider, + filepoolProvider: CoreFilepoolProvider, + sitesProvider: CoreSitesProvider, + domUtils: CoreDomUtilsProvider, + private groupsProvider: CoreGroupsProvider, + private userProvider: CoreUserProvider, + private chatProvider: AddonModChatProvider) { + + super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils); + } + + /** + * Whether or not the handler is enabled on a site level. + * + * @return {boolean|Promise} A boolean, or a promise resolved with a boolean, indicating if the handler is enabled. + */ + isEnabled(): boolean | Promise { + return this.chatProvider.areSessionsAvailable(); + } + + /** + * Invalidate the prefetched content. + * + * @param {number} moduleId The module ID. + * @param {number} courseId The course ID the module belongs to. + * @return {Promise} Promise resolved when the data is invalidated. + */ + invalidateContent(moduleId: number, courseId: number): Promise { + return this.chatProvider.getChat(courseId, moduleId).then((chat) => { + const promises = [ + this.chatProvider.invalidateAllSessions(chat.id), + this.chatProvider.invalidateAllSessionMessages(chat.id) + ]; + + return this.utils.allPromises(promises); + }); + } + + /** + * Invalidate WS calls needed to determine module status (usually, to check if module is downloadable). + * It doesn't need to invalidate check updates. It should NOT invalidate files nor all the prefetched data. + * + * @param {any} module Module. + * @param {number} courseId Course ID the module belongs to. + * @return {Promise} Promise resolved when invalidated. + */ + invalidateModule(module: any, courseId: number): Promise { + const promises = [ + this.chatProvider.invalidateChats(courseId), + this.courseProvider.invalidateModule(module.id) + ]; + + return this.utils.allPromises(promises); + } + + /** + * Prefetch a module. + * + * @param {any} module Module. + * @param {number} courseId Course ID the module belongs to. + * @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section. + * @param {string} [dirPath] Path of the directory where to store all the content files. + * @return {Promise} Promise resolved when done. + */ + prefetch(module: any, courseId?: number, single?: boolean, dirPath?: string): Promise { + return this.prefetchPackage(module, courseId, single, this.prefetchChat.bind(this)); + } + + /** + * Prefetch a chat. + * + * @param {any} module The module object returned by WS. + * @param {number} courseId Course ID the module belongs to. + * @param {boolean} single True if we're downloading a single module, false if we're downloading a whole section. + * @param {string} siteId Site ID. + * @return {Promise} Promise resolved when done. + */ + protected prefetchChat(module: any, courseId: number, single: boolean, siteId: string): Promise { + // Prefetch chat and group info. + const promises = [ + this.chatProvider.getChat(courseId, module.id, siteId), + this.groupsProvider.getActivityGroupInfo(module.id, false, undefined, siteId) + ]; + + return Promise.all(promises).then(([chat, groupInfo]: [any, CoreGroupInfo]) => { + const promises = []; + + let groupIds = [0]; + if (groupInfo.groups && groupInfo.groups.length > 0) { + groupIds = groupInfo.groups.map((group) => group.id); + } + + groupIds.forEach((groupId) => { + // Prefetch complete sessions. + promises.push(this.chatProvider.getSessions(chat.id, groupId, false, true, siteId).catch((error) => { + // Ignore group error. + if (error.errorcode != 'notingroup') { + return Promise.reject(error); + } + })); + + // Prefetch all sessions. + promises.push(this.chatProvider.getSessions(chat.id, groupId, true, true, siteId).then((sessions) => { + const promises = sessions.map((session) => this.prefetchSession(chat.id, session, 0, courseId, siteId)); + + return Promise.all(promises); + }).catch((error) => { + // Ignore group error. + if (error.errorcode != 'notingroup') { + return Promise.reject(error); + } + })); + }); + + return Promise.all(promises); + }); + } + + /** + * Prefetch chat session messages and user profiles. + * + * @param {number} chatId Chat ID. + * @param {any} session Session object. + * @param {number} groupId Group ID. + * @param {number} courseId Course ID the module belongs to. + * @param {string} siteId Site ID. + * @return {Promise} Promise resolved when done. + */ + protected prefetchSession(chatId: number, session: any, groupId: number, courseId: number, siteId: string): Promise { + return this.chatProvider.getSessionMessages(chatId, session.sessionstart, session.sessionend, groupId, true, siteId) + .then((messages) => { + const users = {}; + session.sessionusers.forEach((user) => { + users[user.userid] = true; + }); + messages.forEach((message) => { + users[message.userid] = true; + }); + const userIds = Object.keys(users).map(Number); + + return this.userProvider.prefetchProfiles(userIds, courseId, siteId).catch(() => { + // Ignore errors, some users might not exist. + }); + }); + } +} diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index a82101823..81ca8e7ce 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -345,6 +345,7 @@ "addon.mod_book.errorchapter": "Error reading chapter of book.", "addon.mod_book.modulenameplural": "Books", "addon.mod_chat.beep": "Beep", + "addon.mod_chat.chatreport": "Chat sessions", "addon.mod_chat.currentusers": "Current users", "addon.mod_chat.enterchat": "Click here to enter the chat now", "addon.mod_chat.entermessage": "Enter your message", @@ -356,12 +357,16 @@ "addon.mod_chat.messagebeepsyou": "{{$a}} has just beeped you!", "addon.mod_chat.messageenter": "{{$a}} has just entered this chat", "addon.mod_chat.messageexit": "{{$a}} has left this chat", + "addon.mod_chat.messages": "Messages", "addon.mod_chat.modulenameplural": "Chats", "addon.mod_chat.mustbeonlinetosendmessages": "You must be online to send messages.", "addon.mod_chat.nomessages": "No messages yet", + "addon.mod_chat.nosessionsfound": "No sessions found", "addon.mod_chat.send": "Send", "addon.mod_chat.sessionstart": "The next chat session will start on {{$a.date}}, ({{$a.fromnow}} from now)", + "addon.mod_chat.showincompletesessions": "Show incomplete sessions", "addon.mod_chat.talk": "Talk", + "addon.mod_chat.viewreport": "View past chat sessions", "addon.mod_choice.cannotsubmit": "Sorry, there was a problem submitting your choice. Please try again.", "addon.mod_choice.choiceoptions": "Choice options", "addon.mod_choice.errorgetchoice": "Error getting choice data.", @@ -1294,6 +1299,7 @@ "core.defaultvalue": "Default ({{$a}})", "core.delete": "Delete", "core.deletedoffline": "Deleted offline", + "core.deleteduser": "Deleted user {{$a}}", "core.deleting": "Deleting", "core.description": "Description", "core.dfdaymonthyear": "MM-DD-YYYY", diff --git a/src/lang/en.json b/src/lang/en.json index bc10378ca..a8aeed519 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -57,6 +57,7 @@ "defaultvalue": "Default ({{$a}})", "delete": "Delete", "deletedoffline": "Deleted offline", + "deleteduser": "Deleted user {{$a}}", "deleting": "Deleting", "description": "Description", "dfdaymonthyear": "MM-DD-YYYY", From 2420e3a6fb5a2fcca164e1a892a204915ce8af8b Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Tue, 19 Feb 2019 10:59:39 +0100 Subject: [PATCH 092/191] MOBILE-1928 ws: Save "notingroup" errors to the cache --- scripts/langindex.json | 1 + src/assets/lang/en.json | 1 + src/classes/site.ts | 7 +++++-- src/lang/en.json | 1 + 4 files changed, 8 insertions(+), 2 deletions(-) diff --git a/scripts/langindex.json b/scripts/langindex.json index 047c14c1c..f7c408447 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -1551,6 +1551,7 @@ "core.noresults": "moodle", "core.notapplicable": "local_moodlemobileapp", "core.notice": "moodle", + "core.notingroup": "moodle", "core.notsent": "local_moodlemobileapp", "core.now": "moodle", "core.numwords": "moodle", diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index 81ca8e7ce..4dceedd94 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -1550,6 +1550,7 @@ "core.noresults": "No results", "core.notapplicable": "n/a", "core.notice": "Notice", + "core.notingroup": "Sorry, but you need to be part of a group to see this page.", "core.notsent": "Not sent", "core.now": "now", "core.numwords": "{{$a}} words", diff --git a/src/classes/site.ts b/src/classes/site.ts index 36fd3d598..9776bac47 100644 --- a/src/classes/site.ts +++ b/src/classes/site.ts @@ -632,10 +632,13 @@ export class CoreSite { error.message = this.translate.instant('core.unicodenotsupported'); return Promise.reject(error); - } else if (error.exception === 'required_capability_exception' || error.errorcode === 'nopermission') { + } else if (error.exception === 'required_capability_exception' || error.errorcode === 'nopermission' || + error.errorcode === 'notingroup') { + // Translate error messages with missing strings. if (error.message === 'error/nopermission') { - // This error message is returned by some web services but the string does not exist. error.message = this.translate.instant('core.nopermissionerror'); + } else if (error.message === 'error/notingroup') { + error.message = this.translate.instant('core.notingroup'); } // Save the error instead of deleting the cache entry so the same content is displayed in offline. diff --git a/src/lang/en.json b/src/lang/en.json index a8aeed519..9da338b2e 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -170,6 +170,7 @@ "noresults": "No results", "notapplicable": "n/a", "notice": "Notice", + "notingroup": "Sorry, but you need to be part of a group to see this page.", "notsent": "Not sent", "now": "now", "numwords": "{{$a}} words", From d83f0bb4a8956dacf9d9a5cae12377f0fefef339 Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Mon, 25 Feb 2019 10:10:08 +0100 Subject: [PATCH 093/191] MOBILE-1928 chat: Use "deletedusser" string from core --- scripts/langindex.json | 2 +- src/addon/mod/chat/pages/sessions/sessions.ts | 2 +- src/addon/mod/chat/providers/chat.ts | 2 +- src/assets/lang/en.json | 2 +- src/lang/en.json | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/langindex.json b/scripts/langindex.json index f7c408447..c9486501c 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -1300,7 +1300,7 @@ "core.defaultvalue": "tool_usertours", "core.delete": "moodle", "core.deletedoffline": "local_moodlemobileapp", - "core.deleteduser": "local_moodlemobileapp", + "core.deleteduser": "bulkusers", "core.deleting": "local_moodlemobileapp", "core.description": "moodle", "core.dfdaymonthyear": "local_moodlemobileapp", diff --git a/src/addon/mod/chat/pages/sessions/sessions.ts b/src/addon/mod/chat/pages/sessions/sessions.ts index 59e43b6e2..35f26cb33 100644 --- a/src/addon/mod/chat/pages/sessions/sessions.ts +++ b/src/addon/mod/chat/pages/sessions/sessions.ts @@ -93,7 +93,7 @@ export class AddonModChatSessionsPage { sessionUser.userfullname = user.fullname; }).catch(() => { // Error getting profile, most probably the user is deleted. - sessionUser.userfullname = this.translate.instant('core.deleteduser', {$a: sessionUser.userid}); + sessionUser.userfullname = this.translate.instant('core.deleteduser') + ' ' + sessionUser.userid; })); } }); diff --git a/src/addon/mod/chat/providers/chat.ts b/src/addon/mod/chat/providers/chat.ts index 16e6f7cd1..f12107dc4 100644 --- a/src/addon/mod/chat/providers/chat.ts +++ b/src/addon/mod/chat/providers/chat.ts @@ -154,7 +154,7 @@ export class AddonModChatProvider { message.userprofileimageurl = user.profileimageurl; }).catch(() => { // Error getting profile, most probably the user is deleted. - message.userfullname = this.translate.instant('core.deleteduser', {$a: message.userid}); + message.userfullname = this.translate.instant('core.deleteduser') + ' ' + message.userid; }); }); diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index 4dceedd94..0731749b0 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -1299,7 +1299,7 @@ "core.defaultvalue": "Default ({{$a}})", "core.delete": "Delete", "core.deletedoffline": "Deleted offline", - "core.deleteduser": "Deleted user {{$a}}", + "core.deleteduser": "Deleted user", "core.deleting": "Deleting", "core.description": "Description", "core.dfdaymonthyear": "MM-DD-YYYY", diff --git a/src/lang/en.json b/src/lang/en.json index 9da338b2e..65c423bd1 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -57,7 +57,7 @@ "defaultvalue": "Default ({{$a}})", "delete": "Delete", "deletedoffline": "Deleted offline", - "deleteduser": "Deleted user {{$a}}", + "deleteduser": "Deleted user", "deleting": "Deleting", "description": "Description", "dfdaymonthyear": "MM-DD-YYYY", From f78f3cddc7946d7673954b5c7c055108b8b08e2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Mon, 25 Feb 2019 17:11:42 +0100 Subject: [PATCH 094/191] MOBILE-1973 quiz: Make the navigation modal lateral --- src/addon/mod/quiz/pages/player/player.ts | 2 +- src/app/app.scss | 23 ++++++++++++++++------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/addon/mod/quiz/pages/player/player.ts b/src/addon/mod/quiz/pages/player/player.ts index 669445fbc..83fb1dca3 100644 --- a/src/addon/mod/quiz/pages/player/player.ts +++ b/src/addon/mod/quiz/pages/player/player.ts @@ -94,7 +94,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { // Create the navigation modal. this.navigationModal = modalCtrl.create('AddonModQuizNavigationModalPage', { page: this - }); + }, { cssClass: 'core-modal-lateral' }); } /** diff --git a/src/app/app.scss b/src/app/app.scss index eb0aad801..63d415519 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -721,13 +721,22 @@ ion-app.app-root { width: 100%; } - .core-modal-fullscreen { - .modal-wrapper { - position: absolute; - @include position(0 !important, null, null, 0 !important); - display: block; - width: 100% !important; - height: 100% !important; + .core-modal-fullscreen .modal-wrapper { + position: absolute; + @include position(0 !important, null, null, 0 !important); + display: block; + width: 100% !important; + height: 100% !important; + } + + @media only screen and (min-height: 600px) and (min-width: 768px) { + .core-modal-lateral .modal-wrapper { + position: absolute; + @include position(0 !important, 0 !important, 0 !important, auto); + display: block; + height: 100% !important; + width: auto; + min-width: 400px; } } From c048c6f9bf3c4d482a6a0afda596844521e0b9ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Mon, 25 Feb 2019 17:12:16 +0100 Subject: [PATCH 095/191] MOBILE-1973 feedback: Solve item input feedback styles --- src/app/app.scss | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/app/app.scss b/src/app/app.scss index 63d415519..679a902f5 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -664,6 +664,22 @@ ion-app.app-root { } } + .core-#{$color-name}-item.item-input { + border-bottom: 0 !important; + + &.item-md .item-inner { + @include md-input-highlight($color-base); + } + + &.item-ios .item-inner { + @include ios-input-highlight($color-base); + } + + &.item-wp .item-inner { + border-color: $color-base; + } + } + .core-#{$color-name}-selected-item { @include safe-area-border-start(5px, solid, $color-base); From 04bc8fd81bee9312d7e1f83b36e83a4207fb9f6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Mon, 25 Feb 2019 17:12:47 +0100 Subject: [PATCH 096/191] MOBILE-1973 sync: Do not sync logged out sites --- src/classes/base-sync.ts | 2 +- src/providers/sites.ts | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/classes/base-sync.ts b/src/classes/base-sync.ts index 2d1106973..f4a2bb436 100644 --- a/src/classes/base-sync.ts +++ b/src/classes/base-sync.ts @@ -243,7 +243,7 @@ export class CoreSyncBaseProvider { if (!siteId) { // No site ID defined, sync all sites. this.logger.debug(`Try to sync '${syncFunctionLog}' in all sites.`); - promise = this.sitesProvider.getSitesIds(); + promise = this.sitesProvider.getLoggedInSitesIds(); } else { this.logger.debug(`Try to sync '${syncFunctionLog}' in site '${siteId}'.`); promise = Promise.resolve([siteId]); diff --git a/src/providers/sites.ts b/src/providers/sites.ts index 759b89372..9ae995e8b 100644 --- a/src/providers/sites.ts +++ b/src/providers/sites.ts @@ -1050,6 +1050,19 @@ export class CoreSitesProvider { }); } + /** + * Get the list of IDs of sites stored and not logged out. + * + * @return {Promise} Promise resolved when the sites IDs are retrieved. + */ + getLoggedInSitesIds(): Promise { + return this.appDB.getRecords(this.SITES_TABLE, {loggedOut : 0}).then((sites) => { + return sites.map((site) => { + return site.id; + }); + }); + } + /** * Get the list of IDs of sites stored. * From b2fc4ae0952f10909a53e91988563f5dba56c807 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Mon, 25 Feb 2019 17:13:13 +0100 Subject: [PATCH 097/191] MOBILE-1973 folder: Change folder icon --- .../mod/folder/components/index/addon-mod-folder-index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 f9ccdcf31..62cda2e8e 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 @@ -18,8 +18,8 @@ - -

{{file.name}}

+ +

{{file.name}}

From 05960512d6253a9dc3b04627afa9f27a3fda49de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 22 Feb 2019 11:35:33 +0100 Subject: [PATCH 098/191] MOBILE-2886 scorm: Hide bottom bar when showing as a popup --- src/addon/mod/scorm/pages/player/player.ts | 6 +++++- src/components/ion-tabs/core-ion-tabs.html | 2 +- src/components/ion-tabs/ion-tabs.ts | 17 +++++++++++++++++ 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/addon/mod/scorm/pages/player/player.ts b/src/addon/mod/scorm/pages/player/player.ts index 8fed9f121..bbe6b5314 100644 --- a/src/addon/mod/scorm/pages/player/player.ts +++ b/src/addon/mod/scorm/pages/player/player.ts @@ -19,6 +19,7 @@ import { CoreSitesProvider } from '@providers/sites'; import { CoreSyncProvider } from '@providers/sync'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreTimeUtilsProvider } from '@providers/utils/time'; +import { CoreIonTabsComponent } from '@components/ion-tabs/ion-tabs'; import { AddonModScormProvider, AddonModScormAttemptCountResult } from '../../providers/scorm'; import { AddonModScormHelperProvider } from '../../providers/helper'; import { AddonModScormSyncProvider } from '../../providers/scorm-sync'; @@ -68,7 +69,7 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy { protected sitesProvider: CoreSitesProvider, protected syncProvider: CoreSyncProvider, protected domUtils: CoreDomUtilsProvider, protected timeUtils: CoreTimeUtilsProvider, protected scormProvider: AddonModScormProvider, protected scormHelper: AddonModScormHelperProvider, - protected scormSyncProvider: AddonModScormSyncProvider) { + protected scormSyncProvider: AddonModScormSyncProvider, protected tabs: CoreIonTabsComponent) { this.scorm = navParams.get('scorm') || {}; this.mode = navParams.get('mode') || AddonModScormProvider.MODENORMAL; @@ -92,6 +93,8 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy { this.showToc = this.scormProvider.displayTocInPlayer(this.scorm); if (this.scorm.popup) { + this.tabs.changeVisibility(false); + // If we receive a value <= 100 we need to assume it's a percentage. if (this.scorm.width <= 100) { this.scorm.width = this.scorm.width + '%'; @@ -448,5 +451,6 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy { // Unblock the SCORM so it can be synced. this.syncProvider.unblockOperation(AddonModScormProvider.COMPONENT, this.scorm.id, 'player'); + this.tabs.changeVisibility(true); } } diff --git a/src/components/ion-tabs/core-ion-tabs.html b/src/components/ion-tabs/core-ion-tabs.html index 8a94426df..c3e62e9e6 100644 --- a/src/components/ion-tabs/core-ion-tabs.html +++ b/src/components/ion-tabs/core-ion-tabs.html @@ -1,4 +1,4 @@ -
+
diff --git a/src/components/ion-tabs/ion-tabs.ts b/src/components/ion-tabs/ion-tabs.ts index 4a6eaccf8..deeb76a53 100644 --- a/src/components/ion-tabs/ion-tabs.ts +++ b/src/components/ion-tabs/ion-tabs.ts @@ -53,6 +53,7 @@ export class CoreIonTabsComponent extends Tabs implements OnDestroy { @ViewChild('originalTabs') originalTabsRef: ElementRef; _loaded: boolean; // Whether tabs have been loaded. + hidden = false; // Whether to show/hide tabs. /** * List of tabs that haven't been initialized yet. This is required because IonTab calls add() on the constructor, @@ -325,6 +326,22 @@ export class CoreIonTabsComponent extends Tabs implements OnDestroy { } } + /** + * Change tabs visibility to show/hide them from the view. + * + * @param {boolean} visible If show or hide the tabs. + */ + changeVisibility(visible: boolean): void { + if (this.hidden == visible) { + // Change needed. + this.hidden = !visible; + + setTimeout(() => { + this.viewCtrl.getContent().resize(); + }); + } + } + /** * Component destroyed. */ From f868eaf53ce9b231d950962f3be544314aaeb3d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 19 Feb 2019 10:22:48 +0100 Subject: [PATCH 099/191] MOBILE-2887 blog: Implement link handlers --- scripts/langindex.json | 1 - .../badges/providers/mybadges-link-handler.ts | 2 +- src/addon/blog/blog.module.ts | 9 ++- src/addon/blog/components/entries/entries.ts | 15 ++++ src/addon/blog/pages/entries/entries.html | 2 +- src/addon/blog/pages/entries/entries.ts | 8 +- .../blog/providers/index-link-handler.ts | 76 +++++++++++++++++++ 7 files changed, 107 insertions(+), 6 deletions(-) create mode 100644 src/addon/blog/providers/index-link-handler.ts diff --git a/scripts/langindex.json b/scripts/langindex.json index c9486501c..92ad0f979 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -75,7 +75,6 @@ "addon.calendar.eventstarttime": "calendar", "addon.calendar.gotoactivity": "calendar", "addon.calendar.noevents": "local_moodlemobileapp", - "addon.calendar.notifications": "local_moodlemobileapp", "addon.calendar.reminders": "local_moodlemobileapp", "addon.calendar.setnewreminder": "local_moodlemobileapp", "addon.calendar.typecategory": "calendar", diff --git a/src/addon/badges/providers/mybadges-link-handler.ts b/src/addon/badges/providers/mybadges-link-handler.ts index 8c58e384d..8a36cbbc0 100644 --- a/src/addon/badges/providers/mybadges-link-handler.ts +++ b/src/addon/badges/providers/mybadges-link-handler.ts @@ -19,7 +19,7 @@ import { CoreLoginHelperProvider } from '@core/login/providers/helper'; import { AddonBadgesProvider } from './badges'; /** - * Handler to treat links to user participants page. + * Handler to treat links to user badges page. */ @Injectable() export class AddonBadgesMyBadgesLinkHandler extends CoreContentLinksHandlerBase { diff --git a/src/addon/blog/blog.module.ts b/src/addon/blog/blog.module.ts index 275a16873..f31372745 100644 --- a/src/addon/blog/blog.module.ts +++ b/src/addon/blog/blog.module.ts @@ -16,11 +16,13 @@ import { NgModule } from '@angular/core'; import { CoreMainMenuDelegate } from '@core/mainmenu/providers/delegate'; import { CoreUserDelegate } from '@core/user/providers/user-delegate'; import { CoreCourseOptionsDelegate } from '@core/course/providers/options-delegate'; +import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate'; import { AddonBlogProvider } from './providers/blog'; import { AddonBlogMainMenuHandler } from './providers/mainmenu-handler'; import { AddonBlogUserHandler } from './providers/user-handler'; import { AddonBlogCourseOptionHandler } from './providers/course-option-handler'; import { AddonBlogComponentsModule } from './components/components.module'; +import { AddonBlogIndexLinkHandler } from './providers/index-link-handler'; @NgModule({ declarations: [ @@ -32,15 +34,18 @@ import { AddonBlogComponentsModule } from './components/components.module'; AddonBlogProvider, AddonBlogMainMenuHandler, AddonBlogUserHandler, - AddonBlogCourseOptionHandler + AddonBlogCourseOptionHandler, + AddonBlogIndexLinkHandler ] }) export class AddonBlogModule { constructor(mainMenuDelegate: CoreMainMenuDelegate, menuHandler: AddonBlogMainMenuHandler, userHandler: AddonBlogUserHandler, userDelegate: CoreUserDelegate, - courseOptionHandler: AddonBlogCourseOptionHandler, courseOptionsDelegate: CoreCourseOptionsDelegate) { + courseOptionHandler: AddonBlogCourseOptionHandler, courseOptionsDelegate: CoreCourseOptionsDelegate, + linkHandler: AddonBlogIndexLinkHandler, contentLinksDelegate: CoreContentLinksDelegate) { mainMenuDelegate.registerHandler(menuHandler); userDelegate.registerHandler(userHandler); courseOptionsDelegate.registerHandler(courseOptionHandler); + contentLinksDelegate.registerHandler(linkHandler); } } diff --git a/src/addon/blog/components/entries/entries.ts b/src/addon/blog/components/entries/entries.ts index f946264a8..282c6ef87 100644 --- a/src/addon/blog/components/entries/entries.ts +++ b/src/addon/blog/components/entries/entries.ts @@ -30,6 +30,9 @@ export class AddonBlogEntriesComponent implements OnInit { @Input() userId?: number; @Input() courseId?: number; @Input() cmId?: number; + @Input() entryId?: number; + @Input() groupId?: number; + @Input() tagId?: number; protected filter = {}; protected pageLoaded = 0; @@ -66,6 +69,18 @@ export class AddonBlogEntriesComponent implements OnInit { this.filter['cmid'] = this.cmId; } + if (this.entryId) { + this.filter['entryid'] = this.entryId; + } + + if (this.groupId) { + this.filter['groupid'] = this.groupId; + } + + if (this.tagId) { + this.filter['tagid'] = this.tagId; + } + this.fetchEntries().then(() => { this.blogProvider.logView(this.filter).catch(() => { // Ignore errors. diff --git a/src/addon/blog/pages/entries/entries.html b/src/addon/blog/pages/entries/entries.html index ff5f1b240..e5a12aba8 100644 --- a/src/addon/blog/pages/entries/entries.html +++ b/src/addon/blog/pages/entries/entries.html @@ -4,4 +4,4 @@ - + diff --git a/src/addon/blog/pages/entries/entries.ts b/src/addon/blog/pages/entries/entries.ts index 1a268268f..220de15df 100644 --- a/src/addon/blog/pages/entries/entries.ts +++ b/src/addon/blog/pages/entries/entries.ts @@ -27,14 +27,20 @@ export class AddonBlogEntriesPage { userId: number; courseId: number; cmId: number; + entryId: number; + groupId: number; + tagId: number; title: string; constructor(params: NavParams) { this.userId = params.get('userId'); this.courseId = params.get('courseId'); this.cmId = params.get('cmId'); + this.entryId = params.get('entryId'); + this.groupId = params.get('groupId'); + this.tagId = params.get('tagId'); - if (!this.userId && !this.courseId && !this.cmId) { + if (!this.userId && !this.courseId && !this.cmId && !this.entryId && !this.groupId && !this.tagId) { this.title = 'addon.blog.siteblogheading'; } else { this.title = 'addon.blog.blogentries'; diff --git a/src/addon/blog/providers/index-link-handler.ts b/src/addon/blog/providers/index-link-handler.ts new file mode 100644 index 000000000..176aec76e --- /dev/null +++ b/src/addon/blog/providers/index-link-handler.ts @@ -0,0 +1,76 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler'; +import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate'; +import { CoreLoginHelperProvider } from '@core/login/providers/helper'; +import { AddonBlogProvider } from './blog'; + +/** + * Handler to treat links to blog page. + */ +@Injectable() +export class AddonBlogIndexLinkHandler extends CoreContentLinksHandlerBase { + name = 'AddonBlogIndexLinkHandler'; + featureName = 'CoreUserDelegate_AddonBlog'; + pattern = /\/blog\/index\.php/; + + constructor(private blogProvider: AddonBlogProvider, private loginHelper: CoreLoginHelperProvider) { + super(); + } + + /** + * Get the list of actions for a link (url). + * + * @param {string[]} siteIds List of sites the URL belongs to. + * @param {string} url The URL to treat. + * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param {number} [courseId] Course ID related to the URL. Optional but recommended. + * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. + */ + getActions(siteIds: string[], url: string, params: any, courseId?: number): + CoreContentLinksAction[] | Promise { + const pageParams: any = {}; + + params.userid ? pageParams['userId'] = parseInt(params.userid, 10) : null; + params.modid ? pageParams['cmId'] = parseInt(params.modid, 10) : null; + params.courseid ? pageParams['courseId'] = parseInt(params.courseid, 10) : null; + params.entryid ? pageParams['entryId'] = parseInt(params.entryid, 10) : null; + params.groupid ? pageParams['groupId'] = parseInt(params.groupid, 10) : null; + params.tagid ? pageParams['tagId'] = parseInt(params.tagid, 10) : null; + + return [{ + action: (siteId, navCtrl?): void => { + // Always use redirect to make it the new history root (to avoid "loops" in history). + this.loginHelper.redirect('AddonBlogEntriesPage', pageParams, siteId); + } + }]; + } + + /** + * Check if the handler is enabled for a certain site (site + user) and a URL. + * If not defined, defaults to true. + * + * @param {string} siteId The site ID. + * @param {string} url The URL to treat. + * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} + * @param {number} [courseId] Course ID related to the URL. Optional but recommended. + * @return {boolean|Promise} Whether the handler is enabled for the URL and site. + */ + isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise { + + return this.blogProvider.isPluginEnabled(siteId); + } +} From 9381d3f8dd277d0e2f4c116665d4a525d8ba2d2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 22 Feb 2019 14:05:17 +0100 Subject: [PATCH 100/191] MOBILE-2891 forum: Scroll to the reply form --- src/addon/mod/forum/components/post/post.ts | 23 ++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/addon/mod/forum/components/post/post.ts b/src/addon/mod/forum/components/post/post.ts index 39c84c2ea..92ea42b66 100644 --- a/src/addon/mod/forum/components/post/post.ts +++ b/src/addon/mod/forum/components/post/post.ts @@ -14,7 +14,7 @@ import { Component, Input, Output, Optional, EventEmitter, OnInit, OnDestroy } from '@angular/core'; import { FormControl } from '@angular/forms'; -import { NavController } from 'ionic-angular'; +import { NavController, Content } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; @@ -34,7 +34,6 @@ import { AddonModForumSyncProvider } from '../../providers/sync'; templateUrl: 'addon-mod-forum-post.html', }) export class AddonModForumPostComponent implements OnInit, OnDestroy { - @Input() post: any; // Post. @Input() courseId: number; // Post's course ID. @Input() discussionId: number; // Post's' discussion ID. @@ -64,7 +63,8 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy { private forumHelper: AddonModForumHelperProvider, private forumOffline: AddonModForumOfflineProvider, private forumSync: AddonModForumSyncProvider, - @Optional() private svComponent: CoreSplitViewComponent) { + @Optional() private svComponent: CoreSplitViewComponent, + @Optional() private content: Content) { this.onPostChange = new EventEmitter(); } @@ -122,9 +122,18 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy { // User is editing a post, data needs to be resetted. Ask confirm if there is unsaved data. this.confirmDiscard().then(() => { this.setReplyData(this.post.id); + + if (this.content) { + setTimeout(() => { + this.content.resize(); + this.domUtils.scrollToElementBySelector(this.content, '#addon-forum-reply-edit-form-' + this.uniqueId); + }); + } }).catch(() => { // Cancelled. }); + + return; } else if (!this.replyData.replyingTo) { // User isn't replying, it's a brand new reply. Initialize the data. this.setReplyData(this.post.id); @@ -133,6 +142,14 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy { this.replyData.replyingTo = this.post.id; this.messageControl.setValue(this.replyData.message); } + + if (this.content) { + setTimeout(() => { + this.content.resize(); + this.domUtils.scrollToElementBySelector(this.content, '#addon-forum-reply-edit-form-' + this.uniqueId); + }); + } + } /** From 30f8efd14a8b751a7191e0d66a914dcdcf92b260 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 31 Jan 2019 15:45:45 +0100 Subject: [PATCH 101/191] MOBILE-2831 core: Update push plugin and add new files --- GoogleService-Info.plist | 40 +++++++++++++++++ config.xml | 7 ++- google-services.json | 45 +++++++++++++++++++ package-lock.json | 29 +++++++++++- package.json | 7 ++- .../providers/pushnotifications.ts | 1 - src/config.json | 1 - 7 files changed, 122 insertions(+), 8 deletions(-) create mode 100644 GoogleService-Info.plist create mode 100644 google-services.json diff --git a/GoogleService-Info.plist b/GoogleService-Info.plist new file mode 100644 index 000000000..b80564cf9 --- /dev/null +++ b/GoogleService-Info.plist @@ -0,0 +1,40 @@ + + + + + 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 + + \ No newline at end of file diff --git a/config.xml b/config.xml index cdc0b199d..b39ed9cd6 100644 --- a/config.xml +++ b/config.xml @@ -41,6 +41,7 @@ + @@ -77,6 +78,7 @@ + @@ -137,8 +139,9 @@ - - + + + diff --git a/google-services.json b/google-services.json new file mode 100644 index 000000000..ff50800df --- /dev/null +++ b/google-services.json @@ -0,0 +1,45 @@ +{ + "project_info": { + "project_number": "694767596569", + "firebase_url": "https://moodlemobile-push.firebaseio.com", + "project_id": "moodlemobile-push", + "storage_bucket": "moodlemobile-push.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:694767596569:android:a4cdad4d168c9d1a", + "android_client_info": { + "package_name": "com.moodle.moodlemobile" + } + }, + "oauth_client": [ + { + "client_id": "694767596569-icveqqa2n56oh44l6ev1dr2oh67nh8il.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCb2zogu0P_aZ2LNgdwzshWExITPKTXJyk" + }, + { + "current_key": "AIzaSyDRT1HwT0gSsTty0whOVtoNKAh8SPrJXLE" + } + ], + "services": { + "analytics_service": { + "status": 1 + }, + "appinvite_service": { + "status": 1, + "other_platform_oauth_client": [] + }, + "ads_service": { + "status": 2 + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a72c22b50..0f114980d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1716,6 +1716,11 @@ } } }, + "babel-plugin-add-header-comment": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/babel-plugin-add-header-comment/-/babel-plugin-add-header-comment-1.0.3.tgz", + "integrity": "sha1-URxJAQYmQNWkgLSsPt1pRBlYUOw=" + }, "bach": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", @@ -3220,6 +3225,11 @@ "resolved": "https://registry.npmjs.org/cordova-sqlite-storage-dependencies/-/cordova-sqlite-storage-dependencies-1.2.1.tgz", "integrity": "sha512-4ihQApBGVKR1QZ4oOSGctKFfthtCfiWMTcIIfxe97vKxlvGr9NyXOvYG9vXU9S7yVR7Ua+Rj47hkE7pQIKvQTg==" }, + "cordova-support-google-services": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/cordova-support-google-services/-/cordova-support-google-services-1.1.0.tgz", + "integrity": "sha1-RjTFIgD4cGDReV6yhw6ZRC12Lm0=" + }, "core-js": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.3.0.tgz", @@ -6314,6 +6324,11 @@ "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", "dev": true }, + "install": { + "version": "0.8.9", + "resolved": "https://registry.npmjs.org/install/-/install-0.8.9.tgz", + "integrity": "sha1-n0tcDRhR74cunfheT3Fi1OXc2+0=" + }, "interpret": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", @@ -8554,9 +8569,19 @@ "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", "dev": true }, + "phonegap-plugin-multidex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/phonegap-plugin-multidex/-/phonegap-plugin-multidex-1.0.0.tgz", + "integrity": "sha512-1wvc3iQOQpEBaQbXgLxA2JUiLSQ2azdF/bF29ghXDiQJWSpQ1BF8gSuqttM8WZoj081Ps8OKL0gYxdDBkFNPqA==" + }, "phonegap-plugin-push": { - "version": "git+https://github.com/moodlemobile/phonegap-plugin-push.git#cf2ed2075d9d2d58a4c4f79543f669ed6366c148", - "from": "git+https://github.com/moodlemobile/phonegap-plugin-push.git#moodle" + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/phonegap-plugin-push/-/phonegap-plugin-push-2.2.3.tgz", + "integrity": "sha512-5mjT0G1vfRhXVnZFLwjfzcFwYjVRMibgYDCfYvEujGsP8YwwrIIzcf+xBYAjQV/W2JCjzuNaYd7xJ0yVQaPeig==", + "requires": { + "babel-plugin-add-header-comment": "^1.0.3", + "install": "^0.8.2" + } }, "pify": { "version": "2.3.0", diff --git a/package.json b/package.json index 87f697455..c221f3a8e 100644 --- a/package.json +++ b/package.json @@ -102,6 +102,7 @@ "cordova-plugin-whitelist": "^1.3.3", "cordova-plugin-zip": "^3.1.0", "cordova-sqlite-storage": "^2.6.0", + "cordova-support-google-services": "^1.1.0", "es6-promise-plugin": "^4.2.2", "font-awesome": "^4.7.0", "ionic-angular": "3.9.3", @@ -109,7 +110,8 @@ "jszip": "^3.1.5", "moment": "^2.22.2", "nl.kingsquare.cordova.background-audio": "^1.0.1", - "phonegap-plugin-push": "git+https://github.com/moodlemobile/phonegap-plugin-push.git#moodle", + "phonegap-plugin-multidex": "^1.0.0", + "phonegap-plugin-push": "^2.2.3", "promise.prototype.finally": "^3.1.0", "rxjs": "^5.5.11", "sw-toolbox": "^3.6.0", @@ -169,7 +171,8 @@ "cordova-sqlite-storage": {}, "nl.kingsquare.cordova.background-audio": {}, "phonegap-plugin-push": { - "SENDER_ID": "694767596569" + "ANDROID_SUPPORT_V13_VERSION": "27.+", + "FCM_VERSION": "11.6.2" } } }, diff --git a/src/addon/pushnotifications/providers/pushnotifications.ts b/src/addon/pushnotifications/providers/pushnotifications.ts index fce797a79..6cd40d65b 100644 --- a/src/addon/pushnotifications/providers/pushnotifications.ts +++ b/src/addon/pushnotifications/providers/pushnotifications.ts @@ -94,7 +94,6 @@ export class AddonPushNotificationsProvider { return this.configProvider.get(CoreConstants.SETTINGS_NOTIFICATION_SOUND, true).then((soundEnabled) => { return { android: { - senderID: CoreConfigConstants.gcmpn, sound: !!soundEnabled, icon: 'smallicon' }, diff --git a/src/config.json b/src/config.json index 56244a5a5..331a72b6d 100644 --- a/src/config.json +++ b/src/config.json @@ -64,7 +64,6 @@ "password": "moodle" } }, - "gcmpn": "694767596569", "customurlscheme": "moodlemobile", "siteurl": "", "multisitesdisplay": "", From cca7dee8eb76d3bca31831fe8f72836bafc1b943 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 1 Feb 2019 15:35:13 +0100 Subject: [PATCH 102/191] MOBILE-2831 push: Handle notification channels --- scripts/langindex.json | 2 + src/addon/calendar/lang/en.json | 1 + .../providers/pushnotifications.ts | 45 ++++++++++++++----- src/assets/lang/en.json | 2 + src/lang/en.json | 1 + src/providers/local-notifications.ts | 26 ++++++++++- 6 files changed, 65 insertions(+), 12 deletions(-) diff --git a/scripts/langindex.json b/scripts/langindex.json index 92ad0f979..15958c458 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -68,6 +68,7 @@ "addon.blog.siteblogheading": "blog", "addon.calendar.calendar": "calendar", "addon.calendar.calendarevents": "local_moodlemobileapp", + "addon.calendar.calendarreminders": "local_moodlemobileapp", "addon.calendar.defaultnotificationtime": "local_moodlemobileapp", "addon.calendar.errorloadevent": "local_moodlemobileapp", "addon.calendar.errorloadevents": "local_moodlemobileapp", @@ -1506,6 +1507,7 @@ "core.maxsizeandattachments": "moodle", "core.min": "moodle", "core.mins": "moodle", + "core.misc": "admin", "core.mod_assign": "assign/pluginname", "core.mod_assignment": "assignment/pluginname", "core.mod_book": "book/pluginname", diff --git a/src/addon/calendar/lang/en.json b/src/addon/calendar/lang/en.json index 1fc6d2e34..6ccb04caa 100644 --- a/src/addon/calendar/lang/en.json +++ b/src/addon/calendar/lang/en.json @@ -1,6 +1,7 @@ { "calendar": "Calendar", "calendarevents": "Calendar events", + "calendarreminders": "Calendar reminders", "defaultnotificationtime": "Default notification time", "errorloadevent": "Error loading event.", "errorloadevents": "Error loading events.", diff --git a/src/addon/pushnotifications/providers/pushnotifications.ts b/src/addon/pushnotifications/providers/pushnotifications.ts index 6cd40d65b..63944830c 100644 --- a/src/addon/pushnotifications/providers/pushnotifications.ts +++ b/src/addon/pushnotifications/providers/pushnotifications.ts @@ -13,9 +13,11 @@ // limitations under the License. import { Injectable, NgZone } from '@angular/core'; +import { Platform } from 'ionic-angular'; import { Badge } from '@ionic-native/badge'; import { Push, PushObject, PushOptions } from '@ionic-native/push'; import { Device } from '@ionic-native/device'; +import { TranslateService } from '@ngx-translate/core'; import { CoreAppProvider } from '@providers/app'; import { CoreInitDelegate } from '@providers/init'; import { CoreLoggerProvider } from '@providers/logger'; @@ -67,10 +69,21 @@ export class AddonPushNotificationsProvider { protected pushNotificationsDelegate: AddonPushNotificationsDelegate, protected sitesProvider: CoreSitesProvider, private badge: Badge, private localNotificationsProvider: CoreLocalNotificationsProvider, private utils: CoreUtilsProvider, private textUtils: CoreTextUtilsProvider, private push: Push, - private configProvider: CoreConfigProvider, private device: Device, private zone: NgZone) { + private configProvider: CoreConfigProvider, private device: Device, private zone: NgZone, + private translate: TranslateService, private platform: Platform) { this.logger = logger.getInstance('AddonPushNotificationsProvider'); this.appDB = appProvider.getDB(); this.appDB.createTablesFromSchema(this.tablesSchema); + + platform.ready().then(() => { + // Create the default channel. + this.createDefaultChannel(); + + translate.onLangChange.subscribe((event: any) => { + // Update the channel name. + this.createDefaultChannel(); + }); + }); } /** @@ -85,6 +98,25 @@ export class AddonPushNotificationsProvider { }); } + /** + * Create the default push channel. It is used to change the name. + * + * @return {Promise} Promise resolved when done. + */ + protected createDefaultChannel(): Promise { + if (!this.platform.is('android')) { + return Promise.resolve(); + } + + return this.push.createChannel({ + id: 'PushPluginChannel', + description: this.translate.instant('core.misc'), + importance: 4 + }).catch((error) => { + this.logger.error('Error changing push channel name', error); + }); + } + /** * Returns options for push notifications based on device. * @@ -155,13 +187,7 @@ export class AddonPushNotificationsProvider { if (this.localNotificationsProvider.isAvailable()) { const localNotif: ILocalNotification = { id: 1, - trigger: { - at: new Date() - }, - data: { - notif: data.notif, - site: data.site - }, + data: data, title: '', text: '' }, @@ -193,9 +219,6 @@ export class AddonPushNotificationsProvider { }); } else { // The notification was clicked. - // For compatibility with old push plugin implementation we'll merge all the notification data in a single object. - data.title = notification.title; - data.message = notification.message; this.notificationClicked(data); } }); diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index 0731749b0..1ce4bc648 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -68,6 +68,7 @@ "addon.blog.siteblogheading": "Site blog", "addon.calendar.calendar": "Calendar", "addon.calendar.calendarevents": "Calendar events", + "addon.calendar.calendarreminders": "Calendar reminders", "addon.calendar.defaultnotificationtime": "Default notification time", "addon.calendar.errorloadevent": "Error loading event.", "addon.calendar.errorloadevents": "Error loading events.", @@ -1506,6 +1507,7 @@ "core.maxsizeandattachments": "Maximum size for new files: {{$a.size}}, maximum attachments: {{$a.attachments}}", "core.min": "min", "core.mins": "mins", + "core.misc": "Miscellaneous", "core.mod_assign": "Assignment", "core.mod_assignment": "Assignment 2.2 (Disabled)", "core.mod_book": "Book", diff --git a/src/lang/en.json b/src/lang/en.json index 65c423bd1..7b7086f67 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -126,6 +126,7 @@ "maxsizeandattachments": "Maximum size for new files: {{$a.size}}, maximum attachments: {{$a.attachments}}", "min": "min", "mins": "mins", + "misc": "Miscellaneous", "mod_assign": "Assignment", "mod_assignment": "Assignment 2.2 (Disabled)", "mod_book": "Book", diff --git a/src/providers/local-notifications.ts b/src/providers/local-notifications.ts index 40cfc3926..03b2995b7 100644 --- a/src/providers/local-notifications.ts +++ b/src/providers/local-notifications.ts @@ -15,6 +15,7 @@ import { Injectable } from '@angular/core'; 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 { CoreConfigProvider } from './config'; @@ -104,7 +105,7 @@ export class CoreLocalNotificationsProvider { constructor(logger: CoreLoggerProvider, private localNotifications: LocalNotifications, private platform: Platform, private appProvider: CoreAppProvider, private utils: CoreUtilsProvider, private configProvider: CoreConfigProvider, private textUtils: CoreTextUtilsProvider, private translate: TranslateService, private alertCtrl: AlertController, - eventsProvider: CoreEventsProvider) { + eventsProvider: CoreEventsProvider, private push: Push) { this.logger = logger.getInstance('CoreLocalNotificationsProvider'); this.appDB = appProvider.getDB(); @@ -122,6 +123,14 @@ export class CoreLocalNotificationsProvider { this.notifyClick(notification.data); } }); + + // Create the default channel for local notifications. + this.createDefaultChannel(); + + translate.onLangChange.subscribe((event: any) => { + // Update the channel name. + this.createDefaultChannel(); + }); }); eventsProvider.on(CoreEventsProvider.SITE_DELETED, (site) => { @@ -176,6 +185,21 @@ export class CoreLocalNotificationsProvider { }); } + /** + * Create the default channel. It is used to change the name. + * + * @return {Promise} Promise resolved when done. + */ + protected createDefaultChannel(): Promise { + return this.push.createChannel({ + id: 'default-channel-id', + description: this.translate.instant('addon.calendar.calendarreminders'), + importance: 4 + }).catch((error) => { + this.logger.error('Error changing channel name', error); + }); + } + /** * Get a code to create unique notifications. If there's no code assigned, create a new one. * From f3e72d283aa23f69309a4e7c48c0b155675b8807 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 14 Feb 2019 10:37:23 +0100 Subject: [PATCH 103/191] MOBILE-2831 core: Use beta 3 of local-notifications plugin --- config.xml | 2 +- package-lock.json | 12 ++++++------ package.json | 2 +- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/config.xml b/config.xml index b39ed9cd6..265b5a0a5 100644 --- a/config.xml +++ b/config.xml @@ -129,7 +129,7 @@ - + diff --git a/package-lock.json b/package-lock.json index 0f114980d..8b112c0e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -161,9 +161,9 @@ "integrity": "sha512-2BHO1bV4mehWZNfdsWQ/uojxYFNvk4I6u0KYnNb61RiJRY83joCEw3oFkOMRGLZthPf6TN1cueZUIAGMHXA3nA==" }, "@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", @@ -3173,9 +3173,9 @@ "integrity": "sha512-6ucQ6FdlLdBm8kJfFnzozmBTjru/0xekHP/dAhjoCZggkGRlgs8TsUJFkxa/bV+qi7Dlo50JjmpE4UMWAO+aOQ==" }, "cordova-plugin-local-notification": { - "version": "0.9.0-beta.2", - "resolved": "https://registry.npmjs.org/cordova-plugin-local-notification/-/cordova-plugin-local-notification-0.9.0-beta.2.tgz", - "integrity": "sha512-63n77K1pt8dnbWnNR8QWETi9Glezi1bvNHvHWmGNIOv0xCb0phZnm+Ku49BQ+omwe8Z5voMvrA4I03SYPpv38w==" + "version": "0.9.0-beta.3", + "resolved": "https://registry.npmjs.org/cordova-plugin-local-notification/-/cordova-plugin-local-notification-0.9.0-beta.3.tgz", + "integrity": "sha512-L3Z1velxrkm9nHFcvLnMgBPZjKFt6hwM6hn1lA+JFwIR26Yw6UF72z+/lRMBclAcOxBIDYCqeaLgvezmajjuEg==" }, "cordova-plugin-media-capture": { "version": "3.0.2", diff --git a/package.json b/package.json index c221f3a8e..4ffaa3c37 100644 --- a/package.json +++ b/package.json @@ -93,7 +93,7 @@ "cordova-plugin-globalization": "^1.11.0", "cordova-plugin-inappbrowser": "^3.0.0", "cordova-plugin-ionic-keyboard": "^2.1.3", - "cordova-plugin-local-notification": "^0.9.0-beta.2", + "cordova-plugin-local-notification": "^0.9.0-beta.3", "cordova-plugin-media-capture": "^3.0.2", "cordova-plugin-network-information": "^2.0.1", "cordova-plugin-screen-orientation": "^3.0.1", From 10f54e714938ba841221e802cf4417201f2ef07a Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 14 Feb 2019 10:34:44 +0100 Subject: [PATCH 104/191] MOBILE-2831 core: Fix local notifications in iOS --- src/core/emulator/providers/local-notifications.ts | 11 ++--------- src/providers/local-notifications.ts | 9 ++++++++- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/core/emulator/providers/local-notifications.ts b/src/core/emulator/providers/local-notifications.ts index f8e4f0f65..bcf07ca6b 100644 --- a/src/core/emulator/providers/local-notifications.ts +++ b/src/core/emulator/providers/local-notifications.ts @@ -939,6 +939,8 @@ export class LocalNotificationsMock extends LocalNotifications { /** * Schedules or updates a single or multiple notifications. + * We only support using the "at" property to trigger the notification. Other properties like "in" or "every" + * aren't supported yet. * * @param {ILocalNotification | Array} [options] Notification or notifications. * @param {string} [eventName] Name of the event: schedule or update. @@ -978,15 +980,6 @@ export class LocalNotificationsMock extends LocalNotifications { // Launch the trigger event. this.fireEvent('trigger', notification); - - if (notification.trigger.every && this.scheduled[notification.id] && - !this.scheduled[notification.id].interval) { - - const interval = this.parseInterval(notification.trigger.every); - if (interval > 0) { - this.scheduled[notification.id].interval = setInterval(trigger, interval); - } - } }; this.scheduled[notification.id].timeout = setTimeout(trigger, toTriggerTime); diff --git a/src/providers/local-notifications.ts b/src/providers/local-notifications.ts index 03b2995b7..6c6b7df7c 100644 --- a/src/providers/local-notifications.ts +++ b/src/providers/local-notifications.ts @@ -191,6 +191,10 @@ export class CoreLocalNotificationsProvider { * @return {Promise} Promise resolved when done. */ protected createDefaultChannel(): Promise { + if (!this.platform.is('android')) { + return Promise.resolve(); + } + return this.push.createChannel({ id: 'default-channel-id', description: this.translate.instant('addon.calendar.calendarreminders'), @@ -293,7 +297,8 @@ export class CoreLocalNotificationsProvider { isAvailable(): boolean { const win = window; - return this.appProvider.isDesktop() || !!(win.plugin && win.plugin.notification && win.plugin.notification.local); + return this.appProvider.isDesktop() || !!(win.cordova && win.cordova.plugins && win.cordova.plugins.notification && + win.cordova.plugins.notification.local); } /** @@ -506,6 +511,8 @@ export class CoreLocalNotificationsProvider { delete notification.sound; // Use default value. } + notification.foreground = true; + // Remove from triggered, since the notification could be in there with a different time. this.removeTriggered(notification.id); this.localNotifications.schedule(notification); From 75a273214175209f495fc20517d0f8bd4a8c3ec1 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 15 Feb 2019 13:54:29 +0100 Subject: [PATCH 105/191] MOBILE-2831 push: Add fcm suffix to platform --- src/addon/pushnotifications/providers/pushnotifications.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/addon/pushnotifications/providers/pushnotifications.ts b/src/addon/pushnotifications/providers/pushnotifications.ts index 63944830c..46be2c89e 100644 --- a/src/addon/pushnotifications/providers/pushnotifications.ts +++ b/src/addon/pushnotifications/providers/pushnotifications.ts @@ -403,7 +403,7 @@ export class AddonPushNotificationsProvider { appid: CoreConfigConstants.app_id, name: this.device.manufacturer || '', model: this.device.model, - platform: this.device.platform, + platform: this.device.platform + '-fcm', version: this.device.version, pushid: this.pushID, uuid: this.device.uuid From ceca8cb5c6d53e6000dc6a4b5317734e13bf86ba Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Mon, 25 Feb 2019 11:10:01 +0100 Subject: [PATCH 106/191] MOBILE-2850 choice: Prefetch data after sync choice --- .../mod/choice/components/index/index.ts | 29 ++++++-- src/addon/mod/choice/providers/choice.ts | 64 ++++++++++++------ .../mod/choice/providers/prefetch-handler.ts | 9 ++- src/addon/mod/choice/providers/sync.ts | 24 +++---- src/addon/mod/scorm/providers/scorm-sync.ts | 37 ++-------- src/classes/base-sync.ts | 3 - src/core/course/classes/activity-sync.ts | 67 +++++++++++++++++++ .../course/classes/main-resource-component.ts | 10 +++ src/core/course/providers/helper.ts | 1 + 9 files changed, 167 insertions(+), 77 deletions(-) create mode 100644 src/core/course/classes/activity-sync.ts diff --git a/src/addon/mod/choice/components/index/index.ts b/src/addon/mod/choice/components/index/index.ts index fe583dd04..fb2243153 100644 --- a/src/addon/mod/choice/components/index/index.ts +++ b/src/addon/mod/choice/components/index/index.ts @@ -352,14 +352,13 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo } const modal = this.domUtils.showModalLoading('core.sending', true); - this.choiceProvider.submitResponse(this.choice.id, this.choice.name, this.courseId, responses).then(() => { + this.choiceProvider.submitResponse(this.choice.id, this.choice.name, this.courseId, responses).then((online) => { // Success! // Check completion since it could be configured to complete once the user answers the choice. this.courseProvider.checkModuleCompletion(this.courseId, this.module.completiondata); this.domUtils.scrollToTop(this.content); - // Let's refresh the data. - return this.refreshContent(false); + return this.dataUpdated(online); }).catch((message) => { this.domUtils.showErrorModalDefault(message, 'addon.mod_choice.cannotsubmit', true); }).finally(() => { @@ -377,7 +376,7 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo this.choiceProvider.deleteResponses(this.choice.id, this.choice.name, this.courseId).then(() => { this.domUtils.scrollToTop(this.content); - // Success! Let's refresh the data. + // Refresh the data. Don't call dataUpdated because deleting an answer doesn't mark the choice as outdated. return this.refreshContent(false); }).catch((message) => { this.domUtils.showErrorModalDefault(message, 'addon.mod_choice.cannotsubmit', true); @@ -389,6 +388,28 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo }); } + /** + * Function to call when some data has changed. It will refresh/prefetch data. + * + * @param {boolean} online Whether the data was sent to server or stored in offline. + * @return {Promise} Promise resolved when done. + */ + protected dataUpdated(online: boolean): Promise { + if (online && this.isPrefetched()) { + // The choice is downloaded, update the data. + return this.choiceSync.prefetchAfterUpdate(this.module, this.courseId).then(() => { + // Update the view. + this.showLoadingAndFetch(false, false); + }).catch(() => { + // Prefetch failed, refresh the data. + return this.refreshContent(false); + }); + } else { + // Not downloaded, refresh the data. + return this.refreshContent(false); + } + } + /** * Performs the sync of the activity. * diff --git a/src/addon/mod/choice/providers/choice.ts b/src/addon/mod/choice/providers/choice.ts index 8845aed49..b94e92b9c 100644 --- a/src/addon/mod/choice/providers/choice.ts +++ b/src/addon/mod/choice/providers/choice.ts @@ -19,6 +19,7 @@ import { CoreAppProvider } from '@providers/app'; import { CoreFilepoolProvider } from '@providers/filepool'; import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { AddonModChoiceOfflineProvider } from './offline'; +import { CoreSiteWSPreSets } from '@classes/site'; /** * Service that provides some features for choices. @@ -68,9 +69,9 @@ export class AddonModChoiceProvider { * @param {number} courseId Course ID the choice belongs to. * @param {number[]} [responses] IDs of the answers. If not defined, delete all the answers of the current user. * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the options are deleted. + * @return {Promise} Promise resolved with boolean: true if response was sent to server, false if stored in device. */ - deleteResponses(choiceId: number, name: string, courseId: number, responses?: number[], siteId?: string): Promise { + deleteResponses(choiceId: number, name: string, courseId: number, responses?: number[], siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); responses = responses || []; @@ -173,20 +174,29 @@ export class AddonModChoiceProvider { * @param {number} courseId Course ID. * @param {string} key Name of the property to check. * @param {any} value Value to search. - * @param {boolean} [forceCache=false] True to always get the value from cache, false otherwise. Default false. + * @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @return {Promise} Promise resolved when the choice is retrieved. */ - protected getChoiceByDataKey(siteId: string, courseId: number, key: string, value: any, forceCache: boolean = false) - : Promise { + protected getChoiceByDataKey(siteId: string, courseId: number, key: string, value: any, forceCache?: boolean, + ignoreCache?: boolean): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { const params = { courseids: [courseId] }; - const preSets = { + const preSets: CoreSiteWSPreSets = { cacheKey: this.getChoiceDataCacheKey(courseId), omitExpires: forceCache }; + if (forceCache) { + preSets.omitExpires = true; + } else if (ignoreCache) { + preSets.getFromCache = false; + preSets.emergencyCache = false; + } + return site.read('mod_choice_get_choices_by_courses', params, preSets).then((response) => { if (response && response.choices) { const currentChoice = response.choices.find((choice) => choice[key] == value); @@ -206,11 +216,12 @@ export class AddonModChoiceProvider { * @param {number} courseId Course ID. * @param {number} cmId Course module ID. * @param {string} [siteId] Site ID. If not defined, current site. - * @param {boolean} [forceCache=false] True to always get the value from cache, false otherwise. Default false. + * @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @return {Promise} Promise resolved when the choice is retrieved. */ - getChoice(courseId: number, cmId: number, siteId?: string, forceCache: boolean = false): Promise { - return this.getChoiceByDataKey(siteId, courseId, 'coursemodule', cmId, forceCache); + getChoice(courseId: number, cmId: number, siteId?: string, forceCache?: boolean, ignoreCache?: boolean): Promise { + return this.getChoiceByDataKey(siteId, courseId, 'coursemodule', cmId, forceCache, ignoreCache); } /** @@ -219,29 +230,36 @@ export class AddonModChoiceProvider { * @param {number} courseId Course ID. * @param {number} choiceId Choice ID. * @param {string} [siteId] Site ID. If not defined, current site. - * @param {boolean} [forceCache=false] True to always get the value from cache, false otherwise. Default false. + * @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @return {Promise} Promise resolved when the choice is retrieved. */ - getChoiceById(courseId: number, choiceId: number, siteId?: string, forceCache: boolean = false): Promise { - return this.getChoiceByDataKey(siteId, courseId, 'id', choiceId, forceCache); + getChoiceById(courseId: number, choiceId: number, siteId?: string, forceCache?: boolean, ignoreCache?: boolean): Promise { + return this.getChoiceByDataKey(siteId, courseId, 'id', choiceId, forceCache, ignoreCache); } /** * Get choice options. * - * @param {number} choiceId Choice ID. - * @param {string} [siteId] Site ID. If not defined, current site. + * @param {number} choiceId Choice ID. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). + * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with choice options. */ - getOptions(choiceId: number, siteId?: string): Promise { + getOptions(choiceId: number, ignoreCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const params = { choiceid: choiceId }; - const preSets = { + const preSets: CoreSiteWSPreSets = { cacheKey: this.getChoiceOptionsCacheKey(choiceId) }; + if (ignoreCache) { + preSets.getFromCache = false; + preSets.emergencyCache = false; + } + return site.read('mod_choice_get_choice_options', params, preSets).then((response) => { if (response.options) { return response.options; @@ -255,19 +273,25 @@ export class AddonModChoiceProvider { /** * Get choice results. * - * @param {number} choiceId Choice ID. - * @param {string} [siteId] Site ID. If not defined, current site. + * @param {number} choiceId Choice ID. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). + * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with choice results. */ - getResults(choiceId: number, siteId?: string): Promise { + getResults(choiceId: number, ignoreCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const params = { choiceid: choiceId }; - const preSets = { + const preSets: CoreSiteWSPreSets = { cacheKey: this.getChoiceResultsCacheKey(choiceId) }; + if (ignoreCache) { + preSets.getFromCache = false; + preSets.emergencyCache = false; + } + return site.read('mod_choice_get_choice_results', params, preSets).then((response) => { if (response.options) { return response.options; diff --git a/src/addon/mod/choice/providers/prefetch-handler.ts b/src/addon/mod/choice/providers/prefetch-handler.ts index fb3680e4c..0b2f74eba 100644 --- a/src/addon/mod/choice/providers/prefetch-handler.ts +++ b/src/addon/mod/choice/providers/prefetch-handler.ts @@ -23,7 +23,6 @@ import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseActivityPrefetchHandlerBase } from '@core/course/classes/activity-prefetch-handler'; import { CoreUserProvider } from '@core/user/providers/user'; import { AddonModChoiceProvider } from './choice'; -import { AddonModChoiceSyncProvider } from './sync'; /** * Handler to prefetch choices. @@ -38,7 +37,7 @@ export class AddonModChoicePrefetchHandler extends CoreCourseActivityPrefetchHan constructor(translate: TranslateService, appProvider: CoreAppProvider, utils: CoreUtilsProvider, courseProvider: CoreCourseProvider, filepoolProvider: CoreFilepoolProvider, sitesProvider: CoreSitesProvider, domUtils: CoreDomUtilsProvider, protected choiceProvider: AddonModChoiceProvider, - protected syncProvider: AddonModChoiceSyncProvider, protected userProvider: CoreUserProvider) { + protected userProvider: CoreUserProvider) { super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils); } @@ -66,12 +65,12 @@ export class AddonModChoicePrefetchHandler extends CoreCourseActivityPrefetchHan * @return {Promise} Promise resolved when done. */ protected prefetchChoice(module: any, courseId: number, single: boolean, siteId: string): Promise { - return this.choiceProvider.getChoice(courseId, module.id, siteId).then((choice) => { + return this.choiceProvider.getChoice(courseId, module.id, siteId, false, true).then((choice) => { const promises = []; // Get the options and results. - promises.push(this.choiceProvider.getOptions(choice.id, siteId)); - promises.push(this.choiceProvider.getResults(choice.id, siteId).then((options) => { + promises.push(this.choiceProvider.getOptions(choice.id, true, siteId)); + promises.push(this.choiceProvider.getResults(choice.id, true, siteId).then((options) => { // If we can see the users that answered, prefetch their profile and avatar. const subPromises = []; options.forEach((option) => { diff --git a/src/addon/mod/choice/providers/sync.ts b/src/addon/mod/choice/providers/sync.ts index be612049a..606ffc385 100644 --- a/src/addon/mod/choice/providers/sync.ts +++ b/src/addon/mod/choice/providers/sync.ts @@ -14,7 +14,6 @@ import { Injectable } from '@angular/core'; import { CoreLoggerProvider } from '@providers/logger'; -import { CoreSyncBaseProvider } from '@classes/base-sync'; import { CoreSitesProvider } from '@providers/sites'; import { CoreAppProvider } from '@providers/app'; import { CoreUtilsProvider } from '@providers/utils/utils'; @@ -26,13 +25,16 @@ import { CoreEventsProvider } from '@providers/events'; import { TranslateService } from '@ngx-translate/core'; import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; +import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate'; +import { CoreCourseActivitySyncBaseProvider } from '@core/course/classes/activity-sync'; import { CoreSyncProvider } from '@providers/sync'; +import { AddonModChoicePrefetchHandler } from './prefetch-handler'; /** * Service to sync choices. */ @Injectable() -export class AddonModChoiceSyncProvider extends CoreSyncBaseProvider { +export class AddonModChoiceSyncProvider extends CoreCourseActivitySyncBaseProvider { static AUTO_SYNCED = 'addon_mod_choice_autom_synced'; protected componentTranslate: string; @@ -42,9 +44,11 @@ export class AddonModChoiceSyncProvider extends CoreSyncBaseProvider { private eventsProvider: CoreEventsProvider, private choiceProvider: AddonModChoiceProvider, translate: TranslateService, private utils: CoreUtilsProvider, protected textUtils: CoreTextUtilsProvider, courseProvider: CoreCourseProvider, syncProvider: CoreSyncProvider, timeUtils: CoreTimeUtilsProvider, - private logHelper: CoreCourseLogHelperProvider) { + private logHelper: CoreCourseLogHelperProvider, prefetchHandler: AddonModChoicePrefetchHandler, + prefetchDelegate: CoreCourseModulePrefetchDelegate) { + super('AddonModChoiceSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, - timeUtils); + timeUtils, prefetchDelegate, prefetchHandler); this.componentTranslate = courseProvider.translateModuleName('choice'); } @@ -195,16 +199,8 @@ export class AddonModChoiceSyncProvider extends CoreSyncBaseProvider { }); }).then(() => { if (courseId) { - const promises = [ - this.choiceProvider.invalidateChoiceData(courseId), - choiceId ? this.choiceProvider.invalidateOptions(choiceId) : Promise.resolve(), - choiceId ? this.choiceProvider.invalidateResults(choiceId) : Promise.resolve(), - ]; - - // Data has been sent to server, update choice data. - return Promise.all(promises).then(() => { - return this.choiceProvider.getChoiceById(courseId, choiceId, siteId); - }).catch(() => { + // Data has been sent to server, prefetch choice if needed. + return this.prefetchAfterUpdate(module, courseId, undefined, siteId).catch(() => { // Ignore errors. }); } diff --git a/src/addon/mod/scorm/providers/scorm-sync.ts b/src/addon/mod/scorm/providers/scorm-sync.ts index 861dce5dc..683cfd450 100644 --- a/src/addon/mod/scorm/providers/scorm-sync.ts +++ b/src/addon/mod/scorm/providers/scorm-sync.ts @@ -25,7 +25,7 @@ import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate'; -import { CoreSyncBaseProvider } from '@classes/base-sync'; +import { CoreCourseActivitySyncBaseProvider } from '@core/course/classes/activity-sync'; import { AddonModScormProvider, AddonModScormAttemptCountResult } from './scorm'; import { AddonModScormOfflineProvider } from './scorm-offline'; import { AddonModScormPrefetchHandler } from './prefetch-handler'; @@ -57,7 +57,7 @@ export interface AddonModScormSyncResult { * Service to sync SCORMs. */ @Injectable() -export class AddonModScormSyncProvider extends CoreSyncBaseProvider { +export class AddonModScormSyncProvider extends CoreCourseActivitySyncBaseProvider { static AUTO_SYNCED = 'addon_mod_scorm_autom_synced'; @@ -67,12 +67,12 @@ export class AddonModScormSyncProvider extends CoreSyncBaseProvider { syncProvider: CoreSyncProvider, textUtils: CoreTextUtilsProvider, translate: TranslateService, private eventsProvider: CoreEventsProvider, timeUtils: CoreTimeUtilsProvider, private scormProvider: AddonModScormProvider, private scormOfflineProvider: AddonModScormOfflineProvider, - private prefetchHandler: AddonModScormPrefetchHandler, private utils: CoreUtilsProvider, - private prefetchDelegate: CoreCourseModulePrefetchDelegate, private courseProvider: CoreCourseProvider, + prefetchHandler: AddonModScormPrefetchHandler, private utils: CoreUtilsProvider, + prefetchDelegate: CoreCourseModulePrefetchDelegate, private courseProvider: CoreCourseProvider, private logHelper: CoreCourseLogHelperProvider) { super('AddonModScormSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, - timeUtils); + timeUtils, prefetchDelegate, prefetchHandler); this.componentTranslate = courseProvider.translateModuleName('scorm'); } @@ -196,7 +196,7 @@ export class AddonModScormSyncProvider extends CoreSyncBaseProvider { if (updated) { // Update downloaded data. promise = this.courseProvider.getModuleBasicInfoByInstance(scorm.id, 'scorm', siteId).then((module) => { - return this.prefetchAfterUpdate(module, scorm.course); + return this.prefetchAfterUpdate(module, scorm.course, undefined, siteId); }).catch(() => { // Ignore errors. }); @@ -361,31 +361,6 @@ export class AddonModScormSyncProvider extends CoreSyncBaseProvider { }); } - /** - * Prefetch data after an update. It won't prefetch the data if the package file was updated. - * - * @param {any} module Module. - * @param {number} courseId Course ID. - * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. - */ - prefetchAfterUpdate(module: any, courseId: number, siteId?: string): Promise { - // Get the module updates to check if the package was updated or not. - return this.prefetchDelegate.getModuleUpdates(module, courseId, true, siteId).then((result) => { - - if (result && result.updates) { - // Only prefetch if the package file hasn't changed. - const fileChanged = !!result.updates.find((entry) => { - return entry.name == 'packagefiles'; - }); - - if (!fileChanged) { - return this.prefetchHandler.download(module, courseId); - } - } - }); - } - /** * Save a snapshot from a synchronization. * diff --git a/src/classes/base-sync.ts b/src/classes/base-sync.ts index 2d1106973..9f62c72b4 100644 --- a/src/classes/base-sync.ts +++ b/src/classes/base-sync.ts @@ -46,9 +46,6 @@ export class CoreSyncBaseProvider { // Store sync promises. protected syncPromises: { [siteId: string]: { [uniqueId: string]: Promise } } = {}; - // List of services that will be injected using injector. - // It's done like this so subclasses don't have to send all the services to the parent in the constructor. - constructor(component: string, loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider, protected appProvider: CoreAppProvider, protected syncProvider: CoreSyncProvider, protected textUtils: CoreTextUtilsProvider, protected translate: TranslateService, diff --git a/src/core/course/classes/activity-sync.ts b/src/core/course/classes/activity-sync.ts new file mode 100644 index 000000000..2ccdb8552 --- /dev/null +++ b/src/core/course/classes/activity-sync.ts @@ -0,0 +1,67 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { TranslateService } from '@ngx-translate/core'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreSyncProvider } from '@providers/sync'; +import { CoreLoggerProvider } from '@providers/logger'; +import { CoreAppProvider } from '@providers/app'; +import { CoreTextUtilsProvider } from '@providers/utils/text'; +import { CoreTimeUtilsProvider } from '@providers/utils/time'; +import { CoreSyncBaseProvider } from '@classes/base-sync'; +import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate'; +import { CoreCourseModulePrefetchHandlerBase } from './module-prefetch-handler'; + +/** + * Base class to create activity sync providers. It provides some common functions. + */ +export class CoreCourseActivitySyncBaseProvider extends CoreSyncBaseProvider { + + constructor(component: string, loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider, + protected appProvider: CoreAppProvider, protected syncProvider: CoreSyncProvider, + protected textUtils: CoreTextUtilsProvider, protected translate: TranslateService, + protected timeUtils: CoreTimeUtilsProvider, protected prefetchDelegate: CoreCourseModulePrefetchDelegate, + protected prefetchHandler: CoreCourseModulePrefetchHandlerBase) { + + super(component, loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, timeUtils); + } + + /** + * Conveniece function to refetch data after an update. + * + * @param {any} module Module. + * @param {number} courseId Course ID. + * @param {RegExp} [regex] RegExp to check if it should download data. Defaults to check files. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when done. + */ + prefetchAfterUpdate(module: any, courseId: number, regex?: RegExp, siteId?: string): Promise { + regex = regex || /^.*files$/; + + // Get the module updates to check if the data was updated or not. + return this.prefetchDelegate.getModuleUpdates(module, courseId, true, siteId).then((result) => { + + if (result && result.updates) { + // Only prefetch if files haven't changed. + const fileChanged = !!result.updates.find((entry) => { + return entry.match(regex); + }); + + if (!fileChanged) { + return this.prefetchHandler.download(module, courseId); + } + } + }); + } +} diff --git a/src/core/course/classes/main-resource-component.ts b/src/core/course/classes/main-resource-component.ts index 8bc986812..42727c13f 100644 --- a/src/core/course/classes/main-resource-component.ts +++ b/src/core/course/classes/main-resource-component.ts @@ -23,6 +23,7 @@ import { CoreCourseModuleMainComponent, CoreCourseModuleDelegate } from '@core/c import { CoreCourseSectionPage } from '@core/course/pages/section/section.ts'; import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; import { AddonBlogProvider } from '@addon/blog/providers/blog'; +import { CoreConstants } from '@core/constants'; /** * Template class to easily create CoreCourseModuleMainComponent of resources (or activities without syncing). @@ -42,6 +43,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, description: string; // Module description. refreshIcon: string; // Refresh icon, normally spinner or refresh. prefetchStatusIcon: string; // Used when calling fillContextMenu. + prefetchStatus: string; // Used when calling fillContextMenu. prefetchText: string; // Used when calling fillContextMenu. size: string; // Used when calling fillContextMenu. @@ -222,6 +224,14 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, this.courseHelper.fillContextMenu(this, this.module, this.courseId, refresh, this.component); } + /** + * Check if the module is prefetched or being prefetched. To make it faster, just use the data calculated by fillContextMenu. + * This means that you need to call fillContextMenu to make this work. + */ + protected isPrefetched(): boolean { + return this.prefetchStatus != CoreConstants.NOT_DOWNLOADABLE && this.prefetchStatus != CoreConstants.NOT_DOWNLOADED; + } + /** * Expand the description. */ diff --git a/src/core/course/providers/helper.ts b/src/core/course/providers/helper.ts index 6f05e157f..d9c7b621c 100644 --- a/src/core/course/providers/helper.ts +++ b/src/core/course/providers/helper.ts @@ -732,6 +732,7 @@ export class CoreCourseHelperProvider { return this.getModulePrefetchInfo(module, courseId, invalidateCache, component).then((moduleInfo) => { instance.size = moduleInfo.size > 0 ? moduleInfo.sizeReadable : 0; instance.prefetchStatusIcon = moduleInfo.statusIcon; + instance.prefetchStatus = moduleInfo.status; if (moduleInfo.status != CoreConstants.NOT_DOWNLOADABLE) { // Module is downloadable, get the text to display to prefetch. From 88b5eb7af0651bf11e5874c21a1b59de20188ff5 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Mon, 25 Feb 2019 15:59:53 +0100 Subject: [PATCH 107/191] MOBILE-2850 survey: Prefetch data after syncing survey --- .../mod/survey/components/index/index.ts | 16 +++++++- .../mod/survey/providers/prefetch-handler.ts | 4 +- src/addon/mod/survey/providers/survey.ts | 41 +++++++++++++------ src/addon/mod/survey/providers/sync.ts | 17 ++++---- src/core/course/classes/activity-sync.ts | 2 +- 5 files changed, 54 insertions(+), 26 deletions(-) diff --git a/src/addon/mod/survey/components/index/index.ts b/src/addon/mod/survey/components/index/index.ts index 581064e0e..3a7eb4151 100644 --- a/src/addon/mod/survey/components/index/index.ts +++ b/src/addon/mod/survey/components/index/index.ts @@ -188,8 +188,20 @@ export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityCo }); } - return this.surveyProvider.submitAnswers(this.survey.id, this.survey.name, this.courseId, answers).then(() => { - return this.showLoadingAndRefresh(false); + return this.surveyProvider.submitAnswers(this.survey.id, this.survey.name, this.courseId, answers).then((online) => { + if (online && this.isPrefetched()) { + // The survey is downloaded, update the data. + return this.surveySync.prefetchAfterUpdate(this.module, this.courseId).then(() => { + // Update the view. + this.showLoadingAndFetch(false, false); + }).catch((error) => { + // Prefetch failed, refresh the data. + return this.showLoadingAndRefresh(false); + }); + } else { + // Not downloaded, refresh the data. + return this.showLoadingAndRefresh(false); + } }).finally(() => { modal.dismiss(); }); diff --git a/src/addon/mod/survey/providers/prefetch-handler.ts b/src/addon/mod/survey/providers/prefetch-handler.ts index b3ac45ea6..e6b785486 100644 --- a/src/addon/mod/survey/providers/prefetch-handler.ts +++ b/src/addon/mod/survey/providers/prefetch-handler.ts @@ -111,7 +111,7 @@ export class AddonModSurveyPrefetchHandler extends CoreCourseActivityPrefetchHan * @return {Promise} Promise resolved when done. */ protected prefetchSurvey(module: any, courseId: number, single: boolean, siteId: string): Promise { - return this.surveyProvider.getSurvey(courseId, module.id).then((survey) => { + return this.surveyProvider.getSurvey(courseId, module.id, true, siteId).then((survey) => { const promises = [], files = this.getIntroFilesFromInstance(module, survey); @@ -120,7 +120,7 @@ export class AddonModSurveyPrefetchHandler extends CoreCourseActivityPrefetchHan // If survey isn't answered, prefetch the questions. if (!survey.surveydone) { - promises.push(this.surveyProvider.getQuestions(survey.id)); + promises.push(this.surveyProvider.getQuestions(survey.id, true, siteId)); } return Promise.all(promises); diff --git a/src/addon/mod/survey/providers/survey.ts b/src/addon/mod/survey/providers/survey.ts index 75bd970d0..ff376e2f8 100644 --- a/src/addon/mod/survey/providers/survey.ts +++ b/src/addon/mod/survey/providers/survey.ts @@ -20,6 +20,7 @@ import { CoreAppProvider } from '@providers/app'; import { CoreFilepoolProvider } from '@providers/filepool'; import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { AddonModSurveyOfflineProvider } from './offline'; +import { CoreSiteWSPreSets } from '@classes/site'; /** * Service that provides some features for surveys. @@ -41,18 +42,24 @@ export class AddonModSurveyProvider { * Get a survey's questions. * * @param {number} surveyId Survey ID. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the questions are retrieved. */ - getQuestions(surveyId: number, siteId?: string): Promise { + getQuestions(surveyId: number, ignoreCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const params = { surveyid: surveyId }, - preSets = { + preSets: CoreSiteWSPreSets = { cacheKey: this.getQuestionsCacheKey(surveyId) }; + if (ignoreCache) { + preSets.getFromCache = false; + preSets.emergencyCache = false; + } + return site.read('mod_survey_get_questions', params, preSets).then((response) => { if (response.questions) { return response.questions; @@ -87,20 +94,26 @@ export class AddonModSurveyProvider { * Get a survey data. * * @param {number} courseId Course ID. - * @param {string} key Name of the property to check. - * @param {any} value Value to search. + * @param {string} key Name of the property to check. + * @param {any} value Value to search. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the survey is retrieved. */ - protected getSurveyDataByKey(courseId: number, key: string, value: any, siteId?: string): Promise { + protected getSurveyDataByKey(courseId: number, key: string, value: any, ignoreCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const params = { courseids: [courseId] }, - preSets = { + preSets: CoreSiteWSPreSets = { cacheKey: this.getSurveyCacheKey(courseId) }; + if (ignoreCache) { + preSets.getFromCache = false; + preSets.emergencyCache = false; + } + return site.read('mod_survey_get_surveys_by_courses', params, preSets).then((response) => { if (response && response.surveys) { const currentSurvey = response.surveys.find((survey) => { @@ -120,24 +133,26 @@ export class AddonModSurveyProvider { * Get a survey by course module ID. * * @param {number} courseId Course ID. - * @param {number} cmId Course module ID. + * @param {number} cmId Course module ID. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the survey is retrieved. */ - getSurvey(courseId: number, cmId: number, siteId?: string): Promise { - return this.getSurveyDataByKey(courseId, 'coursemodule', cmId, siteId); + getSurvey(courseId: number, cmId: number, ignoreCache?: boolean, siteId?: string): Promise { + return this.getSurveyDataByKey(courseId, 'coursemodule', cmId, ignoreCache, siteId); } /** * Get a survey by ID. * - * @param {number} courseId Course ID. - * @param {number} id Survey ID. + * @param {number} courseId Course ID. + * @param {number} id Survey ID. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the survey is retrieved. */ - getSurveyById(courseId: number, id: number, siteId?: string): Promise { - return this.getSurveyDataByKey(courseId, 'id', id, siteId); + getSurveyById(courseId: number, id: number, ignoreCache?: boolean, siteId?: string): Promise { + return this.getSurveyDataByKey(courseId, 'id', id, ignoreCache, siteId); } /** diff --git a/src/addon/mod/survey/providers/sync.ts b/src/addon/mod/survey/providers/sync.ts index 2e301f5fa..7ade2bb70 100644 --- a/src/addon/mod/survey/providers/sync.ts +++ b/src/addon/mod/survey/providers/sync.ts @@ -15,7 +15,6 @@ import { Injectable } from '@angular/core'; import { CoreLoggerProvider } from '@providers/logger'; import { CoreSitesProvider } from '@providers/sites'; -import { CoreSyncBaseProvider } from '@classes/base-sync'; import { CoreAppProvider } from '@providers/app'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreTextUtilsProvider } from '@providers/utils/text'; @@ -26,13 +25,16 @@ import { CoreEventsProvider } from '@providers/events'; import { TranslateService } from '@ngx-translate/core'; import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; +import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate'; +import { CoreCourseActivitySyncBaseProvider } from '@core/course/classes/activity-sync'; import { CoreSyncProvider } from '@providers/sync'; +import { AddonModSurveyPrefetchHandler } from './prefetch-handler'; /** * Service to sync surveys. */ @Injectable() -export class AddonModSurveySyncProvider extends CoreSyncBaseProvider { +export class AddonModSurveySyncProvider extends CoreCourseActivitySyncBaseProvider { static AUTO_SYNCED = 'addon_mod_survey_autom_synced'; protected componentTranslate: string; @@ -41,10 +43,11 @@ export class AddonModSurveySyncProvider extends CoreSyncBaseProvider { syncProvider: CoreSyncProvider, textUtils: CoreTextUtilsProvider, translate: TranslateService, courseProvider: CoreCourseProvider, private surveyOffline: AddonModSurveyOfflineProvider, private eventsProvider: CoreEventsProvider, private surveyProvider: AddonModSurveyProvider, - private utils: CoreUtilsProvider, timeUtils: CoreTimeUtilsProvider, private logHelper: CoreCourseLogHelperProvider) { + private utils: CoreUtilsProvider, timeUtils: CoreTimeUtilsProvider, private logHelper: CoreCourseLogHelperProvider, + prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModSurveyPrefetchHandler) { super('AddonModSurveySyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, - timeUtils); + timeUtils, prefetchDelegate, prefetchHandler); this.componentTranslate = courseProvider.translateModuleName('survey'); } @@ -57,7 +60,7 @@ export class AddonModSurveySyncProvider extends CoreSyncBaseProvider { * @return {string} Sync ID. * @protected */ - getSyncId (surveyId: number, userId: number): string { + getSyncId(surveyId: number, userId: number): string { return surveyId + '#' + userId; } @@ -192,9 +195,7 @@ export class AddonModSurveySyncProvider extends CoreSyncBaseProvider { }).then(() => { if (courseId) { // Data has been sent to server, update survey data. - return this.surveyProvider.invalidateSurveyData(courseId, siteId).then(() => { - return this.surveyProvider.getSurveyById(courseId, surveyId, siteId); - }).catch(() => { + return this.prefetchAfterUpdate(module, courseId, undefined, siteId).catch(() => { // Ignore errors. }); } diff --git a/src/core/course/classes/activity-sync.ts b/src/core/course/classes/activity-sync.ts index 2ccdb8552..e6bd7e96c 100644 --- a/src/core/course/classes/activity-sync.ts +++ b/src/core/course/classes/activity-sync.ts @@ -55,7 +55,7 @@ export class CoreCourseActivitySyncBaseProvider extends CoreSyncBaseProvider { if (result && result.updates) { // Only prefetch if files haven't changed. const fileChanged = !!result.updates.find((entry) => { - return entry.match(regex); + return entry.name.match(regex); }); if (!fileChanged) { From 0dd4ac1a22310ddf03a6d8ad8bf074863d558baa Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 26 Feb 2019 09:17:49 +0100 Subject: [PATCH 108/191] MOBILE-2850 feedback: Prefetch data after syncing feedback --- .../mod/feedback/components/index/index.ts | 22 +++- src/addon/mod/feedback/pages/form/form.ts | 7 +- src/addon/mod/feedback/providers/feedback.ts | 116 +++++++++++++----- .../feedback/providers/prefetch-handler.ts | 32 ++--- .../providers/show-entries-link-handler.ts | 2 +- src/addon/mod/feedback/providers/sync.ts | 20 +-- src/core/course/classes/activity-sync.ts | 2 +- src/providers/groups.ts | 9 +- 8 files changed, 145 insertions(+), 65 deletions(-) diff --git a/src/addon/mod/feedback/components/index/index.ts b/src/addon/mod/feedback/components/index/index.ts index 6914e4b03..76610926d 100644 --- a/src/addon/mod/feedback/components/index/index.ts +++ b/src/addon/mod/feedback/components/index/index.ts @@ -77,15 +77,29 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity // Listen for form submit events. this.submitObserver = this.eventsProvider.on(AddonModFeedbackProvider.FORM_SUBMITTED, (data) => { if (this.feedback && data.feedbackId == this.feedback.id) { - // Go to review attempt if an attempt in this quiz was finished and synced. this.tabsLoaded['analysis'] = false; this.tabsLoaded['overview'] = false; this.loaded = false; - if (data.tab != this.tab) { - this.tabChanged(data.tab); + + let promise; + + // Prefetch data if needed. + if (!data.offline && this.isPrefetched()) { + promise = this.feedbackSync.prefetchAfterUpdate(this.module, this.courseId).catch(() => { + // Ignore errors. + }); } else { - this.loadContent(true); + promise = Promise.resolve(); } + + promise.then(() => { + // Load the right tab. + if (data.tab != this.tab) { + this.tabChanged(data.tab); + } else { + this.loadContent(true); + } + }); } }, this.siteId); } diff --git a/src/addon/mod/feedback/pages/form/form.ts b/src/addon/mod/feedback/pages/form/form.ts index 214f12bd7..0d8bbf9fa 100644 --- a/src/addon/mod/feedback/pages/form/form.ts +++ b/src/addon/mod/feedback/pages/form/form.ts @@ -338,8 +338,13 @@ export class AddonModFeedbackFormPage implements OnDestroy { ngOnDestroy(): void { if (this.submitted) { const tab = this.submitted == 'analysis' ? 'analysis' : 'overview'; + // If form has been submitted, the info has been already invalidated but we should update index view. - this.eventsProvider.trigger(AddonModFeedbackProvider.FORM_SUBMITTED, {feedbackId: this.feedback.id, tab: tab}); + this.eventsProvider.trigger(AddonModFeedbackProvider.FORM_SUBMITTED, { + feedbackId: this.feedback.id, + tab: tab, + offline: this.completedOffline + }); } this.onlineObserver && this.onlineObserver.unsubscribe(); } diff --git a/src/addon/mod/feedback/providers/feedback.ts b/src/addon/mod/feedback/providers/feedback.ts index a113791c5..7d3ebd4d9 100644 --- a/src/addon/mod/feedback/providers/feedback.ts +++ b/src/addon/mod/feedback/providers/feedback.ts @@ -20,6 +20,7 @@ import { CoreFilepoolProvider } from '@providers/filepool'; import { CoreAppProvider } from '@providers/app'; import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { AddonModFeedbackOfflineProvider } from './offline'; +import { CoreSiteWSPreSets } from '@classes/site'; /** * Service that provides some features for feedbacks. @@ -215,11 +216,14 @@ export class AddonModFeedbackProvider { * * @param {number} feedbackId Feedback ID. * @param {number} groupId Group id, 0 means that the function will determine the user group. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @param {string} [siteId] Site ID. If not defined, current site. * @param {any} [previous] Only for recurrent use. Object with the previous fetched info. * @return {Promise} Promise resolved when the info is retrieved. */ - getAllNonRespondents(feedbackId: number, groupId: number, siteId?: string, previous?: any): Promise { + getAllNonRespondents(feedbackId: number, groupId: number, ignoreCache?: boolean, siteId?: string, previous?: any) + : Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); if (typeof previous == 'undefined') { previous = { @@ -228,7 +232,7 @@ export class AddonModFeedbackProvider { }; } - return this.getNonRespondents(feedbackId, groupId, previous.page, siteId).then((response) => { + return this.getNonRespondents(feedbackId, groupId, previous.page, ignoreCache, siteId).then((response) => { if (previous.users.length < response.total) { previous.users = previous.users.concat(response.users); } @@ -237,7 +241,7 @@ export class AddonModFeedbackProvider { // Can load more. previous.page++; - return this.getAllNonRespondents(feedbackId, groupId, siteId, previous); + return this.getAllNonRespondents(feedbackId, groupId, ignoreCache, siteId, previous); } previous.total = response.total; @@ -250,11 +254,14 @@ export class AddonModFeedbackProvider { * * @param {number} feedbackId Feedback ID. * @param {number} groupId Group id, 0 means that the function will determine the user group. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @param {string} [siteId] Site ID. If not defined, current site. * @param {any} [previous] Only for recurrent use. Object with the previous fetched info. * @return {Promise} Promise resolved when the info is retrieved. */ - getAllResponsesAnalysis(feedbackId: number, groupId: number, siteId?: string, previous?: any): Promise { + getAllResponsesAnalysis(feedbackId: number, groupId: number, ignoreCache?: boolean, siteId?: string, previous?: any) + : Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); if (typeof previous == 'undefined') { previous = { @@ -264,7 +271,7 @@ export class AddonModFeedbackProvider { }; } - return this.getResponsesAnalysis(feedbackId, groupId, previous.page, siteId).then((responses) => { + return this.getResponsesAnalysis(feedbackId, groupId, previous.page, ignoreCache, siteId).then((responses) => { if (previous.anonattempts.length < responses.totalanonattempts) { previous.anonattempts = previous.anonattempts.concat(responses.anonattempts); } @@ -277,7 +284,7 @@ export class AddonModFeedbackProvider { // Can load more. previous.page++; - return this.getAllResponsesAnalysis(feedbackId, groupId, siteId, previous); + return this.getAllResponsesAnalysis(feedbackId, groupId, ignoreCache, siteId, previous); } previous.totalattempts = responses.totalattempts; @@ -292,15 +299,16 @@ export class AddonModFeedbackProvider { * * @param {number} feedbackId Feedback ID. * @param {number} [groupId] Group ID. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the feedback is retrieved. */ - getAnalysis(feedbackId: number, groupId?: number, siteId?: string): Promise { + getAnalysis(feedbackId: number, groupId?: number, ignoreCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const params = { feedbackid: feedbackId }, - preSets = { + preSets: CoreSiteWSPreSets = { cacheKey: this.getAnalysisDataCacheKey(feedbackId, groupId) }; @@ -308,6 +316,11 @@ export class AddonModFeedbackProvider { params['groupid'] = groupId; } + if (ignoreCache) { + preSets.getFromCache = false; + preSets.emergencyCache = false; + } + return site.read('mod_feedback_get_analysis', params, preSets); }); } @@ -338,11 +351,12 @@ export class AddonModFeedbackProvider { * * @param {number} feedbackId Feedback ID. * @param {number} attemptId Attempt id to find. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @param {string} [siteId] Site ID. If not defined, current site. * @param {any} [previous] Only for recurrent use. Object with the previous fetched info. * @return {Promise} Promise resolved when the info is retrieved. */ - getAttempt(feedbackId: number, attemptId: number, siteId?: string, previous?: any): Promise { + getAttempt(feedbackId: number, attemptId: number, ignoreCache?: boolean, siteId?: string, previous?: any): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); if (typeof previous == 'undefined') { previous = { @@ -352,7 +366,7 @@ export class AddonModFeedbackProvider { }; } - return this.getResponsesAnalysis(feedbackId, 0, previous.page, siteId).then((responses) => { + return this.getResponsesAnalysis(feedbackId, 0, previous.page, ignoreCache, siteId).then((responses) => { let attempt; attempt = responses.attempts.find((attempt) => { @@ -383,7 +397,7 @@ export class AddonModFeedbackProvider { // Can load more. Check there. previous.page++; - return this.getAttempt(feedbackId, attemptId, siteId, previous); + return this.getAttempt(feedbackId, attemptId, ignoreCache, siteId, previous); } // Not found and all loaded. Reject. @@ -405,18 +419,24 @@ export class AddonModFeedbackProvider { * Returns the temporary completion timemodified for the current user. * * @param {number} feedbackId Feedback ID. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the info is retrieved. */ - getCurrentCompletedTimeModified(feedbackId: number, siteId?: string): Promise { + getCurrentCompletedTimeModified(feedbackId: number, ignoreCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const params = { feedbackid: feedbackId }, - preSets = { + preSets: CoreSiteWSPreSets = { cacheKey: this.getCurrentCompletedTimeModifiedDataCacheKey(feedbackId) }; + if (ignoreCache) { + preSets.getFromCache = false; + preSets.emergencyCache = false; + } + return site.read('mod_feedback_get_current_completed_tmp', params, preSets).then((response) => { if (response && typeof response.feedback != 'undefined' && typeof response.feedback.timemodified != 'undefined') { return response.feedback.timemodified; @@ -552,20 +572,26 @@ export class AddonModFeedbackProvider { * @param {string} key Name of the property to check. * @param {any} value Value to search. * @param {string} [siteId] Site ID. If not defined, current site. - * @param {boolean} [forceCache=false] True to always get the value from cache, false otherwise. Default false. + * @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @return {Promise} Promise resolved when the feedback is retrieved. */ - protected getFeedbackDataByKey(courseId: number, key: string, value: any, siteId?: string, forceCache?: boolean): Promise { + protected getFeedbackDataByKey(courseId: number, key: string, value: any, siteId?: string, forceCache?: boolean, + ignoreCache?: boolean): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { const params = { courseids: [courseId] }, - preSets = { + preSets: CoreSiteWSPreSets = { cacheKey: this.getFeedbackCacheKey(courseId) }; if (forceCache) { - preSets['omitExpires'] = true; + preSets.omitExpires = true; + } else if (ignoreCache) { + preSets.getFromCache = false; + preSets.emergencyCache = false; } return site.read('mod_feedback_get_feedbacks_by_courses', params, preSets).then((response) => { @@ -589,11 +615,12 @@ export class AddonModFeedbackProvider { * @param {number} courseId Course ID. * @param {number} cmId Course module ID. * @param {string} [siteId] Site ID. If not defined, current site. - * @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false. + * @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @return {Promise} Promise resolved when the feedback is retrieved. */ - getFeedback(courseId: number, cmId: number, siteId?: string, forceCache?: boolean): Promise { - return this.getFeedbackDataByKey(courseId, 'coursemodule', cmId, siteId, forceCache); + getFeedback(courseId: number, cmId: number, siteId?: string, forceCache?: boolean, ignoreCache?: boolean): Promise { + return this.getFeedbackDataByKey(courseId, 'coursemodule', cmId, siteId, forceCache, ignoreCache); } /** @@ -603,28 +630,35 @@ export class AddonModFeedbackProvider { * @param {number} id Feedback ID. * @param {string} [siteId] Site ID. If not defined, current site. * @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @return {Promise} Promise resolved when the feedback is retrieved. */ - getFeedbackById(courseId: number, id: number, siteId?: string, forceCache?: boolean): Promise { - return this.getFeedbackDataByKey(courseId, 'id', id, siteId, forceCache); + getFeedbackById(courseId: number, id: number, siteId?: string, forceCache?: boolean, ignoreCache?: boolean): Promise { + return this.getFeedbackDataByKey(courseId, 'id', id, siteId, forceCache, ignoreCache); } /** * Returns the items (questions) in the given feedback. * * @param {number} feedbackId Feedback ID. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the info is retrieved. */ - getItems(feedbackId: number, siteId?: string): Promise { + getItems(feedbackId: number, ignoreCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const params = { feedbackid: feedbackId }, - preSets = { + preSets: CoreSiteWSPreSets = { cacheKey: this.getItemsDataCacheKey(feedbackId) }; + if (ignoreCache) { + preSets.getFromCache = false; + preSets.emergencyCache = false; + } + return site.read('mod_feedback_get_items', params, preSets); }); } @@ -645,20 +679,28 @@ export class AddonModFeedbackProvider { * @param {number} feedbackId Feedback ID. * @param {number} [groupId=0] Group id, 0 means that the function will determine the user group. * @param {number} [page=0] The page of records to return. + * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the info is retrieved. */ - getNonRespondents(feedbackId: number, groupId: number = 0, page: number = 0, siteId?: string): Promise { + getNonRespondents(feedbackId: number, groupId: number = 0, page: number = 0, ignoreCache?: boolean, siteId?: string) + : Promise { + return this.sitesProvider.getSite(siteId).then((site) => { const params = { feedbackid: feedbackId, groupid: groupId, page: page }, - preSets = { + preSets: CoreSiteWSPreSets = { cacheKey: this.getNonRespondentsDataCacheKey(feedbackId, groupId) }; + if (ignoreCache) { + preSets.getFromCache = false; + preSets.emergencyCache = false; + } + return site.read('mod_feedback_get_non_respondents', params, preSets); }); } @@ -725,7 +767,7 @@ export class AddonModFeedbackProvider { }); }).catch(() => { // If getPageItems fail we should calculate it using getItems. - return this.getItems(feedbackId, siteId).then((response) => { + return this.getItems(feedbackId, false, siteId).then((response) => { return this.fillValues(feedbackId, response.items, offline, ignoreCache, siteId).then((items) => { // Separate items by pages. let currentPage = 0; @@ -802,20 +844,26 @@ export class AddonModFeedbackProvider { * @param {number} feedbackId Feedback ID. * @param {number} groupId Group id, 0 means that the function will determine the user group. * @param {number} page The page of records to return. + * @param {boolean} [ignoreCache=false] True if it should ignore cached data (it will always fail in offline or server down). * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the info is retrieved. */ - getResponsesAnalysis(feedbackId: number, groupId: number, page: number, siteId?: string): Promise { + getResponsesAnalysis(feedbackId: number, groupId: number, page: number, ignoreCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const params = { feedbackid: feedbackId, groupid: groupId || 0, page: page || 0 }, - preSets = { + preSets: CoreSiteWSPreSets = { cacheKey: this.getResponsesAnalysisDataCacheKey(feedbackId, groupId) }; + if (ignoreCache) { + preSets.getFromCache = false; + preSets.emergencyCache = false; + } + return site.read('mod_feedback_get_responses_analysis', params, preSets); }); } @@ -1037,18 +1085,24 @@ export class AddonModFeedbackProvider { * Returns if feedback has been completed * * @param {number} feedbackId Feedback ID. + * @param {boolean} [ignoreCache=false] True if it should ignore cached data (it will always fail in offline or server down). * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the info is retrieved. */ - isCompleted(feedbackId: number, siteId?: string): Promise { + isCompleted(feedbackId: number, ignoreCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const params = { feedbackid: feedbackId }, - preSets = { + preSets: CoreSiteWSPreSets = { cacheKey: this.getCompletedDataCacheKey(feedbackId) }; + if (ignoreCache) { + preSets.getFromCache = false; + preSets.emergencyCache = false; + } + return this.utils.promiseWorks(site.read('mod_feedback_get_last_completed', params, preSets)); }); } diff --git a/src/addon/mod/feedback/providers/prefetch-handler.ts b/src/addon/mod/feedback/providers/prefetch-handler.ts index 73a9dfc6b..ab9528fc7 100644 --- a/src/addon/mod/feedback/providers/prefetch-handler.ts +++ b/src/addon/mod/feedback/providers/prefetch-handler.ts @@ -173,19 +173,15 @@ export class AddonModFeedbackPrefetchHandler extends CoreCourseActivityPrefetchH */ protected prefetchFeedback(module: any, courseId: number, single: boolean, siteId: string): Promise { // Prefetch the feedback data. - return this.feedbackProvider.getFeedback(courseId, module.id).then((feedback) => { - const p1 = []; + return this.feedbackProvider.getFeedback(courseId, module.id, siteId, false, true).then((feedback) => { + let files = (feedback.pageaftersubmitfiles || []).concat(this.getIntroFilesFromInstance(module, feedback)); - p1.push(this.getFiles(module, courseId).then((files) => { - return this.filepoolProvider.addFilesToQueue(siteId, files, this.component, module.id); - })); - - p1.push(this.feedbackProvider.getFeedbackAccessInformation(feedback.id, false, true, siteId).then((accessData) => { + return this.feedbackProvider.getFeedbackAccessInformation(feedback.id, false, true, siteId).then((accessData) => { const p2 = []; if (accessData.canedititems || accessData.canviewreports) { // Get all groups analysis. - p2.push(this.feedbackProvider.getAnalysis(feedback.id, undefined, siteId)); - p2.push(this.groupsProvider.getActivityGroupInfo(feedback.coursemodule, true, undefined, siteId) + p2.push(this.feedbackProvider.getAnalysis(feedback.id, undefined, true, siteId)); + p2.push(this.groupsProvider.getActivityGroupInfo(feedback.coursemodule, true, undefined, siteId, true) .then((groupInfo) => { const p3 = [], userIds = []; @@ -194,8 +190,8 @@ export class AddonModFeedbackPrefetchHandler extends CoreCourseActivityPrefetchH groupInfo.groups = [{id: 0}]; } groupInfo.groups.forEach((group) => { - p3.push(this.feedbackProvider.getAnalysis(feedback.id, group.id, siteId)); - p3.push(this.feedbackProvider.getAllResponsesAnalysis(feedback.id, group.id, siteId) + p3.push(this.feedbackProvider.getAnalysis(feedback.id, group.id, true, siteId)); + p3.push(this.feedbackProvider.getAllResponsesAnalysis(feedback.id, group.id, true, siteId) .then((responses) => { responses.attempts.forEach((attempt) => { userIds.push(attempt.userid); @@ -203,7 +199,7 @@ export class AddonModFeedbackPrefetchHandler extends CoreCourseActivityPrefetchH })); if (!accessData.isanonymous) { - p3.push(this.feedbackProvider.getAllNonRespondents(feedback.id, group.id, siteId) + p3.push(this.feedbackProvider.getAllNonRespondents(feedback.id, group.id, true, siteId) .then((responses) => { responses.users.forEach((user) => { userIds.push(user.userid); @@ -219,7 +215,13 @@ export class AddonModFeedbackPrefetchHandler extends CoreCourseActivityPrefetchH })); } - p2.push(this.feedbackProvider.getItems(feedback.id, siteId)); + p2.push(this.feedbackProvider.getItems(feedback.id, true, siteId).then((response) => { + response.items.forEach((item) => { + files = files.concat(item.itemfiles); + }); + + return this.filepoolProvider.addFilesToQueue(siteId, files, this.component, module.id); + })); if (accessData.cancomplete && accessData.cansubmit && !accessData.isempty) { // Send empty data, so it will recover last completed feedback attempt values. @@ -234,9 +236,7 @@ export class AddonModFeedbackPrefetchHandler extends CoreCourseActivityPrefetchH } return Promise.all(p2); - })); - - return Promise.all(p1); + }); }); } } diff --git a/src/addon/mod/feedback/providers/show-entries-link-handler.ts b/src/addon/mod/feedback/providers/show-entries-link-handler.ts index fd32a42fe..d10c156da 100644 --- a/src/addon/mod/feedback/providers/show-entries-link-handler.ts +++ b/src/addon/mod/feedback/providers/show-entries-link-handler.ts @@ -65,7 +65,7 @@ export class AddonModFeedbackShowEntriesLinkHandler extends CoreContentLinksHand return this.linkHelper.goInSite(navCtrl, 'AddonModFeedbackRespondentsPage', stateParams, siteId); } - return this.feedbackProvider.getAttempt(module.instance, params.showcompleted, siteId).then((attempt) => { + return this.feedbackProvider.getAttempt(module.instance, params.showcompleted, true, siteId).then((attempt) => { stateParams = { moduleId: module.id, attempt: attempt, diff --git a/src/addon/mod/feedback/providers/sync.ts b/src/addon/mod/feedback/providers/sync.ts index 0a9364b48..b555caa1f 100644 --- a/src/addon/mod/feedback/providers/sync.ts +++ b/src/addon/mod/feedback/providers/sync.ts @@ -15,7 +15,6 @@ import { Injectable } from '@angular/core'; import { CoreLoggerProvider } from '@providers/logger'; import { CoreSitesProvider } from '@providers/sites'; -import { CoreSyncBaseProvider } from '@classes/base-sync'; import { CoreAppProvider } from '@providers/app'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreTextUtilsProvider } from '@providers/utils/text'; @@ -25,14 +24,17 @@ import { AddonModFeedbackProvider } from './feedback'; import { CoreEventsProvider } from '@providers/events'; import { TranslateService } from '@ngx-translate/core'; import { CoreCourseProvider } from '@core/course/providers/course'; -import { CoreSyncProvider } from '@providers/sync'; +import { CoreCourseActivitySyncBaseProvider } from '@core/course/classes/activity-sync'; import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; +import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate'; +import { CoreSyncProvider } from '@providers/sync'; +import { AddonModFeedbackPrefetchHandler } from './prefetch-handler'; /** * Service to sync feedbacks. */ @Injectable() -export class AddonModFeedbackSyncProvider extends CoreSyncBaseProvider { +export class AddonModFeedbackSyncProvider extends CoreCourseActivitySyncBaseProvider { static AUTO_SYNCED = 'addon_mod_feedback_autom_synced'; protected componentTranslate: string; @@ -42,9 +44,11 @@ export class AddonModFeedbackSyncProvider extends CoreSyncBaseProvider { private eventsProvider: CoreEventsProvider, private feedbackProvider: AddonModFeedbackProvider, protected translate: TranslateService, private utils: CoreUtilsProvider, protected textUtils: CoreTextUtilsProvider, courseProvider: CoreCourseProvider, syncProvider: CoreSyncProvider, timeUtils: CoreTimeUtilsProvider, - private logHelper: CoreCourseLogHelperProvider) { + private logHelper: CoreCourseLogHelperProvider, prefetchDelegate: CoreCourseModulePrefetchDelegate, + prefetchHandler: AddonModFeedbackPrefetchHandler) { + super('AddonModFeedbackSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, - timeUtils); + timeUtils, prefetchDelegate, prefetchHandler); this.componentTranslate = courseProvider.translateModuleName('feedback'); } @@ -196,7 +200,7 @@ export class AddonModFeedbackSyncProvider extends CoreSyncBaseProvider { return Promise.all(promises); } - return this.feedbackProvider.getCurrentCompletedTimeModified(feedbackId, siteId).then((timemodified) => { + return this.feedbackProvider.getCurrentCompletedTimeModified(feedbackId, true, siteId).then((timemodified) => { // Sort by page. responses.sort((a, b) => { return a.page - b.page; @@ -216,8 +220,8 @@ export class AddonModFeedbackSyncProvider extends CoreSyncBaseProvider { }); }).then(() => { if (result.updated) { - // Data has been sent to server. Now invalidate the WS calls. - return this.feedbackProvider.invalidateAllFeedbackData(feedbackId, siteId).catch(() => { + // Data has been sent to server, update data. + return this.prefetchAfterUpdate(module, courseId, undefined, siteId).catch(() => { // Ignore errors. }); } diff --git a/src/core/course/classes/activity-sync.ts b/src/core/course/classes/activity-sync.ts index e6bd7e96c..287706f9b 100644 --- a/src/core/course/classes/activity-sync.ts +++ b/src/core/course/classes/activity-sync.ts @@ -52,7 +52,7 @@ export class CoreCourseActivitySyncBaseProvider extends CoreSyncBaseProvider { // Get the module updates to check if the data was updated or not. return this.prefetchDelegate.getModuleUpdates(module, courseId, true, siteId).then((result) => { - if (result && result.updates) { + if (result && result.updates && result.updates.length > 0) { // Only prefetch if files haven't changed. const fileChanged = !!result.updates.find((entry) => { return entry.name.match(regex); diff --git a/src/providers/groups.ts b/src/providers/groups.ts index 437e6e03f..e5bbea314 100644 --- a/src/providers/groups.ts +++ b/src/providers/groups.ts @@ -147,19 +147,22 @@ export class CoreGroupsProvider { * @param {boolean} [addAllParts=true] Whether to add the all participants option. Always true for visible groups. * @param {number} [userId] User ID. If not defined, use current user. * @param {string} [siteId] Site ID. If not defined, current site. + * @param {boolean} [ignoreCache=false] True if it should ignore cached data (it will always fail in offline or server down). * @return {Promise} Promise resolved with the group info. */ - getActivityGroupInfo(cmId: number, addAllParts: boolean = true, userId?: number, siteId?: string): Promise { + getActivityGroupInfo(cmId: number, addAllParts: boolean = true, userId?: number, siteId?: string, ignoreCache?: boolean) + : Promise { + const groupInfo: CoreGroupInfo = { groups: [] }; - return this.getActivityGroupMode(cmId, siteId).then((groupMode) => { + return this.getActivityGroupMode(cmId, siteId, ignoreCache).then((groupMode) => { groupInfo.separateGroups = groupMode === CoreGroupsProvider.SEPARATEGROUPS; groupInfo.visibleGroups = groupMode === CoreGroupsProvider.VISIBLEGROUPS; if (groupInfo.separateGroups || groupInfo.visibleGroups) { - return this.getActivityAllowedGroups(cmId, userId, siteId); + return this.getActivityAllowedGroups(cmId, userId, siteId, ignoreCache); } return []; From b95de260ee46d6278d03cff294015aa11fd99a6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 26 Feb 2019 15:06:33 +0100 Subject: [PATCH 109/191] MOBILE-2819 a11y: Apply aria attributes to components --- .../mod/wiki/components/index/index.scss | 1 - src/components/chrono/chrono.ts | 2 +- .../context-menu/context-menu-popover.ts | 2 ++ src/components/context-menu/context-menu.ts | 25 ++++++++++++---- .../core-context-menu-popover.html | 4 +-- .../context-menu/core-context-menu.html | 2 +- src/components/icon/core-icon.html | 2 +- src/components/icon/icon.ts | 2 ++ .../input-errors/core-input-errors.html | 2 +- src/components/ion-tabs/core-ion-tabs.html | 4 +-- src/components/loading/core-loading.html | 6 ++-- .../progress-bar/core-progress-bar.html | 2 +- .../core-rich-text-editor.html | 4 +-- .../search-box/core-search-box.html | 4 +-- .../core-send-message-form.html | 2 +- src/components/tabs/core-tabs.html | 18 +++++------ src/components/tabs/tab.ts | 28 ++++++++++++++++- src/components/tabs/tabs.scss | 27 ++++++++++------- src/components/tabs/tabs.ts | 5 ++-- src/components/timer/core-timer.html | 4 +-- .../components/format/core-course-format.html | 2 +- src/core/course/components/format/format.ts | 30 +++++++++++++------ .../components/module/core-course-module.html | 2 +- src/core/course/components/module/module.ts | 6 +++- .../section-selector/section-selector.html | 20 +++++++------ .../section-selector/section-selector.scss | 4 +++ src/core/mainmenu/pages/more/more.html | 8 ++--- 27 files changed, 144 insertions(+), 74 deletions(-) diff --git a/src/addon/mod/wiki/components/index/index.scss b/src/addon/mod/wiki/components/index/index.scss index 9b7a2f796..49867d614 100644 --- a/src/addon/mod/wiki/components/index/index.scss +++ b/src/addon/mod/wiki/components/index/index.scss @@ -7,7 +7,6 @@ $addon-mod-wiki-toc-background-color: $gray-light !default; ion-app.app-root addon-mod-wiki-index { background-color: $white; - .core-tabs-content-container, .addon-mod_wiki-page-content { background-color: $white; } diff --git a/src/components/chrono/chrono.ts b/src/components/chrono/chrono.ts index 5770db45f..8783416af 100644 --- a/src/components/chrono/chrono.ts +++ b/src/components/chrono/chrono.ts @@ -28,7 +28,7 @@ import { Component, Input, OnChanges, OnDestroy, Output, EventEmitter, SimpleCha */ @Component({ selector: 'core-chrono', - template: '{{ time / 1000 | coreSecondsToHMS }}' + template: '{{ time / 1000 | coreSecondsToHMS }}' }) export class CoreChronoComponent implements OnChanges, OnDestroy { @Input() running: boolean; // Set it to true to start the chrono. Set it to false to stop it. diff --git a/src/components/context-menu/context-menu-popover.ts b/src/components/context-menu/context-menu-popover.ts index 7aa987c24..aa468f189 100644 --- a/src/components/context-menu/context-menu-popover.ts +++ b/src/components/context-menu/context-menu-popover.ts @@ -26,12 +26,14 @@ import { CoreLoggerProvider } from '@providers/logger'; }) export class CoreContextMenuPopoverComponent { title: string; + uniqueId: string; items: CoreContextMenuItemComponent[]; protected logger: any; constructor(navParams: NavParams, private viewCtrl: ViewController, logger: CoreLoggerProvider) { this.title = navParams.get('title'); this.items = navParams.get('items') || []; + this.uniqueId = navParams.get('id'); this.logger = logger.getInstance('CoreContextMenuPopoverComponent'); } diff --git a/src/components/context-menu/context-menu.ts b/src/components/context-menu/context-menu.ts index 5d453384f..1f674eebd 100644 --- a/src/components/context-menu/context-menu.ts +++ b/src/components/context-menu/context-menu.ts @@ -16,6 +16,7 @@ import { Component, Input, OnInit, OnDestroy, ElementRef, Optional } from '@angu import { PopoverController } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreContextMenuItemComponent } from './context-menu-item'; import { CoreContextMenuPopoverComponent } from './context-menu-popover'; import { CoreTabComponent } from '@components/tabs/tab'; @@ -34,14 +35,16 @@ export class CoreContextMenuComponent implements OnInit, OnDestroy { hideMenu = true; // It will be unhidden when items are added. ariaLabel: string; + expanded = false; protected items: CoreContextMenuItemComponent[] = []; protected itemsMovedToParent: CoreContextMenuItemComponent[] = []; protected itemsChangedStream: Subject; // Stream to update the hideMenu boolean when items change. protected instanceId: string; protected parentContextMenu: CoreContextMenuComponent; + protected uniqueId: string; constructor(private translate: TranslateService, private popoverCtrl: PopoverController, elementRef: ElementRef, - private domUtils: CoreDomUtilsProvider, @Optional() public coreTab: CoreTabComponent) { + private domUtils: CoreDomUtilsProvider, @Optional() public coreTab: CoreTabComponent, utils: CoreUtilsProvider) { // Create the stream and subscribe to it. We ignore successive changes during 250ms. this.itemsChangedStream = new Subject(); this.itemsChangedStream.auditTime(250).subscribe(() => { @@ -56,6 +59,9 @@ export class CoreContextMenuComponent implements OnInit, OnDestroy { }); }); + // Calculate the unique ID. + this.uniqueId = 'core-context-menu-' + utils.getUniqueId('CoreContextMenuComponent'); + this.instanceId = this.domUtils.storeInstanceByElement(elementRef.nativeElement, this); } @@ -170,10 +176,19 @@ export class CoreContextMenuComponent implements OnInit, OnDestroy { * @param {MouseEvent} event Event. */ showContextMenu(event: MouseEvent): void { - const popover = this.popoverCtrl.create(CoreContextMenuPopoverComponent, { title: this.title, items: this.items }); - popover.present({ - ev: event - }); + if (!this.expanded) { + const popover = this.popoverCtrl.create(CoreContextMenuPopoverComponent, + { title: this.title, items: this.items, id: this.uniqueId }); + + popover.onDidDismiss(() => { + this.expanded = false; + }); + popover.present({ + ev: event + }); + + this.expanded = true; + } } /** diff --git a/src/components/context-menu/core-context-menu-popover.html b/src/components/context-menu/core-context-menu-popover.html index 81a1965fb..f3c0d2323 100644 --- a/src/components/context-menu/core-context-menu-popover.html +++ b/src/components/context-menu/core-context-menu-popover.html @@ -1,6 +1,6 @@ - + {{title}} - + diff --git a/src/components/context-menu/core-context-menu.html b/src/components/context-menu/core-context-menu.html index 262bb3256..0628573be 100644 --- a/src/components/context-menu/core-context-menu.html +++ b/src/components/context-menu/core-context-menu.html @@ -1,4 +1,4 @@ - \ No newline at end of file diff --git a/src/components/icon/core-icon.html b/src/components/icon/core-icon.html index 8e4a85f3a..54452fe8f 100644 --- a/src/components/icon/core-icon.html +++ b/src/components/icon/core-icon.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/components/icon/icon.ts b/src/components/icon/icon.ts index 8171e018f..50351f582 100644 --- a/src/components/icon/icon.ts +++ b/src/components/icon/icon.ts @@ -54,6 +54,8 @@ export class CoreIconComponent implements OnInit, OnDestroy { this.newElement.classList.add('icon'); this.newElement.classList.add('fa'); this.newElement.classList.add(this.name); + this.newElement.setAttribute('aria-hidden', 'true'); + this.newElement.setAttribute('role', 'img'); if (this.isTrueProperty(this.fixedWidth)) { this.newElement.classList.add('fa-fw'); } diff --git a/src/components/input-errors/core-input-errors.html b/src/components/input-errors/core-input-errors.html index e960807e8..62ab3df6a 100644 --- a/src/components/input-errors/core-input-errors.html +++ b/src/components/input-errors/core-input-errors.html @@ -12,5 +12,5 @@
-
{{ errorText }}
+
{{ errorText }}
\ No newline at end of file diff --git a/src/components/ion-tabs/core-ion-tabs.html b/src/components/ion-tabs/core-ion-tabs.html index c3e62e9e6..d4276f461 100644 --- a/src/components/ion-tabs/core-ion-tabs.html +++ b/src/components/ion-tabs/core-ion-tabs.html @@ -1,5 +1,5 @@ -
- +
+
diff --git a/src/components/loading/core-loading.html b/src/components/loading/core-loading.html index 0e34fe572..c149e9bf1 100644 --- a/src/components/loading/core-loading.html +++ b/src/components/loading/core-loading.html @@ -1,10 +1,10 @@ -
+
-

{{message}}

+

{{message}}

-
+
\ No newline at end of file diff --git a/src/components/progress-bar/core-progress-bar.html b/src/components/progress-bar/core-progress-bar.html index 7c7319034..d024ba4b8 100644 --- a/src/components/progress-bar/core-progress-bar.html +++ b/src/components/progress-bar/core-progress-bar.html @@ -1,5 +1,5 @@
- +
diff --git a/src/components/rich-text-editor/core-rich-text-editor.html b/src/components/rich-text-editor/core-rich-text-editor.html index 972fe0c0c..7a02610dd 100644 --- a/src/components/rich-text-editor/core-rich-text-editor.html +++ b/src/components/rich-text-editor/core-rich-text-editor.html @@ -1,5 +1,5 @@
-
+
@@ -22,7 +22,7 @@
- +
diff --git a/src/components/search-box/core-search-box.html b/src/components/search-box/core-search-box.html index 7712caf71..260d45215 100644 --- a/src/components/search-box/core-search-box.html +++ b/src/components/search-box/core-search-box.html @@ -1,7 +1,7 @@ -
+ - + 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 f02a7ee2c..b1829854a 100644 --- a/src/components/send-message-form/core-send-message-form.html +++ b/src/components/send-message-form/core-send-message-form.html @@ -1,5 +1,5 @@ - + + + + + + + + + + + + + + {{ rating.timemodified | coreDateDayOrTime }} + +

+

{{ rating.rating }}

+
+
+ +
+
diff --git a/src/core/rating/pages/ratings/ratings.module.ts b/src/core/rating/pages/ratings/ratings.module.ts new file mode 100644 index 000000000..62bf4821b --- /dev/null +++ b/src/core/rating/pages/ratings/ratings.module.ts @@ -0,0 +1,35 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreRatingRatingsPage } from './ratings'; +import { CoreComponentsModule } from '@components/components.module'; +import { CoreDirectivesModule } from '@directives/directives.module'; +import { CorePipesModule } from '@pipes/pipes.module'; + +@NgModule({ + declarations: [ + CoreRatingRatingsPage + ], + imports: [ + CoreComponentsModule, + CoreDirectivesModule, + CorePipesModule, + IonicPageModule.forChild(CoreRatingRatingsPage), + TranslateModule.forChild() + ], +}) +export class CoreRatingRatingsPageModule {} diff --git a/src/core/rating/pages/ratings/ratings.ts b/src/core/rating/pages/ratings/ratings.ts new file mode 100644 index 000000000..1114562ca --- /dev/null +++ b/src/core/rating/pages/ratings/ratings.ts @@ -0,0 +1,95 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component } from '@angular/core'; +import { IonicPage, NavParams, ViewController } from 'ionic-angular'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { CoreRatingProvider, CoreRatingItemRating } from '@core/rating/providers/rating'; + +/** + * Page that displays individual ratings + */ +@IonicPage({ segment: 'core-rating-ratings' }) +@Component({ + selector: 'page-core-rating-ratings', + templateUrl: 'ratings.html', +}) +export class CoreRatingRatingsPage { + contextLevel: string; + instanceId: number; + component: string; + ratingArea: string; + aggregateMethod: number; + itemId: number; + scaleId: number; + courseId: number; + loaded = false; + ratings: CoreRatingItemRating[] = []; + + constructor(navParams: NavParams, private viewCtrl: ViewController, private domUtils: CoreDomUtilsProvider, + private ratingProvider: CoreRatingProvider) { + this.contextLevel = navParams.get('contextLevel'); + this.instanceId = navParams.get('instanceId'); + this.component = navParams.get('ratingComponent'); + this.ratingArea = navParams.get('ratingArea'); + this.aggregateMethod = navParams.get('aggregateMethod'); + this.itemId = navParams.get('itemId'); + this.scaleId = navParams.get('scaleId'); + this.courseId = navParams.get('courseId'); + } + + /** + * View loaded. + */ + ionViewDidLoad(): void { + this.fetchData().finally(() => { + this.loaded = true; + }); + } + + /** + * Fetch all the data required for the view. + * + * @return {Promise} Resolved when done. + */ + fetchData(): Promise { + return this.ratingProvider.getItemRatings(this.contextLevel, this.instanceId, this.component, this.ratingArea, this.itemId, + this.scaleId, undefined, this.courseId).then((ratings) => { + this.ratings = ratings; + }).catch((error) => { + this.domUtils.showErrorModal(error); + }); + } + + /** + * Refresh data. + * + * @param {any} refresher Refresher. + */ + refreshRatings(refresher: any): void { + this.ratingProvider.invalidateRatingItems(this.contextLevel, this.instanceId, this.component, this.ratingArea, this.itemId, + this.scaleId).finally(() => { + return this.fetchData().finally(() => { + refresher.complete(); + }); + }); + } + + /** + * Close modal. + */ + closeModal(): void { + this.viewCtrl.dismiss(); + } +} diff --git a/src/core/rating/providers/offline.ts b/src/core/rating/providers/offline.ts new file mode 100644 index 000000000..6f033da1c --- /dev/null +++ b/src/core/rating/providers/offline.ts @@ -0,0 +1,305 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; +import { CoreUtilsProvider } from '@providers/utils/utils'; + +/** + * Structure of offline ratings. + */ +export interface CoreRatingOfflineRating { + component: string; + ratingarea: string; + contextlevel: string; + instanceid: number; + itemid: number; + itemsetid: number; + courseid: number; + scaleid: number; + rating: number; + rateduserid: number; + aggregation: number; +} + +/** + * Structure of item sets. + */ +export interface CoreRatingItemSet { + component: string; + ratingArea: string; + contextLevel: string; + instanceId: number; + itemSetId: number; + courseId: number; +} + +/** + * Service to handle offline data for rating. + */ +@Injectable() +export class CoreRatingOfflineProvider { + + // Variables for database. + static RATINGS_TABLE = 'rating_ratings'; + protected siteSchema: CoreSiteSchema = { + name: 'CoreCourseOfflineProvider', + version: 1, + tables: [ + { + name: CoreRatingOfflineProvider.RATINGS_TABLE, + columns: [ + { + name: 'component', + type: 'TEXT' + }, + { + name: 'ratingarea', + type: 'TEXT' + }, + { + name: 'contextlevel', + type: 'INTEGER', + }, + { + name: 'instanceid', + type: 'INTEGER' + }, + { + name: 'itemid', + type: 'INTEGER' + }, + { + name: 'itemsetid', + type: 'INTEGER' + }, + { + name: 'courseid', + type: 'INTEGER' + }, + { + name: 'scaleid', + type: 'INTEGER' + }, + { + name: 'rating', + type: 'INTEGER' + }, + { + name: 'rateduserid', + type: 'INTEGER' + }, + { + name: 'aggregation', + type: 'INTEGER' + } + ], + primaryKeys: ['component', 'ratingarea', 'contextlevel', 'instanceid', 'itemid'] + } + ] + }; + + constructor(private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider) { + this.sitesProvider.registerSiteSchema(this.siteSchema); + } + + /** + * Get an offline rating. + * + * @param {string} contextLevel Context level: course, module, user, etc. + * @param {numnber} instanceId Context instance id. + * @param {string} component Component. Example: "mod_forum". + * @param {string} ratingArea Rating area. Example: "post". + * @param {number} itemId Item id. Example: forum post id. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with the saved rating, rejected if not found. + */ + getRating(contextLevel: string, instanceId: number, component: string, ratingArea: string, itemId: number, siteId?: string): + Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + const conditions = { + contextlevel: contextLevel, + instanceid: instanceId, + component: component, + ratingarea: ratingArea, + itemid: itemId + }; + + return site.getDb().getRecord(CoreRatingOfflineProvider.RATINGS_TABLE, conditions); + }); + } + + /** + * Add an offline rating. + * + * @param {string} component Component. Example: "mod_forum". + * @param {string} ratingArea Rating area. Example: "post". + * @param {string} contextLevel Context level: course, module, user, etc. + * @param {numnber} instanceId Context instance id. + * @param {number} itemId Item id. Example: forum post id. + * @param {number} itemSetId Item set id. Example: forum discussion id. + * @param {number} courseId Course id. + * @param {number} scaleId Scale id. + * @param {number} rating Rating value. Use CoreRatingProvider.UNSET_RATING to delete rating. + * @param {number} ratedUserId Rated user id. + * @param {number} aggregateMethod Aggregate method. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the rating is saved. + */ + addRating(component: string, ratingArea: string, contextLevel: string, instanceId: number, itemId: number, itemSetId: number, + courseId: number, scaleId: number, rating: number, ratedUserId: number, aggregateMethod: number, siteId?: string): + Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + const data: CoreRatingOfflineRating = { + component: component, + ratingarea: ratingArea, + contextlevel: contextLevel, + instanceid: instanceId, + itemid: itemId, + itemsetid: itemSetId, + courseid: courseId, + scaleid: scaleId, + rating: rating, + rateduserid: ratedUserId, + aggregation: aggregateMethod + }; + + return site.getDb().insertRecord(CoreRatingOfflineProvider.RATINGS_TABLE, data); + }); + } + + /** + * Delete offline rating. + * + * @param {string} component Component. Example: "mod_forum". + * @param {string} ratingArea Rating area. Example: "post". + * @param {string} contextLevel Context level: course, module, user, etc. + * @param {number} itemId Item id. Example: forum post id. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the rating is saved. + */ + deleteRating(component: string, ratingArea: string, contextLevel: string, instanceId: number, itemId: number, siteId?: string): + Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + const conditions = { + component: component, + ratingarea: ratingArea, + contextlevel: contextLevel, + instanceid: instanceId, + itemid: itemId + }; + + return site.getDb().deleteRecords(CoreRatingOfflineProvider.RATINGS_TABLE, conditions); + }); + } + + /** + * Get the list of item sets in a component or instance. + * + * @param {string} component Component. Example: "mod_forum". + * @param {string} ratingArea Rating Area. Example: "post". + * @param {string} [contextLevel] Context level: course, module, user, etc. + * @param {numnber} [instanceId] Context instance id. + * @param {number} [itemSetId] Item set id. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with the list of item set ids. + */ + getItemSets(component: string, ratingArea: string, contextLevel?: string, instanceId?: number, itemSetId?: number, + siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + const fields = 'DISTINCT contextlevel, instanceid, itemsetid, courseid'; + const conditions: any = { + component, + ratingarea: ratingArea + }; + if (contextLevel != null && instanceId != null) { + conditions.contextlevel = contextLevel; + conditions.instanceId = instanceId; + } + if (itemSetId != null) { + conditions.itemSetId = itemSetId; + } + + return site.getDb().getRecords(CoreRatingOfflineProvider.RATINGS_TABLE, conditions, undefined, fields) + .then((records: any[]) => { + return records.map((record) => { + return { + component, + ratingArea, + contextLevel: record.contextlevel, + instanceId: record.instanceid, + itemSetId: record.itemsetid, + courseId: record.courseid + }; + }); + }); + }); + } + + /** + * Get offline ratings of an item set. + * + * @param {string} component Component. Example: "mod_forum". + * @param {string} ratingArea Rating Area. Example: "post". + * @param {string} contextLevel Context level: course, module, user, etc. + * @param {number} itemId Item id. Example: forum post id. + * @param {number} itemSetId Item set id. Example: forum discussion id. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with the list of ratings. + */ + getRatings(component: string, ratingArea: string, contextLevel: string, instanceId: number, itemSetId: number, siteId?: string): + Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + const conditions = { + component, + ratingarea: ratingArea, + contextlevel: contextLevel, + instanceid: instanceId, + itemsetid: itemSetId + }; + + return site.getDb().getRecords(CoreRatingOfflineProvider.RATINGS_TABLE, conditions); + }); + } + + /** + * Return whether a component, instance or item set has offline ratings. + * + * @param {string} component Component. Example: "mod_forum". + * @param {string} ratingArea Rating Area. Example: "post". + * @param {string} [contextLevel] Context level: course, module, user, etc. + * @param {number} [instanceId] Context instance id. + * @param {number} [itemSetId] Item set id. Example: forum discussion id. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with a boolean. + */ + hasRatings(component: string, ratingArea: string, contextLevel?: string, instanceId?: number, itemSetId?: number, + siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + const conditions: any = { + component, + ratingarea: ratingArea + }; + if (contextLevel != null && instanceId != null) { + conditions.contextlevel = contextLevel; + conditions.instanceId = instanceId; + } + if (itemSetId != null) { + conditions.itemsetid = itemSetId; + } + + return this.utils.promiseWorks(site.getDb().recordExists(CoreRatingOfflineProvider.RATINGS_TABLE, conditions)); + }); + } +} diff --git a/src/core/rating/providers/rating.ts b/src/core/rating/providers/rating.ts new file mode 100644 index 000000000..8756daec7 --- /dev/null +++ b/src/core/rating/providers/rating.ts @@ -0,0 +1,352 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { CoreSiteWSPreSets } from '@classes/site'; +import { CoreAppProvider } from '@providers/app'; +import { CoreEventsProvider } from '@providers/events'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreUserProvider } from '@core/user/providers/user'; +import { CoreUtilsProvider } from '@providers/utils/utils'; +import { CoreRatingOfflineProvider } from './offline'; + +/** + * Structure of the rating info returned by web services. + */ +export interface CoreRatingInfo { + contextid: number; + component: string; + ratingarea: string; + canviewall: boolean; + canviewany: boolean; + scales?: CoreRatingScale[]; + ratings?: CoreRatingInfoItem[]; +} + +/** + * Structure of scales in the rating info. + */ +export interface CoreRatingScale { + id: number; + courseid?: number; + name?: string; + max: number; + isnumeric: boolean; + items?: {value: number, name: string}[]; +} + +/** + * Structure of items in the rating info. + */ +export interface CoreRatingInfoItem { + itemid: number; + scaleid?: number; + scale?: CoreRatingScale; + userid?: number; + aggregate?: number; + aggregatestr?: string; + count?: number; + rating?: number; + canrate?: boolean; + canviewaggregate?: boolean; +} + +/** + * Structure of a rating returned by the item ratings web service. + */ +export interface CoreRatingItemRating { + id: number; + userid: number; + userpictureurl: string; + userfullname: string; + rating: string; + timemodified: number; +} + +/** + * Service to handle ratings. + */ +@Injectable() +export class CoreRatingProvider { + + static AGGREGATE_NONE = 0; // No ratings. + static AGGREGATE_AVERAGE = 1; + static AGGREGATE_COUNT = 2; + static AGGREGATE_MAXIMUM = 3; + static AGGREGATE_MINIMUM = 4; + static AGGREGATE_SUM = 5; + + static UNSET_RATING = -999; + + static AGGREGATE_CHANGED_EVENT = 'core_rating_aggregate_changed'; + static RATING_SAVED_EVENT = 'core_rating_rating_saved'; + + protected ROOT_CACHE_KEY = 'CoreRating:'; + + constructor(private appProvider: CoreAppProvider, + private eventsProvider: CoreEventsProvider, + private sitesProvider: CoreSitesProvider, + private userProvider: CoreUserProvider, + private utils: CoreUtilsProvider, + private ratingOffline: CoreRatingOfflineProvider) {} + + /** + * Returns whether the web serivce to add ratings is available. + * + * @return {boolean} If WS is abalaible. + * @since 3.2 + */ + isAddRatingWSAvailable(): boolean { + return this.sitesProvider.wsAvailableInCurrentSite('core_rating_add_rating'); + } + + /** + * Add a rating to an item. + * + * @param {string} component Component. Example: "mod_forum". + * @param {string} ratingArea Rating area. Example: "post". + * @param {string} contextLevel Context level: course, module, user, etc. + * @param {number} instanceId Context instance id. + * @param {number} itemId Item id. Example: forum post id. + * @param {number} itemSetId Item set id. Example: forum discussion id. + * @param {number} courseId Course id. + * @param {number} scaleId Scale id. + * @param {number} rating Rating value. Use CoreRatingProvider.UNSET_RATING to delete rating. + * @param {number} ratedUserId Rated user id. + * @param {number} aggregateMethod Aggregate method. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with the aggregated rating or null if stored offline. + * @since 3.2 + */ + addRating(component: string, ratingArea: string, contextLevel: string, instanceId: number, itemId: number, itemSetId: number, + courseId: number, scaleId: number, rating: number, ratedUserId: number, aggregateMethod: number, siteId?: string): + Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + // Convenience function to store a rating to be synchronized later. + const storeOffline = (): Promise => { + return this.ratingOffline.addRating(component, ratingArea, contextLevel, instanceId, itemId, itemSetId, courseId, + scaleId, rating, ratedUserId, aggregateMethod, siteId).then(() => { + this.eventsProvider.trigger(CoreRatingProvider.RATING_SAVED_EVENT, { + component, + ratingArea, + contextLevel, + instanceId, + itemSetId, + itemId + }, siteId); + + return null; + }); + }; + + if (!this.appProvider.isOnline()) { + // App is offline, store the action. + return storeOffline(); + } + + return this.ratingOffline.deleteRating(component, ratingArea, contextLevel, instanceId, itemId, siteId).then(() => { + return this.addRatingOnline(component, ratingArea, contextLevel, instanceId, itemId, scaleId, rating, ratedUserId, + aggregateMethod, siteId).catch((error) => { + + if (this.utils.isWebServiceError(error)) { + // The WebService has thrown an error or offline not supported, reject. + return Promise.reject(error); + } + + // Couldn't connect to server, store offline. + return storeOffline(); + }); + }); + } + + /** + * Add a rating to an item. It will fail if offline or cannot connect. + * + * @param {string} component Component. Example: "mod_forum". + * @param {string} ratingArea Rating area. Example: "post". + * @param {string} contextLevel Context level: course, module, user, etc. + * @param {number} instanceId Context instance id. + * @param {number} itemId Item id. Example: forum post id. + * @param {number} scaleId Scale id. + * @param {number} rating Rating value. Use CoreRatingProvider.UNSET_RATING to delete rating. + * @param {number} ratedUserId Rated user id. + * @param {number} aggregateMethod Aggregate method. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with the aggregated rating. + * @since 3.2 + */ + addRatingOnline(component: string, ratingArea: string, contextLevel: string, instanceId: number, itemId: number, + scaleId: number, rating: number, ratedUserId: number, aggregateMethod: number, siteId?: string): + Promise { + + return this.sitesProvider.getSite(siteId).then((site) => { + const params = { + contextlevel: contextLevel, + instanceid: instanceId, + component: component, + ratingarea: ratingArea, + itemid: itemId, + scaleid: scaleId, + rating: rating, + rateduserid: ratedUserId, + aggregation: aggregateMethod + }; + + return site.write('core_rating_add_rating', params).then((response) => { + return this.invalidateRatingItems(contextLevel, instanceId, component, ratingArea, itemId, scaleId).then(() => { + this.eventsProvider.trigger(CoreRatingProvider.AGGREGATE_CHANGED_EVENT, { + contextLevel, + instanceId, + component, + ratingArea, + itemId, + aggregate: response.aggregate, + count: response.count + }); + + return response; + }); + }); + }); + } + + /** + * Get item ratings. + * + * @param {string} contextLevel Context level: course, module, user, etc. + * @param {number} instanceId Context instance id. + * @param {string} component Component. Example: "mod_forum". + * @param {string} ratingArea Rating area. Example: "post". + * @param {number} itemId Item id. Example: forum post id. + * @param {number} scaleId Scale id. + * @param {string} [sort="timemodified"] Sort field. + * @param {number} [courseId] Course id. Used for fetching user profiles. + * @param {string} [siteId] Site ID. If not defined, current site. + * @param {boolean} [ignoreCache=false] True if it should ignore cached data (it will always fail in offline or server down). + * @return {Promise} Promise resolved with the list of ratings. + */ + getItemRatings(contextLevel: string, instanceId: number, component: string, ratingArea: string, itemId: number, + scaleId: number, sort: string = 'timemodified', courseId?: number, siteId?: string, ignoreCache: boolean = false): + Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + const params = { + contextlevel: contextLevel, + instanceid: instanceId, + component: component, + ratingarea: ratingArea, + itemid: itemId, + scaleid: scaleId, + sort: sort + }; + const preSets: CoreSiteWSPreSets = { + cacheKey: this.getItemRatingsCacheKey(contextLevel, instanceId, component, ratingArea, itemId, scaleId, sort) + }; + if (ignoreCache) { + preSets.getFromCache = false; + preSets.emergencyCache = false; + } + + return site.read('core_rating_get_item_ratings', params, preSets).then((response) => { + if (!response || !response.ratings) { + return Promise.reject(null); + } + + // We need to fetch profiles because the returned profile pictures are incorrect. + const promises = response.ratings.map((rating: CoreRatingItemRating) => { + return this.userProvider.getProfile(rating.userid, courseId, true, site.id).then((user) => { + rating.userpictureurl = user.profileimageurl; + }).catch(() => { + // Ignore error. + rating.userpictureurl = null; + }); + }); + + return Promise.all(promises).then(() => { + return response.ratings; + }); + }); + }); + } + + /** + * Invalidate item ratings. + * + * @param {string} contextLevel Context level: course, module, user, etc. + * @param {number} instanceId Context instance id. + * @param {string} component Component. Example: "mod_forum". + * @param {string} ratingArea Rating area. Example: "post". + * @param {number} itemId Item id. Example: forum post id. + * @param {number} scaleId Scale id. + * @param {string} [sort="timemodified"] Sort field. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the data is invalidated. + */ + invalidateRatingItems(contextLevel: string, instanceId: number, component: string, ratingArea: string, + itemId: number, scaleId: number, sort: string = 'timemodified', siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + const key = this.getItemRatingsCacheKey(contextLevel, instanceId, component, ratingArea, itemId, scaleId, sort); + + return site.invalidateWsCacheForKey(key); + }); + } + + /** + * Prefetch individual ratings. + * + * This function should be called from the prefetch handler of activities with ratings. + * + * @param {string} contextLevel Context level: course, module, user, etc. + * @param {number} instanceId Instance id. + * @param {string} [siteId] Site id. If not defined, current site. + * @param {number} [courseId] Course id. Used for prefetching user profiles. + * @param {CoreRatingInfo} [ratingInfo] Rating info returned by web services. + * @return {Promise} Promise resolved when done. + */ + prefetchRatings(contextLevel: string, instanceId: number, scaleId: number, courseId?: number, ratingInfo?: CoreRatingInfo, + siteId?: string): Promise { + if (!ratingInfo || !ratingInfo.ratings) { + return Promise.resolve(); + } + + return this.sitesProvider.getSite(siteId).then((site) => { + const promises = ratingInfo.ratings.map((item) => { + return this.getItemRatings(contextLevel, instanceId, ratingInfo.component, ratingInfo.ratingarea, item.itemid, + scaleId, undefined, courseId, site.id, true).then((ratings) => { + const userIds = ratings.map((rating: CoreRatingItemRating) => rating.userid); + + return this.userProvider.prefetchProfiles(userIds, courseId, site.id); + }); + }); + + return Promise.all(promises); + }); + } + + /** + * Get cache key for rating items WS calls. + * + * @param {string} contextLevel Context level: course, module, user, etc. + * @param {string} component Component. Example: "mod_forum". + * @param {string} ratingArea Rating area. Example: "post". + * @param {number} itemId Item id. Example: forum post id. + * @param {number} scaleId Scale id. + * @param {string} sort Sort field. + * @return {string} Cache key. + */ + protected getItemRatingsCacheKey(contextLevel: string, instanceId: number, component: string, ratingArea: string, + itemId: number, scaleId: number, sort: string): string { + return `${this.ROOT_CACHE_KEY}${contextLevel}:${instanceId}:${component}:${ratingArea}:${itemId}:${scaleId}:${sort}`; + } +} diff --git a/src/core/rating/providers/sync.ts b/src/core/rating/providers/sync.ts new file mode 100644 index 000000000..cffa42d42 --- /dev/null +++ b/src/core/rating/providers/sync.ts @@ -0,0 +1,196 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { CoreSyncBaseProvider } from '@classes/base-sync'; +import { CoreAppProvider } from '@providers/app'; +import { CoreLoggerProvider } from '@providers/logger'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreSyncProvider } from '@providers/sync'; +import { CoreTextUtilsProvider } from '@providers/utils/text'; +import { CoreTimeUtilsProvider } from '@providers/utils/time'; +import { CoreUtilsProvider } from '@providers/utils/utils'; +import { CoreRatingProvider } from './rating'; +import { CoreRatingOfflineProvider, CoreRatingItemSet } from './offline'; +import { CoreEventsProvider } from '@providers/events'; + +/** + * Service to sync ratings. + */ +@Injectable() +export class CoreRatingSyncProvider extends CoreSyncBaseProvider { + + static SYNCED_EVENT = 'core_rating_synced'; + + constructor(translate: TranslateService, + appProvider: CoreAppProvider, + private eventsProvider: CoreEventsProvider, + loggerProvider: CoreLoggerProvider, + sitesProvider: CoreSitesProvider, + syncProvider: CoreSyncProvider, + textUtils: CoreTextUtilsProvider, + timeUtils: CoreTimeUtilsProvider, + private utils: CoreUtilsProvider, + private ratingProvider: CoreRatingProvider, + private ratingOffline: CoreRatingOfflineProvider) { + + super('CoreRatingSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, timeUtils); + } + + /** + * Try to synchronize all the ratings of a certain component, instance or item set. + * + * This function should be called from the sync provider of activities with ratings. + * + * @param {string} component Component. Example: "mod_forum". + * @param {string} ratingArea Rating Area. Example: "post". + * @param {string} [contextLevel] Context level: course, module, user, etc. + * @param {numnber} [instanceId] Context instance id. + * @param {number} [itemSetId] Item set id. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved if sync is successful, rejected if sync fails. + */ + syncRatings(component: string, ratingArea: string, contextLevel?: string, instanceId?: number, itemSetId?: number, + siteId?: string): Promise<{itemSet: CoreRatingItemSet, updated: boolean, warnings: string[]}[]> { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + return this.ratingOffline.getItemSets(component, ratingArea, contextLevel, instanceId, itemSetId, siteId) + .then((itemSets) => { + const results = []; + const promises = itemSets.map((itemSet) => { + return this.syncItemSetIfNeeded(component, ratingArea, itemSet.contextLevel, itemSet.instanceId, + itemSet.itemSetId, siteId).then((result) => { + if (result.updated) { + // Sync successful, send event. + this.eventsProvider.trigger(CoreRatingSyncProvider.SYNCED_EVENT, { + ...itemSet, + warnings: result.warnings + }, siteId); + } + + results.push({itemSet, ...result}); + }); + }); + + return Promise.all(promises).then(() => { + return results; + }); + }); + } + + /** + * Sync ratings of an item set only if a certain time has passed since the last time. + * + * @param {string} component Component. Example: "mod_forum". + * @param {string} ratingArea Rating Area. Example: "post". + * @param {string} contextLevel Context level: course, module, user, etc. + * @param {number} instanceId Context instance id. + * @param {number} itemSetId Item set id. Example: forum discussion id. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when ratings are synced or if it doesn't need to be synced. + */ + protected syncItemSetIfNeeded(component: string, ratingArea: string, contextLevel: string, instanceId: number, + itemSetId: number, siteId?: string): Promise<{updated: boolean, warnings: string[]}> { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + const syncId = this.getItemSetSyncId(component, ratingArea, contextLevel, instanceId, itemSetId); + + return this.isSyncNeeded(syncId, siteId).then((needed) => { + if (needed) { + return this.syncItemSet(component, ratingArea, contextLevel, instanceId, itemSetId, siteId); + } + }); + } + + /** + * Synchronize all offline ratings of an item set. + * + * @param {string} component Component. Example: "mod_forum". + * @param {string} ratingArea Rating Area. Example: "post". + * @param {string} contextLevel Context level: course, module, user, etc. + * @param {number} instanceId Context instance id. + * @param {number} itemSetId Item set id. Example: forum discussion id. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved if sync is successful, rejected otherwise. + */ + protected syncItemSet(component: string, ratingArea: string, contextLevel: string, instanceId: number, itemSetId: number, + siteId?: string): Promise<{updated: boolean, warnings: string[]}> { + + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + const syncId = this.getItemSetSyncId(component, ratingArea, contextLevel, instanceId, itemSetId); + if (this.isSyncing(syncId, siteId)) { + // There's already a sync ongoing for this item set, return the promise. + return this.getOngoingSync(syncId, siteId); + } + + this.logger.debug(`Try to sync ratings of component '${component}' rating area '${ratingArea}'` + + ` context level '${contextLevel}' instance ${instanceId} item set ${itemSetId}`); + + let updated = false; + const warnings = []; + + return this.ratingOffline.getRatings(component, ratingArea, contextLevel, instanceId, itemSetId, siteId).then((ratings) => { + if (!ratings.length) { + // Nothing to sync. + return; + } else if (!this.appProvider.isOnline()) { + // Cannot sync in offline. + return Promise.reject(null); + } + + const promises = ratings.map((rating) => { + return this.ratingProvider.addRatingOnline(component, ratingArea, rating.contextlevel, rating.instanceid, + rating.itemid, rating.scaleid, rating.rating, rating.rateduserid, rating.aggregation, siteId) + .catch((error) => { + if (this.utils.isWebServiceError(error)) { + warnings.push(this.textUtils.getErrorMessageFromError(error)); + } else { + // Couldn't connect to server, reject. + return Promise.reject(error); + } + }).then(() => { + updated = true; + + return this.ratingOffline.deleteRating(component, ratingArea, rating.contextlevel, rating.instanceid, + rating.itemid, siteId).finally(() => { + return this.ratingProvider.invalidateRatingItems(rating.contextlevel, rating.instanceid, component, + ratingArea, rating.itemid, rating.scaleid, undefined, siteId); + }); + }); + }); + + return Promise.all(promises).then(() => { + // All done, return the warnings. + return { updated, warnings }; + }); + }); + } + + /** + * Get the sync id of an item set. + * + * @param {string} component Component. Example: "mod_forum". + * @param {string} ratingArea Rating Area. Example: "post". + * @param {string} contextLevel Context level: course, module, user, etc. + * @param {number} instanceId Context instance id. + * @param {number} itemSetId Item set id. Example: forum discussion id. + * @return {string} Sync id. + */ + protected getItemSetSyncId(component: string, ratingArea: string, contextLevel: string, instanceId: number, itemSetId: number): + string { + return `itemSet#${component}#${ratingArea}#${contextLevel}#${instanceId}#${itemSetId}`; + } +} diff --git a/src/core/rating/rating.module.ts b/src/core/rating/rating.module.ts new file mode 100644 index 000000000..e7c512467 --- /dev/null +++ b/src/core/rating/rating.module.ts @@ -0,0 +1,31 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { CoreRatingProvider } from './providers/rating'; +import { CoreRatingOfflineProvider } from './providers/offline'; +import { CoreRatingSyncProvider } from './providers/sync'; + +@NgModule({ + declarations: [ + ], + imports: [ + ], + providers: [ + CoreRatingProvider, + CoreRatingOfflineProvider, + CoreRatingSyncProvider + ] +}) +export class CoreRatingModule {} From 0f70d83682601468920bb012b67867d7a017a877 Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Thu, 28 Feb 2019 17:34:45 +0100 Subject: [PATCH 114/191] MOBILE-1633 forum: Post ratings --- .../mod/forum/components/components.module.ts | 4 +- .../index/addon-mod-forum-index.html | 2 +- src/addon/mod/forum/components/index/index.ts | 49 ++++++++++++------ .../components/post/addon-mod-forum-post.html | 2 + src/addon/mod/forum/components/post/post.ts | 2 + .../forum/pages/discussion/discussion.html | 8 +-- .../mod/forum/pages/discussion/discussion.ts | 49 ++++++++++++++++-- src/addon/mod/forum/providers/forum.ts | 7 +-- .../mod/forum/providers/prefetch-handler.ts | 21 +++++--- src/addon/mod/forum/providers/sync.ts | 50 ++++++++++++++++++- 10 files changed, 155 insertions(+), 39 deletions(-) diff --git a/src/addon/mod/forum/components/components.module.ts b/src/addon/mod/forum/components/components.module.ts index e9b656899..0f3bf1b10 100644 --- a/src/addon/mod/forum/components/components.module.ts +++ b/src/addon/mod/forum/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 { CoreRatingComponentsModule } from '@core/rating/components/components.module'; import { AddonModForumIndexComponent } from './index/index'; import { AddonModForumPostComponent } from './post/post'; @@ -35,7 +36,8 @@ import { AddonModForumPostComponent } from './post/post'; CoreComponentsModule, CoreDirectivesModule, CorePipesModule, - CoreCourseComponentsModule + CoreCourseComponentsModule, + CoreRatingComponentsModule ], 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 e4d9b273d..9eab4d526 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 @@ -21,7 +21,7 @@ - + {{ 'core.hasdatatosync' | translate:{$a: moduleName} }} diff --git a/src/addon/mod/forum/components/index/index.ts b/src/addon/mod/forum/components/index/index.ts index 774c8ae4d..036697410 100644 --- a/src/addon/mod/forum/components/index/index.ts +++ b/src/addon/mod/forum/components/index/index.ts @@ -19,6 +19,9 @@ import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate'; import { CoreUserProvider } from '@core/user/providers/user'; import { CoreGroupsProvider } from '@providers/groups'; +import { CoreRatingProvider } from '@core/rating/providers/rating'; +import { CoreRatingOfflineProvider } from '@core/rating/providers/offline'; +import { CoreRatingSyncProvider } from '@core/rating/providers/sync'; import { AddonModForumProvider } from '../../providers/forum'; import { AddonModForumHelperProvider } from '../../providers/helper'; import { AddonModForumOfflineProvider } from '../../providers/offline'; @@ -56,6 +59,10 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom protected newDiscObserver: any; protected viewDiscObserver: any; + hasOfflineRatings: boolean; + protected ratingOfflineObserver: any; + protected ratingSyncObserver: any; + constructor(injector: Injector, @Optional() protected content: Content, protected navCtrl: NavController, @@ -66,7 +73,8 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom protected forumOffline: AddonModForumOfflineProvider, protected forumSync: AddonModForumSyncProvider, protected prefetchDelegate: CoreCourseModulePrefetchDelegate, - protected prefetchHandler: AddonModForumPrefetchHandler) { + protected prefetchHandler: AddonModForumPrefetchHandler, + protected ratingOffline: CoreRatingOfflineProvider) { super(injector); } @@ -100,6 +108,22 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom } }, this.sitesProvider.getCurrentSiteId()); + // Listen for offline ratings saved and synced. + this.ratingOfflineObserver = this.eventsProvider.on(CoreRatingProvider.RATING_SAVED_EVENT, (data) => { + if (this.forum && data.component == 'mod_forum' && data.ratingArea == 'post' && + data.contextLevel == 'module' && data.instanceId == this.forum.cmid) { + this.hasOfflineRatings = true; + } + }); + this.ratingSyncObserver = this.eventsProvider.on(CoreRatingSyncProvider.SYNCED_EVENT, (data) => { + if (this.forum && data.component == 'mod_forum' && data.ratingArea == 'post' && + data.contextLevel == 'module' && data.instanceId == this.forum.cmid) { + this.ratingOffline.hasRatings('mod_forum', 'post', 'module', this.forum.cmid).then((hasRatings) => { + this.hasOfflineRatings = hasRatings; + }); + } + }); + this.loadContent(false, true).then(() => { if (!this.forum) { return; @@ -178,6 +202,9 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom return Promise.all([ this.fetchOfflineDiscussion(), this.fetchDiscussions(refresh), + this.ratingOffline.hasRatings('mod_forum', 'post', 'module', this.forum.cmid).then((hasRatings) => { + this.hasOfflineRatings = hasRatings; + }) ]); }).catch((message) => { if (!refresh) { @@ -351,21 +378,9 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom protected sync(): Promise { const promises = []; - promises.push(this.forumSync.syncForumDiscussions(this.forum.id).then((result) => { - if (result.warnings && result.warnings.length) { - this.domUtils.showErrorModal(result.warnings[0]); - } - - return result; - })); - - promises.push(this.forumSync.syncForumReplies(this.forum.id).then((result) => { - if (result.warnings && result.warnings.length) { - this.domUtils.showErrorModal(result.warnings[0]); - } - - return result; - })); + promises.push(this.forumSync.syncForumDiscussions(this.forum.id)); + promises.push(this.forumSync.syncForumReplies(this.forum.id)); + promises.push(this.forumSync.syncRatings(this.forum.cmid)); return Promise.all(promises).then((results) => { return results.reduce((a, b) => ({ @@ -476,5 +491,7 @@ export class AddonModForumIndexComponent extends CoreCourseModuleMainActivityCom this.newDiscObserver && this.newDiscObserver.off(); this.replyObserver && this.replyObserver.off(); this.viewDiscObserver && this.viewDiscObserver.off(); + this.ratingOfflineObserver && this.ratingOfflineObserver.off(); + this.ratingSyncObserver && this.ratingSyncObserver.off(); } } 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 cad199ead..253e7208a 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 @@ -23,6 +23,8 @@
+ +
diff --git a/src/core/course/providers/helper.ts b/src/core/course/providers/helper.ts index 448af929d..941a2f9c4 100644 --- a/src/core/course/providers/helper.ts +++ b/src/core/course/providers/helper.ts @@ -1234,7 +1234,7 @@ export class CoreCourseHelperProvider { prefetchSection(section: any, courseId: number, sections?: any[]): Promise { if (section.id != CoreCourseProvider.ALL_SECTIONS_ID) { // Download only this section. - return this.prefetchSingleSectionIfNeeded(section, courseId).then(() => { + return this.prefetchSingleSectionIfNeeded(section, courseId).finally(() => { // Calculate the status of the section that finished. return this.calculateSectionStatus(section, courseId); }); @@ -1246,7 +1246,7 @@ export class CoreCourseHelperProvider { section.isDownloading = true; sections.forEach((section) => { if (section.id != CoreCourseProvider.ALL_SECTIONS_ID) { - promises.push(this.prefetchSingleSectionIfNeeded(section, courseId).then(() => { + promises.push(this.prefetchSingleSectionIfNeeded(section, courseId).finally(() => { // Calculate the status of the section that finished. return this.calculateSectionStatus(section, courseId).then((result) => { // Calculate "All sections" status. diff --git a/src/core/course/providers/module-prefetch-delegate.ts b/src/core/course/providers/module-prefetch-delegate.ts index 5d43db704..24ca8c933 100644 --- a/src/core/course/providers/module-prefetch-delegate.ts +++ b/src/core/course/providers/module-prefetch-delegate.ts @@ -1208,7 +1208,7 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { }); // Set the promise. - prefetchData.promise = Promise.all(promises).finally(() => { + prefetchData.promise = this.utils.allPromises(promises).finally(() => { // Unsubscribe all observers. prefetchData.subscriptions.forEach((subscription: Subscription) => { subscription.unsubscribe(); From c7529954e7a6e2d470701a78ed0f48058e4c80d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 7 Mar 2019 15:56:29 +0100 Subject: [PATCH 132/191] MOBILE-2911 book: Move navigation to lateral --- .../mod/book/components/components.module.ts | 10 ++---- .../index/addon-mod-book-index.html | 2 +- src/addon/mod/book/components/index/index.ts | 23 ++++++++------ ...don-mod-assign-submission-toc-popover.html | 5 --- src/addon/mod/book/lang/en.json | 3 +- src/addon/mod/book/pages/toc/toc.html | 19 ++++++++++++ src/addon/mod/book/pages/toc/toc.module.ts | 31 +++++++++++++++++++ .../toc-popover.ts => pages/toc/toc.ts} | 20 +++++++++--- src/app/app.scss | 28 +++++++++++------ src/assets/lang/en.json | 1 + src/theme/variables.scss | 14 +++++++++ 11 files changed, 118 insertions(+), 38 deletions(-) delete mode 100644 src/addon/mod/book/components/toc-popover/addon-mod-assign-submission-toc-popover.html create mode 100644 src/addon/mod/book/pages/toc/toc.html create mode 100644 src/addon/mod/book/pages/toc/toc.module.ts rename src/addon/mod/book/{components/toc-popover/toc-popover.ts => pages/toc/toc.ts} (71%) diff --git a/src/addon/mod/book/components/components.module.ts b/src/addon/mod/book/components/components.module.ts index b0ace2f26..54e83ef50 100644 --- a/src/addon/mod/book/components/components.module.ts +++ b/src/addon/mod/book/components/components.module.ts @@ -20,12 +20,10 @@ import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; import { CoreCourseComponentsModule } from '@core/course/components/components.module'; import { AddonModBookIndexComponent } from './index/index'; -import { AddonModBookTocPopoverComponent } from './toc-popover/toc-popover'; @NgModule({ declarations: [ - AddonModBookIndexComponent, - AddonModBookTocPopoverComponent + AddonModBookIndexComponent ], imports: [ CommonModule, @@ -38,12 +36,10 @@ import { AddonModBookTocPopoverComponent } from './toc-popover/toc-popover'; providers: [ ], exports: [ - AddonModBookIndexComponent, - AddonModBookTocPopoverComponent + AddonModBookIndexComponent ], entryComponents: [ - AddonModBookIndexComponent, - AddonModBookTocPopoverComponent + AddonModBookIndexComponent ] }) export class AddonModBookComponentsModule {} 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 51fa95154..5d872fd78 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 @@ -1,6 +1,6 @@ - diff --git a/src/addon/mod/book/components/index/index.ts b/src/addon/mod/book/components/index/index.ts index bcb78e821..908ab8cdd 100644 --- a/src/addon/mod/book/components/index/index.ts +++ b/src/addon/mod/book/components/index/index.ts @@ -13,13 +13,12 @@ // limitations under the License. import { Component, Optional, Injector, Input } from '@angular/core'; -import { Content, PopoverController } from 'ionic-angular'; +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 { AddonModBookPrefetchHandler } from '../../providers/prefetch-handler'; -import { AddonModBookTocPopoverComponent } from '../../components/toc-popover/toc-popover'; /** * Component that displays a book. @@ -42,7 +41,7 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp constructor(injector: Injector, private bookProvider: AddonModBookProvider, private courseProvider: CoreCourseProvider, private appProvider: CoreAppProvider, private prefetchDelegate: AddonModBookPrefetchHandler, - private popoverCtrl: PopoverController, @Optional() private content: Content) { + private modalCtrl: ModalController, @Optional() private content: Content) { super(injector); } @@ -61,15 +60,19 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp * @param {MouseEvent} event Event. */ showToc(event: MouseEvent): void { - const popover = this.popoverCtrl.create(AddonModBookTocPopoverComponent, { - chapters: this.chapters + // Create the toc modal. + const modal = this.modalCtrl.create('AddonModBookTocPage', { + chapters: this.chapters, + selected: this.currentChapter + }, { cssClass: 'core-modal-lateral' }); + + modal.onDidDismiss((chapterId) => { + if (chapterId) { + this.changeChapter(chapterId); + } }); - popover.onDidDismiss((chapterId) => { - this.changeChapter(chapterId); - }); - - popover.present({ + modal.present({ ev: event }); } diff --git a/src/addon/mod/book/components/toc-popover/addon-mod-assign-submission-toc-popover.html b/src/addon/mod/book/components/toc-popover/addon-mod-assign-submission-toc-popover.html deleted file mode 100644 index 6d3dca6f0..000000000 --- a/src/addon/mod/book/components/toc-popover/addon-mod-assign-submission-toc-popover.html +++ /dev/null @@ -1,5 +0,0 @@ - - -

{{chapter.title}}

-
- diff --git a/src/addon/mod/book/lang/en.json b/src/addon/mod/book/lang/en.json index bf58bf921..7d1140fe4 100644 --- a/src/addon/mod/book/lang/en.json +++ b/src/addon/mod/book/lang/en.json @@ -1,4 +1,5 @@ { "errorchapter": "Error reading chapter of book.", - "modulenameplural": "Books" + "modulenameplural": "Books", + "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 new file mode 100644 index 000000000..30e22bea0 --- /dev/null +++ b/src/addon/mod/book/pages/toc/toc.html @@ -0,0 +1,19 @@ + + + {{ 'addon.mod_book.toc' | translate }} + + + + + + + + diff --git a/src/addon/mod/book/pages/toc/toc.module.ts b/src/addon/mod/book/pages/toc/toc.module.ts new file mode 100644 index 000000000..88e436a25 --- /dev/null +++ b/src/addon/mod/book/pages/toc/toc.module.ts @@ -0,0 +1,31 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreDirectivesModule } from '@directives/directives.module'; +import { AddonModBookTocPage } from './toc'; + +@NgModule({ + declarations: [ + AddonModBookTocPage, + ], + imports: [ + CoreDirectivesModule, + IonicPageModule.forChild(AddonModBookTocPage), + TranslateModule.forChild() + ], +}) +export class AddonModBookTocPageModule {} diff --git a/src/addon/mod/book/components/toc-popover/toc-popover.ts b/src/addon/mod/book/pages/toc/toc.ts similarity index 71% rename from src/addon/mod/book/components/toc-popover/toc-popover.ts rename to src/addon/mod/book/pages/toc/toc.ts index 416044a3f..8048eacb8 100644 --- a/src/addon/mod/book/components/toc-popover/toc-popover.ts +++ b/src/addon/mod/book/pages/toc/toc.ts @@ -13,21 +13,24 @@ // limitations under the License. import { Component } from '@angular/core'; -import { NavParams, ViewController } from 'ionic-angular'; +import { IonicPage, NavParams, ViewController } from 'ionic-angular'; import { AddonModBookTocChapter } from '../../providers/book'; /** - * Component to display the TOC of a book. + * Modal to display the TOC of a book. */ +@IonicPage({ segment: 'addon-mod-book-toc-modal' }) @Component({ - selector: 'addon-mod-book-toc-popover', - templateUrl: 'addon-mod-assign-submission-toc-popover.html' + selector: 'page-addon-mod-book-toc', + templateUrl: 'toc.html' }) -export class AddonModBookTocPopoverComponent { +export class AddonModBookTocPage { chapters: AddonModBookTocChapter[]; + selected: number; constructor(navParams: NavParams, private viewCtrl: ViewController) { this.chapters = navParams.get('chapters') || []; + this.selected = navParams.get('selected'); } /** @@ -38,4 +41,11 @@ export class AddonModBookTocPopoverComponent { loadChapter(id: string): void { this.viewCtrl.dismiss(id); } + + /** + * Close modal. + */ + closeModal(): void { + this.viewCtrl.dismiss(); + } } diff --git a/src/app/app.scss b/src/app/app.scss index 679a902f5..f9c07d4f9 100644 --- a/src/app/app.scss +++ b/src/app/app.scss @@ -103,6 +103,10 @@ ion-app.app-root { border: 0; } + .core-nav-item-selected, .item.core-nav-item-selected { + @include core-selected-item($core-splitview-selected); + } + // Recover borders on items inside cards. .card.with-borders .core-as-item, .core-as-item { @@ -745,15 +749,21 @@ ion-app.app-root { height: 100% !important; } - @media only screen and (min-height: 600px) and (min-width: 768px) { - .core-modal-lateral .modal-wrapper { - position: absolute; - @include position(0 !important, 0 !important, 0 !important, auto); - display: block; - height: 100% !important; - width: auto; - min-width: 400px; - } + @media only screen and (min-height: 400px) and (min-width: 300px) { + .core-modal-lateral { + .modal-wrapper { + position: absolute; + @include position(0 !important, 0 !important, 0 !important, auto); + display: block; + height: 100% !important; + width: auto; + min-width: 300px; + box-shadow: 0 28px 48px rgba(0, 0, 0, 0.4); + } + ion-backdrop { + visibility: visible; + } + } } .has-fab .scroll-content{ diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index c883b3e34..a6e3043e2 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -344,6 +344,7 @@ "addon.mod_assign_submission_onlinetext.pluginname": "Online text submissions", "addon.mod_book.errorchapter": "Error reading chapter of book.", "addon.mod_book.modulenameplural": "Books", + "addon.mod_book.toc": "Table of contents", "addon.mod_chat.beep": "Beep", "addon.mod_chat.chatreport": "Chat sessions", "addon.mod_chat.currentusers": "Current users", diff --git a/src/theme/variables.scss b/src/theme/variables.scss index da912fa66..80a7b6d61 100644 --- a/src/theme/variables.scss +++ b/src/theme/variables.scss @@ -473,6 +473,20 @@ $core-dd-question-colors: $white, $blue-light, #DCDCDC, #D8BFD8, #87CEFA, #DAA52 } } +@mixin core-selected-item($selected-color) { + @include border-start(5px, solid, $selected-color); + + &.item-md { + @include padding(null, null, null, $item-md-padding-start - 5px); + } + &.item-ios { + @include padding(null, null, null, $item-ios-padding-start - 5px); + } + &.item-wp { + @include padding(null, null, null, $item-wp-padding-start - 5px); + } +} + // Font Awesome $fa-font-path: $font-path; @import "font-awesome"; From 0eda77d0958872163ce7b4b88a4112365070596f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 7 Mar 2019 16:21:15 +0100 Subject: [PATCH 133/191] MOBILE-2911 scorm: Move navigation to lateral --- .../mod/scorm/components/components.module.ts | 10 ++---- .../addon-mod-scorm-toc-popover.html | 19 ----------- src/addon/mod/scorm/lang/en.json | 1 + src/addon/mod/scorm/pages/player/player.html | 2 +- src/addon/mod/scorm/pages/player/player.ts | 18 +++++----- src/addon/mod/scorm/pages/toc/toc.html | 33 +++++++++++++++++++ src/addon/mod/scorm/pages/toc/toc.module.ts | 31 +++++++++++++++++ .../toc-popover.ts => pages/toc/toc.ts} | 20 ++++++++--- src/assets/lang/en.json | 1 + 9 files changed, 94 insertions(+), 41 deletions(-) delete mode 100644 src/addon/mod/scorm/components/toc-popover/addon-mod-scorm-toc-popover.html create mode 100644 src/addon/mod/scorm/pages/toc/toc.html create mode 100644 src/addon/mod/scorm/pages/toc/toc.module.ts rename src/addon/mod/scorm/{components/toc-popover/toc-popover.ts => pages/toc/toc.ts} (76%) diff --git a/src/addon/mod/scorm/components/components.module.ts b/src/addon/mod/scorm/components/components.module.ts index 197d63ad1..2a9c6569b 100644 --- a/src/addon/mod/scorm/components/components.module.ts +++ b/src/addon/mod/scorm/components/components.module.ts @@ -20,12 +20,10 @@ import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; import { CoreCourseComponentsModule } from '@core/course/components/components.module'; import { AddonModScormIndexComponent } from './index/index'; -import { AddonModScormTocPopoverComponent } from './toc-popover/toc-popover'; @NgModule({ declarations: [ - AddonModScormIndexComponent, - AddonModScormTocPopoverComponent + AddonModScormIndexComponent ], imports: [ CommonModule, @@ -38,12 +36,10 @@ import { AddonModScormTocPopoverComponent } from './toc-popover/toc-popover'; providers: [ ], exports: [ - AddonModScormIndexComponent, - AddonModScormTocPopoverComponent + AddonModScormIndexComponent ], entryComponents: [ - AddonModScormIndexComponent, - AddonModScormTocPopoverComponent + AddonModScormIndexComponent ] }) export class AddonModScormComponentsModule {} diff --git a/src/addon/mod/scorm/components/toc-popover/addon-mod-scorm-toc-popover.html b/src/addon/mod/scorm/components/toc-popover/addon-mod-scorm-toc-popover.html deleted file mode 100644 index 8b4dffff1..000000000 --- a/src/addon/mod/scorm/components/toc-popover/addon-mod-scorm-toc-popover.html +++ /dev/null @@ -1,19 +0,0 @@ - - -

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

-
- -

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

-
- -

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

-
- - - - - - {{ sco.title }} - - -
diff --git a/src/addon/mod/scorm/lang/en.json b/src/addon/mod/scorm/lang/en.json index cef1c9efe..c5b06c51a 100644 --- a/src/addon/mod/scorm/lang/en.json +++ b/src/addon/mod/scorm/lang/en.json @@ -47,6 +47,7 @@ "scormstatusnotdownloaded": "This SCORM package is not downloaded. It will be automatically downloaded when you open it.", "scormstatusoutdated": "This SCORM package has been modified since the last download. It will be automatically downloaded when you open it.", "suspended": "Suspended", + "toc": "TOC", "warningofflinedatadeleted": "Some offline data from attempt {{number}} has been discarded because it couldn't be counted as a new attempt.", "warningsynconlineincomplete": "Some attempts couldn't be synchronised with the site because the last online attempt is not yet finished. Please finish the online attempt first." } \ No newline at end of file diff --git a/src/addon/mod/scorm/pages/player/player.html b/src/addon/mod/scorm/pages/player/player.html index 0fca7e9e9..bdf47bd53 100644 --- a/src/addon/mod/scorm/pages/player/player.html +++ b/src/addon/mod/scorm/pages/player/player.html @@ -3,7 +3,7 @@ - diff --git a/src/addon/mod/scorm/pages/player/player.ts b/src/addon/mod/scorm/pages/player/player.ts index bbe6b5314..6cfa65f7b 100644 --- a/src/addon/mod/scorm/pages/player/player.ts +++ b/src/addon/mod/scorm/pages/player/player.ts @@ -13,7 +13,7 @@ // limitations under the License. import { Component, OnInit, OnDestroy } from '@angular/core'; -import { IonicPage, NavParams, PopoverController } from 'ionic-angular'; +import { IonicPage, NavParams, ModalController } from 'ionic-angular'; import { CoreEventsProvider } from '@providers/events'; import { CoreSitesProvider } from '@providers/sites'; import { CoreSyncProvider } from '@providers/sync'; @@ -24,7 +24,6 @@ import { AddonModScormProvider, AddonModScormAttemptCountResult } from '../../pr import { AddonModScormHelperProvider } from '../../providers/helper'; import { AddonModScormSyncProvider } from '../../providers/scorm-sync'; import { AddonModScormDataModel12 } from '../../classes/data-model-12'; -import { AddonModScormTocPopoverComponent } from '../../components/toc-popover/toc-popover'; /** * Page that allows playing a SCORM. @@ -65,7 +64,7 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy { protected launchPrevObserver: any; protected goOfflineObserver: any; - constructor(navParams: NavParams, protected popoverCtrl: PopoverController, protected eventsProvider: CoreEventsProvider, + constructor(navParams: NavParams, protected modalCtrl: ModalController, protected eventsProvider: CoreEventsProvider, protected sitesProvider: CoreSitesProvider, protected syncProvider: CoreSyncProvider, protected domUtils: CoreDomUtilsProvider, protected timeUtils: CoreTimeUtilsProvider, protected scormProvider: AddonModScormProvider, protected scormHelper: AddonModScormHelperProvider, @@ -382,20 +381,21 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy { * @param {MouseEvent} event Event. */ openToc(event: MouseEvent): void { - const popover = this.popoverCtrl.create(AddonModScormTocPopoverComponent, { + const modal = this.modalCtrl.create('AddonModScormTocPage', { toc: this.toc, attemptToContinue: this.attemptToContinue, - mode: this.mode - }); + mode: this.mode, + selected: this.currentSco && this.currentSco.id + }, { cssClass: 'core-modal-lateral' }); - // If the popover sends back a SCO, load it. - popover.onDidDismiss((sco) => { + // If the modal sends back a SCO, load it. + modal.onDidDismiss((sco) => { if (sco) { this.loadSco(sco); } }); - popover.present({ + modal.present({ ev: event }); } diff --git a/src/addon/mod/scorm/pages/toc/toc.html b/src/addon/mod/scorm/pages/toc/toc.html new file mode 100644 index 000000000..c9a0c71c4 --- /dev/null +++ b/src/addon/mod/scorm/pages/toc/toc.html @@ -0,0 +1,33 @@ + + + {{ 'addon.mod_scorm.toc' | translate }} + + + + + + + + diff --git a/src/addon/mod/scorm/pages/toc/toc.module.ts b/src/addon/mod/scorm/pages/toc/toc.module.ts new file mode 100644 index 000000000..4f7684d1a --- /dev/null +++ b/src/addon/mod/scorm/pages/toc/toc.module.ts @@ -0,0 +1,31 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreDirectivesModule } from '@directives/directives.module'; +import { AddonModScormTocPage } from './toc'; + +@NgModule({ + declarations: [ + AddonModScormTocPage, + ], + imports: [ + CoreDirectivesModule, + IonicPageModule.forChild(AddonModScormTocPage), + TranslateModule.forChild() + ], +}) +export class AddonModScormTocPageModule {} diff --git a/src/addon/mod/scorm/components/toc-popover/toc-popover.ts b/src/addon/mod/scorm/pages/toc/toc.ts similarity index 76% rename from src/addon/mod/scorm/components/toc-popover/toc-popover.ts rename to src/addon/mod/scorm/pages/toc/toc.ts index 7a8b51fb1..036196b6c 100644 --- a/src/addon/mod/scorm/components/toc-popover/toc-popover.ts +++ b/src/addon/mod/scorm/pages/toc/toc.ts @@ -13,27 +13,30 @@ // limitations under the License. import { Component } from '@angular/core'; -import { NavParams, ViewController } from 'ionic-angular'; +import { IonicPage, NavParams, ViewController } from 'ionic-angular'; import { AddonModScormProvider } from '../../providers/scorm'; /** - * Component to display the TOC of a SCORM. + * Modal to display the TOC of a SCORM. */ +@IonicPage({ segment: 'addon-mod-scorm-toc-modal' }) @Component({ - selector: 'addon-mod-scorm-toc-popover', - templateUrl: 'addon-mod-scorm-toc-popover.html' + selector: 'page-addon-mod-scorm-toc', + templateUrl: 'toc.html' }) -export class AddonModScormTocPopoverComponent { +export class AddonModScormTocPage { toc: any[]; isBrowse: boolean; isReview: boolean; attemptToContinue: number; + selected: number; constructor(navParams: NavParams, private viewCtrl: ViewController) { this.toc = navParams.get('toc') || []; this.attemptToContinue = navParams.get('attemptToContinue'); const mode = navParams.get('mode'); + this.selected = navParams.get('selected'); this.isBrowse = mode === AddonModScormProvider.MODEBROWSE; this.isReview = mode === AddonModScormProvider.MODEREVIEW; @@ -51,4 +54,11 @@ export class AddonModScormTocPopoverComponent { this.viewCtrl.dismiss(sco); } + + /** + * Close modal. + */ + closeModal(): void { + this.viewCtrl.dismiss(); + } } diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index a6e3043e2..80ea77457 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -753,6 +753,7 @@ "addon.mod_scorm.scormstatusnotdownloaded": "This SCORM package is not downloaded. It will be automatically downloaded when you open it.", "addon.mod_scorm.scormstatusoutdated": "This SCORM package has been modified since the last download. It will be automatically downloaded when you open it.", "addon.mod_scorm.suspended": "Suspended", + "addon.mod_scorm.toc": "TOC", "addon.mod_scorm.warningofflinedatadeleted": "Some offline data from attempt {{number}} has been discarded because it couldn't be counted as a new attempt.", "addon.mod_scorm.warningsynconlineincomplete": "Some attempts couldn't be synchronised with the site because the last online attempt is not yet finished. Please finish the online attempt first.", "addon.mod_survey.cannotsubmitsurvey": "Sorry, there was a problem submitting your survey. Please try again.", From a349c2a1ac6c916b2d4026ba9f0cde6e1a01f3a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 7 Mar 2019 16:21:48 +0100 Subject: [PATCH 134/191] MOBILE-2911 style: Simplify selected items style --- scripts/langindex.json | 2 ++ .../pages/navigation-modal/navigation-modal.html | 2 +- .../pages/navigation-modal/navigation-modal.scss | 15 --------------- src/addon/mod/workshop/pages/phase/phase.scss | 11 +---------- src/components/split-view/split-view.scss | 12 +----------- 5 files changed, 5 insertions(+), 37 deletions(-) diff --git a/scripts/langindex.json b/scripts/langindex.json index 1d6bf8eae..c87296df8 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -344,6 +344,7 @@ "addon.mod_assign_submission_onlinetext.pluginname": "assignsubmission_onlinetext", "addon.mod_book.errorchapter": "book", "addon.mod_book.modulenameplural": "book", + "addon.mod_book.toc": "book", "addon.mod_chat.beep": "chat", "addon.mod_chat.chatreport": "chat", "addon.mod_chat.currentusers": "chat", @@ -752,6 +753,7 @@ "addon.mod_scorm.scormstatusnotdownloaded": "local_moodlemobileapp", "addon.mod_scorm.scormstatusoutdated": "local_moodlemobileapp", "addon.mod_scorm.suspended": "scorm", + "addon.mod_scorm.toc": "scorm", "addon.mod_scorm.warningofflinedatadeleted": "local_moodlemobileapp", "addon.mod_scorm.warningsynconlineincomplete": "local_moodlemobileapp", "addon.mod_survey.cannotsubmitsurvey": "local_moodlemobileapp", diff --git a/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.html b/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.html index a68cdaa43..9398439f7 100644 --- a/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.html +++ b/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.html @@ -21,7 +21,7 @@ {{ 'addon.mod_quiz.showall' | translate }} {{ 'addon.mod_quiz.showeachpage' | translate }} - + {{ 'core.question.questionno' | translate:{$a: question.number} }} {{ 'core.question.information' | translate }} diff --git a/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.scss b/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.scss index 5b3b72890..6576971e0 100644 --- a/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.scss +++ b/src/addon/mod/quiz/pages/navigation-modal/navigation-modal.scss @@ -1,19 +1,4 @@ ion-app.app-root page-addon-mod-quiz-navigation-modal { - .addon-mod_quiz-selected, .item.addon-mod_quiz-selected { - @include border-start(5px, solid, $core-splitview-selected); - font-weight: bold; - - &.item-md { - @include padding(null, null, null, $item-md-padding-start - 5px); - } - &.item-ios { - @include padding(null, null, null, $item-ios-padding-start - 5px); - } - &.item-wp { - @include padding(null, null, null, $item-wp-padding-start - 5px); - } - } - .item.core-question-correct, .item.core-question-incorrect, .item.core-question-notanswered, diff --git a/src/addon/mod/workshop/pages/phase/phase.scss b/src/addon/mod/workshop/pages/phase/phase.scss index 6b909c4ea..f30416066 100644 --- a/src/addon/mod/workshop/pages/phase/phase.scss +++ b/src/addon/mod/workshop/pages/phase/phase.scss @@ -1,15 +1,6 @@ ion-app.app-root page-addon-mod-workshop-phase-info { .core-workshop-phase-selected { background-color: $white; - @include border-start(5px, solid, $core-splitview-selected); - &.item-md { - @include padding(null, null, null, $item-md-padding-start - 5px); - } - &.item-ios { - @include padding(null, null, null, $item-ios-padding-start - 5px); - } - &.item-wp { - @include padding(null, null, null, $item-wp-padding-start - 5px); - } + @include core-selected-item($core-splitview-selected); } } \ No newline at end of file diff --git a/src/components/split-view/split-view.scss b/src/components/split-view/split-view.scss index 893e7708c..5c399b5e5 100644 --- a/src/components/split-view/split-view.scss +++ b/src/components/split-view/split-view.scss @@ -21,17 +21,7 @@ ion-app.app-root core-split-view { .split-pane-side .core-split-item-selected { background-color: $gray-lighter; - @include safe-area-border-start(5px, solid, $core-splitview-selected); - - &.item-md { - @include padding(null, null, null, $item-md-padding-start - 5px); - } - &.item-ios { - @include padding(null, null, null, $item-ios-padding-start - 5px); - } - &.item-wp { - @include padding(null, null, null, $item-wp-padding-start - 5px); - } + @include core-selected-item($core-splitview-selected); } .item-ios[detail-push] .item-inner, From 58468984229b12d074451c5d3b7e6862b3f0330b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 7 Mar 2019 17:03:35 +0100 Subject: [PATCH 135/191] MOBILE-2911 animation: Modal slide from lateral animation --- src/addon/mod/book/components/index/index.ts | 6 +- src/addon/mod/quiz/pages/player/player.ts | 6 +- src/addon/mod/quiz/pages/review/review.ts | 6 +- src/addon/mod/scorm/pages/player/player.ts | 6 +- src/app/app.module.ts | 2 + src/classes/modal-lateral-transition.ts | 72 ++++++++++++++++++++ 6 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 src/classes/modal-lateral-transition.ts diff --git a/src/addon/mod/book/components/index/index.ts b/src/addon/mod/book/components/index/index.ts index 908ab8cdd..75ad3beb1 100644 --- a/src/addon/mod/book/components/index/index.ts +++ b/src/addon/mod/book/components/index/index.ts @@ -64,7 +64,11 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp const modal = this.modalCtrl.create('AddonModBookTocPage', { chapters: this.chapters, selected: this.currentChapter - }, { cssClass: 'core-modal-lateral' }); + }, { cssClass: 'core-modal-lateral', + showBackdrop: true, + enableBackdropDismiss: true, + enterAnimation: 'core-modal-lateral-transition', + leaveAnimation: 'core-modal-lateral-transition' }); modal.onDidDismiss((chapterId) => { if (chapterId) { diff --git a/src/addon/mod/quiz/pages/player/player.ts b/src/addon/mod/quiz/pages/player/player.ts index 456908e60..12bf38f2b 100644 --- a/src/addon/mod/quiz/pages/player/player.ts +++ b/src/addon/mod/quiz/pages/player/player.ts @@ -96,7 +96,11 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { // Create the navigation modal. this.navigationModal = modalCtrl.create('AddonModQuizNavigationModalPage', { page: this - }, { cssClass: 'core-modal-lateral' }); + }, { cssClass: 'core-modal-lateral', + showBackdrop: true, + enableBackdropDismiss: true, + enterAnimation: 'core-modal-lateral-transition', + leaveAnimation: 'core-modal-lateral-transition' }); } /** diff --git a/src/addon/mod/quiz/pages/review/review.ts b/src/addon/mod/quiz/pages/review/review.ts index bd5825901..6d1bc331d 100644 --- a/src/addon/mod/quiz/pages/review/review.ts +++ b/src/addon/mod/quiz/pages/review/review.ts @@ -69,7 +69,11 @@ export class AddonModQuizReviewPage implements OnInit { this.navigationModal = modalCtrl.create('AddonModQuizNavigationModalPage', { isReview: true, page: this - }, { cssClass: 'core-modal-lateral' }); + }, { cssClass: 'core-modal-lateral', + showBackdrop: true, + enableBackdropDismiss: true, + enterAnimation: 'core-modal-lateral-transition', + leaveAnimation: 'core-modal-lateral-transition' }); } /** diff --git a/src/addon/mod/scorm/pages/player/player.ts b/src/addon/mod/scorm/pages/player/player.ts index 6cfa65f7b..49f2cd000 100644 --- a/src/addon/mod/scorm/pages/player/player.ts +++ b/src/addon/mod/scorm/pages/player/player.ts @@ -386,7 +386,11 @@ export class AddonModScormPlayerPage implements OnInit, OnDestroy { attemptToContinue: this.attemptToContinue, mode: this.mode, selected: this.currentSco && this.currentSco.id - }, { cssClass: 'core-modal-lateral' }); + }, { cssClass: 'core-modal-lateral', + showBackdrop: true, + enableBackdropDismiss: true, + enterAnimation: 'core-modal-lateral-transition', + leaveAnimation: 'core-modal-lateral-transition' }); // If the modal sends back a SCO, load it. modal.onDidDismiss((sco) => { diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 95751524c..fbcdc4cb5 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -31,6 +31,7 @@ import { ScreenOrientation } from '@ionic-native/screen-orientation'; import { MoodleMobileApp } from './app.component'; import { CoreInterceptor } from '@classes/interceptor'; import { CorePageTransition } from '@classes/page-transition'; +import { CoreModalLateralTransition } from '@classes/modal-lateral-transition'; import { CoreLoggerProvider } from '@providers/logger'; import { CoreDbProvider } from '@providers/db'; import { CoreAppProvider } from '@providers/app'; @@ -323,6 +324,7 @@ export class AppModule { // Set transition animation. config.setTransition('core-page-transition', CorePageTransition); + config.setTransition('core-modal-lateral-transition', CoreModalLateralTransition); // Decorate ion-content. this.decorateIonContent(); diff --git a/src/classes/modal-lateral-transition.ts b/src/classes/modal-lateral-transition.ts new file mode 100644 index 000000000..69d4db3c0 --- /dev/null +++ b/src/classes/modal-lateral-transition.ts @@ -0,0 +1,72 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Animation } from 'ionic-angular/animations/animation'; +import { PageTransition } from 'ionic-angular/transitions/page-transition'; + +/** + * Sliding transition for lateral modals. + */ +export class CoreModalLateralTransition extends PageTransition { + /** + * Animation. + */ + init(): void { + const enteringView = this.enteringView; + const leavingView = this.leavingView; + + const plt = this.plt; + const OFF_RIGHT = plt.isRTL ? '-100%' : '100%'; + + if (enteringView && enteringView.pageRef()) { + const ele = enteringView.pageRef().nativeElement; + const wrapper = new Animation(this.plt, ele.querySelector('.modal-wrapper')); + const backdrop = new Animation(this.plt, ele.querySelector('ion-backdrop')); + + wrapper.beforeStyles({ transform: 'translateX(' + OFF_RIGHT + ')', opacity: 0.8 }); + wrapper.fromTo('transform', 'translateX(' + OFF_RIGHT + ')', 'translateX(0)'); + wrapper.fromTo('opacity', 0.8, 1); + backdrop.fromTo('opacity', 0.01, 0.4); + + this + .element(enteringView.pageRef()) + .duration(300) + .easing('cubic-bezier(0.36,0.66,0.04,1)') + .add(wrapper) + .add(backdrop); + } + + if (leavingView && leavingView.pageRef()) { + const ele = this.leavingView.pageRef().nativeElement; + const wrapper = new Animation(this.plt, ele.querySelector('.modal-wrapper')); + const contentWrapper = new Animation(this.plt, ele.querySelector('.wrapper')); + const backdrop = new Animation(this.plt, ele.querySelector('ion-backdrop')); + + wrapper.beforeStyles({ transform: 'translateX(0)', opacity: 1 }); + wrapper.fromTo('transform', 'translateX(0)', 'translateX(' + OFF_RIGHT + ')'); + wrapper.fromTo('opacity', 1, 0.8); + contentWrapper.fromTo('opacity', 1, 0); + backdrop.fromTo('opacity', 0.4, 0); + + this + .element(leavingView.pageRef()) + .duration(300) + .easing('cubic-bezier(0.36,0.66,0.04,1)') + .add(contentWrapper) + .add(wrapper) + .add(backdrop); + + } + } +} From 5cc5394f8efb15209e1f0476534a06ab91b4b545 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 12 Mar 2019 09:30:37 +0100 Subject: [PATCH 136/191] MOBILE-2911 site: Clone response when reusing cache promise --- src/classes/site.ts | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/classes/site.ts b/src/classes/site.ts index bd24fe482..04d133a2c 100644 --- a/src/classes/site.ts +++ b/src/classes/site.ts @@ -576,7 +576,12 @@ export class CoreSite { // Check for an ongoing identical request if we're not ignoring cache. if (preSets.getFromCache && this.ongoingRequests[cacheId]) { - return this.ongoingRequests[cacheId]; + return this.ongoingRequests[cacheId].then((response) => { + // Clone the data, this may prevent errors if in the callback the object is modified. + return this.utils.clone(response); + }).catch((error) => { + return Promise.reject(this.utils.clone(error)); + }); } const promise = this.getFromCache(method, data, preSets, false, originalData).catch(() => { @@ -586,8 +591,7 @@ export class CoreSite { this.saveToCache(method, data, response, preSets); } - // We pass back a clone of the original object, this may prevent errors if in the callback the object is modified. - return this.utils.clone(response); + return response; }).catch((error) => { if (error.errorcode == 'invalidtoken' || (error.errorcode == 'accessexception' && error.message.indexOf('Invalid token - token expired') > -1)) { @@ -694,7 +698,12 @@ export class CoreSite { } }); - return promise; + return promise.then((response) => { + // We pass back a clone of the original object, this may prevent errors if in the callback the object is modified. + return this.utils.clone(response); + }).catch((error) => { + return Promise.reject(this.utils.clone(error)); + }); } /** From 5c95446c5921b0a5d37cc5016f123bd205e2eb19 Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Tue, 12 Mar 2019 13:15:45 +0100 Subject: [PATCH 137/191] MOBILE-2902 core: New component for adding style tags --- src/components/components.module.ts | 7 ++- src/components/style/style.ts | 73 +++++++++++++++++++++++++++++ 2 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 src/components/style/style.ts diff --git a/src/components/components.module.ts b/src/components/components.module.ts index 0e631c2b9..e72475548 100644 --- a/src/components/components.module.ts +++ b/src/components/components.module.ts @@ -51,6 +51,7 @@ import { CoreIonTabsComponent } from './ion-tabs/ion-tabs'; import { CoreIonTabComponent } from './ion-tabs/ion-tab'; import { CoreInfiniteLoadingComponent } from './infinite-loading/infinite-loading'; import { CoreUserAvatarComponent } from './user-avatar/user-avatar'; +import { CoreStyleComponent } from './style/style'; @NgModule({ declarations: [ @@ -87,7 +88,8 @@ import { CoreUserAvatarComponent } from './user-avatar/user-avatar'; CoreIonTabsComponent, CoreIonTabComponent, CoreInfiniteLoadingComponent, - CoreUserAvatarComponent + CoreUserAvatarComponent, + CoreStyleComponent ], entryComponents: [ CoreContextMenuPopoverComponent, @@ -131,7 +133,8 @@ import { CoreUserAvatarComponent } from './user-avatar/user-avatar'; CoreIonTabsComponent, CoreIonTabComponent, CoreInfiniteLoadingComponent, - CoreUserAvatarComponent + CoreUserAvatarComponent, + CoreStyleComponent ] }) export class CoreComponentsModule {} diff --git a/src/components/style/style.ts b/src/components/style/style.ts new file mode 100644 index 000000000..a4420f6b9 --- /dev/null +++ b/src/components/style/style.ts @@ -0,0 +1,73 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, ElementRef, Input, OnChanges, SimpleChange } from '@angular/core'; + +/** + * Component to add a '; + } + } + + /** + * Add a prefix to all rules in a CSS string. + * + * @param {string} css CSS code to be prefixed. + * @param {string} prefix Prefix css selector. + * @return {string} Prefixed CSS. + */ + protected prefixCSS(css: string, prefix: string): string { + if (!css) { + return ''; + } + + if (!prefix) { + return css; + } + + // Remove comments first. + let regExp = /\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm; + css = css.replace(regExp, ''); + + // Add prefix. + regExp = /([^]*?)({[^]*?}|,)/g; + + return css.replace(regExp, prefix + ' $1 $2'); + } +} From 4934624f676a810a5eb36a06c60d5b55bee34d54 Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Tue, 12 Mar 2019 13:16:29 +0100 Subject: [PATCH 138/191] MOBILE-2902 data: Use core-style for the CSS template --- .../index/addon-mod-data-index.html | 4 +--- src/addon/mod/data/components/index/index.ts | 3 --- src/addon/mod/data/pages/edit/edit.html | 6 ++---- src/addon/mod/data/pages/edit/edit.ts | 3 --- src/addon/mod/data/pages/entry/entry.html | 6 ++---- src/addon/mod/data/pages/entry/entry.ts | 5 ----- src/addon/mod/data/providers/helper.ts | 21 ------------------- 7 files changed, 5 insertions(+), 43 deletions(-) diff --git a/src/addon/mod/data/components/index/addon-mod-data-index.html b/src/addon/mod/data/components/index/addon-mod-data-index.html index acc54a271..422a2b75f 100644 --- a/src/addon/mod/data/components/index/addon-mod-data-index.html +++ b/src/addon/mod/data/components/index/addon-mod-data-index.html @@ -67,9 +67,7 @@
- +
diff --git a/src/addon/mod/data/components/index/index.ts b/src/addon/mod/data/components/index/index.ts index 68bdccef6..ddd5975a9 100644 --- a/src/addon/mod/data/components/index/index.ts +++ b/src/addon/mod/data/components/index/index.ts @@ -67,7 +67,6 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp offlineActions: any; offlineEntries: any; entriesRendered = ''; - cssTemplate = ''; extraImports = [AddonModDataComponentsModule]; jsData; foundRecordsData; @@ -298,8 +297,6 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp } if (!this.isEmpty) { - this.cssTemplate = this.dataHelper.prefixCSS(this.data.csstemplate, '.addon-data-entries-' + this.data.id); - const siteInfo = this.sitesProvider.getCurrentSite().getInfo(), promises = []; diff --git a/src/addon/mod/data/pages/edit/edit.html b/src/addon/mod/data/pages/edit/edit.html index 19ca035f7..2e7994fc3 100644 --- a/src/addon/mod/data/pages/edit/edit.html +++ b/src/addon/mod/data/pages/edit/edit.html @@ -18,10 +18,8 @@ -
- +
+ diff --git a/src/addon/mod/data/pages/edit/edit.ts b/src/addon/mod/data/pages/edit/edit.ts index 5520c8e2c..4ca834934 100644 --- a/src/addon/mod/data/pages/edit/edit.ts +++ b/src/addon/mod/data/pages/edit/edit.ts @@ -57,7 +57,6 @@ export class AddonModDataEditPage { loaded = false; selectedGroup = 0; cssClass = ''; - cssTemplate = ''; groupInfo: any; editFormRender = ''; editForm: FormGroup; @@ -132,8 +131,6 @@ export class AddonModDataEditPage { return this.dataProvider.getDatabaseAccessInformation(data.id); }).then((accessData) => { - this.cssTemplate = this.dataHelper.prefixCSS(this.data.csstemplate, '.' + this.cssClass); - if (this.entryId) { return this.groupsProvider.getActivityGroupInfo(this.data.coursemodule, accessData.canmanageentries) .then((groupInfo) => { diff --git a/src/addon/mod/data/pages/entry/entry.html b/src/addon/mod/data/pages/entry/entry.html index 3cc182e48..19dd1987c 100644 --- a/src/addon/mod/data/pages/entry/entry.html +++ b/src/addon/mod/data/pages/entry/entry.html @@ -22,10 +22,8 @@ -
- +
+
diff --git a/src/addon/mod/data/pages/entry/entry.ts b/src/addon/mod/data/pages/entry/entry.ts index d1b26a8f1..6f6cf7649 100644 --- a/src/addon/mod/data/pages/entry/entry.ts +++ b/src/addon/mod/data/pages/entry/entry.ts @@ -55,7 +55,6 @@ export class AddonModDataEntryPage implements OnDestroy { entry: any; offlineActions = []; hasOffline = false; - cssTemplate = ''; previousOffset: number; nextOffset: number; access: any; @@ -64,7 +63,6 @@ export class AddonModDataEntryPage implements OnDestroy { showComments: any; entryRendered = ''; siteId: string; - cssClass = ''; extraImports = [AddonModDataComponentsModule]; jsData; ratingInfo: CoreRatingInfo; @@ -133,7 +131,6 @@ export class AddonModDataEntryPage implements OnDestroy { return this.dataProvider.getDatabase(this.courseId, this.module.id).then((data) => { this.title = data.name || this.title; this.data = data; - this.cssClass = 'addon-data-entries-' + data.id; return this.setEntryIdFromOffset(data.id, this.offset, this.selectedGroup).then(() => { return this.dataProvider.getDatabaseAccessInformation(data.id); @@ -167,8 +164,6 @@ export class AddonModDataEntryPage implements OnDestroy { this.ratingInfo = entry.ratinginfo; entry = entry.entry; - this.cssTemplate = this.dataHelper.prefixCSS(this.data.csstemplate, '.' + this.cssClass); - // Index contents by fieldid. entry.contents = this.utils.arrayToObject(entry.contents, 'fieldid'); diff --git a/src/addon/mod/data/providers/helper.ts b/src/addon/mod/data/providers/helper.ts index 29d6435c4..0de845835 100644 --- a/src/addon/mod/data/providers/helper.ts +++ b/src/addon/mod/data/providers/helper.ts @@ -423,27 +423,6 @@ export class AddonModDataHelperProvider { }); } - /** - * Add a prefix to all rules in a CSS string. - * - * @param {string} css CSS code to be prefixed. - * @param {string} prefix Prefix css selector. - * @return {string} Prefixed CSS. - */ - prefixCSS(css: string, prefix: string): string { - if (!css) { - return ''; - } - - // Remove comments first. - let regExp = /\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm; - css = css.replace(regExp, ''); - // Add prefix. - regExp = /([^]*?)({[^]*?}|,)/g; - - return css.replace(regExp, prefix + ' $1 $2'); - } - /** * Given a list of files (either online files or local files), store the local files in a local folder * to be submitted later. From bf38514c488e78a6d25677e13d01c608ec38d09b Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 13 Mar 2019 09:46:25 +0100 Subject: [PATCH 139/191] MOBILE-2831 config: Use our fork of push plugin --- config.xml | 4 ++-- package-lock.json | 11 +++++------ package.json | 6 +++--- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/config.xml b/config.xml index 265b5a0a5..c3f6a820d 100644 --- a/config.xml +++ b/config.xml @@ -139,9 +139,9 @@ - + - + diff --git a/package-lock.json b/package-lock.json index 8b112c0e9..ffdf940d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3226,9 +3226,9 @@ "integrity": "sha512-4ihQApBGVKR1QZ4oOSGctKFfthtCfiWMTcIIfxe97vKxlvGr9NyXOvYG9vXU9S7yVR7Ua+Rj47hkE7pQIKvQTg==" }, "cordova-support-google-services": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/cordova-support-google-services/-/cordova-support-google-services-1.1.0.tgz", - "integrity": "sha1-RjTFIgD4cGDReV6yhw6ZRC12Lm0=" + "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==" }, "core-js": { "version": "2.3.0", @@ -8575,9 +8575,8 @@ "integrity": "sha512-1wvc3iQOQpEBaQbXgLxA2JUiLSQ2azdF/bF29ghXDiQJWSpQ1BF8gSuqttM8WZoj081Ps8OKL0gYxdDBkFNPqA==" }, "phonegap-plugin-push": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/phonegap-plugin-push/-/phonegap-plugin-push-2.2.3.tgz", - "integrity": "sha512-5mjT0G1vfRhXVnZFLwjfzcFwYjVRMibgYDCfYvEujGsP8YwwrIIzcf+xBYAjQV/W2JCjzuNaYd7xJ0yVQaPeig==", + "version": "git+https://github.com/moodlemobile/phonegap-plugin-push.git#9b1d9fe575d1f21b517327c480e7fe0f73280e7a", + "from": "git+https://github.com/moodlemobile/phonegap-plugin-push.git#moodle-v2", "requires": { "babel-plugin-add-header-comment": "^1.0.3", "install": "^0.8.2" diff --git a/package.json b/package.json index 4ffaa3c37..2fa23a48a 100644 --- a/package.json +++ b/package.json @@ -102,7 +102,7 @@ "cordova-plugin-whitelist": "^1.3.3", "cordova-plugin-zip": "^3.1.0", "cordova-sqlite-storage": "^2.6.0", - "cordova-support-google-services": "^1.1.0", + "cordova-support-google-services": "^1.2.1", "es6-promise-plugin": "^4.2.2", "font-awesome": "^4.7.0", "ionic-angular": "3.9.3", @@ -111,7 +111,7 @@ "moment": "^2.22.2", "nl.kingsquare.cordova.background-audio": "^1.0.1", "phonegap-plugin-multidex": "^1.0.0", - "phonegap-plugin-push": "^2.2.3", + "phonegap-plugin-push": "git+https://github.com/moodlemobile/phonegap-plugin-push.git#moodle-v2", "promise.prototype.finally": "^3.1.0", "rxjs": "^5.5.11", "sw-toolbox": "^3.6.0", @@ -172,7 +172,7 @@ "nl.kingsquare.cordova.background-audio": {}, "phonegap-plugin-push": { "ANDROID_SUPPORT_V13_VERSION": "27.+", - "FCM_VERSION": "11.6.2" + "FCM_VERSION": "17.0.+" } } }, From 470c23ee796b9d3a1f375211a763ca52aacea704 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 13 Mar 2019 10:24:18 +0100 Subject: [PATCH 140/191] MOBILE-2831 config: Fix low vulnerabilities --- package-lock.json | 1770 +++++++++++++++++++++++++++++++++------------ package.json | 2 +- 2 files changed, 1303 insertions(+), 469 deletions(-) diff --git a/package-lock.json b/package-lock.json index ffdf940d0..548822a5a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,14 +55,157 @@ } }, "@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": "7.2.9", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-7.2.9.tgz", + "integrity": "sha512-3itdcfszdyXTHYEsO4eBu4WEx10hU8JpOgUcZyw+OYgwLQLyjEXOD9dfYZZpE/+2F0omoMLseCTHTP//uux+Iw==", "requires": { - "chokidar": "^1.4.2", + "canonical-path": "1.0.0", + "chokidar": "^2.1.1", + "convert-source-map": "^1.5.1", + "dependency-graph": "^0.7.2", + "magic-string": "^0.25.0", "minimist": "^1.2.0", "reflect-metadata": "^0.1.2", - "tsickle": "^0.27.2" + "shelljs": "^0.8.1", + "source-map": "^0.6.1", + "tslib": "^1.9.0", + "yargs": "9.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "requires": { + "locate-path": "^2.0.0" + } + }, + "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=" + }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "strip-bom": "^3.0.0" + } + }, + "magic-string": { + "version": "0.25.2", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.2.tgz", + "integrity": "sha512-iLs9mPjh9IuTtRsqqhNGYcZXGei0Nh/A4xirrsqW7c+QhKVFL2vm7U09ru6cHRD22azaP/wMDgI+HCqbETMTtg==", + "requires": { + "sourcemap-codec": "^1.4.4" + } + }, + "os-locale": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", + "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "requires": { + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" + } + }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "requires": { + "pify": "^2.0.0" + } + }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "requires": { + "load-json-file": "^2.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "requires": { + "find-up": "^2.0.0", + "read-pkg": "^2.0.0" + } + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + }, + "yargs": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-9.0.1.tgz", + "integrity": "sha1-UqzCP+7Kw0BCB47njAwAf1CF20w=", + "requires": { + "camelcase": "^4.1.0", + "cliui": "^3.2.0", + "decamelize": "^1.1.1", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "read-pkg-up": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^7.0.0" + } + }, + "yargs-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", + "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", + "requires": { + "camelcase": "^4.1.0" + } + } } }, "@angular/core": { @@ -1308,8 +1451,7 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "ansi-styles": { "version": "3.2.1", @@ -1344,12 +1486,12 @@ "dev": true }, "anymatch": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", - "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", "requires": { - "micromatch": "^2.1.5", - "normalize-path": "^2.0.0" + "micromatch": "^3.1.4", + "normalize-path": "^2.1.1" } }, "append-buffer": { @@ -1393,12 +1535,9 @@ } }, "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "requires": { - "arr-flatten": "^1.0.1" - } + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" }, "arr-filter": { "version": "1.1.2", @@ -1426,8 +1565,7 @@ "arr-union": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" }, "array-differ": { "version": "1.0.0", @@ -1520,9 +1658,9 @@ "dev": true }, "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=" + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" }, "asn1": { "version": "0.2.3", @@ -1576,8 +1714,7 @@ "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" }, "async": { "version": "2.6.2", @@ -1649,8 +1786,7 @@ "atob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.1.tgz", - "integrity": "sha1-ri1acpR38onWDdf5amMUoi3Wwio=", - "dev": true + "integrity": "sha1-ri1acpR38onWDdf5amMUoi3Wwio=" }, "autoprefixer": { "version": "7.2.6", @@ -1747,7 +1883,6 @@ "version": "0.11.2", "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, "requires": { "cache-base": "^1.0.1", "class-utils": "^0.3.5", @@ -1762,7 +1897,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, "requires": { "is-descriptor": "^1.0.0" } @@ -1771,7 +1905,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -1780,7 +1913,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -1789,7 +1921,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", @@ -1799,14 +1930,12 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" }, "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" } } }, @@ -1937,13 +2066,30 @@ } }, "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } } }, "brorand": { @@ -2092,7 +2238,8 @@ "buffer-from": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.0.tgz", - "integrity": "sha512-c5mRlguI/Pe2dSZmpER62rSCu0ryKmWddzRYsuXc50U2/g8jMOulc31VZMa4mYx31U5xsmSOpDCgH88Vl9cDGQ==" + "integrity": "sha512-c5mRlguI/Pe2dSZmpER62rSCu0ryKmWddzRYsuXc50U2/g8jMOulc31VZMa4mYx31U5xsmSOpDCgH88Vl9cDGQ==", + "dev": true }, "buffer-xor": { "version": "1.0.3", @@ -2160,8 +2307,7 @@ "builtin-modules": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=" }, "builtin-status-codes": { "version": "3.0.0", @@ -2179,7 +2325,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, "requires": { "collection-visit": "^1.0.0", "component-emitter": "^1.2.1", @@ -2195,8 +2340,7 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" } } }, @@ -2222,6 +2366,11 @@ "integrity": "sha512-ekW8NQ3/FvokviDxhdKLZZAx7PptXNwxKgXtnR5y+PR3hckwuP3yJ1Ir+4/c97dsHNqtAyfKUGdw8P4EYzBNgw==", "dev": true }, + "canonical-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/canonical-path/-/canonical-path-1.0.0.tgz", + "integrity": "sha512-feylzsbDxi1gPZ1IjystzIQZagYYLvfKrSuygUCgf7z6x790VEzze5QEkdSV1U58RA7Hi0+v6fv4K54atOzATg==" + }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -2276,19 +2425,501 @@ } }, "chokidar": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", - "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.2.tgz", + "integrity": "sha512-IwXUx0FXc5ibYmPC2XeEj5mpXoV66sR+t3jqu2NS2GYwCktt3KF1/Qqjws/NkegajBA4RbZ5+DDwlOiJsxDHEg==", "requires": { - "anymatch": "^1.3.0", - "async-each": "^1.0.0", - "fsevents": "^1.0.0", - "glob-parent": "^2.0.0", - "inherits": "^2.0.1", + "anymatch": "^2.0.0", + "async-each": "^1.0.1", + "braces": "^2.3.2", + "fsevents": "^1.2.7", + "glob-parent": "^3.1.0", + "inherits": "^2.0.3", "is-binary-path": "^1.0.0", - "is-glob": "^2.0.0", + "is-glob": "^4.0.0", + "normalize-path": "^3.0.0", "path-is-absolute": "^1.0.0", - "readdirp": "^2.0.0" + "readdirp": "^2.2.1", + "upath": "^1.1.0" + }, + "dependencies": { + "fsevents": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.7.tgz", + "integrity": "sha512-Pxm6sI2MeBD7RdD12RYsqaP0nMiwx8eZBXCa6z2L+mRHm2DYrOYwihmhjpkdjUHwQhslWQjRpEgNq4XvBmaAuw==", + "optional": true, + "requires": { + "nan": "^2.9.2", + "node-pre-gyp": "^0.10.0" + }, + "dependencies": { + "abbrev": { + "version": "1.1.1", + "bundled": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true + }, + "aproba": { + "version": "1.2.0", + "bundled": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "bundled": true, + "optional": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "chownr": { + "version": "1.1.1", + "bundled": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.6.0", + "bundled": true, + "optional": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.3", + "bundled": true, + "optional": true + }, + "fs-minipass": { + "version": "1.2.5", + "bundled": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "gauge": { + "version": "2.7.4", + "bundled": 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, + "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, + "optional": true + }, + "iconv-lite": { + "version": "0.4.24", + "bundled": true, + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore-walk": { + "version": "3.0.1", + "bundled": true, + "optional": true, + "requires": { + "minimatch": "^3.0.4" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "optional": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true + }, + "ini": { + "version": "1.3.5", + "bundled": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true + }, + "minipass": { + "version": "2.3.5", + "bundled": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "minizlib": { + "version": "1.2.1", + "bundled": true, + "optional": true, + "requires": { + "minipass": "^2.2.1" + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "needle": { + "version": "2.2.4", + "bundled": 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, + "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, + "optional": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, + "npm-bundled": { + "version": "1.0.5", + "bundled": true, + "optional": true + }, + "npm-packlist": { + "version": "1.2.0", + "bundled": true, + "optional": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1" + } + }, + "npmlog": { + "version": "4.1.2", + "bundled": 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 + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "osenv": { + "version": "0.1.5", + "bundled": true, + "optional": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "optional": true + }, + "process-nextick-args": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "rc": { + "version": "1.2.8", + "bundled": 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, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.3.6", + "bundled": 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, + "optional": true, + "requires": { + "glob": "^7.1.3" + } + }, + "safe-buffer": { + "version": "5.1.2", + "bundled": true + }, + "safer-buffer": { + "version": "2.1.2", + "bundled": true, + "optional": true + }, + "sax": { + "version": "1.2.4", + "bundled": true, + "optional": true + }, + "semver": { + "version": "5.6.0", + "bundled": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "optional": true + }, + "string-width": { + "version": "1.0.2", + "bundled": 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, + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "optional": true + }, + "tar": { + "version": "4.4.8", + "bundled": 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, + "optional": true + }, + "wide-align": { + "version": "1.1.3", + "bundled": true, + "optional": true, + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true + }, + "yallist": { + "version": "3.0.3", + "bundled": true + } + } + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" + }, + "readdirp": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", + "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", + "requires": { + "graceful-fs": "^4.1.11", + "micromatch": "^3.1.10", + "readable-stream": "^2.0.2" + } + } } }, "chromium-pickle-js": { @@ -2317,7 +2948,6 @@ "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, "requires": { "arr-union": "^3.1.0", "define-property": "^0.2.5", @@ -2329,7 +2959,6 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -2337,8 +2966,7 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" } } }, @@ -2370,7 +2998,6 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "dev": true, "requires": { "string-width": "^1.0.1", "strip-ansi": "^3.0.1", @@ -2415,8 +3042,7 @@ "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "collection-map": { "version": "1.0.0", @@ -2444,7 +3070,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, "requires": { "map-visit": "^1.0.0", "object-visit": "^1.0.0" @@ -2501,8 +3126,7 @@ "component-emitter": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" }, "concat-map": { "version": "0.0.1", @@ -2564,7 +3188,6 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", - "dev": true, "requires": { "safe-buffer": "~5.1.1" } @@ -2584,8 +3207,7 @@ "copy-descriptor": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" }, "copy-props": { "version": "2.0.4", @@ -3281,7 +3903,6 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", - "dev": true, "requires": { "lru-cache": "^4.0.1", "shebang-command": "^1.2.0", @@ -3358,7 +3979,6 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, "requires": { "ms": "2.0.0" } @@ -3366,14 +3986,12 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" }, "default-compare": { "version": "1.0.0", @@ -3411,7 +4029,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, "requires": { "is-descriptor": "^1.0.2", "isobject": "^3.0.1" @@ -3421,7 +4038,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -3430,7 +4046,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -3439,7 +4054,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", @@ -3449,14 +4063,12 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" }, "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" } } }, @@ -3478,6 +4090,11 @@ "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", "dev": true }, + "dependency-graph": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.7.2.tgz", + "integrity": "sha512-KqtH4/EZdtdfWX0p6MGP9jljvxSY6msy/pRUD4jgNwVpv3v1QmNLlsB3LDSSUg79BRVSn7jI1QPRtArGABovAQ==" + }, "des.js": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", @@ -3949,7 +4566,6 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", - "dev": true, "requires": { "is-arrayish": "^0.2.1" } @@ -4150,7 +4766,6 @@ "version": "0.7.0", "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "dev": true, "requires": { "cross-spawn": "^5.0.1", "get-stream": "^3.0.0", @@ -4162,19 +4777,35 @@ } }, "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "requires": { - "is-posix-bracket": "^0.1.0" - } - }, - "expand-range": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", - "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", - "requires": { - "fill-range": "^2.1.0" + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } } }, "expand-tilde": { @@ -4242,7 +4873,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, "requires": { "assign-symbols": "^1.0.0", "is-extendable": "^1.0.1" @@ -4252,7 +4882,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, "requires": { "is-plain-object": "^2.0.4" } @@ -4260,11 +4889,67 @@ } }, "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", "requires": { - "is-extglob": "^1.0.0" + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + } } }, "extsprintf": { @@ -4305,21 +4990,25 @@ "websocket-driver": ">=0.5.1" } }, - "filename-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", - "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=" - }, "fill-range": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", - "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "requires": { - "is-number": "^2.1.0", - "isobject": "^2.0.0", - "randomatic": "^3.0.0", - "repeat-element": "^1.1.2", - "repeat-string": "^1.5.2" + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "requires": { + "is-extendable": "^0.1.0" + } + } } }, "finalhandler": { @@ -4689,14 +5378,6 @@ "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" }, - "for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "requires": { - "for-in": "^1.0.1" - } - }, "foreach": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", @@ -4729,7 +5410,6 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, "requires": { "map-cache": "^0.2.2" } @@ -4787,13 +5467,13 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", + "dev": true, "optional": true, "requires": { "nan": "^2.9.2", @@ -4803,20 +5483,24 @@ "abbrev": { "version": "1.1.1", "bundled": true, + "dev": true, "optional": true }, "ansi-regex": { "version": "2.1.1", - "bundled": true + "bundled": true, + "dev": true }, "aproba": { "version": "1.2.0", "bundled": true, + "dev": true, "optional": true }, "are-we-there-yet": { "version": "1.1.4", "bundled": true, + "dev": true, "optional": true, "requires": { "delegates": "^1.0.0", @@ -4825,11 +5509,13 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true + "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" @@ -4838,28 +5524,34 @@ "chownr": { "version": "1.0.1", "bundled": true, + "dev": true, "optional": true }, "code-point-at": { "version": "1.1.0", - "bundled": true + "bundled": true, + "dev": true }, "concat-map": { "version": "0.0.1", - "bundled": true + "bundled": true, + "dev": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true + "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" @@ -4868,21 +5560,25 @@ "deep-extend": { "version": "0.5.1", "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" @@ -4891,11 +5587,13 @@ "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", @@ -4911,6 +5609,7 @@ "glob": { "version": "7.1.2", "bundled": true, + "dev": true, "optional": true, "requires": { "fs.realpath": "^1.0.0", @@ -4924,11 +5623,13 @@ "has-unicode": { "version": "2.0.1", "bundled": true, + "dev": true, "optional": true }, "iconv-lite": { "version": "0.4.21", "bundled": true, + "dev": true, "optional": true, "requires": { "safer-buffer": "^2.1.0" @@ -4937,6 +5638,7 @@ "ignore-walk": { "version": "3.0.1", "bundled": true, + "dev": true, "optional": true, "requires": { "minimatch": "^3.0.4" @@ -4945,6 +5647,7 @@ "inflight": { "version": "1.0.6", "bundled": true, + "dev": true, "optional": true, "requires": { "once": "^1.3.0", @@ -4953,16 +5656,19 @@ }, "inherits": { "version": "2.0.3", - "bundled": true + "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" } @@ -4970,22 +5676,26 @@ "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 + "bundled": true, + "dev": true }, "minipass": { "version": "2.2.4", "bundled": true, + "dev": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -4994,6 +5704,7 @@ "minizlib": { "version": "1.1.0", "bundled": true, + "dev": true, "optional": true, "requires": { "minipass": "^2.2.1" @@ -5002,6 +5713,7 @@ "mkdirp": { "version": "0.5.1", "bundled": true, + "dev": true, "requires": { "minimist": "0.0.8" } @@ -5009,11 +5721,13 @@ "ms": { "version": "2.0.0", "bundled": true, + "dev": true, "optional": true }, "needle": { "version": "2.2.0", "bundled": true, + "dev": true, "optional": true, "requires": { "debug": "^2.1.2", @@ -5024,6 +5738,7 @@ "node-pre-gyp": { "version": "0.10.0", "bundled": true, + "dev": true, "optional": true, "requires": { "detect-libc": "^1.0.2", @@ -5041,6 +5756,7 @@ "nopt": { "version": "4.0.1", "bundled": true, + "dev": true, "optional": true, "requires": { "abbrev": "1", @@ -5050,11 +5766,13 @@ "npm-bundled": { "version": "1.0.3", "bundled": true, + "dev": true, "optional": true }, "npm-packlist": { "version": "1.1.10", "bundled": true, + "dev": true, "optional": true, "requires": { "ignore-walk": "^3.0.1", @@ -5064,6 +5782,7 @@ "npmlog": { "version": "4.1.2", "bundled": true, + "dev": true, "optional": true, "requires": { "are-we-there-yet": "~1.1.2", @@ -5074,16 +5793,19 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true + "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" } @@ -5091,16 +5813,19 @@ "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", @@ -5110,16 +5835,19 @@ "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.7", "bundled": true, + "dev": true, "optional": true, "requires": { "deep-extend": "^0.5.1", @@ -5131,6 +5859,7 @@ "minimist": { "version": "1.2.0", "bundled": true, + "dev": true, "optional": true } } @@ -5138,6 +5867,7 @@ "readable-stream": { "version": "2.3.6", "bundled": true, + "dev": true, "optional": true, "requires": { "core-util-is": "~1.0.0", @@ -5152,6 +5882,7 @@ "rimraf": { "version": "2.6.2", "bundled": true, + "dev": true, "optional": true, "requires": { "glob": "^7.0.5" @@ -5159,36 +5890,43 @@ }, "safe-buffer": { "version": "5.1.1", - "bundled": true + "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.5.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", @@ -5198,6 +5936,7 @@ "string_decoder": { "version": "1.1.1", "bundled": true, + "dev": true, "optional": true, "requires": { "safe-buffer": "~5.1.0" @@ -5206,6 +5945,7 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, + "dev": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5213,11 +5953,13 @@ "strip-json-comments": { "version": "2.0.1", "bundled": true, + "dev": true, "optional": true }, "tar": { "version": "4.4.1", "bundled": true, + "dev": true, "optional": true, "requires": { "chownr": "^1.0.1", @@ -5232,11 +5974,13 @@ "util-deprecate": { "version": "1.0.2", "bundled": true, + "dev": true, "optional": true }, "wide-align": { "version": "1.1.2", "bundled": true, + "dev": true, "optional": true, "requires": { "string-width": "^1.0.2" @@ -5244,11 +5988,13 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true + "bundled": true, + "dev": true }, "yallist": { "version": "3.0.2", - "bundled": true + "bundled": true, + "dev": true } } }, @@ -5297,8 +6043,7 @@ "get-caller-file": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", - "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=", - "dev": true + "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=" }, "get-stdin": { "version": "4.0.1", @@ -5309,14 +6054,12 @@ "get-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" }, "get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" }, "getpass": { "version": "0.1.7", @@ -5339,7 +6082,6 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -5349,21 +6091,23 @@ "path-is-absolute": "^1.0.0" } }, - "glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", - "requires": { - "glob-parent": "^2.0.0", - "is-glob": "^2.0.0" - } - }, "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", "requires": { - "is-glob": "^2.0.0" + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "requires": { + "is-extglob": "^2.1.0" + } + } } }, "glob-stream": { @@ -6125,7 +6869,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, "requires": { "get-value": "^2.0.6", "has-values": "^1.0.0", @@ -6135,8 +6878,7 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" } } }, @@ -6144,7 +6886,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, "requires": { "is-number": "^3.0.0", "kind-of": "^4.0.0" @@ -6154,7 +6895,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, "requires": { "kind-of": "^3.0.2" }, @@ -6163,7 +6903,6 @@ "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -6174,7 +6913,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -6224,8 +6962,7 @@ "hosted-git-info": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz", - "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==", - "dev": true + "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==" }, "http-errors": { "version": "1.6.3", @@ -6307,7 +7044,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -6332,14 +7068,12 @@ "interpret": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", - "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", - "dev": true + "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=" }, "invert-kv": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "dev": true + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" }, "ionic-angular": { "version": "3.9.3", @@ -6371,7 +7105,6 @@ "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, "requires": { "kind-of": "^3.0.2" } @@ -6379,8 +7112,7 @@ "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" }, "is-binary-path": { "version": "1.0.1", @@ -6399,7 +7131,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", - "dev": true, "requires": { "builtin-modules": "^1.0.0" } @@ -6422,7 +7153,6 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, "requires": { "kind-of": "^3.0.2" } @@ -6436,7 +7166,6 @@ "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, "requires": { "is-accessor-descriptor": "^0.1.6", "is-data-descriptor": "^0.1.4", @@ -6446,33 +7175,19 @@ "kind-of": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" } } }, - "is-dotfile": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", - "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=" - }, - "is-equal-shallow": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", - "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", - "requires": { - "is-primitive": "^2.0.0" - } - }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" }, "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=" + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, "is-finite": { "version": "1.0.2", @@ -6487,17 +7202,16 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, "requires": { "number-is-nan": "^1.0.0" } }, "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", "requires": { - "is-extglob": "^1.0.0" + "is-extglob": "^2.1.1" } }, "is-module": { @@ -6513,9 +7227,9 @@ "dev": true }, "is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "requires": { "kind-of": "^3.0.2" } @@ -6524,7 +7238,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-2.0.0.tgz", "integrity": "sha512-OTiixgpZAT1M4NHgS5IguFp/Vz2VI3U7Goh4/HA1adtwyLtSBrxYlcSYkhpAE07s4fKEcjrFxyvtQBND4vFQyQ==", - "dev": true, "requires": { "is-number": "^4.0.0" }, @@ -6532,8 +7245,7 @@ "is-number": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==" } } }, @@ -6541,7 +7253,6 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, "requires": { "isobject": "^3.0.1" }, @@ -6549,21 +7260,10 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" } } }, - "is-posix-bracket": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", - "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=" - }, - "is-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", - "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=" - }, "is-regex": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", @@ -6584,8 +7284,7 @@ "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" }, "is-symbol": { "version": "1.0.1", @@ -6622,8 +7321,7 @@ "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" }, "isarray": { "version": "1.0.0", @@ -6642,16 +7340,12 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "requires": { - "isarray": "1.0.0" - } + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" }, "isstream": { "version": "0.1.2", @@ -6851,7 +7545,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "dev": true, "requires": { "invert-kv": "^1.0.0" } @@ -6929,7 +7622,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", - "dev": true, "requires": { "p-locate": "^2.0.0", "path-exists": "^3.0.0" @@ -6938,8 +7630,7 @@ "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" } } }, @@ -7121,7 +7812,6 @@ "version": "4.1.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", - "dev": true, "requires": { "pseudomap": "^1.0.2", "yallist": "^2.1.2" @@ -7162,8 +7852,7 @@ "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" }, "map-obj": { "version": "1.0.1", @@ -7175,7 +7864,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, "requires": { "object-visit": "^1.0.0" } @@ -7468,11 +8156,6 @@ } } }, - "math-random": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.1.tgz", - "integrity": "sha1-izqsWIuKZuSXXjzepn97sylgH6w=" - }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -7494,7 +8177,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", - "dev": true, "requires": { "mimic-fn": "^1.0.0" } @@ -7540,23 +8222,30 @@ "dev": true }, "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + } } }, "miller-rabin": { @@ -7593,8 +8282,7 @@ "mimic-fn": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", - "dev": true + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" }, "minimalistic-assert": { "version": "1.0.1", @@ -7625,7 +8313,6 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", - "dev": true, "requires": { "for-in": "^1.0.2", "is-extendable": "^1.0.1" @@ -7635,7 +8322,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, "requires": { "is-plain-object": "^2.0.4" } @@ -7646,6 +8332,7 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, "requires": { "minimist": "0.0.8" }, @@ -7653,7 +8340,8 @@ "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true } } }, @@ -7665,8 +8353,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "multipipe": { "version": "0.1.2", @@ -7692,7 +8379,6 @@ "version": "1.2.9", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz", "integrity": "sha512-n8R9bS8yQ6eSXaV6jHUpKzD8gLsin02w1HSFiegwrs9E098Ylhw5jdyKPaYqvHknHaSCKTPp7C8dGCQ0q9koXA==", - "dev": true, "requires": { "arr-diff": "^4.0.0", "array-unique": "^0.3.2", @@ -7711,20 +8397,17 @@ "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" }, "array-unique": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" }, "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" } } }, @@ -8044,7 +8727,6 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", - "dev": true, "requires": { "hosted-git-info": "^2.1.4", "is-builtin-module": "^1.0.0", @@ -8079,7 +8761,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, "requires": { "path-key": "^2.0.0" } @@ -8105,8 +8786,7 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "oauth-sign": { "version": "0.9.0", @@ -8124,7 +8804,6 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, "requires": { "copy-descriptor": "^0.1.0", "define-property": "^0.2.5", @@ -8135,7 +8814,6 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -8151,7 +8829,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, "requires": { "isobject": "^3.0.0" }, @@ -8159,8 +8836,7 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" } } }, @@ -8226,20 +8902,10 @@ } } }, - "object.omit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", - "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", - "requires": { - "for-own": "^0.1.4", - "is-extendable": "^0.1.1" - } - }, "object.pick": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, "requires": { "isobject": "^3.0.1" }, @@ -8247,8 +8913,7 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" } } }, @@ -8286,7 +8951,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, "requires": { "wrappy": "1" } @@ -8371,14 +9035,12 @@ "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" }, "p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", - "dev": true, "requires": { "p-try": "^1.0.0" } @@ -8387,7 +9049,6 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "dev": true, "requires": { "p-limit": "^1.1.0" } @@ -8395,8 +9056,7 @@ "p-try": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", - "dev": true + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" }, "pako": { "version": "1.0.6", @@ -8428,22 +9088,10 @@ "path-root": "^0.1.1" } }, - "parse-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", - "requires": { - "glob-base": "^0.3.0", - "is-dotfile": "^1.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.0" - } - }, "parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, "requires": { "error-ex": "^1.2.0" } @@ -8463,8 +9111,7 @@ "pascalcase": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" }, "path-browserify": { "version": "0.0.0", @@ -8475,8 +9122,7 @@ "path-dirname": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", - "dev": true + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" }, "path-exists": { "version": "2.1.0", @@ -8495,14 +9141,12 @@ "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" }, "path-parse": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", - "dev": true + "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=" }, "path-root": { "version": "0.1.1", @@ -8585,8 +9229,7 @@ "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" }, "pinkie": { "version": "2.0.4", @@ -8669,8 +9312,7 @@ "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" }, "postcss": { "version": "6.0.23", @@ -8689,11 +9331,6 @@ "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", "dev": true }, - "preserve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", - "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=" - }, "pretty-hrtime": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", @@ -8746,8 +9383,7 @@ "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", - "dev": true + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, "psl": { "version": "1.1.29", @@ -8814,28 +9450,6 @@ "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", "dev": true }, - "randomatic": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.0.0.tgz", - "integrity": "sha512-VdxFOIEY3mNO5PtSRkkle/hPJDHvQhK21oa73K4yAc9qmp6N429gAyF1gZMOTMeS0/AYzaV/2Trcef+NaIonSA==", - "requires": { - "is-number": "^4.0.0", - "kind-of": "^6.0.0", - "math-random": "^1.0.1" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==" - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" - } - } - }, "randombytes": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.6.tgz", @@ -8970,6 +9584,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", + "dev": true, "requires": { "graceful-fs": "^4.1.2", "minimatch": "^3.0.2", @@ -8981,7 +9596,6 @@ "version": "0.6.2", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", - "dev": true, "requires": { "resolve": "^1.1.6" } @@ -9001,19 +9615,10 @@ "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.12.tgz", "integrity": "sha512-n+IyV+nGz3+0q3/Yf1ra12KpCyi001bi4XFxSjbiWWjfqb52iTTtpGXmCCAOWWIAn9KEuFZKGqBERHmrtScZ3A==" }, - "regex-cache": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", - "requires": { - "is-equal-shallow": "^0.1.3" - } - }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, "requires": { "extend-shallow": "^3.0.2", "safe-regex": "^1.1.0" @@ -9120,20 +9725,17 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, "require-main-filename": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" }, "resolve": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz", "integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==", - "dev": true, "requires": { "path-parse": "^1.0.5" } @@ -9160,8 +9762,7 @@ "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" }, "restore-cursor": { "version": "2.0.0", @@ -9176,8 +9777,7 @@ "ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" }, "right-align": { "version": "0.1.3", @@ -9239,13 +9839,295 @@ } }, "rollup-pluginutils": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.3.3.tgz", - "integrity": "sha512-2XZwja7b6P5q4RZ5FhyX1+f46xi1Z3qBKigLRZ6VTZjwbN0K1IFGMlwm06Uu0Emcre2Z63l77nq/pzn+KxIEoA==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-2.4.1.tgz", + "integrity": "sha512-wesMQ9/172IJDIW/lYWm0vW0LiKe5Ekjws481R7z9WTRtmO59cqyM/2uUlxvf6yzm/fElFmHUobeQOYz46dZJw==", "dev": true, "requires": { - "estree-walker": "^0.5.2", - "micromatch": "^2.3.11" + "estree-walker": "^0.6.0", + "micromatch": "^3.1.10" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "dev": true + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "estree-walker": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.6.0.tgz", + "integrity": "sha512-peq1RfVAVzr3PU/jL31RaOjUKLoZJpObQWJJ+LgfcxDUifyLZ1RjPQZTl0pzj2uJ45b7A7XpyppXvxdEqzo4rw==", + "dev": true + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, + "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=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, + "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=", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "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" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "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==", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + } } }, "rxjs": { @@ -9271,7 +10153,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, "requires": { "ret": "~0.1.10" } @@ -9332,8 +10213,7 @@ "semver": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", - "dev": true + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" }, "semver-greatest-satisfied-range": { "version": "1.1.0", @@ -9385,19 +10265,18 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "set-immediate-shim": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true }, "set-value": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", - "dev": true, "requires": { "extend-shallow": "^2.0.1", "is-extendable": "^0.1.1", @@ -9409,7 +10288,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, "requires": { "is-extendable": "^0.1.0" } @@ -9442,7 +10320,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, "requires": { "shebang-regex": "^1.0.0" } @@ -9450,14 +10327,22 @@ "shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + }, + "shelljs": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.3.tgz", + "integrity": "sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A==", + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", - "dev": true + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, "slash": { "version": "0.1.3", @@ -9469,7 +10354,6 @@ "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, "requires": { "base": "^0.11.1", "debug": "^2.2.0", @@ -9485,7 +10369,6 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -9494,7 +10377,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, "requires": { "is-extendable": "^0.1.0" } @@ -9502,8 +10384,7 @@ "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" } } }, @@ -9511,7 +10392,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, "requires": { "define-property": "^1.0.0", "isobject": "^3.0.0", @@ -9522,7 +10402,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, "requires": { "is-descriptor": "^1.0.0" } @@ -9531,7 +10410,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -9540,7 +10418,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -9549,7 +10426,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", @@ -9559,14 +10435,12 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" }, "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" } } }, @@ -9574,7 +10448,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, "requires": { "kind-of": "^3.2.0" } @@ -9594,7 +10467,6 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", - "dev": true, "requires": { "atob": "^2.1.1", "decode-uri-component": "^0.2.0", @@ -9607,6 +10479,7 @@ "version": "0.5.6", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.6.tgz", "integrity": "sha512-N4KXEz7jcKqPf2b2vZF11lQIz9W5ZMuUcIOGj243lduidkf2fjkVKJS9vNxVWn3u/uxX38AcE8U9nnH9FPcq+g==", + "dev": true, "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -9615,8 +10488,12 @@ "source-map-url": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" + }, + "sourcemap-codec": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.4.tgz", + "integrity": "sha512-CYAPYdBu34781kLHkaW3m6b/uUSyMOC2R61gcYMWooeuaGtjof86ZA/8T+qVPPt7np1085CR9hmMGrySwEc8Xg==" }, "sparkles": { "version": "1.0.1", @@ -9639,7 +10516,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", - "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" @@ -9648,14 +10524,12 @@ "spdx-exceptions": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", - "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", - "dev": true + "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==" }, "spdx-expression-parse": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", - "dev": true, "requires": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" @@ -9664,14 +10538,12 @@ "spdx-license-ids": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", - "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", - "dev": true + "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==" }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, "requires": { "extend-shallow": "^3.0.0" } @@ -9723,7 +10595,6 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, "requires": { "define-property": "^0.2.5", "object-copy": "^0.1.0" @@ -9733,7 +10604,6 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -9800,7 +10670,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -9819,7 +10688,6 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, "requires": { "ansi-regex": "^2.0.0" } @@ -9836,8 +10704,7 @@ "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" }, "strip-indent": { "version": "1.0.1", @@ -10011,7 +10878,6 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, "requires": { "kind-of": "^3.0.2" } @@ -10020,7 +10886,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, "requires": { "define-property": "^2.0.2", "extend-shallow": "^3.0.2", @@ -10032,7 +10897,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, "requires": { "is-number": "^3.0.0", "repeat-string": "^1.6.1" @@ -10042,7 +10906,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, "requires": { "kind-of": "^3.0.2" } @@ -10097,17 +10960,6 @@ "resolved": "https://registry.npmjs.org/ts-md5/-/ts-md5-1.2.4.tgz", "integrity": "sha512-oW+rNjc9CAhalPFzbPWsLqPLzdNcJ8iSm+OXO+Uv+99r3PzCJuM5sVc0bO1eS+4LD2xv+nfU7ylBdwoemUV9Yw==" }, - "tsickle": { - "version": "0.27.5", - "resolved": "https://registry.npmjs.org/tsickle/-/tsickle-0.27.5.tgz", - "integrity": "sha512-NP+CjM1EXza/M8mOXBLH3vkFEJiu1zfEAlC5WdJxHPn8l96QPz5eooP6uAgYtw1CcKfuSyIiheNUdKxtDWCNeg==", - "requires": { - "minimist": "^1.2.0", - "mkdirp": "^0.5.1", - "source-map": "^0.6.0", - "source-map-support": "^0.5.0" - } - }, "tslib": { "version": "1.9.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.2.tgz", @@ -10328,7 +11180,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", - "dev": true, "requires": { "arr-union": "^3.1.0", "get-value": "^2.0.6", @@ -10340,7 +11191,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, "requires": { "is-extendable": "^0.1.0" } @@ -10349,7 +11199,6 @@ "version": "0.4.3", "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", - "dev": true, "requires": { "extend-shallow": "^2.0.1", "is-extendable": "^0.1.1", @@ -10385,7 +11234,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, "requires": { "has-value": "^0.3.1", "isobject": "^3.0.0" @@ -10395,7 +11243,6 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, "requires": { "get-value": "^2.0.3", "has-values": "^0.1.4", @@ -10406,7 +11253,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, "requires": { "isarray": "1.0.0" } @@ -10416,22 +11262,19 @@ "has-values": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" }, "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" } } }, "upath": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", - "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", - "dev": true + "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==" }, "uri-js": { "version": "4.2.2", @@ -10453,8 +11296,7 @@ "urix": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" }, "url": { "version": "0.11.0", @@ -10478,7 +11320,6 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/use/-/use-3.1.0.tgz", "integrity": "sha512-6UJEQM/L+mzC3ZJNM56Q4DFGLX/evKGRg15UJHGB9X5j5Z3AFbgZvjUh2yq/UJUY4U5dh7Fal++XbNg1uzpRAw==", - "dev": true, "requires": { "kind-of": "^6.0.2" }, @@ -10486,8 +11327,7 @@ "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" } } }, @@ -10536,7 +11376,6 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", - "dev": true, "requires": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" @@ -11820,7 +12659,6 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, "requires": { "isexe": "^2.0.0" } @@ -11865,7 +12703,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true, "requires": { "string-width": "^1.0.1", "strip-ansi": "^3.0.1" @@ -11874,8 +12711,7 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, "ws": { "version": "3.3.2", @@ -11917,14 +12753,12 @@ "y18n": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" }, "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", - "dev": true + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" }, "yargs": { "version": "7.1.0", diff --git a/package.json b/package.json index 2fa23a48a..d7de0b7e3 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "@angular/animations": "^5.2.10", "@angular/common": "^5.2.10", "@angular/compiler": "^5.2.10", - "@angular/compiler-cli": "^5.2.10", + "@angular/compiler-cli": "^7.2.9", "@angular/core": "^5.2.10", "@angular/forms": "^5.2.10", "@angular/http": "^5.2.10", From 6e054bf6cdc938d468418273b76afd7616c2dfa4 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 13 Mar 2019 11:08:15 +0100 Subject: [PATCH 141/191] MOBILE-2919 message: Open conversation when push clicked --- src/addon/messages/messages.module.ts | 11 ++++++++++- .../pages/group-conversations/group-conversations.ts | 7 +++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/addon/messages/messages.module.ts b/src/addon/messages/messages.module.ts index 43b3609c6..9ea151d83 100644 --- a/src/addon/messages/messages.module.ts +++ b/src/addon/messages/messages.module.ts @@ -109,11 +109,20 @@ export class AddonMessagesModule { messagesProvider.invalidateDiscussionsCache(notification.site).finally(() => { // Check if group messaging is enabled, to determine which page should be loaded. messagesProvider.isGroupMessagingEnabledInSite(notification.site).then((enabled) => { + const pageParams: any = {}; let pageName = 'AddonMessagesIndexPage'; if (enabled) { pageName = 'AddonMessagesGroupConversationsPage'; } - linkHelper.goInSite(undefined, pageName, undefined, notification.site); + + // Check if we have enough information to open the conversation. + if (notification.convid && enabled) { + pageParams.conversationId = Number(notification.convid); + } else if (notification.userfromid) { + pageParams.discussionUserId = Number(notification.userfromid); + } + + linkHelper.goInSite(undefined, pageName, pageParams, notification.site); }); }); }); diff --git a/src/addon/messages/pages/group-conversations/group-conversations.ts b/src/addon/messages/pages/group-conversations/group-conversations.ts index 39be42305..6dd5f0d28 100644 --- a/src/addon/messages/pages/group-conversations/group-conversations.ts +++ b/src/addon/messages/pages/group-conversations/group-conversations.ts @@ -70,6 +70,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { protected siteId: string; protected currentUserId: number; protected conversationId: number; + protected discussionUserId: number; protected newMessagesObserver: any; protected pushObserver: any; protected appResumeSubscription: any; @@ -89,7 +90,9 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { this.loadingString = translate.instant('core.loading'); this.siteId = sitesProvider.getCurrentSiteId(); this.currentUserId = sitesProvider.getCurrentSiteUserId(); + // Conversation to load. this.conversationId = navParams.get('conversationId') || false; + this.discussionUserId = !this.conversationId && (navParams.get('discussionUserId') || false); // Update conversations when new message is received. this.newMessagesObserver = eventsProvider.on(AddonMessagesProvider.NEW_MESSAGE_EVENT, (data) => { @@ -213,9 +216,9 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { * Component loaded. */ ngOnInit(): void { - if (this.conversationId) { + if (this.conversationId || this.discussionUserId) { // There is a discussion to load, open the discussion in a new state. - this.gotoConversation(this.conversationId); + this.gotoConversation(this.conversationId, this.discussionUserId); } this.fetchData().then(() => { From 0f8b7f35b16498389925ef4bdfa2cd9fb33d1910 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 13 Mar 2019 11:58:41 +0100 Subject: [PATCH 142/191] MOBILE-2831 config: Restore compiler-cli version --- package-lock.json | 1480 ++++++++++++++------------------------------- package.json | 2 +- 2 files changed, 465 insertions(+), 1017 deletions(-) diff --git a/package-lock.json b/package-lock.json index 548822a5a..282a99d8a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,157 +55,14 @@ } }, "@angular/compiler-cli": { - "version": "7.2.9", - "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-7.2.9.tgz", - "integrity": "sha512-3itdcfszdyXTHYEsO4eBu4WEx10hU8JpOgUcZyw+OYgwLQLyjEXOD9dfYZZpE/+2F0omoMLseCTHTP//uux+Iw==", + "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": { - "canonical-path": "1.0.0", - "chokidar": "^2.1.1", - "convert-source-map": "^1.5.1", - "dependency-graph": "^0.7.2", - "magic-string": "^0.25.0", + "chokidar": "^1.4.2", "minimist": "^1.2.0", "reflect-metadata": "^0.1.2", - "shelljs": "^0.8.1", - "source-map": "^0.6.1", - "tslib": "^1.9.0", - "yargs": "9.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" - }, - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" - }, - "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", - "requires": { - "locate-path": "^2.0.0" - } - }, - "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=" - }, - "load-json-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "strip-bom": "^3.0.0" - } - }, - "magic-string": { - "version": "0.25.2", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.2.tgz", - "integrity": "sha512-iLs9mPjh9IuTtRsqqhNGYcZXGei0Nh/A4xirrsqW7c+QhKVFL2vm7U09ru6cHRD22azaP/wMDgI+HCqbETMTtg==", - "requires": { - "sourcemap-codec": "^1.4.4" - } - }, - "os-locale": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", - "requires": { - "execa": "^0.7.0", - "lcid": "^1.0.0", - "mem": "^1.1.0" - } - }, - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", - "requires": { - "pify": "^2.0.0" - } - }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", - "requires": { - "load-json-file": "^2.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^2.0.0" - } - }, - "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^2.0.0" - } - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "requires": { - "ansi-regex": "^3.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" - }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" - }, - "yargs": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-9.0.1.tgz", - "integrity": "sha1-UqzCP+7Kw0BCB47njAwAf1CF20w=", - "requires": { - "camelcase": "^4.1.0", - "cliui": "^3.2.0", - "decamelize": "^1.1.1", - "get-caller-file": "^1.0.1", - "os-locale": "^2.0.0", - "read-pkg-up": "^2.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^7.0.0" - } - }, - "yargs-parser": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", - "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", - "requires": { - "camelcase": "^4.1.0" - } - } + "tsickle": "^0.27.2" } }, "@angular/core": { @@ -1451,7 +1308,8 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true }, "ansi-styles": { "version": "3.2.1", @@ -1486,12 +1344,12 @@ "dev": true }, "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", + "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" + "micromatch": "^2.1.5", + "normalize-path": "^2.0.0" } }, "append-buffer": { @@ -1535,9 +1393,12 @@ } }, "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "requires": { + "arr-flatten": "^1.0.1" + } }, "arr-filter": { "version": "1.1.2", @@ -1565,7 +1426,8 @@ "arr-union": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "dev": true }, "array-differ": { "version": "1.0.0", @@ -1658,9 +1520,9 @@ "dev": true }, "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=" }, "asn1": { "version": "0.2.3", @@ -1714,7 +1576,8 @@ "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "dev": true }, "async": { "version": "2.6.2", @@ -1786,7 +1649,8 @@ "atob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.1.tgz", - "integrity": "sha1-ri1acpR38onWDdf5amMUoi3Wwio=" + "integrity": "sha1-ri1acpR38onWDdf5amMUoi3Wwio=", + "dev": true }, "autoprefixer": { "version": "7.2.6", @@ -1883,6 +1747,7 @@ "version": "0.11.2", "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "dev": true, "requires": { "cache-base": "^1.0.1", "class-utils": "^0.3.5", @@ -1897,6 +1762,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, "requires": { "is-descriptor": "^1.0.0" } @@ -1905,6 +1771,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -1913,6 +1780,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -1921,6 +1789,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", @@ -1930,12 +1799,14 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "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==" + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true } } }, @@ -2066,30 +1937,13 @@ } }, "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } + "expand-range": "^1.8.1", + "preserve": "^0.2.0", + "repeat-element": "^1.1.2" } }, "brorand": { @@ -2238,8 +2092,7 @@ "buffer-from": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.0.tgz", - "integrity": "sha512-c5mRlguI/Pe2dSZmpER62rSCu0ryKmWddzRYsuXc50U2/g8jMOulc31VZMa4mYx31U5xsmSOpDCgH88Vl9cDGQ==", - "dev": true + "integrity": "sha512-c5mRlguI/Pe2dSZmpER62rSCu0ryKmWddzRYsuXc50U2/g8jMOulc31VZMa4mYx31U5xsmSOpDCgH88Vl9cDGQ==" }, "buffer-xor": { "version": "1.0.3", @@ -2307,7 +2160,8 @@ "builtin-modules": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=" + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true }, "builtin-status-codes": { "version": "3.0.0", @@ -2325,6 +2179,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "dev": true, "requires": { "collection-visit": "^1.0.0", "component-emitter": "^1.2.1", @@ -2340,7 +2195,8 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true } } }, @@ -2366,11 +2222,6 @@ "integrity": "sha512-ekW8NQ3/FvokviDxhdKLZZAx7PptXNwxKgXtnR5y+PR3hckwuP3yJ1Ir+4/c97dsHNqtAyfKUGdw8P4EYzBNgw==", "dev": true }, - "canonical-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/canonical-path/-/canonical-path-1.0.0.tgz", - "integrity": "sha512-feylzsbDxi1gPZ1IjystzIQZagYYLvfKrSuygUCgf7z6x790VEzze5QEkdSV1U58RA7Hi0+v6fv4K54atOzATg==" - }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", @@ -2425,501 +2276,19 @@ } }, "chokidar": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.2.tgz", - "integrity": "sha512-IwXUx0FXc5ibYmPC2XeEj5mpXoV66sR+t3jqu2NS2GYwCktt3KF1/Qqjws/NkegajBA4RbZ5+DDwlOiJsxDHEg==", + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", "requires": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", + "anymatch": "^1.3.0", + "async-each": "^1.0.0", + "fsevents": "^1.0.0", + "glob-parent": "^2.0.0", + "inherits": "^2.0.1", "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", + "is-glob": "^2.0.0", "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.0" - }, - "dependencies": { - "fsevents": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.7.tgz", - "integrity": "sha512-Pxm6sI2MeBD7RdD12RYsqaP0nMiwx8eZBXCa6z2L+mRHm2DYrOYwihmhjpkdjUHwQhslWQjRpEgNq4XvBmaAuw==", - "optional": true, - "requires": { - "nan": "^2.9.2", - "node-pre-gyp": "^0.10.0" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.1", - "bundled": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "debug": { - "version": "2.6.9", - "bundled": true, - "optional": true, - "requires": { - "ms": "2.0.0" - } - }, - "deep-extend": { - "version": "0.6.0", - "bundled": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.5", - "bundled": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": 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, - "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, - "optional": true - }, - "iconv-lite": { - "version": "0.4.24", - "bundled": true, - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.1", - "bundled": true, - "optional": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true - }, - "minipass": { - "version": "2.3.5", - "bundled": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.2.1", - "bundled": true, - "optional": true, - "requires": { - "minipass": "^2.2.1" - } - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "bundled": true, - "optional": true - }, - "needle": { - "version": "2.2.4", - "bundled": 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, - "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, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.0.5", - "bundled": true, - "optional": true - }, - "npm-packlist": { - "version": "1.2.0", - "bundled": true, - "optional": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": 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 - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.0", - "bundled": true, - "optional": true - }, - "rc": { - "version": "1.2.8", - "bundled": 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, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.3.6", - "bundled": 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, - "optional": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "optional": true - }, - "semver": { - "version": "5.6.0", - "bundled": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "bundled": 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, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "tar": { - "version": "4.4.8", - "bundled": 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, - "optional": true - }, - "wide-align": { - "version": "1.1.3", - "bundled": true, - "optional": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true - }, - "yallist": { - "version": "3.0.3", - "bundled": true - } - } - }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" - }, - "readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "requires": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - } - } + "readdirp": "^2.0.0" } }, "chromium-pickle-js": { @@ -2948,6 +2317,7 @@ "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "dev": true, "requires": { "arr-union": "^3.1.0", "define-property": "^0.2.5", @@ -2959,6 +2329,7 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -2966,7 +2337,8 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true } } }, @@ -2998,6 +2370,7 @@ "version": "3.2.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, "requires": { "string-width": "^1.0.1", "strip-ansi": "^3.0.1", @@ -3042,7 +2415,8 @@ "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true }, "collection-map": { "version": "1.0.0", @@ -3070,6 +2444,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "dev": true, "requires": { "map-visit": "^1.0.0", "object-visit": "^1.0.0" @@ -3126,7 +2501,8 @@ "component-emitter": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=" + "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", + "dev": true }, "concat-map": { "version": "0.0.1", @@ -3188,6 +2564,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "dev": true, "requires": { "safe-buffer": "~5.1.1" } @@ -3207,7 +2584,8 @@ "copy-descriptor": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "dev": true }, "copy-props": { "version": "2.0.4", @@ -3903,6 +3281,7 @@ "version": "5.1.0", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, "requires": { "lru-cache": "^4.0.1", "shebang-command": "^1.2.0", @@ -3979,6 +3358,7 @@ "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" } @@ -3986,12 +3366,14 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true }, "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "dev": true }, "default-compare": { "version": "1.0.0", @@ -4029,6 +3411,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "dev": true, "requires": { "is-descriptor": "^1.0.2", "isobject": "^3.0.1" @@ -4038,6 +3421,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -4046,6 +3430,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -4054,6 +3439,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", @@ -4063,12 +3449,14 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "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==" + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true } } }, @@ -4090,11 +3478,6 @@ "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", "dev": true }, - "dependency-graph": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.7.2.tgz", - "integrity": "sha512-KqtH4/EZdtdfWX0p6MGP9jljvxSY6msy/pRUD4jgNwVpv3v1QmNLlsB3LDSSUg79BRVSn7jI1QPRtArGABovAQ==" - }, "des.js": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.0.tgz", @@ -4566,6 +3949,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", + "dev": true, "requires": { "is-arrayish": "^0.2.1" } @@ -4766,6 +4150,7 @@ "version": "0.7.0", "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "dev": true, "requires": { "cross-spawn": "^5.0.1", "get-stream": "^3.0.0", @@ -4777,35 +4162,19 @@ } }, "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } + "is-posix-bracket": "^0.1.0" + } + }, + "expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "requires": { + "fill-range": "^2.1.0" } }, "expand-tilde": { @@ -4873,6 +4242,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "dev": true, "requires": { "assign-symbols": "^1.0.0", "is-extendable": "^1.0.1" @@ -4882,6 +4252,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, "requires": { "is-plain-object": "^2.0.4" } @@ -4889,67 +4260,11 @@ } }, "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" - } + "is-extglob": "^1.0.0" } }, "extsprintf": { @@ -4990,25 +4305,21 @@ "websocket-driver": ">=0.5.1" } }, + "filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=" + }, "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", + "integrity": "sha512-cnrcCbj01+j2gTG921VZPnHbjmdAf8oQV/iGeV2kZxGSyfYjjTyY79ErsK1WJWMpw6DaApEX72binqJE+/d+5Q==", "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "requires": { - "is-extendable": "^0.1.0" - } - } + "is-number": "^2.1.0", + "isobject": "^2.0.0", + "randomatic": "^3.0.0", + "repeat-element": "^1.1.2", + "repeat-string": "^1.5.2" } }, "finalhandler": { @@ -5378,6 +4689,14 @@ "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" }, + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "requires": { + "for-in": "^1.0.1" + } + }, "foreach": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", @@ -5410,6 +4729,7 @@ "version": "0.2.1", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "dev": true, "requires": { "map-cache": "^0.2.2" } @@ -5467,13 +4787,13 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true }, "fsevents": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.4.tgz", "integrity": "sha512-z8H8/diyk76B7q5wg+Ud0+CqzcAF3mBBI/bA5ne5zrRUUIvNkJY//D3BqyH571KuAC4Nr7Rw7CjWX4r0y9DvNg==", - "dev": true, "optional": true, "requires": { "nan": "^2.9.2", @@ -5483,24 +4803,20 @@ "abbrev": { "version": "1.1.1", "bundled": true, - "dev": true, "optional": true }, "ansi-regex": { "version": "2.1.1", - "bundled": true, - "dev": true + "bundled": true }, "aproba": { "version": "1.2.0", "bundled": true, - "dev": true, "optional": true }, "are-we-there-yet": { "version": "1.1.4", "bundled": true, - "dev": true, "optional": true, "requires": { "delegates": "^1.0.0", @@ -5509,13 +4825,11 @@ }, "balanced-match": { "version": "1.0.0", - "bundled": true, - "dev": true + "bundled": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, - "dev": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5524,34 +4838,28 @@ "chownr": { "version": "1.0.1", "bundled": true, - "dev": true, "optional": true }, "code-point-at": { "version": "1.1.0", - "bundled": true, - "dev": true + "bundled": true }, "concat-map": { "version": "0.0.1", - "bundled": true, - "dev": true + "bundled": true }, "console-control-strings": { "version": "1.1.0", - "bundled": true, - "dev": true + "bundled": 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" @@ -5560,25 +4868,21 @@ "deep-extend": { "version": "0.5.1", "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" @@ -5587,13 +4891,11 @@ "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", @@ -5609,7 +4911,6 @@ "glob": { "version": "7.1.2", "bundled": true, - "dev": true, "optional": true, "requires": { "fs.realpath": "^1.0.0", @@ -5623,13 +4924,11 @@ "has-unicode": { "version": "2.0.1", "bundled": true, - "dev": true, "optional": true }, "iconv-lite": { "version": "0.4.21", "bundled": true, - "dev": true, "optional": true, "requires": { "safer-buffer": "^2.1.0" @@ -5638,7 +4937,6 @@ "ignore-walk": { "version": "3.0.1", "bundled": true, - "dev": true, "optional": true, "requires": { "minimatch": "^3.0.4" @@ -5647,7 +4945,6 @@ "inflight": { "version": "1.0.6", "bundled": true, - "dev": true, "optional": true, "requires": { "once": "^1.3.0", @@ -5656,19 +4953,16 @@ }, "inherits": { "version": "2.0.3", - "bundled": true, - "dev": true + "bundled": 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" } @@ -5676,26 +4970,22 @@ "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 + "bundled": true }, "minipass": { "version": "2.2.4", "bundled": true, - "dev": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -5704,7 +4994,6 @@ "minizlib": { "version": "1.1.0", "bundled": true, - "dev": true, "optional": true, "requires": { "minipass": "^2.2.1" @@ -5713,7 +5002,6 @@ "mkdirp": { "version": "0.5.1", "bundled": true, - "dev": true, "requires": { "minimist": "0.0.8" } @@ -5721,13 +5009,11 @@ "ms": { "version": "2.0.0", "bundled": true, - "dev": true, "optional": true }, "needle": { "version": "2.2.0", "bundled": true, - "dev": true, "optional": true, "requires": { "debug": "^2.1.2", @@ -5738,7 +5024,6 @@ "node-pre-gyp": { "version": "0.10.0", "bundled": true, - "dev": true, "optional": true, "requires": { "detect-libc": "^1.0.2", @@ -5756,7 +5041,6 @@ "nopt": { "version": "4.0.1", "bundled": true, - "dev": true, "optional": true, "requires": { "abbrev": "1", @@ -5766,13 +5050,11 @@ "npm-bundled": { "version": "1.0.3", "bundled": true, - "dev": true, "optional": true }, "npm-packlist": { "version": "1.1.10", "bundled": true, - "dev": true, "optional": true, "requires": { "ignore-walk": "^3.0.1", @@ -5782,7 +5064,6 @@ "npmlog": { "version": "4.1.2", "bundled": true, - "dev": true, "optional": true, "requires": { "are-we-there-yet": "~1.1.2", @@ -5793,19 +5074,16 @@ }, "number-is-nan": { "version": "1.0.1", - "bundled": true, - "dev": true + "bundled": 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" } @@ -5813,19 +5091,16 @@ "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", @@ -5835,19 +5110,16 @@ "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.7", "bundled": true, - "dev": true, "optional": true, "requires": { "deep-extend": "^0.5.1", @@ -5859,7 +5131,6 @@ "minimist": { "version": "1.2.0", "bundled": true, - "dev": true, "optional": true } } @@ -5867,7 +5138,6 @@ "readable-stream": { "version": "2.3.6", "bundled": true, - "dev": true, "optional": true, "requires": { "core-util-is": "~1.0.0", @@ -5882,7 +5152,6 @@ "rimraf": { "version": "2.6.2", "bundled": true, - "dev": true, "optional": true, "requires": { "glob": "^7.0.5" @@ -5890,43 +5159,36 @@ }, "safe-buffer": { "version": "5.1.1", - "bundled": true, - "dev": true + "bundled": 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.5.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", @@ -5936,7 +5198,6 @@ "string_decoder": { "version": "1.1.1", "bundled": true, - "dev": true, "optional": true, "requires": { "safe-buffer": "~5.1.0" @@ -5945,7 +5206,6 @@ "strip-ansi": { "version": "3.0.1", "bundled": true, - "dev": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5953,13 +5213,11 @@ "strip-json-comments": { "version": "2.0.1", "bundled": true, - "dev": true, "optional": true }, "tar": { "version": "4.4.1", "bundled": true, - "dev": true, "optional": true, "requires": { "chownr": "^1.0.1", @@ -5974,13 +5232,11 @@ "util-deprecate": { "version": "1.0.2", "bundled": true, - "dev": true, "optional": true }, "wide-align": { "version": "1.1.2", "bundled": true, - "dev": true, "optional": true, "requires": { "string-width": "^1.0.2" @@ -5988,13 +5244,11 @@ }, "wrappy": { "version": "1.0.2", - "bundled": true, - "dev": true + "bundled": true }, "yallist": { "version": "3.0.2", - "bundled": true, - "dev": true + "bundled": true } } }, @@ -6043,7 +5297,8 @@ "get-caller-file": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", - "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=" + "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=", + "dev": true }, "get-stdin": { "version": "4.0.1", @@ -6054,12 +5309,14 @@ "get-stream": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true }, "get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "dev": true }, "getpass": { "version": "0.1.7", @@ -6082,6 +5339,7 @@ "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -6091,23 +5349,21 @@ "path-is-absolute": "^1.0.0" } }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "requires": { - "is-extglob": "^2.1.0" - } - } + "glob-parent": "^2.0.0", + "is-glob": "^2.0.0" + } + }, + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "requires": { + "is-glob": "^2.0.0" } }, "glob-stream": { @@ -6869,6 +6125,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "dev": true, "requires": { "get-value": "^2.0.6", "has-values": "^1.0.0", @@ -6878,7 +6135,8 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true } } }, @@ -6886,6 +6144,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "dev": true, "requires": { "is-number": "^3.0.0", "kind-of": "^4.0.0" @@ -6895,6 +6154,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, "requires": { "kind-of": "^3.0.2" }, @@ -6903,6 +6163,7 @@ "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" } @@ -6913,6 +6174,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, "requires": { "is-buffer": "^1.1.5" } @@ -6962,7 +6224,8 @@ "hosted-git-info": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz", - "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==" + "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==", + "dev": true }, "http-errors": { "version": "1.6.3", @@ -7044,6 +6307,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, "requires": { "once": "^1.3.0", "wrappy": "1" @@ -7068,12 +6332,14 @@ "interpret": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", - "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=" + "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", + "dev": true }, "invert-kv": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true }, "ionic-angular": { "version": "3.9.3", @@ -7105,6 +6371,7 @@ "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "dev": true, "requires": { "kind-of": "^3.0.2" } @@ -7112,7 +6379,8 @@ "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true }, "is-binary-path": { "version": "1.0.1", @@ -7131,6 +6399,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "dev": true, "requires": { "builtin-modules": "^1.0.0" } @@ -7153,6 +6422,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "dev": true, "requires": { "kind-of": "^3.0.2" } @@ -7166,6 +6436,7 @@ "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "dev": true, "requires": { "is-accessor-descriptor": "^0.1.6", "is-data-descriptor": "^0.1.4", @@ -7175,19 +6446,33 @@ "kind-of": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==" + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true } } }, + "is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=" + }, + "is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "requires": { + "is-primitive": "^2.0.0" + } + }, "is-extendable": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=" }, "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=" }, "is-finite": { "version": "1.0.2", @@ -7202,16 +6487,17 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, "requires": { "number-is-nan": "^1.0.0" } }, "is-glob": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", - "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", "requires": { - "is-extglob": "^2.1.1" + "is-extglob": "^1.0.0" } }, "is-module": { @@ -7227,9 +6513,9 @@ "dev": true }, "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", "requires": { "kind-of": "^3.0.2" } @@ -7238,6 +6524,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-2.0.0.tgz", "integrity": "sha512-OTiixgpZAT1M4NHgS5IguFp/Vz2VI3U7Goh4/HA1adtwyLtSBrxYlcSYkhpAE07s4fKEcjrFxyvtQBND4vFQyQ==", + "dev": true, "requires": { "is-number": "^4.0.0" }, @@ -7245,7 +6532,8 @@ "is-number": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==" + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", + "dev": true } } }, @@ -7253,6 +6541,7 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, "requires": { "isobject": "^3.0.1" }, @@ -7260,10 +6549,21 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true } } }, + "is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=" + }, + "is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=" + }, "is-regex": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", @@ -7284,7 +6584,8 @@ "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true }, "is-symbol": { "version": "1.0.1", @@ -7321,7 +6622,8 @@ "is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==" + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "dev": true }, "isarray": { "version": "1.0.0", @@ -7340,12 +6642,16 @@ "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true }, "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "requires": { + "isarray": "1.0.0" + } }, "isstream": { "version": "0.1.2", @@ -7545,6 +6851,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true, "requires": { "invert-kv": "^1.0.0" } @@ -7622,6 +6929,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, "requires": { "p-locate": "^2.0.0", "path-exists": "^3.0.0" @@ -7630,7 +6938,8 @@ "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true } } }, @@ -7812,6 +7121,7 @@ "version": "4.1.5", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, "requires": { "pseudomap": "^1.0.2", "yallist": "^2.1.2" @@ -7852,7 +7162,8 @@ "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "dev": true }, "map-obj": { "version": "1.0.1", @@ -7864,6 +7175,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "dev": true, "requires": { "object-visit": "^1.0.0" } @@ -8156,6 +7468,11 @@ } } }, + "math-random": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.4.tgz", + "integrity": "sha512-rUxjysqif/BZQH2yhd5Aaq7vXMSx9NdEsQcyA07uEzIvxgI7zIr33gGsh+RU0/XjmQpCW7RsVof1vlkvQVCK5A==" + }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -8177,6 +7494,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", + "dev": true, "requires": { "mimic-fn": "^1.0.0" } @@ -8222,30 +7540,23 @@ "dev": true }, "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" - } + "arr-diff": "^2.0.0", + "array-unique": "^0.2.1", + "braces": "^1.8.2", + "expand-brackets": "^0.1.4", + "extglob": "^0.3.1", + "filename-regex": "^2.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.1", + "kind-of": "^3.0.2", + "normalize-path": "^2.0.1", + "object.omit": "^2.0.0", + "parse-glob": "^3.0.4", + "regex-cache": "^0.4.2" } }, "miller-rabin": { @@ -8282,7 +7593,8 @@ "mimic-fn": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" + "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", + "dev": true }, "minimalistic-assert": { "version": "1.0.1", @@ -8313,6 +7625,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", + "dev": true, "requires": { "for-in": "^1.0.2", "is-extendable": "^1.0.1" @@ -8322,6 +7635,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "dev": true, "requires": { "is-plain-object": "^2.0.4" } @@ -8332,7 +7646,6 @@ "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, "requires": { "minimist": "0.0.8" }, @@ -8340,8 +7653,7 @@ "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" } } }, @@ -8353,7 +7665,8 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true }, "multipipe": { "version": "0.1.2", @@ -8379,6 +7692,7 @@ "version": "1.2.9", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz", "integrity": "sha512-n8R9bS8yQ6eSXaV6jHUpKzD8gLsin02w1HSFiegwrs9E098Ylhw5jdyKPaYqvHknHaSCKTPp7C8dGCQ0q9koXA==", + "dev": true, "requires": { "arr-diff": "^4.0.0", "array-unique": "^0.3.2", @@ -8397,17 +7711,20 @@ "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "dev": true }, "array-unique": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "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==" + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true } } }, @@ -8727,6 +8044,7 @@ "version": "2.4.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "dev": true, "requires": { "hosted-git-info": "^2.1.4", "is-builtin-module": "^1.0.0", @@ -8761,6 +8079,7 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, "requires": { "path-key": "^2.0.0" } @@ -8786,7 +8105,8 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true }, "oauth-sign": { "version": "0.9.0", @@ -8804,6 +8124,7 @@ "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "dev": true, "requires": { "copy-descriptor": "^0.1.0", "define-property": "^0.2.5", @@ -8814,6 +8135,7 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -8829,6 +8151,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "dev": true, "requires": { "isobject": "^3.0.0" }, @@ -8836,7 +8159,8 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true } } }, @@ -8902,10 +8226,20 @@ } } }, + "object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "requires": { + "for-own": "^0.1.4", + "is-extendable": "^0.1.1" + } + }, "object.pick": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, "requires": { "isobject": "^3.0.1" }, @@ -8913,7 +8247,8 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true } } }, @@ -8951,6 +8286,7 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, "requires": { "wrappy": "1" } @@ -9035,12 +8371,14 @@ "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true }, "p-limit": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, "requires": { "p-try": "^1.0.0" } @@ -9049,6 +8387,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, "requires": { "p-limit": "^1.1.0" } @@ -9056,7 +8395,8 @@ "p-try": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true }, "pako": { "version": "1.0.6", @@ -9088,10 +8428,22 @@ "path-root": "^0.1.1" } }, + "parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", + "requires": { + "glob-base": "^0.3.0", + "is-dotfile": "^1.0.0", + "is-extglob": "^1.0.0", + "is-glob": "^2.0.0" + } + }, "parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, "requires": { "error-ex": "^1.2.0" } @@ -9111,7 +8463,8 @@ "pascalcase": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "dev": true }, "path-browserify": { "version": "0.0.0", @@ -9122,7 +8475,8 @@ "path-dirname": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "dev": true }, "path-exists": { "version": "2.1.0", @@ -9141,12 +8495,14 @@ "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true }, "path-parse": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=" + "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", + "dev": true }, "path-root": { "version": "0.1.1", @@ -9229,7 +8585,8 @@ "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true }, "pinkie": { "version": "2.0.4", @@ -9312,7 +8669,8 @@ "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "dev": true }, "postcss": { "version": "6.0.23", @@ -9331,6 +8689,11 @@ "integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==", "dev": true }, + "preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=" + }, "pretty-hrtime": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", @@ -9383,7 +8746,8 @@ "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true }, "psl": { "version": "1.1.29", @@ -9450,6 +8814,28 @@ "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", "dev": true }, + "randomatic": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.1.1.tgz", + "integrity": "sha512-TuDE5KxZ0J461RVjrJZCJc+J+zCkTb1MbH9AQUq68sMhOMcy9jLcb3BrZKgp9q9Ncltdg4QVqWrH02W2EFFVYw==", + "requires": { + "is-number": "^4.0.0", + "kind-of": "^6.0.0", + "math-random": "^1.0.1" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==" + }, + "kind-of": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + } + } + }, "randombytes": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.6.tgz", @@ -9584,7 +8970,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", - "dev": true, "requires": { "graceful-fs": "^4.1.2", "minimatch": "^3.0.2", @@ -9596,6 +8981,7 @@ "version": "0.6.2", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, "requires": { "resolve": "^1.1.6" } @@ -9615,10 +9001,19 @@ "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.12.tgz", "integrity": "sha512-n+IyV+nGz3+0q3/Yf1ra12KpCyi001bi4XFxSjbiWWjfqb52iTTtpGXmCCAOWWIAn9KEuFZKGqBERHmrtScZ3A==" }, + "regex-cache": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "requires": { + "is-equal-shallow": "^0.1.3" + } + }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "dev": true, "requires": { "extend-shallow": "^3.0.2", "safe-regex": "^1.1.0" @@ -9725,17 +9120,20 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true }, "require-main-filename": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true }, "resolve": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz", "integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==", + "dev": true, "requires": { "path-parse": "^1.0.5" } @@ -9762,7 +9160,8 @@ "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "dev": true }, "restore-cursor": { "version": "2.0.0", @@ -9777,7 +9176,8 @@ "ret": { "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "dev": true }, "right-align": { "version": "0.1.3", @@ -10153,6 +9553,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "dev": true, "requires": { "ret": "~0.1.10" } @@ -10213,7 +9614,8 @@ "semver": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" + "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "dev": true }, "semver-greatest-satisfied-range": { "version": "1.1.0", @@ -10265,18 +9667,19 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true }, "set-immediate-shim": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", - "dev": true + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=" }, "set-value": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", + "dev": true, "requires": { "extend-shallow": "^2.0.1", "is-extendable": "^0.1.1", @@ -10288,6 +9691,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, "requires": { "is-extendable": "^0.1.0" } @@ -10320,6 +9724,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, "requires": { "shebang-regex": "^1.0.0" } @@ -10327,22 +9732,14 @@ "shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" - }, - "shelljs": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.3.tgz", - "integrity": "sha512-fc0BKlAWiLpwZljmOvAOTE/gXawtCoNrP5oaY7KIaQbbyHeQVg01pSEuEGvGh3HEdBU4baCD7wQBwADmM/7f7A==", - "requires": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - } + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true }, "slash": { "version": "0.1.3", @@ -10354,6 +9751,7 @@ "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "dev": true, "requires": { "base": "^0.11.1", "debug": "^2.2.0", @@ -10369,6 +9767,7 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -10377,6 +9776,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, "requires": { "is-extendable": "^0.1.0" } @@ -10384,7 +9784,8 @@ "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true } } }, @@ -10392,6 +9793,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "dev": true, "requires": { "define-property": "^1.0.0", "isobject": "^3.0.0", @@ -10402,6 +9804,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "dev": true, "requires": { "is-descriptor": "^1.0.0" } @@ -10410,6 +9813,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -10418,6 +9822,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, "requires": { "kind-of": "^6.0.0" } @@ -10426,6 +9831,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", @@ -10435,12 +9841,14 @@ "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "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==" + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true } } }, @@ -10448,6 +9856,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "dev": true, "requires": { "kind-of": "^3.2.0" } @@ -10467,6 +9876,7 @@ "version": "0.5.2", "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", "integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==", + "dev": true, "requires": { "atob": "^2.1.1", "decode-uri-component": "^0.2.0", @@ -10479,7 +9889,6 @@ "version": "0.5.6", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.6.tgz", "integrity": "sha512-N4KXEz7jcKqPf2b2vZF11lQIz9W5ZMuUcIOGj243lduidkf2fjkVKJS9vNxVWn3u/uxX38AcE8U9nnH9FPcq+g==", - "dev": true, "requires": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -10488,12 +9897,8 @@ "source-map-url": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" - }, - "sourcemap-codec": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.4.tgz", - "integrity": "sha512-CYAPYdBu34781kLHkaW3m6b/uUSyMOC2R61gcYMWooeuaGtjof86ZA/8T+qVPPt7np1085CR9hmMGrySwEc8Xg==" + "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", + "dev": true }, "sparkles": { "version": "1.0.1", @@ -10516,6 +9921,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", + "dev": true, "requires": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" @@ -10524,12 +9930,14 @@ "spdx-exceptions": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", - "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==" + "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==", + "dev": true }, "spdx-expression-parse": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "dev": true, "requires": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" @@ -10538,12 +9946,14 @@ "spdx-license-ids": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", - "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==" + "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==", + "dev": true }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "dev": true, "requires": { "extend-shallow": "^3.0.0" } @@ -10595,6 +10005,7 @@ "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "dev": true, "requires": { "define-property": "^0.2.5", "object-copy": "^0.1.0" @@ -10604,6 +10015,7 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "dev": true, "requires": { "is-descriptor": "^0.1.0" } @@ -10670,6 +10082,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -10688,6 +10101,7 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, "requires": { "ansi-regex": "^2.0.0" } @@ -10704,7 +10118,8 @@ "strip-eof": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=" + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true }, "strip-indent": { "version": "1.0.1", @@ -10878,6 +10293,7 @@ "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "dev": true, "requires": { "kind-of": "^3.0.2" } @@ -10886,6 +10302,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "dev": true, "requires": { "define-property": "^2.0.2", "extend-shallow": "^3.0.2", @@ -10897,6 +10314,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "dev": true, "requires": { "is-number": "^3.0.0", "repeat-string": "^1.6.1" @@ -10906,6 +10324,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, "requires": { "kind-of": "^3.0.2" } @@ -10960,6 +10379,17 @@ "resolved": "https://registry.npmjs.org/ts-md5/-/ts-md5-1.2.4.tgz", "integrity": "sha512-oW+rNjc9CAhalPFzbPWsLqPLzdNcJ8iSm+OXO+Uv+99r3PzCJuM5sVc0bO1eS+4LD2xv+nfU7ylBdwoemUV9Yw==" }, + "tsickle": { + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/tsickle/-/tsickle-0.27.5.tgz", + "integrity": "sha512-NP+CjM1EXza/M8mOXBLH3vkFEJiu1zfEAlC5WdJxHPn8l96QPz5eooP6uAgYtw1CcKfuSyIiheNUdKxtDWCNeg==", + "requires": { + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "source-map": "^0.6.0", + "source-map-support": "^0.5.0" + } + }, "tslib": { "version": "1.9.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.2.tgz", @@ -11180,6 +10610,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", + "dev": true, "requires": { "arr-union": "^3.1.0", "get-value": "^2.0.6", @@ -11191,6 +10622,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "dev": true, "requires": { "is-extendable": "^0.1.0" } @@ -11199,6 +10631,7 @@ "version": "0.4.3", "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", + "dev": true, "requires": { "extend-shallow": "^2.0.1", "is-extendable": "^0.1.1", @@ -11234,6 +10667,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "dev": true, "requires": { "has-value": "^0.3.1", "isobject": "^3.0.0" @@ -11243,6 +10677,7 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "dev": true, "requires": { "get-value": "^2.0.3", "has-values": "^0.1.4", @@ -11253,6 +10688,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, "requires": { "isarray": "1.0.0" } @@ -11262,19 +10698,22 @@ "has-values": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "dev": true }, "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true } } }, "upath": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.1.0.tgz", - "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==" + "integrity": "sha512-bzpH/oBhoS/QI/YtbkqCg6VEiPYjSZtrHQM6/QnJS6OL9pKUFLqb3aFh4Scvwm45+7iAgiMkLhSbaZxUqmrprw==", + "dev": true }, "uri-js": { "version": "4.2.2", @@ -11296,7 +10735,8 @@ "urix": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "dev": true }, "url": { "version": "0.11.0", @@ -11320,6 +10760,7 @@ "version": "3.1.0", "resolved": "https://registry.npmjs.org/use/-/use-3.1.0.tgz", "integrity": "sha512-6UJEQM/L+mzC3ZJNM56Q4DFGLX/evKGRg15UJHGB9X5j5Z3AFbgZvjUh2yq/UJUY4U5dh7Fal++XbNg1uzpRAw==", + "dev": true, "requires": { "kind-of": "^6.0.2" }, @@ -11327,7 +10768,8 @@ "kind-of": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==" + "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", + "dev": true } } }, @@ -11376,6 +10818,7 @@ "version": "3.0.3", "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", + "dev": true, "requires": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" @@ -12659,6 +12102,7 @@ "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, "requires": { "isexe": "^2.0.0" } @@ -12703,6 +12147,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, "requires": { "string-width": "^1.0.1", "strip-ansi": "^3.0.1" @@ -12711,7 +12156,8 @@ "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true }, "ws": { "version": "3.3.2", @@ -12753,12 +12199,14 @@ "y18n": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true }, "yallist": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true }, "yargs": { "version": "7.1.0", diff --git a/package.json b/package.json index d7de0b7e3..2fa23a48a 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "@angular/animations": "^5.2.10", "@angular/common": "^5.2.10", "@angular/compiler": "^5.2.10", - "@angular/compiler-cli": "^7.2.9", + "@angular/compiler-cli": "^5.2.10", "@angular/core": "^5.2.10", "@angular/forms": "^5.2.10", "@angular/http": "^5.2.10", From 67dfaada274f43d5df73c45238272fc2358d53aa Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Wed, 13 Mar 2019 10:00:57 +0100 Subject: [PATCH 143/191] MOBILE-2915 core: Inject CSS safely --- src/components/style/style.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/style/style.ts b/src/components/style/style.ts index a4420f6b9..b0790ca96 100644 --- a/src/components/style/style.ts +++ b/src/components/style/style.ts @@ -38,10 +38,12 @@ export class CoreStyleComponent implements OnChanges { * Component being changed. */ ngOnChanges(changes: { [name: string]: SimpleChange }): void { - const css = this.prefixCSS(this.css, this.prefix); - if (this.element && this.element.nativeElement) { - this.element.nativeElement.innerHTML = ''; + const style = document.createElement('style'); + style.innerText = this.prefixCSS(this.css, this.prefix); + + this.element.nativeElement.innerHTML = ''; + this.element.nativeElement.appendChild(style); } } From b5a75a4108191a0bf7aa640b959d990f6637a3dd Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Wed, 13 Mar 2019 16:47:27 +0100 Subject: [PATCH 144/191] MOBILE-2915 site: Don't clone errors, they might contain cyclic references --- src/classes/site.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/classes/site.ts b/src/classes/site.ts index 04d133a2c..acdde5a92 100644 --- a/src/classes/site.ts +++ b/src/classes/site.ts @@ -579,8 +579,6 @@ export class CoreSite { return this.ongoingRequests[cacheId].then((response) => { // Clone the data, this may prevent errors if in the callback the object is modified. return this.utils.clone(response); - }).catch((error) => { - return Promise.reject(this.utils.clone(error)); }); } @@ -701,8 +699,6 @@ export class CoreSite { return promise.then((response) => { // We pass back a clone of the original object, this may prevent errors if in the callback the object is modified. return this.utils.clone(response); - }).catch((error) => { - return Promise.reject(this.utils.clone(error)); }); } From 8b2ed818fa0851d8df6476176f6c25f646e51f9a Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 14 Mar 2019 09:25:29 +0100 Subject: [PATCH 145/191] MOBILE-2919 forum: Handle forum discussion notification clicks --- .../components/post/addon-mod-forum-post.html | 2 +- .../mod/forum/pages/discussion/discussion.ts | 11 +++++- .../notifications/notifications.module.ts | 36 ++++++++++++++++++- src/providers/utils/url.ts | 13 +++++-- 4 files changed, 57 insertions(+), 5 deletions(-) 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 19a55682d..6592dabb2 100644 --- a/src/addon/mod/forum/components/post/addon-mod-forum-post.html +++ b/src/addon/mod/forum/components/post/addon-mod-forum-post.html @@ -1,4 +1,4 @@ - +

diff --git a/src/addon/mod/forum/pages/discussion/discussion.ts b/src/addon/mod/forum/pages/discussion/discussion.ts index d25e6e4d1..7b5f6f6f1 100644 --- a/src/addon/mod/forum/pages/discussion/discussion.ts +++ b/src/addon/mod/forum/pages/discussion/discussion.ts @@ -76,6 +76,7 @@ export class AddonModForumDiscussionPage implements OnDestroy { cmId: number; protected forumId: number; + protected postId: number; protected onlineObserver: any; protected syncObserver: any; protected syncManualObserver: any; @@ -107,6 +108,7 @@ export class AddonModForumDiscussionPage implements OnDestroy { this.discussionId = navParams.get('discussionId'); this.trackPosts = navParams.get('trackPosts'); this.locked = navParams.get('locked'); + this.postId = navParams.get('postId'); this.isOnline = this.appProvider.isOnline(); this.onlineObserver = network.onchange().subscribe((online) => { @@ -124,7 +126,14 @@ export class AddonModForumDiscussionPage implements OnDestroy { * View loaded. */ ionViewDidLoad(): void { - this.fetchPosts(true, false, true); + this.fetchPosts(true, false, true).then(() => { + if (this.postId) { + // Scroll to the post. + setTimeout(() => { + this.domUtils.scrollToElementBySelector(this.content, '#addon-mod_forum-post-' + this.postId); + }); + } + }); } /** diff --git a/src/addon/notifications/notifications.module.ts b/src/addon/notifications/notifications.module.ts index 384901f44..3dbd875f4 100644 --- a/src/addon/notifications/notifications.module.ts +++ b/src/addon/notifications/notifications.module.ts @@ -24,8 +24,10 @@ import { CoreSettingsDelegate } from '@core/settings/providers/delegate'; import { CoreCronDelegate } from '@providers/cron'; import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; import { CoreSitesProvider } from '@providers/sites'; +import { CoreUrlUtilsProvider } from '@providers/utils/url'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { AddonPushNotificationsDelegate } from '@addon/pushnotifications/providers/delegate'; +import { AddonModForumProvider } from '@addon/mod/forum/providers/forum'; // List of providers (without handlers). export const ADDON_NOTIFICATIONS_PROVIDERS: any[] = [ @@ -50,12 +52,44 @@ export class AddonNotificationsModule { cronDelegate: CoreCronDelegate, cronHandler: AddonNotificationsCronHandler, zone: NgZone, appProvider: CoreAppProvider, utils: CoreUtilsProvider, sitesProvider: CoreSitesProvider, notificationsProvider: AddonNotificationsProvider, localNotifications: CoreLocalNotificationsProvider, - linkHelper: CoreContentLinksHelperProvider, pushNotificationsDelegate: AddonPushNotificationsDelegate) { + linkHelper: CoreContentLinksHelperProvider, pushNotificationsDelegate: AddonPushNotificationsDelegate, + urlUtils: CoreUrlUtilsProvider, forumProvider: AddonModForumProvider) { + mainMenuDelegate.registerHandler(mainMenuHandler); settingsDelegate.registerHandler(settingsHandler); cronDelegate.register(cronHandler); const notificationClicked = (notification: any): void => { + + // Temporary fix to make forum notifications work. This will be improved in next release. + if (notification.moodlecomponent == 'mod_forum' && notification.name == 'posts') { + sitesProvider.isFeatureDisabled('CoreCourseModuleDelegate_AddonModForum', notification.site).then((disabled) => { + if (disabled) { + // Forum is disabled, stop. + return; + } + + const contextUrlParams = urlUtils.extractUrlParams(notification.contexturl), + pageParams: any = { + courseId: Number(notification.courseid), + discussionId: Number(contextUrlParams.d), + }; + + if (contextUrlParams.urlHash) { + pageParams.postId = Number(contextUrlParams.urlHash.replace('p', '')); + } + + forumProvider.invalidateDiscussionPosts(pageParams.discussionId).catch(() => { + // Ignore errors. + }).then(() => { + linkHelper.goInSite(undefined, 'AddonModForumDiscussionPage', pageParams, notification.site); + }); + }); + } else { + goToNotifications(notification); + } + }; + const goToNotifications = (notification: any): void => { sitesProvider.isFeatureDisabled('CoreMainMenuDelegate_AddonNotifications', notification.site).then((disabled) => { if (disabled) { // Notifications are disabled, stop. diff --git a/src/providers/utils/url.ts b/src/providers/utils/url.ts index 4f463276e..35a1655ba 100644 --- a/src/providers/utils/url.ts +++ b/src/providers/utils/url.ts @@ -52,14 +52,23 @@ export class CoreUrlUtilsProvider { */ extractUrlParams(url: string): any { const regex = /[?&]+([^=&]+)=?([^&]*)?/gi, - params = {}; + params: any = {}, + urlAndHash = url.split('#'); - url.replace(regex, (match: string, key: string, value: string): string => { + urlAndHash[0].replace(regex, (match: string, key: string, value: string): string => { params[key] = typeof value != 'undefined' ? value : ''; return match; }); + if (urlAndHash.length > 1) { + // Remove the URL from the array. + urlAndHash.shift(); + + // Add the hash as a param with a special name. Use a join in case there is more than one #. + params.urlHash = urlAndHash.join('#'); + } + return params; } From dc751554b9223c3005f6b75ae25462299515febb Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 13 Mar 2019 12:56:54 +0100 Subject: [PATCH 146/191] MOBILE-2915 config: Lock platform and plugin versions --- config.xml | 46 +++++++------- package.json | 176 +++++++++++++++++++++++++-------------------------- 2 files changed, 111 insertions(+), 111 deletions(-) diff --git a/config.xml b/config.xml index c3f6a820d..a8ee57ec4 100644 --- a/config.xml +++ b/config.xml @@ -112,33 +112,33 @@ - - + + - - - - + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + diff --git a/package.json b/package.json index 2fa23a48a..221a31a47 100644 --- a/package.json +++ b/package.json @@ -39,100 +39,100 @@ "windows.store": "electron-windows-store --input-directory .\\desktop\\dist\\win-unpacked --output-directory .\\desktop\\store --flatten true -a .\\resources\\desktop -m .\\desktop\\assets\\windows\\AppXManifest.xml --package-version 0.0.0.0 --package-name MoodleDesktop" }, "dependencies": { - "@angular/animations": "^5.2.10", - "@angular/common": "^5.2.10", - "@angular/compiler": "^5.2.10", - "@angular/compiler-cli": "^5.2.10", - "@angular/core": "^5.2.10", - "@angular/forms": "^5.2.10", - "@angular/http": "^5.2.10", - "@angular/platform-browser": "^5.2.10", - "@angular/platform-browser-dynamic": "^5.2.10", - "@ionic-native/badge": "^4.17.0", - "@ionic-native/camera": "^4.17.0", - "@ionic-native/clipboard": "^4.17.0", - "@ionic-native/core": "^4.11.0", - "@ionic-native/device": "^4.17.0", - "@ionic-native/file": "^4.17.0", - "@ionic-native/file-opener": "^4.17.0", - "@ionic-native/file-transfer": "^4.17.0", - "@ionic-native/globalization": "^4.17.0", - "@ionic-native/in-app-browser": "^4.17.0", - "@ionic-native/keyboard": "^4.17.0", - "@ionic-native/local-notifications": "^4.17.0", - "@ionic-native/media-capture": "^4.17.0", - "@ionic-native/network": "^4.17.0", - "@ionic-native/push": "^4.17.0", - "@ionic-native/screen-orientation": "^4.17.0", - "@ionic-native/splash-screen": "^4.17.0", - "@ionic-native/sqlite": "^4.17.0", - "@ionic-native/status-bar": "^4.17.0", - "@ionic-native/web-intent": "^4.17.0", - "@ionic-native/zip": "^4.17.0", - "@ngx-translate/core": "^8.0.0", - "@ngx-translate/http-loader": "^2.0.1", - "@types/cordova": "^0.0.34", - "@types/cordova-plugin-file-transfer": "^0.0.3", - "@types/cordova-plugin-globalization": "^0.0.3", - "@types/cordova-plugin-network-information": "^0.0.3", - "@types/node": "^8.10.19", - "@types/promise.prototype.finally": "^2.0.2", - "chart.js": "^2.7.2", - "com-darryncampbell-cordova-plugin-intent": "^1.1.1", + "@angular/animations": "5.2.10", + "@angular/common": "5.2.10", + "@angular/compiler": "5.2.10", + "@angular/compiler-cli": "5.2.10", + "@angular/core": "5.2.10", + "@angular/forms": "5.2.10", + "@angular/http": "5.2.10", + "@angular/platform-browser": "5.2.10", + "@angular/platform-browser-dynamic": "5.2.10", + "@ionic-native/badge": "4.17.0", + "@ionic-native/camera": "4.17.0", + "@ionic-native/clipboard": "4.17.0", + "@ionic-native/core": "4.11.0", + "@ionic-native/device": "4.17.0", + "@ionic-native/file": "4.17.0", + "@ionic-native/file-opener": "4.17.0", + "@ionic-native/file-transfer": "4.17.0", + "@ionic-native/globalization": "4.17.0", + "@ionic-native/in-app-browser": "4.17.0", + "@ionic-native/keyboard": "4.17.0", + "@ionic-native/local-notifications": "4.17.0", + "@ionic-native/media-capture": "4.17.0", + "@ionic-native/network": "4.17.0", + "@ionic-native/push": "4.17.0", + "@ionic-native/screen-orientation": "4.17.0", + "@ionic-native/splash-screen": "4.17.0", + "@ionic-native/sqlite": "4.17.0", + "@ionic-native/status-bar": "4.17.0", + "@ionic-native/web-intent": "4.17.0", + "@ionic-native/zip": "4.17.0", + "@ngx-translate/core": "8.0.0", + "@ngx-translate/http-loader": "2.0.1", + "@types/cordova": "0.0.34", + "@types/cordova-plugin-file-transfer": "0.0.3", + "@types/cordova-plugin-globalization": "0.0.3", + "@types/cordova-plugin-network-information": "0.0.3", + "@types/node": "8.10.19", + "@types/promise.prototype.finally": "2.0.2", + "chart.js": "2.7.2", + "com-darryncampbell-cordova-plugin-intent": "1.1.1", "cordova-android": "7.1.2", - "cordova-android-support-gradle-release": "^2.0.1", - "cordova-clipboard": "^1.2.1", + "cordova-android-support-gradle-release": "2.0.1", + "cordova-clipboard": "1.2.1", "cordova-ios": "4.5.5", - "cordova-plugin-badge": "^0.8.8", - "cordova-plugin-camera": "^4.0.3", - "cordova-plugin-customurlscheme": "^4.3.0", - "cordova-plugin-device": "^2.0.2", - "cordova-plugin-file": "^6.0.1", - "cordova-plugin-file-opener2": "^2.0.19", - "cordova-plugin-file-transfer": "^1.7.1", - "cordova-plugin-globalization": "^1.11.0", - "cordova-plugin-inappbrowser": "^3.0.0", - "cordova-plugin-ionic-keyboard": "^2.1.3", - "cordova-plugin-local-notification": "^0.9.0-beta.3", - "cordova-plugin-media-capture": "^3.0.2", - "cordova-plugin-network-information": "^2.0.1", - "cordova-plugin-screen-orientation": "^3.0.1", - "cordova-plugin-splashscreen": "^5.0.2", - "cordova-plugin-statusbar": "^2.4.2", - "cordova-plugin-whitelist": "^1.3.3", - "cordova-plugin-zip": "^3.1.0", - "cordova-sqlite-storage": "^2.6.0", - "cordova-support-google-services": "^1.2.1", - "es6-promise-plugin": "^4.2.2", - "font-awesome": "^4.7.0", + "cordova-plugin-badge": "0.8.8", + "cordova-plugin-camera": "4.0.3", + "cordova-plugin-customurlscheme": "4.3.0", + "cordova-plugin-device": "2.0.2", + "cordova-plugin-file": "6.0.1", + "cordova-plugin-file-opener2": "2.0.19", + "cordova-plugin-file-transfer": "1.7.1", + "cordova-plugin-globalization": "1.11.0", + "cordova-plugin-inappbrowser": "3.0.0", + "cordova-plugin-ionic-keyboard": "2.1.3", + "cordova-plugin-local-notification": "0.9.0-beta.3", + "cordova-plugin-media-capture": "3.0.2", + "cordova-plugin-network-information": "2.0.1", + "cordova-plugin-screen-orientation": "3.0.1", + "cordova-plugin-splashscreen": "5.0.2", + "cordova-plugin-statusbar": "2.4.2", + "cordova-plugin-whitelist": "1.3.3", + "cordova-plugin-zip": "3.1.0", + "cordova-sqlite-storage": "2.6.0", + "cordova-support-google-services": "1.2.1", + "es6-promise-plugin": "4.2.2", + "font-awesome": "4.7.0", "ionic-angular": "3.9.3", - "ionicons": "^3.0.0", - "jszip": "^3.1.5", - "moment": "^2.22.2", - "nl.kingsquare.cordova.background-audio": "^1.0.1", - "phonegap-plugin-multidex": "^1.0.0", + "ionicons": "3.0.0", + "jszip": "3.1.5", + "moment": "2.22.2", + "nl.kingsquare.cordova.background-audio": "1.0.1", + "phonegap-plugin-multidex": "1.0.0", "phonegap-plugin-push": "git+https://github.com/moodlemobile/phonegap-plugin-push.git#moodle-v2", - "promise.prototype.finally": "^3.1.0", - "rxjs": "^5.5.11", - "sw-toolbox": "^3.6.0", - "ts-md5": "^1.2.4", - "web-animations-js": "^2.3.1", - "zone.js": "^0.8.26" + "promise.prototype.finally": "3.1.0", + "rxjs": "5.5.11", + "sw-toolbox": "3.6.0", + "ts-md5": "1.2.4", + "web-animations-js": "2.3.1", + "zone.js": "0.8.26" }, "devDependencies": { - "@ionic/app-scripts": "^3.2.2", - "electron-builder-lib": "^20.23.1", - "electron-rebuild": "^1.8.1", - "gulp": "^4.0.0", - "gulp-clip-empty-files": "^0.1.2", - "gulp-flatten": "^0.4.0", - "gulp-rename": "^1.3.0", - "gulp-slash": "^1.1.3", - "gulp-util": "^3.0.8", - "node-loader": "^0.6.0", - "through": "^2.3.8", - "typescript": "^2.6.2", - "webpack-merge": "^4.1.2" + "@ionic/app-scripts": "3.2.2", + "electron-builder-lib": "20.23.1", + "electron-rebuild": "1.8.1", + "gulp": "4.0.0", + "gulp-clip-empty-files": "0.1.2", + "gulp-flatten": "0.4.0", + "gulp-rename": "1.3.0", + "gulp-slash": "1.1.3", + "gulp-util": "3.0.8", + "node-loader": "0.6.0", + "through": "2.3.8", + "typescript": "2.6.2", + "webpack-merge": "4.1.2" }, "browser": { "electron": false From 7cca6d77c3faee9a47bfe2d1269ce579e034c403 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 14 Mar 2019 11:36:24 +0100 Subject: [PATCH 147/191] MOBILE-2915 lang: Build lang files --- src/addon/messages/lang/en.json | 2 +- src/addon/mod/choice/lang/en.json | 4 ++-- src/assets/lang/en.json | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/addon/messages/lang/en.json b/src/addon/messages/lang/en.json index 75419bf5b..1b542a0c7 100644 --- a/src/addon/messages/lang/en.json +++ b/src/addon/messages/lang/en.json @@ -74,6 +74,6 @@ "warningmessagenotsent": "Couldn't send message(s) to user {{user}}. {{error}}", "wouldliketocontactyou": "Would like to contact you", "you": "You:", - "youhaveblockeduser": "You have blocked this user in the past", + "youhaveblockeduser": "You have blocked this user.", "yourcontactrequestpending": "Your contact request is pending with {{$a}}" } \ No newline at end of file diff --git a/src/addon/mod/choice/lang/en.json b/src/addon/mod/choice/lang/en.json index a0714b34a..e5c48508f 100644 --- a/src/addon/mod/choice/lang/en.json +++ b/src/addon/mod/choice/lang/en.json @@ -2,11 +2,11 @@ "cannotsubmit": "Sorry, there was a problem submitting your choice. Please try again.", "choiceoptions": "Choice options", "errorgetchoice": "Error getting choice data.", - "expired": "Sorry, this activity closed on {{$a}} and is no longer available", + "expired": "This activity closed on {{$a}}.", "full": "(Full)", "modulenameplural": "Choices", "noresultsviewable": "The results are not currently viewable.", - "notopenyet": "Sorry, this activity is not available until {{$a}}", + "notopenyet": "This activity is not available until {{$a}}.", "numberofuser": "Number of responses", "numberofuserinpercentage": "Percentage of responses", "previewonly": "This is just a preview of the available options for this activity. You will not be able to submit your choice until {{$a}}.", diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index 59cf7b125..68ddb31fb 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -234,7 +234,7 @@ "addon.messages.warningmessagenotsent": "Couldn't send message(s) to user {{user}}. {{error}}", "addon.messages.wouldliketocontactyou": "Would like to contact you", "addon.messages.you": "You:", - "addon.messages.youhaveblockeduser": "You have blocked this user in the past", + "addon.messages.youhaveblockeduser": "You have blocked this user.", "addon.messages.yourcontactrequestpending": "Your contact request is pending with {{$a}}", "addon.mod_assign.acceptsubmissionstatement": "Please accept the submission statement.", "addon.mod_assign.addattempt": "Allow another attempt", @@ -372,11 +372,11 @@ "addon.mod_choice.cannotsubmit": "Sorry, there was a problem submitting your choice. Please try again.", "addon.mod_choice.choiceoptions": "Choice options", "addon.mod_choice.errorgetchoice": "Error getting choice data.", - "addon.mod_choice.expired": "Sorry, this activity closed on {{$a}} and is no longer available", + "addon.mod_choice.expired": "This activity closed on {{$a}}.", "addon.mod_choice.full": "(Full)", "addon.mod_choice.modulenameplural": "Choices", "addon.mod_choice.noresultsviewable": "The results are not currently viewable.", - "addon.mod_choice.notopenyet": "Sorry, this activity is not available until {{$a}}", + "addon.mod_choice.notopenyet": "This activity is not available until {{$a}}.", "addon.mod_choice.numberofuser": "Number of responses", "addon.mod_choice.numberofuserinpercentage": "Percentage of responses", "addon.mod_choice.previewonly": "This is just a preview of the available options for this activity. You will not be able to submit your choice until {{$a}}.", From a0bac9301e402b096519326ff4abfd5b376b3e12 Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Thu, 14 Mar 2019 12:40:26 +0100 Subject: [PATCH 148/191] MOBILE-2915 rating: Change string "No rating" to "None" --- scripts/langindex.json | 1 - src/assets/lang/en.json | 1 - src/core/rating/components/rate/rate.ts | 2 +- src/core/rating/lang/en.json | 1 - 4 files changed, 1 insertion(+), 4 deletions(-) diff --git a/scripts/langindex.json b/scripts/langindex.json index a9d2e941e..da66b6940 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -1599,7 +1599,6 @@ "core.rating.aggregatemax": "moodle", "core.rating.aggregatemin": "moodle", "core.rating.aggregatesum": "moodle", - "core.rating.norating": "local_moodlemobileapp", "core.rating.noratings": "moodle", "core.rating.rating": "moodle", "core.rating.ratings": "moodle", diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index 59cf7b125..b90ed8140 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -1599,7 +1599,6 @@ "core.rating.aggregatemax": "Maximum rating", "core.rating.aggregatemin": "Minimum rating", "core.rating.aggregatesum": "Sum of ratings", - "core.rating.norating": "No rating", "core.rating.noratings": "No ratings submitted", "core.rating.rating": "Rating", "core.rating.ratings": "Ratings", diff --git a/src/core/rating/components/rate/rate.ts b/src/core/rating/components/rate/rate.ts index 1a214bfc0..581de6e66 100644 --- a/src/core/rating/components/rate/rate.ts +++ b/src/core/rating/components/rate/rate.ts @@ -72,7 +72,7 @@ export class CoreRatingRateComponent implements OnChanges { // Add "No rating" item to the scale. if (!this.scale.items[0] || this.scale.items[0].value != CoreRatingProvider.UNSET_RATING) { this.scale.items.unshift({ - name: this.translate.instant('core.rating.norating'), + name: this.translate.instant('core.none'), value: CoreRatingProvider.UNSET_RATING }); } diff --git a/src/core/rating/lang/en.json b/src/core/rating/lang/en.json index d8a28f089..5b5799c29 100644 --- a/src/core/rating/lang/en.json +++ b/src/core/rating/lang/en.json @@ -4,7 +4,6 @@ "aggregatemax": "Maximum rating", "aggregatemin": "Minimum rating", "aggregatesum": "Sum of ratings", - "norating": "No rating", "noratings": "No ratings submitted", "rating": "Rating", "ratings": "Ratings" From f94b1a34e746ac1a46bd6ac8843f3136bdb9ebd4 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 15 Mar 2019 08:43:45 +0100 Subject: [PATCH 149/191] MOBILE-2919 message: Fix error when auto-open discussion --- src/addon/messages/components/discussions/discussions.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/addon/messages/components/discussions/discussions.ts b/src/addon/messages/components/discussions/discussions.ts index bf3eb9f21..afb9b5d88 100644 --- a/src/addon/messages/components/discussions/discussions.ts +++ b/src/addon/messages/components/discussions/discussions.ts @@ -62,7 +62,7 @@ export class AddonMessagesDiscussionsComponent implements OnDestroy { // Update discussions when new message is received. this.newMessagesObserver = eventsProvider.on(AddonMessagesProvider.NEW_MESSAGE_EVENT, (data) => { - if (data.userId) { + if (data.userId && this.discussions) { const discussion = this.discussions.find((disc) => { return disc.message.user == data.userId; }); @@ -82,7 +82,7 @@ export class AddonMessagesDiscussionsComponent implements OnDestroy { // Update discussions when a message is read. this.readChangedObserver = eventsProvider.on(AddonMessagesProvider.READ_CHANGED_EVENT, (data) => { - if (data.userId) { + if (data.userId && this.discussions) { const discussion = this.discussions.find((disc) => { return disc.message.user == data.userId; }); From a7bd0d2f0c022705e712b03c8fe0a993f0ae4ebd Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 15 Mar 2019 10:00:05 +0100 Subject: [PATCH 150/191] MOBILE-2819 tabs: Fix wrong aria-hidden in core-tabs --- src/components/tabs/core-tabs.html | 4 ++-- src/components/tabs/tab.ts | 24 +++++++++++++++++------- src/components/tabs/tabs.ts | 19 +++++++++++++++++++ 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/src/components/tabs/core-tabs.html b/src/components/tabs/core-tabs.html index 977b8899e..ec8a460a4 100644 --- a/src/components/tabs/core-tabs.html +++ b/src/components/tabs/core-tabs.html @@ -1,7 +1,7 @@
- diff --git a/src/components/tabs/tab.ts b/src/components/tabs/tab.ts index e65068f8f..e47563acc 100644 --- a/src/components/tabs/tab.ts +++ b/src/components/tabs/tab.ts @@ -55,9 +55,7 @@ export class CoreTabComponent implements OnInit, OnDestroy { if (this.initialized && hasChanged) { this.tabs.tabVisibilityChanged(); - - this.tabElement = document.getElementById(this.id + '-tab'); - this.tabElement && this.tabElement.setAttribute('aria-hidden', !this._show); + this.updateAriaHidden(); } } } @@ -96,8 +94,7 @@ export class CoreTabComponent implements OnInit, OnDestroy { this.initialized = true; setTimeout(() => { - this.tabElement = document.getElementById(this.id + '-tab'); - this.tabElement && this.tabElement.setAttribute('aria-hidden', !this._show); + this.updateAriaHidden(); }, 1000); } @@ -116,8 +113,8 @@ export class CoreTabComponent implements OnInit, OnDestroy { this.tabElement = this.tabElement || document.getElementById(this.id + '-tab'); + this.updateAriaHidden(); this.tabElement && this.tabElement.setAttribute('aria-selected', true); - this.tabElement && this.tabElement.setAttribute('aria-hidden', !this._show); this.loaded = true; this.ionSelect.emit(this); @@ -137,8 +134,8 @@ export class CoreTabComponent implements OnInit, OnDestroy { * Unselect tab. */ unselectTab(): void { + this.updateAriaHidden(); this.tabElement && this.tabElement.setAttribute('aria-selected', false); - this.tabElement && this.tabElement.setAttribute('aria-hidden', true); this.element.classList.remove('selected'); this.showHideNavBarButtons(false); @@ -176,4 +173,17 @@ export class CoreTabComponent implements OnInit, OnDestroy { instances[i].forceHide(!show); } } + + /** + * Update aria hidden attribute. + */ + updateAriaHidden(): void { + if (!this.tabElement) { + this.tabElement = document.getElementById(this.id + '-tab'); + } + + if (this.tabElement) { + this.tabElement && this.tabElement.setAttribute('aria-hidden', !this._show); + } + } } diff --git a/src/components/tabs/tabs.ts b/src/components/tabs/tabs.ts index 042f727fd..d15eec81a 100644 --- a/src/components/tabs/tabs.ts +++ b/src/components/tabs/tabs.ts @@ -327,6 +327,8 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges, OnDe // Current tab has changed, don't slide to initial anymore. this.shouldSlideToInitial = false; } + + this.updateAriaHidden(); // Sliding resets the aria-hidden, update it. } /** @@ -354,11 +356,18 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges, OnDe if (this.shouldSlideToInitial) { this.slides.slideTo(this.selected, 0); this.shouldSlideToInitial = false; + this.updateAriaHidden(); // Slide's slideTo() sets aria-hidden to true, update it. } }, 400); + + return; } else if (this.selected) { this.hasSliddenToInitial = true; } + + setTimeout(() => { + this.updateAriaHidden(); // Slide's update() sets aria-hidden to true, update it. + }, 400); }); } @@ -458,6 +467,7 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges, OnDe if (this.selected) { this.slides.slideTo(index); + this.updateAriaHidden(); // Slide's slideTo() sets aria-hidden to true, update it. } this.selectHistory.push(index); @@ -491,6 +501,15 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges, OnDe this.calculateSlides(); } + /** + * Update aria-hidden of all tabs. + */ + protected updateAriaHidden(): void { + this.tabs.forEach((tab, index) => { + tab.updateAriaHidden(); + }); + } + /** * Component destroyed. */ From 88a0a2851ff46084cc42440bb6e788d722bcbdd9 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 15 Mar 2019 11:36:32 +0100 Subject: [PATCH 151/191] MOBILE-2819 a11y: Fix image description read twice --- .../picture/component/addon-mod-data-field-picture.html | 4 ++-- .../module-completion/core-course-module-completion.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/addon/mod/data/fields/picture/component/addon-mod-data-field-picture.html b/src/addon/mod/data/fields/picture/component/addon-mod-data-field-picture.html index d0bbb6885..c5a4a560e 100644 --- a/src/addon/mod/data/fields/picture/component/addon-mod-data-field-picture.html +++ b/src/addon/mod/data/fields/picture/component/addon-mod-data-field-picture.html @@ -11,6 +11,6 @@ - + - + diff --git a/src/core/course/components/module-completion/core-course-module-completion.html b/src/core/course/components/module-completion/core-course-module-completion.html index 7de6d78d5..9238f78ba 100644 --- a/src/core/course/components/module-completion/core-course-module-completion.html +++ b/src/core/course/components/module-completion/core-course-module-completion.html @@ -1,3 +1,3 @@ - + \ No newline at end of file From 926adcf7cbe99579e6bcee46d5200c0f5f2e5dac Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 15 Mar 2019 13:20:05 +0100 Subject: [PATCH 152/191] MOBILE-2919 message: Fix auto-open discussion by userid --- .../pages/group-conversations/group-conversations.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/addon/messages/pages/group-conversations/group-conversations.ts b/src/addon/messages/pages/group-conversations/group-conversations.ts index 6dd5f0d28..b48e9401d 100644 --- a/src/addon/messages/pages/group-conversations/group-conversations.ts +++ b/src/addon/messages/pages/group-conversations/group-conversations.ts @@ -222,7 +222,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { } this.fetchData().then(() => { - if (!this.conversationId && this.splitviewCtrl.isOn()) { + if (!this.conversationId && !this.discussionUserId && this.splitviewCtrl.isOn()) { // Load the first conversation. let conversation; const expandedOption = this.getExpandedOption(); @@ -256,7 +256,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { return Promise.all(promises).then(() => { if (typeof this.favourites.expanded == 'undefined') { // The expanded status hasn't been initialized. Do it now. - if (this.conversationId) { + if (this.conversationId || this.discussionUserId) { // A certain conversation should be opened. // We don't know which option it belongs to, so we need to fetch the data for all of them. const promises = []; @@ -267,7 +267,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { return Promise.all(promises).then(() => { // All conversations have been loaded, find the one we need to load and expand its option. - const conversation = this.findConversation(this.conversationId); + const conversation = this.findConversation(this.conversationId, this.discussionUserId); if (conversation) { const option = this.getConversationOption(conversation); From 13e80c382ce12dd7ab3a6945ceb8f9dfe00dbb1a Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Fri, 15 Mar 2019 15:02:03 +0100 Subject: [PATCH 153/191] MOBILE-2915 tabs: Fix database search tabs --- src/components/tabs/tabs.scss | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/tabs/tabs.scss b/src/components/tabs/tabs.scss index d20bee9de..8d6c3a5e1 100644 --- a/src/components/tabs/tabs.scss +++ b/src/components/tabs/tabs.scss @@ -10,7 +10,7 @@ ion-app.app-root .core-tabs-bar { width: 100%; } - ion-slide.tab-slide { + .tab-slide { @extend .tab-button; background: $core-top-tabs-background; @@ -68,7 +68,7 @@ ion-app.app-root .core-tabs-bar { } } -ion-app.app-root.md .core-tabs-bar ion-slide.tab-slide { +ion-app.app-root.md .core-tabs-bar .tab-slide { // @extend .tabs-md .tab-button; min-height: $tabs-md-tab-min-height; @@ -76,7 +76,7 @@ ion-app.app-root.md .core-tabs-bar ion-slide.tab-slide { color: $tabs-md-tab-text-color; } -ion-app.app-root.ios .core-tabs-bar ion-slide.tab-slide { +ion-app.app-root.ios .core-tabs-bar .tab-slide { // @extend .tabs-ios .tab-button; max-width: $tabs-ios-tab-max-width; min-height: $tabs-ios-tab-min-height; @@ -86,7 +86,7 @@ ion-app.app-root.ios .core-tabs-bar ion-slide.tab-slide { color: $tabs-ios-tab-text-color; } -ion-app.app-root.wp .core-tabs-bar ion-slide.tab-slide { +ion-app.app-root.wp .core-tabs-bar .tab-slide { //@extend .tabs-wp .tab-button; @include border-radius(0); From 2659835d2b427af9297ba2590a64405d5856df00 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 15 Mar 2019 15:09:30 +0100 Subject: [PATCH 154/191] MOBILE-2915 config: Fix package-lock.json --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 282a99d8a..d970a674d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -55,9 +55,9 @@ } }, "@angular/compiler-cli": { - "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==", + "version": "5.2.10", + "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-5.2.10.tgz", + "integrity": "sha512-RhI26rVALRn3LrU0CAIEj56m60vLyCd8e2Ah79yRP6vlXL8k6SylXytUljTeXIBtiVu2Bi1qKGf2s1X674GzCw==", "requires": { "chokidar": "^1.4.2", "minimist": "^1.2.0", @@ -161,9 +161,9 @@ "integrity": "sha512-2BHO1bV4mehWZNfdsWQ/uojxYFNvk4I6u0KYnNb61RiJRY83joCEw3oFkOMRGLZthPf6TN1cueZUIAGMHXA3nA==" }, "@ionic-native/local-notifications": { - "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==" + "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==" }, "@ionic-native/media-capture": { "version": "4.17.0", From 2a7ca6651c0ef046058c6adf489aa6878fc1d1f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 20 Mar 2019 10:56:50 +0100 Subject: [PATCH 155/191] MOBILE-2915 book: Do not show index if not loaded --- src/addon/mod/book/components/index/addon-mod-book-index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 5d872fd78..b5ce9efc2 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 @@ -1,6 +1,6 @@ - From 92d6d09b8f3e3acc93cd7d226d633ac2a8e7a441 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 20 Mar 2019 14:31:33 +0100 Subject: [PATCH 156/191] MOBILE-2915 completion: Display warning for teachers viewing own --- scripts/langindex.json | 1 + .../report/addon-course-completion-report.html | 11 ++++++++--- .../coursecompletion/components/report/report.ts | 11 +++++++++-- src/addon/coursecompletion/lang/en.json | 1 + src/assets/lang/en.json | 1 + 5 files changed, 20 insertions(+), 5 deletions(-) diff --git a/scripts/langindex.json b/scripts/langindex.json index da66b6940..2bbbdb083 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -145,6 +145,7 @@ "addon.coursecompletion.criteriarequiredany": "completion", "addon.coursecompletion.inprogress": "completion", "addon.coursecompletion.manualselfcompletion": "completion", + "addon.coursecompletion.nottracked": "completion", "addon.coursecompletion.notyetstarted": "completion", "addon.coursecompletion.pending": "completion", "addon.coursecompletion.required": "moodle", diff --git a/src/addon/coursecompletion/components/report/addon-course-completion-report.html b/src/addon/coursecompletion/components/report/addon-course-completion-report.html index b4a6a25eb..2b4b92070 100644 --- a/src/addon/coursecompletion/components/report/addon-course-completion-report.html +++ b/src/addon/coursecompletion/components/report/addon-course-completion-report.html @@ -3,7 +3,7 @@ - +

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

{{ completion.statusText | translate }}

@@ -14,7 +14,7 @@

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

- + {{ 'addon.coursecompletion.requiredcriteria' | translate }}

@@ -41,11 +41,16 @@
- + {{ 'addon.coursecompletion.manualselfcompletion' | translate }} + +
+ + {{ 'addon.coursecompletion.nottracked' | translate }} +
diff --git a/src/addon/coursecompletion/components/report/report.ts b/src/addon/coursecompletion/components/report/report.ts index 69583770f..50d12cf90 100644 --- a/src/addon/coursecompletion/components/report/report.ts +++ b/src/addon/coursecompletion/components/report/report.ts @@ -31,6 +31,7 @@ export class AddonCourseCompletionReportComponent implements OnInit { completionLoaded = false; completion: any; showSelfComplete: boolean; + tracked = true; // Whether completion is tracked. constructor( private sitesProvider: CoreSitesProvider, @@ -62,8 +63,14 @@ export class AddonCourseCompletionReportComponent implements OnInit { this.completion = completion; this.showSelfComplete = this.courseCompletionProvider.canMarkSelfCompleted(this.userId, completion); - }).catch((message) => { - this.domUtils.showErrorModalDefault(message, 'addon.coursecompletion.couldnotloadreport', true); + this.tracked = true; + }).catch((error) => { + if (error && error.errorcode == 'notenroled') { + // Not enrolled error, probably a teacher. + this.tracked = false; + } else { + this.domUtils.showErrorModalDefault(error, 'addon.coursecompletion.couldnotloadreport', true); + } }); } diff --git a/src/addon/coursecompletion/lang/en.json b/src/addon/coursecompletion/lang/en.json index 7607702c6..81ef0272e 100644 --- a/src/addon/coursecompletion/lang/en.json +++ b/src/addon/coursecompletion/lang/en.json @@ -12,6 +12,7 @@ "criteriarequiredany": "Any criteria below are required", "inprogress": "In progress", "manualselfcompletion": "Manual self completion", + "nottracked": "You are currently not being tracked by completion in this course", "notyetstarted": "Not yet started", "pending": "Pending", "required": "Required", diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index b6072b623..9f14d682a 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -145,6 +145,7 @@ "addon.coursecompletion.criteriarequiredany": "Any criteria below are required", "addon.coursecompletion.inprogress": "In progress", "addon.coursecompletion.manualselfcompletion": "Manual self completion", + "addon.coursecompletion.nottracked": "You are currently not being tracked by completion in this course", "addon.coursecompletion.notyetstarted": "Not yet started", "addon.coursecompletion.pending": "Pending", "addon.coursecompletion.required": "Required", From 2f56db4511350f7ffe8a917b3e8e64348fefcc06 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 20 Mar 2019 14:49:15 +0100 Subject: [PATCH 157/191] MOBILE-2915 core: Fix Uncaught promise error if request fails --- src/classes/site.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/classes/site.ts b/src/classes/site.ts index acdde5a92..26290190f 100644 --- a/src/classes/site.ts +++ b/src/classes/site.ts @@ -582,7 +582,7 @@ export class CoreSite { }); } - const promise = this.getFromCache(method, data, preSets, false, originalData).catch(() => { + let promise = this.getFromCache(method, data, preSets, false, originalData).catch(() => { // Do not pass those options to the core WS factory. return this.wsProvider.call(method, data, wsPreSets).then((response) => { if (preSets.saveToCache) { @@ -688,12 +688,13 @@ export class CoreSite { }); this.ongoingRequests[cacheId] = promise; + // Clear ongoing request after setting the promise (just in case it's already resolved). - promise.finally(() => { - // Make sure we don't clear the promise of a newer request that ignores the cache. - if (this.ongoingRequests[cacheId] === promise) { - delete this.ongoingRequests[cacheId]; - } + promise = promise.finally(() => { + // Make sure we don't clear the promise of a newer request that ignores the cache. + if (this.ongoingRequests[cacheId] === promise) { + delete this.ongoingRequests[cacheId]; + } }); return promise.then((response) => { From 0d46a6a426858dafb3b9a61c68abcb8153065eda Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 21 Mar 2019 16:00:11 +0100 Subject: [PATCH 158/191] MOBILE-2915 config: Revert file-opener to 2.0.19 --- config.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.xml b/config.xml index a8ee57ec4..0a1343541 100644 --- a/config.xml +++ b/config.xml @@ -124,7 +124,7 @@ - + From 0449c63c7110b0733c930c4ad564d1499383d447 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 22 Mar 2019 07:02:08 +0100 Subject: [PATCH 159/191] MOBILE-2915 course: Fix title not displayed for guest courses --- src/core/course/pages/section/section.ts | 4 ++- src/core/course/providers/helper.ts | 27 ++++++++++++++ src/core/courses/providers/courses.ts | 46 ++++++++++++++++++++++-- 3 files changed, 73 insertions(+), 4 deletions(-) diff --git a/src/core/course/pages/section/section.ts b/src/core/course/pages/section/section.ts index 65adf6fb4..7246d3fd9 100644 --- a/src/core/course/pages/section/section.ts +++ b/src/core/course/pages/section/section.ts @@ -169,7 +169,9 @@ export class CoreCourseSectionPage implements OnDestroy { */ protected loadData(refresh?: boolean, sync?: boolean): Promise { // First of all, get the course because the data might have changed. - return this.coursesProvider.getUserCourse(this.course.id).catch(() => { + return this.courseHelper.getCourse(this.course.id).then((result) => { + return result.course; + }).catch(() => { // Error getting the course, probably guest access. }).then((course) => { if (course) { diff --git a/src/core/course/providers/helper.ts b/src/core/course/providers/helper.ts index 941a2f9c4..aa57300b6 100644 --- a/src/core/course/providers/helper.ts +++ b/src/core/course/providers/helper.ts @@ -767,6 +767,33 @@ export class CoreCourseHelperProvider { }); } + /** + * Get a course. It will first check the user courses, and fallback to another WS if not enrolled. + * + * @param {number} courseId Course ID. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise<{enrolled: boolean, course: any}>} Promise resolved with the course. + */ + getCourse(courseId: number, siteId?: string): Promise<{enrolled: boolean, course: any}> { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + // Try with enrolled courses first. + return this.coursesProvider.getUserCourse(courseId, false, siteId).then((course) => { + return { enrolled: true, course: course }; + }).catch(() => { + // Not enrolled or an error happened. Try to use another WebService. + return this.coursesProvider.isGetCoursesByFieldAvailableInSite(siteId).then((available) => { + if (available) { + return this.coursesProvider.getCourseByField('id', courseId, siteId); + } else { + return this.coursesProvider.getCourse(courseId, siteId); + } + }).then((course) => { + return { enrolled: false, course: course }; + }); + }); + } + /** * Check if the course has a block with that name. * diff --git a/src/core/courses/providers/courses.ts b/src/core/courses/providers/courses.ts index d4e492aa8..2760b2f0c 100644 --- a/src/core/courses/providers/courses.ts +++ b/src/core/courses/providers/courses.ts @@ -389,6 +389,30 @@ export class CoreCoursesProvider { } } + /** + * Get the first course returned by getCoursesByField. + * + * @param {string} [field] The field to search. Can be left empty for all courses or: + * id: course id. + * ids: comma separated course ids. + * shortname: course short name. + * idnumber: course id number. + * category: category id the course belongs to. + * @param {any} [value] The value to match. + * @param {string} [siteId] Site ID. If not defined, use current site. + * @return {Promise} Promise resolved with the first course. + * @since 3.2 + */ + getCourseByField(field?: string, value?: any, siteId?: string): Promise { + return this.getCoursesByField(field, value, siteId).then((courses) => { + if (courses && courses.length > 0) { + return courses[0]; + } + + return Promise.reject(null); + }); + } + /** * Get courses. They can be filtered by field. * @@ -482,13 +506,29 @@ export class CoreCoursesProvider { } /** - * Check if get courses by field WS is available. + * Check if get courses by field WS is available in a certain site. * + * @param {CoreSite} [site] Site to check. * @return {boolean} Whether get courses by field is available. * @since 3.2 */ - isGetCoursesByFieldAvailable(): boolean { - return this.sitesProvider.wsAvailableInCurrentSite('core_course_get_courses_by_field'); + isGetCoursesByFieldAvailable(site?: CoreSite): boolean { + site = site || this.sitesProvider.getCurrentSite(); + + return site.wsAvailable('core_course_get_courses_by_field'); + } + + /** + * Check if get courses by field WS is available in a certain site, by site ID. + * + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with boolean: whether get courses by field is available. + * @since 3.2 + */ + isGetCoursesByFieldAvailableInSite(siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + return this.isGetCoursesByFieldAvailable(site); + }); } /** From a9299b1560ff04e91fdda437a287155a1f367992 Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Mon, 25 Mar 2019 17:15:55 +0100 Subject: [PATCH 160/191] MOBILE-2915 site: Fix clearing of cached request promises --- src/classes/site.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/classes/site.ts b/src/classes/site.ts index 26290190f..52a919a24 100644 --- a/src/classes/site.ts +++ b/src/classes/site.ts @@ -582,7 +582,7 @@ export class CoreSite { }); } - let promise = this.getFromCache(method, data, preSets, false, originalData).catch(() => { + const promise = this.getFromCache(method, data, preSets, false, originalData).catch(() => { // Do not pass those options to the core WS factory. return this.wsProvider.call(method, data, wsPreSets).then((response) => { if (preSets.saveToCache) { @@ -690,14 +690,12 @@ export class CoreSite { this.ongoingRequests[cacheId] = promise; // Clear ongoing request after setting the promise (just in case it's already resolved). - promise = promise.finally(() => { + return promise.finally(() => { // Make sure we don't clear the promise of a newer request that ignores the cache. if (this.ongoingRequests[cacheId] === promise) { delete this.ongoingRequests[cacheId]; } - }); - - return promise.then((response) => { + }).then((response) => { // We pass back a clone of the original object, this may prevent errors if in the callback the object is modified. return this.utils.clone(response); }); From 08d7d6e1f9595cee554ea7a8b6cb6518bee857ef Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Mon, 25 Mar 2019 14:33:51 +0100 Subject: [PATCH 161/191] MOBILE-2915 split: Fix call split push too soon --- src/components/split-view/split-view.ts | 28 ++++++++++++++++--------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/src/components/split-view/split-view.ts b/src/components/split-view/split-view.ts index ac9e434f5..3e26f6e86 100644 --- a/src/components/split-view/split-view.ts +++ b/src/components/split-view/split-view.ts @@ -49,7 +49,7 @@ export class CoreSplitViewComponent implements OnInit, OnDestroy { @ViewChild('menu') menu: Menu; @Input() when?: string | boolean = 'md'; - protected isEnabled = false; + protected isEnabled; protected masterPageName = ''; protected masterPageIndex = 0; protected loadDetailPage: any = false; @@ -174,7 +174,7 @@ export class CoreSplitViewComponent implements OnInit, OnDestroy { * @return {boolean} If split view is enabled. */ isOn(): boolean { - return this.isEnabled; + return !!this.isEnabled; } /** @@ -182,16 +182,24 @@ export class CoreSplitViewComponent implements OnInit, OnDestroy { * * @param {any} page The component class or deeplink name you want to push onto the navigation stack. * @param {any} params Any NavParams you want to pass along to the next view. + * @param {boolean} [retrying] Whether it's retrying. */ - push(page: any, params?: any): void { - if (this.isEnabled) { - this.detailNav.setRoot(page, params); + push(page: any, params?: any, retrying?: boolean): void { + if (typeof this.isEnabled == 'undefined' && !retrying) { + // Hasn't calculated if it's enabled yet. Wait a bit and try again. + setTimeout(() => { + this.push(page, params, true); + }, 200); } else { - this.loadDetailPage = { - component: page, - data: params - }; - this.masterNav.push(page, params); + if (this.isEnabled) { + this.detailNav.setRoot(page, params); + } else { + this.loadDetailPage = { + component: page, + data: params + }; + this.masterNav.push(page, params); + } } } From 47bb9aa435774b24dfca2c2b2551575ba2a9aed2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 26 Mar 2019 11:30:56 +0100 Subject: [PATCH 162/191] MOBILE-2915 ratings: Fix contextual menu sync item --- src/addon/mod/data/components/index/addon-mod-data-index.html | 4 ++-- .../mod/forum/components/index/addon-mod-forum-index.html | 4 ++-- .../glossary/components/index/addon-mod-glossary-index.html | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/addon/mod/data/components/index/addon-mod-data-index.html b/src/addon/mod/data/components/index/addon-mod-data-index.html index 422a2b75f..d6c306669 100644 --- a/src/addon/mod/data/components/index/addon-mod-data-index.html +++ b/src/addon/mod/data/components/index/addon-mod-data-index.html @@ -7,8 +7,8 @@ - - + + 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 9eab4d526..47b6037c1 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 @@ -4,8 +4,8 @@ - - + +
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 ad00c5d0f..b5617f259 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 @@ -7,8 +7,8 @@ - - + + From b3663e591c28d1eb6b7276f08cbc5762ec2a1648 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 26 Mar 2019 12:05:37 +0100 Subject: [PATCH 163/191] MOBILE-2915 desktop: Fix 'push' click in desktop apps --- src/core/emulator/providers/local-notifications.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/core/emulator/providers/local-notifications.ts b/src/core/emulator/providers/local-notifications.ts index bcf07ca6b..3b943aba1 100644 --- a/src/core/emulator/providers/local-notifications.ts +++ b/src/core/emulator/providers/local-notifications.ts @@ -294,10 +294,6 @@ export class LocalNotificationsMock extends LocalNotifications { notification.timeoutAfter = this.parseToInt('timeoutAfter', notification); } - if (typeof notification.data == 'object') { - notification.data = JSON.stringify(notification.data); - } - this.convertPriority(notification); this.convertTrigger(notification); this.convertActions(notification); From 63cfc0604a9ee78f03b5ab0bdb3eacdd23904409 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 26 Mar 2019 16:13:23 +0100 Subject: [PATCH 164/191] MOBILE-2915 ios: Fix shared files treated twice in iOS 12 --- src/core/sharedfiles/sharedfiles.module.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/core/sharedfiles/sharedfiles.module.ts b/src/core/sharedfiles/sharedfiles.module.ts index 82c41169a..692ace04d 100644 --- a/src/core/sharedfiles/sharedfiles.module.ts +++ b/src/core/sharedfiles/sharedfiles.module.ts @@ -44,15 +44,27 @@ export class CoreSharedFilesModule { delegate.registerHandler(handler); if (platform.is('ios')) { + let lastCheck = 0; + // Check if there are new files at app start and when the app is resumed. helper.searchIOSNewSharedFiles(); platform.resume.subscribe(() => { - helper.searchIOSNewSharedFiles(); + // Wait a bit to make sure that APP_LAUNCHED_URL is treated before this callback. + setTimeout(() => { + if (Date.now() - lastCheck < 1000) { + // Last check less than 1s ago, don't do anything. + return; + } + + lastCheck = Date.now(); + helper.searchIOSNewSharedFiles(); + }, 200); }); eventsProvider.on(CoreEventsProvider.APP_LAUNCHED_URL, (url) => { if (url && url.indexOf('file://') === 0) { // We received a file in iOS, it's probably a shared file. Treat it. + lastCheck = Date.now(); helper.searchIOSNewSharedFiles(url); } }); From d8084bba5c6a08b40fd9299a4aac52dc41a71c28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 27 Mar 2019 11:21:30 +0100 Subject: [PATCH 165/191] MOBILE-2915 scorm: Fix hide bottom tabs from MOBILE-2886 --- src/components/ion-tabs/core-ion-tabs.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ion-tabs/core-ion-tabs.html b/src/components/ion-tabs/core-ion-tabs.html index d4276f461..e255871b6 100644 --- a/src/components/ion-tabs/core-ion-tabs.html +++ b/src/components/ion-tabs/core-ion-tabs.html @@ -1,4 +1,4 @@ -
+
From c28b48cc67d82ffbea07f7028e3c766dc51c2321 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 27 Mar 2019 11:32:20 +0100 Subject: [PATCH 166/191] MOBILE-2915 data: Do not show unsupported tags field --- src/addon/mod/data/pages/search/search.ts | 4 ++++ src/addon/mod/data/providers/helper.ts | 1 + 2 files changed, 5 insertions(+) diff --git a/src/addon/mod/data/pages/search/search.ts b/src/addon/mod/data/pages/search/search.ts index d3eaf37a2..4ca20b5d0 100644 --- a/src/addon/mod/data/pages/search/search.ts +++ b/src/addon/mod/data/pages/search/search.ts @@ -117,6 +117,10 @@ export class AddonModDataSearchPage { [placeholder]="\'addon.mod_data.authorlastname\' | translate" formControlName="lastname">'; template = template.replace(replace, render); + // Tags are unsupported right now. + replace = new RegExp('##tags##', 'gi'); + template = template.replace(replace, ''); + return template; } diff --git a/src/addon/mod/data/providers/helper.ts b/src/addon/mod/data/providers/helper.ts index 0de845835..c713f3674 100644 --- a/src/addon/mod/data/providers/helper.ts +++ b/src/addon/mod/data/providers/helper.ts @@ -173,6 +173,7 @@ export class AddonModDataHelperProvider { comments: database.comments, // Unsupported actions. + tags: false, delcheck: false, export: false }; From ba27f753247aa3dbfbf57fd25274b18d4db1fe20 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 27 Mar 2019 12:01:27 +0100 Subject: [PATCH 167/191] MOBILE-2915 login: Fix signup errors not displayed --- .../login/pages/email-signup/email-signup.ts | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/core/login/pages/email-signup/email-signup.ts b/src/core/login/pages/email-signup/email-signup.ts index 553a33a55..b038da362 100644 --- a/src/core/login/pages/email-signup/email-signup.ts +++ b/src/core/login/pages/email-signup/email-signup.ts @@ -261,25 +261,25 @@ export class CoreLoginEmailSignupPage { (fieldsData) => { params.customprofilefields = fieldsData; - this.wsProvider.callAjax('auth_email_signup_user', params, { siteUrl: this.siteUrl }).then((result) => { - if (result.success) { - // 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); - this.navCtrl.pop(); - } else { - if (result.warnings && result.warnings.length) { - let error = result.warnings[0].message; - if (error == 'incorrect-captcha-sol') { - error = this.translate.instant('core.login.recaptchaincorrect'); - } - - this.domUtils.showErrorModal(error); - } else { - this.domUtils.showErrorModal('core.login.usernotaddederror', true); + return this.wsProvider.callAjax('auth_email_signup_user', params, { siteUrl: this.siteUrl }); + }).then((result) => { + if (result.success) { + // 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); + this.navCtrl.pop(); + } else { + if (result.warnings && result.warnings.length) { + let error = result.warnings[0].message; + if (error == 'incorrect-captcha-sol') { + error = this.translate.instant('core.login.recaptchaincorrect'); } + + this.domUtils.showErrorModal(error); + } else { + this.domUtils.showErrorModal('core.login.usernotaddederror', true); } - }); + } }).catch((error) => { this.domUtils.showErrorModalDefault(error, 'core.login.usernotaddederror', true); }).finally(() => { From 9978bd0caeeb67fb0e7e8e328c4e6944e8bb26ce Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 27 Mar 2019 13:38:58 +0100 Subject: [PATCH 168/191] MOBILE-2915 file: Fix delete files with special chars --- src/components/local-file/local-file.ts | 14 +++++++------- src/providers/file.ts | 11 ++++++++++- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/components/local-file/local-file.ts b/src/components/local-file/local-file.ts index f507b2275..b5f3140ec 100644 --- a/src/components/local-file/local-file.ts +++ b/src/components/local-file/local-file.ts @@ -62,13 +62,6 @@ export class CoreLocalFileComponent implements OnInit { ngOnInit(): void { this.manage = this.utils.isTrueOrOne(this.manage); - // Let's calculate the relative path for the file. - this.relativePath = this.fileProvider.removeBasePath(this.file.toURL()); - if (!this.relativePath) { - // Didn't find basePath, use fullPath but if the user tries to manage the file it'll probably fail. - this.relativePath = this.file.fullPath; - } - this.loadFileBasicData(); // Get the size and timemodified. @@ -88,6 +81,13 @@ export class CoreLocalFileComponent implements OnInit { this.fileName = this.file.name; this.fileIcon = this.mimeUtils.getFileIcon(this.file.name); this.fileExtension = this.mimeUtils.getFileExtension(this.file.name); + + // Let's calculate the relative path for the file. + this.relativePath = this.fileProvider.removeBasePath(this.file.toURL()); + if (!this.relativePath) { + // Didn't find basePath, use fullPath but if the user tries to manage the file it'll probably fail. + this.relativePath = this.file.fullPath; + } } /** diff --git a/src/providers/file.ts b/src/providers/file.ts index 579a7745f..a2c53ceb1 100644 --- a/src/providers/file.ts +++ b/src/providers/file.ts @@ -324,7 +324,16 @@ export class CoreFileProvider { path = this.removeStartingSlash(path.replace(this.basePath, '')); this.logger.debug('Remove file: ' + path); - return this.file.removeFile(this.basePath, path); + return this.file.removeFile(this.basePath, path).catch((error) => { + // The delete can fail if the path has encoded characters. Try again if that's the case. + const decodedPath = decodeURI(path); + + if (decodedPath != path) { + return this.file.removeFile(this.basePath, decodedPath); + } else { + return Promise.reject(error); + } + }); }); } From 3f370f97caf88c8cc37b48a847b5c9cc35409ede Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 27 Mar 2019 16:35:34 +0100 Subject: [PATCH 169/191] MOBILE-2915 login: Mark recaptcha as required --- src/core/login/pages/email-signup/email-signup.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/login/pages/email-signup/email-signup.html b/src/core/login/pages/email-signup/email-signup.html index c8909aafe..e00adbfca 100644 --- a/src/core/login/pages/email-signup/email-signup.html +++ b/src/core/login/pages/email-signup/email-signup.html @@ -115,7 +115,7 @@ - {{ 'core.login.security_question' | translate }} + {{ 'core.login.security_question' | translate }} From 5c4fcaf09e48aa9d1dd32d8db66dc64c6594b3cf Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 27 Mar 2019 17:06:13 +0100 Subject: [PATCH 170/191] MOBILE-2915 core: Fix multiple change password errors --- src/classes/site.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/classes/site.ts b/src/classes/site.ts index 52a919a24..b9068bd13 100644 --- a/src/classes/site.ts +++ b/src/classes/site.ts @@ -605,7 +605,7 @@ export class CoreSite { // Session expired, trigger event. this.eventsProvider.trigger(CoreEventsProvider.SESSION_EXPIRED, {}, this.id); - // Change error message. We'll try to get data from cache. + // Change error message. Try to get data from cache, the event will handle the error. error.message = this.translate.instant('core.lostconnection'); } else if (error.errorcode === 'userdeleted') { // User deleted, trigger event. @@ -614,17 +614,15 @@ export class CoreSite { return Promise.reject(error); } else if (error.errorcode === 'forcepasswordchangenotice') { - // Password Change Forced, trigger event. + // Password Change Forced, trigger event. Try to get data from cache, the event will handle the error. this.eventsProvider.trigger(CoreEventsProvider.PASSWORD_CHANGE_FORCED, {}, this.id); error.message = this.translate.instant('core.forcepasswordchangenotice'); - return Promise.reject(error); } else if (error.errorcode === 'usernotfullysetup') { - // User not fully setup, trigger event. + // User not fully setup, trigger event. Try to get data from cache, the event will handle the error. this.eventsProvider.trigger(CoreEventsProvider.USER_NOT_FULLY_SETUP, {}, this.id); error.message = this.translate.instant('core.usernotfullysetup'); - return Promise.reject(error); } else if (error.errorcode === 'sitepolicynotagreed') { // Site policy not agreed, trigger event. this.eventsProvider.trigger(CoreEventsProvider.SITE_POLICY_NOT_AGREED, {}, this.id); From 8b5b33ec82713027b354ba491091813426dcc2c2 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 27 Mar 2019 18:03:36 +0100 Subject: [PATCH 171/191] MOBILE-2915 forum: Fix discussion sync --- src/addon/mod/forum/pages/discussion/discussion.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/addon/mod/forum/pages/discussion/discussion.ts b/src/addon/mod/forum/pages/discussion/discussion.ts index 7b5f6f6f1..7cf56cdf0 100644 --- a/src/addon/mod/forum/pages/discussion/discussion.ts +++ b/src/addon/mod/forum/pages/discussion/discussion.ts @@ -357,7 +357,7 @@ export class AddonModForumDiscussionPage implements OnDestroy { protected syncDiscussion(showErrors: boolean): Promise { const promises = []; - promises.push(this.forumSync.syncDiscussionReplies(this.forumId, this.discussionId).then((result) => { + promises.push(this.forumSync.syncDiscussionReplies(this.discussionId).then((result) => { if (result.warnings && result.warnings.length) { this.domUtils.showErrorModal(result.warnings[0]); } From bbc9421a5eaf97fa0a4b758e4f9221a16897c897 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 27 Mar 2019 18:24:15 +0100 Subject: [PATCH 172/191] MOBILE-2915 recaptcha: Change recaptcha button string --- scripts/langindex.json | 1 + src/assets/lang/en.json | 1 + src/components/recaptcha/core-recaptcha.html | 2 +- src/lang/en.json | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/scripts/langindex.json b/scripts/langindex.json index 2bbbdb083..4c0f0bc57 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -1608,6 +1608,7 @@ "core.remove": "moodle", "core.required": "moodle", "core.requireduserdatamissing": "local_moodlemobileapp", + "core.resourcedisplayopen": "moodle", "core.resources": "moodle", "core.restore": "moodle", "core.retry": "local_moodlemobileapp", diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index 9f14d682a..e0c8c523a 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -1608,6 +1608,7 @@ "core.remove": "Remove", "core.required": "Required", "core.requireduserdatamissing": "This user lacks some required profile data. Please enter the data in your site and try again.
{{$a}}", + "core.resourcedisplayopen": "Open", "core.resources": "Resources", "core.restore": "Restore", "core.retry": "Retry", diff --git a/src/components/recaptcha/core-recaptcha.html b/src/components/recaptcha/core-recaptcha.html index a0688a12b..d4e096dba 100644 --- a/src/components/recaptcha/core-recaptcha.html +++ b/src/components/recaptcha/core-recaptcha.html @@ -2,7 +2,7 @@
- +

{{ 'core.answered' | translate }}

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

diff --git a/src/lang/en.json b/src/lang/en.json index 7b7086f67..3df5da21b 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -196,6 +196,7 @@ "remove": "Remove", "required": "Required", "requireduserdatamissing": "This user lacks some required profile data. Please enter the data in your site and try again.
{{$a}}", + "resourcedisplayopen": "Open", "resources": "Resources", "restore": "Restore", "retry": "Retry", From ce8699a03f8d7e267a9464bfca11c970753763e6 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 27 Mar 2019 18:55:29 +0100 Subject: [PATCH 173/191] MOBILE-2915 mod: Fix feedback and workshop prefetch and sync --- src/addon/mod/feedback/components/index/index.ts | 2 ++ src/addon/mod/workshop/components/index/index.ts | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/addon/mod/feedback/components/index/index.ts b/src/addon/mod/feedback/components/index/index.ts index 76610926d..55ffc4d21 100644 --- a/src/addon/mod/feedback/components/index/index.ts +++ b/src/addon/mod/feedback/components/index/index.ts @@ -36,6 +36,7 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity @Input() tab = 'overview'; @Input() group = 0; + component = AddonModFeedbackProvider.COMPONENT; moduleName = 'feedback'; access = { @@ -67,6 +68,7 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity firstSelectedTab: number; protected submitObserver: any; + protected syncEventName = AddonModFeedbackSyncProvider.AUTO_SYNCED; constructor(injector: Injector, private feedbackProvider: AddonModFeedbackProvider, @Optional() content: Content, private feedbackOffline: AddonModFeedbackOfflineProvider, private groupsProvider: CoreGroupsProvider, diff --git a/src/addon/mod/workshop/components/index/index.ts b/src/addon/mod/workshop/components/index/index.ts index 496039184..1515e91da 100644 --- a/src/addon/mod/workshop/components/index/index.ts +++ b/src/addon/mod/workshop/components/index/index.ts @@ -32,6 +32,7 @@ import { AddonModWorkshopOfflineProvider } from '../../providers/offline'; export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivityComponent { @Input() group = 0; + component = AddonModWorkshopProvider.COMPONENT; moduleName = 'workshop'; workshop: any; page = 0; @@ -64,6 +65,7 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity protected obsAssessmentSaved: any; protected appResumeSubscription: any; protected syncObserver: any; + protected syncEventName = AddonModWorkshopSyncProvider.AUTO_SYNCED; constructor(injector: Injector, private workshopProvider: AddonModWorkshopProvider, @Optional() content: Content, private workshopOffline: AddonModWorkshopOfflineProvider, private groupsProvider: CoreGroupsProvider, From 322d6ad2ca863878c13a787471f9256c6545d408 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 28 Mar 2019 09:21:44 +0100 Subject: [PATCH 174/191] MOBILE-2915 lesson: Improve buttons displayed if finished offline --- .../components/index/addon-mod-lesson-index.html | 14 +++++++++++--- src/addon/mod/lesson/components/index/index.ts | 6 ++++++ 2 files changed, 17 insertions(+), 3 deletions(-) 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 61a19ce3c..c0c7b311a 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 @@ -58,7 +58,7 @@ {{ 'addon.mod_lesson.review' | translate }} - +

@@ -73,7 +73,7 @@
- +

@@ -87,7 +87,7 @@

- + {{ 'core.start' | translate }} @@ -98,6 +98,14 @@
+ + + + + {{ 'addon.mod_lesson.continue' | translate }} + + + diff --git a/src/addon/mod/lesson/components/index/index.ts b/src/addon/mod/lesson/components/index/index.ts index 0f98b9444..3750b56dc 100644 --- a/src/addon/mod/lesson/components/index/index.ts +++ b/src/addon/mod/lesson/components/index/index.ts @@ -56,6 +56,7 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo reportLoaded: boolean; // Whether the report data has been loaded. selectedGroupName: string; // The name of the selected group. overview: any; // Reports overview data. + finishedOffline: boolean; // Whether a retake was finished in offline. protected syncEventName = AddonModLessonSyncProvider.AUTO_SYNCED; protected accessInfo: any; // Lesson access info. @@ -159,6 +160,11 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo } })); + // Check if the ser has a finished retake in offline. + promises.push(this.lessonOffline.hasFinishedRetake(this.lesson.id).then((finished) => { + this.finishedOffline = finished; + })); + // Update the list of content pages viewed and question attempts. promises.push(this.lessonProvider.getContentPagesViewedOnline(this.lesson.id, info.attemptscount)); promises.push(this.lessonProvider.getQuestionsAttemptsOnline(this.lesson.id, info.attemptscount)); From a36a54cd848c5519b687a3ccf1934f679359c26f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 28 Mar 2019 09:44:55 +0100 Subject: [PATCH 175/191] MOBILE-2915 lesson: Fix password form styling --- .../mod/lesson/components/index/addon-mod-lesson-index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 61a19ce3c..f5ab57245 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 @@ -35,9 +35,9 @@ - + + {{ 'addon.mod_lesson.enterpassword' | translate }} - {{ 'addon.mod_lesson.enterpassword' | translate }} From 757b1b1bc31d5f82908bb2df55d9c73019becf19 Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Thu, 28 Mar 2019 10:52:03 +0100 Subject: [PATCH 176/191] MOBILE-2915 assign: Display specific message when exceeding word limit --- scripts/langindex.json | 1 + .../assign/submission/onlinetext/lang/en.json | 3 ++- .../submission/onlinetext/providers/handler.ts | 16 +++++++++++++++- src/assets/lang/en.json | 1 + 4 files changed, 19 insertions(+), 2 deletions(-) diff --git a/scripts/langindex.json b/scripts/langindex.json index 2bbbdb083..c9384e9b6 100644 --- a/scripts/langindex.json +++ b/scripts/langindex.json @@ -344,6 +344,7 @@ "addon.mod_assign_submission_comments.pluginname": "assignsubmission_comments", "addon.mod_assign_submission_file.pluginname": "assignsubmission_file", "addon.mod_assign_submission_onlinetext.pluginname": "assignsubmission_onlinetext", + "addon.mod_assign_submission_onlinetext.wordlimitexceeded": "assignsubmission_onlinetext", "addon.mod_book.errorchapter": "book", "addon.mod_book.modulenameplural": "book", "addon.mod_book.toc": "book", diff --git a/src/addon/mod/assign/submission/onlinetext/lang/en.json b/src/addon/mod/assign/submission/onlinetext/lang/en.json index 9b8a3d9f9..e49362133 100644 --- a/src/addon/mod/assign/submission/onlinetext/lang/en.json +++ b/src/addon/mod/assign/submission/onlinetext/lang/en.json @@ -1,3 +1,4 @@ { - "pluginname": "Online text submissions" + "pluginname": "Online text submissions", + "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." } \ No newline at end of file diff --git a/src/addon/mod/assign/submission/onlinetext/providers/handler.ts b/src/addon/mod/assign/submission/onlinetext/providers/handler.ts index 718a13f51..80745907b 100644 --- a/src/addon/mod/assign/submission/onlinetext/providers/handler.ts +++ b/src/addon/mod/assign/submission/onlinetext/providers/handler.ts @@ -14,6 +14,7 @@ // limitations under the License. import { Injectable, Injector } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; import { CoreSitesProvider } from '@providers/sites'; import { CoreWSProvider } from '@providers/ws'; import { CoreTextUtilsProvider } from '@providers/utils/text'; @@ -31,7 +32,7 @@ export class AddonModAssignSubmissionOnlineTextHandler implements AddonModAssign name = 'AddonModAssignSubmissionOnlineTextHandler'; type = 'onlinetext'; - constructor(private sitesProvider: CoreSitesProvider, private wsProvider: CoreWSProvider, + constructor(private translate: TranslateService, private sitesProvider: CoreSitesProvider, private wsProvider: CoreWSProvider, private textUtils: CoreTextUtilsProvider, private assignProvider: AddonModAssignProvider, private assignOfflineProvider: AddonModAssignOfflineProvider, private assignHelper: AddonModAssignHelperProvider) { } @@ -238,6 +239,19 @@ export class AddonModAssignSubmissionOnlineTextHandler implements AddonModAssign let text = this.getTextToSubmit(plugin, inputData); + // Check word limit. + const configs = this.assignHelper.getPluginConfig(assign, 'assignsubmission', plugin.type); + if (parseInt(configs.wordlimitenabled, 10)) { + const words = this.textUtils.countWords(text); + const wordlimit = parseInt(configs.wordlimit, 10); + if (words > wordlimit) { + const params = {$a: {count: words, limit: wordlimit}}; + const message = this.translate.instant('addon.mod_assign_submission_onlinetext.wordlimitexceeded', params); + + return Promise.reject(message); + } + } + // Add some HTML to the text if needed. text = this.textUtils.formatHtmlLines(text); diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index 9f14d682a..ec03a1bfe 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -344,6 +344,7 @@ "addon.mod_assign_submission_comments.pluginname": "Submission comments", "addon.mod_assign_submission_file.pluginname": "File submissions", "addon.mod_assign_submission_onlinetext.pluginname": "Online text submissions", + "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.toc": "Table of contents", From f969f99e019dbd10ff27b495518855a10ddf0c42 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 28 Mar 2019 11:00:49 +0100 Subject: [PATCH 177/191] MOBILE-2915 lesson: Fix review after sync if no retakes --- .../index/addon-mod-lesson-index.html | 95 ++++++++++--------- 1 file changed, 49 insertions(+), 46 deletions(-) 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 c0c7b311a..c72ea903e 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 @@ -51,61 +51,64 @@ - + + -

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

+

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

{{ 'addon.mod_lesson.review' | translate }}
- - -

- - - - {{ 'core.no' | translate }} - - - {{ 'core.yes' | translate }} - - - -
+ + + +

+ + + + {{ 'core.no' | translate }} + + + {{ 'core.yes' | translate }} + + + +
- - -

- - {{ 'addon.mod_lesson.continue' | translate }} - - -
+ + +

+ + {{ 'addon.mod_lesson.continue' | translate }} + + +
- - -

-
+ + +

+
- - - - {{ 'core.start' | translate }} - - - - {{ 'addon.mod_lesson.preview' | translate }} - - - + + + + {{ 'core.start' | translate }} + + + + {{ 'addon.mod_lesson.preview' | translate }} + + + - - - - {{ 'addon.mod_lesson.continue' | translate }} - - - + + + + {{ 'addon.mod_lesson.continue' | translate }} + + + +
From c0e95f644a2cda92f01c7b4e6e34a734f6f6f678 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Thu, 28 Mar 2019 11:04:39 +0100 Subject: [PATCH 178/191] MOBILE-2915 data: Trim url field --- src/addon/mod/data/fields/url/providers/handler.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/addon/mod/data/fields/url/providers/handler.ts b/src/addon/mod/data/fields/url/providers/handler.ts index ae8eb55d5..1854df829 100644 --- a/src/addon/mod/data/fields/url/providers/handler.ts +++ b/src/addon/mod/data/fields/url/providers/handler.ts @@ -54,7 +54,7 @@ export class AddonModDataFieldUrlHandler extends AddonModDataFieldTextHandler { { fieldid: field.id, subfield: '0', - value: inputData[fieldName] || '' + value: (inputData[fieldName] && inputData[fieldName].trim()) || '' } ]; } From d3ff0824540d5d31aa9d42e922439af2edf56e27 Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Thu, 28 Mar 2019 11:31:42 +0100 Subject: [PATCH 179/191] MOBILE-2915 assign: Fix prefetch for teachers --- src/addon/mod/assign/providers/assign.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/addon/mod/assign/providers/assign.ts b/src/addon/mod/assign/providers/assign.ts index 72320b5f7..93e8204f0 100644 --- a/src/addon/mod/assign/providers/assign.ts +++ b/src/addon/mod/assign/providers/assign.ts @@ -379,9 +379,13 @@ export class AddonModAssignProvider { * * @param {any} assign Assign. * @param {any} attempt Attempt. - * @return {any} Submission object. + * @return {any} Submission object or null. */ getSubmissionObjectFromAttempt(assign: any, attempt: any): any { + if (!attempt) { + return null; + } + return assign.teamsubmission ? attempt.teamsubmission : attempt.submission; } From 644f3c8a8bb76ae970180bd0fa2117ff7a82d86c Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 28 Mar 2019 11:59:24 +0100 Subject: [PATCH 180/191] MOBILE-2915 lesson: Fix 'End of cluster' text displayed --- src/addon/mod/lesson/pages/player/player.html | 4 ++-- src/addon/mod/lesson/providers/helper.ts | 14 +++++++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/addon/mod/lesson/pages/player/player.html b/src/addon/mod/lesson/pages/player/player.html index 428adceef..0c2e2ac0b 100644 --- a/src/addon/mod/lesson/pages/player/player.html +++ b/src/addon/mod/lesson/pages/player/player.html @@ -32,14 +32,14 @@ - + - + diff --git a/src/addon/mod/lesson/providers/helper.ts b/src/addon/mod/lesson/providers/helper.ts index e88652f6c..234c0fd45 100644 --- a/src/addon/mod/lesson/providers/helper.ts +++ b/src/addon/mod/lesson/providers/helper.ts @@ -27,7 +27,8 @@ import { AddonModLessonProvider } from './lesson'; export class AddonModLessonHelperProvider { constructor(private domUtils: CoreDomUtilsProvider, private fb: FormBuilder, private translate: TranslateService, - private textUtils: CoreTextUtilsProvider, private timeUtils: CoreTimeUtilsProvider) { } + private textUtils: CoreTextUtilsProvider, private timeUtils: CoreTimeUtilsProvider, + private lessonProvider: AddonModLessonProvider) { } /** * Given the HTML of next activity link, format it to extract the href and the text. @@ -149,8 +150,15 @@ export class AddonModLessonHelperProvider { return contents.innerHTML.trim(); } - // Cannot find contents element, return the page.contents (some elements like videos might not work). - return data.page.contents; + // Cannot find contents element. + if (this.lessonProvider.isQuestionPage(data.page.type) || + data.page.qtype == AddonModLessonProvider.LESSON_PAGE_BRANCHTABLE) { + // Return page.contents to prevent having duplicated elements (some elements like videos might not work). + return data.page.contents; + } else { + // It's an end of cluster, end of branch, etc. Return the whole pagecontent to match what's displayed in web. + return data.pagecontent; + } } /** From d81004f528be6adb73742f0789c2cb8c9e11b730 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 28 Mar 2019 15:25:58 +0100 Subject: [PATCH 181/191] MOBILE-2915 messages: Fix error if push clicked in convs view --- .../group-conversations.ts | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/addon/messages/pages/group-conversations/group-conversations.ts b/src/addon/messages/pages/group-conversations/group-conversations.ts index b48e9401d..332bae82c 100644 --- a/src/addon/messages/pages/group-conversations/group-conversations.ts +++ b/src/addon/messages/pages/group-conversations/group-conversations.ts @@ -141,8 +141,8 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { conversation.unreadcount = 0; // Conversations changed, invalidate them and refresh unread counts. - this.messagesProvider.invalidateConversations(); - this.messagesProvider.refreshUnreadConversationCounts(); + this.messagesProvider.invalidateConversations(this.siteId); + this.messagesProvider.refreshUnreadConversationCounts(this.siteId); } } }, this.siteId); @@ -251,7 +251,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { const promises = []; promises.push(this.fetchConversationCounts()); - promises.push(this.messagesProvider.getContactRequestsCount()); // View updated by the event observer. + promises.push(this.messagesProvider.getContactRequestsCount(this.siteId)); // View updated by the event observer. return Promise.all(promises).then(() => { if (typeof this.favourites.expanded == 'undefined') { @@ -323,7 +323,8 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { promises.push(this.fetchConversationCounts()); if (refreshUnreadCounts) { - promises.push(this.messagesProvider.refreshUnreadConversationCounts()); // View updated by the event observer. + // View updated by event observer. + promises.push(this.messagesProvider.refreshUnreadConversationCounts(this.siteId)); } return Promise.all(promises); @@ -347,10 +348,10 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { offlineMessages; // Get the conversations and, if needed, the offline messages. Always try to get the latest data. - promises.push(this.messagesProvider.invalidateConversations().catch(() => { + promises.push(this.messagesProvider.invalidateConversations(this.siteId).catch(() => { // Shouldn't happen. }).then(() => { - return this.messagesProvider.getConversations(option.type, option.favourites, limitFrom); + return this.messagesProvider.getConversations(option.type, option.favourites, limitFrom, this.siteId); }).then((result) => { data = result; })); @@ -362,7 +363,8 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { promises.push(this.fetchConversationCounts()); if (refreshUnreadCounts) { - promises.push(this.messagesProvider.refreshUnreadConversationCounts()); // View updated by the event observer. + // View updated by the event observer. + promises.push(this.messagesProvider.refreshUnreadConversationCounts(this.siteId)); } } @@ -392,10 +394,10 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { */ protected fetchConversationCounts(): Promise { // Always try to get the latest data. - return this.messagesProvider.invalidateConversationCounts().catch(() => { + return this.messagesProvider.invalidateConversationCounts(this.siteId).catch(() => { // Shouldn't happen. }).then(() => { - return this.messagesProvider.getConversationCounts(); + return this.messagesProvider.getConversationCounts(this.siteId); }).then((counts) => { this.favourites.count = counts.favourites; this.individual.count = counts.individual; @@ -610,7 +612,7 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy { refreshData(refresher?: any, refreshUnreadCounts: boolean = true): Promise { // Don't invalidate conversations and so, they always try to get latest data. const promises = [ - this.messagesProvider.invalidateContactRequestsCountCache() + this.messagesProvider.invalidateContactRequestsCountCache(this.siteId) ]; return this.utils.allPromises(promises).finally(() => { From 2f1171fec84dcb19829807c1c76dc644056c520a Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 28 Mar 2019 16:27:43 +0100 Subject: [PATCH 182/191] MOBILE-2915 lesson: Allow review when max attempts reached --- src/addon/mod/lesson/pages/player/player.ts | 2 +- src/addon/mod/lesson/providers/lesson.ts | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/addon/mod/lesson/pages/player/player.ts b/src/addon/mod/lesson/pages/player/player.ts index c40f6ed8c..10e5e3ab0 100644 --- a/src/addon/mod/lesson/pages/player/player.ts +++ b/src/addon/mod/lesson/pages/player/player.ts @@ -245,7 +245,7 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy { if (info.preventaccessreasons && info.preventaccessreasons.length) { // If it's a password protected lesson and we have the password, allow playing it. - const preventReason = this.lessonProvider.getPreventAccessReason(info, !!this.password); + const preventReason = this.lessonProvider.getPreventAccessReason(info, !!this.password, this.review); if (preventReason) { // Lesson cannot be played, show message and go back. return Promise.reject(preventReason.message); diff --git a/src/addon/mod/lesson/providers/lesson.ts b/src/addon/mod/lesson/providers/lesson.ts index 5441b3f3a..a364d1693 100644 --- a/src/addon/mod/lesson/providers/lesson.ts +++ b/src/addon/mod/lesson/providers/lesson.ts @@ -2367,9 +2367,10 @@ export class AddonModLessonProvider { * * @param {any} info Lesson access info. * @param {boolean} [ignorePassword] Whether password protected reason should be ignored (user already entered the password). + * @param {boolean} [isReview] Whether user is reviewing a retake. * @return {any} Prevent access reason. */ - getPreventAccessReason(info: any, ignorePassword?: boolean): any { + getPreventAccessReason(info: any, ignorePassword?: boolean, isReview?: boolean): any { let result; if (info && info.preventaccessreasons) { @@ -2384,6 +2385,8 @@ export class AddonModLessonProvider { // Treat password before all other reasons. result = entry; } + } else if (entry.reason == 'noretake' && isReview) { + // Ignore noretake error when reviewing. } else if (!result) { // Rest of cases, just return any of them. result = entry; From 489094598a69889f785279a6f7f0474c42e72be9 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 28 Mar 2019 16:28:21 +0100 Subject: [PATCH 183/191] MOBILE-2915 core: Try to fix freezes when clicking push --- src/core/contentlinks/providers/helper.ts | 18 +++++++++++------- src/providers/local-notifications.ts | 17 ++++++++++------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/src/core/contentlinks/providers/helper.ts b/src/core/contentlinks/providers/helper.ts index ad3d9b6f5..e36e5a20b 100644 --- a/src/core/contentlinks/providers/helper.ts +++ b/src/core/contentlinks/providers/helper.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Injectable } from '@angular/core'; +import { Injectable, NgZone } from '@angular/core'; import { NavController } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; import { CoreAppProvider } from '@providers/app'; @@ -40,7 +40,7 @@ export class CoreContentLinksHelperProvider { private contentLinksDelegate: CoreContentLinksDelegate, private appProvider: CoreAppProvider, private domUtils: CoreDomUtilsProvider, private urlUtils: CoreUrlUtilsProvider, private translate: TranslateService, private initDelegate: CoreInitDelegate, eventsProvider: CoreEventsProvider, private textUtils: CoreTextUtilsProvider, - private sitePluginsProvider: CoreSitePluginsProvider) { + private sitePluginsProvider: CoreSitePluginsProvider, private zone: NgZone) { this.logger = logger.getInstance('CoreContentLinksHelperProvider'); // Listen for app launched URLs. If we receive one, check if it's a content link. @@ -91,11 +91,15 @@ export class CoreContentLinksHelperProvider { */ goInSite(navCtrl: NavController, pageName: string, pageParams: any, siteId?: string): void { siteId = siteId || this.sitesProvider.getCurrentSiteId(); - if (navCtrl && siteId == this.sitesProvider.getCurrentSiteId()) { - navCtrl.push(pageName, pageParams); - } else { - this.loginHelper.redirect(pageName, pageParams, siteId); - } + + // Execute the code in the Angular zone, so change detection doesn't stop working. + this.zone.run(() => { + if (navCtrl && siteId == this.sitesProvider.getCurrentSiteId()) { + navCtrl.push(pageName, pageParams); + } else { + this.loginHelper.redirect(pageName, pageParams, siteId); + } + }); } /** diff --git a/src/providers/local-notifications.ts b/src/providers/local-notifications.ts index 6c6b7df7c..49ea21312 100644 --- a/src/providers/local-notifications.ts +++ b/src/providers/local-notifications.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Injectable } from '@angular/core'; +import { Injectable, NgZone } from '@angular/core'; import { Platform, Alert, AlertController } from 'ionic-angular'; import { LocalNotifications, ILocalNotification } from '@ionic-native/local-notifications'; import { Push } from '@ionic-native/push'; @@ -105,7 +105,7 @@ export class CoreLocalNotificationsProvider { constructor(logger: CoreLoggerProvider, private localNotifications: LocalNotifications, private platform: Platform, private appProvider: CoreAppProvider, private utils: CoreUtilsProvider, private configProvider: CoreConfigProvider, private textUtils: CoreTextUtilsProvider, private translate: TranslateService, private alertCtrl: AlertController, - eventsProvider: CoreEventsProvider, private push: Push) { + eventsProvider: CoreEventsProvider, private push: Push, private zone: NgZone) { this.logger = logger.getInstance('CoreLocalNotificationsProvider'); this.appDB = appProvider.getDB(); @@ -327,12 +327,15 @@ export class CoreLocalNotificationsProvider { * @param {any} data Data received by the notification. */ notifyClick(data: any): void { - const component = data.component; - if (component) { - if (this.observables[component]) { - this.observables[component].next(data); + // Execute the code in the Angular zone, so change detection doesn't stop working. + this.zone.run(() => { + const component = data.component; + if (component) { + if (this.observables[component]) { + this.observables[component].next(data); + } } - } + }); } /** From 879792ac11ca8d15d482deca0d24f99bc9e430b3 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 29 Mar 2019 11:05:46 +0100 Subject: [PATCH 184/191] MOBILE-2915 messages: Fix error if push clicked in discs view --- .../messages/components/discussions/discussions.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/addon/messages/components/discussions/discussions.ts b/src/addon/messages/components/discussions/discussions.ts index afb9b5d88..b072b719b 100644 --- a/src/addon/messages/components/discussions/discussions.ts +++ b/src/addon/messages/components/discussions/discussions.ts @@ -92,8 +92,8 @@ export class AddonMessagesDiscussionsComponent implements OnDestroy { discussion.unread = false; // Conversations changed, invalidate them and refresh unread counts. - this.messagesProvider.invalidateConversations(); - this.messagesProvider.refreshUnreadConversationCounts(); + this.messagesProvider.invalidateConversations(this.siteId); + this.messagesProvider.refreshUnreadConversationCounts(this.siteId); } } }, this.siteId); @@ -145,10 +145,10 @@ export class AddonMessagesDiscussionsComponent implements OnDestroy { */ refreshData(refresher?: any, refreshUnreadCounts: boolean = true): Promise { const promises = []; - promises.push(this.messagesProvider.invalidateDiscussionsCache()); + promises.push(this.messagesProvider.invalidateDiscussionsCache(this.siteId)); if (refreshUnreadCounts) { - promises.push(this.messagesProvider.invalidateUnreadConversationCounts()); + promises.push(this.messagesProvider.invalidateUnreadConversationCounts(this.siteId)); } return this.utils.allPromises(promises).finally(() => { @@ -171,7 +171,7 @@ export class AddonMessagesDiscussionsComponent implements OnDestroy { const promises = []; - promises.push(this.messagesProvider.getDiscussions().then((discussions) => { + promises.push(this.messagesProvider.getDiscussions(this.siteId).then((discussions) => { // Convert to an array for sorting. const discussionsSorted = []; for (const userId in discussions) { @@ -184,7 +184,7 @@ export class AddonMessagesDiscussionsComponent implements OnDestroy { }); })); - promises.push(this.messagesProvider.getUnreadConversationCounts()); + promises.push(this.messagesProvider.getUnreadConversationCounts(this.siteId)); return Promise.all(promises).catch((error) => { this.domUtils.showErrorModalDefault(error, 'addon.messages.errorwhileretrievingdiscussions', true); @@ -216,7 +216,7 @@ export class AddonMessagesDiscussionsComponent implements OnDestroy { this.loaded = false; this.loadingMessage = this.search.loading; - return this.messagesProvider.searchMessages(query).then((searchResults) => { + return this.messagesProvider.searchMessages(query, undefined, undefined, undefined, this.siteId).then((searchResults) => { this.search.showResults = true; this.search.results = searchResults.messages; }).catch((error) => { From b16eb29ac0ee6685cd34a83252c89080e142ec4e Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 29 Mar 2019 11:07:36 +0100 Subject: [PATCH 185/191] MOBILE-2915 core: Fix redirect to logged out sites --- src/core/login/pages/init/init.ts | 3 ++- src/core/login/pages/reconnect/reconnect.ts | 8 ++------ src/core/login/providers/helper.ts | 12 +++++++++--- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/core/login/pages/init/init.ts b/src/core/login/pages/init/init.ts index 45fb36760..3758d04c3 100644 --- a/src/core/login/pages/init/init.ts +++ b/src/core/login/pages/init/init.ts @@ -55,7 +55,8 @@ export class CoreLoginInitPage { .then((loggedIn) => { if (loggedIn) { - return this.navCtrl.setRoot(redirectData.page, redirectData.params, { animate: false }); + return this.loginHelper.goToSiteInitialPage(this.navCtrl, redirectData.page, redirectData.params, + { animate: false }); } }).catch(() => { // Site doesn't exist. diff --git a/src/core/login/pages/reconnect/reconnect.ts b/src/core/login/pages/reconnect/reconnect.ts index 4b56f7dee..03f17efa8 100644 --- a/src/core/login/pages/reconnect/reconnect.ts +++ b/src/core/login/pages/reconnect/reconnect.ts @@ -146,12 +146,8 @@ export class CoreLoginReconnectPage { // Reset fields so the data is not in the view anymore. this.credForm.controls['password'].reset(); - if (this.pageName) { - // Page defined, go to that page instead of site initial page. - return this.navCtrl.setRoot(this.pageName, this.pageParams); - } else { - return this.loginHelper.goToSiteInitialPage(); - } + // Go to the site initial page. + return this.loginHelper.goToSiteInitialPage(this.navCtrl, this.pageName, this.pageParams); }).catch((error) => { // Error, go back to login page. this.domUtils.showErrorModalDefault(error, 'core.login.errorupdatesite', true); diff --git a/src/core/login/providers/helper.ts b/src/core/login/providers/helper.ts index bf8abb21b..3d84c5232 100644 --- a/src/core/login/providers/helper.ts +++ b/src/core/login/providers/helper.ts @@ -14,7 +14,7 @@ import { Injectable } from '@angular/core'; import { Location } from '@angular/common'; -import { Platform, AlertController } from 'ionic-angular'; +import { Platform, AlertController, NavController, NavOptions } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; import { CoreAppProvider } from '@providers/app'; import { CoreConfigProvider } from '@providers/config'; @@ -416,14 +416,20 @@ export class CoreLoginHelperProvider { /** * Go to the initial page of a site depending on 'userhomepage' setting. * + * @param {NavController} [navCtrl] NavController to use. Defaults to app root NavController. + * @param {string} [page] Name of the page to load after loading the main page. + * @param {any} [params] Params to pass to the page. + * @param {NavOptions} [options] Navigation options. * @return {Promise} Promise resolved when done. */ - goToSiteInitialPage(): Promise { + goToSiteInitialPage(navCtrl?: NavController, page?: string, params?: any, options?: NavOptions): Promise { + navCtrl = navCtrl || this.appProvider.getRootNavController(); + // Due to DeepLinker, we need to remove the path from the URL before going to main menu. // IonTabs checks the URL to determine which path to load for deep linking, so we clear the URL. this.location.replaceState(''); - return this.appProvider.getRootNavController().setRoot('CoreMainMenuPage'); + return navCtrl.setRoot('CoreMainMenuPage', { redirectPage: page, redirectParams: params }, options); } /** From ceff8858f4e073e411dbbd0ddc08ab44ef2c3308 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 29 Mar 2019 11:43:25 +0100 Subject: [PATCH 186/191] MOBILE-2915 blog: Always display show my entries toggle --- src/addon/blog/components/entries/entries.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/addon/blog/components/entries/entries.ts b/src/addon/blog/components/entries/entries.ts index 282c6ef87..54f84d30a 100644 --- a/src/addon/blog/components/entries/entries.ts +++ b/src/addon/blog/components/entries/entries.ts @@ -135,9 +135,7 @@ export class AddonBlogEntriesComponent implements OnInit { this.canLoadMore = result.totalentries > this.entries.length; this.pageLoaded++; - this.showMyIssuesToggle = !this.userId && (this.showMyIssuesToggle || this.entries.some((entry) => { - return entry.userid == this.currentUserId; - })); + this.showMyIssuesToggle = !this.userId; return Promise.all(promises); }).catch((message) => { From e272a3963683de7b09cd4623d2cc95108997f312 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Fri, 29 Mar 2019 12:06:09 +0100 Subject: [PATCH 187/191] MOBILE-2915 workshop: Fix asssessment prefetch --- .../workshop/providers/prefetch-handler.ts | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/addon/mod/workshop/providers/prefetch-handler.ts b/src/addon/mod/workshop/providers/prefetch-handler.ts index d7e213044..ccdb6c4d2 100644 --- a/src/addon/mod/workshop/providers/prefetch-handler.ts +++ b/src/addon/mod/workshop/providers/prefetch-handler.ts @@ -332,17 +332,14 @@ export class AddonModWorkshopPrefetchHandler extends CoreCourseActivityPrefetchH }); } - if (assessments.length > 0) { - reportPromise = reportPromise.finally(() => { - const promises3 = []; - assessments.forEach((assessment, id) => { - promises3.push(this.workshopProvider.getAssessmentForm(workshop.id, id, undefined, undefined, - undefined, siteId)); - }); - - return Promise.all(promises3); - }); - } + reportPromise = reportPromise.finally(() => { + if (assessments.length > 0) { + return Promise.all(assessments.map((assessment, id) => { + return this.workshopProvider.getAssessmentForm(workshop.id, id, undefined, undefined, undefined, + siteId); + })); + } + }); promises2.push(reportPromise); if (workshop.phase == AddonModWorkshopProvider.PHASE_CLOSED) { From 155e0e5aeadcac91b917edd71c2da287f264670d Mon Sep 17 00:00:00 2001 From: Albert Gasset Date: Fri, 29 Mar 2019 12:25:23 +0100 Subject: [PATCH 188/191] MOBILE-2915 data: Hide rate component in non-approved entries --- src/addon/mod/data/pages/entry/entry.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/addon/mod/data/pages/entry/entry.html b/src/addon/mod/data/pages/entry/entry.html index 19dd1987c..1767dc9c0 100644 --- a/src/addon/mod/data/pages/entry/entry.html +++ b/src/addon/mod/data/pages/entry/entry.html @@ -28,8 +28,8 @@
- - + + From 201a7032581f63248807d10c5e5896f29fffbe6d Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 29 Mar 2019 12:46:04 +0100 Subject: [PATCH 189/191] MOBILE-2915 core: Show file not found error if no files --- src/addon/mod/resource/components/index/index.ts | 6 ++++-- src/assets/lang/en.json | 1 + src/core/course/providers/helper.ts | 2 +- src/lang/en.json | 1 + 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/addon/mod/resource/components/index/index.ts b/src/addon/mod/resource/components/index/index.ts index 2304a319e..2c1283958 100644 --- a/src/addon/mod/resource/components/index/index.ts +++ b/src/addon/mod/resource/components/index/index.ts @@ -15,6 +15,7 @@ import { Component, Injector } from '@angular/core'; import { CoreAppProvider } from '@providers/app'; import { CoreSitesProvider } from '@providers/sites'; +import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseModuleMainResourceComponent } from '@core/course/classes/main-resource-component'; import { AddonModResourceProvider } from '../../providers/resource'; @@ -38,7 +39,8 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource constructor(injector: Injector, private resourceProvider: AddonModResourceProvider, private courseProvider: CoreCourseProvider, private appProvider: CoreAppProvider, private prefetchHandler: AddonModResourcePrefetchHandler, - private resourceHelper: AddonModResourceHelperProvider, private sitesProvider: CoreSitesProvider) { + private resourceHelper: AddonModResourceHelperProvider, private sitesProvider: CoreSitesProvider, + private utils: CoreUtilsProvider) { super(injector); } @@ -78,7 +80,7 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource // Load module contents if needed. Passing refresh is needed to force reloading contents. return this.courseProvider.loadModuleContents(this.module, this.courseId, null, false, refresh).then(() => { if (!this.module.contents || !this.module.contents.length) { - return Promise.reject(null); + return Promise.reject(this.utils.createFakeWSError('core.filenotfound', true)); } // Get the resource instance to get the latest name/description and to know if it's embedded. diff --git a/src/assets/lang/en.json b/src/assets/lang/en.json index 8fd9e1499..8e7ff5128 100644 --- a/src/assets/lang/en.json +++ b/src/assets/lang/en.json @@ -1342,6 +1342,7 @@ "core.favourites": "Starred", "core.filename": "Filename", "core.filenameexist": "File name already exists: {{$a}}", + "core.filenotfound": "File not found, sorry.", "core.fileuploader.addfiletext": "Add file", "core.fileuploader.audio": "Audio", "core.fileuploader.camera": "Camera", diff --git a/src/core/course/providers/helper.ts b/src/core/course/providers/helper.ts index aa57300b6..a79546849 100644 --- a/src/core/course/providers/helper.ts +++ b/src/core/course/providers/helper.ts @@ -528,7 +528,7 @@ export class CoreCourseHelperProvider { // Make sure that module contents are loaded. return promise.then(() => { if (!files || !files.length) { - return Promise.reject(null); + return Promise.reject(this.utils.createFakeWSError('core.filenotfound', true)); } return this.sitesProvider.getSite(siteId); diff --git a/src/lang/en.json b/src/lang/en.json index 3df5da21b..27e5e170f 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -95,6 +95,7 @@ "favourites": "Starred", "filename": "Filename", "filenameexist": "File name already exists: {{$a}}", + "filenotfound": "File not found, sorry.", "folder": "Folder", "forcepasswordchangenotice": "You must change your password to proceed.", "fulllistofcourses": "All courses", From 1846b5aabc728ad4b9307bceb52ad5cd6abbd27f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Tue, 26 Mar 2019 13:01:48 +0100 Subject: [PATCH 190/191] MOBILE-2885 lang: Include Hindi language --- src/assets/lang/hi.json | 806 ++++++++++++++++++++++++++++++++++++++++ src/config.json | 1 + 2 files changed, 807 insertions(+) create mode 100644 src/assets/lang/hi.json diff --git a/src/assets/lang/hi.json b/src/assets/lang/hi.json new file mode 100644 index 000000000..0870e0e6e --- /dev/null +++ b/src/assets/lang/hi.json @@ -0,0 +1,806 @@ +{ + "addon.badges.badgedetails": "पदक विवरण", + "addon.badges.badges": "पदक", + "addon.badges.contact": "संपर्क", + "addon.block_activitymodules.pluginname": "गतिविधियाँ", + "addon.block_myoverview.all": "सब", + "addon.block_myoverview.favourites": "तारांकित", + "addon.block_myoverview.future": "भविष्य", + "addon.block_myoverview.hiddencourses": "छिपा हुआ", + "addon.block_myoverview.inprogress": "चालू", + "addon.block_myoverview.lastaccessed": "अंतिम अक्सेस्सेड", + "addon.block_myoverview.morecourses": "अधिक पाठ्यक्रम", + "addon.block_myoverview.nocourses": "कोई पाठ्यक्रम नहीं", + "addon.block_myoverview.past": "अतीत", + "addon.block_myoverview.pluginname": "पाठ्यक्रम अवलोकन", + "addon.block_myoverview.title": "कोर्स का नाम", + "addon.block_recentlyaccessedcourses.nocourses": "कोई रीसेंट पाठ्यक्रम नहीं", + "addon.block_recentlyaccessedcourses.pluginname": "हाल ही में पहुँचा पाठ्यक्रम", + "addon.block_recentlyaccesseditems.noitems": "हाल की कोई आइटम नहीं", + "addon.block_recentlyaccesseditems.pluginname": "हाल ही में एक्सेस किए गए आइटम", + "addon.block_sitemainmenu.pluginname": "मुख्य मॅन्यु", + "addon.block_starredcourses.nocourses": "कोई तारांकित पाठ्यक्रम नहीं", + "addon.block_starredcourses.pluginname": "तारांकित पाठ्यक्रम", + "addon.block_timeline.duedate": "नियत तारीख", + "addon.block_timeline.next30days": "अगले 30 दिन", + "addon.block_timeline.next3months": "अगले 3 महीने", + "addon.block_timeline.next6months": "अगले 6 महीने", + "addon.block_timeline.next7days": "अगले 7 दिन", + "addon.block_timeline.nocoursesinprogress": "कोई इन-प्रोग्रेस कोर्सेस नहीं", + "addon.block_timeline.noevents": "कोई आगामी गतिविधियाँ नहीं बाकि", + "addon.block_timeline.overdue": "अतिदेय", + "addon.block_timeline.pluginname": "समय", + "addon.block_timeline.sortbycourses": "पाठ्यक्रमों द्वारा क्रमबद्ध करें", + "addon.block_timeline.sortbydates": "तिथियों के आधार पर छाँटें", + "addon.calendar.calendar": "कैलॅन्डर", + "addon.calendar.calendarevents": "कैलेंडर ईवेंट", + "addon.calendar.defaultnotificationtime": "डिफ़ॉल्ट सूचना समय", + "addon.calendar.errorloadevent": "ईवेंट लोड करने में त्रुटि।", + "addon.calendar.errorloadevents": "घटनाओं को लोड करने में त्रुटि।", + "addon.calendar.eventendtime": "समाप्ति समय", + "addon.calendar.eventstarttime": "आराम्भ समय", + "addon.calendar.noevents": "कोई घटना नहीं है", + "addon.competency.errornocompetenciesfound": "कोई योग्यता नहीं मिली", + "addon.competency.nocompetencies": "कोई योग्यता नहीं", + "addon.coursecompletion.complete": "पूर्ण", + "addon.coursecompletion.completecourse": "पूरा कोर्स", + "addon.coursecompletion.completed": "संपन्न", + "addon.coursecompletion.completiondate": "पूरा करने की तिथि", + "addon.coursecompletion.completionmenuitem": "समापन", + "addon.coursecompletion.couldnotloadreport": "पाठ्यक्रम पूरा होने की रिपोर्ट लोड नहीं कर सका। बाद में पुन: प्रयास करें।", + "addon.coursecompletion.coursecompletion": "पाठ्यक्रम सम्पूर्ण", + "addon.coursecompletion.criteria": "मानदंड", + "addon.coursecompletion.criteriagroup": "मानदंड समूह", + "addon.coursecompletion.criteriarequiredall": "नीचे दिए गए सभी मापदंडों आवश्यक हैं", + "addon.coursecompletion.criteriarequiredany": "नीचे दिए गए कोई भी मापदंड आवश्यक हैं", + "addon.coursecompletion.required": "ज़रूरी हैं", + "addon.coursecompletion.status": "दर्जा", + "addon.coursecompletion.viewcoursereport": "पाठ्यक्रम रिपोर्ट देखें", + "addon.files.couldnotloadfiles": "फाइलों की सूची लोड नहीं की जा सकी।", + "addon.files.emptyfilelist": "दिखाने के लिए फाइलें नहीं हैं।", + "addon.files.erroruploadnotworking": "दुर्भाग्य से वर्तमान में आपकी साइट पर फ़ाइलें अपलोड करना संभव नहीं है।", + "addon.files.files": "फ़ाइलें", + "addon.files.sitefiles": "साइट फ़ाइलें", + "addon.messageoutput_airnotifier.processorsettingsdesc": "उपकरणों को कॉन्फ़िगर करें", + "addon.messages.addcontact": "संपर्क जोड़ना", + "addon.messages.contactlistempty": "संपर्क सूची खाली है", + "addon.messages.contactname": "संपर्क नाम", + "addon.messages.contacts": "संपर्क", + "addon.messages.deletemessage": "संदेश को हटाएं", + "addon.messages.deletemessageconfirmation": "क्या आप निश्चित रूप से यह संदेश हटाना चाहते हैं? यह केवल आपके संदेश इतिहास से हटा दिया जाएगा और अभी भी उपयोगकर्ता द्वारा देखा जा सकता है जिसने संदेश भेजा या प्राप्त किया।", + "addon.messages.errordeletemessage": "संदेश हटाते समय त्रुटि।", + "addon.messages.errorwhileretrievingcontacts": "सर्वर से संपर्क पुनः प्राप्त करते समय त्रुटि।", + "addon.messages.errorwhileretrievingdiscussions": "सर्वर से चर्चाएँ प्राप्त करते समय त्रुटि।", + "addon.messages.errorwhileretrievingmessages": "सर्वर से संदेश प्राप्त करते समय त्रुटि।", + "addon.messages.errorwhileretrievingusers": "सर्वर से उपयोगकर्ताओं को पुनर्प्राप्त करते समय त्रुटि।", + "addon.messages.message": "सन्देश", + "addon.messages.messagenotsent": "संदेश नहीं भेजा गया था। बाद में पुन: प्रयास करें।", + "addon.messages.messages": "सन्देश", + "addon.messages.newmessages": "नए संदेश", + "addon.messages.nousersfound": "कोई उपयोग्कर्ता नहीं मिले", + "addon.messages.showdeletemessages": "डिलीट मैसेज दिखाओ", + "addon.messages.type_blocked": "अवरोधित", + "addon.messages.type_offline": "ऑफलाइन", + "addon.messages.type_online": "ऑनलाइन", + "addon.messages.type_search": "खोज परिणाम", + "addon.messages.type_strangers": "अन्य लोग", + "addon.messages.warningconversationmessagenotsent": "बातचीत के लिए संदेश नहीं भेजा जा सका {{conversation}}. {{error}}", + "addon.messages.warningmessagenotsent": "उपयोगकर्ता को संदेश (संदेश) नहीं भेज सका{{user}}. {{error}}", + "addon.mod_assign.acceptsubmissionstatement": "कृपया सबमिशन स्टेटमेंट स्वीकार करें।", + "addon.mod_assign.addattempt": "एक और प्रयास को अनुमति दें", + "addon.mod_assign.addnewattempt": "एक नया प्रयास जोड़ें", + "addon.mod_assign.addsubmission": "प्रस्तुति जोड़ें", + "addon.mod_assign.allowsubmissionsfromdate": "से प्रस्तुतियाँ की अनुमति", + "addon.mod_assign.assignmentisdue": "कार्य उपयुक्त है", + "addon.mod_assign.attemptreopenmethod_manual": "हाथ से", + "addon.mod_assign.cannoteditduetostatementsubmission": "आप एप्लिकेशन में कोई सबमिशन जोड़ या संपादित नहीं कर सकते क्योंकि सबमिशन स्टेटमेंट को साइट से पुनर्प्राप्त नहीं किया जा सकता था", + "addon.mod_assign.cannotgradefromapp": "कुछ ग्रेडिंग विधियों को अभी तक ऐप द्वारा समर्थित नहीं किया गया है और उन्हें संशोधित नहीं किया जा सकता है।", + "addon.mod_assign.cannotsubmitduetostatementsubmission": "आप एप्लिकेशन में एक सबमिशन नहीं कर सकते क्योंकि सबमिशन स्टेटमेंट को साइट से पुनर्प्राप्त नहीं किया जा सकता था।", + "addon.mod_assign.erroreditpluginsnotsupported": "आप एप्लिकेशन में कोई सबमिशन जोड़ या संपादित नहीं कर सकते क्योंकि कुछ प्लगइन्स अभी तक संपादन के लिए समर्थित नहीं हैं।", + "addon.mod_assign.errorshowinginformation": "सबमिशन की जानकारी प्रदर्शित नहीं की जा सकती।", + "addon.mod_assign.feedbacknotsupported": "यह फ़ीडबैक ऐप द्वारा समर्थित नहीं है और इसमें सभी जानकारी नहीं हो सकती है।", + "addon.mod_assign.gradenotsynced": "ग्रेड सिंक नहीं किया गया", + "addon.mod_assign.notallparticipantsareshown": "जिन प्रतिभागियों ने प्रस्तुत नहीं किया है, उन्हें नहीं दिखाया गया है।", + "addon.mod_assign.numwords": "{{$a}} शब्द", + "addon.mod_assign.submissionnotsupported": "यह सबमिशन ऐप द्वारा समर्थित नहीं है और इसमें सभी जानकारी नहीं हो सकती है।", + "addon.mod_assign.userwithid": "आईडी के साथ उपयोगकर्ता {{id}}", + "addon.mod_assign.warningsubmissiongrademodified": "प्रस्तुत ग्रेड को साइट पर संशोधित किया गया था।", + "addon.mod_assign.warningsubmissionmodified": "उपयोगकर्ता प्रस्तुत साइट पर संशोधित किया गया था।", + "addon.mod_assign_feedback_comments.pluginname": "प्रतिक्रिया टिप्पणी", + "addon.mod_assign_feedback_editpdf.pluginname": "एनोटेट पीडीएफ", + "addon.mod_assign_feedback_file.pluginname": "फ़ाइल प्रतिक्रिया", + "addon.mod_assign_submission_comments.pluginname": "प्रस्तुत टिप्पणी", + "addon.mod_assign_submission_file.pluginname": "फ़ाइल प्रस्तुतियाँ", + "addon.mod_chat.beep": "बीप्", + "addon.mod_chat.chatreport": "चैट सेशन", + "addon.mod_chat.currentusers": "वर्तमान यूज़र", + "addon.mod_chat.errorwhileconnecting": "चैट से कनेक्ट करते समय त्रुटि।", + "addon.mod_chat.errorwhilegettingchatdata": "चैट डेटा प्राप्त करते समय त्रुटि।", + "addon.mod_chat.errorwhilegettingchatusers": "चैट उपयोगकर्ताओं को प्राप्त करते समय त्रुटि।", + "addon.mod_chat.errorwhileretrievingmessages": "सर्वर से संदेश प्राप्त करते समय त्रुटि।", + "addon.mod_chat.errorwhilesendingmessage": "संदेश भेजते समय त्रुटि।", + "addon.mod_chat.messages": "सन्देश", + "addon.mod_chat.modulenameplural": "चैट्स्", + "addon.mod_chat.mustbeonlinetosendmessages": "आपको संदेश भेजने के लिए ऑनलाइन होना चाहिए।", + "addon.mod_chat.nomessages": "कोई सन्देश नहीं", + "addon.mod_choice.errorgetchoice": "विकल्प डेटा प्राप्त करने में त्रुटि।", + "addon.mod_choice.responsesresultgraphdescription": "{{number}}% उपयोगकर्ताओं ने विकल्प चुना: {{text}}.", + "addon.mod_choice.resultsnotsynced": "परिणामों में शामिल करने से पहले आपकी अंतिम प्रतिक्रिया को सिंक्रनाइज़ किया जाना चाहिए।", + "addon.mod_data.errorapproving": "प्रविष्टि को अस्वीकार या अस्वीकार करने में त्रुटि।", + "addon.mod_data.errordeleting": "प्रविष्टि हटाने में त्रुटि।", + "addon.mod_feedback.captchaofflinewarning": "कैप्चा के साथ प्रतिक्रिया ऑफ़लाइन पूरी नहीं हो सकती है, या यदि कॉन्फ़िगर नहीं है, या यदि सर्वर डाउन है।", + "addon.mod_feedback.feedback_submitted_offline": "इस फ़ीडबैक को बाद में सबमिट करने के लिए सहेजा गया है।", + "addon.mod_folder.emptyfilelist": "दिखाने के लिए फाइलें नहीं हैं।", + "addon.mod_forum.addanewtopic": "नया विषय जोड़िये", + "addon.mod_forum.discussion": "चर्चा", + "addon.mod_forum.errorgetforum": "फ़ोरम डेटा प्राप्त करने में त्रुटि।", + "addon.mod_forum.errorgetgroups": "समूह सेटिंग्स प्राप्त करने में त्रुटि।", + "addon.mod_forum.forumnodiscussionsyet": "इस फोरम में अभी कोई चर्चा नहीं हुई है।", + "addon.mod_forum.group": "समूह", + "addon.mod_forum.message": "सन्देश", + "addon.mod_forum.modulenameplural": "फ़ोरम", + "addon.mod_forum.numdiscussions": "{{numdiscussions}} चर्चाएँ", + "addon.mod_forum.numreplies": "{{numreplies}} उत्तर", + "addon.mod_forum.refreshdiscussions": "चर्चा को ताज़ा करें", + "addon.mod_forum.refreshposts": "पदों को ताज़ा करें", + "addon.mod_forum.reply": "उत्तर दीजिए", + "addon.mod_forum.subject": "विषय", + "addon.mod_glossary.browsemode": "प्रविष्टियां ब्राउज़ करें", + "addon.mod_glossary.byalphabet": "वर्णक्रम", + "addon.mod_glossary.byauthor": "लेखक द्वारा समूह", + "addon.mod_glossary.bycategory": "श्रेणी के अनुसार समूह", + "addon.mod_glossary.bynewestfirst": "नवीनतम पहले", + "addon.mod_glossary.byrecentlyupdated": "हाल ही में अद्यतित", + "addon.mod_glossary.bysearch": "खोज", + "addon.mod_glossary.cannoteditentry": "प्रविष्टि संपादित नहीं की जा सकती", + "addon.mod_glossary.definition": "परिभाषा", + "addon.mod_glossary.entriestobesynced": "प्रविष्ट किए जाने की प्रविष्टियाँ", + "addon.mod_glossary.entrypendingapproval": "यह प्रविष्टि लंबित अनुमोदन है।", + "addon.mod_glossary.errorloadingentries": "प्रविष्टियाँ लोड करते समय एक त्रुटि हुई।", + "addon.mod_glossary.errorloadingentry": "प्रविष्टि लोड करते समय एक त्रुटि हुई।", + "addon.mod_glossary.errorloadingglossary": "शब्दकोष लोड करते समय एक त्रुटि हुई।", + "addon.mod_glossary.noentriesfound": "कोई प्रविष्टि नहीं मिली।", + "addon.mod_glossary.searchquery": "पूछताछ कीजिए", + "addon.mod_imscp.showmoduledescription": "विवरण दिखाएं", + "addon.mod_lesson.answer": "उत्तर", + "addon.mod_lesson.errorprefetchrandombranch": "इस पाठ में एक रैंडम कंटेंट पेज पर जंप होता है। जब तक इसे वेब ब्राउज़र में शुरू नहीं किया गया है, तब तक इसे ऐप में डालने का प्रयास नहीं किया जा सकता है।", + "addon.mod_lesson.errorreviewretakenotlast": "इस प्रयास की अब समीक्षा नहीं की जा सकती है क्योंकि एक और प्रयास समाप्त हो चुका है।", + "addon.mod_lesson.finishretakeoffline": "यह प्रयास ऑफ़लाइन समाप्त हो गया था।", + "addon.mod_lesson.or": "या", + "addon.mod_lesson.question": "प्रश्न", + "addon.mod_lesson.retakefinishedinsync": "एक ऑफ़लाइन प्रयास सिंक्रनाइज़ किया गया था। क्या आप इसकी समीक्षा करना चाहते हैं?", + "addon.mod_lesson.retakelabelfull": "{{retake}}: {{grade}} {{timestart}} ({{duration}})", + "addon.mod_lesson.retakelabelshort": "{{retake}}: {{grade}} {{timestart}}", + "addon.mod_lesson.warningretakefinished": "साइट पर प्रयास समाप्त हो गया था।", + "addon.mod_lti.errorgetlti": "मॉड्यूल डेटा प्राप्त करने में त्रुटि।", + "addon.mod_lti.errorinvalidlaunchurl": "लॉन्च URL मान्य नहीं है।", + "addon.mod_lti.launchactivity": "गतिविधि का शुभारंभ करें", + "addon.mod_page.errorwhileloadingthepage": "पृष्ठ सामग्री लोड करते समय त्रुटि।", + "addon.mod_quiz.cannotsubmitquizdueto": "यह प्रश्नोत्तरी प्रयास निम्नलिखित कारणों से प्रस्तुत नहीं किया जा सकता है:", + "addon.mod_quiz.confirmcontinueoffline": "यह प्रयास {{$a}} के बाद से सिंक्रनाइज़ नहीं किया गया है। यदि आपने इस प्रयास को किसी अन्य डिवाइस में जारी रखा है, तो आप डेटा खो सकते हैं।", + "addon.mod_quiz.confirmleavequizonerror": "उत्तर सहेजते समय एक त्रुटि हुई। क्या आप वाकई क्विज़ छोड़ना चाहते हैं?", + "addon.mod_quiz.errorbehaviournotsupported": "इस क्विज़ को ऐप में डालने का प्रयास नहीं किया जा सकता क्योंकि प्रश्न व्यवहार ऐप द्वारा समर्थित नहीं है:", + "addon.mod_quiz.errordownloading": "आवश्यक डेटा डाउनलोड करने में त्रुटि।", + "addon.mod_quiz.errorgetattempt": "प्रयास डेटा प्राप्त करने में त्रुटि।", + "addon.mod_quiz.errorgetquestions": "प्रश्न प्राप्त करने में त्रुटि।", + "addon.mod_quiz.errorgetquiz": "प्रश्नोत्तरी डेटा प्राप्त करने में त्रुटि", + "addon.mod_quiz.errorparsequestions": "प्रश्न पढ़ते समय एक त्रुटि हुई। कृपया वेब ब्राउज़र में इस क्विज़ का प्रयास करें।", + "addon.mod_quiz.errorquestionsnotsupported": "इस क्विज़ को ऐप में लेने का प्रयास नहीं किया जा सकता है क्योंकि इसमें ऐप द्वारा समर्थित प्रश्न नहीं हैं:", + "addon.mod_quiz.errorrulesnotsupported": "इस क्विज़ को ऐप में लेने का प्रयास नहीं किया जा सकता क्योंकि इसमें ऐप द्वारा समर्थित नियम नहीं हैं:", + "addon.mod_quiz.errorsaveattempt": "प्रयास डेटा सहेजते समय एक त्रुटि हुई।", + "addon.mod_quiz.finishnotsynced": "समाप्त हो गया, लेकिन सिंक्रनाइज़ नहीं किया गया", + "addon.mod_quiz.opentoc": "नेविगेशन पॉपओवर खोलें", + "addon.mod_quiz.warningattemptfinished": "ऑफ़लाइन प्रयास खारिज कर दिया गया क्योंकि यह साइट पर समाप्त हो गया था या नहीं मिला।", + "addon.mod_quiz.warningdatadiscarded": "कुछ ऑफ़लाइन उत्तरों को छोड़ दिया गया क्योंकि प्रश्न ऑनलाइन संशोधित किए गए थे।", + "addon.mod_quiz.warningdatadiscardedfromfinished": "कुछ अधूरे उत्तर छोड़ दिए जाने के कारण अधूरा रह गया। कृपया अपने उत्तरों की समीक्षा करें और फिर प्रयास को फिर से शुरू करें।", + "addon.mod_resource.errorwhileloadingthecontent": "सामग्री लोड करते समय त्रुटि।", + "addon.mod_resource.openthefile": "फ़ाइल खोलें", + "addon.mod_scorm.cannotcalculategrade": "ग्रेड की गणना नहीं की जा सकी।", + "addon.mod_scorm.dataattemptshown": "यह डेटा प्रयास नंबर {{number}} का है।", + "addon.mod_scorm.errorcreateofflineattempt": "नया ऑफ़लाइन प्रयास करते समय एक त्रुटि हुई। कृपया पुन: प्रयास करें", + "addon.mod_scorm.errordownloadscorm": "SCORM डाउनलोड करने में त्रुटि:\"{{name}}\".", + "addon.mod_scorm.errorgetscorm": "SCORM डेटा प्राप्त करने में त्रुटि।", + "addon.mod_scorm.errorinvalidversion": "क्षमा करें, एप्लिकेशन केवल SCORM 1.2 का समर्थन करता है।", + "addon.mod_scorm.errornotdownloadable": "SCORM पैकेज का डाउनलोड अक्षम है। कृपया अपने साइट व्यवस्थापक से संपर्क करें।", + "addon.mod_scorm.errornovalidsco": "इस SCORM पैकेज में लोड करने के लिए एक दृश्य SCO नहीं है।", + "addon.mod_scorm.errorpackagefile": "क्षमा करें, एप्लिकेशन केवल ज़िप पैकेज का समर्थन करता है।", + "addon.mod_scorm.errorsyncscorm": "सिंक्रनाइज़ करते समय एक त्रुटि हुई। कृपया पुन: प्रयास करें।", + "addon.mod_scorm.modulenameplural": "SCORMs", + "addon.mod_scorm.offlineattemptnote": "इस प्रयास में ऐसा डेटा है जिसे सिंक्रनाइज़ नहीं किया गया है।", + "addon.mod_scorm.offlineattemptovermax": "यह प्रयास नहीं भेजा जा सकता है क्योंकि आप अधिकतम प्रयासों को पार कर चुके हैं।", + "addon.mod_scorm.passed": "उत्तीर्ण", + "addon.mod_scorm.scormstatusnotdownloaded": "यह SCORM पैकेज डाउनलोड नहीं किया गया है। इसे खोलते ही यह अपने आप डाउनलोड हो जाएगा।", + "addon.mod_scorm.scormstatusoutdated": "इस SCORM पैकेज को अंतिम डाउनलोड के बाद संशोधित किया गया है। इसे खोलते ही यह अपने आप डाउनलोड हो जाएगा।", + "addon.mod_scorm.warningofflinedatadeleted": "प्रयास {{number}} से कुछ ऑफ़लाइन डेटा को छोड़ दिया गया है क्योंकि इसे नए प्रयास के रूप में नहीं गिना जा सकता।", + "addon.mod_scorm.warningsynconlineincomplete": "कुछ प्रयास साइट के साथ सिंक्रनाइज़ नहीं किए जा सके क्योंकि अंतिम ऑनलाइन प्रयास अभी तक समाप्त नहीं हुआ है। कृपया पहले ऑनलाइन प्रयास समाप्त करें।", + "addon.mod_survey.cannotsubmitsurvey": "क्षमा करें, आपके सर्वेक्षण को सबमिट करने में एक समस्या थी। कृपया पुन: प्रयास करें।", + "addon.mod_survey.errorgetsurvey": "सर्वेक्षण डेटा प्राप्त करने में त्रुटि।", + "addon.mod_survey.results": "परिणाम", + "addon.mod_url.accessurl": "URL तक पहुँचें", + "addon.mod_url.pointingtourl": "URL जो संसाधन को इंगित करता है।", + "addon.mod_wiki.errorloadingpage": "पृष्ठ लोड करते समय एक त्रुटि हुई।", + "addon.mod_wiki.errornowikiavailable": "इस विकी के पास अभी तक कोई सामग्री नहीं है।", + "addon.mod_wiki.gowikihome": "इस विकी के पास अभी तक कोई सामग्री नहीं है।", + "addon.mod_wiki.subwiki": "उप-विकि", + "addon.mod_wiki.titleshouldnotbeempty": "शीर्षक खाली नहीं होना चाहिए", + "addon.mod_wiki.viewpage": "पृष्ठ देखें", + "addon.mod_wiki.wikipage": "विकी पेज", + "addon.mod_workshop.assessmentstrategynotsupported": "मूल्यांकन रणनीति {{$a}} समर्थित नहीं है", + "addon.mod_workshop.submissionrequiredtitle": "You need to enter a title.", + "addon.mod_workshop.warningassessmentmodified": "साइट पर सबमिशन संशोधित किया गया था।", + "addon.mod_workshop.warningsubmissionmodified": "मूल्यांकन साइट पर संशोधित किया गया था।", + "addon.notes.userwithid": "आईडी {{id}} के साथ उपयोगकर्ता", + "addon.notes.warningnotenotsent": "नोट (ओं) को निश्चित रूप से नहीं जोड़ा जा सका {{course}}. {{error}}", + "addon.notifications.errorgetnotifications": "सूचनाएं प्राप्त करने में त्रुटि।", + "addon.notifications.notifications": "सूचनाएं", + "addon.notifications.playsound": "ध्वनि खेलने", + "addon.notifications.therearentnotificationsyet": "कोई सूचना नहीं है।", + "assets.countries.AD": "एंडोरा", + "assets.countries.AE": "संयुक्त अरब Emirates", + "assets.countries.AF": "अफ़्गानिस्तान", + "assets.countries.AG": "एंटीगुआ और बारबुडा", + "assets.countries.AL": "अल्बानिया", + "assets.countries.AM": "अर्मेनिआ", + "assets.countries.AO": "अंगोला", + "assets.countries.AQ": "अन्टार्टिका", + "assets.countries.AR": "अर्जेंटीना", + "assets.countries.AS": "अमेरिकन समोआ", + "assets.countries.AT": "ऑस्ट्रिया", + "assets.countries.AU": "ऑस्ट्रेलिया", + "assets.countries.AW": "अरूबा", + "assets.countries.BB": "बार्बाडोस", + "assets.countries.BD": "बांग्लादेश", + "assets.countries.BE": "बेल्जियम", + "assets.countries.BG": "बुल्गारिया", + "assets.countries.BH": "बहरीन", + "assets.countries.BJ": "बेनिन", + "assets.countries.BM": "बर्मूडा", + "assets.countries.BN": "ब्रुनेइ दारुस्सलम", + "assets.countries.BR": "ब्राज़ील", + "assets.countries.BS": "बहामाज़", + "assets.countries.BT": "भूटान", + "assets.countries.BW": "बोट्स्वाना", + "assets.countries.BY": "बेलारूस", + "assets.countries.BZ": "बेलीज़", + "assets.countries.CA": "कनाडा", + "assets.countries.CF": "मध्य अफ्रीकी गणराज्य", + "assets.countries.CG": "कौंगो", + "assets.countries.CL": "चिली", + "assets.countries.CM": "कैमेरून", + "assets.countries.CN": "चीन", + "assets.countries.CU": "क्यूबा", + "assets.countries.CY": "साय्प्रस", + "assets.countries.CZ": "चेक गणराज्य", + "assets.countries.DE": "जर्मनी", + "assets.countries.DK": "डेन्मार्क", + "assets.countries.EH": "पश्चिमी सहारा", + "assets.countries.ES": "स्पेन", + "assets.countries.ET": "इथियोपिया", + "assets.countries.FI": "फ़िनलैंड", + "assets.countries.FJ": "फ़िजी", + "assets.countries.FR": "फ़्रांस", + "assets.countries.GB": "यूनाइटेड किंगडम", + "assets.countries.GE": "जॉर्जिया", + "assets.countries.GH": "घाना", + "assets.countries.GL": "ग्रीन्लैन्ड", + "assets.countries.GR": "ग्रीस", + "assets.countries.GT": "ग्वाटेमाला", + "assets.countries.GY": "गुयाना", + "assets.countries.HK": "हांग कांग", + "assets.countries.HN": "होंडुरस", + "assets.countries.HR": "क्रोएशिया", + "assets.countries.HT": "हैती", + "assets.countries.HU": "हंगरी", + "assets.countries.ID": "इंडोनेशिया", + "assets.countries.IE": "आयरलैंड", + "assets.countries.IL": "इज़रायल", + "assets.countries.IN": "भारत", + "assets.countries.IQ": "इराक", + "assets.countries.IT": "इटली", + "assets.countries.JP": "जापान", + "assets.countries.KZ": "कजाखस्तान", + "assets.countries.LB": "लेबनान", + "assets.countries.LI": "लिचेंस्टीन", + "assets.countries.LK": "श्रीलंका", + "assets.countries.LR": "लाइबेरिया", + "assets.countries.LT": "लिथुआनिया", + "assets.countries.LU": "लक्ज़मबर्ग", + "assets.countries.LY": "लीबिया", + "assets.countries.MA": "मोरक्को", + "assets.countries.MC": "मोनाको", + "assets.countries.MG": "मेडागास्कर", + "assets.countries.ML": "माली", + "assets.countries.MM": "म्यांमार", + "assets.countries.MN": "मंगोलिया", + "assets.countries.MU": "मॉरिशस", + "assets.countries.MV": "मालद्वीप", + "assets.countries.MX": "मेक्सिको", + "assets.countries.MY": "मलेशिया", + "assets.countries.PH": "फ़िलिपींस", + "assets.countries.PK": "पाकिस्तान", + "assets.countries.PL": "पोलैंड", + "assets.countries.PT": "पुर्तगाल", + "assets.countries.QA": "क़तर", + "assets.countries.RO": "रोमानिया", + "assets.countries.RS": "सर्बिआ", + "assets.countries.SA": "सऊदी अरब", + "assets.countries.SD": "सूडान", + "assets.countries.SE": "स्वीडन", + "assets.countries.SG": "सिंगापुर", + "assets.countries.SI": "स्लोवेनिया", + "assets.countries.SK": "स्लोवाकिया", + "assets.countries.SN": "सेनेगल", + "assets.countries.SO": "सोमालिया", + "assets.countries.TR": "टर्की", + "assets.countries.TT": "त्रिनिदाद एवं टोबेगो", + "assets.countries.TW": "ताइवान", + "assets.countries.UA": "यूक्रेन", + "assets.countries.UG": "यूगांडा", + "assets.countries.US": "अमेरिका", + "assets.countries.UY": "उरुग्वे", + "assets.countries.UZ": "उज़्बेकिस्तान", + "assets.countries.VN": "वियतनाम", + "assets.countries.YE": "यमन", + "assets.countries.ZA": "दक्षिण अफ्रीका", + "assets.countries.ZW": "ज़िम्बाब्वे", + "core.accounts": "खाते ", + "core.add": "जोड़िए", + "core.agelocationverification": "आयु और स्थान सत्यापन", + "core.ago": "{{$a}} पहले", + "core.all": "सभी", + "core.allparticipants": "सभी प्रतिभागी", + "core.android": "एंड्रॉयड", + "core.answer": "जवाब", + "core.areyousure": "आप को यकीन हैं ?", + "core.back": "वापस", + "core.cancel": "कैन्सॅल", + "core.cannotconnect": "कनेक्ट नहीं कर सकता: सत्यापित करें कि आपने URL को सही प्रकार से टाइप किया है और आपकी साइट Moodle 2.4 या बाद का उपयोग करती है।", + "core.cannotdownloadfiles": "फ़ाइल डाउनलोडिंग अक्षम है। कृपया अपने साइट व्यवस्थापक से संपर्क करें।", + "core.captureaudio": "ध्वनि रिकॉर्ड करें", + "core.capturedimage": "चित्र लिया।", + "core.captureimage": "तस्वीर ले लो", + "core.capturevideo": "वीडियो रिकॉर्ड करो", + "core.category": "वर्ग", + "core.choose": "चुनिए", + "core.choosedots": "चुनें ...", + "core.clearsearch": "स्पष्ट खोज", + "core.clicktohideshow": "विस्तार या पतन के लिए क्लिक करें", + "core.clicktoseefull": "पूर्ण सामग्री देखने के लिए क्लिक करें।", + "core.comments": "टिप्पणियां", + "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}})", + "core.completion-alt-auto-pass": "समाप्त: {{$a}} (पास ग्रेड हासिल किया गया)", + "core.completion-alt-auto-y": "समाप्त: {{$a}}", + "core.completion-alt-auto-y-override": "समाप्त: {{$a.modname}} ({{$a.overrideuser}} द्वारा निर्धारित)", + "core.completion-alt-manual-n": "समाप्त नहीं हुआ: {{$a}} समाप्त के रूप में चिह्नित करने के लिए चुनें।", + "core.completion-alt-manual-n-override": "समाप्त नहीं हुआ: {{$a.modname}} ({{$a.overrideuser}} द्वारा निर्धारित) समाप्त के रूप में चिह्नित करने के लिए चुनें।", + "core.completion-alt-manual-y": "समाप्त हो गया: {{$a}} समाप्त नहीं होने के रूप में चिह्नित करने के लिए चुनें।", + "core.completion-alt-manual-y-override": "समाप्त हो गया: {{$a.modname}} ({{$a.overrideuser}} द्वारा निर्धारित) समाप्त नहीं होने के रूप में चिह्नित करने के लिए चुनें।", + "core.confirmcanceledit": "क्या आप वाकई इस पृष्ठ को छोड़ना चाहते हैं? सभी परिवर्तन खो जाएंगे।", + "core.confirmloss": "क्या आपको यकीन है? सभी परिवर्तन खो जाएंगे।", + "core.confirmopeninbrowser": "क्या आप इसे वेब ब्राउज़र में खोलना चाहते हैं?", + "core.considereddigitalminor": "इस साइट पर खाता बनाने के लिए आप बहुत छोटे हैं।", + "core.content": "सामग्री", + "core.contenteditingsynced": "आपके द्वारा संपादित की जा रही सामग्री सिंक हो गई है।", + "core.contentlinks.chooseaccount": "खाता चुनें", + "core.contentlinks.chooseaccounttoopenlink": "लिंक खोलने के लिए एक खाता चुनें।", + "core.contentlinks.confirmurlothersite": "यह लिंक दूसरी साइट का है। क्या आप इसे खोलना चाहते हैं?", + "core.contentlinks.errornoactions": "इस लिंक के साथ प्रदर्शन करने के लिए कोई क्रिया नहीं मिली।", + "core.contentlinks.errornosites": "इस लिंक को संभालने के लिए कोई साइट नहीं मिली।", + "core.continue": "जारी रखें", + "core.copiedtoclipboard": "क्लिपबोर्ड पर पाठ कॉपी किया गया", + "core.course": "कोर्स", + "core.course.activitydisabled": "आपके संगठन ने इस गतिविधि को मोबाइल ऐप में अक्षम कर दिया है।", + "core.course.activitynotyetviewableremoteaddon": "आपके संगठन ने एक प्लगइन स्थापित किया है जो अभी तक समर्थित नहीं है।", + "core.course.activitynotyetviewablesiteupgradeneeded": "आपके संगठन की Moodle स्थापना को अद्यतन करने की आवश्यकता है।", + "core.course.allsections": "सभी वर्गों", + "core.course.askadmintosupport": "साइट व्यवस्थापक से संपर्क करें और उन्हें बताएं कि आप इस गतिविधि का उपयोग Moodle मोबाइल ऐप के साथ करना चाहते हैं।", + "core.course.confirmdeletemodulefiles": "क्या आप वाकई इन फ़ाइलों को हटाना चाहते हैं?", + "core.course.confirmdownload": "आप {{size}} डाउनलोड करने वाले हैं। क्या तुम वाकई जारी रखना चाहते हो?", + "core.course.confirmdownloadunknownsize": "डाउनलोड के आकार की गणना करना संभव नहीं था। क्या तुम वाकई जारी रखना चाहते हो?", + "core.course.confirmpartialdownloadsize": "आप कम से कम {{size}} डाउनलोड करने वाले हैं। क्या तुम वाकई जारी रखना चाहते हो?", + "core.course.contents": "अंतर्वस्तु", + "core.course.couldnotloadsectioncontent": "अनुभाग सामग्री लोड नहीं की जा सकी। बाद में पुन: प्रयास करें।", + "core.course.couldnotloadsections": "अनुभागों को लोड नहीं किया जा सका। बाद में पुन: प्रयास करें।", + "core.course.coursesummary": "पाठ्यक्रम सारांश", + "core.course.errordownloadingcourse": "पाठ्यक्रम डाउनलोड करने में त्रुटि।", + "core.course.errordownloadingsection": "अनुभाग डाउनलोड करने में त्रुटि।", + "core.course.errorgetmodule": "गतिविधि डेटा प्राप्त करने में त्रुटि।", + "core.course.hiddenfromstudents": "छात्रों से छिपा हुआ", + "core.course.hiddenoncoursepage": "उपलब्ध है लेकिन पाठ्यक्रम पृष्ठ पर नहीं दिखाया गया है", + "core.course.manualcompletionnotsynced": "मैनुअल पूरा नहीं सिंक्रनाइज़ किया गया", + "core.course.nocontentavailable": "फिलहाल कोई सामग्री उपलब्ध नहीं है।", + "core.course.refreshcourse": "रिफ्रेश कोर्स", + "core.course.sections": "अनुभागें", + "core.course.useactivityonbrowser": "आप अभी भी अपने डिवाइस के वेब ब्राउज़र का उपयोग करके इसका उपयोग कर सकते हैं।", + "core.course.warningmanualcompletionmodified": "एक गतिविधि के मैनुअल पूरा होने को साइट पर संशोधित किया गया था।", + "core.course.warningofflinemanualcompletiondeleted": "निश्चित रूप से '{{name}}' के कुछ ऑफ़लाइन मैनुअल को हटा दिया गया है। {{error}}", + "core.coursedetails": "पाठ्यक्रम विवरण", + "core.courses.addtofavourites": "इस पाठ्यक्रम को स्टार दें", + "core.courses.allowguests": "यह कोर्स अतिथि यूज़र्स को प्रवेश करने देता है", + "core.courses.availablecourses": "उपलब्ध पाठ्यक्रम", + "core.courses.cannotretrievemorecategories": "स्तर {{$ a}} से अधिक गहरी श्रेणियों को पुनर्प्राप्त नहीं किया जा सकता है।", + "core.courses.categories": "कोर्स वर्ग", + "core.courses.confirmselfenrol": "क्या आप वाकई इस पाठ्यक्रम में स्वयं को नामांकित करना चाहते हैं?", + "core.courses.courses": "पाठ्यक्रम", + "core.courses.downloadcourses": "पाठ्यक्रम डाउनलोड करें", + "core.courses.enrolme": "मेरे दाखिला लिया", + "core.courses.errorloadcategories": "श्रेणियां लोड करते समय एक त्रुटि हुई।", + "core.courses.errorloadcourses": "पाठ्यक्रम लोड करते समय एक त्रुटि हुई", + "core.courses.errorsearching": "खोज करते समय एक त्रुटि हुई।", + "core.courses.errorselfenrol": "स्वयं नामांकन करते समय एक त्रुटि हुई।", + "core.courses.filtermycourses": "मेरे पाठ्यक्रमों को फ़िल्टर करें", + "core.courses.hidecourse": "व्यू से छिपाना", + "core.courses.mycourses": "मेरे कोर्सस", + "core.courses.nocoursesyet": "इस वर्ग में कोई कोर्स उपलब्ध नहीं है", + "core.courses.notenrollable": "आप इस पाठ्यक्रम में अपना नामांकन नहीं कर सकते।", + "core.courses.password": "नामांकन की कुंजी", + "core.courses.removefromfavourites": "इस कोर्स को अनस्टार करें", + "core.courses.search": "खोज", + "core.courses.searchcourses": "कोर्स की खोज करें", + "core.courses.searchcoursesadvice": "आप अतिथि के रूप में उपयोग करने के लिए पाठ्यक्रम खोजने के लिए खोज पाठ्यक्रम बटन का उपयोग कर सकते हैं या उन पाठ्यक्रमों में खुद को नामांकित कर सकते हैं जो इसे अनुमति देते हैं।", + "core.courses.selfenrolment": "स्व नामांकन करना", + "core.courses.show": "इस कोर्स को दिखाएं", + "core.courses.totalcoursesearchresults": "कुल पाठ्यक्रम: {{$a}}", + "core.currentdevice": "वर्तमान डिवाइस", + "core.datastoredoffline": "डिवाइस में डेटा संग्रहीत है क्योंकि इसे भेजा नहीं जा सका है। इसे बाद में स्वचालित रूप से भेजा जाएगा।", + "core.date": "तारीख", + "core.day": "दिन", + "core.days": "दिन", + "core.decsep": ".", + "core.delete": "डिलीट", + "core.deletedoffline": "ऑफ़लाइन हटा दिया गया", + "core.deleting": "हटाया जा रहा है", + "core.description": "विवरण", + "core.dfdaymonthyear": "MM-DD-YYYY", + "core.dfdayweekmonth": "ddd, D MMM", + "core.dffulldate": "dddd, D MMMM YYYY h[:]mm A", + "core.dflastweekdate": "ddd", + "core.dfmediumdate": "LLL", + "core.dftimedate": "h[:]mm A", + "core.digitalminor": "डिजिटल नाबालिग", + "core.digitalminor_desc": "इस साइट पर खाता बनाने के लिए कृपया अपने माता-पिता / अभिभावक को निम्नलिखित व्यक्ति से संपर्क करें।", + "core.discard": "छोड़ना", + "core.dismiss": "खारिज", + "core.download": "डाउनलोड", + "core.downloading": "डाउनलोड कर रहा है", + "core.edit": " का सम्पादन कीजिए", + "core.emptysplit": "यदि बाएं पैनल खाली है या लोड हो रहा है तो यह पृष्ठ रिक्त दिखाई देगा", + "core.error": "गलती", + "core.errorchangecompletion": "पूर्ण स्थिति बदलते समय एक त्रुटि हुई। कृपया पुन: प्रयास करें।", + "core.errordeletefile": "फ़ाइल को हटाने में त्रुटि। कृपया पुन: प्रयास करें।", + "core.errordownloading": "फ़ाइल डाउनलोड करने में त्रुटि।", + "core.errordownloadingsomefiles": "फ़ाइलों को डाउनलोड करने में त्रुटि। कुछ फाइलें गायब हो सकती हैं।", + "core.errorfileexistssamename": "इस नाम की एक फ़ाइल पहले से मौजूद है।", + "core.errorinvalidform": "फॉर्म में अमान्य डेटा है। कृपया जांचें कि सभी आवश्यक फ़ील्ड भरे हुए हैं और डेटा मान्य है।", + "core.errorinvalidresponse": "अमान्य प्रतिसाद प्राप्त हुआ। यदि त्रुटि बनी रहती है, तो कृपया अपने साइट व्यवस्थापक से संपर्क करें।", + "core.errorloadingcontent": "सामग्री लोड करने में त्रुटि।", + "core.errorofflinedisabled": "ऑफ़लाइन ब्राउज़िंग आपकी साइट पर अक्षम है। ऐप का उपयोग करने के लिए आपको इंटरनेट से जुड़ा होना चाहिए।", + "core.erroropenfilenoapp": "फ़ाइल खोलने में त्रुटि: इस प्रकार की फ़ाइल को खोलने के लिए कोई ऐप नहीं मिला।", + "core.erroropenfilenoextension": "फ़ाइल खोलने में त्रुटि: फ़ाइल में एक्सटेंशन नहीं है।", + "core.erroropenpopup": "यह गतिविधि पॉपअप खोलने का प्रयास कर रही है। यह एप्लिकेशन में समर्थित नहीं है।", + "core.errorrenamefile": "फ़ाइल का नाम बदलने में त्रुटि। कृपया पुन: प्रयास करें।", + "core.errorsync": "सिंक्रनाइज़ करते समय एक त्रुटि हुई। कृपया पुन: प्रयास करें।", + "core.errorsyncblocked": "यह {{$a}} अभी चल रही प्रक्रिया के कारण सिंक्रनाइज़ नहीं किया जा सकता है। बाद में पुन: प्रयास करें। यदि समस्या बनी रहती है, तो एप्लिकेशन को पुनरारंभ करने का प्रयास करें।", + "core.explanationdigitalminor": "यह जानकारी यह निर्धारित करने के लिए आवश्यक है कि आपकी उम्र सहमति की डिजिटल आयु से अधिक है या नहीं। यह वह उम्र है जब कोई व्यक्ति नियम और शर्तों और उनके डेटा को कानूनी रूप से संग्रहीत और संसाधित करने के लिए सहमति दे सकता है।", + "core.filenameexist": "फ़ाइल नाम पहले से मौजूद है: {{$a}}", + "core.fileuploader.audio": "ऑडियो", + "core.fileuploader.camera": "कैमरा", + "core.fileuploader.confirmuploadfile": "आप {{size}} अपलोड करने वाले हैं। क्या तुम वाकई जारी रखना चाहते हो?", + "core.fileuploader.confirmuploadunknownsize": "अपलोड के आकार की गणना करना संभव नहीं था। क्या तुम वाकई जारी रखना चाहते हो?", + "core.fileuploader.errorcapturingaudio": "ऑडियो कैप्चर करने में त्रुटि।", + "core.fileuploader.errorcapturingimage": "छवि कैप्चर करने में त्रुटि", + "core.fileuploader.errorcapturingvideo": "वीडियो कैप्चर करने में त्रुटि।", + "core.fileuploader.errorgettingimagealbum": "एल्बम से छवि प्राप्त करने में त्रुटि।", + "core.fileuploader.errormustbeonlinetoupload": "फाइल अपलोड करने के लिए आपको ऑनलाइन रहना होगा।", + "core.fileuploader.errornoapp": "इस क्रिया को करने के लिए आपके पास कोई ऐप इंस्टॉल नहीं है।", + "core.fileuploader.errorreadingfile": "फ़ाइल पढ़ने में त्रुटि।", + "core.fileuploader.errorwhileuploading": "फ़ाइल अपलोड के दौरान एक त्रुटि हुई।", + "core.fileuploader.file": "फ़ाइल", + "core.fileuploader.filesofthesetypes": "स्वीकृत फ़ाइल प्रकार:", + "core.fileuploader.fileuploaded": "फ़ाइल सफलतापूर्वक अपलोड कर दी गई थी।", + "core.fileuploader.maxbytesfile": "फ़ाइल {{$a.file}} बहुत बड़ी है। आपके द्वारा अपलोड किया जाने वाला अधिकतम आकार {{$a.size}} है।", + "core.fileuploader.photoalbums": "तस्वीर चित्राधार", + "core.fileuploader.readingfile": "फाइल पढ़ना", + "core.fileuploader.readingfileperc": "फ़ाइल पढ़ना: {{$a}}%", + "core.fileuploader.selectafile": "किसी फाइल का चयन करें", + "core.fileuploader.uploadafile": "एक फाइल अपलोड करें", + "core.fileuploader.uploading": "अपलोड हो रहा है", + "core.fileuploader.uploadingperc": "अपलोड करना: {{$a}}%", + "core.fileuploader.video": "वीडियो", + "core.folder": "फोल्डर", + "core.forcepasswordchangenotice": "आगे बढ़ने के लिए आपको अपना पासवर्ड बदलना होगा।", + "core.fulllistofcourses": "सभी कोर्सस", + "core.fullnameandsitename": "{{fullname}} ({{sitename}})", + "core.grades.average": "औसत", + "core.groupsseparate": "अलग ग्रूप्स", + "core.groupsvisible": "अलग ग्रूप्स", + "core.hasdatatosync": "इस {{$a}} समकालित होने के लिए ऑफ़लाइन डेटा है।", + "core.help": "सहायता", + "core.hide": "छिपादो", + "core.hour": "घंटा", + "core.hours": "घंट", + "core.humanreadablesize": "{{size}} {{unit}}", + "core.image": "छवि", + "core.imageviewer": "छवि दर्शक", + "core.info": "सूचना", + "core.ios": "आईओएस", + "core.lastaccess": "पिछ्ला आगमन", + "core.lastdownloaded": "अंतिम बार डाउनलोड किया गया", + "core.lastmodified": "पिछ्ला सुधार", + "core.lastsync": "अंतिम तुल्यकालन", + "core.list": "सूची", + "core.listsep": ";", + "core.loadmore": "और लोड करें", + "core.location": "स्थान", + "core.login.authenticating": "प्रमाणित कर रहा है", + "core.login.cancel": "कैन्सॅल", + "core.login.checksiteversion": "जांचें कि आपकी साइट Moodle 2.4 या बाद का उपयोग करती है।", + "core.login.confirmdeletesite": "क्या आप वाकई {{sitename}} साइट को हटाना चाहते हैं?", + "core.login.connect": "जुडिये!", + "core.login.connecttomoodle": "मूडल से कनेक्ट करें", + "core.login.contactyouradministrator": "आगे की मदद के लिए अपनी साइट के व्यवस्थापक से संपर्क करें।", + "core.login.contactyouradministratorissue": "कृपया निम्न समस्या की जाँच करने के लिए अपने साइट व्यवस्थापक से पूछें: {{$a}}", + "core.login.createaccount": "मेरा नया खाता बनाइए", + "core.login.createuserandpass": "लॉगिन करने के लिए यूज़रनेम और पास्वर्ड बनाइए", + "core.login.credentialsdescription": "कृपया लॉग इन करने के लिए अपना उपयोगकर्ता नाम और पासवर्ड प्रदान करें।", + "core.login.emailconfirmsent": "

आपके पते {{$a}}

पर एक ईमेल भेजा जाना चाहिए था\n

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

यदि आपको कुछ कठिनाई हो रही है, तो साइट व्यवस्थापक से संपर्क करें। ", + "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": "साइट के टोकन को अपडेट करते समय एक त्रुटि हुई।", + "core.login.findyoursite": "अपनी साइट खोजें", + "core.login.firsttime": "क्या आप यहाँ पहली बार आयें हैं?", + "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": "निर्देश", + "core.login.invalidaccount": "कृपया अपना लॉगिन विवरण जांचें या साइट कॉन्फ़िगरेशन की जांच करने के लिए अपने साइट व्यवस्थापक से पूछें।", + "core.login.invalidemail": "अमान्य ईमेल पता", + "core.login.invalidmoodleversion": "अमान्य Moodle संस्करण। आवश्यक न्यूनतम संस्करण 2.4 है।", + "core.login.invalidsite": "साइट URL अमान्य है।", + "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": "लॉग-इन", + "core.login.loginbutton": "लॉग इन करें", + "core.login.logininsiterequired": "आपको ब्राउज़र विंडो में साइट पर लॉग इन करना होगा।", + "core.login.loginsteps": "नमस्ते! पूर्ण प्रवेश के लिये आपको एक मिनट का समय निकाल कर इस सा‌इट पर अपना ऍका‌उन्ट बनना होगा। प्रत्येक कोर्स के लिये एक\n\"प्रवेश की\" भी हो सकती है, जिसकी आवश्यकता आपको बाद में\nपड़ सकती है। कृपया निम्नलिखित निर्देशों का पालन कीजिये:\n\n

    \n
  1. नये ऍकाउन्ट का फ़ॉर्म भरिये।
  2. \n\n
  3. हम आपको ई-मेल भेजेंगे।
  4. \n\n
  5. आप भेजे गए ई-मेल सन्देश को पढ़ने के बाद उसमें दी गई लिंक पर क्लिक करिए।
  6. \n\n
  7. आपके ऍकाउन्ट की पुष्टि होने के बाद आप लॉग-इन कर सकेंगे।
  8. \n\n
  9. अब आप अपना मनपसन्द कोर्स चुन सकते हैं।
  10. \n\n
  11. आगर कोर्स के लिये \"प्रवेश की\" की आवश्यकता है, तो अपने\nटीचर द्वारा दी गयी \"प्रवेश की\" क उपयोग करें. आप कोर्स में प्रवेश\nपा सकेंगे।
  12. \n\n
  13. अब आप कोर्स में पूर्ण प्रवेश पा सकेंगे. अब से आपको अपने कोर्स में प्रवेश पाने के लिये केवल अपना यूज़रनेम और पासवर्ड देना गा।
  14. \n\n
", + "core.login.missingemail": "ई-मेल गायब", + "core.login.missingfirstname": "प्रथम नाम गायब", + "core.login.missinglastname": "उपनाम गायब", + "core.login.mobileservicesnotenabled": "आपकी साइट पर मोबाइल पहुंच सक्षम नहीं है। कृपया अपने साइट व्यवस्थापक से संपर्क करें यदि आपको लगता है कि इसे सक्षम किया जाना चाहिए।", + "core.login.newaccount": "नया ऍकाउन्ट", + "core.login.newsitedescription": "कृपया अपनी Moodle साइट का URL दर्ज करें। ध्यान दें कि इस ऐप के साथ काम करने के लिए इसे कॉन्फ़िगर नहीं किया जा सकता है।", + "core.login.notloggedin": "आपको लॉग इन करने की जरूरत है।", + "core.login.password": "पासवर्ड", + "core.login.passwordrequired": "पासवर्ड आवश्यक", + "core.login.problemconnectingerror": "हमें कनेक्ट करने में समस्या हो रही है", + "core.login.problemconnectingerrorcontinue": "दोबारा जांच करें कि आपने पता सही दर्ज किया है और पुनः प्रयास करें।", + "core.login.recaptchachallengeimage": "reCAPTCHA चुनौती छवि", + "core.login.recaptchaexpired": "सत्यापन समाप्त हो गया। सुरक्षा प्रश्न का उत्तर फिर से दें।", + "core.login.recaptchaincorrect": "सुरक्षा प्रश्न उत्तर गलत है।", + "core.login.reconnect": "फिर से कनेक्ट करें", + "core.login.reconnectdescription": "आपका प्रमाणीकरण टोकन अमान्य है या समाप्त हो चुका है। आपको साइट को फिर से कनेक्ट करना होगा।", + "core.login.reconnectssodescription": "आपका प्रमाणीकरण टोकन अमान्य है या समाप्त हो चुका है। आपको साइट को फिर से कनेक्ट करना होगा। आपको ब्राउज़र विंडो में साइट पर लॉग इन करना होगा।", + "core.login.searchby": "खोज से:", + "core.login.selectacountry": "एक देश को चुनिए", + "core.login.selectsite": "Please select your site:", + "core.login.signupplugindisabled": "{{$ a}} सक्षम नहीं है।", + "core.login.siteaddress": "साइट का पता", + "core.login.sitehasredirect": "आपकी साइट में कम से कम एक HTTP पुनर्निर्देशित है। ऐप पुनर्निर्देशित नहीं कर सकता है, यह समस्या हो सकती है जो ऐप को आपकी साइट से कनेक्ट करने से रोक रही है।", + "core.login.siteinmaintenance": "आपकी साइट रखरखाव मोड में है", + "core.login.sitepolicynotagreederror": "साइट नीति सहमत नहीं है।", + "core.login.siteurl": "साईट यूआरएल", + "core.login.siteurlrequired": "साइट URL की आवश्यकता है i.e http://www.yourmoodlesite.org ", + "core.login.startsignup": "नया ऍकाउन्ट बनाइये", + "core.login.stillcantconnect": "अभी भी नहीं जुड़ सकता है?", + "core.login.username": "यूज़रनेम", + "core.login.usernameoremail": "यूज़रनेम या ईमेल दर्ज करें", + "core.login.usernamerequired": "उपयोगकर्ता का नाम (आवश्यक", + "core.login.visitchangepassword": "क्या आप पासवर्ड बदलने के लिए साइट पर जाना चाहते हैं?", + "core.login.webservicesnotenabled": "आपकी साइट में वेब सेवाएँ सक्षम नहीं हैं। कृपया अपने साइट व्यवस्थापक से संपर्क करें यदि आपको लगता है कि उन्हें सक्षम होना चाहिए।", + "core.lostconnection": "आपका प्रमाणीकरण टोकन अमान्य है या समाप्त हो चुका है। आपको साइट को फिर से कनेक्ट करना होगा।", + "core.mainmenu.appsettings": "एप्लिकेशन सेटिंग", + "core.mainmenu.changesite": "साइट बदलें", + "core.mainmenu.help": "सहायता", + "core.mainmenu.logout": "लॉग ऑउट", + "core.mainmenu.website": "वेबसाइट", + "core.maxsizeandattachments": "नई फ़ाइलों के लिए अधिकतम आकार: {$ a->size}}, अधिकतम संलग्नक: {{$a.attachments}}", + "core.min": "मिनट", + "core.mins": "मिनट", + "core.mod_chat": "चैट", + "core.mod_file": "फ़ाइल", + "core.mod_forum": "फ़ोरम", + "core.mod_label": "लेबल", + "core.mod_workshop": "वर्कशॉप", + "core.more": "और", + "core.name": "नाम", + "core.networkerroriframemsg": "यह सामग्री ऑफ़लाइन उपलब्ध नहीं है। कृपया इंटरनेट से कनेक्ट करें और पुनः प्रयास करें।", + "core.networkerrormsg": "साइट से कनेक्ट करने में समस्या थी। कृपया अपने संपर्क की जांच करे और फिर से प्रयास करें।", + "core.never": "कभी नहीं", + "core.next": "अगला", + "core.no": "नहीं", + "core.nograde": "कोई ग्रेड नही", + "core.none": "कोई नहीं", + "core.nopasswordchangeforced": "आप अपना पासवर्ड बदले बिना आगे नहीं बढ़ सकते।", + "core.nopermissionerror": "क्षमा करें, लेकिन आपके पास वर्तमान में ऐसा करने की अनुमति नहीं है", + "core.noresults": "परिणाम", + "core.notapplicable": "n/a", + "core.notsent": "नहीं भेजा गया", + "core.numwords": "{{$a}} शब्द", + "core.offline": "ऑफ़लाइन", + "core.ok": "ठीक", + "core.online": "ऑनलाइन", + "core.openfullimage": "पूर्ण आकार की छवि प्रदर्शित करने के लिए यहां क्लिक करें", + "core.openinbrowser": "ब्राउज़र में खोलें", + "core.percentagenumber": "{{$a}}%", + "core.phone": "टेलीफोन", + "core.pictureof": "{{$a}} का चित्र", + "core.pulltorefresh": "रीफ़्रेश करने के लिए खींचें", + "core.question.answer": "उत्तर", + "core.question.cannotdeterminestatus": "स्थिति का निर्धारण नहीं किया जा सकता", + "core.question.complete": "पूरा", + "core.question.correct": "सही", + "core.question.errorattachmentsnotsupported": "एप्लिकेशन उत्तर देने के लिए फ़ाइलों को संलग्न करने का समर्थन नहीं करता है।", + "core.question.errorinlinefilesnotsupported": "एप्लिकेशन अभी तक इनलाइन फ़ाइलों के संपादन का समर्थन नहीं करता है।", + "core.question.errorquestionnotsupported": "यह प्रश्न प्रकार ऐप द्वारा समर्थित नहीं है: {{$a}}", + "core.question.howtodraganddrop": "चुनने के लिए टैप करें फिर ड्रॉप करने के लिए टैप करें।", + "core.question.questionmessage": "प्रश्न {{$a}}: {{$b}}", + "core.redirectingtosite": "आपको साइट पर पुनः निर्देशित किया जाएगा।", + "core.refresh": "ताज़ा करना", + "core.remove": "निकले", + "core.required": "ज़रूरी हैं", + "core.requireduserdatamissing": "इस उपयोगकर्ता के पास कुछ आवश्यक प्रोफ़ाइल डेटा का अभाव है। कृपया अपनी साइट में डेटा दर्ज करें और पुनः प्रयास करें।
{{$a}}", + "core.resources": "संसाधने", + "core.restore": "रिस्टोर", + "core.retry": "पुन: प्रयास करें", + "core.search": "खोज", + "core.searching": "खोज कर", + "core.searchresults": "खोज परिणाम", + "core.sec": "सेकेंड", + "core.secs": "सेकेंड्स", + "core.settings.about": "के बारे में", + "core.settings.appready": "ऐप तैयार है", + "core.settings.cannotsyncoffline": "ऑफ़लाइन सिंक्रनाइज़ नहीं किया जा सकता।", + "core.settings.cannotsyncwithoutwifi": "समन्‍वयित नहीं किया जा सकता है क्‍योंकि वर्तमान सेटिंग केवल वाई-फाई से कनेक्‍ट होने पर समन्‍वयित करने की अनुमति देती है। कृपया वाई-फाई नेटवर्क से कनेक्ट करें।", + "core.settings.compilationinfo": "संकलन की जानकारी", + "core.settings.cordovadevicemodel": "कॉर्डोवा डिवाइस मॉडल", + "core.settings.cordovadeviceosversion": "कॉर्डोवा डिवाइस ओएस संस्करण", + "core.settings.cordovadeviceplatform": "कॉर्डोवा डिवाइस प्लेटफॉर्म", + "core.settings.cordovadeviceuuid": "Cordova device UUID", + "core.settings.cordovaversion": "कॉर्डोवा संस्करण", + "core.settings.currentlanguage": "वर्तमान भाषा", + "core.settings.debugdisplaydescription": "यदि सक्षम किया गया है, तो त्रुटि मोडल्यूस संभव हो तो त्रुटि के बारे में अधिक डेटा प्रदर्शित करेगा।", + "core.settings.deletesitefiles": "क्या आप वाकई '{{sitename}}' साइट से डाउनलोड की गई फ़ाइलों को हटाना चाहते हैं?", + "core.settings.deletesitefilestitle": "साइट फ़ाइलें हटाएं", + "core.settings.deviceinfo": "यंत्र की जानकारी", + "core.settings.deviceos": "डिवाइस ओएस", + "core.settings.devicewebworkers": "डिवाइस वेब कार्यकर्ताओं ने समर्थन किया", + "core.settings.displayformat": "प्रारूप को प्रदर्शित करें", + "core.settings.enabledownloadsection": "डाउनलोड अनुभाग सक्षम करें", + "core.settings.enablerichtexteditor": "पाठ संपादक सक्षम करें", + "core.settings.enablerichtexteditordescription": "यदि सक्षम है, तो सामग्री दर्ज करते समय एक पाठ संपादक उपलब्ध होगा।", + "core.settings.enablesyncwifi": "वाई-फाई पर केवल तब ही सिंक करने की अनुमति दें", + "core.settings.errordeletesitefiles": "साइट फ़ाइलों को हटाने में त्रुटि।", + "core.settings.errorsyncsite": "साइट डेटा सिंक्रनाइज़ करने में त्रुटि। अपने इंटरनेट कनेक्शन की जाँच करें और पुन: प्रयास करें।", + "core.settings.estimatedfreespace": "अनुमानित खाली स्थान", + "core.settings.filesystemroot": "फ़ाइल सिस्टम रूट", + "core.settings.general": "सामान्य", + "core.settings.language": "भाषा", + "core.settings.license": "GPL लाइसेन्स", + "core.settings.localnotifavailable": "स्थानीय सूचनाएं उपलब्ध हैं", + "core.settings.locationhref": "वेब व्यू URL", + "core.settings.loggedin": "ऑनलाइन", + "core.settings.loggedoff": "ऑनलाइन नहीं", + "core.settings.navigatorlanguage": "नेविगेटर भाषा", + "core.settings.navigatoruseragent": "नेविगेटर userAgent", + "core.settings.networkstatus": "Internet connection status", + "core.settings.privacypolicy": "गोपनीयता नीति", + "core.settings.reportinbackground": "त्रुटियों की रिपोर्ट स्वचालित रूप से करें", + "core.settings.settings": "सेट्टिंग्स", + "core.settings.showdownloadoptions": "डाउनलोड विकल्प दिखाएं", + "core.settings.sites": "साईट्स", + "core.settings.spaceusage": "अंतरिक्ष उपयोग", + "core.settings.synchronization": "तुल्यकालन", + "core.settings.synchronizenow": "अब सिंक्रनाइज़ करें", + "core.settings.syncsettings": "सिंक्रनाइज़ेशन सेटिंग्स", + "core.settings.total": "कुल", + "core.settings.versioncode": "संस्करण कोड", + "core.settings.versionname": "संस्करण का नाम", + "core.settings.wificonnection": "वाईफाई कनेक्शन", + "core.sharedfiles.chooseaccountstorefile": "फ़ाइल को स्टोर करने के लिए एक खाता चुनें।", + "core.sharedfiles.chooseactionrepeatedfile": "इस नाम की एक फ़ाइल पहले से मौजूद है। क्या आप मौजूदा फ़ाइल को बदलना चाहते हैं या इसे \"{{$a}}\" नाम देना चाहते हैं?", + "core.sharedfiles.errorreceivefilenosites": "There are no sites stored. Please add a site before sharing a file with the app.", + "core.sharedfiles.nosharedfiles": "इस साइट पर कोई साझा फ़ाइलें संग्रहीत नहीं हैं।", + "core.sharedfiles.nosharedfilestoupload": "आपके पास यहां अपलोड करने के लिए कोई फाइल नहीं है। यदि आप किसी अन्य ऐप से फ़ाइल अपलोड करना चाहते हैं, तो फ़ाइल का पता लगाएं और 'ओपन' बटन पर क्लिक करें।", + "core.sharedfiles.rename": "नाम बदलें", + "core.sharedfiles.replace": "बदलने के", + "core.sharedfiles.sharedfiles": "फ़ाइलें साझा की हैं", + "core.sharedfiles.successstorefile": "फ़ाइल सफलतापूर्वक संग्रहीत की गई। अपनी निजी फ़ाइलों को अपलोड करने के लिए फ़ाइल का चयन करें या किसी गतिविधि में उपयोग करें।", + "core.show": "दिखाए", + "core.showmore": "अधिक दिखाएं...", + "core.site": "साइट", + "core.sitehome.sitenews": "साइट समाचार", + "core.sizeb": "बाइट्स", + "core.sizegb": "GB", + "core.sizekb": "KB", + "core.sizemb": "MB", + "core.sizetb": "TB", + "core.sorry": "माफ़ कीजिये...", + "core.strftimedate": "%d %B %Y", + "core.strftimedateshort": "%d %B", + "core.strftimedatetime": "%d %B %Y, %I:%M %p", + "core.strftimedatetimeshort": "%d/%m/%Y %H:%M", + "core.strftimedaydate": "%A, %d %B %Y", + "core.strftimedaydatetime": "%A, %d %B %Y, %I:%M %p", + "core.strftimedayshort": "%A, %d %B", + "core.strftimedaytime": "%a, %H:%M", + "core.strftimemonthyear": "%B %Y", + "core.strftimerecent": "%d %b, %H:%M", + "core.strftimerecentfull": "%a, %d %b %Y, %I:%M %p", + "core.strftimetime": "%I:%M %p", + "core.tablet": "Tablet", + "core.teachers": "अध्यपके", + "core.thereisdatatosync": "सिंक्रनाइज़ होने के लिए ऑफ़लाइन {{$a}} हैं।", + "core.thisdirection": "ltr", + "core.time": "समय", + "core.today": "आज", + "core.tryagain": "पुनः प्रयास करें", + "core.twoparagraphs": "{{p1}}

{{p2}}", + "core.uhoh": "उह ओह!", + "core.unexpectederror": "अप्रत्याशित त्रुटि। कृपया आवेदन को बंद करें और फिर से खोलें।", + "core.unicodenotsupported": "इस साइट पर कुछ इमोजीस समर्थित नहीं हैं। संदेश भेजे जाने पर ऐसे वर्ण हटा दिए जाएंगे।", + "core.unicodenotsupportedcleanerror": "यूनिकोड वर्णों की सफाई करते समय खाली पाठ पाया गया।", + "core.unknown": "अनजान", + "core.unlimited": "असीमित", + "core.unzipping": "अनज़िप", + "core.user.address": "पता", + "core.user.city": "शहर/गाँव", + "core.user.contact": "संपर्क करें", + "core.user.country": "देश", + "core.user.description": "विवरण", + "core.user.details": "विस्तार", + "core.user.detailsnotavailable": "इस उपयोगकर्ता का विवरण आपके लिए उपलब्ध नहीं है।", + "core.user.editingteacher": "टीचर", + "core.user.email": "ई-मेल पता", + "core.user.emailagain": "ई-मेल (दोबारा)", + "core.user.errorloaduser": "उपयोगकर्ता लोड करने में त्रुटि।", + "core.user.firstname": "प्रथम नाम", + "core.user.interests": "रुचिया", + "core.user.lastname": "सरनेम", + "core.user.newpicture": "नया चित्र", + "core.user.phone1": "टेलीफोन", + "core.user.roles": "भूमिकाएं", + "core.user.sendemail": "ईमेल", + "core.user.student": "विद्यार्थी", + "core.user.webpage": "वेब पेज", + "core.userdeleted": "यह यूज़र ऍकाउन्ट डिलीट कर दिया गया है", + "core.userdetails": "उपयोगकर्ता का विवरण", + "core.users": "उपयोगकर्ता", + "core.view": "देखें", + "core.viewcode": "कोड देखें", + "core.vieweditor": "संपादक देखें", + "core.viewembeddedcontent": "एम्बेडेड सामग्री देखें", + "core.viewprofile": "प्रोफ़ाइल देखें", + "core.warningofflinedatadeleted": "{{componenet}} '{{name}}' से ऑफ़लाइन डेटा हटा दिया गया है। {{error}}", + "core.whoops": "Oops!", + "core.whyisthishappening": "ये क्यों हो रहा है?", + "core.windowsphone": "विंडोज फ़ोन", + "core.wsfunctionnotavailable": "वेब सेवा फ़ंक्शन उपलब्ध नहीं है।", + "core.year": "साल", + "core.years": "सालो", + "core.yes": "हाँ" +} \ No newline at end of file diff --git a/src/config.json b/src/config.json index 331a72b6d..c91878403 100644 --- a/src/config.json +++ b/src/config.json @@ -24,6 +24,7 @@ "fi": "Suomi", "fr": "Français", "he": "עברית", + "hi": "हिंदी", "hr": "Hrvatski", "hu": "magyar", "id": "Indonesian", From 3c1163b2e6ef848d7aff871795d71f13fc178c8d Mon Sep 17 00:00:00 2001 From: Juan Leyva Date: Fri, 29 Mar 2019 13:42:31 +0100 Subject: [PATCH 191/191] MOBILE-2915 release: Set definitive version name --- src/config.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config.json b/src/config.json index c91878403..94700c03c 100644 --- a/src/config.json +++ b/src/config.json @@ -3,7 +3,7 @@ "appname": "Moodle Mobile", "desktopappname": "Moodle Desktop", "versioncode": 3610, - "versionname": "3.6.1-dev", + "versionname": "3.6.1", "cache_expiration_time": 300000, "default_lang": "en", "languages": { @@ -71,4 +71,4 @@ "skipssoconfirmation": false, "forcedefaultlanguage": false, "privacypolicy": "https:\/\/moodle.org\/mod\/page\/view.php?id=8148" -} \ No newline at end of file +}