diff --git a/src/addons/mod/assign/classes/base-feedback-plugin-component.ts b/src/addons/mod/assign/classes/base-feedback-plugin-component.ts new file mode 100644 index 000000000..48eb2ef64 --- /dev/null +++ b/src/addons/mod/assign/classes/base-feedback-plugin-component.ts @@ -0,0 +1,80 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, Input } from '@angular/core'; +import { CoreCanceledError } from '@classes/errors/cancelederror'; +import { CoreError } from '@classes/errors/error'; +import { ModalController } from '@singletons'; +import { AddonModAssignEditFeedbackModalComponent } from '../components/edit-feedback-modal/edit-feedback-modal'; +import { AddonModAssignFeedbackCommentsTextData } from '../feedback/comments/services/handler'; +import { AddonModAssignAssign, AddonModAssignPlugin, AddonModAssignSubmission } from '../services/assign'; + +/** + * Base class for component to render a feedback plugin. + */ +@Component({ + template: '', +}) +export class AddonModAssignFeedbackPluginBaseComponent { + + @Input() assign!: AddonModAssignAssign; // The assignment. + @Input() submission!: AddonModAssignSubmission; // The submission. + @Input() plugin!: AddonModAssignPlugin; // The plugin object. + @Input() userId!: number; // The user ID of the submission. + @Input() configs?: Record; // The configs for the plugin. + @Input() canEdit = false; // Whether the user can edit. + @Input() edit = false; // Whether the user is editing. + + /** + * Open a modal to edit the feedback plugin. + * + * @return Promise resolved with the input data, rejected if cancelled. + */ + async editFeedback(): Promise { + if (!this.canEdit) { + throw new CoreError('Cannot edit feedback'); + } + + // Create the navigation modal. + const modal = await ModalController.create({ + component: AddonModAssignEditFeedbackModalComponent, + componentProps: { + assign: this.assign, + submission: this.submission, + plugin: this.plugin, + userId: this.userId, + }, + }); + + await modal.present(); + + const result = await modal.onDidDismiss(); + + if (typeof result.data == 'undefined') { + throw new CoreCanceledError(); // User cancelled. + } else { + return result.data; + } + } + + /** + * Invalidate the data. + * + * @return Promise resolved when done. + */ + async invalidate(): Promise { + return; + } + +} diff --git a/src/addons/mod/assign/classes/base-submission-plugin-component.ts b/src/addons/mod/assign/classes/base-submission-plugin-component.ts new file mode 100644 index 000000000..8ec1a4598 --- /dev/null +++ b/src/addons/mod/assign/classes/base-submission-plugin-component.ts @@ -0,0 +1,42 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, Input } from '@angular/core'; +import { AddonModAssignAssign, AddonModAssignPlugin, AddonModAssignSubmission } from '../services/assign'; + +/** + * Base class for component to render a submission plugin. + */ +@Component({ + template: '', +}) +export class AddonModAssignSubmissionPluginBaseComponent { + + @Input() assign!: AddonModAssignAssign; // The assignment. + @Input() submission!: AddonModAssignSubmission; // The submission. + @Input() plugin!: AddonModAssignPlugin; // The plugin object. + @Input() configs?: Record; // The configs for the plugin. + @Input() edit = false; // Whether the user is editing. + @Input() allowOffline = false; // Whether to allow offline. + + /** + * Invalidate the data. + * + * @return Promise resolved when done. + */ + async invalidate(): Promise { + return; + } + +} diff --git a/src/addons/mod/assign/components/feedback-plugin/feedback-plugin.ts b/src/addons/mod/assign/components/feedback-plugin/feedback-plugin.ts index ed4f46182..c8bb4220e 100644 --- a/src/addons/mod/assign/components/feedback-plugin/feedback-plugin.ts +++ b/src/addons/mod/assign/components/feedback-plugin/feedback-plugin.ts @@ -13,11 +13,8 @@ // limitations under the License. import { Component, Input, OnInit, ViewChild, Type } from '@angular/core'; -import { CoreError } from '@classes/errors/error'; import { CoreDynamicComponent } from '@components/dynamic-component/dynamic-component'; import { CoreWSFile } from '@services/ws'; -import { ModalController } from '@singletons'; -import { AddonModAssignFeedbackCommentsTextData } from '../../feedback/comments/services/handler'; import { AddonModAssignAssign, AddonModAssignSubmission, @@ -27,7 +24,6 @@ import { } from '../../services/assign'; import { AddonModAssignHelper, AddonModAssignPluginConfig } from '../../services/assign-helper'; import { AddonModAssignFeedbackDelegate } from '../../services/feedback-delegate'; -import { AddonModAssignEditFeedbackModalComponent } from '../edit-feedback-modal/edit-feedback-modal'; /** * Component that displays an assignment feedback plugin. @@ -99,38 +95,6 @@ export class AddonModAssignFeedbackPluginComponent implements OnInit { } } - /** - * Open a modal to edit the feedback plugin. - * - * @return Promise resolved with the input data, rejected if cancelled. - */ - async editFeedback(): Promise { - if (!this.canEdit) { - throw new CoreError('Cannot edit feedback'); - } - - // Create the navigation modal. - const modal = await ModalController.create({ - component: AddonModAssignEditFeedbackModalComponent, - componentProps: { - assign: this.assign, - submission: this.submission, - plugin: this.plugin, - userId: this.userId, - }, - }); - - await modal.present(); - - const result = await modal.onDidDismiss(); - - if (typeof result.data == 'undefined') { - throw null; // User cancelled. - } else { - return result.data; - } - } - /** * Invalidate the plugin data. * diff --git a/src/addons/mod/assign/feedback/comments/component/comments.ts b/src/addons/mod/assign/feedback/comments/component/comments.ts index 7646562ad..371d82140 100644 --- a/src/addons/mod/assign/feedback/comments/component/comments.ts +++ b/src/addons/mod/assign/feedback/comments/component/comments.ts @@ -14,7 +14,6 @@ import { Component, OnInit, ElementRef } from '@angular/core'; import { FormBuilder, FormControl } from '@angular/forms'; -import { AddonModAssignFeedbackPluginComponent } from '@addons/mod/assign/components/feedback-plugin/feedback-plugin'; import { AddonModAssign, AddonModAssignProvider } from '@addons/mod/assign/services/assign'; import { CoreTextUtils } from '@services/utils/text'; import { @@ -25,6 +24,7 @@ import { import { AddonModAssignFeedbackDelegate } from '@addons/mod/assign/services/feedback-delegate'; import { AddonModAssignOffline } from '@addons/mod/assign/services/assign-offline'; import { CoreUtils } from '@services/utils/utils'; +import { AddonModAssignFeedbackPluginBaseComponent } from '@addons/mod/assign/classes/base-feedback-plugin-component'; /** * Component to render a comments feedback plugin. */ @@ -32,7 +32,7 @@ import { CoreUtils } from '@services/utils/utils'; selector: 'addon-mod-assign-feedback-comments', templateUrl: 'addon-mod-assign-feedback-comments.html', }) -export class AddonModAssignFeedbackCommentsComponent extends AddonModAssignFeedbackPluginComponent implements OnInit { +export class AddonModAssignFeedbackCommentsComponent extends AddonModAssignFeedbackPluginBaseComponent implements OnInit { control?: FormControl; component = AddonModAssignProvider.COMPONENT; diff --git a/src/addons/mod/assign/feedback/editpdf/component/editpdf.ts b/src/addons/mod/assign/feedback/editpdf/component/editpdf.ts index 733aa8d47..fc499adfb 100644 --- a/src/addons/mod/assign/feedback/editpdf/component/editpdf.ts +++ b/src/addons/mod/assign/feedback/editpdf/component/editpdf.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { AddonModAssignFeedbackPluginComponent } from '@addons/mod/assign/components/feedback-plugin/feedback-plugin'; +import { AddonModAssignFeedbackPluginBaseComponent } from '@addons/mod/assign/classes/base-feedback-plugin-component'; import { AddonModAssignProvider, AddonModAssign } from '@addons/mod/assign/services/assign'; import { Component, OnInit } from '@angular/core'; import { CoreWSFile } from '@services/ws'; @@ -24,7 +24,7 @@ import { CoreWSFile } from '@services/ws'; selector: 'addon-mod-assign-feedback-edit-pdf', templateUrl: 'addon-mod-assign-feedback-editpdf.html', }) -export class AddonModAssignFeedbackEditPdfComponent extends AddonModAssignFeedbackPluginComponent implements OnInit { +export class AddonModAssignFeedbackEditPdfComponent extends AddonModAssignFeedbackPluginBaseComponent implements OnInit { component = AddonModAssignProvider.COMPONENT; files: CoreWSFile[] = []; diff --git a/src/addons/mod/assign/feedback/file/component/file.ts b/src/addons/mod/assign/feedback/file/component/file.ts index c407baf9d..24a7c5793 100644 --- a/src/addons/mod/assign/feedback/file/component/file.ts +++ b/src/addons/mod/assign/feedback/file/component/file.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { AddonModAssignFeedbackPluginComponent } from '@addons/mod/assign/components/feedback-plugin/feedback-plugin'; +import { AddonModAssignFeedbackPluginBaseComponent } from '@addons/mod/assign/classes/base-feedback-plugin-component'; import { AddonModAssign, AddonModAssignProvider } from '@addons/mod/assign/services/assign'; import { Component, OnInit } from '@angular/core'; import { CoreWSFile } from '@services/ws'; @@ -24,7 +24,7 @@ import { CoreWSFile } from '@services/ws'; selector: 'addon-mod-assign-feedback-file', templateUrl: 'addon-mod-assign-feedback-file.html', }) -export class AddonModAssignFeedbackFileComponent extends AddonModAssignFeedbackPluginComponent implements OnInit { +export class AddonModAssignFeedbackFileComponent extends AddonModAssignFeedbackPluginBaseComponent implements OnInit { component = AddonModAssignProvider.COMPONENT; files: CoreWSFile[] = []; diff --git a/src/addons/mod/assign/submission/comments/component/comments.ts b/src/addons/mod/assign/submission/comments/component/comments.ts index 13cfd965d..2714f790b 100644 --- a/src/addons/mod/assign/submission/comments/component/comments.ts +++ b/src/addons/mod/assign/submission/comments/component/comments.ts @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { AddonModAssignSubmissionPluginBaseComponent } from '@addons/mod/assign/classes/base-submission-plugin-component'; import { Component, ViewChild } from '@angular/core'; -import { AddonModAssignSubmissionPluginComponent } from '@addons/mod/assign/components/submission-plugin/submission-plugin'; import { CoreCommentsCommentsComponent } from '@features/comments/components/comments/comments'; import { CoreComments } from '@features/comments/services/comments'; @@ -24,7 +24,7 @@ import { CoreComments } from '@features/comments/services/comments'; selector: 'addon-mod-assign-submission-comments', templateUrl: 'addon-mod-assign-submission-comments.html', }) -export class AddonModAssignSubmissionCommentsComponent extends AddonModAssignSubmissionPluginComponent { +export class AddonModAssignSubmissionCommentsComponent extends AddonModAssignSubmissionPluginBaseComponent { @ViewChild(CoreCommentsCommentsComponent) commentsComponent!: CoreCommentsCommentsComponent; diff --git a/src/addons/mod/assign/submission/file/component/file.ts b/src/addons/mod/assign/submission/file/component/file.ts index e7e2fb471..49ca11a07 100644 --- a/src/addons/mod/assign/submission/file/component/file.ts +++ b/src/addons/mod/assign/submission/file/component/file.ts @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { AddonModAssignSubmissionPluginComponent } from '@addons/mod/assign/components/submission-plugin/submission-plugin'; import { AddonModAssign, AddonModAssignProvider } from '@addons/mod/assign/services/assign'; import { AddonModAssignHelper } from '@addons/mod/assign/services/assign-helper'; import { AddonModAssignOffline } from '@addons/mod/assign/services/assign-offline'; @@ -22,6 +21,8 @@ import { CoreFileSession } from '@services/file-session'; import { CoreUtils } from '@services/utils/utils'; import { AddonModAssignSubmissionFileHandlerService } from '../services/handler'; import { FileEntry } from '@ionic-native/file/ngx'; +import { AddonModAssignSubmissionPluginBaseComponent } from '@addons/mod/assign/classes/base-submission-plugin-component'; +import { CoreFileEntry } from '@services/file-helper'; /** * Component to render a file submission plugin. @@ -30,9 +31,10 @@ import { FileEntry } from '@ionic-native/file/ngx'; selector: 'addon-mod-assign-submission-file', templateUrl: 'addon-mod-assign-submission-file.html', }) -export class AddonModAssignSubmissionFileComponent extends AddonModAssignSubmissionPluginComponent implements OnInit { +export class AddonModAssignSubmissionFileComponent extends AddonModAssignSubmissionPluginBaseComponent implements OnInit { component = AddonModAssignProvider.COMPONENT; + files: CoreFileEntry[] = []; maxSize?: number; acceptedTypes?: string; @@ -48,12 +50,12 @@ export class AddonModAssignSubmissionFileComponent extends AddonModAssignSubmiss undefined, ); - this.acceptedTypes = this.data?.configs.filetypeslist; - this.maxSize = this.data?.configs.maxsubmissionsizebytes - ? parseInt(this.data?.configs.maxsubmissionsizebytes, 10) + this.acceptedTypes = this.configs?.filetypeslist; + this.maxSize = this.configs?.maxsubmissionsizebytes + ? parseInt(this.configs.maxsubmissionsizebytes, 10) : undefined; - this.maxSubmissions = this.data?.configs.maxfilesubmissions - ? parseInt(this.data?.configs.maxfilesubmissions, 10) + this.maxSubmissions = this.configs?.maxfilesubmissions + ? parseInt(this.configs.maxfilesubmissions, 10) : undefined; try { diff --git a/src/addons/mod/assign/submission/onlinetext/component/onlinetext.ts b/src/addons/mod/assign/submission/onlinetext/component/onlinetext.ts index 6e5d6c56a..733ce4474 100644 --- a/src/addons/mod/assign/submission/onlinetext/component/onlinetext.ts +++ b/src/addons/mod/assign/submission/onlinetext/component/onlinetext.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { AddonModAssignSubmissionPluginComponent } from '@addons/mod/assign/components/submission-plugin/submission-plugin'; +import { AddonModAssignSubmissionPluginBaseComponent } from '@addons/mod/assign/classes/base-submission-plugin-component'; import { AddonModAssignProvider, AddonModAssign } from '@addons/mod/assign/services/assign'; import { AddonModAssignOffline } from '@addons/mod/assign/services/assign-offline'; import { Component, OnInit, ElementRef } from '@angular/core'; @@ -29,7 +29,7 @@ import { AddonModAssignSubmissionOnlineTextPluginData } from '../services/handle selector: 'addon-mod-assign-submission-online-text', templateUrl: 'addon-mod-assign-submission-onlinetext.html', }) -export class AddonModAssignSubmissionOnlineTextComponent extends AddonModAssignSubmissionPluginComponent implements OnInit { +export class AddonModAssignSubmissionOnlineTextComponent extends AddonModAssignSubmissionPluginBaseComponent implements OnInit { control?: FormControl; words = 0; @@ -62,8 +62,8 @@ export class AddonModAssignSubmissionOnlineTextComponent extends AddonModAssignS undefined, ); - this.wordLimitEnabled = !!parseInt(this.data?.configs.wordlimitenabled || '0', 10); - this.wordLimit = parseInt(this.data?.configs.wordlimit || '0'); + this.wordLimitEnabled = !!parseInt(this.configs?.wordlimitenabled || '0', 10); + this.wordLimit = parseInt(this.configs?.wordlimit || '0'); try { if (offlineData && offlineData.plugindata && offlineData.plugindata.onlinetext_editor) { diff --git a/src/core/features/siteplugins/components/assign-feedback/assign-feedback.ts b/src/core/features/siteplugins/components/assign-feedback/assign-feedback.ts index 09478e69d..643470967 100644 --- a/src/core/features/siteplugins/components/assign-feedback/assign-feedback.ts +++ b/src/core/features/siteplugins/components/assign-feedback/assign-feedback.ts @@ -31,6 +31,7 @@ export class CoreSitePluginsAssignFeedbackComponent extends CoreSitePluginsCompi @Input() submission!: AddonModAssignSubmission; // The submission. @Input() plugin!: AddonModAssignPlugin; // The plugin object. @Input() userId!: number; // The user ID of the submission. + @Input() configs?: Record; // The configs for the plugin. @Input() canEdit = false; // Whether the user can edit. @Input() edit = false; // Whether the user is editing. @@ -43,6 +44,7 @@ export class CoreSitePluginsAssignFeedbackComponent extends CoreSitePluginsCompi this.jsData.submission = this.submission; this.jsData.plugin = this.plugin; this.jsData.userId = this.userId; + this.jsData.configs = this.configs; this.jsData.edit = this.edit; this.jsData.canEdit = this.canEdit; diff --git a/src/core/features/siteplugins/components/assign-submission/assign-submission.ts b/src/core/features/siteplugins/components/assign-submission/assign-submission.ts index d45e62922..367d8090a 100644 --- a/src/core/features/siteplugins/components/assign-submission/assign-submission.ts +++ b/src/core/features/siteplugins/components/assign-submission/assign-submission.ts @@ -30,6 +30,7 @@ export class CoreSitePluginsAssignSubmissionComponent extends CoreSitePluginsCom @Input() assign!: AddonModAssignAssign; // The assignment. @Input() submission!: AddonModAssignSubmission; // The submission. @Input() plugin!: AddonModAssignPlugin; // The plugin object. + @Input() configs?: Record; // The configs for the plugin. @Input() edit = false; // Whether the user is editing. @Input() allowOffline = false; // Whether to allow offline. @@ -41,6 +42,7 @@ export class CoreSitePluginsAssignSubmissionComponent extends CoreSitePluginsCom this.jsData.assign = this.assign; this.jsData.submission = this.submission; this.jsData.plugin = this.plugin; + this.jsData.configs = this.configs; this.jsData.edit = this.edit; this.jsData.allowOffline = this.allowOffline; diff --git a/src/core/services/utils/text.ts b/src/core/services/utils/text.ts index aba818639..50db71836 100644 --- a/src/core/services/utils/text.ts +++ b/src/core/services/utils/text.ts @@ -304,6 +304,7 @@ export class CoreTextUtilsProvider { /** * Count words in a text. + * This function is based on Moodle's count_words. * * @param text Text to count. * @return Number of words. @@ -312,27 +313,32 @@ export class CoreTextUtilsProvider { if (!text || typeof text != 'string') { return 0; } - const blockTags = ['address', 'article', 'aside', 'blockquote', 'br', ' details', 'dialog', 'dd', 'div', 'dl', 'dt', - 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hgroup', 'hr', - 'li', 'main', 'nav', 'ol', 'p', 'pre', 'section', 'table', 'ul']; - // Clean HTML scripts and tags. - text = text.replace(/]*>([\S\s]*?)<\/script>/gmi, ''); - // Replace block tags by space to get word count aware of line break and remove inline tags. - text = text.replace(/<(\/[ ]*)?([a-zA-Z0-9]+)[^>]*>/gi, (str, p1, match) => { - if (blockTags.indexOf(match) >= 0) { - return ' '; - } + // Before stripping tags, add a space after the close tag of anything that is not obviously inline. + // Also, br is a special case because it definitely delimits a word, but has no close tag. + text = text.replace(/(<\/(?!a>|b>|del>|em>|i>|ins>|s>|small>|strong>|sub>|sup>|u>)\w+>|
|)/ig, '$1 '); - return ''; - }); + // Now remove HTML tags. + text = text.replace(/(<([^>]+)>)/ig, ''); // Decode HTML entities. text = this.decodeHTMLEntities(text); - // Replace underscores (which are classed as word characters) with spaces. - text = text.replace(/_/gi, ' '); - // This RegEx will detect any word change including Unicode chars. Some languages without spaces won't be counted fine. - return text.match(/\S+/gi)?.length || 0; + // Now, the word count is the number of blocks of characters separated + // by any sort of space. That seems to be the definition used by all other systems. + // To be precise about what is considered to separate words: + // * Anything that Unicode considers a 'Separator' + // * Anything that Unicode considers a 'Control character' + // * An em- or en- dash. + let words: string[]; + try { + words = text.split(/[\p{Z}\p{Cc}—–]+/u); + } catch { + // Unicode-aware flag not supported. + words = text.split(/\s+/); + } + + // Filter empty words. + return words.filter(word => word).length; } /**