commit
78a92e5509
|
@ -33,7 +33,7 @@
|
||||||
<preference name="AndroidPersistentFileLocation" value="Compatibility" />
|
<preference name="AndroidPersistentFileLocation" value="Compatibility" />
|
||||||
<preference name="iosPersistentFileLocation" value="Compatibility" />
|
<preference name="iosPersistentFileLocation" value="Compatibility" />
|
||||||
<preference name="SplashScreen" value="screen" />
|
<preference name="SplashScreen" value="screen" />
|
||||||
<preference name="SplashScreenDelay" value="10000" />
|
<preference name="SplashScreenDelay" value="15000" />
|
||||||
<preference name="SplashMaintainAspectRatio" value="true" />
|
<preference name="SplashMaintainAspectRatio" value="true" />
|
||||||
<feature name="StatusBar">
|
<feature name="StatusBar">
|
||||||
<param name="ios-package" onload="true" value="CDVStatusBar" />
|
<param name="ios-package" onload="true" value="CDVStatusBar" />
|
||||||
|
|
|
@ -73,5 +73,8 @@ export class AddonCalendarModule {
|
||||||
'categoryid', 'groupid', 'userid', 'instance', 'modulename', 'timemodified', 'repeatid', 'visible', 'uuid',
|
'categoryid', 'groupid', 'userid', 'instance', 'modulename', 'timemodified', 'repeatid', 'visible', 'uuid',
|
||||||
'sequence', 'subscriptionid', 'notificationtime']
|
'sequence', 'subscriptionid', 'notificationtime']
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Migrate the component name.
|
||||||
|
updateManager.registerLocalNotifComponentMigration('mmaCalendarComponent', AddonCalendarProvider.COMPONENT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,9 +31,9 @@ export class AddonCalendarProvider {
|
||||||
static DAYS_INTERVAL = 30;
|
static DAYS_INTERVAL = 30;
|
||||||
static COMPONENT = 'AddonCalendarEvents';
|
static COMPONENT = 'AddonCalendarEvents';
|
||||||
static DEFAULT_NOTIFICATION_TIME_CHANGED = 'AddonCalendarDefaultNotificationTimeChangedEvent';
|
static DEFAULT_NOTIFICATION_TIME_CHANGED = 'AddonCalendarDefaultNotificationTimeChangedEvent';
|
||||||
protected DEFAULT_NOTIFICATION_TIME_SETTING = 'mmaCalendarDefaultNotifTime';
|
static DEFAULT_NOTIFICATION_TIME_SETTING = 'mmaCalendarDefaultNotifTime';
|
||||||
|
static DEFAULT_NOTIFICATION_TIME = 60;
|
||||||
protected ROOT_CACHE_KEY = 'mmaCalendar:';
|
protected ROOT_CACHE_KEY = 'mmaCalendar:';
|
||||||
protected DEFAULT_NOTIFICATION_TIME = 60;
|
|
||||||
|
|
||||||
// Variables for database.
|
// Variables for database.
|
||||||
static EVENTS_TABLE = 'addon_calendar_events';
|
static EVENTS_TABLE = 'addon_calendar_events';
|
||||||
|
@ -136,6 +136,18 @@ export class AddonCalendarProvider {
|
||||||
this.sitesProvider.createTablesFromSchema(this.tablesSchema);
|
this.sitesProvider.createTablesFromSchema(this.tablesSchema);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all calendar events from local Db.
|
||||||
|
*
|
||||||
|
* @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site.
|
||||||
|
* @return {Promise<any[]>} Promise resolved with all the events.
|
||||||
|
*/
|
||||||
|
getAllEventsFromLocalDb(siteId?: string): Promise<any[]> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return site.getDb().getAllRecords(AddonCalendarProvider.EVENTS_TABLE);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the configured default notification time.
|
* Get the configured default notification time.
|
||||||
*
|
*
|
||||||
|
@ -145,9 +157,9 @@ export class AddonCalendarProvider {
|
||||||
getDefaultNotificationTime(siteId?: string): Promise<number> {
|
getDefaultNotificationTime(siteId?: string): Promise<number> {
|
||||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||||
|
|
||||||
const key = this.DEFAULT_NOTIFICATION_TIME_SETTING + '#' + siteId;
|
const key = AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME_SETTING + '#' + siteId;
|
||||||
|
|
||||||
return this.configProvider.get(key, this.DEFAULT_NOTIFICATION_TIME);
|
return this.configProvider.get(key, AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -496,11 +508,24 @@ export class AddonCalendarProvider {
|
||||||
setDefaultNotificationTime(time: number, siteId?: string): Promise<any[]> {
|
setDefaultNotificationTime(time: number, siteId?: string): Promise<any[]> {
|
||||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||||
|
|
||||||
const key = this.DEFAULT_NOTIFICATION_TIME_SETTING + '#' + siteId;
|
const key = AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME_SETTING + '#' + siteId;
|
||||||
|
|
||||||
return this.configProvider.set(key, time);
|
return this.configProvider.set(key, time);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store an event in local DB as it is.
|
||||||
|
*
|
||||||
|
* @param {any} event Event to store.
|
||||||
|
* @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when stored.
|
||||||
|
*/
|
||||||
|
storeEventInLocalDb(event: any, siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return site.getDb().insertRecord(AddonCalendarProvider.EVENTS_TABLE, event);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Store events in local DB.
|
* Store events in local DB.
|
||||||
*
|
*
|
||||||
|
|
|
@ -131,5 +131,9 @@ export class AddonMessagesModule {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Migrate the component name.
|
||||||
|
updateManager.registerLocalNotifComponentMigration('mmaMessagesPushSimulation',
|
||||||
|
AddonMessagesProvider.PUSH_SIMULATION_COMPONENT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,9 +15,7 @@
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-label stacked>{{ 'addon.mod_glossary.definition' | translate }}</ion-label>
|
<ion-label stacked>{{ 'addon.mod_glossary.definition' | translate }}</ion-label>
|
||||||
<core-rich-text-editor item-content [control]="definitionControl" (contentChanged)="onDefinitionChange($event)" [placeholder]="'addon.mod_glossary.definition' | translate" name="addon_mod_glossary_edit"></core-rich-text-editor>
|
<core-rich-text-editor item-content [control]="definitionControl" (contentChanged)="onDefinitionChange($event)" [placeholder]="'addon.mod_glossary.definition' | translate" name="addon_mod_glossary_edit" [component]="component" [componentId]="glossary.cmid"></core-rich-text-editor>
|
||||||
<!-- @todo: Attributes that were passed to RTE in Ionic 1 but now they aren't supported yet:
|
|
||||||
[component]="component" [componentId]="glossary.cmid" -->
|
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item *ngIf="categories.length > 0">
|
<ion-item *ngIf="categories.length > 0">
|
||||||
<ion-label stacked id="addon-mod-glossary-categories-label">{{ 'addon.mod_glossary.categories' | translate }}</ion-label>
|
<ion-label stacked id="addon-mod-glossary-categories-label">{{ 'addon.mod_glossary.categories' | translate }}</ion-label>
|
||||||
|
|
|
@ -37,7 +37,7 @@
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<core-show-password item-content [name]="'password'">
|
<core-show-password item-content [name]="'password'">
|
||||||
<ion-label>{{ 'addon.mod_lesson.enterpassword' | translate }}</ion-label>
|
<ion-label>{{ 'addon.mod_lesson.enterpassword' | translate }}</ion-label>
|
||||||
<ion-input name="password" type="password" placeholder="{{ 'core.login.password' | translate }}" core-auto-focus #passwordinput></ion-input>
|
<ion-input name="password" type="password" placeholder="{{ 'core.login.password' | translate }}" [core-auto-focus] #passwordinput></ion-input>
|
||||||
</core-show-password>
|
</core-show-password>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<core-show-password item-content [name]="'password'">
|
<core-show-password item-content [name]="'password'">
|
||||||
<ion-label>{{ 'addon.mod_lesson.enterpassword' | translate }}</ion-label>
|
<ion-label>{{ 'addon.mod_lesson.enterpassword' | translate }}</ion-label>
|
||||||
<ion-input name="password" type="password" placeholder="{{ 'core.login.password' | translate }}" core-auto-focus #passwordinput></ion-input>
|
<ion-input name="password" type="password" placeholder="{{ 'core.login.password' | translate }}" [core-auto-focus] #passwordinput></ion-input>
|
||||||
</core-show-password>
|
</core-show-password>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
|
|
|
@ -20,7 +20,9 @@
|
||||||
<core-rich-text-editor item-content [control]="contentControl" [placeholder]="'core.content' | translate" name="wiki_page_content" [component]="component" [componentId]="componentId"></core-rich-text-editor>
|
<core-rich-text-editor item-content [control]="contentControl" [placeholder]="'core.content' | translate" name="wiki_page_content" [component]="component" [componentId]="componentId"></core-rich-text-editor>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
||||||
<ion-badge color="danger" *ngIf="wrongVersionLock" item-end>{{ 'addon.mod_wiki.wrongversionlock' | translate }}</ion-badge> <!-- @todo: Check this. -->
|
<ion-item *ngIf="wrongVersionLock" text-center class="addon-mod_wiki-wrongversionlock" >
|
||||||
|
<ion-badge color="danger" padding>{{ 'addon.mod_wiki.wrongversionlock' | translate }}</ion-badge>
|
||||||
|
</ion-item>
|
||||||
</form>
|
</form>
|
||||||
</core-loading>
|
</core-loading>
|
||||||
</ion-content>
|
</ion-content>
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
page-addon-mod-wiki-edit {
|
||||||
|
.addon-mod_wiki-wrongversionlock .label {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -83,5 +83,8 @@ export class AddonPushNotificationsModule {
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Migrate the component name.
|
||||||
|
updateManager.registerLocalNotifComponentMigration('mmaPushNotifications', AddonPushNotificationsProvider.COMPONENT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,10 @@
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<!-- Edit. -->
|
<!-- Edit. -->
|
||||||
<ion-item *ngIf="edit && field && field.shortname" [formGroup]="form">
|
<ion-item *ngIf="edit && field && field.shortname" [formGroup]="form">
|
||||||
<ion-label [core-mark-required]="field.required">{{ field.name }}</ion-label>
|
<ion-label>
|
||||||
|
<span [core-mark-required]="field.required">{{ field.name }}</span>
|
||||||
|
<core-input-errors [control]="form.controls[field.modelName]"></core-input-errors>
|
||||||
|
</ion-label>
|
||||||
<ion-checkbox item-end [formControlName]="field.modelName">
|
<ion-checkbox item-end [formControlName]="field.modelName">
|
||||||
</ion-checkbox>
|
</ion-checkbox>
|
||||||
<core-input-errors [control]="form.controls[field.modelName]" [errorMessages]="errors"></core-input-errors>
|
|
||||||
</ion-item>
|
</ion-item>
|
|
@ -7,4 +7,5 @@
|
||||||
<ion-item *ngIf="edit && field && field.shortname" text-wrap [formGroup]="form">
|
<ion-item *ngIf="edit && field && field.shortname" text-wrap [formGroup]="form">
|
||||||
<ion-label stacked [core-mark-required]="field.required">{{ field.name }}</ion-label>
|
<ion-label stacked [core-mark-required]="field.required">{{ field.name }}</ion-label>
|
||||||
<ion-datetime [formControlName]="field.modelName" [placeholder]="'core.choosedots' | translate" [displayFormat]="field.format" [max]="field.max" [min]="field.min"></ion-datetime>
|
<ion-datetime [formControlName]="field.modelName" [placeholder]="'core.choosedots' | translate" [displayFormat]="field.format" [max]="field.max" [min]="field.min"></ion-datetime>
|
||||||
|
<core-input-errors item-content [control]="form.controls[field.modelName]"></core-input-errors>
|
||||||
</ion-item>
|
</ion-item>
|
|
@ -10,4 +10,5 @@
|
||||||
<ion-option value="">{{ 'core.choosedots' | translate }}</ion-option>
|
<ion-option value="">{{ 'core.choosedots' | translate }}</ion-option>
|
||||||
<ion-option *ngFor="let option of field.options" [value]="option">{{option}}</ion-option>
|
<ion-option *ngFor="let option of field.options" [value]="option">{{option}}</ion-option>
|
||||||
</ion-select>
|
</ion-select>
|
||||||
|
<core-input-errors item-content [control]="form.controls[field.modelName]"></core-input-errors>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
|
@ -7,4 +7,5 @@
|
||||||
<ion-item *ngIf="edit && field && field.shortname" text-wrap [formGroup]="form">
|
<ion-item *ngIf="edit && field && field.shortname" text-wrap [formGroup]="form">
|
||||||
<ion-label stacked [core-mark-required]="field.required">{{ field.name }}</ion-label>
|
<ion-label stacked [core-mark-required]="field.required">{{ field.name }}</ion-label>
|
||||||
<ion-input [type]="field.inputType" [formControlName]="field.modelName" [placeholder]="field.name" maxlength="{{field.maxlength}}"></ion-input>
|
<ion-input [type]="field.inputType" [formControlName]="field.modelName" [placeholder]="field.name" maxlength="{{field.maxlength}}"></ion-input>
|
||||||
|
<core-input-errors item-content [control]="form.controls[field.modelName]"></core-input-errors>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<!-- Edit. -->
|
<!-- Edit. -->
|
||||||
<ion-item *ngIf="edit && field && field.shortname" text-wrap [formGroup]="form">
|
<ion-item *ngIf="edit && field && field.shortname" text-wrap [formGroup]="form">
|
||||||
<ion-label stacked [core-mark-required]="field.required">{{ field.name }}</ion-label>
|
<ion-label stacked>
|
||||||
|
<span [core-mark-required]="field.required">{{ field.name }}</span>
|
||||||
|
<core-input-errors [control]="control"></core-input-errors>
|
||||||
|
</ion-label>
|
||||||
<core-rich-text-editor item-content [control]="control" [placeholder]="field.name"></core-rich-text-editor>
|
<core-rich-text-editor item-content [control]="control" [placeholder]="field.name"></core-rich-text-editor>
|
||||||
</ion-item>
|
</ion-item>
|
|
@ -15,7 +15,6 @@
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { Platform } from 'ionic-angular';
|
import { Platform } from 'ionic-angular';
|
||||||
import { StatusBar } from '@ionic-native/status-bar';
|
import { StatusBar } from '@ionic-native/status-bar';
|
||||||
import { SplashScreen } from '@ionic-native/splash-screen';
|
|
||||||
import { CoreAppProvider } from '@providers/app';
|
import { CoreAppProvider } from '@providers/app';
|
||||||
import { CoreEventsProvider } from '@providers/events';
|
import { CoreEventsProvider } from '@providers/events';
|
||||||
import { CoreLangProvider } from '@providers/lang';
|
import { CoreLangProvider } from '@providers/lang';
|
||||||
|
@ -33,7 +32,7 @@ export class MoodleMobileApp implements OnInit {
|
||||||
protected logger;
|
protected logger;
|
||||||
protected lastUrls = {};
|
protected lastUrls = {};
|
||||||
|
|
||||||
constructor(private platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen, logger: CoreLoggerProvider,
|
constructor(private platform: Platform, statusBar: StatusBar, logger: CoreLoggerProvider,
|
||||||
private eventsProvider: CoreEventsProvider, private loginHelper: CoreLoginHelperProvider,
|
private eventsProvider: CoreEventsProvider, private loginHelper: CoreLoginHelperProvider,
|
||||||
private appProvider: CoreAppProvider, private langProvider: CoreLangProvider, private sitesProvider: CoreSitesProvider) {
|
private appProvider: CoreAppProvider, private langProvider: CoreLangProvider, private sitesProvider: CoreSitesProvider) {
|
||||||
this.logger = logger.getInstance('AppComponent');
|
this.logger = logger.getInstance('AppComponent');
|
||||||
|
@ -46,8 +45,6 @@ export class MoodleMobileApp implements OnInit {
|
||||||
} else {
|
} else {
|
||||||
statusBar.styleDefault();
|
statusBar.styleDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
splashScreen.hide();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1226,13 +1226,14 @@ export class CoreSite {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (alertMessage) {
|
if (alertMessage) {
|
||||||
const alert = this.domUtils.showAlert(this.translate.instant('core.notice'), alertMessage, undefined, 3000);
|
this.domUtils.showAlert(this.translate.instant('core.notice'), alertMessage, undefined, 3000).then((alert) => {
|
||||||
alert.onDidDismiss(() => {
|
alert.onDidDismiss(() => {
|
||||||
if (inApp) {
|
if (inApp) {
|
||||||
resolve(this.utils.openInApp(url, options));
|
resolve(this.utils.openInApp(url, options));
|
||||||
} else {
|
} else {
|
||||||
resolve(this.utils.openInBrowser(url));
|
resolve(this.utils.openInBrowser(url));
|
||||||
}
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
if (inApp) {
|
if (inApp) {
|
||||||
|
|
|
@ -1,7 +1,15 @@
|
||||||
<div class="core-input-error-container" role="alert" *ngIf="(formControl && formControl.dirty && !formControl.valid) || errorText">
|
<div class="core-input-error-container" role="alert" *ngIf="(formControl && formControl.dirty && !formControl.valid) || errorText">
|
||||||
<ng-container *ngIf="formControl && formControl.dirty && !formControl.valid">
|
<ng-container *ngIf="formControl && formControl.dirty && !formControl.valid">
|
||||||
<ng-container *ngFor="let error of errorKeys">
|
<ng-container *ngFor="let error of errorKeys">
|
||||||
<div *ngIf="formControl.hasError(error)" class="core-input-error">{{errorMessages[error]}}</div>
|
<div *ngIf="formControl.hasError(error)" class="core-input-error">
|
||||||
|
<span *ngIf="errorMessages[error]">{{errorMessages[error]}}</span>
|
||||||
|
<span *ngIf="!errorMessages[error] && error == 'max' && formControl.errors.max">
|
||||||
|
{{ 'core.login.invalidvaluemax' | translate:{$a: formControl.errors.max.max} }}
|
||||||
|
</span>
|
||||||
|
<span *ngIf="!errorMessages[error] && error == 'min' && formControl.errors.min">
|
||||||
|
{{ 'core.login.invalidvaluemin' | translate:{$a: formControl.errors.min.min} }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<div *ngIf="errorText" class="core-input-error">{{ errorText }}</div>
|
<div *ngIf="errorText" class="core-input-error">{{ errorText }}</div>
|
||||||
|
|
|
@ -75,7 +75,9 @@ export class CoreInputErrorsComponent implements OnInit, OnChanges {
|
||||||
this.errorMessages.time = this.errorMessages.time || this.translate.instant('core.login.invalidtime');
|
this.errorMessages.time = this.errorMessages.time || this.translate.instant('core.login.invalidtime');
|
||||||
this.errorMessages.url = this.errorMessages.url || this.translate.instant('core.login.invalidurl');
|
this.errorMessages.url = this.errorMessages.url || this.translate.instant('core.login.invalidurl');
|
||||||
|
|
||||||
// @todo: Check how to handle min/max errors once we have a test case to use. Also, review previous errors.
|
// Set empty values by default, the default error messages will be built in the template when needed.
|
||||||
|
this.errorMessages.max = this.errorMessages.max || '';
|
||||||
|
this.errorMessages.min = this.errorMessages.min || '';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -197,6 +197,7 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy
|
||||||
} else {
|
} else {
|
||||||
// Don't emit event so our valueChanges doesn't get notified by this change.
|
// Don't emit event so our valueChanges doesn't get notified by this change.
|
||||||
this.control.setValue(this.editorElement.innerHTML, {emitEvent: false});
|
this.control.setValue(this.editorElement.innerHTML, {emitEvent: false});
|
||||||
|
this.control.markAsDirty();
|
||||||
this.textarea.value = this.editorElement.innerHTML;
|
this.textarea.value = this.editorElement.innerHTML;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -205,6 +206,7 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy
|
||||||
} else {
|
} else {
|
||||||
// Don't emit event so our valueChanges doesn't get notified by this change.
|
// Don't emit event so our valueChanges doesn't get notified by this change.
|
||||||
this.control.setValue(this.textarea.value, {emitEvent: false});
|
this.control.setValue(this.textarea.value, {emitEvent: false});
|
||||||
|
this.control.markAsDirty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -376,6 +378,11 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy
|
||||||
* Treating videos and audios in here is complex, so if a user manually adds one he won't be able to play it in the editor.
|
* Treating videos and audios in here is complex, so if a user manually adds one he won't be able to play it in the editor.
|
||||||
*/
|
*/
|
||||||
protected treatExternalContent(): void {
|
protected treatExternalContent(): void {
|
||||||
|
if (!this.sitesProvider.isLoggedIn()) {
|
||||||
|
// Only treat external content if the user is logged in.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const elements = Array.from(this.editorElement.querySelectorAll('img')),
|
const elements = Array.from(this.editorElement.querySelectorAll('img')),
|
||||||
siteId = this.sitesProvider.getCurrentSiteId(),
|
siteId = this.sitesProvider.getCurrentSiteId(),
|
||||||
canDownloadFiles = this.sitesProvider.getCurrentSite().canDownloadFiles();
|
canDownloadFiles = this.sitesProvider.getCurrentSite().canDownloadFiles();
|
||||||
|
|
|
@ -41,7 +41,6 @@ import { NavController, Nav } from 'ionic-angular';
|
||||||
templateUrl: 'core-split-view.html'
|
templateUrl: 'core-split-view.html'
|
||||||
})
|
})
|
||||||
export class CoreSplitViewComponent implements OnInit {
|
export class CoreSplitViewComponent implements OnInit {
|
||||||
// @todo Mix both panels header buttons
|
|
||||||
|
|
||||||
@ViewChild('detailNav') detailNav: Nav;
|
@ViewChild('detailNav') detailNav: Nav;
|
||||||
@Input() when?: string | boolean = 'md';
|
@Input() when?: string | boolean = 'md';
|
||||||
|
|
|
@ -83,7 +83,7 @@ import { CoreSitePluginsQuizAccessRuleComponent } from '@core/siteplugins/compon
|
||||||
import { CoreSitePluginsAssignFeedbackComponent } from '@core/siteplugins/components/assign-feedback/assign-feedback';
|
import { CoreSitePluginsAssignFeedbackComponent } from '@core/siteplugins/components/assign-feedback/assign-feedback';
|
||||||
import { CoreSitePluginsAssignSubmissionComponent } from '@core/siteplugins/components/assign-submission/assign-submission';
|
import { CoreSitePluginsAssignSubmissionComponent } from '@core/siteplugins/components/assign-submission/assign-submission';
|
||||||
|
|
||||||
// Import addon providers. Do not import database module because it causes circular dependencies. @todo workshop
|
// Import addon providers. Do not import database module because it causes circular dependencies.
|
||||||
import { ADDON_BADGES_PROVIDERS } from '@addon/badges/badges.module';
|
import { ADDON_BADGES_PROVIDERS } from '@addon/badges/badges.module';
|
||||||
import { ADDON_CALENDAR_PROVIDERS } from '@addon/calendar/calendar.module';
|
import { ADDON_CALENDAR_PROVIDERS } from '@addon/calendar/calendar.module';
|
||||||
import { ADDON_COMPETENCY_PROVIDERS } from '@addon/competency/competency.module';
|
import { ADDON_COMPETENCY_PROVIDERS } from '@addon/competency/competency.module';
|
||||||
|
|
|
@ -819,8 +819,6 @@ export class CoreCourseHelperProvider {
|
||||||
moduleInfo.sizeReadable = this.textUtils.bytesToSize(moduleSize, 2);
|
moduleInfo.sizeReadable = this.textUtils.bytesToSize(moduleSize, 2);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// @todo: Decide what to display instead of timemodified. Last check_updates?
|
|
||||||
|
|
||||||
promises.push(this.prefetchDelegate.getModuleStatus(module, courseId).then((moduleStatus) => {
|
promises.push(this.prefetchDelegate.getModuleStatus(module, courseId).then((moduleStatus) => {
|
||||||
moduleInfo.status = moduleStatus;
|
moduleInfo.status = moduleStatus;
|
||||||
switch (moduleStatus) {
|
switch (moduleStatus) {
|
||||||
|
|
|
@ -128,9 +128,11 @@
|
||||||
<p><a [href]="settings.sitepolicy" core-link capture="false">{{ 'core.login.policyagreementclick' | translate }}</a></p>
|
<p><a [href]="settings.sitepolicy" core-link capture="false">{{ 'core.login.policyagreementclick' | translate }}</a></p>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item text-wrap>
|
<ion-item text-wrap>
|
||||||
<ion-label core-mark-required="true">{{ 'core.login.policyaccept' | translate }}</ion-label>
|
<ion-label>
|
||||||
|
<span [core-mark-required]="true">{{ 'core.login.policyaccept' | translate }}</span>
|
||||||
|
<core-input-errors [control]="signupForm.controls.policyagreed" [errorMessages]="policyErrors"></core-input-errors>
|
||||||
|
</ion-label>
|
||||||
<ion-checkbox item-end name="policyagreed" formControlName="policyagreed"></ion-checkbox>
|
<ion-checkbox item-end name="policyagreed" formControlName="policyagreed"></ion-checkbox>
|
||||||
<core-input-errors [control]="signupForm.controls.policyagreed" [errorMessages]="policyErrors"></core-input-errors>
|
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</div>
|
</div>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-input type="text" name="value" placeholder="{{ 'core.login.usernameoremail' | translate }}" formControlName="value" autocapitalize="none" autocorrect="off" core-auto-focus></ion-input>
|
<ion-input type="text" name="value" placeholder="{{ 'core.login.usernameoremail' | translate }}" formControlName="value" autocapitalize="none" autocorrect="off" [core-auto-focus]></ion-input>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<button text-wrap ion-button block [disabled]="!myForm.valid">{{ 'core.courses.search' | translate }}</button>
|
<button text-wrap ion-button block [disabled]="!myForm.valid">{{ 'core.courses.search' | translate }}</button>
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
|
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { IonicPage, NavController } from 'ionic-angular';
|
import { IonicPage, NavController } from 'ionic-angular';
|
||||||
|
import { SplashScreen } from '@ionic-native/splash-screen';
|
||||||
import { CoreAppProvider } from '@providers/app';
|
import { CoreAppProvider } from '@providers/app';
|
||||||
import { CoreInitDelegate } from '@providers/init';
|
import { CoreInitDelegate } from '@providers/init';
|
||||||
import { CoreSitesProvider } from '@providers/sites';
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
|
@ -31,7 +32,8 @@ import { CoreLoginHelperProvider } from '../../providers/helper';
|
||||||
export class CoreLoginInitPage {
|
export class CoreLoginInitPage {
|
||||||
|
|
||||||
constructor(private navCtrl: NavController, private appProvider: CoreAppProvider, private initDelegate: CoreInitDelegate,
|
constructor(private navCtrl: NavController, private appProvider: CoreAppProvider, private initDelegate: CoreInitDelegate,
|
||||||
private sitesProvider: CoreSitesProvider, private loginHelper: CoreLoginHelperProvider) { }
|
private sitesProvider: CoreSitesProvider, private loginHelper: CoreLoginHelperProvider,
|
||||||
|
private splashScreen: SplashScreen) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* View loaded.
|
* View loaded.
|
||||||
|
@ -51,11 +53,11 @@ export class CoreLoginInitPage {
|
||||||
// The redirect is pointing to a site, load it.
|
// The redirect is pointing to a site, load it.
|
||||||
return this.sitesProvider.loadSite(redirectData.siteId).then(() => {
|
return this.sitesProvider.loadSite(redirectData.siteId).then(() => {
|
||||||
if (!this.loginHelper.isSiteLoggedOut(redirectData.page, redirectData.params)) {
|
if (!this.loginHelper.isSiteLoggedOut(redirectData.page, redirectData.params)) {
|
||||||
this.navCtrl.setRoot(redirectData.page, redirectData.params, { animate: false });
|
return this.navCtrl.setRoot(redirectData.page, redirectData.params, { animate: false });
|
||||||
}
|
}
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
// Site doesn't exist.
|
// Site doesn't exist.
|
||||||
this.loadPage();
|
return this.loadPage();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// No site to load, just open the state.
|
// No site to load, just open the state.
|
||||||
|
@ -64,24 +66,37 @@ export class CoreLoginInitPage {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loadPage();
|
return this.loadPage();
|
||||||
|
}).then(() => {
|
||||||
|
// If we hide the splash screen now, the init view is still seen for an instant. Wait a bit to make sure it isn't seen.
|
||||||
|
setTimeout(() => {
|
||||||
|
this.splashScreen.hide();
|
||||||
|
}, 100);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load the right page.
|
* Load the right page.
|
||||||
|
*
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
protected loadPage(): void {
|
protected loadPage(): Promise<any> {
|
||||||
if (this.sitesProvider.isLoggedIn()) {
|
if (this.sitesProvider.isLoggedIn()) {
|
||||||
if (!this.loginHelper.isSiteLoggedOut()) {
|
if (!this.loginHelper.isSiteLoggedOut()) {
|
||||||
this.loginHelper.goToSiteInitialPage();
|
// User is logged in, go to site initial page.
|
||||||
|
return this.loginHelper.goToSiteInitialPage();
|
||||||
|
} else {
|
||||||
|
// The site is marked as logged out. Logout and try again.
|
||||||
|
return this.sitesProvider.logout().then(() => {
|
||||||
|
return this.loadPage();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.sitesProvider.hasSites().then((hasSites) => {
|
return this.sitesProvider.hasSites().then((hasSites) => {
|
||||||
if (hasSites) {
|
if (hasSites) {
|
||||||
this.navCtrl.setRoot('CoreLoginSitesPage');
|
return this.navCtrl.setRoot('CoreLoginSitesPage');
|
||||||
} else {
|
} else {
|
||||||
this.loginHelper.goToAddSite(true);
|
return this.loginHelper.goToAddSite(true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@
|
||||||
<form [formGroup]="credForm" (ngSubmit)="login()">
|
<form [formGroup]="credForm" (ngSubmit)="login()">
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<core-show-password item-content [name]="'password'">
|
<core-show-password item-content [name]="'password'">
|
||||||
<ion-input class="core-ioninput-password" name="password" type="password" placeholder="{{ 'core.login.password' | translate }}" formControlName="password" core-auto-focus></ion-input>
|
<ion-input class="core-ioninput-password" name="password" type="password" placeholder="{{ 'core.login.password' | translate }}" formControlName="password" [core-auto-focus]></ion-input>
|
||||||
</core-show-password>
|
</core-show-password>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
<ion-grid>
|
<ion-grid>
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
<div *ngIf="!fixedSites">
|
<div *ngIf="!fixedSites">
|
||||||
<p padding>{{ 'core.login.newsitedescription' | translate }}</p>
|
<p padding>{{ 'core.login.newsitedescription' | translate }}</p>
|
||||||
<ion-item>
|
<ion-item>
|
||||||
<ion-input type="url" name="url" placeholder="{{ 'core.login.siteaddress' | translate }}" formControlName="siteUrl" [core-auto-focus]></ion-input>
|
<ion-input type="url" name="url" placeholder="{{ 'core.login.siteaddress' | translate }}" formControlName="siteUrl" [core-auto-focus]="showKeyboard"></ion-input>
|
||||||
</ion-item>
|
</ion-item>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component } from '@angular/core';
|
import { Component } from '@angular/core';
|
||||||
import { IonicPage, NavController, ModalController } from 'ionic-angular';
|
import { IonicPage, NavController, ModalController, NavParams } from 'ionic-angular';
|
||||||
import { CoreAppProvider } from '@providers/app';
|
import { CoreAppProvider } from '@providers/app';
|
||||||
import { CoreSitesProvider } from '@providers/sites';
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||||
|
@ -33,10 +33,14 @@ export class CoreLoginSitePage {
|
||||||
siteForm: FormGroup;
|
siteForm: FormGroup;
|
||||||
fixedSites: any[];
|
fixedSites: any[];
|
||||||
displayAsButtons = false;
|
displayAsButtons = false;
|
||||||
|
showKeyboard = false;
|
||||||
|
|
||||||
constructor(private navCtrl: NavController, fb: FormBuilder, private appProvider: CoreAppProvider,
|
constructor(navParams: NavParams, private navCtrl: NavController, fb: FormBuilder, private appProvider: CoreAppProvider,
|
||||||
private sitesProvider: CoreSitesProvider, private loginHelper: CoreLoginHelperProvider,
|
private sitesProvider: CoreSitesProvider, private loginHelper: CoreLoginHelperProvider,
|
||||||
private modalCtrl: ModalController, private domUtils: CoreDomUtilsProvider) {
|
private modalCtrl: ModalController, private domUtils: CoreDomUtilsProvider) {
|
||||||
|
|
||||||
|
this.showKeyboard = !!navParams.get('showKeyboard');
|
||||||
|
|
||||||
let url = '';
|
let url = '';
|
||||||
|
|
||||||
// Load fixed sites if they're set.
|
// Load fixed sites if they're set.
|
||||||
|
|
|
@ -67,7 +67,7 @@ export class CoreLoginSitesPage {
|
||||||
* Go to the page to add a site.
|
* Go to the page to add a site.
|
||||||
*/
|
*/
|
||||||
add(): void {
|
add(): void {
|
||||||
this.loginHelper.goToAddSite(false);
|
this.loginHelper.goToAddSite(false, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -91,7 +91,7 @@ export class CoreLoginSitesPage {
|
||||||
// If there are no sites left, go to add site.
|
// If there are no sites left, go to add site.
|
||||||
this.sitesProvider.hasSites().then((hasSites) => {
|
this.sitesProvider.hasSites().then((hasSites) => {
|
||||||
if (!hasSites) {
|
if (!hasSites) {
|
||||||
this.loginHelper.goToAddSite(true);
|
this.loginHelper.goToAddSite(true, true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
|
|
|
@ -380,9 +380,10 @@ export class CoreLoginHelperProvider {
|
||||||
* If a fixed URL is configured, go to credentials instead.
|
* If a fixed URL is configured, go to credentials instead.
|
||||||
*
|
*
|
||||||
* @param {boolean} [setRoot] True to set the new page as root, false to add it to the stack.
|
* @param {boolean} [setRoot] True to set the new page as root, false to add it to the stack.
|
||||||
|
* @param {boolean} [showKeyboard] Whether to show keyboard in the new page. Only if no fixed URL set.
|
||||||
* @return {Promise<any>} Promise resolved when done.
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
goToAddSite(setRoot?: boolean): Promise<any> {
|
goToAddSite(setRoot?: boolean, showKeyboard?: boolean): Promise<any> {
|
||||||
let pageName,
|
let pageName,
|
||||||
params;
|
params;
|
||||||
|
|
||||||
|
@ -395,6 +396,9 @@ export class CoreLoginHelperProvider {
|
||||||
params = { siteUrl: url };
|
params = { siteUrl: url };
|
||||||
} else {
|
} else {
|
||||||
pageName = 'CoreLoginSitePage';
|
pageName = 'CoreLoginSitePage';
|
||||||
|
params = {
|
||||||
|
showKeyboard: showKeyboard
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (setRoot) {
|
if (setRoot) {
|
||||||
|
@ -685,9 +689,10 @@ export class CoreLoginHelperProvider {
|
||||||
* @param {string} error Error message.
|
* @param {string} error Error message.
|
||||||
*/
|
*/
|
||||||
openChangePassword(siteUrl: string, error: string): void {
|
openChangePassword(siteUrl: string, error: string): void {
|
||||||
const alert = this.domUtils.showAlert(this.translate.instant('core.notice'), error, undefined, 3000);
|
this.domUtils.showAlert(this.translate.instant('core.notice'), error, undefined, 3000).then((alert) => {
|
||||||
alert.onDidDismiss(() => {
|
alert.onDidDismiss(() => {
|
||||||
this.utils.openInApp(siteUrl + '/login/change_password.php');
|
this.utils.openInApp(siteUrl + '/login/change_password.php');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
<core-site-plugins-plugin-content *ngIf="component && method" [component]="component" [method]="method" [args]="args" [initResult]="initResult"></core-site-plugins-plugin-content>
|
<core-site-plugins-plugin-content *ngIf="component && method" [component]="component" [method]="method" [args]="args" [initResult]="initResult" [data]="data"></core-site-plugins-plugin-content>
|
|
@ -12,7 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component, OnInit, Input, ViewChild } from '@angular/core';
|
import { Component, OnChanges, Input, ViewChild } from '@angular/core';
|
||||||
import { CoreSitePluginsProvider } from '../../providers/siteplugins';
|
import { CoreSitePluginsProvider } from '../../providers/siteplugins';
|
||||||
import { CoreSitePluginsPluginContentComponent } from '../plugin-content/plugin-content';
|
import { CoreSitePluginsPluginContentComponent } from '../plugin-content/plugin-content';
|
||||||
|
|
||||||
|
@ -23,10 +23,12 @@ import { CoreSitePluginsPluginContentComponent } from '../plugin-content/plugin-
|
||||||
selector: 'core-site-plugins-course-format',
|
selector: 'core-site-plugins-course-format',
|
||||||
templateUrl: 'core-siteplugins-course-format.html',
|
templateUrl: 'core-siteplugins-course-format.html',
|
||||||
})
|
})
|
||||||
export class CoreSitePluginsCourseFormatComponent implements OnInit {
|
export class CoreSitePluginsCourseFormatComponent implements OnChanges {
|
||||||
@Input() course: any; // The course to render.
|
@Input() course: any; // The course to render.
|
||||||
@Input() sections: any[]; // List of course sections.
|
@Input() sections: any[]; // List of course sections.
|
||||||
@Input() downloadEnabled?: boolean; // Whether the download of sections and modules is enabled.
|
@Input() downloadEnabled?: boolean; // Whether the download of sections and modules is enabled.
|
||||||
|
@Input() initialSectionId?: number; // The section to load first (by ID).
|
||||||
|
@Input() initialSectionNumber?: number; // The section to load first (by number).
|
||||||
|
|
||||||
@ViewChild(CoreSitePluginsPluginContentComponent) content: CoreSitePluginsPluginContentComponent;
|
@ViewChild(CoreSitePluginsPluginContentComponent) content: CoreSitePluginsPluginContentComponent;
|
||||||
|
|
||||||
|
@ -34,24 +36,37 @@ export class CoreSitePluginsCourseFormatComponent implements OnInit {
|
||||||
method: string;
|
method: string;
|
||||||
args: any;
|
args: any;
|
||||||
initResult: any;
|
initResult: any;
|
||||||
|
data: any;
|
||||||
|
|
||||||
constructor(protected sitePluginsProvider: CoreSitePluginsProvider) { }
|
constructor(protected sitePluginsProvider: CoreSitePluginsProvider) { }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component being initialized.
|
* Detect changes on input properties.
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnChanges(): void {
|
||||||
if (this.course && this.course.format) {
|
if (this.course && this.course.format) {
|
||||||
const handler = this.sitePluginsProvider.getSitePluginHandler(this.course.format);
|
if (!this.component) {
|
||||||
if (handler) {
|
// Initialize the data.
|
||||||
this.component = handler.plugin.component;
|
const handler = this.sitePluginsProvider.getSitePluginHandler(this.course.format);
|
||||||
this.method = handler.handlerSchema.method;
|
if (handler) {
|
||||||
this.args = {
|
this.component = handler.plugin.component;
|
||||||
courseid: this.course.id,
|
this.method = handler.handlerSchema.method;
|
||||||
downloadenabled: this.downloadEnabled
|
this.args = {
|
||||||
};
|
courseid: this.course.id,
|
||||||
this.initResult = handler.initResult;
|
downloadenabled: this.downloadEnabled
|
||||||
|
};
|
||||||
|
this.initResult = handler.initResult;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Pass input data to the component.
|
||||||
|
this.data = {
|
||||||
|
course: this.course,
|
||||||
|
sections: this.sections,
|
||||||
|
downloadEnabled: this.downloadEnabled,
|
||||||
|
initialSectionId: this.initialSectionId,
|
||||||
|
initialSectionNumber: this.initialSectionNumber
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component, OnInit, Input, Output, EventEmitter, Optional } from '@angular/core';
|
import { Component, OnInit, Input, Output, EventEmitter, Optional, DoCheck, KeyValueDiffers } from '@angular/core';
|
||||||
import { NavController } from 'ionic-angular';
|
import { NavController } from 'ionic-angular';
|
||||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||||
import { CoreSitePluginsProvider } from '../../providers/siteplugins';
|
import { CoreSitePluginsProvider } from '../../providers/siteplugins';
|
||||||
|
@ -25,11 +25,12 @@ import { Subject } from 'rxjs';
|
||||||
selector: 'core-site-plugins-plugin-content',
|
selector: 'core-site-plugins-plugin-content',
|
||||||
templateUrl: 'core-siteplugins-plugin-content.html',
|
templateUrl: 'core-siteplugins-plugin-content.html',
|
||||||
})
|
})
|
||||||
export class CoreSitePluginsPluginContentComponent implements OnInit {
|
export class CoreSitePluginsPluginContentComponent implements OnInit, DoCheck {
|
||||||
@Input() component: string;
|
@Input() component: string;
|
||||||
@Input() method: string;
|
@Input() method: string;
|
||||||
@Input() args: any;
|
@Input() args: any;
|
||||||
@Input() initResult: any; // Result of the init WS call of the handler.
|
@Input() initResult: any; // Result of the init WS call of the handler.
|
||||||
|
@Input() data: any; // Data to pass to the component.
|
||||||
@Output() onContentLoaded?: EventEmitter<boolean>; // Emits an event when the content is loaded.
|
@Output() onContentLoaded?: EventEmitter<boolean>; // Emits an event when the content is loaded.
|
||||||
@Output() onLoadingContent?: EventEmitter<boolean>; // Emits an event when starts to load the content.
|
@Output() onLoadingContent?: EventEmitter<boolean>; // Emits an event when starts to load the content.
|
||||||
|
|
||||||
|
@ -40,11 +41,14 @@ export class CoreSitePluginsPluginContentComponent implements OnInit {
|
||||||
invalidateObservable: Subject<void>; // An observable to notify observers when to invalidate data.
|
invalidateObservable: Subject<void>; // An observable to notify observers when to invalidate data.
|
||||||
jsData: any; // Data to pass to the component.
|
jsData: any; // Data to pass to the component.
|
||||||
|
|
||||||
|
protected differ: any; // To detect changes in the data input.
|
||||||
|
|
||||||
constructor(protected domUtils: CoreDomUtilsProvider, protected sitePluginsProvider: CoreSitePluginsProvider,
|
constructor(protected domUtils: CoreDomUtilsProvider, protected sitePluginsProvider: CoreSitePluginsProvider,
|
||||||
@Optional() protected navCtrl: NavController) {
|
@Optional() protected navCtrl: NavController, differs: KeyValueDiffers) {
|
||||||
this.onContentLoaded = new EventEmitter();
|
this.onContentLoaded = new EventEmitter();
|
||||||
this.onLoadingContent = new EventEmitter();
|
this.onLoadingContent = new EventEmitter();
|
||||||
this.invalidateObservable = new Subject<void>();
|
this.invalidateObservable = new Subject<void>();
|
||||||
|
this.differ = differs.find([]).create();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -54,6 +58,21 @@ export class CoreSitePluginsPluginContentComponent implements OnInit {
|
||||||
this.fetchContent();
|
this.fetchContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Detect and act upon changes that Angular can’t or won’t detect on its own (objects and arrays).
|
||||||
|
*/
|
||||||
|
ngDoCheck(): void {
|
||||||
|
if (!this.data || !this.jsData) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there's any change in the data object.
|
||||||
|
const changes = this.differ.diff(this.data);
|
||||||
|
if (changes) {
|
||||||
|
this.jsData = Object.assign(this.jsData, this.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches the content to render.
|
* Fetches the content to render.
|
||||||
*
|
*
|
||||||
|
@ -67,7 +86,9 @@ export class CoreSitePluginsPluginContentComponent implements OnInit {
|
||||||
this.content = result.templates.length ? result.templates[0].html : ''; // Load first template.
|
this.content = result.templates.length ? result.templates[0].html : ''; // Load first template.
|
||||||
this.javascript = result.javascript;
|
this.javascript = result.javascript;
|
||||||
this.otherData = result.otherdata;
|
this.otherData = result.otherdata;
|
||||||
this.jsData = this.sitePluginsProvider.createDataForJS(this.initResult, result);
|
this.data = this.data || {};
|
||||||
|
|
||||||
|
this.jsData = Object.assign(this.data, this.sitePluginsProvider.createDataForJS(this.initResult, result));
|
||||||
|
|
||||||
// Pass some methods as jsData so they can be called from the template too.
|
// Pass some methods as jsData so they can be called from the template too.
|
||||||
this.jsData.openContent = this.openContent.bind(this);
|
this.jsData.openContent = this.openContent.bind(this);
|
||||||
|
|
|
@ -187,7 +187,6 @@ export class CoreExternalContentDirective implements AfterViewInit {
|
||||||
this.logger.debug('Using URL ' + finalUrl + ' for ' + url);
|
this.logger.debug('Using URL ' + finalUrl + ' for ' + url);
|
||||||
if (tagName === 'SOURCE') {
|
if (tagName === 'SOURCE') {
|
||||||
// The browser does not catch changes in SRC, we need to add a new source.
|
// The browser does not catch changes in SRC, we need to add a new source.
|
||||||
// @todo: Check if changing src works in Android 4.4, maybe the problem was only in 4.1-4.3.
|
|
||||||
this.addSource(finalUrl);
|
this.addSource(finalUrl);
|
||||||
} else {
|
} else {
|
||||||
this.element.setAttribute(targetAttr, finalUrl);
|
this.element.setAttribute(targetAttr, finalUrl);
|
||||||
|
|
|
@ -186,7 +186,6 @@ export class CoreFormatTextDirective implements OnChanges {
|
||||||
this.element.innerHTML = ''; // Remove current contents.
|
this.element.innerHTML = ''; // Remove current contents.
|
||||||
if (this.maxHeight && div.innerHTML != '') {
|
if (this.maxHeight && div.innerHTML != '') {
|
||||||
// Move the children to the current element to be able to calculate the height.
|
// Move the children to the current element to be able to calculate the height.
|
||||||
// @todo: Display the element?
|
|
||||||
this.domUtils.moveChildren(div, this.element);
|
this.domUtils.moveChildren(div, this.element);
|
||||||
|
|
||||||
// Height cannot be calculated if the element is not shown while calculating.
|
// Height cannot be calculated if the element is not shown while calculating.
|
||||||
|
|
|
@ -90,8 +90,8 @@ export class CoreLinkDirective implements OnInit {
|
||||||
href = href.substr(1);
|
href = href.substr(1);
|
||||||
// In site links
|
// In site links
|
||||||
if (href.charAt(0) == '/') {
|
if (href.charAt(0) == '/') {
|
||||||
// @todo: Investigate how to achieve this behaviour.
|
// @todo: This cannot be achieved with push/pop navigation, location.go() doesn't update the state, only the URL.
|
||||||
// $location.url(href);
|
// In Ionic 4 the navigation will change, so maybe it can be done by then.
|
||||||
} else {
|
} else {
|
||||||
// Look for id or name.
|
// Look for id or name.
|
||||||
this.domUtils.scrollToElementBySelector(this.content, '#' + href + ', [name=\'' + href + '\']');
|
this.domUtils.scrollToElementBySelector(this.content, '#' + href + ', [name=\'' + href + '\']');
|
||||||
|
|
|
@ -47,9 +47,6 @@ export interface CoreILocalNotification extends ILocalNotification {
|
||||||
*
|
*
|
||||||
* See https://angular.io/guide/dependency-injection for more info on providers
|
* See https://angular.io/guide/dependency-injection for more info on providers
|
||||||
* and Angular DI.
|
* and Angular DI.
|
||||||
*
|
|
||||||
* @todo We might have to translate the old component name to the new one.
|
|
||||||
* Otherwise the unique ID of local notifications could change.
|
|
||||||
*/
|
*/
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CoreLocalNotificationsProvider {
|
export class CoreLocalNotificationsProvider {
|
||||||
|
@ -502,4 +499,18 @@ export class CoreLocalNotificationsProvider {
|
||||||
|
|
||||||
return this.appDB.insertRecord(this.TRIGGERED_TABLE, entry);
|
return this.appDB.insertRecord(this.TRIGGERED_TABLE, entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update a component name.
|
||||||
|
*
|
||||||
|
* @param {string} oldName The old name.
|
||||||
|
* @param {string} newName The new name.
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
updateComponentName(oldName: string, newName: string): Promise<any> {
|
||||||
|
const oldId = this.COMPONENTS_TABLE + '#' + oldName,
|
||||||
|
newId = this.COMPONENTS_TABLE + '#' + newName;
|
||||||
|
|
||||||
|
return this.appDB.updateRecords(this.COMPONENTS_TABLE, {id: newId}, {id: oldId});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,6 +22,7 @@ import { CoreLoggerProvider } from './logger';
|
||||||
import { CoreSitesProvider } from './sites';
|
import { CoreSitesProvider } from './sites';
|
||||||
import { CoreUtilsProvider } from './utils/utils';
|
import { CoreUtilsProvider } from './utils/utils';
|
||||||
import { CoreConfigConstants } from '../configconstants';
|
import { CoreConfigConstants } from '../configconstants';
|
||||||
|
import { AddonCalendarProvider } from '@addon/calendar/providers/calendar';
|
||||||
import { SQLiteDB } from '@classes/sqlitedb';
|
import { SQLiteDB } from '@classes/sqlitedb';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -71,6 +72,7 @@ export class CoreUpdateManagerProvider implements CoreInitHandler {
|
||||||
|
|
||||||
protected VERSION_APPLIED = 'version_applied';
|
protected VERSION_APPLIED = 'version_applied';
|
||||||
protected logger;
|
protected logger;
|
||||||
|
protected localNotificationsComponentsMigrate: {[old: string]: string} = {};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tables to migrate from app DB ('MoodleMobile'). Include all the core ones to decrease the dependencies.
|
* Tables to migrate from app DB ('MoodleMobile'). Include all the core ones to decrease the dependencies.
|
||||||
|
@ -323,7 +325,8 @@ export class CoreUpdateManagerProvider implements CoreInitHandler {
|
||||||
|
|
||||||
constructor(logger: CoreLoggerProvider, private configProvider: CoreConfigProvider, private sitesProvider: CoreSitesProvider,
|
constructor(logger: CoreLoggerProvider, private configProvider: CoreConfigProvider, private sitesProvider: CoreSitesProvider,
|
||||||
private filepoolProvider: CoreFilepoolProvider, private notifProvider: CoreLocalNotificationsProvider,
|
private filepoolProvider: CoreFilepoolProvider, private notifProvider: CoreLocalNotificationsProvider,
|
||||||
private utils: CoreUtilsProvider, private appProvider: CoreAppProvider) {
|
private utils: CoreUtilsProvider, private appProvider: CoreAppProvider,
|
||||||
|
private calendarProvider: AddonCalendarProvider) {
|
||||||
this.logger = logger.getInstance('CoreUpdateManagerProvider');
|
this.logger = logger.getInstance('CoreUpdateManagerProvider');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -341,6 +344,9 @@ export class CoreUpdateManagerProvider implements CoreInitHandler {
|
||||||
if (!versionApplied) {
|
if (!versionApplied) {
|
||||||
// No version applied, either the app was just installed or it's being updated from Ionic 1.
|
// No version applied, either the app was just installed or it's being updated from Ionic 1.
|
||||||
return this.migrateAllDBs().then(() => {
|
return this.migrateAllDBs().then(() => {
|
||||||
|
// Now that the DBs have been migrated, migrate the local notification components names.
|
||||||
|
return this.migrateLocalNotificationsComponents();
|
||||||
|
}).then(() => {
|
||||||
// DBs migrated, get the version applied again.
|
// DBs migrated, get the version applied again.
|
||||||
return this.configProvider.get(this.VERSION_APPLIED, 0);
|
return this.configProvider.get(this.VERSION_APPLIED, 0);
|
||||||
});
|
});
|
||||||
|
@ -358,9 +364,8 @@ export class CoreUpdateManagerProvider implements CoreInitHandler {
|
||||||
promises.push(this.setSitesConfig());
|
promises.push(this.setSitesConfig());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (versionCode >= 2018 && versionApplied < 2018 && versionApplied > 0) {
|
// In version 2018 we adapted the forum offline stores to match a new schema.
|
||||||
promises.push(this.adaptForumOfflineStores());
|
// However, due to the migration of data to SQLite we can no longer do that.
|
||||||
}
|
|
||||||
|
|
||||||
return Promise.all(promises).then(() => {
|
return Promise.all(promises).then(() => {
|
||||||
return this.configProvider.set(this.VERSION_APPLIED, versionCode);
|
return this.configProvider.set(this.VERSION_APPLIED, versionCode);
|
||||||
|
@ -410,6 +415,16 @@ export class CoreUpdateManagerProvider implements CoreInitHandler {
|
||||||
this.siteDBTables.push(table);
|
this.siteDBTables.push(table);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a migration of component name for local notifications.
|
||||||
|
*
|
||||||
|
* @param {string} oldName The old name.
|
||||||
|
* @param {string} newName The new name.
|
||||||
|
*/
|
||||||
|
registerLocalNotifComponentMigration(oldName: string, newName: string): void {
|
||||||
|
this.localNotificationsComponentsMigrate[oldName] = newName;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Migrate all DBs and tables from the old format to SQLite.
|
* Migrate all DBs and tables from the old format to SQLite.
|
||||||
*
|
*
|
||||||
|
@ -555,6 +570,30 @@ export class CoreUpdateManagerProvider implements CoreInitHandler {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Migrate local notifications components from the old nomenclature to the new one.
|
||||||
|
*
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected migrateLocalNotificationsComponents(): Promise<any> {
|
||||||
|
if (!this.notifProvider.isAvailable()) {
|
||||||
|
// Local notifications not available, nothing to do.
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
for (const oldName in this.localNotificationsComponentsMigrate) {
|
||||||
|
const newName = this.localNotificationsComponentsMigrate[oldName];
|
||||||
|
|
||||||
|
promises.push(this.notifProvider.updateComponentName(oldName, newName).catch((error) => {
|
||||||
|
this.logger.error('Error migrating local notif component from ' + oldName + ' to ' + newName + ': ', error);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calendar default notification time is configurable from version 3.2.1, and a new option "Default" is added.
|
* Calendar default notification time is configurable from version 3.2.1, and a new option "Default" is added.
|
||||||
* All events that were configured to use the fixed default time should now be configured to use "Default" option.
|
* All events that were configured to use the fixed default time should now be configured to use "Default" option.
|
||||||
|
@ -567,8 +606,27 @@ export class CoreUpdateManagerProvider implements CoreInitHandler {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
// @todo: Implement it once Calendar addon is implemented.
|
return this.sitesProvider.getSitesIds().then((siteIds) => {
|
||||||
return Promise.resolve();
|
|
||||||
|
const promises = [];
|
||||||
|
siteIds.forEach((siteId) => {
|
||||||
|
// Get stored events.
|
||||||
|
promises.push(this.calendarProvider.getAllEventsFromLocalDb(siteId).then((events) => {
|
||||||
|
const eventPromises = [];
|
||||||
|
|
||||||
|
events.forEach((event) => {
|
||||||
|
if (event.notificationtime == AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME) {
|
||||||
|
event.notificationtime = -1;
|
||||||
|
eventPromises.push(this.calendarProvider.storeEventInLocalDb(event, siteId));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(eventPromises);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -626,15 +684,4 @@ export class CoreUpdateManagerProvider implements CoreInitHandler {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The data stored for offline discussions and posts changed its format. Adapt the entries already stored.
|
|
||||||
* Since it can be slow, we'll only block migrating the db of current site, the rest will be in background.
|
|
||||||
*
|
|
||||||
* @return {Promise<any>} Promise resolved when the db is migrated.
|
|
||||||
*/
|
|
||||||
protected adaptForumOfflineStores(): Promise<any> {
|
|
||||||
// @todo: Implement it once Forum addon is implemented.
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,22 +44,6 @@ export class CoreDomUtilsProvider {
|
||||||
private platform: Platform, private configProvider: CoreConfigProvider, private urlUtils: CoreUrlUtilsProvider,
|
private platform: Platform, private configProvider: CoreConfigProvider, private urlUtils: CoreUrlUtilsProvider,
|
||||||
private modalCtrl: ModalController) { }
|
private modalCtrl: ModalController) { }
|
||||||
|
|
||||||
/**
|
|
||||||
* Wraps a message with core-format-text if the message contains HTML tags.
|
|
||||||
* @todo Finish the adaptation
|
|
||||||
*
|
|
||||||
* @param {string} message Message to wrap.
|
|
||||||
* @return {string} Result message.
|
|
||||||
*/
|
|
||||||
private addFormatTextIfNeeded(message: string): string {
|
|
||||||
// @todo
|
|
||||||
if (this.textUtils.hasHTMLTags(message)) {
|
|
||||||
return '<core-format-text watch="true">' + message + '</core-format-text>';
|
|
||||||
}
|
|
||||||
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Equivalent to element.closest(). If the browser doesn't support element.closest, it will
|
* Equivalent to element.closest(). If the browser doesn't support element.closest, it will
|
||||||
* traverse the parents to achieve the same functionality.
|
* traverse the parents to achieve the same functionality.
|
||||||
|
@ -776,24 +760,43 @@ export class CoreDomUtilsProvider {
|
||||||
* @param {string} message Message to show.
|
* @param {string} message Message to show.
|
||||||
* @param {string} [buttonText] Text of the button.
|
* @param {string} [buttonText] Text of the button.
|
||||||
* @param {number} [autocloseTime] Number of milliseconds to wait to close the modal. If not defined, modal won't be closed.
|
* @param {number} [autocloseTime] Number of milliseconds to wait to close the modal. If not defined, modal won't be closed.
|
||||||
* @return {Alert} The alert modal.
|
* @return {Promise<Alert>} Promise resolved with the alert modal.
|
||||||
*/
|
*/
|
||||||
showAlert(title: string, message: string, buttonText?: string, autocloseTime?: number): Alert {
|
showAlert(title: string, message: string, buttonText?: string, autocloseTime?: number): Promise<Alert> {
|
||||||
const alert = this.alertCtrl.create({
|
const hasHTMLTags = this.textUtils.hasHTMLTags(message);
|
||||||
title: title,
|
let promise;
|
||||||
message: this.addFormatTextIfNeeded(message), // Add format-text to handle links.
|
|
||||||
buttons: [buttonText || this.translate.instant('core.ok')]
|
|
||||||
});
|
|
||||||
|
|
||||||
alert.present();
|
if (hasHTMLTags) {
|
||||||
|
// Format the text.
|
||||||
if (autocloseTime > 0) {
|
promise = this.textUtils.formatText(message);
|
||||||
setTimeout(() => {
|
} else {
|
||||||
alert.dismiss();
|
promise = Promise.resolve(message);
|
||||||
}, autocloseTime);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return alert;
|
return promise.then((message) => {
|
||||||
|
|
||||||
|
const alert = this.alertCtrl.create({
|
||||||
|
title: title,
|
||||||
|
message: message,
|
||||||
|
buttons: [buttonText || this.translate.instant('core.ok')]
|
||||||
|
});
|
||||||
|
|
||||||
|
alert.present().then(() => {
|
||||||
|
if (hasHTMLTags) {
|
||||||
|
// Treat all anchors so they don't override the app.
|
||||||
|
const alertMessageEl: HTMLElement = alert.pageRef().nativeElement.querySelector('.alert-message');
|
||||||
|
this.treatAnchors(alertMessageEl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (autocloseTime > 0) {
|
||||||
|
setTimeout(() => {
|
||||||
|
alert.dismiss();
|
||||||
|
}, autocloseTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
return alert;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -803,9 +806,9 @@ export class CoreDomUtilsProvider {
|
||||||
* @param {string} message Message to show.
|
* @param {string} message Message to show.
|
||||||
* @param {string} [buttonText] Text of the button.
|
* @param {string} [buttonText] Text of the button.
|
||||||
* @param {number} [autocloseTime] Number of milliseconds to wait to close the modal. If not defined, modal won't be closed.
|
* @param {number} [autocloseTime] Number of milliseconds to wait to close the modal. If not defined, modal won't be closed.
|
||||||
* @return {Alert} The alert modal.
|
* @return {Promise<Alert>} Promise resolved with the alert modal.
|
||||||
*/
|
*/
|
||||||
showAlertTranslated(title: string, message: string, buttonText?: string, autocloseTime?: number): Alert {
|
showAlertTranslated(title: string, message: string, buttonText?: string, autocloseTime?: number): Promise<Alert> {
|
||||||
title = title ? this.translate.instant(title) : title;
|
title = title ? this.translate.instant(title) : title;
|
||||||
message = message ? this.translate.instant(message) : message;
|
message = message ? this.translate.instant(message) : message;
|
||||||
buttonText = buttonText ? this.translate.instant(buttonText) : buttonText;
|
buttonText = buttonText ? this.translate.instant(buttonText) : buttonText;
|
||||||
|
@ -825,30 +828,50 @@ export class CoreDomUtilsProvider {
|
||||||
*/
|
*/
|
||||||
showConfirm(message: string, title?: string, okText?: string, cancelText?: string, options?: any): Promise<void> {
|
showConfirm(message: string, title?: string, okText?: string, cancelText?: string, options?: any): Promise<void> {
|
||||||
return new Promise<void>((resolve, reject): void => {
|
return new Promise<void>((resolve, reject): void => {
|
||||||
options = options || {};
|
const hasHTMLTags = this.textUtils.hasHTMLTags(message);
|
||||||
|
let promise;
|
||||||
|
|
||||||
options.message = this.addFormatTextIfNeeded(message); // Add format-text to handle links.
|
if (hasHTMLTags) {
|
||||||
options.title = title;
|
// Format the text.
|
||||||
if (!title) {
|
promise = this.textUtils.formatText(message);
|
||||||
options.cssClass = 'core-nohead';
|
} else {
|
||||||
|
promise = Promise.resolve(message);
|
||||||
}
|
}
|
||||||
options.buttons = [
|
|
||||||
{
|
|
||||||
text: cancelText || this.translate.instant('core.cancel'),
|
|
||||||
role: 'cancel',
|
|
||||||
handler: (): void => {
|
|
||||||
reject(this.createCanceledError());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: okText || this.translate.instant('core.ok'),
|
|
||||||
handler: (): void => {
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
this.alertCtrl.create(options).present();
|
promise.then((message) => {
|
||||||
|
options = options || {};
|
||||||
|
|
||||||
|
options.message = message;
|
||||||
|
options.title = title;
|
||||||
|
if (!title) {
|
||||||
|
options.cssClass = 'core-nohead';
|
||||||
|
}
|
||||||
|
options.buttons = [
|
||||||
|
{
|
||||||
|
text: cancelText || this.translate.instant('core.cancel'),
|
||||||
|
role: 'cancel',
|
||||||
|
handler: (): void => {
|
||||||
|
reject(this.createCanceledError());
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: okText || this.translate.instant('core.ok'),
|
||||||
|
handler: (): void => {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const alert = this.alertCtrl.create(options);
|
||||||
|
|
||||||
|
alert.present().then(() => {
|
||||||
|
if (hasHTMLTags) {
|
||||||
|
// Treat all anchors so they don't override the app.
|
||||||
|
const alertMessageEl: HTMLElement = alert.pageRef().nativeElement.querySelector('.alert-message');
|
||||||
|
this.treatAnchors(alertMessageEl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -858,9 +881,9 @@ export class CoreDomUtilsProvider {
|
||||||
* @param {any} error Message to show.
|
* @param {any} error Message to show.
|
||||||
* @param {boolean} [needsTranslate] Whether the error needs to be translated.
|
* @param {boolean} [needsTranslate] Whether the error needs to be translated.
|
||||||
* @param {number} [autocloseTime] Number of milliseconds to wait to close the modal. If not defined, modal won't be closed.
|
* @param {number} [autocloseTime] Number of milliseconds to wait to close the modal. If not defined, modal won't be closed.
|
||||||
* @return {Alert} The alert modal.
|
* @return {Promise<Alert>} Promise resolved with the alert modal.
|
||||||
*/
|
*/
|
||||||
showErrorModal(error: any, needsTranslate?: boolean, autocloseTime?: number): Alert {
|
showErrorModal(error: any, needsTranslate?: boolean, autocloseTime?: number): Promise<Alert> {
|
||||||
if (typeof error == 'object') {
|
if (typeof error == 'object') {
|
||||||
// We received an object instead of a string. Search for common properties.
|
// We received an object instead of a string. Search for common properties.
|
||||||
if (error.coreCanceled) {
|
if (error.coreCanceled) {
|
||||||
|
@ -903,9 +926,9 @@ export class CoreDomUtilsProvider {
|
||||||
* @param {any} [defaultError] Message to show if the error is not a string.
|
* @param {any} [defaultError] Message to show if the error is not a string.
|
||||||
* @param {boolean} [needsTranslate] Whether the error needs to be translated.
|
* @param {boolean} [needsTranslate] Whether the error needs to be translated.
|
||||||
* @param {number} [autocloseTime] Number of milliseconds to wait to close the modal. If not defined, modal won't be closed.
|
* @param {number} [autocloseTime] Number of milliseconds to wait to close the modal. If not defined, modal won't be closed.
|
||||||
* @return {Alert} The alert modal.
|
* @return {Promise<Alert>} Promise resolved with the alert modal.
|
||||||
*/
|
*/
|
||||||
showErrorModalDefault(error: any, defaultError: any, needsTranslate?: boolean, autocloseTime?: number): Alert {
|
showErrorModalDefault(error: any, defaultError: any, needsTranslate?: boolean, autocloseTime?: number): Promise<Alert> {
|
||||||
if (error && error.coreCanceled) {
|
if (error && error.coreCanceled) {
|
||||||
// It's a canceled error, don't display an error.
|
// It's a canceled error, don't display an error.
|
||||||
return;
|
return;
|
||||||
|
@ -927,9 +950,9 @@ export class CoreDomUtilsProvider {
|
||||||
* @param {any} [defaultError] Message to show if the error is not a string.
|
* @param {any} [defaultError] Message to show if the error is not a string.
|
||||||
* @param {boolean} [needsTranslate] Whether the error needs to be translated.
|
* @param {boolean} [needsTranslate] Whether the error needs to be translated.
|
||||||
* @param {number} [autocloseTime] Number of milliseconds to wait to close the modal. If not defined, modal won't be closed.
|
* @param {number} [autocloseTime] Number of milliseconds to wait to close the modal. If not defined, modal won't be closed.
|
||||||
* @return {Alert} The alert modal.
|
* @return {Promise<Alert>} Promise resolved with the alert modal.
|
||||||
*/
|
*/
|
||||||
showErrorModalFirstWarning(warnings: any, defaultError: any, needsTranslate?: boolean, autocloseTime?: number): Alert {
|
showErrorModalFirstWarning(warnings: any, defaultError: any, needsTranslate?: boolean, autocloseTime?: number): Promise<Alert> {
|
||||||
const error = warnings && warnings.length && warnings[0].message;
|
const error = warnings && warnings.length && warnings[0].message;
|
||||||
|
|
||||||
return this.showErrorModalDefault(error, defaultError, needsTranslate, autocloseTime);
|
return this.showErrorModalDefault(error, defaultError, needsTranslate, autocloseTime);
|
||||||
|
@ -974,32 +997,52 @@ export class CoreDomUtilsProvider {
|
||||||
*/
|
*/
|
||||||
showPrompt(message: string, title?: string, placeholder?: string, type: string = 'password'): Promise<any> {
|
showPrompt(message: string, title?: string, placeholder?: string, type: string = 'password'): Promise<any> {
|
||||||
return new Promise((resolve, reject): void => {
|
return new Promise((resolve, reject): void => {
|
||||||
this.alertCtrl.create({
|
const hasHTMLTags = this.textUtils.hasHTMLTags(message);
|
||||||
message: this.addFormatTextIfNeeded(message), // Add format-text to handle links.
|
let promise;
|
||||||
title: title,
|
|
||||||
inputs: [
|
if (hasHTMLTags) {
|
||||||
{
|
// Format the text.
|
||||||
name: 'promptinput',
|
promise = this.textUtils.formatText(message);
|
||||||
placeholder: placeholder || this.translate.instant('core.login.password'),
|
} else {
|
||||||
type: type
|
promise = Promise.resolve(message);
|
||||||
}
|
}
|
||||||
],
|
|
||||||
buttons: [
|
promise.then((message) => {
|
||||||
{
|
const alert = this.alertCtrl.create({
|
||||||
text: this.translate.instant('core.cancel'),
|
message: message,
|
||||||
role: 'cancel',
|
title: title,
|
||||||
handler: (): void => {
|
inputs: [
|
||||||
reject();
|
{
|
||||||
|
name: 'promptinput',
|
||||||
|
placeholder: placeholder || this.translate.instant('core.login.password'),
|
||||||
|
type: type
|
||||||
}
|
}
|
||||||
},
|
],
|
||||||
{
|
buttons: [
|
||||||
text: this.translate.instant('core.ok'),
|
{
|
||||||
handler: (data): void => {
|
text: this.translate.instant('core.cancel'),
|
||||||
resolve(data.promptinput);
|
role: 'cancel',
|
||||||
|
handler: (): void => {
|
||||||
|
reject();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: this.translate.instant('core.ok'),
|
||||||
|
handler: (data): void => {
|
||||||
|
resolve(data.promptinput);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
alert.present().then(() => {
|
||||||
|
if (hasHTMLTags) {
|
||||||
|
// Treat all anchors so they don't override the app.
|
||||||
|
const alertMessageEl: HTMLElement = alert.pageRef().nativeElement.querySelector('.alert-message');
|
||||||
|
this.treatAnchors(alertMessageEl);
|
||||||
}
|
}
|
||||||
]
|
});
|
||||||
}).present();
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1069,6 +1112,42 @@ export class CoreDomUtilsProvider {
|
||||||
return element.children;
|
return element.children;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Treat anchors inside alert/modals.
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} container The HTMLElement that can contain anchors.
|
||||||
|
*/
|
||||||
|
protected treatAnchors(container: HTMLElement): void {
|
||||||
|
const anchors = Array.from(container.querySelectorAll('a'));
|
||||||
|
|
||||||
|
anchors.forEach((anchor) => {
|
||||||
|
anchor.addEventListener('click', (event) => {
|
||||||
|
if (event.defaultPrevented) {
|
||||||
|
// Stop.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const href = anchor.getAttribute('href');
|
||||||
|
if (href) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
// We cannot use CoreDomUtilsProvider.openInBrowser due to circular dependencies.
|
||||||
|
if (this.appProvider.isDesktop()) {
|
||||||
|
// It's a desktop app, use Electron shell library to open the browser.
|
||||||
|
const shell = require('electron').shell;
|
||||||
|
if (!shell.openExternal(href)) {
|
||||||
|
// Open browser failed, open a new window in the app.
|
||||||
|
window.open(href, '_system');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
window.open(href, '_system');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* View an image in a new page or modal.
|
* View an image in a new page or modal.
|
||||||
*
|
*
|
||||||
|
|
Loading…
Reference in New Issue