Merge pull request #2618 from dpalou/MOBILE-3523
MOBILE-3523 essay: Fix essay text not displayed in review
This commit is contained in:
		
						commit
						ee8c3a32c4
					
				| @ -58,7 +58,7 @@ | ||||
|                         </ion-note> | ||||
|                     </ion-item-divider> | ||||
|                     <!-- Body of the question. --> | ||||
|                     <core-question text-wrap [question]="question" [component]="component" [componentId]="quiz.coursemodule" [attemptId]="attempt.id" [usageId]="attempt.uniqueid" [offlineEnabled]="offline" contextLevel="module" [contextInstanceId]="quiz.coursemodule" [courseId]="courseId" (onAbort)="abortQuiz()" (buttonClicked)="behaviourButtonClicked($event)"></core-question> | ||||
|                     <core-question text-wrap [question]="question" [component]="component" [componentId]="quiz.coursemodule" [attemptId]="attempt.id" [usageId]="attempt.uniqueid" [offlineEnabled]="offline" contextLevel="module" [contextInstanceId]="quiz.coursemodule" [courseId]="courseId" [review]="false" (onAbort)="abortQuiz()" (buttonClicked)="behaviourButtonClicked($event)"></core-question> | ||||
|                 </ion-card> | ||||
|             </div> | ||||
|         </form> | ||||
|  | ||||
| @ -77,7 +77,7 @@ | ||||
|                         </ion-note> | ||||
|                     </ion-item-divider> | ||||
|                     <!-- Body of the question. --> | ||||
|                     <core-question text-wrap [question]="question" [component]="component" [componentId]="componentId" [attemptId]="attempt.id" [usageId]="attempt.uniqueid" [offlineEnabled]="false" contextLevel="module" [contextInstanceId]="quiz.coursemodule" [courseId]="courseId"></core-question> | ||||
|                     <core-question text-wrap [question]="question" [component]="component" [componentId]="componentId" [attemptId]="attempt.id" [usageId]="attempt.uniqueid" [offlineEnabled]="false" contextLevel="module" [contextInstanceId]="quiz.coursemodule" [courseId]="courseId" [review]="true"></core-question> | ||||
|                 </ion-card> | ||||
|             </div> | ||||
| 
 | ||||
|  | ||||
| @ -4,49 +4,50 @@ | ||||
|         <p><core-format-text [component]="component" [componentId]="componentId" [text]="question.text" [contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId"></core-format-text></p> | ||||
|     </ion-item> | ||||
| 
 | ||||
|     <!-- Textarea. --> | ||||
|     <ion-item *ngIf="question.textarea && (!question.hasDraftFiles || uploadFilesSupported)"> | ||||
|         <!-- "Format" and draftid hidden inputs --> | ||||
|         <input item-content *ngIf="question.formatInput" type="hidden" [name]="question.formatInput.name" [value]="question.formatInput.value" > | ||||
|         <input item-content *ngIf="question.answerDraftIdInput" type="hidden" [name]="question.answerDraftIdInput.name" [value]="question.answerDraftIdInput.value" > | ||||
|         <!-- 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" [autoSave]="false"></core-rich-text-editor> | ||||
|     </ion-item> | ||||
|     <!-- Editing the question. --> | ||||
|     <ng-container *ngIf="!review"> | ||||
|         <!-- Textarea. --> | ||||
|         <ion-item *ngIf="question.textarea && (!question.hasDraftFiles || uploadFilesSupported)"> | ||||
|             <!-- "Format" and draftid hidden inputs --> | ||||
|             <input item-content *ngIf="question.formatInput" type="hidden" [name]="question.formatInput.name" [value]="question.formatInput.value" > | ||||
|             <input item-content *ngIf="question.answerDraftIdInput" type="hidden" [name]="question.answerDraftIdInput.name" [value]="question.answerDraftIdInput.value" > | ||||
|             <!-- 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" [autoSave]="false"></core-rich-text-editor> | ||||
|         </ion-item> | ||||
| 
 | ||||
|     <!-- Draft files not supported. --> | ||||
|     <ng-container *ngIf="question.textarea && question.hasDraftFiles && !uploadFilesSupported"> | ||||
|         <ion-item text-wrap class="core-danger-item"> | ||||
|             <p class="core-question-warning">{{ 'core.question.errorembeddedfilesnotsupportedinsite' | translate }}</p> | ||||
|         </ion-item> | ||||
|         <ion-item text-wrap> | ||||
|             <p><core-format-text [component]="component" [componentId]="componentId" [text]="question.textarea.text" [contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId"></core-format-text></p> | ||||
|         </ion-item> | ||||
|         <!-- Draft files not supported. --> | ||||
|         <ng-container *ngIf="question.textarea && question.hasDraftFiles && !uploadFilesSupported"> | ||||
|             <ion-item text-wrap class="core-danger-item"> | ||||
|                 <p class="core-question-warning">{{ 'core.question.errorembeddedfilesnotsupportedinsite' | translate }}</p> | ||||
|             </ion-item> | ||||
|             <ion-item text-wrap> | ||||
|                 <p><core-format-text [component]="component" [componentId]="componentId" [text]="question.textarea.text" [contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId"></core-format-text></p> | ||||
|             </ion-item> | ||||
|         </ng-container> | ||||
| 
 | ||||
|         <!-- Attachments. --> | ||||
|         <ng-container *ngIf="question.allowsAttachments"> | ||||
|             <core-attachments *ngIf="uploadFilesSupported && question.attachmentsDraftIdInput" [files]="attachments" [component]="component" [componentId]="componentId" [maxSize]="question.attachmentsMaxBytes" [maxSubmissions]="question.attachmentsMaxFiles" [allowOffline]="offlineEnabled" [acceptedTypes]="question.attachmentsAcceptedTypes"></core-attachments> | ||||
| 
 | ||||
|             <input item-content *ngIf="question.attachmentsDraftIdInput" type="hidden" [name]="question.attachmentsDraftIdInput.name" [value]="question.attachmentsDraftIdInput.value" > | ||||
| 
 | ||||
|             <!-- Attachments not supported in this site. --> | ||||
|             <ion-item text-wrap *ngIf="!uploadFilesSupported" class="core-danger-item"> | ||||
|                 <p class="core-question-warning">{{ 'core.question.errorattachmentsnotsupportedinsite' | translate }}</p> | ||||
|             </ion-item> | ||||
|         </ng-container> | ||||
|     </ng-container> | ||||
| 
 | ||||
|     <!-- Attachments. --> | ||||
|     <ng-container *ngIf="question.allowsAttachments"> | ||||
|         <core-attachments *ngIf="uploadFilesSupported && question.attachmentsDraftIdInput" [files]="attachments" [component]="component" [componentId]="componentId" [maxSize]="question.attachmentsMaxBytes" [maxSubmissions]="question.attachmentsMaxFiles" [allowOffline]="offlineEnabled" [acceptedTypes]="question.attachmentsAcceptedTypes"></core-attachments> | ||||
| 
 | ||||
|         <core-files *ngIf="uploadFilesSupported && !question.attachmentsDraftIdInput" [files]="attachments" [component]="component" [componentId]="componentId"></core-files> | ||||
| 
 | ||||
|         <input item-content *ngIf="question.attachmentsDraftIdInput" type="hidden" [name]="question.attachmentsDraftIdInput.name" [value]="question.attachmentsDraftIdInput.value" > | ||||
| 
 | ||||
|         <!-- Attachments not supported in this site. --> | ||||
|         <ion-item text-wrap *ngIf="!uploadFilesSupported" class="core-danger-item"> | ||||
|             <p class="core-question-warning">{{ 'core.question.errorattachmentsnotsupportedinsite' | translate }}</p> | ||||
|     <!-- Reviewing the question. --> | ||||
|     <ng-container *ngIf="review"> | ||||
|         <!-- Answer to the question and attachments (reviewing). --> | ||||
|         <ion-item text-wrap *ngIf="question.answer || question.answer == ''"> | ||||
|             <p><core-format-text [ngClass]='{"core-monospaced": question.isMonospaced}' [component]="component" [componentId]="componentId" [text]="question.answer" [contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId"></core-format-text></p> | ||||
|         </ion-item> | ||||
| 
 | ||||
|         <!-- List of attachments when reviewing. --> | ||||
|         <core-files *ngIf="question.attachments" [files]="question.attachments" [component]="component" [componentId]="componentId"></core-files> | ||||
|     </ng-container> | ||||
| 
 | ||||
|     <!-- Answer to the question and attachments (reviewing). --> | ||||
|     <ion-item text-wrap *ngIf="!question.textarea && (question.answer || question.answer == '')"> | ||||
|         <p><core-format-text [ngClass]='{"core-monospaced": question.isMonospaced}' [component]="component" [componentId]="componentId" [text]="question.answer" [contextLevel]="contextLevel" [contextInstanceId]="contextInstanceId" [courseId]="courseId"></core-format-text></p> | ||||
|     </ion-item> | ||||
| 
 | ||||
|     <ion-item *ngIf="!question.textarea && question.attachments && question.attachments.length"> | ||||
|         <div no-lines> | ||||
|             <core-file *ngFor="let attachment of question.attachments" [file]="attachment" [component]="component" [componentId]="componentId"></core-file> | ||||
|         </div> | ||||
|     </ion-item> | ||||
| </section> | ||||
|  | ||||
| @ -44,11 +44,11 @@ export class AddonQtypeEssayComponent extends CoreQuestionBaseComponent implemen | ||||
|      */ | ||||
|     ngOnInit(): void { | ||||
|         this.uploadFilesSupported = typeof this.question.responsefileareas != 'undefined'; | ||||
|         this.initEssayComponent(); | ||||
|         this.initEssayComponent(this.review); | ||||
| 
 | ||||
|         this.formControl = this.fb.control(this.question.textarea && this.question.textarea.text); | ||||
| 
 | ||||
|         if (this.question.allowsAttachments && this.uploadFilesSupported) { | ||||
|         if (this.question.allowsAttachments && this.uploadFilesSupported && !this.review) { | ||||
|             this.loadAttachments(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -32,6 +32,7 @@ export class CoreQuestionBaseComponent { | ||||
|     @Input() contextLevel?: string; // The context level.
 | ||||
|     @Input() contextInstanceId?: number; // The instance ID related to the context.
 | ||||
|     @Input() courseId?: number; // The course the question belongs to (if any).
 | ||||
|     @Input() review?: boolean; // Whether the user is in review mode.
 | ||||
|     @Output() buttonClicked: EventEmitter<any>; // Should emit an event when a behaviour button is clicked.
 | ||||
|     @Output() onAbort: EventEmitter<void>; // Should emit an event if the question should be aborted.
 | ||||
| 
 | ||||
| @ -207,99 +208,118 @@ export class CoreQuestionBaseComponent { | ||||
|     /** | ||||
|      * Initialize a question component of type essay. | ||||
|      * | ||||
|      * @param review Whether we're in review mode. | ||||
|      * @return Element containing the question HTML, void if the data is not valid. | ||||
|      */ | ||||
|     initEssayComponent(): void | HTMLElement { | ||||
|     initEssayComponent(review?: boolean): void | HTMLElement { | ||||
|         const questionEl = this.initComponent(); | ||||
| 
 | ||||
|         if (questionEl) { | ||||
|             const textarea = <HTMLTextAreaElement> questionEl.querySelector('textarea[name*=_answer]'); | ||||
|             const answerDraftIdInput = <HTMLInputElement> questionEl.querySelector('input[name*="_answer:itemid"]'); | ||||
|         if (!questionEl) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         const answerDraftIdInput = <HTMLInputElement> questionEl.querySelector('input[name*="_answer:itemid"]'); | ||||
| 
 | ||||
|         if (this.question.settings) { | ||||
|             this.question.allowsAttachments = this.question.settings.attachments != '0'; | ||||
|             this.question.allowsAnswerFiles = this.question.settings.responseformat == 'editorfilepicker'; | ||||
|             this.question.isMonospaced = this.question.settings.responseformat == 'monospaced'; | ||||
|             this.question.isPlainText = this.question.isMonospaced || this.question.settings.responseformat == 'plain'; | ||||
|             this.question.hasInlineText = this.question.settings.responseformat != 'noinline'; | ||||
|         } else { | ||||
|             this.question.allowsAttachments = !!questionEl.querySelector('div[id*=filemanager]'); | ||||
|             this.question.allowsAnswerFiles = !!answerDraftIdInput; | ||||
|             this.question.isMonospaced = !!questionEl.querySelector('.qtype_essay_monospaced'); | ||||
|             this.question.isPlainText = this.question.isMonospaced || !!questionEl.querySelector('.qtype_essay_plain'); | ||||
|         } | ||||
| 
 | ||||
|         if (review) { | ||||
|             // Search the answer and the attachments.
 | ||||
|             this.question.answer = this.domUtils.getContentsOfElement(questionEl, '.qtype_essay_response'); | ||||
| 
 | ||||
|             if (this.question.settings) { | ||||
|                 this.question.allowsAttachments = this.question.settings.attachments != '0'; | ||||
|                 this.question.allowsAnswerFiles = this.question.settings.responseformat == 'editorfilepicker'; | ||||
|                 this.question.isMonospaced = this.question.settings.responseformat == 'monospaced'; | ||||
|                 this.question.isPlainText = this.question.isMonospaced || this.question.settings.responseformat == 'plain'; | ||||
|                 this.question.attachments = Array.from(this.questionHelper.getResponseFileAreaFiles(this.question, 'attachments')); | ||||
|             } else { | ||||
|                 this.question.allowsAttachments = !!questionEl.querySelector('div[id*=filemanager]'); | ||||
|                 this.question.allowsAnswerFiles = !!answerDraftIdInput; | ||||
|                 this.question.isMonospaced = !!questionEl.querySelector('.qtype_essay_monospaced'); | ||||
|                 this.question.isPlainText = this.question.isMonospaced || !!questionEl.querySelector('.qtype_essay_plain'); | ||||
|             } | ||||
| 
 | ||||
|             this.question.hasDraftFiles = this.question.allowsAnswerFiles && | ||||
|                     this.questionHelper.hasDraftFileUrls(questionEl.innerHTML); | ||||
| 
 | ||||
|             if (!textarea && !this.question.allowsAttachments) { | ||||
|                 // Textarea and filemanager not found, we might be in review. Search the answer and the attachments.
 | ||||
|                 this.question.answer = this.domUtils.getContentsOfElement(questionEl, '.qtype_essay_response'); | ||||
|                 this.question.attachments = this.questionHelper.getQuestionAttachmentsFromHtml( | ||||
|                         this.domUtils.getContentsOfElement(questionEl, '.attachments')); | ||||
| 
 | ||||
|                 return questionEl; | ||||
|             } | ||||
| 
 | ||||
|             if (textarea) { | ||||
|                 const input = <HTMLInputElement> questionEl.querySelector('input[type="hidden"][name*=answerformat]'); | ||||
|                 let content = this.textUtils.decodeHTML(textarea.innerHTML || ''); | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|                 if (this.question.hasDraftFiles && this.question.responsefileareas) { | ||||
|                     content = this.textUtils.replaceDraftfileUrls(CoreSites.instance.getCurrentSite().getURL(), content, | ||||
|                             this.questionHelper.getResponseFileAreaFiles(this.question, 'answer')).text; | ||||
|                 } | ||||
|         const textarea = <HTMLTextAreaElement> questionEl.querySelector('textarea[name*=_answer]'); | ||||
| 
 | ||||
|                 this.question.textarea = { | ||||
|                     id: textarea.id, | ||||
|                     name: textarea.name, | ||||
|                     text: content, | ||||
|                 }; | ||||
|         this.question.hasDraftFiles = this.question.allowsAnswerFiles && | ||||
|                 this.questionHelper.hasDraftFileUrls(questionEl.innerHTML); | ||||
| 
 | ||||
|                 if (input) { | ||||
|                     this.question.formatInput = { | ||||
|                         name: input.name, | ||||
|                         value: input.value | ||||
|                     }; | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             if (answerDraftIdInput) { | ||||
|                 this.question.answerDraftIdInput = { | ||||
|                     name: answerDraftIdInput.name, | ||||
|                     value: Number(answerDraftIdInput.value), | ||||
|                 }; | ||||
|             } | ||||
| 
 | ||||
|             if (this.question.allowsAttachments) { | ||||
|                 const attachmentsInput = <HTMLInputElement> questionEl.querySelector('.attachments input[name*=_attachments]'); | ||||
|                 const objectElement = <HTMLObjectElement> questionEl.querySelector('.attachments object'); | ||||
|                 const fileManagerUrl = objectElement && objectElement.data; | ||||
| 
 | ||||
|                 if (attachmentsInput) { | ||||
|                     this.question.attachmentsDraftIdInput = { | ||||
|                         name: attachmentsInput.name, | ||||
|                         value: Number(attachmentsInput.value), | ||||
|                     }; | ||||
|                 } | ||||
| 
 | ||||
|                 if (this.question.settings) { | ||||
|                     this.question.attachmentsMaxFiles = Number(this.question.settings.attachments); | ||||
|                     this.question.attachmentsAcceptedTypes = this.question.settings.filetypeslist && | ||||
|                         this.question.settings.filetypeslist.join(','); | ||||
|                 } | ||||
| 
 | ||||
|                 if (fileManagerUrl) { | ||||
|                     const params = CoreUrlUtils.instance.extractUrlParams(fileManagerUrl); | ||||
|                     const maxBytes = Number(params.maxbytes); | ||||
|                     const areaMaxBytes = Number(params.areamaxbytes); | ||||
| 
 | ||||
|                     this.question.attachmentsMaxBytes = maxBytes === -1 || areaMaxBytes === -1 ? | ||||
|                             Math.max(maxBytes, areaMaxBytes) : Math.min(maxBytes, areaMaxBytes); | ||||
|                 } | ||||
|             } | ||||
|         if (!textarea && (this.question.hasInlineText || !this.question.allowsAttachments)) { | ||||
|             // Textarea not found, we might be in review. Search the answer and the attachments.
 | ||||
|             this.question.answer = this.domUtils.getContentsOfElement(questionEl, '.qtype_essay_response'); | ||||
|             this.question.attachments = this.questionHelper.getQuestionAttachmentsFromHtml( | ||||
|                     this.domUtils.getContentsOfElement(questionEl, '.attachments')); | ||||
| 
 | ||||
|             return questionEl; | ||||
|         } | ||||
| 
 | ||||
|         if (textarea) { | ||||
|             const input = <HTMLInputElement> questionEl.querySelector('input[type="hidden"][name*=answerformat]'); | ||||
|             let content = this.textUtils.decodeHTML(textarea.innerHTML || ''); | ||||
| 
 | ||||
|             if (this.question.hasDraftFiles && this.question.responsefileareas) { | ||||
|                 content = this.textUtils.replaceDraftfileUrls(CoreSites.instance.getCurrentSite().getURL(), content, | ||||
|                         this.questionHelper.getResponseFileAreaFiles(this.question, 'answer')).text; | ||||
|             } | ||||
| 
 | ||||
|             this.question.textarea = { | ||||
|                 id: textarea.id, | ||||
|                 name: textarea.name, | ||||
|                 text: content, | ||||
|             }; | ||||
| 
 | ||||
|             if (input) { | ||||
|                 this.question.formatInput = { | ||||
|                     name: input.name, | ||||
|                     value: input.value | ||||
|                 }; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (answerDraftIdInput) { | ||||
|             this.question.answerDraftIdInput = { | ||||
|                 name: answerDraftIdInput.name, | ||||
|                 value: Number(answerDraftIdInput.value), | ||||
|             }; | ||||
|         } | ||||
| 
 | ||||
|         if (this.question.allowsAttachments) { | ||||
|             const attachmentsInput = <HTMLInputElement> questionEl.querySelector('.attachments input[name*=_attachments]'); | ||||
|             const objectElement = <HTMLObjectElement> questionEl.querySelector('.attachments object'); | ||||
|             const fileManagerUrl = objectElement && objectElement.data; | ||||
| 
 | ||||
|             if (attachmentsInput) { | ||||
|                 this.question.attachmentsDraftIdInput = { | ||||
|                     name: attachmentsInput.name, | ||||
|                     value: Number(attachmentsInput.value), | ||||
|                 }; | ||||
|             } | ||||
| 
 | ||||
|             if (this.question.settings) { | ||||
|                 this.question.attachmentsMaxFiles = Number(this.question.settings.attachments); | ||||
|                 this.question.attachmentsAcceptedTypes = this.question.settings.filetypeslist && | ||||
|                     this.question.settings.filetypeslist.join(','); | ||||
|             } | ||||
| 
 | ||||
|             if (fileManagerUrl) { | ||||
|                 const params = CoreUrlUtils.instance.extractUrlParams(fileManagerUrl); | ||||
|                 const maxBytes = Number(params.maxbytes); | ||||
|                 const areaMaxBytes = Number(params.areamaxbytes); | ||||
| 
 | ||||
|                 this.question.attachmentsMaxBytes = maxBytes === -1 || areaMaxBytes === -1 ? | ||||
|                         Math.max(maxBytes, areaMaxBytes) : Math.min(maxBytes, areaMaxBytes); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return questionEl; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -39,6 +39,7 @@ export class CoreQuestionComponent implements OnInit { | ||||
|     @Input() contextLevel?: string; // The context level.
 | ||||
|     @Input() contextInstanceId?: number; // The instance ID related to the context.
 | ||||
|     @Input() courseId?: number; // Course ID the question belongs to (if any). It can be used to improve performance with filters.
 | ||||
|     @Input() review?: boolean; // Whether the user is in review mode.
 | ||||
|     @Output() buttonClicked: EventEmitter<any>; // Will emit an event when a behaviour button is clicked.
 | ||||
|     @Output() onAbort: EventEmitter<void>; // Will emit an event if the question should be aborted.
 | ||||
| 
 | ||||
| @ -88,8 +89,9 @@ export class CoreQuestionComponent implements OnInit { | ||||
|                     contextLevel: this.contextLevel, | ||||
|                     contextInstanceId: this.contextInstanceId, | ||||
|                     courseId: this.courseId, | ||||
|                     review: this.review, | ||||
|                     buttonClicked: this.buttonClicked, | ||||
|                     onAbort: this.onAbort | ||||
|                     onAbort: this.onAbort, | ||||
|                 }; | ||||
| 
 | ||||
|                 // Treat the question.
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user