commit
7eec78fb92
|
@ -1482,6 +1482,8 @@
|
|||
"core.downloaded": "local_moodlemobileapp",
|
||||
"core.downloading": "local_moodlemobileapp",
|
||||
"core.edit": "moodle",
|
||||
"core.editor.autosavesucceeded": "editor_atto",
|
||||
"core.editor.textrecovered": "editor_atto",
|
||||
"core.emptysplit": "local_moodlemobileapp",
|
||||
"core.error": "moodle",
|
||||
"core.errorchangecompletion": "local_moodlemobileapp",
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
</ion-refresher>
|
||||
|
||||
<core-loading [hideUntil]="loaded">
|
||||
<form ion-list [formGroup]="eventForm" *ngIf="!error">
|
||||
<form ion-list [formGroup]="eventForm" *ngIf="!error" #editEventForm>
|
||||
<!-- Event name. -->
|
||||
<ion-item text-wrap>
|
||||
<ion-label stacked><h2 [core-mark-required]="true">{{ 'addon.calendar.eventname' | translate }}</h2></ion-label>
|
||||
|
@ -86,7 +86,7 @@
|
|||
<!-- Description. -->
|
||||
<ion-item text-wrap>
|
||||
<ion-label stacked><h2>{{ 'core.description' | translate }}</h2></ion-label>
|
||||
<core-rich-text-editor item-content [control]="descriptionControl" [placeholder]="'core.description' | translate" name="description" [component]="component" [componentId]="eventId"></core-rich-text-editor>
|
||||
<core-rich-text-editor item-content [control]="descriptionControl" [placeholder]="'core.description' | translate" name="description" [component]="component" [componentId]="eventId" [autoSave]="false"></core-rich-text-editor>
|
||||
</ion-item>
|
||||
|
||||
<!-- Location. -->
|
||||
|
|
|
@ -17,6 +17,7 @@ import { IonicPageModule } from 'ionic-angular';
|
|||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
import { CoreEditorComponentsModule } from '@core/editor/components/components.module';
|
||||
import { AddonCalendarEditEventPage } from './edit-event';
|
||||
|
||||
@NgModule({
|
||||
|
@ -26,6 +27,7 @@ import { AddonCalendarEditEventPage } from './edit-event';
|
|||
imports: [
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule,
|
||||
CoreEditorComponentsModule,
|
||||
IonicPageModule.forChild(AddonCalendarEditEventPage),
|
||||
TranslateModule.forChild()
|
||||
],
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit, OnDestroy, Optional, ViewChild } from '@angular/core';
|
||||
import { Component, OnInit, OnDestroy, Optional, ViewChild, ElementRef } from '@angular/core';
|
||||
import { FormControl, FormGroup, FormBuilder, Validators } from '@angular/forms';
|
||||
import { IonicPage, NavController, NavParams } from 'ionic-angular';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
@ -25,7 +25,7 @@ import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
|||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { CoreCoursesProvider } from '@core/courses/providers/courses';
|
||||
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
||||
import { CoreRichTextEditorComponent } from '@components/rich-text-editor/rich-text-editor.ts';
|
||||
import { CoreEditorRichTextEditorComponent } from '@core/editor/components/rich-text-editor/rich-text-editor.ts';
|
||||
import { AddonCalendarProvider, AddonCalendarGetAccessInfoResult, AddonCalendarEvent } from '../../providers/calendar';
|
||||
import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline';
|
||||
import { AddonCalendarHelperProvider } from '../../providers/helper';
|
||||
|
@ -43,7 +43,8 @@ import { CoreFilterHelperProvider } from '@core/filter/providers/helper';
|
|||
})
|
||||
export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
|
||||
|
||||
@ViewChild(CoreRichTextEditorComponent) descriptionEditor: CoreRichTextEditorComponent;
|
||||
@ViewChild(CoreEditorRichTextEditorComponent) descriptionEditor: CoreEditorRichTextEditorComponent;
|
||||
@ViewChild('editEventForm') formElement: ElementRef;
|
||||
|
||||
title: string;
|
||||
dateFormat: string;
|
||||
|
@ -496,6 +497,8 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
|
|||
this.calendarProvider.submitEvent(this.eventId, data).then((result) => {
|
||||
event = result.event;
|
||||
|
||||
this.domUtils.triggerFormSubmittedEvent(this.formElement.nativeElement, result.sent, this.currentSite.getId());
|
||||
|
||||
if (result.sent) {
|
||||
// Event created or edited, invalidate right days & months.
|
||||
const numberOfRepetitions = formData.repeat ? formData.repeats :
|
||||
|
@ -557,6 +560,9 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
|
|||
discard(): void {
|
||||
this.domUtils.showConfirm(this.translate.instant('core.areyousure')).then(() => {
|
||||
this.calendarOffline.deleteEvent(this.eventId).then(() => {
|
||||
|
||||
this.domUtils.triggerFormCancelledEvent(this.formElement.nativeElement, this.currentSite.getId());
|
||||
|
||||
this.returnToList();
|
||||
}).catch(() => {
|
||||
// Shouldn't happen.
|
||||
|
@ -572,16 +578,18 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy {
|
|||
*
|
||||
* @return Resolved if we can leave it, rejected if not.
|
||||
*/
|
||||
ionViewCanLeave(): boolean | Promise<void> {
|
||||
|
||||
async ionViewCanLeave(): Promise<void> {
|
||||
if (this.calendarHelper.hasEventDataChanged(this.eventForm.value, this.originalData)) {
|
||||
// Show confirmation if some data has been modified.
|
||||
return this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit'));
|
||||
} else {
|
||||
return Promise.resolve();
|
||||
await this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit'));
|
||||
}
|
||||
|
||||
this.domUtils.triggerFormCancelledEvent(this.formElement.nativeElement, this.currentSite.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Unblock sync.
|
||||
*/
|
||||
protected unblockSync(): void {
|
||||
if (this.eventId) {
|
||||
this.syncProvider.unblockOperation(AddonCalendarProvider.COMPONENT, this.eventId);
|
||||
|
|
|
@ -21,6 +21,7 @@ import { AddonModAssignFeedbackCommentsComponent } from './component/comments';
|
|||
import { AddonModAssignFeedbackDelegate } from '../../providers/feedback-delegate';
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
import { CoreEditorComponentsModule } from '@core/editor/components/components.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -31,7 +32,8 @@ import { CoreDirectivesModule } from '@directives/directives.module';
|
|||
IonicModule,
|
||||
TranslateModule.forChild(),
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule
|
||||
CoreDirectivesModule,
|
||||
CoreEditorComponentsModule,
|
||||
],
|
||||
providers: [
|
||||
AddonModAssignFeedbackCommentsHandler
|
||||
|
|
|
@ -19,5 +19,6 @@
|
|||
|
||||
<!-- Edit -->
|
||||
<ion-item text-wrap *ngIf="edit && loaded">
|
||||
<core-rich-text-editor item-content [control]="control" [placeholder]="plugin.name" name="assignfeedbackcomments_editor" [component]="component" [componentId]="assign.cmid"></core-rich-text-editor>
|
||||
<core-rich-text-editor item-content [control]="control" [placeholder]="plugin.name" name="assignfeedbackcomments_editor" [component]="component" [componentId]="assign.cmid" [autoSave]="true" contextLevel="module" [contextInstanceId]="assign.cmid" elementId="assignfeedbackcomments_editor" [draftExtraParams]="{userid: userId, action: 'grade'}">
|
||||
</core-rich-text-editor>
|
||||
</ion-item>
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
</ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<form name="addon-mod_assign-edit-feedback-form" *ngIf="userId && plugin">
|
||||
<form name="addon-mod_assign-edit-feedback-form" *ngIf="userId && plugin" #editFeedbackForm>
|
||||
<addon-mod-assign-feedback-plugin [assign]="assign" [submission]="submission" [userId]="userId" [plugin]="plugin" [edit]="true"></addon-mod-assign-feedback-plugin>
|
||||
<button ion-button block (click)="done($event)">{{ 'core.done' | translate }}</button>
|
||||
</form>
|
||||
|
|
|
@ -12,9 +12,11 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, Input } from '@angular/core';
|
||||
import { Component, Input, ViewChild, ElementRef } from '@angular/core';
|
||||
import { IonicPage, ViewController, NavParams } from 'ionic-angular';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { AddonModAssignFeedbackDelegate } from '../../providers/feedback-delegate';
|
||||
import {
|
||||
|
@ -36,10 +38,17 @@ export class AddonModAssignEditFeedbackModalPage {
|
|||
@Input() plugin: AddonModAssignPlugin; // The plugin object.
|
||||
@Input() userId: number; // The user ID of the submission.
|
||||
|
||||
@ViewChild('editFeedbackForm') formElement: ElementRef;
|
||||
|
||||
protected forceLeave = false; // To allow leaving the page without checking for changes.
|
||||
|
||||
constructor(params: NavParams, protected viewCtrl: ViewController, protected domUtils: CoreDomUtilsProvider,
|
||||
protected translate: TranslateService, protected feedbackDelegate: AddonModAssignFeedbackDelegate) {
|
||||
constructor(params: NavParams,
|
||||
protected viewCtrl: ViewController,
|
||||
protected domUtils: CoreDomUtilsProvider,
|
||||
protected translate: TranslateService,
|
||||
protected feedbackDelegate: AddonModAssignFeedbackDelegate,
|
||||
protected eventsProvider: CoreEventsProvider,
|
||||
protected sitesProvider: CoreSitesProvider) {
|
||||
|
||||
this.assign = params.get('assign');
|
||||
this.submission = params.get('submission');
|
||||
|
@ -52,16 +61,17 @@ export class AddonModAssignEditFeedbackModalPage {
|
|||
*
|
||||
* @return Resolved if we can leave it, rejected if not.
|
||||
*/
|
||||
ionViewCanLeave(): boolean | Promise<void> {
|
||||
async ionViewCanLeave(): Promise<void> {
|
||||
if (this.forceLeave) {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
return this.hasDataChanged().then((changed) => {
|
||||
if (changed) {
|
||||
return this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit'));
|
||||
}
|
||||
});
|
||||
const changed = await this.hasDataChanged();
|
||||
if (changed) {
|
||||
await this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit'));
|
||||
}
|
||||
|
||||
this.domUtils.triggerFormCancelledEvent(this.formElement.nativeElement, this.sitesProvider.getCurrentSiteId());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -82,6 +92,8 @@ export class AddonModAssignEditFeedbackModalPage {
|
|||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.domUtils.triggerFormSubmittedEvent(this.formElement.nativeElement, false, this.sitesProvider.getCurrentSiteId());
|
||||
|
||||
// Close the modal, sending the input data.
|
||||
this.forceLeave = true;
|
||||
this.closeModal(this.getInputData());
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<core-loading [hideUntil]="loaded">
|
||||
<ion-list>
|
||||
<!-- @todo: plagiarism_print_disclosure -->
|
||||
<form name="addon-mod_assign-edit-form" *ngIf="userSubmission && userSubmission.plugins && userSubmission.plugins.length">
|
||||
<form name="addon-mod_assign-edit-form" *ngIf="userSubmission && userSubmission.plugins && userSubmission.plugins.length" #editSubmissionForm>
|
||||
<!-- Submission statement. -->
|
||||
<ion-item text-wrap *ngIf="submissionStatement">
|
||||
<ion-label><core-format-text [text]="submissionStatement" [filter]="false"></core-format-text></ion-label>
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core';
|
||||
import { IonicPage, NavController, NavParams } from 'ionic-angular';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
|
@ -34,6 +34,9 @@ import { AddonModAssignHelperProvider } from '../../providers/helper';
|
|||
templateUrl: 'edit.html',
|
||||
})
|
||||
export class AddonModAssignEditPage implements OnInit, OnDestroy {
|
||||
|
||||
@ViewChild('editSubmissionForm') formElement: ElementRef;
|
||||
|
||||
title: string; // Title to display.
|
||||
assign: AddonModAssignAssign; // Assignment.
|
||||
courseId: number; // Course ID the assignment belongs to.
|
||||
|
@ -82,20 +85,21 @@ export class AddonModAssignEditPage implements OnInit, OnDestroy {
|
|||
*
|
||||
* @return Resolved if we can leave it, rejected if not.
|
||||
*/
|
||||
ionViewCanLeave(): boolean | Promise<void> {
|
||||
async ionViewCanLeave(): Promise<void> {
|
||||
if (this.forceLeave) {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if data has changed.
|
||||
return this.hasDataChanged().then((changed) => {
|
||||
if (changed) {
|
||||
return this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit'));
|
||||
}
|
||||
}).then(() => {
|
||||
// Nothing has changed or user confirmed to leave. Clear temporary data from plugins.
|
||||
this.assignHelper.clearSubmissionPluginTmpData(this.assign, this.userSubmission, this.getInputData());
|
||||
});
|
||||
const changed = await this.hasDataChanged();
|
||||
if (changed) {
|
||||
await this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit'));
|
||||
}
|
||||
|
||||
// Nothing has changed or user confirmed to leave. Clear temporary data from plugins.
|
||||
this.assignHelper.clearSubmissionPluginTmpData(this.assign, this.userSubmission, this.getInputData());
|
||||
|
||||
this.domUtils.triggerFormCancelledEvent(this.formElement.nativeElement, this.sitesProvider.getCurrentSiteId());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -265,69 +269,74 @@ export class AddonModAssignEditPage implements OnInit, OnDestroy {
|
|||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected saveSubmission(): Promise<any> {
|
||||
protected async saveSubmission(): Promise<void> {
|
||||
const inputData = this.getInputData();
|
||||
|
||||
if (this.submissionStatement && (!inputData.submissionstatement || inputData.submissionstatement === 'false')) {
|
||||
return Promise.reject(this.translate.instant('addon.mod_assign.acceptsubmissionstatement'));
|
||||
throw this.translate.instant('addon.mod_assign.acceptsubmissionstatement');
|
||||
}
|
||||
|
||||
let modal = this.domUtils.showModalLoading();
|
||||
let size;
|
||||
|
||||
// Get size to ask for confirmation.
|
||||
return this.assignHelper.getSubmissionSizeForEdit(this.assign, this.userSubmission, inputData).catch(() => {
|
||||
try {
|
||||
size = await this.assignHelper.getSubmissionSizeForEdit(this.assign, this.userSubmission, inputData);
|
||||
} catch (error) {
|
||||
// Error calculating size, return -1.
|
||||
return -1;
|
||||
}).then((size) => {
|
||||
modal.dismiss();
|
||||
size = -1;
|
||||
}
|
||||
|
||||
modal.dismiss();
|
||||
|
||||
try {
|
||||
// Confirm action.
|
||||
return this.fileUploaderHelper.confirmUploadFile(size, true, this.allowOffline);
|
||||
}).then(() => {
|
||||
await this.fileUploaderHelper.confirmUploadFile(size, true, this.allowOffline);
|
||||
|
||||
modal = this.domUtils.showModalLoading('core.sending', true);
|
||||
|
||||
return this.prepareSubmissionData(inputData).then((pluginData) => {
|
||||
if (!Object.keys(pluginData).length) {
|
||||
// Nothing to save.
|
||||
return;
|
||||
}
|
||||
const pluginData = await this.prepareSubmissionData(inputData);
|
||||
if (!Object.keys(pluginData).length) {
|
||||
// Nothing to save.
|
||||
return;
|
||||
}
|
||||
|
||||
let promise;
|
||||
let sent: boolean;
|
||||
|
||||
if (this.saveOffline) {
|
||||
// Save submission in offline.
|
||||
promise = this.assignOfflineProvider.saveSubmission(this.assign.id, this.courseId, pluginData,
|
||||
this.userSubmission.timemodified, !this.assign.submissiondrafts, this.userId);
|
||||
} else {
|
||||
// Try to send it to server.
|
||||
promise = this.assignProvider.saveSubmission(this.assign.id, this.courseId, pluginData, this.allowOffline,
|
||||
this.userSubmission.timemodified, !!this.assign.submissiondrafts, this.userId);
|
||||
}
|
||||
if (this.saveOffline) {
|
||||
// Save submission in offline.
|
||||
sent = false;
|
||||
await this.assignOfflineProvider.saveSubmission(this.assign.id, this.courseId, pluginData,
|
||||
this.userSubmission.timemodified, !this.assign.submissiondrafts, this.userId);
|
||||
} else {
|
||||
// Try to send it to server.
|
||||
sent = await this.assignProvider.saveSubmission(this.assign.id, this.courseId, pluginData, this.allowOffline,
|
||||
this.userSubmission.timemodified, !!this.assign.submissiondrafts, this.userId);
|
||||
}
|
||||
|
||||
return promise.then(() => {
|
||||
// Clear temporary data from plugins.
|
||||
return this.assignHelper.clearSubmissionPluginTmpData(this.assign, this.userSubmission, inputData);
|
||||
}).then(() => {
|
||||
// Submission saved, trigger event.
|
||||
const params = {
|
||||
assignmentId: this.assign.id,
|
||||
submissionId: this.userSubmission.id,
|
||||
userId: this.userId,
|
||||
};
|
||||
// Clear temporary data from plugins.
|
||||
await this.assignHelper.clearSubmissionPluginTmpData(this.assign, this.userSubmission, inputData);
|
||||
|
||||
this.eventsProvider.trigger(AddonModAssignProvider.SUBMISSION_SAVED_EVENT, params,
|
||||
this.sitesProvider.getCurrentSiteId());
|
||||
// Submission saved, trigger events.
|
||||
this.domUtils.triggerFormSubmittedEvent(this.formElement.nativeElement, sent, this.sitesProvider.getCurrentSiteId());
|
||||
|
||||
if (!this.assign.submissiondrafts) {
|
||||
// No drafts allowed, so it was submitted. Trigger event.
|
||||
this.eventsProvider.trigger(AddonModAssignProvider.SUBMITTED_FOR_GRADING_EVENT, params,
|
||||
this.sitesProvider.getCurrentSiteId());
|
||||
}
|
||||
});
|
||||
});
|
||||
}).finally(() => {
|
||||
const params = {
|
||||
assignmentId: this.assign.id,
|
||||
submissionId: this.userSubmission.id,
|
||||
userId: this.userId,
|
||||
};
|
||||
|
||||
this.eventsProvider.trigger(AddonModAssignProvider.SUBMISSION_SAVED_EVENT, params,
|
||||
this.sitesProvider.getCurrentSiteId());
|
||||
|
||||
if (!this.assign.submissiondrafts) {
|
||||
// No drafts allowed, so it was submitted. Trigger event.
|
||||
this.eventsProvider.trigger(AddonModAssignProvider.SUBMITTED_FOR_GRADING_EVENT, params,
|
||||
this.sitesProvider.getCurrentSiteId());
|
||||
}
|
||||
} finally {
|
||||
modal.dismiss();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -15,6 +15,6 @@
|
|||
<p>{{ 'core.numwords' | translate: {'$a': words + ' / ' + configs.wordlimit} }}</p>
|
||||
</ion-item>
|
||||
<ion-item text-wrap>
|
||||
<core-rich-text-editor item-content [control]="control" [placeholder]="plugin.name" name="onlinetext_editor_text" (contentChanged)="onChange($event)" [component]="component" [componentId]="assign.cmid"></core-rich-text-editor>
|
||||
<core-rich-text-editor item-content [control]="control" [placeholder]="plugin.name" name="onlinetext_editor_text" (contentChanged)="onChange($event)" [component]="component" [componentId]="assign.cmid" [autoSave]="true" contextLevel="module" [contextInstanceId]="assign.cmid" elementId="onlinetext_editor" [draftExtraParams]="{userid: currentUserId, action: 'editsubmission'}"></core-rich-text-editor>
|
||||
</ion-item>
|
||||
</div>
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
import { Component, OnInit, ElementRef } from '@angular/core';
|
||||
import { FormBuilder, FormControl } from '@angular/forms';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
import { AddonModAssignProvider } from '../../../providers/assign';
|
||||
|
@ -35,16 +36,23 @@ export class AddonModAssignSubmissionOnlineTextComponent extends AddonModAssignS
|
|||
text: string;
|
||||
loaded: boolean;
|
||||
wordLimitEnabled: boolean;
|
||||
currentUserId: number;
|
||||
|
||||
protected wordCountTimeout: any;
|
||||
protected element: HTMLElement;
|
||||
|
||||
constructor(protected fb: FormBuilder, protected domUtils: CoreDomUtilsProvider, protected textUtils: CoreTextUtilsProvider,
|
||||
protected assignProvider: AddonModAssignProvider, protected assignOfflineProvider: AddonModAssignOfflineProvider,
|
||||
element: ElementRef) {
|
||||
constructor(
|
||||
protected fb: FormBuilder,
|
||||
protected domUtils: CoreDomUtilsProvider,
|
||||
protected textUtils: CoreTextUtilsProvider,
|
||||
protected assignProvider: AddonModAssignProvider,
|
||||
protected assignOfflineProvider: AddonModAssignOfflineProvider,
|
||||
element: ElementRef,
|
||||
sitesProvider: CoreSitesProvider) {
|
||||
|
||||
super();
|
||||
this.element = element.nativeElement;
|
||||
this.currentUserId = sitesProvider.getCurrentSiteUserId();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -21,6 +21,7 @@ import { AddonModAssignSubmissionOnlineTextComponent } from './component/onlinet
|
|||
import { AddonModAssignSubmissionDelegate } from '../../providers/submission-delegate';
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
import { CoreEditorComponentsModule } from '@core/editor/components/components.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -31,7 +32,8 @@ import { CoreDirectivesModule } from '@directives/directives.module';
|
|||
IonicModule,
|
||||
TranslateModule.forChild(),
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule
|
||||
CoreDirectivesModule,
|
||||
CoreEditorComponentsModule,
|
||||
],
|
||||
providers: [
|
||||
AddonModAssignSubmissionOnlineTextHandler
|
||||
|
|
|
@ -40,7 +40,7 @@ import { AddonModDataFieldUrlModule } from './url/url.module';
|
|||
AddonModDataFieldRadiobuttonModule,
|
||||
AddonModDataFieldTextModule,
|
||||
AddonModDataFieldTextareaModule,
|
||||
AddonModDataFieldUrlModule
|
||||
AddonModDataFieldUrlModule,
|
||||
],
|
||||
providers: [
|
||||
],
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
<ion-input *ngIf="mode == 'search'" type="text" [placeholder]="field.name" [formControlName]="'f_'+field.id"></ion-input>
|
||||
|
||||
<span *ngIf="mode == 'edit'" [core-mark-required]="field.required" class="core-mark-required"></span>
|
||||
<core-rich-text-editor *ngIf="mode == 'edit'" item-content [control]="form.controls['f_'+field.id]" [placeholder]="field.name" [formControlName]="'f_'+field.id" [component]="component" [componentId]="componentId"></core-rich-text-editor>
|
||||
<core-rich-text-editor *ngIf="mode == 'edit'" item-content [control]="form.controls['f_'+field.id]" [placeholder]="field.name" [formControlName]="'f_'+field.id" [component]="component" [componentId]="componentId" [autoSave]="true" contextLevel="module" [contextInstanceId]="componentId" [elementId]="'field_'+field.id"></core-rich-text-editor>
|
||||
<core-input-errors *ngIf="error && mode == 'edit'" [control]="form.controls['f_'+field.id]" [errorText]="error"></core-input-errors>
|
||||
</span>
|
||||
|
||||
|
|
|
@ -49,10 +49,10 @@ export class AddonModDataFieldTextareaComponent extends AddonModDataFieldPluginC
|
|||
* Initialize field.
|
||||
*/
|
||||
protected init(): void {
|
||||
if (this.isShowOrListMode()) {
|
||||
this.component = AddonModDataProvider.COMPONENT;
|
||||
this.componentId = this.database.coursemodule;
|
||||
this.component = AddonModDataProvider.COMPONENT;
|
||||
this.componentId = this.database.coursemodule;
|
||||
|
||||
if (this.isShowOrListMode()) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@ import { AddonModDataFieldsDelegate } from '../../providers/fields-delegate';
|
|||
import { AddonModDataFieldTextareaComponent } from './component/textarea';
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
import { CoreEditorComponentsModule } from '@core/editor/components/components.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -30,7 +31,8 @@ import { CoreDirectivesModule } from '@directives/directives.module';
|
|||
IonicModule,
|
||||
TranslateModule.forChild(),
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule
|
||||
CoreDirectivesModule,
|
||||
CoreEditorComponentsModule,
|
||||
],
|
||||
providers: [
|
||||
AddonModDataFieldTextareaHandler
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<div class="addon-data-contents addon-data-entries-{{data.id}}" *ngIf="data">
|
||||
<core-style [css]="data.csstemplate" prefix=".addon-data-entries-{{data.id}}"></core-style>
|
||||
|
||||
<form (ngSubmit)="save($event)" [formGroup]="editForm">
|
||||
<form (ngSubmit)="save($event)" [formGroup]="editForm" #editFormEl>
|
||||
<core-compile-html [text]="editFormRender" [jsData]="jsData" [extraImports]="extraImports"></core-compile-html>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, ViewChild } from '@angular/core';
|
||||
import { Component, ViewChild, ElementRef } from '@angular/core';
|
||||
import { Content, IonicPage, NavParams, NavController } from 'ionic-angular';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { FormGroup } from '@angular/forms';
|
||||
|
@ -40,6 +40,7 @@ import { CoreTagProvider } from '@core/tag/providers/tag';
|
|||
})
|
||||
export class AddonModDataEditPage {
|
||||
@ViewChild(Content) content: Content;
|
||||
@ViewChild('editFormEl') formElement: ElementRef;
|
||||
|
||||
protected module: any;
|
||||
protected courseId: number;
|
||||
|
@ -95,28 +96,25 @@ export class AddonModDataEditPage {
|
|||
*
|
||||
* @return Resolved if we can leave it, rejected if not.
|
||||
*/
|
||||
ionViewCanLeave(): boolean | Promise<void> {
|
||||
async ionViewCanLeave(): Promise<void> {
|
||||
if (this.forceLeave || !this.entry) {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
const inputData = this.editForm.value;
|
||||
|
||||
return this.dataHelper.hasEditDataChanged(inputData, this.fieldsArray, this.data.id,
|
||||
this.entry.contents).then((changed) => {
|
||||
if (!changed) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
const changed = await this.dataHelper.hasEditDataChanged(inputData, this.fieldsArray, this.data.id, this.entry.contents);
|
||||
|
||||
if (changed) {
|
||||
// Show confirmation if some data has been modified.
|
||||
return this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit'));
|
||||
}).then(() => {
|
||||
// Delete the local files from the tmp folder.
|
||||
return this.dataHelper.getEditTmpFiles(inputData, this.fieldsArray, this.data.id,
|
||||
this.entry.contents).then((files) => {
|
||||
this.fileUploaderProvider.clearTmpFiles(files);
|
||||
});
|
||||
});
|
||||
await this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit'));
|
||||
}
|
||||
|
||||
// Delete the local files from the tmp folder.
|
||||
const files = await this.dataHelper.getEditTmpFiles(inputData, this.fieldsArray, this.data.id, this.entry.contents);
|
||||
this.fileUploaderProvider.clearTmpFiles(files);
|
||||
|
||||
this.domUtils.triggerFormCancelledEvent(this.formElement.nativeElement, this.siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -216,6 +214,9 @@ export class AddonModDataEditPage {
|
|||
|
||||
// This is done if entry is updated when editing or creating if not.
|
||||
if ((this.entryId && result.updated) || (!this.entryId && result.newentryid)) {
|
||||
|
||||
this.domUtils.triggerFormSubmittedEvent(this.formElement.nativeElement, result.sent, this.siteId);
|
||||
|
||||
const promises = [];
|
||||
|
||||
this.entryId = this.entryId || result.newentryid;
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<a class="tab-slide" [attr.aria-selected]="!search.searchingAdvanced" (click)="changeAdvanced(false)">{{ 'addon.mod_data.search' | translate}}</a>
|
||||
<a class="tab-slide" [attr.aria-selected]="search.searchingAdvanced" (click)="changeAdvanced(true)">{{ 'addon.mod_data.advancedsearch' | translate }}</a>
|
||||
</div>
|
||||
<form (ngSubmit)="searchEntries($event)" [formGroup]="searchForm">
|
||||
<form (ngSubmit)="searchEntries($event)" [formGroup]="searchForm" #searchFormEl>
|
||||
<ion-list no-margin>
|
||||
<ion-item [hidden]="search.searchingAdvanced">
|
||||
<ion-input type="text" placeholder="{{ 'addon.mod_data.search' | translate}}" [(ngModel)]="search.text" name="text" formControlName="text"></ion-input>
|
||||
|
|
|
@ -12,9 +12,11 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, ViewChild, ElementRef } from '@angular/core';
|
||||
import { IonicPage, NavParams, ViewController } from 'ionic-angular';
|
||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
|
@ -32,6 +34,8 @@ import { CoreTagProvider } from '@core/tag/providers/tag';
|
|||
templateUrl: 'search.html',
|
||||
})
|
||||
export class AddonModDataSearchPage {
|
||||
@ViewChild('searchFormEl') formElement: ElementRef;
|
||||
|
||||
search: any;
|
||||
fields: any;
|
||||
data: any;
|
||||
|
@ -41,10 +45,17 @@ export class AddonModDataSearchPage {
|
|||
jsData: any;
|
||||
fieldsArray: any;
|
||||
|
||||
constructor(params: NavParams, private viewCtrl: ViewController, fb: FormBuilder, protected utils: CoreUtilsProvider,
|
||||
protected domUtils: CoreDomUtilsProvider, protected fieldsDelegate: AddonModDataFieldsDelegate,
|
||||
protected textUtils: CoreTextUtilsProvider, protected dataHelper: AddonModDataHelperProvider,
|
||||
private tagProvider: CoreTagProvider) {
|
||||
constructor(params: NavParams,
|
||||
protected viewCtrl: ViewController,
|
||||
fb: FormBuilder,
|
||||
protected utils: CoreUtilsProvider,
|
||||
protected domUtils: CoreDomUtilsProvider,
|
||||
protected fieldsDelegate: AddonModDataFieldsDelegate,
|
||||
protected textUtils: CoreTextUtilsProvider,
|
||||
protected dataHelper: AddonModDataHelperProvider,
|
||||
protected tagProvider: CoreTagProvider,
|
||||
protected eventsProvider: CoreEventsProvider,
|
||||
protected sitesProvider: CoreSitesProvider) {
|
||||
this.search = params.get('search');
|
||||
this.fields = params.get('fields');
|
||||
this.data = params.get('data');
|
||||
|
@ -175,6 +186,12 @@ export class AddonModDataSearchPage {
|
|||
* @param data Data to return to the page.
|
||||
*/
|
||||
closeModal(data?: any): void {
|
||||
if (typeof data == 'undefined') {
|
||||
this.domUtils.triggerFormCancelledEvent(this.formElement.nativeElement, this.sitesProvider.getCurrentSiteId());
|
||||
} else {
|
||||
this.domUtils.triggerFormSubmittedEvent(this.formElement.nativeElement, false, this.sitesProvider.getCurrentSiteId());
|
||||
}
|
||||
|
||||
this.viewCtrl.dismiss(data);
|
||||
}
|
||||
|
||||
|
|
|
@ -126,7 +126,8 @@ export class AddonModDataProvider {
|
|||
.then((entry) => {
|
||||
return {
|
||||
// Return provissional entry Id.
|
||||
newentryid: entry
|
||||
newentryid: entry,
|
||||
sent: false,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
@ -142,7 +143,11 @@ export class AddonModDataProvider {
|
|||
return storeOffline();
|
||||
}
|
||||
|
||||
return this.addEntryOnline(dataId, contents, groupId, siteId).catch((error) => {
|
||||
return this.addEntryOnline(dataId, contents, groupId, siteId).then((result) => {
|
||||
result.sent = true;
|
||||
|
||||
return result;
|
||||
}).catch((error) => {
|
||||
if (this.utils.isWebServiceError(error)) {
|
||||
// The WebService has thrown an error, this means that responses cannot be submitted.
|
||||
return Promise.reject(error);
|
||||
|
@ -194,7 +199,12 @@ export class AddonModDataProvider {
|
|||
const storeOffline = (): Promise<any> => {
|
||||
const action = approve ? 'approve' : 'disapprove';
|
||||
|
||||
return this.dataOffline.saveEntry(dataId, entryId, action, courseId, undefined, undefined, undefined, siteId);
|
||||
return this.dataOffline.saveEntry(dataId, entryId, action, courseId, undefined, undefined, undefined, siteId)
|
||||
.then(() => {
|
||||
return {
|
||||
sent: false,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// Get if the opposite action is not synced.
|
||||
|
@ -210,7 +220,11 @@ export class AddonModDataProvider {
|
|||
return storeOffline();
|
||||
}
|
||||
|
||||
return this.approveEntryOnline(entryId, approve, siteId).catch((error) => {
|
||||
return this.approveEntryOnline(entryId, approve, siteId).then(() => {
|
||||
return {
|
||||
sent: true,
|
||||
};
|
||||
}).catch((error) => {
|
||||
if (this.utils.isWebServiceError(error)) {
|
||||
// The WebService has thrown an error, this means that responses cannot be submitted.
|
||||
return Promise.reject(error);
|
||||
|
@ -288,7 +302,12 @@ export class AddonModDataProvider {
|
|||
|
||||
// Convenience function to store a data to be synchronized later.
|
||||
const storeOffline = (): Promise<any> => {
|
||||
return this.dataOffline.saveEntry(dataId, entryId, 'delete', courseId, undefined, undefined, undefined, siteId);
|
||||
return this.dataOffline.saveEntry(dataId, entryId, 'delete', courseId, undefined, undefined, undefined, siteId)
|
||||
.then(() => {
|
||||
return {
|
||||
sent: false,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
let justAdded = false;
|
||||
|
@ -318,7 +337,11 @@ export class AddonModDataProvider {
|
|||
return storeOffline();
|
||||
}
|
||||
|
||||
return this.deleteEntryOnline(entryId, siteId).catch((error) => {
|
||||
return this.deleteEntryOnline(entryId, siteId).then(() => {
|
||||
return {
|
||||
sent: true,
|
||||
};
|
||||
}).catch((error) => {
|
||||
if (this.utils.isWebServiceError(error)) {
|
||||
// The WebService has thrown an error, this means that responses cannot be submitted.
|
||||
return Promise.reject(error);
|
||||
|
@ -368,7 +391,8 @@ export class AddonModDataProvider {
|
|||
return this.dataOffline.saveEntry(dataId, entryId, 'edit', courseId, undefined, contents, undefined, siteId)
|
||||
.then(() => {
|
||||
return {
|
||||
updated: true
|
||||
updated: true,
|
||||
sent: false,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
@ -408,6 +432,7 @@ export class AddonModDataProvider {
|
|||
return this.addEntry(dataId, entryId, courseId, contents, groupId, fields, siteId, forceOffline)
|
||||
.then((result) => {
|
||||
result.updated = true;
|
||||
result.sent = true;
|
||||
|
||||
return result;
|
||||
});
|
||||
|
@ -418,7 +443,11 @@ export class AddonModDataProvider {
|
|||
return storeOffline();
|
||||
}
|
||||
|
||||
return this.editEntryOnline(entryId, contents, siteId).catch((error) => {
|
||||
return this.editEntryOnline(entryId, contents, siteId).then((result) => {
|
||||
result.sent = true;
|
||||
|
||||
return result;
|
||||
}).catch((error) => {
|
||||
if (this.utils.isWebServiceError(error)) {
|
||||
// The WebService has thrown an error, this means that responses cannot be submitted.
|
||||
return Promise.reject(error);
|
||||
|
|
|
@ -26,6 +26,7 @@ import { AddonModForumIndexComponent } from './index/index';
|
|||
import { AddonModForumPostComponent } from './post/post';
|
||||
import { AddonForumDiscussionOptionsMenuComponent } from './discussion-options-menu/discussion-options-menu';
|
||||
import { AddonForumPostOptionsMenuComponent } from './post-options-menu/post-options-menu';
|
||||
import { CoreEditorComponentsModule } from '@core/editor/components/components.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -43,7 +44,8 @@ import { AddonForumPostOptionsMenuComponent } from './post-options-menu/post-opt
|
|||
CorePipesModule,
|
||||
CoreCourseComponentsModule,
|
||||
CoreRatingComponentsModule,
|
||||
CoreTagComponentsModule
|
||||
CoreTagComponentsModule,
|
||||
CoreEditorComponentsModule,
|
||||
],
|
||||
providers: [
|
||||
],
|
||||
|
|
|
@ -64,7 +64,7 @@
|
|||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label stacked>{{ 'addon.mod_forum.message' | translate }}</ion-label>
|
||||
<core-rich-text-editor item-content [control]="messageControl" (contentChanged)="onMessageChange($event)" [placeholder]="'addon.mod_forum.replyplaceholder' | translate" [name]="'mod_forum_reply_' + post.id" [component]="component" [componentId]="componentId"></core-rich-text-editor>
|
||||
<core-rich-text-editor item-content [control]="messageControl" (contentChanged)="onMessageChange($event)" [placeholder]="'addon.mod_forum.replyplaceholder' | translate" [name]="'mod_forum_reply_' + post.id" [component]="component" [componentId]="componentId" [autoSave]="true" contextLevel="module" [contextInstanceId]="forum && forum.cmid" elementId="message" [draftExtraParams]="{reply: post.id}"></core-rich-text-editor>
|
||||
</ion-item>
|
||||
<ion-item text-wrap *ngIf="accessInfo.canpostprivatereply">
|
||||
<ion-label>{{ 'addon.mod_forum.privatereply' | translate }}</ion-label>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label stacked>{{ 'addon.mod_forum.message' | translate }}</ion-label>
|
||||
<core-rich-text-editor item-content [control]="messageControl" (contentChanged)="onMessageChange($event)" [placeholder]="'addon.mod_forum.replyplaceholder' | translate" [name]="'mod_forum_reply_' + replyData.id" [component]="component" [componentId]="componentId"></core-rich-text-editor>
|
||||
<core-rich-text-editor item-content [control]="messageControl" (contentChanged)="onMessageChange($event)" [placeholder]="'addon.mod_forum.replyplaceholder' | translate" [name]="'mod_forum_reply_' + replyData.id" [component]="component" [componentId]="componentId" [autoSave]="true" contextLevel="module" [contextInstanceId]="forum.cmid" elementId="message" [draftExtraParams]="{edit: replyData.id}"></core-rich-text-editor>
|
||||
</ion-item>
|
||||
<ion-item-divider text-wrap (click)="toggleAdvanced()" class="core-expandable">
|
||||
<core-icon *ngIf="!advanced" name="fa-caret-right" item-start></core-icon>
|
||||
|
|
|
@ -19,6 +19,7 @@ import { CoreComponentsModule } from '@components/components.module';
|
|||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
import { AddonModForumComponentsModule } from '../../components/components.module';
|
||||
import { AddonModForumEditPostPage } from './edit-post';
|
||||
import { CoreEditorComponentsModule } from '@core/editor/components/components.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -28,6 +29,7 @@ import { AddonModForumEditPostPage } from './edit-post';
|
|||
CoreComponentsModule,
|
||||
CoreDirectivesModule,
|
||||
AddonModForumComponentsModule,
|
||||
CoreEditorComponentsModule,
|
||||
IonicPageModule.forChild(AddonModForumEditPostPage),
|
||||
TranslateModule.forChild()
|
||||
],
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label stacked>{{ 'addon.mod_forum.message' | translate }}</ion-label>
|
||||
<core-rich-text-editor item-content [control]="messageControl" (contentChanged)="onMessageChange($event)" [placeholder]="'addon.mod_forum.message' | translate" name="addon_mod_forum_new_discussion" [component]="component" [componentId]="forum.cmid"></core-rich-text-editor>
|
||||
<core-rich-text-editor item-content [control]="messageControl" (contentChanged)="onMessageChange($event)" [placeholder]="'addon.mod_forum.message' | translate" name="addon_mod_forum_new_discussion" [component]="component" [componentId]="forum.cmid" [autoSave]="true" contextLevel="module" [contextInstanceId]="forum.cmid" elementId="message"></core-rich-text-editor>
|
||||
</ion-item>
|
||||
<ion-item-divider text-wrap (click)="toggleAdvanced()" class="core-expandable">
|
||||
<core-icon *ngIf="!advanced" name="fa-caret-right" item-start></core-icon>
|
||||
|
|
|
@ -18,6 +18,7 @@ import { TranslateModule } from '@ngx-translate/core';
|
|||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
import { AddonModForumNewDiscussionPage } from './new-discussion';
|
||||
import { CoreEditorComponentsModule } from '@core/editor/components/components.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -26,6 +27,7 @@ import { AddonModForumNewDiscussionPage } from './new-discussion';
|
|||
imports: [
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule,
|
||||
CoreEditorComponentsModule,
|
||||
IonicPageModule.forChild(AddonModForumNewDiscussionPage),
|
||||
TranslateModule.forChild()
|
||||
],
|
||||
|
|
|
@ -25,7 +25,7 @@ import { CoreTextUtilsProvider } from '@providers/utils/text';
|
|||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { CoreFileUploaderProvider } from '@core/fileuploader/providers/fileuploader';
|
||||
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
||||
import { CoreRichTextEditorComponent } from '@components/rich-text-editor/rich-text-editor.ts';
|
||||
import { CoreEditorRichTextEditorComponent } from '@core/editor/components/rich-text-editor/rich-text-editor.ts';
|
||||
import { AddonModForumProvider } from '../../providers/forum';
|
||||
import { AddonModForumOfflineProvider } from '../../providers/offline';
|
||||
import { AddonModForumHelperProvider } from '../../providers/helper';
|
||||
|
@ -41,7 +41,7 @@ import { AddonModForumSyncProvider } from '../../providers/sync';
|
|||
})
|
||||
export class AddonModForumNewDiscussionPage implements OnDestroy {
|
||||
|
||||
@ViewChild(CoreRichTextEditorComponent) messageEditor: CoreRichTextEditorComponent;
|
||||
@ViewChild(CoreEditorRichTextEditorComponent) messageEditor: CoreEditorRichTextEditorComponent;
|
||||
|
||||
component = AddonModForumProvider.COMPONENT;
|
||||
messageControl = new FormControl();
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
</ion-item>
|
||||
<ion-item>
|
||||
<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" [component]="component" [componentId]="glossary.cmid"></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" [autoSave]="true" contextLevel="module" [contextInstanceId]="module.id" elementId="definition_editor" [draftExtraParams]="editorExtraParams"></core-rich-text-editor>
|
||||
</ion-item>
|
||||
<ion-item *ngIf="categories.length > 0">
|
||||
<ion-label stacked id="addon-mod-glossary-categories-label">{{ 'addon.mod_glossary.categories' | translate }}</ion-label>
|
||||
|
|
|
@ -18,6 +18,7 @@ import { TranslateModule } from '@ngx-translate/core';
|
|||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
import { AddonModGlossaryEditPage } from './edit';
|
||||
import { CoreEditorComponentsModule } from '@core/editor/components/components.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -26,6 +27,7 @@ import { AddonModGlossaryEditPage } from './edit';
|
|||
imports: [
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule,
|
||||
CoreEditorComponentsModule,
|
||||
IonicPageModule.forChild(AddonModGlossaryEditPage),
|
||||
TranslateModule.forChild()
|
||||
],
|
||||
|
|
|
@ -51,6 +51,7 @@ export class AddonModGlossaryEditPage implements OnInit {
|
|||
attachments = [];
|
||||
definitionControl = new FormControl();
|
||||
categories = [];
|
||||
editorExtraParams: {[name: string]: any} = {};
|
||||
|
||||
protected courseId: number;
|
||||
protected module: any;
|
||||
|
@ -113,6 +114,10 @@ export class AddonModGlossaryEditPage implements OnInit {
|
|||
this.originalData.files = files.slice();
|
||||
});
|
||||
}
|
||||
|
||||
if (entry.id) {
|
||||
this.editorExtraParams.id = entry.id;
|
||||
}
|
||||
}
|
||||
|
||||
this.definitionControl.setValue(this.entry.definition);
|
||||
|
|
|
@ -34,7 +34,7 @@
|
|||
|
||||
<!-- Input password for protected lessons. -->
|
||||
<ion-card *ngIf="askPassword">
|
||||
<form ion-list (ngSubmit)="submitPassword($event, passwordinput)">
|
||||
<form ion-list (ngSubmit)="submitPassword($event, passwordinput)" #passwordForm>
|
||||
<ion-item text-wrap>
|
||||
<ion-label stacked>{{ 'addon.mod_lesson.enterpassword' | translate }}</ion-label>
|
||||
<core-show-password item-content [name]="'password'">
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, Optional, Injector, Input, ViewChild } from '@angular/core';
|
||||
import { Component, Optional, Injector, Input, ViewChild, ElementRef } from '@angular/core';
|
||||
import { Content, NavController } from 'ionic-angular';
|
||||
import { CoreGroupsProvider, CoreGroupInfo } from '@providers/groups';
|
||||
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||
|
@ -35,6 +35,7 @@ import { CoreTabsComponent } from '@components/tabs/tabs';
|
|||
})
|
||||
export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityComponent {
|
||||
@ViewChild(CoreTabsComponent) tabsComponent: CoreTabsComponent;
|
||||
@ViewChild('passwordForm') formElement: ElementRef;
|
||||
|
||||
@Input() group: number; // The group to display.
|
||||
@Input() action: string; // The "action" to display first.
|
||||
|
@ -584,6 +585,8 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
|
|||
this.loaded = true;
|
||||
this.refreshIcon = 'refresh';
|
||||
this.syncIcon = 'sync';
|
||||
|
||||
this.domUtils.triggerFormSubmittedEvent(this.formElement.nativeElement, true, this.siteId);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
</ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content padding class="addon-mod_lesson-password-modal">
|
||||
<form ion-list (ngSubmit)="submitPassword($event, passwordinput)">
|
||||
<form ion-list (ngSubmit)="submitPassword($event, passwordinput)" #passwordForm>
|
||||
<ion-item>
|
||||
<core-show-password item-content [name]="'password'">
|
||||
<ion-label>{{ 'addon.mod_lesson.enterpassword' | translate }}</ion-label>
|
||||
|
|
|
@ -12,8 +12,11 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, ViewChild, ElementRef } from '@angular/core';
|
||||
import { IonicPage, ViewController } from 'ionic-angular';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
|
||||
/**
|
||||
* Modal that asks the password for a lesson.
|
||||
|
@ -24,8 +27,12 @@ import { IonicPage, ViewController } from 'ionic-angular';
|
|||
templateUrl: 'password-modal.html',
|
||||
})
|
||||
export class AddonModLessonPasswordModalPage {
|
||||
@ViewChild('passwordForm') formElement: ElementRef;
|
||||
|
||||
constructor(protected viewCtrl: ViewController) { }
|
||||
constructor(protected viewCtrl: ViewController,
|
||||
protected eventsProvider: CoreEventsProvider,
|
||||
protected sitesProvider: CoreSitesProvider,
|
||||
protected domUtils: CoreDomUtilsProvider) { }
|
||||
|
||||
/**
|
||||
* Send the password back.
|
||||
|
@ -37,6 +44,8 @@ export class AddonModLessonPasswordModalPage {
|
|||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.domUtils.triggerFormSubmittedEvent(this.formElement.nativeElement, false, this.sitesProvider.getCurrentSiteId());
|
||||
|
||||
this.viewCtrl.dismiss(password.value);
|
||||
}
|
||||
|
||||
|
@ -44,6 +53,8 @@ export class AddonModLessonPasswordModalPage {
|
|||
* Close modal.
|
||||
*/
|
||||
closeModal(): void {
|
||||
this.domUtils.triggerFormCancelledEvent(this.formElement.nativeElement, this.sitesProvider.getCurrentSiteId());
|
||||
|
||||
this.viewCtrl.dismiss();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
|
||||
<!-- Question page. -->
|
||||
<!-- We need to set ngIf loaded to make formGroup directive restart every time a page changes, see MOBILE-2540. -->
|
||||
<form *ngIf="question && loaded" ion-list [formGroup]="questionForm">
|
||||
<form *ngIf="question && loaded" ion-list [formGroup]="questionForm" #questionFormEl>
|
||||
<ion-item-divider text-wrap *ngIf="pageContent">
|
||||
<core-format-text [component]="component" [componentId]="lesson.coursemodule" [text]="pageContent" contextLevel="module" [contextInstanceId]="lesson.coursemodule" [courseId]="courseId"></core-format-text>
|
||||
</ion-item-divider>
|
||||
|
@ -57,7 +57,7 @@
|
|||
<!-- Essay. -->
|
||||
<ng-container *ngSwitchCase="'essay'">
|
||||
<ion-item *ngIf="question.textarea">
|
||||
<core-rich-text-editor item-content placeholder="{{ 'addon.mod_lesson.youranswer' | translate }}" [control]="question.control" [component]="component" [componentId]="lesson.coursemodule"></core-rich-text-editor>
|
||||
<core-rich-text-editor item-content placeholder="{{ 'addon.mod_lesson.youranswer' | translate }}" [control]="question.control" [component]="component" [componentId]="lesson.coursemodule" [autoSave]="true" contextLevel="module" [contextInstanceId]="lesson.coursemodule" elementId="answer_editor"></core-rich-text-editor>
|
||||
</ion-item>
|
||||
<ion-item text-wrap *ngIf="!question.textarea && question.useranswer">
|
||||
<p class="item-heading">{{ 'addon.mod_lesson.youranswer' | translate }}</p>
|
||||
|
|
|
@ -18,6 +18,7 @@ import { TranslateModule } from '@ngx-translate/core';
|
|||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
import { AddonModLessonPlayerPage } from './player';
|
||||
import { CoreEditorComponentsModule } from '@core/editor/components/components.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -26,6 +27,7 @@ import { AddonModLessonPlayerPage } from './player';
|
|||
imports: [
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule,
|
||||
CoreEditorComponentsModule,
|
||||
IonicPageModule.forChild(AddonModLessonPlayerPage),
|
||||
TranslateModule.forChild()
|
||||
],
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit, OnDestroy, ViewChild, ChangeDetectorRef } from '@angular/core';
|
||||
import { Component, OnInit, OnDestroy, ViewChild, ChangeDetectorRef, ElementRef } from '@angular/core';
|
||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
import { IonicPage, NavParams, Content, PopoverController, ModalController, Modal, NavController } from 'ionic-angular';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
@ -41,6 +41,7 @@ import { AddonModLessonHelperProvider } from '../../providers/helper';
|
|||
})
|
||||
export class AddonModLessonPlayerPage implements OnInit, OnDestroy {
|
||||
@ViewChild(Content) content: Content;
|
||||
@ViewChild('questionFormEl') formElement: ElementRef;
|
||||
|
||||
component = AddonModLessonProvider.COMPONENT;
|
||||
LESSON_EOL = AddonModLessonProvider.LESSON_EOL;
|
||||
|
@ -136,19 +137,19 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy {
|
|||
*
|
||||
* @return Resolved if we can leave it, rejected if not.
|
||||
*/
|
||||
ionViewCanLeave(): boolean | Promise<void> {
|
||||
async ionViewCanLeave(): Promise<void> {
|
||||
if (this.forceLeave) {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.question && !this.eolData && !this.processData && this.originalData) {
|
||||
// Question shown. Check if there is any change.
|
||||
if (!this.utils.basicLeftCompare(this.questionForm.getRawValue(), this.originalData, 3)) {
|
||||
return this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit'));
|
||||
await this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit'));
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
this.domUtils.triggerFormCancelledEvent(this.formElement.nativeElement, this.sitesProvider.getCurrentSiteId());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -540,15 +541,21 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy {
|
|||
* Process a page, sending some data.
|
||||
*
|
||||
* @param data The data to send.
|
||||
* @param formSubmitted Whether a form was submitted.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected processPage(data: any): Promise<any> {
|
||||
protected processPage(data: any, formSubmitted?: boolean): Promise<any> {
|
||||
this.loaded = false;
|
||||
|
||||
const args = [this.lesson, this.courseId, this.pageData, data, this.password, this.review, this.offline, this.accessInfo,
|
||||
this.jumps];
|
||||
|
||||
return this.callFunction(this.lessonProvider.processPage.bind(this.lessonProvider), args, 6, 8).then((result) => {
|
||||
if (formSubmitted) {
|
||||
this.domUtils.triggerFormSubmittedEvent(this.formElement.nativeElement, result.sent,
|
||||
this.sitesProvider.getCurrentSiteId());
|
||||
}
|
||||
|
||||
if (!this.offline && !this.review && this.lessonProvider.isLessonOffline(this.lesson)) {
|
||||
// Lesson allows offline and the user changed some data in server. Update cached data.
|
||||
const retake = this.accessInfo.attemptscount;
|
||||
|
@ -637,7 +644,7 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy {
|
|||
// Use getRawValue to include disabled values.
|
||||
const data = this.lessonHelper.prepareQuestionData(this.question, this.questionForm.getRawValue());
|
||||
|
||||
this.processPage(data).finally(() => {
|
||||
this.processPage(data, true).finally(() => {
|
||||
this.loaded = true;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -3089,6 +3089,7 @@ export class AddonModLessonProvider {
|
|||
result.warnings = [];
|
||||
result.displaymenu = pageData.displaymenu; // Keep the same value since we can't calculate it in offline.
|
||||
result.messages = this.getPageProcessMessages(lesson, accessInfo, result, review, jumps);
|
||||
result.sent = false;
|
||||
Object.assign(result, calculatedData);
|
||||
|
||||
return result;
|
||||
|
@ -3104,6 +3105,8 @@ export class AddonModLessonProvider {
|
|||
review: review
|
||||
}, this.sitesProvider.getCurrentSiteId());
|
||||
|
||||
response.sent = true;
|
||||
|
||||
return response;
|
||||
});
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@
|
|||
</div>
|
||||
|
||||
<!-- Questions -->
|
||||
<form name="addon-mod_quiz-player-form" *ngIf="questions && questions.length && !quizAborted && !showSummary">
|
||||
<form name="addon-mod_quiz-player-form" *ngIf="questions && questions.length && !quizAborted && !showSummary" #quizForm>
|
||||
<div *ngFor="let question of questions">
|
||||
<ion-card id="addon-mod_quiz-question-{{question.slot}}">
|
||||
<!-- "Header" of the question. -->
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit, OnDestroy, ViewChild, ChangeDetectorRef, ViewChildren, QueryList } from '@angular/core';
|
||||
import { Component, OnInit, OnDestroy, ViewChild, ChangeDetectorRef, ViewChildren, QueryList, ElementRef } from '@angular/core';
|
||||
import { IonicPage, NavParams, Content, PopoverController, ModalController, Modal, NavController } from 'ionic-angular';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
|
@ -41,6 +41,7 @@ import { Subscription } from 'rxjs';
|
|||
export class AddonModQuizPlayerPage implements OnInit, OnDestroy {
|
||||
@ViewChild(Content) content: Content;
|
||||
@ViewChildren(CoreQuestionComponent) questionComponents: QueryList<CoreQuestionComponent>;
|
||||
@ViewChild('quizForm') formElement: ElementRef;
|
||||
|
||||
quiz: any; // The quiz the attempt belongs to.
|
||||
attempt: any; // The attempt being attempted.
|
||||
|
@ -136,26 +137,28 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy {
|
|||
*
|
||||
* @return Resolved if we can leave it, rejected if not.
|
||||
*/
|
||||
ionViewCanLeave(): boolean | Promise<void> {
|
||||
async ionViewCanLeave(): Promise<void> {
|
||||
if (this.forceLeave) {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.questions && this.questions.length && !this.showSummary) {
|
||||
// Save answers.
|
||||
const modal = this.domUtils.showModalLoading('core.sending', true);
|
||||
|
||||
return this.processAttempt(false, false).catch(() => {
|
||||
try {
|
||||
await this.processAttempt(false, false);
|
||||
} catch (error) {
|
||||
// Save attempt failed. Show confirmation.
|
||||
modal.dismiss();
|
||||
|
||||
return this.domUtils.showConfirm(this.translate.instant('addon.mod_quiz.confirmleavequizonerror'));
|
||||
}).finally(() => {
|
||||
modal.dismiss();
|
||||
});
|
||||
}
|
||||
await this.domUtils.showConfirm(this.translate.instant('addon.mod_quiz.confirmleavequizonerror'));
|
||||
|
||||
return Promise.resolve();
|
||||
this.domUtils.triggerFormCancelledEvent(this.formElement.nativeElement, this.sitesProvider.getCurrentSiteId());
|
||||
} finally {
|
||||
modal.dismiss();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -585,6 +588,9 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy {
|
|||
// Answers saved, cancel auto save.
|
||||
this.autoSave.cancelAutoSave();
|
||||
this.autoSave.hideAutoSaveError();
|
||||
|
||||
this.domUtils.triggerFormSubmittedEvent(this.formElement.nativeElement, !this.offline,
|
||||
this.sitesProvider.getCurrentSiteId());
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
</ion-header>
|
||||
<ion-content padding class="addon-mod_quiz-preflight-modal">
|
||||
<core-loading [hideUntil]="loaded">
|
||||
<form ion-list [formGroup]="preflightForm" (ngSubmit)="sendData($event)">
|
||||
<form ion-list [formGroup]="preflightForm" (ngSubmit)="sendData($event)" #preflightFormEl>
|
||||
<!-- Access rules. -->
|
||||
<ng-container *ngFor="let data of accessRulesData; let last = last">
|
||||
<core-dynamic-component [component]="data.component" [data]="data.data">
|
||||
|
|
|
@ -12,10 +12,11 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit, Injector, ViewChild } from '@angular/core';
|
||||
import { Component, OnInit, Injector, ViewChild, ElementRef } from '@angular/core';
|
||||
import { IonicPage, ViewController, NavParams, Content } from 'ionic-angular';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { AddonModQuizAccessRuleDelegate } from '../../providers/access-rules-delegate';
|
||||
|
@ -31,6 +32,7 @@ import { AddonModQuizAccessRuleDelegate } from '../../providers/access-rules-del
|
|||
export class AddonModQuizPreflightModalPage implements OnInit {
|
||||
|
||||
@ViewChild(Content) content: Content;
|
||||
@ViewChild('preflightFormEl') formElement: ElementRef;
|
||||
|
||||
preflightForm: FormGroup;
|
||||
title: string;
|
||||
|
@ -43,9 +45,15 @@ export class AddonModQuizPreflightModalPage implements OnInit {
|
|||
protected siteId: string;
|
||||
protected rules: string[];
|
||||
|
||||
constructor(params: NavParams, fb: FormBuilder, translate: TranslateService, sitesProvider: CoreSitesProvider,
|
||||
protected viewCtrl: ViewController, protected accessRuleDelegate: AddonModQuizAccessRuleDelegate,
|
||||
protected injector: Injector, protected domUtils: CoreDomUtilsProvider) {
|
||||
constructor(params: NavParams,
|
||||
fb: FormBuilder,
|
||||
translate: TranslateService,
|
||||
sitesProvider: CoreSitesProvider,
|
||||
protected viewCtrl: ViewController,
|
||||
protected accessRuleDelegate: AddonModQuizAccessRuleDelegate,
|
||||
protected injector: Injector,
|
||||
protected domUtils: CoreDomUtilsProvider,
|
||||
protected eventsProvider: CoreEventsProvider) {
|
||||
|
||||
this.title = params.get('title') || translate.instant('addon.mod_quiz.startattempt');
|
||||
this.quiz = params.get('quiz');
|
||||
|
@ -112,6 +120,8 @@ export class AddonModQuizPreflightModalPage implements OnInit {
|
|||
this.domUtils.showErrorModal('core.errorinvalidform', true);
|
||||
}
|
||||
} else {
|
||||
this.domUtils.triggerFormSubmittedEvent(this.formElement.nativeElement, false, this.siteId);
|
||||
|
||||
this.viewCtrl.dismiss(this.preflightForm.value);
|
||||
}
|
||||
}
|
||||
|
@ -120,6 +130,8 @@ export class AddonModQuizPreflightModalPage implements OnInit {
|
|||
* Close modal.
|
||||
*/
|
||||
closeModal(): void {
|
||||
this.domUtils.triggerFormCancelledEvent(this.formElement.nativeElement, this.siteId);
|
||||
|
||||
this.viewCtrl.dismiss();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,13 +11,13 @@
|
|||
</ion-header>
|
||||
<ion-content>
|
||||
<core-loading [hideUntil]="loaded">
|
||||
<form ion-list [formGroup]="pageForm">
|
||||
<form ion-list [formGroup]="pageForm" #editPageForm>
|
||||
<ion-item text-wrap *ngIf="canEditTitle" class="item-title">
|
||||
<ion-input name="title" type="text" [placeholder]="'addon.mod_wiki.newpagetitle' | translate" [formControlName]="'title'"></ion-input>
|
||||
</ion-item>
|
||||
|
||||
<ion-item>
|
||||
<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" [autoSave]="true" contextLevel="module" [contextInstanceId]="module.id || 0" elementId="newcontent_editor" [draftExtraParams]="editorExtraParams"></core-rich-text-editor>
|
||||
</ion-item>
|
||||
|
||||
<ion-item *ngIf="wrongVersionLock" text-center class="addon-mod_wiki-wrongversionlock" >
|
||||
|
|
|
@ -18,6 +18,7 @@ import { TranslateModule } from '@ngx-translate/core';
|
|||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
import { AddonModWikiEditPage } from './edit';
|
||||
import { CoreEditorComponentsModule } from '@core/editor/components/components.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -26,6 +27,7 @@ import { AddonModWikiEditPage } from './edit';
|
|||
imports: [
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule,
|
||||
CoreEditorComponentsModule,
|
||||
IonicPageModule.forChild(AddonModWikiEditPage),
|
||||
TranslateModule.forChild()
|
||||
],
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core';
|
||||
import { IonicPage, NavController, NavParams } from 'ionic-angular';
|
||||
import { FormControl, FormGroup, FormBuilder } from '@angular/forms';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
@ -37,6 +37,8 @@ import { AddonModWikiSyncProvider, AddonModWikiSyncSubwikiResult } from '../../p
|
|||
})
|
||||
export class AddonModWikiEditPage implements OnInit, OnDestroy {
|
||||
|
||||
@ViewChild('editPageForm') formElement: ElementRef;
|
||||
|
||||
title: string; // Title to display.
|
||||
pageForm: FormGroup; // The form group.
|
||||
contentControl: FormControl; // The FormControl for the page content.
|
||||
|
@ -45,6 +47,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy {
|
|||
component = AddonModWikiProvider.COMPONENT; // Component to link the files to.
|
||||
componentId: number; // Component ID to link the files to.
|
||||
wrongVersionLock: boolean; // Whether the page lock doesn't match the initial one.
|
||||
editorExtraParams: {[name: string]: any} = {};
|
||||
|
||||
protected module: any; // Wiki module instance.
|
||||
protected courseId: number; // Course the wiki belongs to.
|
||||
|
@ -101,6 +104,20 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy {
|
|||
|
||||
// Block the wiki so it cannot be synced.
|
||||
this.syncProvider.blockOperation(this.component, this.blockId);
|
||||
|
||||
if (!this.module.id) {
|
||||
this.editorExtraParams.type = 'wiki';
|
||||
}
|
||||
|
||||
if (this.pageId) {
|
||||
this.editorExtraParams.pageid = this.pageId;
|
||||
|
||||
if (this.section) {
|
||||
this.editorExtraParams.section = this.section;
|
||||
}
|
||||
} else if (pageTitle) {
|
||||
this.editorExtraParams.pagetitle = pageTitle;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -329,17 +346,17 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy {
|
|||
*
|
||||
* @return Resolved if we can leave it, rejected if not.
|
||||
*/
|
||||
ionViewCanLeave(): boolean | Promise<void> {
|
||||
async ionViewCanLeave(): Promise<void> {
|
||||
if (this.forceLeave) {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if data has changed.
|
||||
if (this.hasDataChanged()) {
|
||||
return this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit'));
|
||||
await this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit'));
|
||||
}
|
||||
|
||||
return true;
|
||||
this.domUtils.triggerFormCancelledEvent(this.formElement.nativeElement, this.sitesProvider.getCurrentSiteId());
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -408,6 +425,10 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy {
|
|||
if (this.editing) {
|
||||
// Edit existing page.
|
||||
promise = this.wikiProvider.editPage(this.pageId, text, this.section).then(() => {
|
||||
|
||||
this.domUtils.triggerFormSubmittedEvent(this.formElement.nativeElement, true,
|
||||
this.sitesProvider.getCurrentSiteId());
|
||||
|
||||
// Invalidate page since it changed.
|
||||
return this.wikiProvider.invalidatePage(this.pageId).then(() => {
|
||||
return this.gotoPage(title);
|
||||
|
@ -441,6 +462,10 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy {
|
|||
let wikiId = this.wikiId || (this.module && this.module.instance);
|
||||
|
||||
return this.wikiProvider.newPage(title, text, this.subwikiId, wikiId, this.userId, this.groupId).then((id) => {
|
||||
|
||||
this.domUtils.triggerFormSubmittedEvent(this.formElement.nativeElement, id > 0,
|
||||
this.sitesProvider.getCurrentSiteId());
|
||||
|
||||
if (id > 0) {
|
||||
// Page was created, get its data and go to the page.
|
||||
this.pageId = id;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<h3 padding>{{ 'addon.mod_workshop.assessmentform' | translate }}</h3>
|
||||
|
||||
<form name="mma-mod_workshop-assessment-form">
|
||||
<form name="mma-mod_workshop-assessment-form" #assessmentForm>
|
||||
<core-loading [hideUntil]="assessmentStrategyLoaded">
|
||||
<ng-container *ngIf="componentClass && assessmentStrategyLoaded">
|
||||
<core-dynamic-component [component]="componentClass" [data]="data"></core-dynamic-component>
|
||||
|
@ -16,7 +16,7 @@
|
|||
</ion-item>
|
||||
<ion-item stacked *ngIf="edit">
|
||||
<ion-label stacked [core-mark-required]="overallFeedkbackRequired">{{ 'addon.mod_workshop.feedbackauthor' | translate }}</ion-label>
|
||||
<core-rich-text-editor item-content [control]="feedbackControl" [component]="component" [componentId]="workshop.coursemodule" (contentChanged)="onFeedbackChange($event)"></core-rich-text-editor>
|
||||
<core-rich-text-editor item-content [control]="feedbackControl" [component]="component" [componentId]="workshop.coursemodule" [autoSave]="true" contextLevel="module" [contextInstanceId]="workshop.coursemodule" elementId="feedbackauthor_editor" [draftExtraParams]="{asid: assessmentId}" (contentChanged)="onFeedbackChange($event)"></core-rich-text-editor>
|
||||
<core-input-errors item-content *ngIf="overallFeedkbackRequired && fieldErrors && fieldErrors['feedbackauthor']" [errorText]="fieldErrors['feedbackauthor']"></core-input-errors>
|
||||
</ion-item>
|
||||
<core-attachments *ngIf="edit && workshop.overallfeedbackfiles" [files]="data.assessment.feedbackattachmentfiles" [maxSize]="workshop.overallfeedbackmaxbytes"
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, Input, OnInit, Injector } from '@angular/core';
|
||||
import { Component, Input, OnInit, Injector, ViewChild, ElementRef } from '@angular/core';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreSyncProvider } from '@providers/sync';
|
||||
|
@ -44,6 +44,8 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit {
|
|||
@Input() strategy: string;
|
||||
@Input() edit?: boolean;
|
||||
|
||||
@ViewChild('assessmentForm') formElement: ElementRef;
|
||||
|
||||
componentClass: any;
|
||||
data = {
|
||||
workshopId: 0,
|
||||
|
@ -292,7 +294,7 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit {
|
|||
// Save assessment in offline.
|
||||
return this.workshopOffline.saveAssessment(this.workshop.id, this.assessmentId, this.workshop.course,
|
||||
assessmentData).then(() => {
|
||||
// Don't return anything.
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -301,6 +303,9 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit {
|
|||
return this.workshopProvider.updateAssessment(this.workshop.id, this.assessmentId, this.workshop.course,
|
||||
assessmentData, false, allowOffline);
|
||||
}).then((grade) => {
|
||||
|
||||
this.domUtils.triggerFormSubmittedEvent(this.formElement.nativeElement, !!grade, this.sitesProvider.getCurrentSiteId());
|
||||
|
||||
const promises = [];
|
||||
|
||||
// If sent to the server, invalidate and clean.
|
||||
|
|
|
@ -24,6 +24,7 @@ import { AddonModWorkshopIndexComponent } from './index/index';
|
|||
import { AddonModWorkshopSubmissionComponent } from './submission/submission';
|
||||
import { AddonModWorkshopAssessmentComponent } from './assessment/assessment';
|
||||
import { AddonModWorkshopAssessmentStrategyComponent } from './assessment-strategy/assessment-strategy';
|
||||
import { CoreEditorComponentsModule } from '@core/editor/components/components.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -39,7 +40,8 @@ import { AddonModWorkshopAssessmentStrategyComponent } from './assessment-strate
|
|||
CoreComponentsModule,
|
||||
CoreDirectivesModule,
|
||||
CorePipesModule,
|
||||
CoreCourseComponentsModule
|
||||
CoreCourseComponentsModule,
|
||||
CoreEditorComponentsModule,
|
||||
],
|
||||
providers: [
|
||||
],
|
||||
|
|
|
@ -38,7 +38,7 @@
|
|||
|
||||
<addon-mod-workshop-assessment-strategy *ngIf="assessment && assessmentId && showGrade(assessment.grade) && workshop && access" [workshop]="workshop" [access]="access" [assessmentId]="assessmentId" [userId]="profile && profile.id" [strategy]="strategy"></addon-mod-workshop-assessment-strategy>
|
||||
|
||||
<form ion-list [formGroup]="evaluateForm" *ngIf="evaluating">
|
||||
<form ion-list [formGroup]="evaluateForm" *ngIf="evaluating" #evaluateFormEl>
|
||||
<ion-item text-wrap>
|
||||
<h2>{{ 'addon.mod_workshop.assessmentsettings' | translate }}</h2>
|
||||
</ion-item>
|
||||
|
@ -60,7 +60,7 @@
|
|||
</ion-item>
|
||||
<ion-item *ngIf="access.canoverridegrades">
|
||||
<ion-label stacked>{{ 'addon.mod_workshop.feedbackreviewer' | translate }}</ion-label>
|
||||
<core-rich-text-editor item-content [control]="evaluateForm.controls['text']" formControlName="text"></core-rich-text-editor>
|
||||
<core-rich-text-editor item-content [control]="evaluateForm.controls['text']" formControlName="text" [autoSave]="true" contextLevel="module" [contextInstanceId]="workshop.coursemodule" elementId="feedbackreviewer_editor" [draftExtraParams]="{asid: assessmentId}"></core-rich-text-editor>
|
||||
</ion-item>
|
||||
</form>
|
||||
<ion-list *ngIf="!evaluating && evaluate && evaluate.text">
|
||||
|
|
|
@ -19,6 +19,7 @@ import { CoreComponentsModule } from '@components/components.module';
|
|||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
import { AddonModWorkshopComponentsModule } from '../../components/components.module';
|
||||
import { AddonModWorkshopAssessmentPage } from './assessment';
|
||||
import { CoreEditorComponentsModule } from '@core/editor/components/components.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -28,6 +29,7 @@ import { AddonModWorkshopAssessmentPage } from './assessment';
|
|||
CoreDirectivesModule,
|
||||
CoreComponentsModule,
|
||||
AddonModWorkshopComponentsModule,
|
||||
CoreEditorComponentsModule,
|
||||
IonicPageModule.forChild(AddonModWorkshopAssessmentPage),
|
||||
TranslateModule.forChild()
|
||||
],
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core';
|
||||
import { IonicPage, NavParams, NavController } from 'ionic-angular';
|
||||
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
@ -39,6 +39,8 @@ import { AddonModWorkshopSyncProvider } from '../../providers/sync';
|
|||
})
|
||||
export class AddonModWorkshopAssessmentPage implements OnInit, OnDestroy {
|
||||
|
||||
@ViewChild('evaluateFormEl') formElement: ElementRef;
|
||||
|
||||
assessment: any;
|
||||
submission: any;
|
||||
profile: any;
|
||||
|
@ -118,17 +120,19 @@ export class AddonModWorkshopAssessmentPage implements OnInit, OnDestroy {
|
|||
*
|
||||
* @return Resolved if we can leave it, rejected if not.
|
||||
*/
|
||||
ionViewCanLeave(): boolean | Promise<void> {
|
||||
async ionViewCanLeave(): Promise<void> {
|
||||
if (this.forceLeave || !this.evaluating) {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.hasEvaluationChanged()) {
|
||||
return Promise.resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
// Show confirmation if some data has been modified.
|
||||
return this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit'));
|
||||
await this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit'));
|
||||
|
||||
this.domUtils.triggerFormCancelledEvent(this.formElement.nativeElement, this.siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -340,7 +344,10 @@ export class AddonModWorkshopAssessmentPage implements OnInit, OnDestroy {
|
|||
|
||||
// Try to send it to server.
|
||||
return this.workshopProvider.evaluateAssessment(this.workshopId, this.assessmentId, this.courseId, inputData.text,
|
||||
inputData.weight, inputData.grade).then(() => {
|
||||
inputData.weight, inputData.grade).then((result) => {
|
||||
|
||||
this.domUtils.triggerFormSubmittedEvent(this.formElement.nativeElement, !!result, this.siteId);
|
||||
|
||||
const data = {
|
||||
workshopId: this.workshopId,
|
||||
assessmentId: this.assessmentId,
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
</ion-header>
|
||||
<ion-content>
|
||||
<core-loading [hideUntil]="loaded">
|
||||
<form ion-list [formGroup]="editForm" *ngIf="workshop">
|
||||
<form ion-list [formGroup]="editForm" *ngIf="workshop" #editFormEl>
|
||||
<ion-item text-wrap>
|
||||
<ion-label stacked core-mark-required="true">{{ 'addon.mod_workshop.submissiontitle' | translate }}</ion-label>
|
||||
<ion-input name="title" type="text" [placeholder]="'addon.mod_workshop.submissiontitle' | translate" formControlName="title"></ion-input>
|
||||
|
@ -18,7 +18,7 @@
|
|||
|
||||
<ion-item *ngIf="textAvailable">
|
||||
<ion-label stacked [core-mark-required]="textRequired">{{ 'addon.mod_workshop.submissioncontent' | translate }}</ion-label>
|
||||
<core-rich-text-editor item-content [control]="editForm.controls['content']" formControlName="content" [placeholder]="'addon.mod_workshop.submissioncontent' | translate" name="content" [component]="component" [componentId]="componentId"></core-rich-text-editor>
|
||||
<core-rich-text-editor item-content [control]="editForm.controls['content']" formControlName="content" [placeholder]="'addon.mod_workshop.submissioncontent' | translate" name="content" [component]="component" [componentId]="componentId" [autoSave]="true" contextLevel="module" [contextInstanceId]="module.id" elementId="content_editor" [draftExtraParams]="editorExtraParams"></core-rich-text-editor>
|
||||
</ion-item>
|
||||
|
||||
<core-attachments *ngIf="fileAvailable" [files]="submission.attachmentfiles" [maxSize]="workshop.maxbytes" [maxSubmissions]="workshop.nattachments" [component]="component" [componentId]="workshop.coursemodule" allowOffline="true" [acceptedTypes]="workshop.submissionfiletypes" [required]="fileRequired"></core-attachments>
|
||||
|
|
|
@ -18,6 +18,7 @@ import { TranslateModule } from '@ngx-translate/core';
|
|||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
import { AddonModWorkshopEditSubmissionPage } from './edit-submission';
|
||||
import { CoreEditorComponentsModule } from '@core/editor/components/components.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -26,6 +27,7 @@ import { AddonModWorkshopEditSubmissionPage } from './edit-submission';
|
|||
imports: [
|
||||
CoreDirectivesModule,
|
||||
CoreComponentsModule,
|
||||
CoreEditorComponentsModule,
|
||||
IonicPageModule.forChild(AddonModWorkshopEditSubmissionPage),
|
||||
TranslateModule.forChild()
|
||||
],
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Component, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core';
|
||||
import { IonicPage, NavParams, NavController } from 'ionic-angular';
|
||||
import { FormGroup, FormBuilder, Validators } from '@angular/forms';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
@ -37,6 +37,8 @@ import { AddonModWorkshopOfflineProvider } from '../../providers/offline';
|
|||
})
|
||||
export class AddonModWorkshopEditSubmissionPage implements OnInit, OnDestroy {
|
||||
|
||||
@ViewChild('editFormEl') formElement: ElementRef;
|
||||
|
||||
module: any;
|
||||
courseId: number;
|
||||
access: any;
|
||||
|
@ -51,6 +53,7 @@ export class AddonModWorkshopEditSubmissionPage implements OnInit, OnDestroy {
|
|||
component = AddonModWorkshopProvider.COMPONENT;
|
||||
componentId: number;
|
||||
editForm: FormGroup; // The form group.
|
||||
editorExtraParams: {[name: string]: any} = {};
|
||||
|
||||
protected workshopId: number;
|
||||
protected submissionId: number;
|
||||
|
@ -86,6 +89,10 @@ export class AddonModWorkshopEditSubmissionPage implements OnInit, OnDestroy {
|
|||
this.editForm = new FormGroup({});
|
||||
this.editForm.addControl('title', this.fb.control('', Validators.required));
|
||||
this.editForm.addControl('content', this.fb.control(''));
|
||||
|
||||
if (this.submissionId) {
|
||||
this.editorExtraParams.id = this.submissionId;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -105,27 +112,23 @@ export class AddonModWorkshopEditSubmissionPage implements OnInit, OnDestroy {
|
|||
*
|
||||
* @return Resolved if we can leave it, rejected if not.
|
||||
*/
|
||||
ionViewCanLeave(): boolean | Promise<void> {
|
||||
async ionViewCanLeave(): Promise<void> {
|
||||
if (this.forceLeave) {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
let promise;
|
||||
|
||||
// Check if data has changed.
|
||||
if (!this.hasDataChanged()) {
|
||||
promise = Promise.resolve();
|
||||
} else {
|
||||
if (this.hasDataChanged()) {
|
||||
// Show confirmation if some data has been modified.
|
||||
promise = this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit'));
|
||||
await this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit'));
|
||||
}
|
||||
|
||||
return promise.then(() => {
|
||||
if (this.submission.attachmentfiles) {
|
||||
// Delete the local files from the tmp folder.
|
||||
this.fileUploaderProvider.clearTmpFiles(this.submission.attachmentfiles);
|
||||
}
|
||||
});
|
||||
if (this.submission.attachmentfiles) {
|
||||
// Delete the local files from the tmp folder.
|
||||
this.fileUploaderProvider.clearTmpFiles(this.submission.attachmentfiles);
|
||||
}
|
||||
|
||||
this.domUtils.triggerFormCancelledEvent(this.formElement.nativeElement, this.siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -347,7 +350,7 @@ export class AddonModWorkshopEditSubmissionPage implements OnInit, OnDestroy {
|
|||
// Save submission in offline.
|
||||
return this.workshopOffline.saveSubmission(this.workshopId, this.courseId, inputData.title,
|
||||
inputData.content, attachmentsId, submissionId, 'update').then(() => {
|
||||
// Don't return anything.
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -360,8 +363,8 @@ export class AddonModWorkshopEditSubmissionPage implements OnInit, OnDestroy {
|
|||
if (saveOffline) {
|
||||
// Save submission in offline.
|
||||
return this.workshopOffline.saveSubmission(this.workshopId, this.courseId, inputData.title, inputData.content,
|
||||
attachmentsId, submissionId, 'add').then(() => {
|
||||
// Don't return anything.
|
||||
attachmentsId, submissionId, 'add').then(() => {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -370,6 +373,9 @@ export class AddonModWorkshopEditSubmissionPage implements OnInit, OnDestroy {
|
|||
return this.workshopProvider.addSubmission(this.workshopId, this.courseId, inputData.title, inputData.content,
|
||||
attachmentsId, undefined, submissionId, allowOffline);
|
||||
}).then((newSubmissionId) => {
|
||||
|
||||
this.domUtils.triggerFormSubmittedEvent(this.formElement.nativeElement, !!newSubmissionId, this.siteId);
|
||||
|
||||
const data = {
|
||||
workshopId: this.workshopId,
|
||||
cmId: this.module.cmid
|
||||
|
|
|
@ -65,7 +65,7 @@
|
|||
<addon-mod-workshop-assessment *ngFor="let reviewer of submissionInfo.reviewerof" [assessment]="reviewer" [courseId]="courseId" summary="true" [workshop]="workshop" [access]="access"></addon-mod-workshop-assessment>
|
||||
</ion-list>
|
||||
|
||||
<form ion-list [formGroup]="feedbackForm" *ngIf="canAddFeedback">
|
||||
<form ion-list [formGroup]="feedbackForm" *ngIf="canAddFeedback" #feedbackFormEl>
|
||||
<ion-item text-wrap>
|
||||
<h2>{{ 'addon.mod_workshop.feedbackauthor' | translate }}</h2>
|
||||
</ion-item>
|
||||
|
@ -87,7 +87,7 @@
|
|||
</ion-item>
|
||||
<ion-item>
|
||||
<ion-label stacked>{{ 'addon.mod_workshop.feedbackauthor' | translate }}</ion-label>
|
||||
<core-rich-text-editor item-content [control]="feedbackForm.controls['text']" formControlName="text"></core-rich-text-editor>
|
||||
<core-rich-text-editor item-content [control]="feedbackForm.controls['text']" formControlName="text" [autoSave]="true" contextLevel="module" [contextInstanceId]="module.id" elementId="feedbackauthor_editor" [draftExtraParams]="{id: submissionId}"></core-rich-text-editor>
|
||||
</ion-item>
|
||||
</form>
|
||||
|
||||
|
|
|
@ -19,6 +19,7 @@ import { CoreComponentsModule } from '@components/components.module';
|
|||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
import { AddonModWorkshopComponentsModule } from '../../components/components.module';
|
||||
import { AddonModWorkshopSubmissionPage } from './submission';
|
||||
import { CoreEditorComponentsModule } from '@core/editor/components/components.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -28,6 +29,7 @@ import { AddonModWorkshopSubmissionPage } from './submission';
|
|||
CoreDirectivesModule,
|
||||
CoreComponentsModule,
|
||||
AddonModWorkshopComponentsModule,
|
||||
CoreEditorComponentsModule,
|
||||
IonicPageModule.forChild(AddonModWorkshopSubmissionPage),
|
||||
TranslateModule.forChild()
|
||||
],
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, OnInit, OnDestroy, Optional, ViewChild } from '@angular/core';
|
||||
import { Component, OnInit, OnDestroy, Optional, ViewChild, ElementRef } from '@angular/core';
|
||||
import { Content, IonicPage, NavParams, NavController } from 'ionic-angular';
|
||||
import { FormGroup, FormBuilder } from '@angular/forms';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
@ -41,6 +41,7 @@ import { AddonModWorkshopSyncProvider } from '../../providers/sync';
|
|||
export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy {
|
||||
|
||||
@ViewChild(AddonModWorkshopAssessmentStrategyComponent) assessmentStrategy: AddonModWorkshopAssessmentStrategyComponent;
|
||||
@ViewChild('feedbackFormEl') formElement: ElementRef;
|
||||
|
||||
module: any;
|
||||
workshop: any;
|
||||
|
@ -143,14 +144,16 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy {
|
|||
*
|
||||
* @return Resolved if we can leave it, rejected if not.
|
||||
*/
|
||||
ionViewCanLeave(): boolean | Promise<void> {
|
||||
async ionViewCanLeave(): Promise<void> {
|
||||
const assessmentHasChanged = this.assessmentStrategy && this.assessmentStrategy.hasDataChanged();
|
||||
if (this.forceLeave || (!this.hasEvaluationChanged() && !assessmentHasChanged)) {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Show confirmation if some data has been modified.
|
||||
return this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit'));
|
||||
await this.domUtils.showConfirm(this.translate.instant('core.confirmcanceledit'));
|
||||
|
||||
this.domUtils.triggerFormCancelledEvent(this.formElement.nativeElement, this.siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -444,7 +447,10 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy {
|
|||
|
||||
// Try to send it to server.
|
||||
return this.workshopProvider.evaluateSubmission(this.workshopId, this.submissionId, this.courseId, inputData.text,
|
||||
inputData.published, inputData.grade).then(() => {
|
||||
inputData.published, inputData.grade).then((result) => {
|
||||
|
||||
this.domUtils.triggerFormSubmittedEvent(this.formElement.nativeElement, !!result, this.siteId);
|
||||
|
||||
const data = {
|
||||
workshopId: this.workshopId,
|
||||
cmId: this.module.cmid,
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
</ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<form name="itemEdit" (ngSubmit)="addNote($event)">
|
||||
<form name="itemEdit" (ngSubmit)="addNote($event)" #itemEdit>
|
||||
<ion-item>
|
||||
<ion-label>{{ 'addon.notes.publishstate' | translate }}</ion-label>
|
||||
<ion-select [(ngModel)]="type" name="publishState" interface="popover">
|
||||
|
|
|
@ -12,9 +12,11 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, ViewChild, ElementRef } from '@angular/core';
|
||||
import { IonicPage, ViewController, NavParams } from 'ionic-angular';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { AddonNotesProvider } from '../../providers/notes';
|
||||
|
||||
|
@ -27,14 +29,22 @@ import { AddonNotesProvider } from '../../providers/notes';
|
|||
templateUrl: 'add.html',
|
||||
})
|
||||
export class AddonNotesAddPage {
|
||||
|
||||
@ViewChild('itemEdit') formElement: ElementRef;
|
||||
|
||||
userId: number;
|
||||
courseId: number;
|
||||
type = 'personal';
|
||||
text = '';
|
||||
processing = false;
|
||||
|
||||
constructor(params: NavParams, private viewCtrl: ViewController, private appProvider: CoreAppProvider,
|
||||
private domUtils: CoreDomUtilsProvider, private notesProvider: AddonNotesProvider) {
|
||||
constructor(params: NavParams,
|
||||
protected viewCtrl: ViewController,
|
||||
protected appProvider: CoreAppProvider,
|
||||
protected domUtils: CoreDomUtilsProvider,
|
||||
protected notesProvider: AddonNotesProvider,
|
||||
protected eventsProvider: CoreEventsProvider,
|
||||
protected sitesProvider: CoreSitesProvider) {
|
||||
this.userId = params.get('userId');
|
||||
this.courseId = params.get('courseId');
|
||||
this.type = params.get('type') || 'personal';
|
||||
|
@ -54,6 +64,9 @@ export class AddonNotesAddPage {
|
|||
// Freeze the add note button.
|
||||
this.processing = true;
|
||||
this.notesProvider.addNote(this.userId, this.courseId, this.type, this.text).then((sent) => {
|
||||
|
||||
this.domUtils.triggerFormSubmittedEvent(this.formElement.nativeElement, sent, this.sitesProvider.getCurrentSiteId());
|
||||
|
||||
this.viewCtrl.dismiss({type: this.type, sent: true}).finally(() => {
|
||||
this.domUtils.showToast(sent ? 'addon.notes.eventnotecreated' : 'core.datastoredoffline', true, 3000);
|
||||
});
|
||||
|
@ -69,6 +82,8 @@ export class AddonNotesAddPage {
|
|||
* Close modal.
|
||||
*/
|
||||
closeModal(): void {
|
||||
this.domUtils.triggerFormCancelledEvent(this.formElement.nativeElement, this.sitesProvider.getCurrentSiteId());
|
||||
|
||||
this.viewCtrl.dismiss({type: this.type});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<!-- Plain text textarea. -->
|
||||
<ion-textarea *ngIf="question.isPlainText" class="core-question-textarea" [ngClass]='{"core-monospaced": question.isMonospaced}' placeholder="{{ 'core.question.answer' | translate }}" [attr.name]="question.textarea.name" aria-multiline="true" [ngModel]="question.textarea.text"></ion-textarea>
|
||||
<!-- Rich text editor. -->
|
||||
<core-rich-text-editor item-content *ngIf="!question.isPlainText" placeholder="{{ 'core.question.answer' | translate }}" [control]="formControl" [name]="question.textarea.name" [component]="component" [componentId]="componentId"></core-rich-text-editor>
|
||||
<core-rich-text-editor item-content *ngIf="!question.isPlainText" placeholder="{{ 'core.question.answer' | translate }}" [control]="formControl" [name]="question.textarea.name" [component]="component" [componentId]="componentId" [autoSave]="false"></core-rich-text-editor>
|
||||
</ion-item>
|
||||
|
||||
<!-- Draft files not supported. -->
|
||||
|
|
|
@ -20,6 +20,7 @@ import { CoreComponentsModule } from '@components/components.module';
|
|||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
import { AddonQtypeEssayHandler } from './providers/handler';
|
||||
import { AddonQtypeEssayComponent } from './component/essay';
|
||||
import { CoreEditorComponentsModule } from '@core/editor/components/components.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -29,7 +30,8 @@ import { AddonQtypeEssayComponent } from './component/essay';
|
|||
IonicModule,
|
||||
TranslateModule.forChild(),
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule
|
||||
CoreDirectivesModule,
|
||||
CoreEditorComponentsModule,
|
||||
],
|
||||
providers: [
|
||||
AddonQtypeEssayHandler
|
||||
|
|
|
@ -9,5 +9,5 @@
|
|||
<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" [autoSave]="true" [contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [elementId]="'profile_field_' + field.shortname"></core-rich-text-editor>
|
||||
</ion-item>
|
|
@ -20,6 +20,7 @@ import { CoreUserProfileFieldDelegate } from '@core/user/providers/user-profile-
|
|||
import { AddonUserProfileFieldTextareaComponent } from './component/textarea';
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
import { CoreDirectivesModule } from '@directives/directives.module';
|
||||
import { CoreEditorComponentsModule } from '@core/editor/components/components.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -29,7 +30,8 @@ import { CoreDirectivesModule } from '@directives/directives.module';
|
|||
IonicModule,
|
||||
TranslateModule.forChild(),
|
||||
CoreComponentsModule,
|
||||
CoreDirectivesModule
|
||||
CoreDirectivesModule,
|
||||
CoreEditorComponentsModule,
|
||||
],
|
||||
providers: [
|
||||
AddonUserProfileFieldTextareaHandler
|
||||
|
|
|
@ -87,6 +87,7 @@ import { CoreTagModule } from '@core/tag/tag.module';
|
|||
import { CoreFilterModule } from '@core/filter/filter.module';
|
||||
import { CoreH5PModule } from '@core/h5p/h5p.module';
|
||||
import { CoreSearchModule } from '@core/search/search.module';
|
||||
import { CoreEditorModule } from '@core/editor/editor.module';
|
||||
|
||||
// Addon modules.
|
||||
import { AddonBadgesModule } from '@addon/badges/badges.module';
|
||||
|
@ -235,6 +236,7 @@ export const WP_PROVIDER: any = null;
|
|||
CoreFilterModule,
|
||||
CoreH5PModule,
|
||||
CoreSearchModule,
|
||||
CoreEditorModule,
|
||||
AddonBadgesModule,
|
||||
AddonBlogModule,
|
||||
AddonCalendarModule,
|
||||
|
|
|
@ -1482,6 +1482,8 @@
|
|||
"core.downloaded": "Downloaded",
|
||||
"core.downloading": "Downloading",
|
||||
"core.edit": "Edit",
|
||||
"core.editor.autosavesucceeded": "Draft saved.",
|
||||
"core.editor.textrecovered": "A draft version of this text was automatically restored.",
|
||||
"core.emptysplit": "This page will appear blank if the left panel is empty or is loading.",
|
||||
"core.error": "Error",
|
||||
"core.errorchangecompletion": "An error occurred while changing the completion status. Please try again.",
|
||||
|
|
|
@ -39,7 +39,6 @@ import { CoreLocalFileComponent } from './local-file/local-file';
|
|||
import { CoreSitePickerComponent } from './site-picker/site-picker';
|
||||
import { CoreTabsComponent } from './tabs/tabs';
|
||||
import { CoreTabComponent } from './tabs/tab';
|
||||
import { CoreRichTextEditorComponent } from './rich-text-editor/rich-text-editor';
|
||||
import { CoreNavBarButtonsComponent } from './navbar-buttons/navbar-buttons';
|
||||
import { CoreDynamicComponent } from './dynamic-component/dynamic-component';
|
||||
import { CoreSendMessageFormComponent } from './send-message-form/send-message-form';
|
||||
|
@ -79,7 +78,6 @@ import { CoreBSTooltipComponent } from './bs-tooltip/bs-tooltip';
|
|||
CoreSitePickerComponent,
|
||||
CoreTabsComponent,
|
||||
CoreTabComponent,
|
||||
CoreRichTextEditorComponent,
|
||||
CoreNavBarButtonsComponent,
|
||||
CoreDynamicComponent,
|
||||
CoreSendMessageFormComponent,
|
||||
|
@ -128,7 +126,6 @@ import { CoreBSTooltipComponent } from './bs-tooltip/bs-tooltip';
|
|||
CoreSitePickerComponent,
|
||||
CoreTabsComponent,
|
||||
CoreTabComponent,
|
||||
CoreRichTextEditorComponent,
|
||||
CoreNavBarButtonsComponent,
|
||||
CoreDynamicComponent,
|
||||
CoreSendMessageFormComponent,
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<form (ngSubmit)="changeName(newFileName, $event)">
|
||||
<form (ngSubmit)="changeName(newFileName, $event)" #nameForm>
|
||||
<a ion-item text-wrap stacked class="item-media" [class.item-input]="editMode" (click)="fileClicked($event)" detail-none>
|
||||
<img [src]="fileIcon" alt="{{fileExtension}}" role="presentation" item-start />
|
||||
|
||||
|
|
|
@ -12,8 +12,10 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, Input, Output, OnInit, EventEmitter } from '@angular/core';
|
||||
import { Component, Input, Output, OnInit, EventEmitter, ViewChild, ElementRef } from '@angular/core';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreFileProvider } from '@providers/file';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype';
|
||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
|
@ -38,6 +40,8 @@ export class CoreLocalFileComponent implements OnInit {
|
|||
@Output() onRename?: EventEmitter<any>; // Will notify when the file is renamed. Receives the FileEntry as the param.
|
||||
@Output() onClick?: EventEmitter<void>; // Will notify when the file is clicked. Only if overrideClick is true.
|
||||
|
||||
@ViewChild('nameForm') formElement: ElementRef;
|
||||
|
||||
fileName: string;
|
||||
fileIcon: string;
|
||||
fileExtension: string;
|
||||
|
@ -47,12 +51,14 @@ export class CoreLocalFileComponent implements OnInit {
|
|||
editMode: boolean;
|
||||
relativePath: string;
|
||||
|
||||
constructor(private mimeUtils: CoreMimetypeUtilsProvider,
|
||||
private utils: CoreUtilsProvider,
|
||||
private textUtils: CoreTextUtilsProvider,
|
||||
private fileProvider: CoreFileProvider,
|
||||
private domUtils: CoreDomUtilsProvider,
|
||||
private timeUtils: CoreTimeUtilsProvider) {
|
||||
constructor(protected mimeUtils: CoreMimetypeUtilsProvider,
|
||||
protected utils: CoreUtilsProvider,
|
||||
protected textUtils: CoreTextUtilsProvider,
|
||||
protected fileProvider: CoreFileProvider,
|
||||
protected domUtils: CoreDomUtilsProvider,
|
||||
protected timeUtils: CoreTimeUtilsProvider,
|
||||
protected sitesProvider: CoreSitesProvider,
|
||||
protected eventsProvider: CoreEventsProvider) {
|
||||
this.onDelete = new EventEmitter();
|
||||
this.onRename = new EventEmitter();
|
||||
this.onClick = new EventEmitter();
|
||||
|
@ -137,6 +143,7 @@ export class CoreLocalFileComponent implements OnInit {
|
|||
if (newName == this.file.name) {
|
||||
// Name hasn't changed, stop.
|
||||
this.editMode = false;
|
||||
this.domUtils.triggerFormCancelledEvent(this.formElement.nativeElement, this.sitesProvider.getCurrentSiteId());
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -152,6 +159,10 @@ export class CoreLocalFileComponent implements OnInit {
|
|||
}).catch(() => {
|
||||
// File doesn't exist, move it.
|
||||
return this.fileProvider.moveFile(this.relativePath, newPath).then((fileEntry) => {
|
||||
|
||||
this.domUtils.triggerFormSubmittedEvent(this.formElement.nativeElement, false,
|
||||
this.sitesProvider.getCurrentSiteId());
|
||||
|
||||
this.editMode = false;
|
||||
this.file = fileEntry;
|
||||
this.loadFileBasicData();
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
<form>
|
||||
<form #messageForm>
|
||||
<textarea class="core-send-message-input" [core-auto-focus]="showKeyboard" [placeholder]="placeholder" rows="1" core-auto-rows [(ngModel)]="message" name="message" (onResize)="textareaResized()" (keydown.enter)="enterClicked($event)" (keydown.control.enter)="enterClicked($event, 'control')" (keydown.meta.enter)="enterClicked($event, 'meta')" aria-multiline="true"></textarea>
|
||||
<ion-buttons end>
|
||||
<button ion-button icon-only clear="true" type="submit" [disabled]="!message || sendDisabled" [attr.aria-label]="'core.send' | translate" [core-suppress-events] (onClick)="submitForm($event)">
|
||||
|
|
|
@ -12,13 +12,14 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
|
||||
import { Component, Input, Output, EventEmitter, OnInit, ViewChild, ElementRef } from '@angular/core';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
import { CoreConfigProvider } from '@providers/config';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreConstants } from '@core/constants';
|
||||
|
||||
/**
|
||||
|
@ -43,10 +44,17 @@ export class CoreSendMessageFormComponent implements OnInit {
|
|||
@Output() onSubmit: EventEmitter<string>; // Send data when submitting the message form.
|
||||
@Output() onResize: EventEmitter<void>; // Emit when resizing the textarea.
|
||||
|
||||
@ViewChild('messageForm') formElement: ElementRef;
|
||||
|
||||
protected sendOnEnter: boolean;
|
||||
|
||||
constructor(private utils: CoreUtilsProvider, private textUtils: CoreTextUtilsProvider, configProvider: CoreConfigProvider,
|
||||
eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, private appProvider: CoreAppProvider) {
|
||||
constructor(protected utils: CoreUtilsProvider,
|
||||
protected textUtils: CoreTextUtilsProvider,
|
||||
configProvider: CoreConfigProvider,
|
||||
protected eventsProvider: CoreEventsProvider,
|
||||
protected sitesProvider: CoreSitesProvider,
|
||||
protected appProvider: CoreAppProvider,
|
||||
protected domUtils: CoreDomUtilsProvider) {
|
||||
|
||||
this.onSubmit = new EventEmitter();
|
||||
this.onResize = new EventEmitter();
|
||||
|
@ -82,6 +90,8 @@ export class CoreSendMessageFormComponent implements OnInit {
|
|||
|
||||
this.message = ''; // Reset the form.
|
||||
|
||||
this.domUtils.triggerFormSubmittedEvent(this.formElement.nativeElement, false, this.sitesProvider.getCurrentSiteId());
|
||||
|
||||
value = this.textUtils.replaceNewLines(value, '<br>');
|
||||
this.onSubmit.emit(value);
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
</ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<form name="itemEdit" (ngSubmit)="addComment($event)">
|
||||
<form name="itemEdit" (ngSubmit)="addComment($event)" #commentForm>
|
||||
<ion-item>
|
||||
<ion-textarea placeholder="{{ 'core.comments.addcomment' | translate }}" rows="5" [(ngModel)]="content" name="content" required="required"></ion-textarea>
|
||||
</ion-item>
|
||||
|
|
|
@ -12,9 +12,11 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, ViewChild, ElementRef } from '@angular/core';
|
||||
import { IonicPage, ViewController, NavParams } from 'ionic-angular';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreCommentsProvider } from '../../providers/comments';
|
||||
|
||||
|
@ -27,6 +29,8 @@ import { CoreCommentsProvider } from '../../providers/comments';
|
|||
templateUrl: 'add.html',
|
||||
})
|
||||
export class CoreCommentsAddPage {
|
||||
@ViewChild('commentForm') formElement: ElementRef;
|
||||
|
||||
protected contextLevel: string;
|
||||
protected instanceId: number;
|
||||
protected componentName: string;
|
||||
|
@ -36,8 +40,13 @@ export class CoreCommentsAddPage {
|
|||
content = '';
|
||||
processing = false;
|
||||
|
||||
constructor(params: NavParams, private viewCtrl: ViewController, private appProvider: CoreAppProvider,
|
||||
private domUtils: CoreDomUtilsProvider, private commentsProvider: CoreCommentsProvider) {
|
||||
constructor(params: NavParams,
|
||||
protected viewCtrl: ViewController,
|
||||
protected appProvider: CoreAppProvider,
|
||||
protected domUtils: CoreDomUtilsProvider,
|
||||
protected commentsProvider: CoreCommentsProvider,
|
||||
protected eventsProvider: CoreEventsProvider,
|
||||
protected sitesProvider: CoreSitesProvider) {
|
||||
this.contextLevel = params.get('contextLevel');
|
||||
this.instanceId = params.get('instanceId');
|
||||
this.componentName = params.get('componentName');
|
||||
|
@ -61,6 +70,10 @@ export class CoreCommentsAddPage {
|
|||
this.processing = true;
|
||||
this.commentsProvider.addComment(this.content, this.contextLevel, this.instanceId, this.componentName, this.itemId,
|
||||
this.area).then((commentsResponse) => {
|
||||
|
||||
this.domUtils.triggerFormSubmittedEvent(this.formElement.nativeElement, !!commentsResponse,
|
||||
this.sitesProvider.getCurrentSiteId());
|
||||
|
||||
this.viewCtrl.dismiss({comments: commentsResponse}).finally(() => {
|
||||
this.domUtils.showToast(commentsResponse ? 'core.comments.eventcommentcreated' : 'core.datastoredoffline', true,
|
||||
3000);
|
||||
|
@ -77,6 +90,7 @@ export class CoreCommentsAddPage {
|
|||
* Close modal.
|
||||
*/
|
||||
closeModal(): void {
|
||||
this.domUtils.triggerFormCancelledEvent(this.formElement.nativeElement, this.sitesProvider.getCurrentSiteId());
|
||||
this.viewCtrl.dismiss();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -54,7 +54,7 @@ export class CoreCommentsProvider {
|
|||
// Convenience function to store a comment to be synchronized later.
|
||||
const storeOffline = (): Promise<any> => {
|
||||
return this.commentsOffline.saveComment(content, contextLevel, instanceId, component, itemId, area, siteId).then(() => {
|
||||
return Promise.resolve(false);
|
||||
return false;
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
</ion-navbar>
|
||||
</ion-header>
|
||||
<ion-content>
|
||||
<form ion-list #f="ngForm" (ngSubmit)="submitPassword($event, f.value.password)">
|
||||
<form ion-list #f="ngForm" (ngSubmit)="submitPassword($event, f.value.password)" #enrolPasswordForm>
|
||||
<ion-item>
|
||||
<core-show-password item-content [name]="'password'">
|
||||
<ion-input text-wrap class="core-ioninput-password" name="password" type="password" placeholder="{{ 'core.courses.password' | translate }}" ngModel [core-auto-focus] [clearOnEdit]="false"></ion-input>
|
||||
|
|
|
@ -12,8 +12,11 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, ViewChild, ElementRef } from '@angular/core';
|
||||
import { IonicPage, ViewController } from 'ionic-angular';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
|
||||
/**
|
||||
* Page that displays a form to enter a password to self enrol in a course.
|
||||
|
@ -24,12 +27,19 @@ import { IonicPage, ViewController } from 'ionic-angular';
|
|||
templateUrl: 'self-enrol-password.html',
|
||||
})
|
||||
export class CoreCoursesSelfEnrolPasswordPage {
|
||||
constructor(private viewCtrl: ViewController) { }
|
||||
|
||||
@ViewChild('enrolPasswordForm') formElement: ElementRef;
|
||||
|
||||
constructor(protected viewCtrl: ViewController,
|
||||
protected eventsProvider: CoreEventsProvider,
|
||||
protected sitesProvider: CoreSitesProvider,
|
||||
protected domUtils: CoreDomUtilsProvider) { }
|
||||
|
||||
/**
|
||||
* Close help modal.
|
||||
*/
|
||||
close(): void {
|
||||
this.domUtils.triggerFormCancelledEvent(this.formElement.nativeElement, this.sitesProvider.getCurrentSiteId());
|
||||
this.viewCtrl.dismiss();
|
||||
}
|
||||
|
||||
|
@ -43,6 +53,8 @@ export class CoreCoursesSelfEnrolPasswordPage {
|
|||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
this.domUtils.triggerFormSubmittedEvent(this.formElement.nativeElement, false, this.sitesProvider.getCurrentSiteId());
|
||||
|
||||
this.viewCtrl.dismiss(password);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { IonicModule } from 'ionic-angular';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
import { CoreEditorRichTextEditorComponent } from './rich-text-editor/rich-text-editor';
|
||||
import { CoreComponentsModule } from '@components/components.module';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
CoreEditorRichTextEditorComponent
|
||||
],
|
||||
imports: [
|
||||
CommonModule,
|
||||
IonicModule,
|
||||
TranslateModule.forChild(),
|
||||
CoreComponentsModule,
|
||||
],
|
||||
providers: [
|
||||
],
|
||||
exports: [
|
||||
CoreEditorRichTextEditorComponent
|
||||
],
|
||||
entryComponents: [
|
||||
CoreEditorRichTextEditorComponent
|
||||
]
|
||||
})
|
||||
export class CoreEditorComponentsModule {}
|
|
@ -1,7 +1,14 @@
|
|||
<div [hidden]="!rteEnabled" #editor contenteditable="true" class="core-rte-editor" tappable (focus)="showToolbar()" (longPress)="showToolbar()" (blur)="hideToolbar($event)" [attr.data-placeholder-text]="placeholder" role="textbox">
|
||||
</div>
|
||||
<div class="core-rte-editor-container" (click)="focusRTE()" [class.toolbar-hidden]="toolbarHidden">
|
||||
<div [hidden]="!rteEnabled" #editor contenteditable="true" class="core-rte-editor" tappable (focus)="showToolbar()" (longPress)="showToolbar()" (blur)="hideToolbar($event)" [attr.data-placeholder-text]="placeholder" role="textbox">
|
||||
</div>
|
||||
|
||||
<ion-textarea [hidden]="rteEnabled" #textarea class="core-textarea" [placeholder]="placeholder" [attr.name]="name" ngControl="control" (ionChange)="onChange($event)" (focus)="showToolbar()" (longPress)="showToolbar()" (blur)="hideToolbar($event)" role="textbox"></ion-textarea>
|
||||
<ion-textarea [hidden]="rteEnabled" #textarea class="core-textarea" [placeholder]="placeholder" [attr.name]="name" ngControl="control" (ionChange)="onChange($event)" (focus)="showToolbar()" (longPress)="showToolbar()" (blur)="hideToolbar($event)" role="textbox"></ion-textarea>
|
||||
|
||||
<div class="core-rte-info-message" *ngIf="infoMessage">
|
||||
<ion-icon name="information-circle"></ion-icon>
|
||||
{{ infoMessage | translate }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div #toolbar class="core-rte-toolbar" [class.toolbar-hidden]="toolbarHidden">
|
||||
<button *ngIf="toolbarArrows" class="toolbar-arrow" [class.toolbar-arrow-hidden]="toolbarPrevHidden" (click)="toolbarPrev($event)" (mousedown)="stopBubble($event)">
|
|
@ -10,12 +10,35 @@ ion-app.app-root core-rich-text-editor {
|
|||
background-color: $gray-darker;
|
||||
}
|
||||
|
||||
.core-rte-editor-container {
|
||||
max-height: calc(100% - 46px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex-grow: 1;
|
||||
&.toolbar-hidden {
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.core-rte-info-message {
|
||||
padding: 5px;
|
||||
border-top: 1px solid $info;
|
||||
background: white;
|
||||
flex-shrink: 1;
|
||||
font-size: 1.4rem;
|
||||
|
||||
.icon {
|
||||
color: $info;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.core-rte-editor, .core-textarea {
|
||||
padding: 2px;
|
||||
margin: 2px;
|
||||
width: 100%;
|
||||
resize: none;
|
||||
background-color: $white;
|
||||
flex-grow: 1;
|
||||
@include darkmode() {
|
||||
background-color: $gray-darker;
|
||||
color: $white;
|
||||
|
@ -147,7 +170,6 @@ ion-app.app-root core-rich-text-editor {
|
|||
border: none;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
body.keyboard-is-open ion-app.app-root core-rich-text-editor {
|
|
@ -21,28 +21,23 @@ import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
|||
import { CoreUrlUtilsProvider } from '@providers/utils/url';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreEditorOfflineProvider } from '../../providers/editor-offline';
|
||||
import { FormControl } from '@angular/forms';
|
||||
import { Subscription } from 'rxjs';
|
||||
|
||||
/**
|
||||
* Directive to display a rich text editor if enabled.
|
||||
* Component to display a rich text editor if enabled.
|
||||
*
|
||||
* If enabled, this directive will show a rich text editor. Otherwise it'll show a regular textarea.
|
||||
*
|
||||
* This directive requires an OBJECT model. The text written in the editor or textarea will be stored inside
|
||||
* a "text" property in that object. This is to ensure 2-way data-binding, since using a string as a model
|
||||
* could be easily broken.
|
||||
* If enabled, this component will show a rich text editor. Otherwise it'll show a regular textarea.
|
||||
*
|
||||
* Example:
|
||||
* <core-rich-text-editor item-content [control]="control" [placeholder]="field.name"></core-rich-text-editor>
|
||||
*
|
||||
* In the example above, the text written in the editor will be stored in newpost.text.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'core-rich-text-editor',
|
||||
templateUrl: 'core-rich-text-editor.html'
|
||||
templateUrl: 'core-editor-rich-text-editor.html'
|
||||
})
|
||||
export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy {
|
||||
export class CoreEditorRichTextEditorComponent implements AfterContentInit, OnDestroy {
|
||||
// Based on: https://github.com/judgewest2000/Ionic3RichText/
|
||||
// @todo: Anchor button, fullscreen...
|
||||
// @todo: Textarea height is not being updated when editor is resized. Height is calculated if any css is changed.
|
||||
|
@ -52,11 +47,19 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy
|
|||
@Input() name = 'core-rich-text-editor'; // Name to set to the textarea.
|
||||
@Input() component?: string; // The component to link the files to.
|
||||
@Input() componentId?: number; // An ID to use in conjunction with the component.
|
||||
@Input() autoSave?: boolean | string; // Whether to auto-save the contents in a draft. Defaults to true.
|
||||
@Input() contextLevel?: string; // The context level of the text.
|
||||
@Input() contextInstanceId?: number; // The instance ID related to the context.
|
||||
@Input() elementId?: string; // An ID to set to the element.
|
||||
@Input() draftExtraParams: {[name: string]: any}; // Extra params to identify the draft.
|
||||
@Output() contentChanged: EventEmitter<string>;
|
||||
|
||||
@ViewChild('editor') editor: ElementRef; // WYSIWYG editor.
|
||||
@ViewChild('textarea') textarea: TextInput; // Textarea editor.
|
||||
|
||||
protected DRAFT_AUTOSAVE_FREQUENCY = 30000;
|
||||
protected RESTORE_MESSAGE_CLEAR_TIME = 6000;
|
||||
protected SAVE_MESSAGE_CLEAR_TIME = 2000;
|
||||
protected element: HTMLDivElement;
|
||||
protected editorElement: HTMLDivElement;
|
||||
protected kbHeight = 0; // Last known keyboard height.
|
||||
|
@ -64,7 +67,8 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy
|
|||
|
||||
protected valueChangeSubscription: Subscription;
|
||||
protected keyboardObs: any;
|
||||
protected initHeightInterval;
|
||||
protected resetObs: any;
|
||||
protected initHeightInterval: NodeJS.Timer;
|
||||
|
||||
rteEnabled = false;
|
||||
editorSupported = true;
|
||||
|
@ -90,16 +94,31 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy
|
|||
ul: 'false',
|
||||
ol: 'false',
|
||||
};
|
||||
infoMessage: string;
|
||||
protected isCurrentView = true;
|
||||
protected toolbarButtonWidth = 40;
|
||||
protected toolbarArrowWidth = 28;
|
||||
protected pageInstance: string;
|
||||
protected autoSaveInterval: NodeJS.Timer;
|
||||
protected hideMessageTimeout: NodeJS.Timer;
|
||||
protected lastDraft = '';
|
||||
protected draftWasRestored = false;
|
||||
protected originalContent: string;
|
||||
|
||||
constructor(private domUtils: CoreDomUtilsProvider, private urlUtils: CoreUrlUtilsProvider,
|
||||
private sitesProvider: CoreSitesProvider, private filepoolProvider: CoreFilepoolProvider,
|
||||
@Optional() private content: Content, elementRef: ElementRef, private events: CoreEventsProvider,
|
||||
private utils: CoreUtilsProvider, private platform: Platform) {
|
||||
constructor(
|
||||
protected domUtils: CoreDomUtilsProvider,
|
||||
protected urlUtils: CoreUrlUtilsProvider,
|
||||
protected sitesProvider: CoreSitesProvider,
|
||||
protected filepoolProvider: CoreFilepoolProvider,
|
||||
@Optional() protected content: Content,
|
||||
elementRef: ElementRef,
|
||||
protected events: CoreEventsProvider,
|
||||
protected utils: CoreUtilsProvider,
|
||||
protected platform: Platform,
|
||||
protected editorOffline: CoreEditorOfflineProvider) {
|
||||
this.contentChanged = new EventEmitter<string>();
|
||||
this.element = elementRef.nativeElement as HTMLDivElement;
|
||||
this.pageInstance = 'app_' + Date.now(); // Generate a "unique" ID based on timestamp.
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -115,6 +134,8 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy
|
|||
// Setup the editor.
|
||||
this.editorElement = this.editor.nativeElement as HTMLDivElement;
|
||||
this.setContent(this.control.value);
|
||||
this.originalContent = this.control.value;
|
||||
this.lastDraft = this.control.value;
|
||||
this.editorElement.onchange = this.onChange.bind(this);
|
||||
this.editorElement.onkeyup = this.onChange.bind(this);
|
||||
this.editorElement.onpaste = this.onChange.bind(this);
|
||||
|
@ -123,7 +144,20 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy
|
|||
|
||||
// Listen for changes on the control to update the editor (if it is updated from outside of this component).
|
||||
this.valueChangeSubscription = this.control.valueChanges.subscribe((param) => {
|
||||
this.setContent(param);
|
||||
if (!this.draftWasRestored || this.originalContent != param) {
|
||||
// Apply the new content.
|
||||
this.setContent(param);
|
||||
this.originalContent = param;
|
||||
this.infoMessage = null;
|
||||
|
||||
// Save a draft so the original content is saved.
|
||||
this.lastDraft = param;
|
||||
this.editorOffline.saveDraft(this.contextLevel, this.contextInstanceId, this.elementId,
|
||||
this.draftExtraParams, this.pageInstance, param, param);
|
||||
} else {
|
||||
// A draft was restored and the content hasn't changed in the site. Use the draft value instead of this one.
|
||||
this.control.setValue(this.lastDraft, {emitEvent: false});
|
||||
}
|
||||
});
|
||||
|
||||
// Use paragraph on enter.
|
||||
|
@ -148,6 +182,20 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy
|
|||
});
|
||||
|
||||
this.updateToolbarButtons();
|
||||
|
||||
if (this.elementId) {
|
||||
// Prepend elementId with 'id_' like in web. Don't use a setter for this because the value shouldn't change.
|
||||
this.elementId = 'id_' + this.elementId;
|
||||
this.element.setAttribute('id', this.elementId);
|
||||
}
|
||||
|
||||
if (this.shouldAutoSaveDrafts()) {
|
||||
this.restoreDraft();
|
||||
|
||||
this.autoSaveDrafts();
|
||||
|
||||
this.deleteDraftOnSubmitOrCancel();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -196,7 +244,7 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy
|
|||
}
|
||||
|
||||
if (height > this.minHeight) {
|
||||
this.element.style.height = this.domUtils.formatPixelsSize(height);
|
||||
this.element.style.height = this.domUtils.formatPixelsSize(height - 1);
|
||||
} else {
|
||||
this.element.style.height = '';
|
||||
}
|
||||
|
@ -549,10 +597,23 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Focus editor when click the area.
|
||||
*
|
||||
* @param e Event
|
||||
*/
|
||||
focusRTE(e?: Event): void {
|
||||
if (this.rteEnabled) {
|
||||
this.editorElement.focus();
|
||||
} else {
|
||||
this.textarea.setFocus();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the toolbar in phone mode.
|
||||
*/
|
||||
hideToolbar($event: any): void {
|
||||
hideToolbar($event: Event): void {
|
||||
this.stopBubble($event);
|
||||
|
||||
if (this.isPhone) {
|
||||
|
@ -673,6 +734,121 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if should auto save drafts.
|
||||
*
|
||||
* @return {boolean} Whether it should auto save drafts.
|
||||
*/
|
||||
protected shouldAutoSaveDrafts(): boolean {
|
||||
return !!this.sitesProvider.getCurrentSite() &&
|
||||
(typeof this.autoSave == 'undefined' || this.utils.isTrueOrOne(this.autoSave)) &&
|
||||
typeof this.contextLevel != 'undefined' &&
|
||||
typeof this.contextInstanceId != 'undefined' &&
|
||||
typeof this.elementId != 'undefined';
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore a draft if there is any.
|
||||
*
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async restoreDraft(): Promise<void> {
|
||||
try {
|
||||
const entry = await this.editorOffline.resumeDraft(this.contextLevel, this.contextInstanceId, this.elementId,
|
||||
this.draftExtraParams, this.pageInstance, this.originalContent);
|
||||
|
||||
if (typeof entry == 'undefined') {
|
||||
// No draft found.
|
||||
return;
|
||||
}
|
||||
|
||||
let draftText = entry.drafttext;
|
||||
|
||||
// Revert untouched editor contents to an empty string.
|
||||
if (draftText == '<p></p>' || draftText == '<p><br></p>' || draftText == '<br>' ||
|
||||
draftText == '<p> </p>' || draftText == '<p><br> </p>') {
|
||||
draftText = '';
|
||||
}
|
||||
|
||||
if (draftText !== '' && draftText != this.control.value) {
|
||||
// Restore the draft.
|
||||
this.control.setValue(draftText, {emitEvent: false});
|
||||
this.setContent(draftText);
|
||||
this.lastDraft = draftText;
|
||||
this.draftWasRestored = true;
|
||||
this.originalContent = entry.originalcontent;
|
||||
|
||||
if (entry.drafttext != entry.originalcontent) {
|
||||
// Notify the user.
|
||||
this.showMessage('core.editor.textrecovered', this.RESTORE_MESSAGE_CLEAR_TIME);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
// Ignore errors, shouldn't happen.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Automatically save drafts every certain time.
|
||||
*/
|
||||
protected autoSaveDrafts(): void {
|
||||
this.autoSaveInterval = setInterval(async () => {
|
||||
const newText = this.control.value;
|
||||
|
||||
if (this.lastDraft == newText) {
|
||||
// Text hasn't changed, nothing to save.
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.editorOffline.saveDraft(this.contextLevel, this.contextInstanceId, this.elementId,
|
||||
this.draftExtraParams, this.pageInstance, newText, this.originalContent);
|
||||
|
||||
// Draft saved, notify the user.
|
||||
this.lastDraft = newText;
|
||||
this.showMessage('core.editor.autosavesucceeded', this.SAVE_MESSAGE_CLEAR_TIME);
|
||||
} catch (error) {
|
||||
// Error saving draft.
|
||||
}
|
||||
}, this.DRAFT_AUTOSAVE_FREQUENCY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the draft when the form is submitted or cancelled.
|
||||
*/
|
||||
protected deleteDraftOnSubmitOrCancel(): void {
|
||||
|
||||
this.resetObs = this.events.on(CoreEventsProvider.FORM_ACTION, async (data) => {
|
||||
const form = this.element.closest('form');
|
||||
|
||||
if (data.form && form && data.form == form) {
|
||||
try {
|
||||
await this.editorOffline.deleteDraft(this.contextLevel, this.contextInstanceId, this.elementId,
|
||||
this.draftExtraParams);
|
||||
} catch (error) {
|
||||
// Error deleting draft. Shouldn't happen.
|
||||
}
|
||||
}
|
||||
}, this.sitesProvider.getCurrentSiteId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a message.
|
||||
*
|
||||
* @param message Identifier of the message to display.
|
||||
* @param timeout Number of milliseconds when to remove the message.
|
||||
*/
|
||||
protected showMessage(message: string, timeout: number): void {
|
||||
clearTimeout(this.hideMessageTimeout);
|
||||
|
||||
this.infoMessage = message;
|
||||
|
||||
this.hideMessageTimeout = setTimeout(() => {
|
||||
this.hideMessageTimeout = null;
|
||||
this.infoMessage = null;
|
||||
}, timeout);
|
||||
}
|
||||
|
||||
/**
|
||||
* User entered the page that contains the component.
|
||||
*/
|
||||
|
@ -698,5 +874,8 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy
|
|||
document.removeEventListener('selectionchange', this.updateToolbarStyles);
|
||||
clearInterval(this.initHeightInterval);
|
||||
this.keyboardObs && this.keyboardObs.off();
|
||||
clearInterval(this.autoSaveInterval);
|
||||
clearTimeout(this.hideMessageTimeout);
|
||||
this.resetObs && this.resetObs.off();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
import { CoreEditorComponentsModule } from './components/components.module';
|
||||
import { CoreEditorOfflineProvider } from './providers/editor-offline';
|
||||
|
||||
// List of providers (without handlers).
|
||||
export const CORE_GRADES_PROVIDERS: any[] = [
|
||||
CoreEditorOfflineProvider,
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
],
|
||||
imports: [
|
||||
CoreEditorComponentsModule,
|
||||
],
|
||||
providers: [
|
||||
CoreEditorOfflineProvider,
|
||||
],
|
||||
})
|
||||
export class CoreEditorModule {
|
||||
constructor(editorOffline: CoreEditorOfflineProvider) {
|
||||
// Inject the helper even if it isn't used here it's instantiated.
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"autosavesucceeded": "Draft saved.",
|
||||
"textrecovered": "A draft version of this text was automatically restored."
|
||||
}
|
|
@ -0,0 +1,277 @@
|
|||
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites';
|
||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
|
||||
/**
|
||||
* Service with features regarding rich text editor in offline.
|
||||
*/
|
||||
@Injectable()
|
||||
export class CoreEditorOfflineProvider {
|
||||
|
||||
protected DRAFT_TABLE = 'editor_draft';
|
||||
|
||||
protected logger;
|
||||
protected siteSchema: CoreSiteSchema = {
|
||||
name: 'CoreEditorProvider',
|
||||
version: 1,
|
||||
tables: [
|
||||
{
|
||||
name: this.DRAFT_TABLE,
|
||||
columns: [
|
||||
{
|
||||
name: 'contextlevel',
|
||||
type: 'TEXT',
|
||||
},
|
||||
{
|
||||
name: 'contextinstanceid',
|
||||
type: 'INTEGER',
|
||||
},
|
||||
{
|
||||
name: 'elementid',
|
||||
type: 'TEXT',
|
||||
},
|
||||
{
|
||||
name: 'extraparams', // Moodle web uses a page hash built with URL. App will use some params stringified.
|
||||
type: 'TEXT',
|
||||
},
|
||||
{
|
||||
name: 'drafttext',
|
||||
type: 'TEXT',
|
||||
notNull: true,
|
||||
},
|
||||
{
|
||||
name: 'pageinstance',
|
||||
type: 'TEXT',
|
||||
notNull: true,
|
||||
},
|
||||
{
|
||||
name: 'timecreated',
|
||||
type: 'INTEGER',
|
||||
notNull: true,
|
||||
},
|
||||
{
|
||||
name: 'timemodified',
|
||||
type: 'INTEGER',
|
||||
notNull: true,
|
||||
},
|
||||
{
|
||||
name: 'originalcontent',
|
||||
type: 'TEXT',
|
||||
},
|
||||
],
|
||||
primaryKeys: ['contextlevel', 'contextinstanceid', 'elementid', 'extraparams'],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
constructor(
|
||||
logger: CoreLoggerProvider,
|
||||
protected sitesProvider: CoreSitesProvider,
|
||||
protected textUtils: CoreTextUtilsProvider,
|
||||
protected utils: CoreUtilsProvider) {
|
||||
this.logger = logger.getInstance('CoreEditorProvider');
|
||||
|
||||
this.sitesProvider.registerSiteSchema(this.siteSchema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a draft from DB.
|
||||
*
|
||||
* @param contextLevel Context level.
|
||||
* @param contextInstanceId The instance ID related to the context.
|
||||
* @param elementId Element ID.
|
||||
* @param extraParams Object with extra params to identify the draft.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
async deleteDraft(contextLevel: string, contextInstanceId: number, elementId: string, extraParams: {[name: string]: any},
|
||||
siteId?: string): Promise<void> {
|
||||
|
||||
try {
|
||||
const db = await this.sitesProvider.getSiteDb(siteId);
|
||||
|
||||
const params = this.fixDraftPrimaryData(contextLevel, contextInstanceId, elementId, extraParams);
|
||||
|
||||
return db.deleteRecords(this.DRAFT_TABLE, params);
|
||||
} catch (error) {
|
||||
// Ignore errors, probably no draft stored.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an object with the draft primary data converted to the right format.
|
||||
*
|
||||
* @param contextLevel Context level.
|
||||
* @param contextInstanceId The instance ID related to the context.
|
||||
* @param elementId Element ID.
|
||||
* @param extraParams Object with extra params to identify the draft.
|
||||
* @return Object with the fixed primary data.
|
||||
*/
|
||||
protected fixDraftPrimaryData(contextLevel: string, contextInstanceId: number, elementId: string,
|
||||
extraParams: {[name: string]: any}): CoreEditorDraftPrimaryData {
|
||||
|
||||
return {
|
||||
contextlevel: contextLevel,
|
||||
contextinstanceid: contextInstanceId,
|
||||
elementid: elementId,
|
||||
extraparams: this.utils.sortAndStringify(extraParams || {}),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a draft from DB.
|
||||
*
|
||||
* @param contextLevel Context level.
|
||||
* @param contextInstanceId The instance ID related to the context.
|
||||
* @param elementId Element ID.
|
||||
* @param extraParams Object with extra params to identify the draft.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with the draft data. Undefined if no draft stored.
|
||||
*/
|
||||
async getDraft(contextLevel: string, contextInstanceId: number, elementId: string, extraParams: {[name: string]: any},
|
||||
siteId?: string): Promise<CoreEditorDraft> {
|
||||
|
||||
const db = await this.sitesProvider.getSiteDb(siteId);
|
||||
|
||||
const params = this.fixDraftPrimaryData(contextLevel, contextInstanceId, elementId, extraParams);
|
||||
|
||||
return db.getRecord(this.DRAFT_TABLE, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get draft to resume it.
|
||||
*
|
||||
* @param contextLevel Context level.
|
||||
* @param contextInstanceId The instance ID related to the context.
|
||||
* @param elementId Element ID.
|
||||
* @param extraParams Object with extra params to identify the draft.
|
||||
* @param pageInstance Unique identifier to prevent storing data from several sources at the same time.
|
||||
* @param originalContent Original content of the editor.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with the draft data. Undefined if no draft stored.
|
||||
*/
|
||||
async resumeDraft(contextLevel: string, contextInstanceId: number, elementId: string, extraParams: {[name: string]: any},
|
||||
pageInstance: string, originalContent?: string, siteId?: string): Promise<CoreEditorDraft> {
|
||||
|
||||
try {
|
||||
// Check if there is a draft stored.
|
||||
const entry = await this.getDraft(contextLevel, contextInstanceId, elementId, extraParams, siteId);
|
||||
|
||||
// There is a draft stored. Update its page instance.
|
||||
try {
|
||||
const db = await this.sitesProvider.getSiteDb(siteId);
|
||||
|
||||
entry.pageinstance = pageInstance;
|
||||
entry.timemodified = Date.now();
|
||||
|
||||
if (originalContent && entry.originalcontent != originalContent) {
|
||||
entry.originalcontent = originalContent;
|
||||
entry.drafttext = ''; // "Discard" the draft.
|
||||
}
|
||||
|
||||
await db.insertRecord(this.DRAFT_TABLE, entry);
|
||||
} catch (error) {
|
||||
// Ignore errors saving the draft. It shouldn't happen.
|
||||
}
|
||||
|
||||
return entry;
|
||||
} catch (error) {
|
||||
// No draft stored. Store an empty draft to save the pageinstance.
|
||||
await this.saveDraft(contextLevel, contextInstanceId, elementId, extraParams, pageInstance, '', originalContent,
|
||||
siteId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a draft in DB.
|
||||
*
|
||||
* @param contextLevel Context level.
|
||||
* @param contextInstanceId The instance ID related to the context.
|
||||
* @param elementId Element ID.
|
||||
* @param extraParams Object with extra params to identify the draft.
|
||||
* @param pageInstance Unique identifier to prevent storing data from several sources at the same time.
|
||||
* @param draftText The text to store.
|
||||
* @param originalContent Original content of the editor.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
async saveDraft(contextLevel: string, contextInstanceId: number, elementId: string, extraParams: {[name: string]: any},
|
||||
pageInstance: string, draftText: string, originalContent?: string, siteId?: string): Promise<void> {
|
||||
|
||||
let timecreated = Date.now();
|
||||
let entry: CoreEditorDraft;
|
||||
|
||||
// Check if there is a draft already stored.
|
||||
try {
|
||||
entry = await this.getDraft(contextLevel, contextInstanceId, elementId, extraParams, siteId);
|
||||
|
||||
timecreated = entry.timecreated;
|
||||
} catch (error) {
|
||||
// No draft already stored.
|
||||
}
|
||||
|
||||
if (entry) {
|
||||
if (entry.pageinstance != pageInstance) {
|
||||
this.logger.warning(`Discarding draft because of pageinstance. Context '${contextLevel}' '${contextInstanceId}', ` +
|
||||
`element '${elementId}'`);
|
||||
throw null;
|
||||
}
|
||||
|
||||
if (!originalContent) {
|
||||
// Original content not set, use the one in the entry.
|
||||
originalContent = entry.originalcontent;
|
||||
}
|
||||
}
|
||||
|
||||
const db = await this.sitesProvider.getSiteDb(siteId);
|
||||
|
||||
const data: CoreEditorDraft = this.fixDraftPrimaryData(contextLevel, contextInstanceId, elementId, extraParams);
|
||||
|
||||
data.drafttext = (draftText || '').trim();
|
||||
data.pageinstance = pageInstance;
|
||||
data.timecreated = timecreated;
|
||||
data.timemodified = Date.now();
|
||||
if (originalContent) {
|
||||
data.originalcontent = originalContent;
|
||||
}
|
||||
|
||||
await db.insertRecord(this.DRAFT_TABLE, data);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Primary data to identify a stored draft.
|
||||
*/
|
||||
type CoreEditorDraftPrimaryData = {
|
||||
contextlevel: string; // Context level.
|
||||
contextinstanceid: number; // The instance ID related to the context.
|
||||
elementid: string; // Element ID.
|
||||
extraparams: string; // Extra params stringified.
|
||||
};
|
||||
|
||||
/**
|
||||
* Draft data stored.
|
||||
*/
|
||||
type CoreEditorDraft = CoreEditorDraftPrimaryData & {
|
||||
drafttext?: string; // Draft text stored.
|
||||
pageinstance?: string; // Unique identifier to prevent storing data from several sources at the same time.
|
||||
timecreated?: number; // Time created.
|
||||
timemodified?: number; // Time modified.
|
||||
originalcontent?: string; // Original content of the editor.
|
||||
};
|
|
@ -23,7 +23,7 @@
|
|||
<p *ngIf="siteName" padding class="item-heading core-sitename"><core-format-text [text]="siteName" [filter]="false"></core-format-text></p>
|
||||
<p *ngIf="siteName" class="core-siteurl">{{siteUrl}}</p>
|
||||
</div>
|
||||
<form ion-list [formGroup]="credForm" (ngSubmit)="login($event)" class="core-login-form">
|
||||
<form ion-list [formGroup]="credForm" (ngSubmit)="login($event)" class="core-login-form" #credentialsForm>
|
||||
<ion-item *ngIf="siteChecked && !isBrowserSSO">
|
||||
<ion-input type="text" name="username" placeholder="{{ 'core.login.username' | translate }}" formControlName="username" autocapitalize="none" autocorrect="off"></ion-input>
|
||||
</ion-item>
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, ViewChild, ElementRef } from '@angular/core';
|
||||
import { IonicPage, NavController, NavParams } from 'ionic-angular';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
|
@ -32,6 +32,9 @@ import { CoreConfigConstants } from '../../../../configconstants';
|
|||
templateUrl: 'credentials.html',
|
||||
})
|
||||
export class CoreLoginCredentialsPage {
|
||||
|
||||
@ViewChild('credentialsForm') formElement: ElementRef;
|
||||
|
||||
credForm: FormGroup;
|
||||
siteUrl: string;
|
||||
siteChecked = false;
|
||||
|
@ -242,6 +245,8 @@ export class CoreLoginCredentialsPage {
|
|||
}
|
||||
}).finally(() => {
|
||||
modal.dismiss();
|
||||
|
||||
this.domUtils.triggerFormSubmittedEvent(this.formElement.nativeElement, true);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<core-loading [hideUntil]="settingsLoaded" *ngIf="!isMinor">
|
||||
|
||||
<!-- Age verification. -->
|
||||
<form ion-list *ngIf="settingsLoaded && settings && ageDigitalConsentVerification" [formGroup]="ageVerificationForm" (ngSubmit)="verifyAge($event)">
|
||||
<form ion-list *ngIf="settingsLoaded && settings && ageDigitalConsentVerification" [formGroup]="ageVerificationForm" (ngSubmit)="verifyAge($event)" #ageForm>
|
||||
<ion-item-divider text-wrap>
|
||||
<p class="item-heading">{{ 'core.agelocationverification' | translate }}</p>
|
||||
</ion-item-divider>
|
||||
|
@ -47,7 +47,7 @@
|
|||
</form>
|
||||
|
||||
<!-- Signup form. -->
|
||||
<form ion-list *ngIf="settingsLoaded && settings && !ageDigitalConsentVerification" [formGroup]="signupForm" (ngSubmit)="create($event)">
|
||||
<form ion-list *ngIf="settingsLoaded && settings && !ageDigitalConsentVerification" [formGroup]="signupForm" (ngSubmit)="create($event)" #signupFormEl>
|
||||
<ion-item text-wrap text-center>
|
||||
<!-- If no sitename show big siteurl. -->
|
||||
<p *ngIf="!siteName" padding class="item-heading">{{siteUrl}}</p>
|
||||
|
|
|
@ -12,9 +12,10 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, ViewChild } from '@angular/core';
|
||||
import { Component, ViewChild, ElementRef } from '@angular/core';
|
||||
import { IonicPage, NavController, NavParams, Content } from 'ionic-angular';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||
|
@ -35,6 +36,8 @@ import { CoreConfigConstants } from '../../../../configconstants';
|
|||
})
|
||||
export class CoreLoginEmailSignupPage {
|
||||
@ViewChild(Content) content: Content;
|
||||
@ViewChild('ageForm') ageFormElement: ElementRef;
|
||||
@ViewChild('signupFormEl') signupFormElement: ElementRef;
|
||||
|
||||
signupForm: FormGroup;
|
||||
siteUrl: string;
|
||||
|
@ -66,10 +69,18 @@ export class CoreLoginEmailSignupPage {
|
|||
policyErrors: any;
|
||||
namefieldsErrors: any;
|
||||
|
||||
constructor(private navCtrl: NavController, navParams: NavParams, private fb: FormBuilder, private wsProvider: CoreWSProvider,
|
||||
private sitesProvider: CoreSitesProvider, private loginHelper: CoreLoginHelperProvider,
|
||||
private domUtils: CoreDomUtilsProvider, private translate: TranslateService, private utils: CoreUtilsProvider,
|
||||
private textUtils: CoreTextUtilsProvider, private userProfileFieldDelegate: CoreUserProfileFieldDelegate) {
|
||||
constructor(protected navCtrl: NavController,
|
||||
navParams: NavParams,
|
||||
protected fb: FormBuilder,
|
||||
protected wsProvider: CoreWSProvider,
|
||||
protected sitesProvider: CoreSitesProvider,
|
||||
protected loginHelper: CoreLoginHelperProvider,
|
||||
protected domUtils: CoreDomUtilsProvider,
|
||||
protected translate: TranslateService,
|
||||
protected utils: CoreUtilsProvider,
|
||||
protected textUtils: CoreTextUtilsProvider,
|
||||
protected userProfileFieldDelegate: CoreUserProfileFieldDelegate,
|
||||
protected eventsProvider: CoreEventsProvider) {
|
||||
|
||||
this.siteUrl = navParams.get('siteUrl');
|
||||
|
||||
|
@ -265,6 +276,9 @@ export class CoreLoginEmailSignupPage {
|
|||
return this.wsProvider.callAjax('auth_email_signup_user', params, { siteUrl: this.siteUrl });
|
||||
}).then((result) => {
|
||||
if (result.success) {
|
||||
|
||||
this.domUtils.triggerFormSubmittedEvent(this.signupFormElement.nativeElement, true);
|
||||
|
||||
// Show alert and ho back.
|
||||
const message = this.translate.instant('core.login.emailconfirmsent', { $a: params.email });
|
||||
this.domUtils.showAlert(this.translate.instant('core.success'), message);
|
||||
|
@ -334,6 +348,9 @@ export class CoreLoginEmailSignupPage {
|
|||
params.age = parseInt(params.age, 10); // Use just the integer part.
|
||||
|
||||
this.wsProvider.callAjax('core_auth_is_minor', params, {siteUrl: this.siteUrl}).then((result) => {
|
||||
|
||||
this.domUtils.triggerFormSubmittedEvent(this.ageFormElement.nativeElement, true);
|
||||
|
||||
if (!result.status) {
|
||||
if (this.countryControl.value) {
|
||||
this.signUpCountryControl.setValue(this.countryControl.value);
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
</ion-item>
|
||||
</ion-list>
|
||||
<ion-card>
|
||||
<form ion-list [formGroup]="myForm" (ngSubmit)="resetPassword($event)">
|
||||
<form ion-list [formGroup]="myForm" (ngSubmit)="resetPassword($event)" #resetPasswordForm>
|
||||
<ion-item-divider text-wrap>
|
||||
{{ 'core.login.searchby' | translate }}
|
||||
</ion-item-divider>
|
||||
|
|
|
@ -12,9 +12,11 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, ViewChild, ElementRef } from '@angular/core';
|
||||
import { IonicPage, NavController, NavParams } from 'ionic-angular';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreLoginHelperProvider } from '../../providers/helper';
|
||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
||||
|
@ -28,11 +30,20 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
|||
templateUrl: 'forgotten-password.html',
|
||||
})
|
||||
export class CoreLoginForgottenPasswordPage {
|
||||
|
||||
@ViewChild('resetPasswordForm') formElement: ElementRef;
|
||||
|
||||
myForm: FormGroup;
|
||||
siteUrl: string;
|
||||
|
||||
constructor(private navCtrl: NavController, navParams: NavParams, fb: FormBuilder, private translate: TranslateService,
|
||||
private loginHelper: CoreLoginHelperProvider, private domUtils: CoreDomUtilsProvider) {
|
||||
constructor(protected navCtrl: NavController,
|
||||
navParams: NavParams,
|
||||
fb: FormBuilder,
|
||||
protected translate: TranslateService,
|
||||
protected loginHelper: CoreLoginHelperProvider,
|
||||
protected domUtils: CoreDomUtilsProvider,
|
||||
protected eventsProvider: CoreEventsProvider,
|
||||
protected sitesProvider: CoreSitesProvider) {
|
||||
|
||||
this.siteUrl = navParams.get('siteUrl');
|
||||
this.myForm = fb.group({
|
||||
|
@ -71,6 +82,8 @@ export class CoreLoginForgottenPasswordPage {
|
|||
this.domUtils.showErrorModal(response.notice);
|
||||
} else {
|
||||
// Success.
|
||||
this.domUtils.triggerFormSubmittedEvent(this.formElement.nativeElement, true);
|
||||
|
||||
this.domUtils.showAlert(this.translate.instant('core.success'), response.notice);
|
||||
this.navCtrl.pop();
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@
|
|||
<ion-icon padding name="alert"></ion-icon> {{ 'core.login.reconnectdescription' | translate }}
|
||||
</p>
|
||||
</div>
|
||||
<form ion-list *ngIf="!isOAuth" [formGroup]="credForm" (ngSubmit)="login($event)" class="core-login-form">
|
||||
<form ion-list *ngIf="!isOAuth" [formGroup]="credForm" (ngSubmit)="login($event)" class="core-login-form" #reconnectForm>
|
||||
<ion-item text-wrap class="core-username">
|
||||
<p>{{username}}</p>
|
||||
</ion-item>
|
||||
|
|
|
@ -12,9 +12,10 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, ViewChild, ElementRef } from '@angular/core';
|
||||
import { IonicPage, NavController, NavParams } from 'ionic-angular';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreLoginHelperProvider } from '../../providers/helper';
|
||||
|
@ -29,6 +30,9 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
|||
templateUrl: 'reconnect.html',
|
||||
})
|
||||
export class CoreLoginReconnectPage {
|
||||
|
||||
@ViewChild('reconnectForm') formElement: ElementRef;
|
||||
|
||||
credForm: FormGroup;
|
||||
siteUrl: string;
|
||||
username: string;
|
||||
|
@ -47,13 +51,14 @@ export class CoreLoginReconnectPage {
|
|||
protected isLoggedOut: boolean;
|
||||
protected siteId: string;
|
||||
|
||||
constructor(private navCtrl: NavController,
|
||||
constructor(protected navCtrl: NavController,
|
||||
navParams: NavParams,
|
||||
fb: FormBuilder,
|
||||
private appProvider: CoreAppProvider,
|
||||
private sitesProvider: CoreSitesProvider,
|
||||
private loginHelper: CoreLoginHelperProvider,
|
||||
private domUtils: CoreDomUtilsProvider) {
|
||||
protected appProvider: CoreAppProvider,
|
||||
protected sitesProvider: CoreSitesProvider,
|
||||
protected loginHelper: CoreLoginHelperProvider,
|
||||
protected domUtils: CoreDomUtilsProvider,
|
||||
protected eventsProvider: CoreEventsProvider) {
|
||||
|
||||
const currentSite = this.sitesProvider.getCurrentSite();
|
||||
|
||||
|
@ -175,6 +180,9 @@ export class CoreLoginReconnectPage {
|
|||
// Start the authentication process.
|
||||
this.sitesProvider.getUserToken(siteUrl, username, password).then((data) => {
|
||||
return this.sitesProvider.updateSiteToken(this.infoSiteUrl, username, data.token, data.privateToken).then(() => {
|
||||
|
||||
this.domUtils.triggerFormSubmittedEvent(this.formElement.nativeElement, true);
|
||||
|
||||
// Update site info too because functions might have changed (e.g. unisntall local_mobile).
|
||||
return this.sitesProvider.updateSiteInfoByUrl(this.infoSiteUrl, username).then(() => {
|
||||
// Reset fields so the data is not in the view anymore.
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<div text-center padding>
|
||||
<img src="assets/img/login_logo.png" class="avatar-full login-logo" role="presentation">
|
||||
</div>
|
||||
<form ion-list [formGroup]="siteForm" (ngSubmit)="connect($event, siteForm.value.siteUrl)" *ngIf="!fixedSites || fixedDisplay == 'select'">
|
||||
<form ion-list [formGroup]="siteForm" (ngSubmit)="connect($event, siteForm.value.siteUrl)" *ngIf="!fixedSites || fixedDisplay == 'select'" #siteFormEl>
|
||||
<!-- Form to input the site URL if there are no fixed sites. -->
|
||||
<ng-container *ngIf="!fixedSites">
|
||||
<p padding>{{ 'core.login.newsitedescription' | translate }}</p>
|
||||
|
|
|
@ -12,9 +12,10 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component } from '@angular/core';
|
||||
import { Component, ViewChild, ElementRef } from '@angular/core';
|
||||
import { IonicPage, NavController, ModalController, NavParams } from 'ionic-angular';
|
||||
import { CoreAppProvider } from '@providers/app';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreSitesProvider, CoreSiteCheckResponse } from '@providers/sites';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreConfigConstants } from '../../../../configconstants';
|
||||
|
@ -31,6 +32,9 @@ import { CoreUrl } from '@classes/utils/url';
|
|||
templateUrl: 'site.html',
|
||||
})
|
||||
export class CoreLoginSitePage {
|
||||
|
||||
@ViewChild('siteFormEl') formElement: ElementRef;
|
||||
|
||||
siteForm: FormGroup;
|
||||
fixedSites: any[];
|
||||
filteredSites: any[];
|
||||
|
@ -38,9 +42,15 @@ export class CoreLoginSitePage {
|
|||
showKeyboard = false;
|
||||
filter = '';
|
||||
|
||||
constructor(navParams: NavParams, private navCtrl: NavController, fb: FormBuilder, private appProvider: CoreAppProvider,
|
||||
private sitesProvider: CoreSitesProvider, private loginHelper: CoreLoginHelperProvider,
|
||||
private modalCtrl: ModalController, private domUtils: CoreDomUtilsProvider) {
|
||||
constructor(navParams: NavParams,
|
||||
protected navCtrl: NavController,
|
||||
fb: FormBuilder,
|
||||
protected appProvider: CoreAppProvider,
|
||||
protected sitesProvider: CoreSitesProvider,
|
||||
protected loginHelper: CoreLoginHelperProvider,
|
||||
protected modalCtrl: ModalController,
|
||||
protected domUtils: CoreDomUtilsProvider,
|
||||
protected eventsProvider: CoreEventsProvider) {
|
||||
|
||||
this.showKeyboard = !!navParams.get('showKeyboard');
|
||||
|
||||
|
@ -96,6 +106,9 @@ export class CoreLoginSitePage {
|
|||
// It's a demo site.
|
||||
this.sitesProvider.getUserToken(siteData.url, siteData.username, siteData.password).then((data) => {
|
||||
return this.sitesProvider.newSite(data.siteUrl, data.token, data.privateToken).then(() => {
|
||||
|
||||
this.domUtils.triggerFormSubmittedEvent(this.formElement.nativeElement, true);
|
||||
|
||||
return this.loginHelper.goToSiteInitialPage();
|
||||
}, (error) => {
|
||||
this.loginHelper.treatUserTokenError(siteData.url, error, siteData.username, siteData.password);
|
||||
|
@ -175,6 +188,9 @@ export class CoreLoginSitePage {
|
|||
*/
|
||||
protected async login(response: CoreSiteCheckResponse): Promise<void> {
|
||||
return this.sitesProvider.checkRequiredMinimumVersion(response.config).then(() => {
|
||||
|
||||
this.domUtils.triggerFormSubmittedEvent(this.formElement.nativeElement, true);
|
||||
|
||||
if (response.warning) {
|
||||
this.domUtils.showErrorModal(response.warning, true, 4000);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<ion-card>
|
||||
<form #f="ngForm" (ngSubmit)="submitForm($event)" role="search">
|
||||
<form #f="ngForm" (ngSubmit)="submitForm($event)" role="search" #searchForm>
|
||||
<ion-item>
|
||||
<ion-input type="search" name="search" [(ngModel)]="searchText" [placeholder]="placeholder" [autocorrect]="autocorrect" [spellcheck]="spellcheck" [core-auto-focus]="autoFocus" [disabled]="disabled" role="searchbox" (focus)="showHistory()" (blur)="hideHistory()"></ion-input>
|
||||
<button item-end ion-button clear icon-only type="submit" class="button-small" [attr.aria-label]="searchLabel" [disabled]="disabled || !searchText || (searchText.length < lengthCheck)">
|
||||
|
|
|
@ -12,8 +12,11 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Component, Input, Output, EventEmitter, OnInit, OnDestroy } from '@angular/core';
|
||||
import { Component, Input, Output, EventEmitter, OnInit, OnDestroy, ViewChild, ElementRef } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreEventsProvider } from '@providers/events';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { CoreSearchHistoryProvider, CoreSearchHistoryItem } from '../../providers/search-history';
|
||||
|
||||
|
@ -47,9 +50,11 @@ export class CoreSearchBoxComponent implements OnInit, OnDestroy {
|
|||
@Output() onSubmit: EventEmitter<string>; // Send data when submitting the search form.
|
||||
@Output() onClear: EventEmitter<void>; // Send event when clearing the search form.
|
||||
|
||||
@ViewChild('searchForm') formElement: ElementRef;
|
||||
|
||||
searched = ''; // Last search emitted.
|
||||
searchText = '';
|
||||
history: CoreSearchHistoryItem[];
|
||||
history: CoreSearchHistoryItem[] = [];
|
||||
historyShown = false;
|
||||
|
||||
protected elementClicked = '';
|
||||
|
@ -58,6 +63,9 @@ export class CoreSearchBoxComponent implements OnInit, OnDestroy {
|
|||
constructor(protected translate: TranslateService,
|
||||
protected utils: CoreUtilsProvider,
|
||||
protected searchHistoryProvider: CoreSearchHistoryProvider,
|
||||
protected eventsProvider: CoreEventsProvider,
|
||||
protected sitesProvider: CoreSitesProvider,
|
||||
protected domUtils: CoreDomUtilsProvider,
|
||||
) {
|
||||
this.onSubmit = new EventEmitter<string>();
|
||||
this.onClear = new EventEmitter<void>();
|
||||
|
@ -95,6 +103,8 @@ export class CoreSearchBoxComponent implements OnInit, OnDestroy {
|
|||
this.saveSearchToHistory(this.searchText);
|
||||
}
|
||||
|
||||
this.domUtils.triggerFormSubmittedEvent(this.formElement.nativeElement, false, this.sitesProvider.getCurrentSiteId());
|
||||
|
||||
this.searched = this.searchText;
|
||||
this.onSubmit.emit(this.searchText);
|
||||
}
|
||||
|
|
|
@ -64,6 +64,7 @@ export class CoreEventsProvider {
|
|||
static SELECT_COURSE_TAB = 'select_course_tab';
|
||||
static WS_CACHE_INVALIDATED = 'ws_cache_invalidated';
|
||||
static SITE_STORAGE_DELETED = 'site_storage_deleted';
|
||||
static FORM_ACTION = 'form_action';
|
||||
|
||||
protected logger;
|
||||
protected observables: { [s: string]: Subject<any> } = {};
|
||||
|
|
|
@ -22,6 +22,7 @@ import { TranslateService } from '@ngx-translate/core';
|
|||
import { CoreTextUtilsProvider } from './text';
|
||||
import { CoreAppProvider } from '../app';
|
||||
import { CoreConfigProvider } from '../config';
|
||||
import { CoreEventsProvider } from '../events';
|
||||
import { CoreLoggerProvider } from '../logger';
|
||||
import { CoreUrlUtilsProvider } from './url';
|
||||
import { CoreFileProvider } from '@providers/file';
|
||||
|
@ -64,20 +65,21 @@ export class CoreDomUtilsProvider {
|
|||
protected displayedAlerts = {}; // To prevent duplicated alerts.
|
||||
protected logger;
|
||||
|
||||
constructor(private translate: TranslateService,
|
||||
private loadingCtrl: LoadingController,
|
||||
private toastCtrl: ToastController,
|
||||
private alertCtrl: AlertController,
|
||||
private textUtils: CoreTextUtilsProvider,
|
||||
private appProvider: CoreAppProvider,
|
||||
private platform: Platform,
|
||||
private configProvider: CoreConfigProvider,
|
||||
private urlUtils: CoreUrlUtilsProvider,
|
||||
private modalCtrl: ModalController,
|
||||
private sanitizer: DomSanitizer,
|
||||
private popoverCtrl: PopoverController,
|
||||
private fileProvider: CoreFileProvider,
|
||||
loggerProvider: CoreLoggerProvider) {
|
||||
constructor(protected translate: TranslateService,
|
||||
protected loadingCtrl: LoadingController,
|
||||
protected toastCtrl: ToastController,
|
||||
protected alertCtrl: AlertController,
|
||||
protected textUtils: CoreTextUtilsProvider,
|
||||
protected appProvider: CoreAppProvider,
|
||||
protected platform: Platform,
|
||||
protected configProvider: CoreConfigProvider,
|
||||
protected urlUtils: CoreUrlUtilsProvider,
|
||||
protected modalCtrl: ModalController,
|
||||
protected sanitizer: DomSanitizer,
|
||||
protected popoverCtrl: PopoverController,
|
||||
protected fileProvider: CoreFileProvider,
|
||||
loggerProvider: CoreLoggerProvider,
|
||||
protected eventsProvider: CoreEventsProvider) {
|
||||
|
||||
this.logger = loggerProvider.getInstance('CoreDomUtilsProvider');
|
||||
|
||||
|
@ -1625,4 +1627,32 @@ export class CoreDomUtilsProvider {
|
|||
// Now move the element into the wrapper.
|
||||
wrapper.appendChild(el);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger form cancelled event.
|
||||
*
|
||||
* @param form Form element.
|
||||
* @param siteId The site affected. If not provided, no site affected.
|
||||
*/
|
||||
triggerFormCancelledEvent(form: HTMLElement, siteId?: string): void {
|
||||
this.eventsProvider.trigger(CoreEventsProvider.FORM_ACTION, {
|
||||
action: 'cancel',
|
||||
form: form,
|
||||
}, siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger form submitted event.
|
||||
*
|
||||
* @param form Form element.
|
||||
* @param online Whether the action was done in offline or not.
|
||||
* @param siteId The site affected. If not provided, no site affected.
|
||||
*/
|
||||
triggerFormSubmittedEvent(form: HTMLElement, online?: boolean, siteId?: string): void {
|
||||
this.eventsProvider.trigger(CoreEventsProvider.FORM_ACTION, {
|
||||
action: 'submit',
|
||||
form: form,
|
||||
online: !!online,
|
||||
}, siteId);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -134,11 +134,6 @@ $link-color: $blue !default;
|
|||
$background-color: $gray-light !default;
|
||||
$subdued-text-color: $gray-darker !default;
|
||||
|
||||
$core-warning-color: colors($colors, warning) !default; // yellow.
|
||||
$core-success-color: colors($colors, success) !default; // green.
|
||||
$core-info-color: colors($colors, info) !default; // / blue.
|
||||
$core-error-color: colors($colors, alert) !default; // Red.
|
||||
|
||||
$list-background-color: $white !default;
|
||||
|
||||
$tabs-background: $gray-darker !default;
|
||||
|
|
Loading…
Reference in New Issue