diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml
index d6c708a28..7f710b31b 100644
--- a/.github/workflows/testing.yml
+++ b/.github/workflows/testing.yml
@@ -69,7 +69,7 @@ jobs:
cat circular-dependencies
lines=$(cat circular-dependencies | wc -l)
echo "Total circular dependencies: $lines"
- test $lines -eq 131
+ test $lines -eq 130
- name: JavaScript code compatibility
run: |
npx check-es-compat www/*.js --polyfills="\{Array,String,TypedArray\}.prototype.at,Object.hasOwn"
diff --git a/src/addons/block/myoverview/components/myoverview/myoverview.ts b/src/addons/block/myoverview/components/myoverview/myoverview.ts
index f8fbc1614..57ada4ddb 100644
--- a/src/addons/block/myoverview/components/myoverview/myoverview.ts
+++ b/src/addons/block/myoverview/components/myoverview/myoverview.ts
@@ -29,7 +29,7 @@ import { CoreBlockBaseComponent } from '@features/block/classes/base-block-compo
import { CoreSite } from '@classes/sites/site';
import { CoreUtils } from '@services/utils/utils';
import { CoreDomUtils } from '@services/utils/dom';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { AddonCourseCompletion } from '@addons/coursecompletion/services/coursecompletion';
import { IonSearchbar } from '@ionic/angular';
import { CoreNavigator } from '@services/navigator';
@@ -375,7 +375,7 @@ export class AddonBlockMyOverviewComponent extends CoreBlockBaseComponent implem
this.filters.show.custom = config?.displaygroupingcustomfield?.value == '1' && !!config?.customfieldsexport?.value;
this.filters.customFilters = this.filters.show.custom
- ? CoreTextUtils.parseJSON(config?.customfieldsexport?.value || '[]', [])
+ ? CoreText.parseJSON(config?.customfieldsexport?.value || '[]', [])
: [];
// Check if any selector is shown and not disabled.
diff --git a/src/addons/block/recentlyaccesseditems/components/recentlyaccesseditems/recentlyaccesseditems.ts b/src/addons/block/recentlyaccesseditems/components/recentlyaccesseditems/recentlyaccesseditems.ts
index c56027c1d..31d199720 100644
--- a/src/addons/block/recentlyaccesseditems/components/recentlyaccesseditems/recentlyaccesseditems.ts
+++ b/src/addons/block/recentlyaccesseditems/components/recentlyaccesseditems/recentlyaccesseditems.ts
@@ -19,7 +19,7 @@ import {
AddonBlockRecentlyAccessedItems,
AddonBlockRecentlyAccessedItemsItemCalculatedData,
} from '../../services/recentlyaccesseditems';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreLoadings } from '@services/loadings';
import { CoreUtils } from '@services/utils/utils';
import { CoreSharedModule } from '@/core/shared.module';
@@ -92,7 +92,7 @@ export class AddonBlockRecentlyAccessedItemsComponent extends CoreBlockBaseCompo
e.preventDefault();
e.stopPropagation();
- const url = CoreTextUtils.decodeHTMLEntities(item.viewurl);
+ const url = CoreText.decodeHTMLEntities(item.viewurl);
const modal = await CoreLoadings.show();
try {
diff --git a/src/addons/block/timeline/components/events/events.ts b/src/addons/block/timeline/components/events/events.ts
index 5156464dc..141b8b917 100644
--- a/src/addons/block/timeline/components/events/events.ts
+++ b/src/addons/block/timeline/components/events/events.ts
@@ -15,7 +15,7 @@
import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core';
import { CoreSites } from '@services/sites';
import { CoreLoadings } from '@services/loadings';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreEnrolledCourseDataWithOptions } from '@features/courses/services/courses-helper';
import { AddonBlockTimelineDayEvents } from '@addons/block/timeline/classes/section';
import { CoreSharedModule } from '@/core/shared.module';
@@ -64,7 +64,7 @@ export class AddonBlockTimelineEventsComponent implements OnInit {
event.stopPropagation();
// Fix URL format.
- url = CoreTextUtils.decodeHTMLEntities(url);
+ url = CoreText.decodeHTMLEntities(url);
const modal = await CoreLoadings.show();
diff --git a/src/addons/blog/pages/index/index.ts b/src/addons/blog/pages/index/index.ts
index 872477654..6a100b720 100644
--- a/src/addons/blog/pages/index/index.ts
+++ b/src/addons/blog/pages/index/index.ts
@@ -24,7 +24,7 @@ import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { CoreNavigator } from '@services/navigator';
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreFileHelper } from '@services/file-helper';
import { CoreUrl } from '@singletons/url';
import { CoreUtils } from '@services/utils/utils';
import { CoreArray } from '@singletons/array';
@@ -212,7 +212,7 @@ export class AddonBlogIndexPage implements OnInit, OnDestroy {
entry.contextInstanceId = entry.userid;
}
- entry.summary = CoreTextUtils.replacePluginfileUrls(entry.summary, entry.summaryfiles || []);
+ entry.summary = CoreFileHelper.replacePluginfileUrls(entry.summary, entry.summaryfiles || []);
entry.user = await CoreUtils.ignoreErrors(CoreUser.getProfile(entry.userid, entry.courseid, true));
});
diff --git a/src/addons/calendar/pages/event/event.ts b/src/addons/calendar/pages/event/event.ts
index 67b82e7dc..d97f1d3dd 100644
--- a/src/addons/calendar/pages/event/event.ts
+++ b/src/addons/calendar/pages/event/event.ts
@@ -25,7 +25,7 @@ import { AddonCalendarSync, AddonCalendarSyncEvents, AddonCalendarSyncProvider }
import { CoreNetwork } from '@services/network';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { CoreDomUtils } from '@services/utils/dom';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreSites } from '@services/sites';
import { CoreCourse } from '@features/course/services/course';
import { CoreTimeUtils } from '@services/utils/time';
@@ -45,6 +45,7 @@ import { CoreConfig } from '@services/config';
import { CoreToasts, ToastDuration } from '@services/toasts';
import { CorePopovers } from '@services/popovers';
import { CoreLoadings } from '@services/loadings';
+import { CoreUrl } from '@singletons/url';
/**
* Page that displays a single calendar event.
@@ -288,8 +289,8 @@ export class AddonCalendarEventPage implements OnInit, OnDestroy {
if (this.event.location) {
// Build a link to open the address in maps.
- this.event.location = CoreTextUtils.decodeHTML(this.event.location);
- this.event.encodedLocation = CoreTextUtils.buildAddressURL(this.event.location);
+ this.event.location = CoreText.decodeHTML(this.event.location);
+ this.event.encodedLocation = CoreUrl.buildAddressURL(this.event.location);
}
// Check if event was deleted in offine.
diff --git a/src/addons/calendar/services/calendar.ts b/src/addons/calendar/services/calendar.ts
index 71817798b..0492f66e4 100644
--- a/src/addons/calendar/services/calendar.ts
+++ b/src/addons/calendar/services/calendar.ts
@@ -16,7 +16,7 @@ import { Injectable } from '@angular/core';
import { CoreSites } from '@services/sites';
import { CoreSite } from '@classes/sites/site';
import { CoreNetwork } from '@services/network';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreTimeUtils } from '@services/utils/time';
import { CoreUrl } from '@singletons/url';
import { CoreUtils } from '@services/utils/utils';
@@ -330,7 +330,7 @@ export class AddonCalendarProvider {
): Promise {
const getTimeHtml = (time: string, a11yLangKey: string): string =>
- `${time} `;
+ `${time} `;
const getStartTimeHtml = (time: string): string => getTimeHtml(time, 'core.startingtime');
const getEndTimeHtml = (time: string): string => getTimeHtml(time, 'core.endingtime');
@@ -666,18 +666,18 @@ export class AddonCalendarProvider {
eventConverted.iscategoryevent = originalEvent.eventtype == AddonCalendarEventType.CATEGORY;
eventConverted.normalisedeventtype = this.getEventType(recordAsRecord);
try {
- eventConverted.category = CoreTextUtils.parseJSON(recordAsRecord.category || '');
+ eventConverted.category = CoreText.parseJSON(recordAsRecord.category || '');
} catch {
// Ignore errors.
}
try {
- eventConverted.course = CoreTextUtils.parseJSON(recordAsRecord.course || '');
+ eventConverted.course = CoreText.parseJSON(recordAsRecord.course || '');
} catch {
// Ignore errors.
}
try {
- eventConverted.subscription = CoreTextUtils.parseJSON(recordAsRecord.subscription || '');
+ eventConverted.subscription = CoreText.parseJSON(recordAsRecord.subscription || '');
} catch {
// Ignore errors.
}
diff --git a/src/addons/filter/displayh5p/services/handlers/displayh5p.ts b/src/addons/filter/displayh5p/services/handlers/displayh5p.ts
index e71a429fa..8e4dbcb47 100644
--- a/src/addons/filter/displayh5p/services/handlers/displayh5p.ts
+++ b/src/addons/filter/displayh5p/services/handlers/displayh5p.ts
@@ -20,6 +20,7 @@ import { makeSingleton } from '@singletons';
import { CoreH5PPlayerComponent } from '@features/h5p/components/h5p-player/h5p-player';
import { CoreUrl } from '@singletons/url';
import { CoreH5PHelper } from '@features/h5p/classes/helper';
+import { CoreText } from '@singletons/text';
/**
* Handler to support the Display H5P filter.
@@ -30,47 +31,45 @@ export class AddonFilterDisplayH5PHandlerService extends CoreFilterDefaultHandle
name = 'AddonFilterDisplayH5PHandler';
filterName = 'displayh5p';
- protected template = document.createElement('template'); // A template element to convert HTML to element.
-
/**
* @inheritdoc
*/
filter(
text: string,
): string | Promise {
- this.template.innerHTML = text;
+ return CoreText.processHTML(text, (element) => {
+ const h5pIframes = Array.from(element.querySelectorAll('iframe.h5p-iframe'));
- const h5pIframes = Array.from(this.template.content.querySelectorAll('iframe.h5p-iframe'));
+ // Replace all iframes with an empty div that will be treated in handleHtml.
+ h5pIframes.forEach((iframe) => {
+ const placeholder = document.createElement('div');
- // Replace all iframes with an empty div that will be treated in handleHtml.
- h5pIframes.forEach((iframe) => {
- const placeholder = document.createElement('div');
+ placeholder.classList.add('core-h5p-tmp-placeholder');
+ placeholder.setAttribute('data-player-src', iframe.src);
- placeholder.classList.add('core-h5p-tmp-placeholder');
- placeholder.setAttribute('data-player-src', iframe.src);
+ iframe.parentElement?.replaceChild(placeholder, iframe);
+ });
- iframe.parentElement?.replaceChild(placeholder, iframe);
+ // Handle H5P iframes embedded using the embed HTML code.
+ const embeddedH5PIframes = Array.from(
+ element.querySelectorAll('iframe.h5p-player'),
+ );
+
+ embeddedH5PIframes.forEach((iframe) => {
+ // Add the preventredirect param to allow authenticating if auto-login fails.
+ iframe.src = CoreUrl.addParamsToUrl(iframe.src, { preventredirect: false });
+
+ // Add resizer script so the H5P has the right height.
+ CoreH5PHelper.addResizerScript();
+
+ // If the iframe has a small height, add some minimum initial height so it's seen if auto-login fails.
+ const styleHeight = Number(iframe.style.height);
+ const height = Number(iframe.getAttribute('height'));
+ if ((!height || height < 400) && (!styleHeight || styleHeight < 400)) {
+ iframe.style.height = '400px';
+ }
+ });
});
-
- // Handle H5P iframes embedded using the embed HTML code.
- const embeddedH5PIframes = Array.from(this.template.content.querySelectorAll('iframe.h5p-player'));
-
- embeddedH5PIframes.forEach((iframe) => {
- // Add the preventredirect param to allow authenticating if auto-login fails.
- iframe.src = CoreUrl.addParamsToUrl(iframe.src, { preventredirect: false });
-
- // Add resizer script so the H5P has the right height.
- CoreH5PHelper.addResizerScript();
-
- // If the iframe has a small height, add some minimum initial height so it's seen if auto-login fails.
- const styleHeight = Number(iframe.style.height);
- const height = Number(iframe.getAttribute('height'));
- if ((!height || height < 400) && (!styleHeight || styleHeight < 400)) {
- iframe.style.height = '400px';
- }
- });
-
- return this.template.innerHTML;
}
/**
diff --git a/src/addons/filter/mathjaxloader/services/handlers/mathjaxloader.ts b/src/addons/filter/mathjaxloader/services/handlers/mathjaxloader.ts
index caa72b2d2..acf7683cb 100644
--- a/src/addons/filter/mathjaxloader/services/handlers/mathjaxloader.ts
+++ b/src/addons/filter/mathjaxloader/services/handlers/mathjaxloader.ts
@@ -18,7 +18,7 @@ import { CoreFilterDefaultHandler } from '@features/filter/services/handlers/def
import { CoreFilterFilter, CoreFilterFormatTextOptions } from '@features/filter/services/filter';
import { CoreLang } from '@services/lang';
import { CoreSites } from '@services/sites';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUtils } from '@services/utils/utils';
import { CoreEvents } from '@singletons/events';
import { CoreSite } from '@classes/sites/site';
@@ -189,7 +189,7 @@ export class AddonFilterMathJaxLoaderHandlerService extends CoreFilterDefaultHan
* @returns The whole text with the span inserted around the defined substring.
*/
protected insertSpan(text: string, start: number, end: number): string {
- return CoreTextUtils.substrReplace(
+ return CoreText.substrReplace(
text,
'' + text.substring(start, end + 1) + ' ',
start,
diff --git a/src/addons/filter/mediaplugin/services/handlers/mediaplugin.ts b/src/addons/filter/mediaplugin/services/handlers/mediaplugin.ts
index 5c5cea576..363cc109c 100644
--- a/src/addons/filter/mediaplugin/services/handlers/mediaplugin.ts
+++ b/src/addons/filter/mediaplugin/services/handlers/mediaplugin.ts
@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+import { CoreText } from '@singletons/text';
import { AddonFilterMediaPluginVideoJS } from '@addons/filter/mediaplugin/services/videojs';
import { Injectable } from '@angular/core';
@@ -28,21 +29,17 @@ export class AddonFilterMediaPluginHandlerService extends CoreFilterDefaultHandl
name = 'AddonFilterMediaPluginHandler';
filterName = 'mediaplugin';
- protected template = document.createElement('template'); // A template element to convert HTML to element.
-
/**
* @inheritdoc
*/
filter(text: string): string | Promise {
- this.template.innerHTML = text;
+ return CoreText.processHTML(text, (element) => {
+ const videos = Array.from(element.querySelectorAll('video'));
- const videos = Array.from(this.template.content.querySelectorAll('video'));
-
- videos.forEach((video) => {
- AddonFilterMediaPluginVideoJS.treatYoutubeVideos(video);
+ videos.forEach((video) => {
+ AddonFilterMediaPluginVideoJS.treatYoutubeVideos(video);
+ });
});
-
- return this.template.innerHTML;
}
/**
diff --git a/src/addons/filter/mediaplugin/services/videojs.ts b/src/addons/filter/mediaplugin/services/videojs.ts
index a51932900..4d04a019c 100644
--- a/src/addons/filter/mediaplugin/services/videojs.ts
+++ b/src/addons/filter/mediaplugin/services/videojs.ts
@@ -16,7 +16,7 @@ import { Injectable } from '@angular/core';
import { CorePromisedValue } from '@classes/promised-value';
import { CoreExternalContentDirective } from '@directives/external-content';
import { CoreLang } from '@services/lang';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUrl } from '@singletons/url';
import { makeSingleton } from '@singletons';
import { CoreDirectivesRegistry } from '@singletons/directives-registry';
@@ -64,7 +64,7 @@ export class AddonFilterMediaPluginVideoJSService {
// Create player.
const videojs = await this.getVideoJS();
const dataSetupString = element.getAttribute('data-setup') || element.getAttribute('data-setup-lazy') || '{}';
- const data = CoreTextUtils.parseJSON(dataSetupString, {});
+ const data = CoreText.parseJSON(dataSetupString, {});
const player = videojs(
element,
{
@@ -106,7 +106,7 @@ export class AddonFilterMediaPluginVideoJSService {
}
const dataSetupString = video.getAttribute('data-setup') || video.getAttribute('data-setup-lazy') || '{}';
- const data = CoreTextUtils.parseJSON(dataSetupString, {});
+ const data = CoreText.parseJSON(dataSetupString, {});
const youtubeUrl = data.techOrder?.[0] == 'youtube' && CoreUrl.getYoutubeEmbedUrl(data.sources?.[0]?.src);
if (!youtubeUrl) {
diff --git a/src/addons/messages/pages/discussion/discussion.ts b/src/addons/messages/pages/discussion/discussion.ts
index 60784c767..9f0f18050 100644
--- a/src/addons/messages/pages/discussion/discussion.ts
+++ b/src/addons/messages/pages/discussion/discussion.ts
@@ -32,7 +32,6 @@ import { AddonMessagesSync, AddonMessagesSyncProvider } from '../../services/mes
import { CoreUser } from '@features/user/services/user';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils';
-import { CoreTextUtils } from '@services/utils/text';
import { CoreLogger } from '@singletons/logger';
import { CoreInfiniteLoadingComponent } from '@components/infinite-loading/infinite-loading';
import { Md5 } from 'ts-md5/dist/md5';
@@ -928,7 +927,7 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView
*/
copyMessage(message: AddonMessagesConversationMessageFormatted): void {
const text = 'smallmessage' in message ? message.smallmessage || message.text || '' : message.text || '';
- CoreText.copyToClipboard(CoreTextUtils.decodeHTMLEntities(text));
+ CoreText.copyToClipboard(CoreText.decodeHTMLEntities(text));
}
/**
diff --git a/src/addons/messages/pages/settings/settings.ts b/src/addons/messages/pages/settings/settings.ts
index 21a2e9886..9fcb9ede7 100644
--- a/src/addons/messages/pages/settings/settings.ts
+++ b/src/addons/messages/pages/settings/settings.ts
@@ -27,7 +27,7 @@ import { CoreDomUtils } from '@services/utils/dom';
import { CoreConstants } from '@/core/constants';
import { AddonNotificationsPreferencesNotificationProcessorState } from '@addons/notifications/services/notifications';
import { CorePlatform } from '@services/platform';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreErrorHelper } from '@services/error-helper';
import { CoreLoadings } from '@services/loadings';
/**
@@ -112,7 +112,7 @@ export class AddonMessagesSettingsPage implements OnInit, OnDestroy {
this.warningMessage.set(undefined);
} catch (error) {
if (error.errorcode === 'nopermissions') {
- this.warningMessage.set(CoreTextUtils.getErrorMessageFromError(error));
+ this.warningMessage.set(CoreErrorHelper.getErrorMessageFromError(error));
return;
}
diff --git a/src/addons/messages/services/messages-offline.ts b/src/addons/messages/services/messages-offline.ts
index ac4987ae7..0d1321f35 100644
--- a/src/addons/messages/services/messages-offline.ts
+++ b/src/addons/messages/services/messages-offline.ts
@@ -15,7 +15,7 @@
import { Injectable } from '@angular/core';
import { CoreSites } from '@services/sites';
import { CoreNetwork } from '@services/network';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import {
AddonMessagesOfflineConversationMessagesDBRecord,
AddonMessagesOfflineMessagesDBRecord,
@@ -227,7 +227,7 @@ export class AddonMessagesOfflineProvider {
text: message.text,
timecreated: message.timecreated,
deviceoffline: message.deviceoffline,
- conversation: message.conversation ? CoreTextUtils.parseJSON(message.conversation, undefined) : undefined,
+ conversation: message.conversation ? CoreText.parseJSON(message.conversation, undefined) : undefined,
pending: true,
useridfrom: userIdFrom,
};
diff --git a/src/addons/messages/services/messages-sync.ts b/src/addons/messages/services/messages-sync.ts
index 73b09de33..c4c415d4c 100644
--- a/src/addons/messages/services/messages-sync.ts
+++ b/src/addons/messages/services/messages-sync.ts
@@ -30,9 +30,9 @@ import { CoreNetwork } from '@services/network';
import { CoreConstants } from '@/core/constants';
import { CoreUser } from '@features/user/services/user';
import { CoreError } from '@classes/errors/error';
-import { CoreTextErrorObject, CoreTextUtils } from '@services/utils/text';
import { CoreSiteWSPreSets } from '@classes/sites/authenticated-site';
import { CoreWait } from '@singletons/wait';
+import { CoreErrorHelper, CoreErrorObject } from '@services/error-helper';
/**
* Service to sync messages.
@@ -178,7 +178,7 @@ export class AddonMessagesSyncProvider extends CoreSyncBaseProvider {
if (!errors || errors.length <= 0) {
@@ -356,7 +356,7 @@ export class AddonMessagesSyncProvider extends CoreSyncBaseProvider {
warnings.push(Translate.instant('addon.messages.warningconversationmessagenotsent', {
conversation: conversationIdentifier,
- error: CoreTextUtils.getErrorMessageFromError(error),
+ error: CoreErrorHelper.getErrorMessageFromError(error),
}));
});
} else if (userId) {
@@ -373,7 +373,7 @@ export class AddonMessagesSyncProvider extends CoreSyncBaseProvider {
warnings.push(Translate.instant('addon.messages.warningmessagenotsent', {
user: userIdentifier,
- error: CoreTextUtils.getErrorMessageFromError(error),
+ error: CoreErrorHelper.getErrorMessageFromError(error),
}));
});
}
diff --git a/src/addons/mod/assign/components/submission/submission.ts b/src/addons/mod/assign/components/submission/submission.ts
index e5a0c1d42..f5fdd8df2 100644
--- a/src/addons/mod/assign/components/submission/submission.ts
+++ b/src/addons/mod/assign/components/submission/submission.ts
@@ -41,7 +41,7 @@ import { CoreMenuItem, CoreUtils } from '@services/utils/utils';
import { AddonModAssignHelper, AddonModAssignSubmissionFormatted } from '../../services/assign-helper';
import { CoreDomUtils } from '@services/utils/dom';
import { Translate } from '@singletons';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreCourse, CoreCourseModuleGradeInfo, CoreCourseModuleGradeOutcome } from '@features/course/services/course';
import { AddonModAssignOffline } from '../../services/assign-offline';
import { CoreUser, CoreUserProfile } from '@features/user/services/user';
@@ -1078,7 +1078,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can
if (!grade.outcomeid && !grade.scaleid) {
// Clean HTML tags, grade can contain an icon.
- const gradeFormatted = CoreTextUtils.cleanTags(grade.gradeformatted || '');
+ const gradeFormatted = CoreText.cleanTags(grade.gradeformatted || '');
// Not using outcomes or scale, get the numeric grade.
if (this.grade.scale) {
this.grade.gradebookGrade = CoreUtils.formatFloat(
@@ -1099,7 +1099,7 @@ export class AddonModAssignSubmissionComponent implements OnInit, OnDestroy, Can
gradeInfo.outcomes?.forEach((outcome) => {
if (outcome.id == String(grade.outcomeid)) {
// Clean HTML tags, grade can contain an icon.
- outcome.selected = CoreTextUtils.cleanTags(grade.gradeformatted || '');
+ outcome.selected = CoreText.cleanTags(grade.gradeformatted || '');
outcome.modified = grade.gradedategraded;
if (outcome.options) {
outcome.selectedId = CoreGradesHelper.getGradeValueFromLabel(outcome.options, outcome.selected);
diff --git a/src/addons/mod/assign/feedback/comments/component/comments.ts b/src/addons/mod/assign/feedback/comments/component/comments.ts
index e6f44ae02..0513d89ef 100644
--- a/src/addons/mod/assign/feedback/comments/component/comments.ts
+++ b/src/addons/mod/assign/feedback/comments/component/comments.ts
@@ -15,7 +15,7 @@
import { Component, OnInit, ElementRef } from '@angular/core';
import { FormBuilder, FormControl } from '@angular/forms';
import { AddonModAssign } from '@addons/mod/assign/services/assign';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreFileHelper } from '@services/file-helper';
import {
AddonModAssignFeedbackCommentsDraftData,
AddonModAssignFeedbackCommentsHandler,
@@ -159,7 +159,7 @@ export class AddonModAssignFeedbackCommentsComponent extends AddonModAssignFeedb
replacePluginfileUrls(text: string): string {
const files = this.plugin.fileareas && this.plugin.fileareas[0] && this.plugin.fileareas[0].files;
- return CoreTextUtils.replacePluginfileUrls(text, files || []);
+ return CoreFileHelper.replacePluginfileUrls(text, files || []);
}
}
diff --git a/src/addons/mod/assign/feedback/comments/services/handler.ts b/src/addons/mod/assign/feedback/comments/services/handler.ts
index 6bb08860b..88c2ecb7d 100644
--- a/src/addons/mod/assign/feedback/comments/services/handler.ts
+++ b/src/addons/mod/assign/feedback/comments/services/handler.ts
@@ -24,11 +24,12 @@ import { AddonModAssignOffline } from '@addons/mod/assign/services/assign-offlin
import { AddonModAssignFeedbackHandler } from '@addons/mod/assign/services/feedback-delegate';
import { Injectable, Type } from '@angular/core';
import { CoreSites } from '@services/sites';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUtils } from '@services/utils/utils';
import { CoreWSFile } from '@services/ws';
import { makeSingleton } from '@singletons';
import { AddonModAssignFeedbackCommentsComponent } from '../component/comments';
+import { CoreFileHelper } from '@services/file-helper';
/**
* Handler for comments feedback plugin.
@@ -56,7 +57,7 @@ export class AddonModAssignFeedbackCommentsHandlerService implements AddonModAss
const files = plugin.fileareas && plugin.fileareas[0] ? plugin.fileareas[0].files : [];
- return CoreTextUtils.restorePluginfileUrls(inputData.assignfeedbackcomments_editor, files || []);
+ return CoreFileHelper.restorePluginfileUrls(inputData.assignfeedbackcomments_editor, files || []);
}
/**
@@ -180,7 +181,7 @@ export class AddonModAssignFeedbackCommentsHandlerService implements AddonModAss
if (draft) {
// Add some HTML to the text if needed.
- draft.text = CoreTextUtils.formatHtmlLines(draft.text);
+ draft.text = CoreText.formatHtmlLines(draft.text);
pluginData.assignfeedbackcomments_editor = draft;
}
diff --git a/src/addons/mod/assign/services/assign-offline.ts b/src/addons/mod/assign/services/assign-offline.ts
index 305092943..7c411ca7b 100644
--- a/src/addons/mod/assign/services/assign-offline.ts
+++ b/src/addons/mod/assign/services/assign-offline.ts
@@ -17,7 +17,7 @@ import { CoreError } from '@classes/errors/error';
import { SQLiteDBRecordValues } from '@classes/sqlitedb';
import { CoreFile } from '@services/file';
import { CoreSites } from '@services/sites';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreTimeUtils } from '@services/utils/time';
import { makeSingleton } from '@singletons';
import { CorePath } from '@singletons/path';
@@ -137,7 +137,7 @@ export class AddonModAssignOfflineProvider {
assignid: submission.assignid,
userid: submission.userid,
courseid: submission.courseid,
- plugindata: CoreTextUtils.parseJSON(submission.plugindata, {}),
+ plugindata: CoreText.parseJSON(submission.plugindata, {}),
onlinetimemodified: submission.onlinetimemodified,
timecreated: submission.timecreated,
timemodified: submission.timemodified,
@@ -195,8 +195,8 @@ export class AddonModAssignOfflineProvider {
addattempt: submission.addattempt,
workflowstate: submission.workflowstate,
applytoall: submission.applytoall,
- outcomes: CoreTextUtils.parseJSON(submission.outcomes, {}),
- plugindata: CoreTextUtils.parseJSON(submission.plugindata, {}),
+ outcomes: CoreText.parseJSON(submission.outcomes, {}),
+ plugindata: CoreText.parseJSON(submission.plugindata, {}),
timemodified: submission.timemodified,
}));
}
diff --git a/src/addons/mod/assign/services/assign.ts b/src/addons/mod/assign/services/assign.ts
index f44ccc25c..976ca8abe 100644
--- a/src/addons/mod/assign/services/assign.ts
+++ b/src/addons/mod/assign/services/assign.ts
@@ -19,7 +19,6 @@ import { CoreInterceptor } from '@classes/interceptor';
import { CoreWSExternalWarning, CoreWSExternalFile, CoreWSFile } from '@services/ws';
import { makeSingleton, Translate } from '@singletons';
import { CoreCourseCommonModWSOptions } from '@features/course/services/course';
-import { CoreTextUtils } from '@services/utils/text';
import { CoreGrades } from '@features/grades/services/grades';
import { CoreTimeUtils } from '@services/utils/time';
import { CoreCourseLogHelper } from '@features/course/services/log-helper';
@@ -431,7 +430,7 @@ export class AddonModAssignProvider {
});
if (!keepUrls && submissionPlugin.fileareas && submissionPlugin.fileareas[0]) {
- text = CoreTextUtils.replacePluginfileUrls(text, submissionPlugin.fileareas[0].files || []);
+ text = CoreFileHelper.replacePluginfileUrls(text, submissionPlugin.fileareas[0].files || []);
}
return text;
diff --git a/src/addons/mod/assign/submission/onlinetext/component/onlinetext.ts b/src/addons/mod/assign/submission/onlinetext/component/onlinetext.ts
index 5b4390a98..47760672d 100644
--- a/src/addons/mod/assign/submission/onlinetext/component/onlinetext.ts
+++ b/src/addons/mod/assign/submission/onlinetext/component/onlinetext.ts
@@ -18,7 +18,7 @@ import { AddonModAssignOffline } from '@addons/mod/assign/services/assign-offlin
import { Component, OnInit, ElementRef } from '@angular/core';
import { FormBuilder, FormControl } from '@angular/forms';
import { CoreSites } from '@services/sites';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUtils } from '@services/utils/utils';
import { AddonModAssignSubmissionOnlineTextPluginData } from '../services/handler';
import { ContextLevel } from '@/core/constants';
@@ -102,7 +102,7 @@ export class AddonModAssignSubmissionOnlineTextComponent extends AddonModAssignS
// Calculate initial words.
if (this.wordLimitEnabled) {
- this.words = CoreTextUtils.countWords(this.text);
+ this.words = CoreText.countWords(this.text);
}
} finally {
this.loaded = true;
@@ -123,7 +123,7 @@ export class AddonModAssignSubmissionOnlineTextComponent extends AddonModAssignS
// Wait before calculating, if the user keeps inputing we won't calculate.
// This is to prevent slowing down devices, this calculation can be slow if the text is long.
this.wordCountTimeout = window.setTimeout(() => {
- this.words = CoreTextUtils.countWords(text);
+ this.words = CoreText.countWords(text);
}, 1500);
}
}
diff --git a/src/addons/mod/assign/submission/onlinetext/services/handler.ts b/src/addons/mod/assign/submission/onlinetext/services/handler.ts
index 8ae7b7f78..ac88ded41 100644
--- a/src/addons/mod/assign/submission/onlinetext/services/handler.ts
+++ b/src/addons/mod/assign/submission/onlinetext/services/handler.ts
@@ -25,7 +25,7 @@ import { AddonModAssignSubmissionHandler } from '@addons/mod/assign/services/sub
import { Injectable, Type } from '@angular/core';
import { CoreError } from '@classes/errors/error';
import { CoreFileHelper } from '@services/file-helper';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUtils } from '@services/utils/utils';
import { CoreWSFile } from '@services/ws';
import { makeSingleton, Translate } from '@singletons';
@@ -139,7 +139,7 @@ export class AddonModAssignSubmissionOnlineTextHandlerService implements AddonMo
const text = inputData.onlinetext_editor_text;
const files = plugin.fileareas && plugin.fileareas[0] && plugin.fileareas[0].files || [];
- return CoreTextUtils.restorePluginfileUrls(text, files || []);
+ return CoreFileHelper.restorePluginfileUrls(text, files || []);
}
/**
@@ -198,7 +198,7 @@ export class AddonModAssignSubmissionOnlineTextHandlerService implements AddonMo
// Check word limit.
const configs = AddonModAssignHelper.getPluginConfig(assign, 'assignsubmission', plugin.type);
if (parseInt(configs.wordlimitenabled, 10)) {
- const words = CoreTextUtils.countWords(text);
+ const words = CoreText.countWords(text);
const wordlimit = parseInt(configs.wordlimit, 10);
if (words > wordlimit) {
const params = { $a: { count: words, limit: wordlimit } };
@@ -209,7 +209,7 @@ export class AddonModAssignSubmissionOnlineTextHandlerService implements AddonMo
}
// Add some HTML to the text if needed.
- text = CoreTextUtils.formatHtmlLines(text);
+ text = CoreText.formatHtmlLines(text);
pluginData.onlinetext_editor = {
text: text,
diff --git a/src/addons/mod/bigbluebuttonbn/components/index/index.ts b/src/addons/mod/bigbluebuttonbn/components/index/index.ts
index 954fe5906..2c3e5d53f 100644
--- a/src/addons/mod/bigbluebuttonbn/components/index/index.ts
+++ b/src/addons/mod/bigbluebuttonbn/components/index/index.ts
@@ -21,7 +21,7 @@ import { CoreApp } from '@services/app';
import { CoreGroupInfo, CoreGroups } from '@services/groups';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreTimeUtils } from '@services/utils/time';
import { CoreUtils } from '@services/utils/utils';
import { Translate } from '@singletons';
@@ -33,6 +33,7 @@ import {
} from '../../services/bigbluebuttonbn';
import { ADDON_MOD_BBB_COMPONENT } from '../../constants';
import { CoreLoadings } from '@services/loadings';
+import { convertTextToHTMLElement } from '@/core/utils/create-html-element';
/**
* Component that displays a Big Blue Button activity.
@@ -147,7 +148,7 @@ export class AddonModBBBIndexComponent extends CoreCourseModuleMainActivityCompo
this.recordings = recordingsTable.parsedData.map(recordingData => {
const details: RecordingDetail[] = [];
- const playbacksEl = CoreDomUtils.convertToElement(String(recordingData.playback));
+ const playbacksEl = convertTextToHTMLElement(String(recordingData.playback));
const playbacks: RecordingPlayback[] = Array.from(playbacksEl.querySelectorAll('a')).map(playbackAnchor => ({
name: playbackAnchor.textContent ?? '',
url: playbackAnchor.href,
@@ -164,7 +165,7 @@ export class AddonModBBBIndexComponent extends CoreCourseModuleMainActivityCompo
value = CoreTimeUtils.userDate(Number(value), 'core.strftimedaydate');
} else if (columnData.allowHTML && typeof value === 'string') {
// If the HTML is empty, don't display it.
- const valueElement = CoreDomUtils.convertToElement(value);
+ const valueElement = convertTextToHTMLElement(value);
if (!valueElement.querySelector('img') && (valueElement.textContent ?? '').trim() === '') {
return;
}
@@ -185,7 +186,7 @@ export class AddonModBBBIndexComponent extends CoreCourseModuleMainActivityCompo
});
return {
- name: CoreTextUtils.cleanTags(String(recordingData.recording), { singleLine: true }),
+ name: CoreText.cleanTags(String(recordingData.recording), { singleLine: true }),
playbackLabel: columns.playback.label,
playbacks,
details,
diff --git a/src/addons/mod/bigbluebuttonbn/services/bigbluebuttonbn.ts b/src/addons/mod/bigbluebuttonbn/services/bigbluebuttonbn.ts
index a96f4e5d3..042cc9ce0 100644
--- a/src/addons/mod/bigbluebuttonbn/services/bigbluebuttonbn.ts
+++ b/src/addons/mod/bigbluebuttonbn/services/bigbluebuttonbn.ts
@@ -20,7 +20,7 @@ import { CoreSite } from '@classes/sites/site';
import { CoreCourseCommonModWSOptions } from '@features/course/services/course';
import { CoreCourseLogHelper } from '@features/course/services/log-helper';
import { CoreSites, CoreSitesCommonWSOptions } from '@services/sites';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUtils } from '@services/utils/utils';
import { CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws';
import { makeSingleton, Translate } from '@singletons';
@@ -247,7 +247,7 @@ export class AddonModBBBService {
return {
...result.tabledata,
- parsedData: CoreTextUtils.parseJSON(result.tabledata?.data, []),
+ parsedData: CoreText.parseJSON(result.tabledata?.data, []),
};
}
diff --git a/src/addons/mod/book/pages/contents/contents.ts b/src/addons/mod/book/pages/contents/contents.ts
index 141d51dc6..29a8a5f43 100644
--- a/src/addons/mod/book/pages/contents/contents.ts
+++ b/src/addons/mod/book/pages/contents/contents.ts
@@ -25,7 +25,7 @@ import { CoreTag, CoreTagItem } from '@features/tag/services/tag';
import { CoreNetwork } from '@services/network';
import { CoreNavigator } from '@services/navigator';
import { CoreDomUtils } from '@services/utils/dom';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreErrorHelper } from '@services/error-helper';
import { CoreUtils } from '@services/utils/utils';
import { Translate } from '@singletons';
import {
@@ -385,7 +385,7 @@ class AddonModBookSlidesItemsManagerSource extends CoreSwipeSlidesItemsManagerSo
return newChapters;
} catch (error) {
- if (!CoreTextUtils.getErrorMessageFromError(error)) {
+ if (!CoreErrorHelper.getErrorMessageFromError(error)) {
throw new CoreError(Translate.instant('addon.mod_book.errorchapter'));
}
diff --git a/src/addons/mod/book/services/book.ts b/src/addons/mod/book/services/book.ts
index e8da3e010..097383876 100644
--- a/src/addons/mod/book/services/book.ts
+++ b/src/addons/mod/book/services/book.ts
@@ -22,12 +22,13 @@ import { CoreCourseLogHelper } from '@features/course/services/log-helper';
import { CoreCourse, CoreCourseModuleContentFile } from '@features/course/services/course';
import { CoreUtils } from '@services/utils/utils';
import { CoreFilepool } from '@services/filepool';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreFile } from '@services/file';
import { CoreError } from '@classes/errors/error';
import { CoreSiteWSPreSets } from '@classes/sites/authenticated-site';
import { ADDON_MOD_BOOK_COMPONENT } from '../constants';
+import { CoreUrl } from '@singletons/url';
/**
* Service that provides some features for books.
@@ -177,7 +178,7 @@ export class AddonModBookProvider {
key = content.filepath.replace('/' + chapter + '/', '') + content.filename;
}
- map[chapter].paths[CoreTextUtils.decodeURIComponent(key)] = content.fileurl;
+ map[chapter].paths[CoreUrl.decodeURIComponent(key)] = content.fileurl;
});
return map;
@@ -224,7 +225,7 @@ export class AddonModBookProvider {
return [];
}
- return CoreTextUtils.parseJSON(contents[0].content, []);
+ return CoreText.parseJSON(contents[0].content, []);
}
/**
diff --git a/src/addons/mod/choice/services/choice-offline.ts b/src/addons/mod/choice/services/choice-offline.ts
index 137fa5325..85de0aafe 100644
--- a/src/addons/mod/choice/services/choice-offline.ts
+++ b/src/addons/mod/choice/services/choice-offline.ts
@@ -14,7 +14,7 @@
import { Injectable } from '@angular/core';
import { CoreSites } from '@services/sites';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { makeSingleton } from '@singletons';
import { AddonModChoiceResponsesDBRecord, RESPONSES_TABLE_NAME } from './database/choice';
@@ -101,7 +101,7 @@ export class AddonModChoiceOfflineProvider {
protected parseResponse(entry: AddonModChoiceResponsesDBRecord): AddonModChoiceOfflineResponses {
return {
...entry,
- responses: CoreTextUtils.parseJSON(entry.responses, []),
+ responses: CoreText.parseJSON(entry.responses, []),
};
}
diff --git a/src/addons/mod/data/components/search-modal/search-modal.ts b/src/addons/mod/data/components/search-modal/search-modal.ts
index 9f7cf1086..993563ea0 100644
--- a/src/addons/mod/data/components/search-modal/search-modal.ts
+++ b/src/addons/mod/data/components/search-modal/search-modal.ts
@@ -17,7 +17,7 @@ import { FormGroup, FormBuilder } from '@angular/forms';
import { CoreTag } from '@features/tag/services/tag';
import { CoreSites } from '@services/sites';
import { CoreFormFields, CoreForms } from '@singletons/form';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUtils } from '@services/utils/utils';
import { ModalController } from '@singletons';
import {
@@ -78,7 +78,7 @@ export class AddonModDataSearchModalComponent implements OnInit {
this.search.advanced?.forEach((field) => {
if (field !== undefined) {
this.advancedIndexed[field.name] = field.value
- ? CoreTextUtils.parseJSON(field.value, '')
+ ? CoreText.parseJSON(field.value, '')
: '';
}
});
diff --git a/src/addons/mod/data/fields/textarea/component/textarea.ts b/src/addons/mod/data/fields/textarea/component/textarea.ts
index 13960a700..47bc4260b 100644
--- a/src/addons/mod/data/fields/textarea/component/textarea.ts
+++ b/src/addons/mod/data/fields/textarea/component/textarea.ts
@@ -15,7 +15,7 @@
import { Component } from '@angular/core';
import { AddonModDataFieldPluginBaseComponent } from '../../../classes/base-field-plugin-component';
import { AddonModDataEntryField } from '@addons/mod/data/services/data';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreFileHelper } from '@services/file-helper';
import { CoreWSFile } from '@services/ws';
import { ADDON_MOD_DATA_COMPONENT } from '@addons/mod/data/constants';
@@ -40,7 +40,7 @@ export class AddonModDataFieldTextareaComponent extends AddonModDataFieldPluginB
format(value?: Partial): string {
const files: CoreWSFile[] = (value && value.files) || [];
- return value ? CoreTextUtils.replacePluginfileUrls(value.content || '', files) : '';
+ return value ? CoreFileHelper.replacePluginfileUrls(value.content || '', files) : '';
}
/**
diff --git a/src/addons/mod/data/fields/textarea/services/handler.ts b/src/addons/mod/data/fields/textarea/services/handler.ts
index c749aa4ca..5b58cda67 100644
--- a/src/addons/mod/data/fields/textarea/services/handler.ts
+++ b/src/addons/mod/data/fields/textarea/services/handler.ts
@@ -15,13 +15,14 @@
import { AddonModDataEntryField, AddonModDataField, AddonModDataSubfieldData } from '@addons/mod/data/services/data';
import { Injectable, Type } from '@angular/core';
import { CoreFormFields } from '@singletons/form';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreWSFile } from '@services/ws';
import { makeSingleton, Translate } from '@singletons';
import { AddonModDataFieldTextHandlerService } from '../../text/services/handler';
import { AddonModDataFieldTextareaComponent } from '../component/textarea';
-import { CoreFileEntry } from '@services/file-helper';
+import { CoreFileEntry, CoreFileHelper } from '@services/file-helper';
import type { AddonModDataFieldPluginBaseComponent } from '@addons/mod/data/classes/base-field-plugin-component';
+import { CoreDom } from '@singletons/dom';
/**
* Handler for textarea data field plugin.
@@ -50,12 +51,12 @@ export class AddonModDataFieldTextareaHandlerService extends AddonModDataFieldTe
const fieldName = 'f_' + field.id;
const files = this.getFieldEditFiles(field, inputData, originalFieldData);
- let text = CoreTextUtils.restorePluginfileUrls(inputData[fieldName] || '', files);
+ let text = CoreFileHelper.restorePluginfileUrls(inputData[fieldName] || '', files);
// Add some HTML to the text if needed.
- text = CoreTextUtils.formatHtmlLines(text);
+ text = CoreText.formatHtmlLines(text);
// WS does not properly check if HTML content is blank when the field is required.
- if (CoreTextUtils.htmlIsBlank(text)) {
+ if (CoreDom.htmlIsBlank(text)) {
text = '';
}
@@ -102,7 +103,7 @@ export class AddonModDataFieldTextareaHandlerService extends AddonModDataFieldTe
const value = inputData.find((value) => value.subfield == '');
- if (!value || CoreTextUtils.htmlIsBlank(value.value || '')) {
+ if (!value || CoreDom.htmlIsBlank(value.value || '')) {
return Translate.instant('addon.mod_data.errormustsupplyvalue');
}
@@ -115,7 +116,7 @@ export class AddonModDataFieldTextareaHandlerService extends AddonModDataFieldTe
originalContent.content = offlineContent[''] || '';
if (originalContent.content.length > 0 && originalContent.files && originalContent.files.length > 0) {
// Take the original files since we cannot edit them on the app.
- originalContent.content = CoreTextUtils.replacePluginfileUrls(
+ originalContent.content = CoreFileHelper.replacePluginfileUrls(
originalContent.content,
originalContent.files,
);
diff --git a/src/addons/mod/data/pages/edit/edit.ts b/src/addons/mod/data/pages/edit/edit.ts
index 5940aa0b2..610ad8eef 100644
--- a/src/addons/mod/data/pages/edit/edit.ts
+++ b/src/addons/mod/data/pages/edit/edit.ts
@@ -40,7 +40,7 @@ import {
import { AddonModDataHelper } from '../../services/data-helper';
import { CoreDom } from '@singletons/dom';
import { AddonModDataEntryFieldInitialized } from '../../classes/base-field-plugin-component';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreTime } from '@singletons/time';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { ADDON_MOD_DATA_COMPONENT, ADDON_MOD_DATA_ENTRY_CHANGED, AddonModDataTemplateType } from '../../constants';
@@ -395,7 +395,7 @@ export class AddonModDataEditPage implements OnInit {
if (updateEntryResult.generalnotifications?.length) {
CoreDomUtils.showAlertWithOptions({
header: Translate.instant('core.notice'),
- message: CoreTextUtils.buildMessage(updateEntryResult.generalnotifications),
+ message: CoreText.buildMessage(updateEntryResult.generalnotifications),
buttons: [Translate.instant('core.ok')],
});
}
diff --git a/src/addons/mod/data/services/data-helper.ts b/src/addons/mod/data/services/data-helper.ts
index 10dad51bc..a8d828af7 100644
--- a/src/addons/mod/data/services/data-helper.ts
+++ b/src/addons/mod/data/services/data-helper.ts
@@ -21,7 +21,7 @@ import { FileEntry } from '@awesome-cordova-plugins/file/ngx';
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreFormFields } from '@singletons/form';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUtils } from '@services/utils/utils';
import { makeSingleton, Translate } from '@singletons';
import { CoreEvents } from '@singletons/events';
@@ -97,9 +97,9 @@ export class AddonModDataHelperProvider {
if (offlineContent.subfield) {
offlineContents[offlineContent.fieldid][offlineContent.subfield] =
- CoreTextUtils.parseJSON(offlineContent.value, '');
+ CoreText.parseJSON(offlineContent.value, '');
} else {
- offlineContents[offlineContent.fieldid][''] = CoreTextUtils.parseJSON(offlineContent.value, '');
+ offlineContents[offlineContent.fieldid][''] = CoreText.parseJSON(offlineContent.value, '');
}
});
diff --git a/src/addons/mod/data/services/data-offline.ts b/src/addons/mod/data/services/data-offline.ts
index c74d92633..69b363586 100644
--- a/src/addons/mod/data/services/data-offline.ts
+++ b/src/addons/mod/data/services/data-offline.ts
@@ -16,7 +16,7 @@ import { Injectable } from '@angular/core';
import { CoreFileUploader, CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
import { CoreFile } from '@services/file';
import { CoreSites } from '@services/sites';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUtils } from '@services/utils/utils';
import { makeSingleton } from '@singletons';
import { CorePath } from '@singletons/path';
@@ -90,7 +90,7 @@ export class AddonModDataOfflineProvider {
const promises: Promise[] = [];
entry.fields.forEach((field) => {
- const value = CoreTextUtils.parseJSON(field.value, null);
+ const value = CoreText.parseJSON(field.value, null);
if (!value || !value.offline) {
return;
@@ -234,7 +234,7 @@ export class AddonModDataOfflineProvider {
*/
protected parseRecord(record: AddonModDataEntryDBRecord): AddonModDataOfflineAction {
return Object.assign(record, {
- fields: CoreTextUtils.parseJSON(record.fields),
+ fields: CoreText.parseJSON(record.fields),
});
}
diff --git a/src/addons/mod/data/services/data-sync.ts b/src/addons/mod/data/services/data-sync.ts
index 973597caa..26ba459d0 100644
--- a/src/addons/mod/data/services/data-sync.ts
+++ b/src/addons/mod/data/services/data-sync.ts
@@ -25,7 +25,7 @@ import { CoreNetwork } from '@services/network';
import { CoreFileEntry } from '@services/file-helper';
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
import { CoreSync, CoreSyncResult } from '@services/sync';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreErrorHelper } from '@services/error-helper';
import { CoreUtils } from '@services/utils/utils';
import { Translate, makeSingleton } from '@singletons';
import { CoreEvents } from '@singletons/events';
@@ -33,6 +33,7 @@ import { AddonModData, AddonModDataData } from './data';
import { AddonModDataHelper } from './data-helper';
import { AddonModDataOffline, AddonModDataOfflineAction } from './data-offline';
import { ADDON_MOD_DATA_AUTO_SYNCED, ADDON_MOD_DATA_COMPONENT, AddonModDataAction } from '../constants';
+import { CoreText } from '@singletons/text';
/**
* Service to sync databases.
@@ -326,7 +327,7 @@ export class AddonModDataSyncProvider extends CoreCourseActivitySyncBaseProvider
} catch (error) {
if (error && CoreUtils.isWebServiceError(error)) {
// The WebService has thrown an error, this means it cannot be performed. Discard.
- entryResult.discardError = CoreTextUtils.getErrorMessageFromError(error);
+ entryResult.discardError = CoreErrorHelper.getErrorMessageFromError(error);
} else {
// Couldn't connect to server, reject.
throw error;
@@ -345,7 +346,7 @@ export class AddonModDataSyncProvider extends CoreCourseActivitySyncBaseProvider
try {
await Promise.all(editAction.fields.map(async (field) => {
// Upload Files if asked.
- const value = CoreTextUtils.parseJSON(field.value || '', null);
+ const value = CoreText.parseJSON(field.value || '', null);
if (value && (value.online || value.offline)) {
let files: CoreFileEntry[] = value.online || [];
@@ -384,7 +385,7 @@ export class AddonModDataSyncProvider extends CoreCourseActivitySyncBaseProvider
} catch (error) {
if (error && CoreUtils.isWebServiceError(error)) {
// The WebService has thrown an error, this means it cannot be performed. Discard.
- entryResult.discardError = CoreTextUtils.getErrorMessageFromError(error);
+ entryResult.discardError = CoreErrorHelper.getErrorMessageFromError(error);
} else {
// Couldn't connect to server, reject.
throw error;
@@ -402,7 +403,7 @@ export class AddonModDataSyncProvider extends CoreCourseActivitySyncBaseProvider
} catch (error) {
if (error && CoreUtils.isWebServiceError(error)) {
// The WebService has thrown an error, this means it cannot be performed. Discard.
- entryResult.discardError = CoreTextUtils.getErrorMessageFromError(error);
+ entryResult.discardError = CoreErrorHelper.getErrorMessageFromError(error);
} else {
// Couldn't connect to server, reject.
throw error;
diff --git a/src/addons/mod/feedback/components/index/index.ts b/src/addons/mod/feedback/components/index/index.ts
index 957973d9e..7a24de452 100644
--- a/src/addons/mod/feedback/components/index/index.ts
+++ b/src/addons/mod/feedback/components/index/index.ts
@@ -20,7 +20,7 @@ import { CoreCourseContentsPage } from '@features/course/pages/contents/contents
import { IonContent } from '@ionic/angular';
import { CoreGroupInfo, CoreGroups } from '@services/groups';
import { CoreNavigator } from '@services/navigator';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreTimeUtils } from '@services/utils/time';
import { CoreUtils } from '@services/utils/utils';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
@@ -312,7 +312,7 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity
case 'info':
item.data = item.data.map((dataItem) => {
- const parsed = > CoreTextUtils.parseJSON(dataItem);
+ const parsed = > CoreText.parseJSON(dataItem);
return parsed.show !== undefined ? parsed.show : false;
}).filter((dataItem) => dataItem); // Filter false entries.
@@ -325,7 +325,7 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity
case 'multichoicerated':
case 'multichoice': {
const parsedData = []> item.data.map((dataItem) => {
- const parsed = > CoreTextUtils.parseJSON(dataItem);
+ const parsed = > CoreText.parseJSON(dataItem);
return parsed.answertext !== undefined ? parsed : false;
}).filter((dataItem) => dataItem); // Filter false entries.
diff --git a/src/addons/mod/feedback/pages/attempt/attempt.ts b/src/addons/mod/feedback/pages/attempt/attempt.ts
index ec1bac755..08016f94f 100644
--- a/src/addons/mod/feedback/pages/attempt/attempt.ts
+++ b/src/addons/mod/feedback/pages/attempt/attempt.ts
@@ -18,7 +18,7 @@ import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/
import { CoreSwipeNavigationItemsManager } from '@classes/items-management/swipe-navigation-items-manager';
import { CoreNavigator } from '@services/navigator';
import { CoreDomUtils } from '@services/utils/dom';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreFileHelper } from '@services/file-helper';
import { AddonModFeedbackAttemptsSource } from '../../classes/feedback-attempts-source';
import {
AddonModFeedback,
@@ -140,7 +140,7 @@ export class AddonModFeedbackAttemptPage implements OnInit, OnDestroy {
const attemptItem = formItem;
if (item.typ == 'label') {
- attemptItem.submittedValue = CoreTextUtils.replacePluginfileUrls(item.presentation, item.itemfiles);
+ attemptItem.submittedValue = CoreFileHelper.replacePluginfileUrls(item.presentation, item.itemfiles);
} else {
for (const x in attempt.responses) {
if (attempt.responses[x].id == item.id) {
diff --git a/src/addons/mod/feedback/services/feedback-helper.ts b/src/addons/mod/feedback/services/feedback-helper.ts
index b474ea4a6..80728c8d3 100644
--- a/src/addons/mod/feedback/services/feedback-helper.ts
+++ b/src/addons/mod/feedback/services/feedback-helper.ts
@@ -18,7 +18,7 @@ import { CoreUser } from '@features/user/services/user';
import { CoreNavigator } from '@services/navigator';
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreFileHelper } from '@services/file-helper';
import { CoreTimeUtils } from '@services/utils/time';
import { CoreUtils } from '@services/utils/utils';
import { makeSingleton, Translate } from '@singletons';
@@ -41,6 +41,7 @@ import {
ADDON_MOD_FEEDBACK_PAGE_NAME,
} from '../constants';
import { CoreLoadings } from '@services/loadings';
+import { CoreText } from '@singletons/text';
const MODE_RESPONSETIME = 1;
const MODE_COURSE = 2;
@@ -253,7 +254,7 @@ export class AddonModFeedbackHelperProvider {
*/
protected getItemFormLabel(item: AddonModFeedbackItem): AddonModFeedbackFormBasicItem {
item.name = '';
- item.presentation = CoreTextUtils.replacePluginfileUrls(item.presentation, item.itemfiles);
+ item.presentation = CoreFileHelper.replacePluginfileUrls(item.presentation, item.itemfiles);
return Object.assign(item, {
templateName: 'label',
@@ -421,7 +422,7 @@ export class AddonModFeedbackHelperProvider {
slottedLabel: false,
});
- const data = CoreTextUtils.parseJSON(item.otherdata);
+ const data = CoreText.parseJSON(item.otherdata);
if (data && data.length > 3) {
formItem.captcha = {
recaptchapublickey: data[3],
diff --git a/src/addons/mod/feedback/services/feedback-offline.ts b/src/addons/mod/feedback/services/feedback-offline.ts
index 2cfaa0a0d..7cfa363ec 100644
--- a/src/addons/mod/feedback/services/feedback-offline.ts
+++ b/src/addons/mod/feedback/services/feedback-offline.ts
@@ -14,7 +14,7 @@
import { Injectable } from '@angular/core';
import { CoreSites } from '@services/sites';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreTimeUtils } from '@services/utils/time';
import { makeSingleton } from '@singletons';
import { AddonModFeedbackResponseDBRecord, FEEDBACK_TABLE_NAME } from './database/feedback';
@@ -116,7 +116,7 @@ export class AddonModFeedbackOfflineProvider {
*/
protected parseResponse(record: AddonModFeedbackResponseDBRecord): AddonModFeedbackOfflineResponse {
return Object.assign(record, {
- responses: > CoreTextUtils.parseJSON(record.responses),
+ responses: > CoreText.parseJSON(record.responses),
});
}
diff --git a/src/addons/mod/folder/services/handlers/module.ts b/src/addons/mod/folder/services/handlers/module.ts
index aefa39df7..d0603a298 100644
--- a/src/addons/mod/folder/services/handlers/module.ts
+++ b/src/addons/mod/folder/services/handlers/module.ts
@@ -18,7 +18,7 @@ import { CoreModuleHandlerBase } from '@features/course/classes/module-base-hand
import { CoreCourseModuleData } from '@features/course/services/course-helper';
import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@features/course/services/module-delegate';
import { CoreNavigator } from '@services/navigator';
-import { CoreDomUtils } from '@services/utils/dom';
+import { convertTextToHTMLElement } from '@/core/utils/create-html-element';
import { makeSingleton } from '@singletons';
import { ADDON_MOD_FOLDER_PAGE_NAME } from '../../constants';
@@ -58,7 +58,7 @@ export class AddonModFolderModuleHandlerService extends CoreModuleHandlerBase im
if (module.description) {
// Module description can contain the folder contents if it's inline, remove it.
- const descriptionElement = CoreDomUtils.convertToElement(module.description);
+ const descriptionElement = convertTextToHTMLElement(module.description);
Array.from(descriptionElement.querySelectorAll('.foldertree, .folderbuttons, .tertiary-navigation'))
.forEach(element => element.remove());
diff --git a/src/addons/mod/forum/components/post/post.ts b/src/addons/mod/forum/components/post/post.ts
index 0f2028c1b..9e10b3c35 100644
--- a/src/addons/mod/forum/components/post/post.ts
+++ b/src/addons/mod/forum/components/post/post.ts
@@ -41,7 +41,7 @@ import { Translate } from '@singletons';
import { CoreFileUploader } from '@features/fileuploader/services/fileuploader';
import { AddonModForumSync } from '../../services/forum-sync';
import { CoreSync } from '@services/sync';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { AddonModForumHelper } from '../../services/forum-helper';
import { AddonModForumOffline } from '../../services/forum-offline';
import { CoreUtils } from '@services/utils/utils';
@@ -370,7 +370,7 @@ export class AddonModForumPostComponent implements OnInit, OnDestroy, OnChanges
const modal = await CoreLoadings.show('core.sending', true);
// Add some HTML to the message if needed.
- message = CoreTextUtils.formatHtmlLines(message);
+ message = CoreText.formatHtmlLines(message);
// Upload attachments first if any.
let attachments;
diff --git a/src/addons/mod/forum/pages/new-discussion/new-discussion.ts b/src/addons/mod/forum/pages/new-discussion/new-discussion.ts
index 963083e3c..2c0c3c18f 100644
--- a/src/addons/mod/forum/pages/new-discussion/new-discussion.ts
+++ b/src/addons/mod/forum/pages/new-discussion/new-discussion.ts
@@ -34,7 +34,7 @@ import { AddonModForumDiscussionOptions, AddonModForumOffline } from '@addons/mo
import { CoreUtils } from '@services/utils/utils';
import { AddonModForumHelper } from '@addons/mod/forum/services/forum-helper';
import { CoreFileUploader } from '@features/fileuploader/services/fileuploader';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CanLeave } from '@guards/can-leave';
import { CoreSplitViewComponent } from '@components/split-view/split-view';
import { CoreForms } from '@singletons/form';
@@ -553,7 +553,7 @@ export class AddonModForumNewDiscussionPage implements OnInit, OnDestroy, CanLea
const modal = await CoreLoadings.show('core.sending', true);
// Add some HTML to the message if needed.
- message = CoreTextUtils.formatHtmlLines(message);
+ message = CoreText.formatHtmlLines(message);
if (pin) {
options.discussionpinned = true;
diff --git a/src/addons/mod/forum/services/forum-offline.ts b/src/addons/mod/forum/services/forum-offline.ts
index 3628192a4..1ab1116b1 100644
--- a/src/addons/mod/forum/services/forum-offline.ts
+++ b/src/addons/mod/forum/services/forum-offline.ts
@@ -16,7 +16,7 @@ import { Injectable } from '@angular/core';
import { CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
import { CoreFile } from '@services/file';
import { CoreSites } from '@services/sites';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { makeSingleton } from '@singletons';
import {
AddonModForumOfflineDiscussionDBRecord,
@@ -399,7 +399,7 @@ export class AddonModForumOfflineProvider {
R extends { options: string },
O extends Record = Record
>(record: R): Omit & { options: O } {
- record.options = CoreTextUtils.parseJSON(record.options);
+ record.options = CoreText.parseJSON(record.options);
return record as unknown as Omit & { options: O };
}
diff --git a/src/addons/mod/forum/services/handlers/module.ts b/src/addons/mod/forum/services/handlers/module.ts
index 9335ce228..3572ef649 100644
--- a/src/addons/mod/forum/services/handlers/module.ts
+++ b/src/addons/mod/forum/services/handlers/module.ts
@@ -21,7 +21,7 @@ import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@features/
import { CoreConstants, ModPurpose } from '@/core/constants';
import { CoreModuleHandlerBase } from '@features/course/classes/module-base-handler';
import { CoreCourseModuleData } from '@features/course/services/course-helper';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUser } from '@features/user/services/user';
import { ADDON_MOD_FORUM_MARK_READ_EVENT, ADDON_MOD_FORUM_PAGE_NAME } from '../../constants';
@@ -57,7 +57,7 @@ export class AddonModForumModuleHandlerService extends CoreModuleHandlerBase imp
const data = await super.getData(module, courseId);
const customData = module.customdata ?
- CoreTextUtils.parseJSON<{ trackingtype?: string | number } | ''>(module.customdata, {}) : {};
+ CoreText.parseJSON<{ trackingtype?: string | number } | ''>(module.customdata, {}) : {};
const trackingType = typeof customData !== 'string' && customData.trackingtype !== undefined ?
Number(customData.trackingtype) : undefined;
diff --git a/src/addons/mod/glossary/components/index/index.ts b/src/addons/mod/glossary/components/index/index.ts
index 28358bc66..ec6f8bd57 100644
--- a/src/addons/mod/glossary/components/index/index.ts
+++ b/src/addons/mod/glossary/components/index/index.ts
@@ -29,7 +29,7 @@ import { IonContent } from '@ionic/angular';
import { CoreNavigator } from '@services/navigator';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { Translate } from '@singletons';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import {
@@ -312,7 +312,7 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity
// Consider it is 'letter_all'.
const getDivider = (entry) => {
// Try to get the first letter without HTML tags.
- const noTags = CoreTextUtils.cleanTags(entry.concept);
+ const noTags = CoreText.cleanTags(entry.concept);
return (noTags || entry.concept).substring(0, 1).toUpperCase();
};
diff --git a/src/addons/mod/glossary/pages/edit/edit.ts b/src/addons/mod/glossary/pages/edit/edit.ts
index e178775f1..950751961 100644
--- a/src/addons/mod/glossary/pages/edit/edit.ts
+++ b/src/addons/mod/glossary/pages/edit/edit.ts
@@ -24,7 +24,7 @@ import { CoreNavigator } from '@services/navigator';
import { CoreNetwork } from '@services/network';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUtils } from '@services/utils/utils';
import { Translate } from '@singletons';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
@@ -472,7 +472,7 @@ class AddonModGlossaryOfflineFormHandler extends AddonModGlossaryFormHandler {
const originalData = this.page.originalData;
const data = this.page.data;
const options = this.getSaveOptions(glossary);
- const definition = CoreTextUtils.formatHtmlLines(data.definition);
+ const definition = CoreText.formatHtmlLines(data.definition);
if (!originalData) {
return;
@@ -561,7 +561,7 @@ class AddonModGlossaryNewFormHandler extends AddonModGlossaryFormHandler {
): Promise {
const data = this.page.data;
const options = this.getSaveOptions(glossary);
- const definition = CoreTextUtils.formatHtmlLines(data.definition);
+ const definition = CoreText.formatHtmlLines(data.definition);
await this.checkDuplicates(glossary);
await AddonModGlossaryOffline.addOfflineEntry(
@@ -594,7 +594,7 @@ class AddonModGlossaryNewFormHandler extends AddonModGlossaryFormHandler {
): Promise {
const data = this.page.data;
const options = this.getSaveOptions(glossary);
- const definition = CoreTextUtils.formatHtmlLines(data.definition);
+ const definition = CoreText.formatHtmlLines(data.definition);
const entryId = await AddonModGlossary.addEntry(
glossary.id,
data.concept,
@@ -674,7 +674,7 @@ class AddonModGlossaryOnlineFormHandler extends AddonModGlossaryFormHandler {
const data = this.page.data;
const options = this.getSaveOptions(glossary);
- const definition = CoreTextUtils.formatHtmlLines(data.definition);
+ const definition = CoreText.formatHtmlLines(data.definition);
// Upload attachments, if any.
let attachmentsId: number | undefined = undefined;
diff --git a/src/addons/mod/glossary/services/glossary-offline.ts b/src/addons/mod/glossary/services/glossary-offline.ts
index a4807e6f9..96ee41b59 100644
--- a/src/addons/mod/glossary/services/glossary-offline.ts
+++ b/src/addons/mod/glossary/services/glossary-offline.ts
@@ -16,7 +16,7 @@ import { Injectable } from '@angular/core';
import { CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
import { CoreFile } from '@services/file';
import { CoreSites } from '@services/sites';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { makeSingleton } from '@singletons';
import { CoreEvents } from '@singletons/events';
import { CorePath } from '@singletons/path';
@@ -269,9 +269,9 @@ export class AddonModGlossaryOfflineProvider {
*/
protected parseRecord(record: AddonModGlossaryOfflineEntryDBRecord): AddonModGlossaryOfflineEntry {
return Object.assign(record, {
- options: > CoreTextUtils.parseJSON(record.options),
+ options: > CoreText.parseJSON(record.options),
attachments: record.attachments ?
- CoreTextUtils.parseJSON(record.attachments) : undefined,
+ CoreText.parseJSON(record.attachments) : undefined,
});
}
diff --git a/src/addons/mod/h5pactivity/components/index/index.ts b/src/addons/mod/h5pactivity/components/index/index.ts
index 542a2b366..f202bacb8 100644
--- a/src/addons/mod/h5pactivity/components/index/index.ts
+++ b/src/addons/mod/h5pactivity/components/index/index.ts
@@ -44,7 +44,7 @@ import {
AddonModH5PActivitySyncResult,
} from '../../services/h5pactivity-sync';
import { CoreFileHelper } from '@services/file-helper';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUtils } from '@services/utils/utils';
import {
ADDON_MOD_H5PACTIVITY_AUTO_SYNCED,
@@ -269,7 +269,7 @@ export class AddonModH5PActivityIndexComponent extends CoreCourseModuleMainActiv
return;
}
- const contentStateObj = CoreTextUtils.parseJSON<{h5p: string}>(contentState, { h5p: '{}' });
+ const contentStateObj = CoreText.parseJSON<{h5p: string}>(contentState, { h5p: '{}' });
// The H5P state doesn't always use JSON, so an h5p property was added to jsonize it.
this.contentState = contentStateObj.h5p ?? '{}';
diff --git a/src/addons/mod/h5pactivity/services/h5pactivity-sync.ts b/src/addons/mod/h5pactivity/services/h5pactivity-sync.ts
index dc822ba76..8a2c3361b 100644
--- a/src/addons/mod/h5pactivity/services/h5pactivity-sync.ts
+++ b/src/addons/mod/h5pactivity/services/h5pactivity-sync.ts
@@ -31,7 +31,7 @@ import {
AddonModH5PActivityData,
} from './h5pactivity';
import { CoreXAPIStateDBRecord, CoreXAPIStatementDBRecord } from '@features/xapi/services/database/xapi';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreErrorHelper } from '@services/error-helper';
import { CoreXAPIIRI } from '@features/xapi/classes/iri';
import { CoreXAPIItemAgent } from '@features/xapi/classes/item-agent';
import { CoreWSError } from '@classes/errors/wserror';
@@ -196,7 +196,7 @@ export class AddonModH5PActivitySyncProvider extends CoreCourseActivitySyncBaseP
} catch (error) {
if (
CoreUtils.isWebServiceError(error) ||
- CoreTextUtils.getErrorMessageFromError(error) === Translate.instant('core.course.modulenotfound')
+ CoreErrorHelper.getErrorMessageFromError(error) === Translate.instant('core.course.modulenotfound')
) {
// Activity no longer accessible. Delete the data and finish the sync.
await deleteOfflineData();
diff --git a/src/addons/mod/imscp/pages/view/view.ts b/src/addons/mod/imscp/pages/view/view.ts
index 41d3b782c..354dabe01 100644
--- a/src/addons/mod/imscp/pages/view/view.ts
+++ b/src/addons/mod/imscp/pages/view/view.ts
@@ -23,7 +23,7 @@ import { CoreCourseModulePrefetchDelegate } from '@features/course/services/modu
import { CoreNetwork } from '@services/network';
import { CoreNavigator } from '@services/navigator';
import { CoreDomUtils } from '@services/utils/dom';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreErrorHelper } from '@services/error-helper';
import { CoreUtils } from '@services/utils/utils';
import { Translate } from '@singletons';
import { AddonModImscp, AddonModImscpImscp, AddonModImscpTocItem } from '../../services/imscp';
@@ -124,7 +124,7 @@ export class AddonModImscpViewPage implements OnInit {
}
if (downloadResult?.failed) {
- const error = CoreTextUtils.getErrorMessageFromError(downloadResult.error) || downloadResult.error;
+ const error = CoreErrorHelper.getErrorMessageFromError(downloadResult.error) || downloadResult.error;
this.warning = Translate.instant('core.errordownloadingsomefiles') + (error ? ' ' + error : '');
} else {
this.warning = '';
diff --git a/src/addons/mod/imscp/services/imscp.ts b/src/addons/mod/imscp/services/imscp.ts
index caa1cab77..9e2741d88 100644
--- a/src/addons/mod/imscp/services/imscp.ts
+++ b/src/addons/mod/imscp/services/imscp.ts
@@ -21,7 +21,7 @@ import { CoreCourseLogHelper } from '@features/course/services/log-helper';
import { CoreNetwork } from '@services/network';
import { CoreFilepool } from '@services/filepool';
import { CoreSitesCommonWSOptions, CoreSites } from '@services/sites';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUtils } from '@services/utils/utils';
import { CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws';
import { makeSingleton, Translate } from '@singletons';
@@ -48,7 +48,7 @@ export class AddonModImscpProvider {
return [];
}
- return CoreTextUtils.parseJSON(contents[0].content || '');
+ return CoreText.parseJSON(contents[0].content || '');
}
/**
diff --git a/src/addons/mod/lesson/components/index/index.ts b/src/addons/mod/lesson/components/index/index.ts
index d5ad92415..d89a3cc3f 100644
--- a/src/addons/mod/lesson/components/index/index.ts
+++ b/src/addons/mod/lesson/components/index/index.ts
@@ -24,7 +24,7 @@ import { CoreGroupInfo, CoreGroups } from '@services/groups';
import { CoreNavigator } from '@services/navigator';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreForms } from '@singletons/form';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUtils } from '@services/utils/utils';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { AddonModLessonRetakeFinishedInSyncDBRecord } from '../../services/database/lesson';
@@ -550,20 +550,20 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
if (formattedData.lessonscored) {
if (formattedData.numofattempts && formattedData.avescore != null) {
- formattedData.avescore = CoreTextUtils.roundToDecimals(formattedData.avescore, 2);
+ formattedData.avescore = CoreText.roundToDecimals(formattedData.avescore, 2);
}
if (formattedData.highscore != null) {
- formattedData.highscore = CoreTextUtils.roundToDecimals(formattedData.highscore, 2);
+ formattedData.highscore = CoreText.roundToDecimals(formattedData.highscore, 2);
}
if (formattedData.lowscore != null) {
- formattedData.lowscore = CoreTextUtils.roundToDecimals(formattedData.lowscore, 2);
+ formattedData.lowscore = CoreText.roundToDecimals(formattedData.lowscore, 2);
}
}
if (formattedData.students) {
// Get the user data for each student returned.
await CoreUtils.allPromises(formattedData.students.map(async (student) => {
- student.bestgrade = CoreTextUtils.roundToDecimals(student.bestgrade, 2);
+ student.bestgrade = CoreText.roundToDecimals(student.bestgrade, 2);
const user = await CoreUtils.ignoreErrors(CoreUser.getProfile(student.id, this.courseId, true));
if (user) {
diff --git a/src/addons/mod/lesson/pages/user-retake/user-retake.ts b/src/addons/mod/lesson/pages/user-retake/user-retake.ts
index ba3cc1028..3ecb605a4 100644
--- a/src/addons/mod/lesson/pages/user-retake/user-retake.ts
+++ b/src/addons/mod/lesson/pages/user-retake/user-retake.ts
@@ -19,7 +19,7 @@ import { CoreUser } from '@features/user/services/user';
import { CoreNavigator } from '@services/navigator';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUtils } from '@services/utils/utils';
import { Translate } from '@singletons';
import {
@@ -145,7 +145,7 @@ export class AddonModLessonUserRetakePage implements OnInit {
throw new CoreError(Translate.instant('addon.mod_lesson.cannotfindattempt'));
}
- student.bestgrade = CoreTextUtils.roundToDecimals(student.bestgrade, 2);
+ student.bestgrade = CoreText.roundToDecimals(student.bestgrade, 2);
student.attempts.forEach((retake) => {
if (!this.selectedRetake && this.retakeNumber == retake.try) {
// The retake specified as parameter exists. Use it.
@@ -222,7 +222,7 @@ export class AddonModLessonUserRetakePage implements OnInit {
if (formattedData.userstats.gradeinfo) {
// Completed.
- formattedData.userstats.grade = CoreTextUtils.roundToDecimals(formattedData.userstats.grade, 2);
+ formattedData.userstats.grade = CoreText.roundToDecimals(formattedData.userstats.grade, 2);
this.timeTakenReadable = CoreTime.formatTime(formattedData.userstats.timetotake);
}
diff --git a/src/addons/mod/lesson/services/lesson-helper.ts b/src/addons/mod/lesson/services/lesson-helper.ts
index 9398f2b74..1bd5c7397 100644
--- a/src/addons/mod/lesson/services/lesson-helper.ts
+++ b/src/addons/mod/lesson/services/lesson-helper.ts
@@ -17,7 +17,7 @@ import { FormBuilder, FormControl, FormGroup } from '@angular/forms';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreFormFields } from '@singletons/form';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreTimeUtils } from '@services/utils/time';
import { makeSingleton, Translate } from '@singletons';
import {
@@ -28,6 +28,7 @@ import {
import { CoreTime } from '@singletons/time';
import { CoreUtils } from '@services/utils/utils';
import { AddonModLessonPageSubtype } from '../constants';
+import { convertTextToHTMLElement } from '@/core/utils/create-html-element';
/**
* Helper service that provides some features for quiz.
@@ -46,7 +47,7 @@ export class AddonModLessonHelperProvider {
* @returns Formatted data.
*/
formatActivityLink(activityLink: string): AddonModLessonActivityLink {
- const element = CoreDomUtils.convertToElement(activityLink);
+ const element = convertTextToHTMLElement(activityLink);
const anchor = element.querySelector('a');
if (!anchor) {
@@ -76,7 +77,7 @@ export class AddonModLessonHelperProvider {
buttonText: '',
content: '',
};
- const element = CoreDomUtils.convertToElement(html);
+ const element = convertTextToHTMLElement(html);
// Search the input button.
const button = element.querySelector('input[type="button"]');
@@ -100,7 +101,7 @@ export class AddonModLessonHelperProvider {
*/
getPageButtonsFromHtml(html: string): AddonModLessonPageButton[] {
const buttons: AddonModLessonPageButton[] = [];
- const element = CoreDomUtils.convertToElement(html);
+ const element = convertTextToHTMLElement(html);
// Get the container of the buttons if it exists.
let buttonsContainer = element.querySelector('.branchbuttoncontainer');
@@ -152,7 +153,7 @@ export class AddonModLessonHelperProvider {
*/
getPageContentsFromPageData(data: AddonModLessonGetPageDataWSResponse): string {
// Search the page contents inside the whole page HTML. Use data.pagecontent because it's filtered.
- const element = CoreDomUtils.convertToElement(data.pagecontent || '');
+ const element = convertTextToHTMLElement(data.pagecontent || '');
const contents = element.querySelector('.contents');
if (contents) {
@@ -178,7 +179,7 @@ export class AddonModLessonHelperProvider {
* @returns Question data.
*/
getQuestionFromPageData(questionForm: FormGroup, pageData: AddonModLessonGetPageDataWSResponse): AddonModLessonQuestion {
- const element = CoreDomUtils.convertToElement(pageData.pagecontent || '');
+ const element = convertTextToHTMLElement(pageData.pagecontent || '');
// Get the container of the question answers if it exists.
const fieldContainer = element.querySelector('.fcontainer');
@@ -463,7 +464,7 @@ export class AddonModLessonHelperProvider {
* @returns Object with the data to render the answer. If the answer doesn't require any parsing, return a string with the HTML.
*/
getQuestionPageAnswerDataFromHtml(html: string): AddonModLessonAnswerData {
- const element = CoreDomUtils.convertToElement(html);
+ const element = convertTextToHTMLElement(html);
// Check if it has a checkbox.
let input = element.querySelector('input[type="checkbox"][name*="answer"]');
@@ -567,7 +568,7 @@ export class AddonModLessonHelperProvider {
// Add some HTML to the answer if needed.
if (textarea) {
- data[textarea.name] = CoreTextUtils.formatHtmlLines( data[textarea.name] || '');
+ data[textarea.name] = CoreText.formatHtmlLines( data[textarea.name] || '');
}
} else if (question.template == 'multichoice' && ( question).multi) {
// Only send the options with value set to true.
@@ -588,7 +589,7 @@ export class AddonModLessonHelperProvider {
* @returns Feedback without the question text.
*/
removeQuestionFromFeedback(html: string): string {
- const element = CoreDomUtils.convertToElement(html);
+ const element = convertTextToHTMLElement(html);
// Remove the question text.
CoreDomUtils.removeElement(element, '.generalbox:not(.feedback):not(.correctanswer)');
diff --git a/src/addons/mod/lesson/services/lesson-offline.ts b/src/addons/mod/lesson/services/lesson-offline.ts
index 5e50397db..daf5b656c 100644
--- a/src/addons/mod/lesson/services/lesson-offline.ts
+++ b/src/addons/mod/lesson/services/lesson-offline.ts
@@ -15,7 +15,7 @@
import { Injectable } from '@angular/core';
import { CoreSites } from '@services/sites';
import { CoreFormFields } from '@singletons/form';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreTimeUtils } from '@services/utils/time';
import { CoreUtils } from '@services/utils/utils';
import { makeSingleton } from '@singletons';
@@ -454,8 +454,8 @@ export class AddonModLessonOfflineProvider {
protected parsePageAttempt(attempt: AddonModLessonPageAttemptDBRecord): AddonModLessonPageAttemptRecord {
return {
...attempt,
- data: attempt.data ? CoreTextUtils.parseJSON(attempt.data) : null,
- useranswer: attempt.useranswer ? CoreTextUtils.parseJSON(attempt.useranswer) : null,
+ data: attempt.data ? CoreText.parseJSON(attempt.data) : null,
+ useranswer: attempt.useranswer ? CoreText.parseJSON(attempt.useranswer) : null,
};
}
diff --git a/src/addons/mod/lesson/services/lesson.ts b/src/addons/mod/lesson/services/lesson.ts
index 1fc068f10..b9dcd4105 100644
--- a/src/addons/mod/lesson/services/lesson.ts
+++ b/src/addons/mod/lesson/services/lesson.ts
@@ -18,8 +18,8 @@ import { CoreSite } from '@classes/sites/site';
import { CoreCourseCommonModWSOptions } from '@features/course/services/course';
import { CoreCourseLogHelper } from '@features/course/services/log-helper';
import { CoreSites, CoreSitesCommonWSOptions, CoreSitesReadingStrategy } from '@services/sites';
-import { CoreDomUtils } from '@services/utils/dom';
-import { CoreTextUtils } from '@services/utils/text';
+import { convertTextToHTMLElement } from '@/core/utils/create-html-element';
+import { CoreText } from '@singletons/text';
import { CoreUtils } from '@services/utils/utils';
import { CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws';
import { makeSingleton, Translate } from '@singletons';
@@ -90,7 +90,7 @@ export class AddonModLessonProvider {
className: string,
): string {
// Add a table row containing the answer.
- feedback += '' + (answerFormat ? answer : CoreTextUtils.cleanTags(answer)) +
+ feedback += ' ' + (answerFormat ? answer : CoreText.cleanTags(answer)) +
' ';
// If the response exists, add a table row containing the response. If not, add en empty row.
@@ -164,7 +164,7 @@ export class AddonModLessonProvider {
if (page.answerdata && !this.answerPageIsQuestion(page)) {
// It isn't a question page, but it can be an end of branch, etc. Check if the first answer has a button.
if (page.answerdata.answers && page.answerdata.answers[0]) {
- const element = CoreDomUtils.convertToElement(page.answerdata.answers[0][0]);
+ const element = convertTextToHTMLElement(page.answerdata.answers[0][0]);
return !!element.querySelector('input[type="button"]');
}
@@ -301,7 +301,7 @@ export class AddonModLessonProvider {
}
// Progress calculation as a percent.
- return CoreTextUtils.roundToDecimals(viewedPagesIds.length / Object.keys(validPages).length, 2) * 100;
+ return CoreText.roundToDecimals(viewedPagesIds.length / Object.keys(validPages).length, 2) * 100;
}
/**
@@ -482,7 +482,7 @@ export class AddonModLessonProvider {
return;
}
- value = CoreTextUtils.decodeHTML(value);
+ value = CoreText.decodeHTML(value);
userResponse.push(value);
if (answers[id] !== undefined) {
@@ -747,7 +747,7 @@ export class AddonModLessonProvider {
}
} else {
expectedAnswer = expectedAnswer.replace('*', '#####');
- expectedAnswer = CoreTextUtils.escapeForRegex(expectedAnswer);
+ expectedAnswer = CoreText.escapeForRegex(expectedAnswer);
expectedAnswer = expectedAnswer.replace('#####', '.*');
}
@@ -830,7 +830,7 @@ export class AddonModLessonProvider {
this.checkOtherAnswers(lesson, pageData, result);
result.userresponse = studentAnswer;
- result.studentanswer = CoreTextUtils.s(studentAnswer); // Clean student answer as it goes to output.
+ result.studentanswer = CoreText.s(studentAnswer); // Clean student answer as it goes to output.
}
/**
@@ -981,7 +981,7 @@ export class AddonModLessonProvider {
response.data.forEach((entry) => {
if (entry.value && typeof entry.value == 'string' && entry.value !== '1') {
// It's a JSON encoded object. Try to decode it.
- entry.value = CoreTextUtils.parseJSON(entry.value);
+ entry.value = CoreText.parseJSON(entry.value);
}
map[entry.name] = entry;
@@ -1108,7 +1108,7 @@ export class AddonModLessonProvider {
}
if (lesson.grade !== undefined && lesson.grade !== CoreGradeType.NONE) {
- entryData.grade = CoreTextUtils.roundToDecimals(gradeInfo.grade * lesson.grade / 100, 1);
+ entryData.grade = CoreText.roundToDecimals(gradeInfo.grade * lesson.grade / 100, 1);
entryData.total = lesson.grade;
this.addResultValueEolPage(result, 'yourcurrentgradeisoutof', entryData, true);
}
@@ -1904,7 +1904,7 @@ export class AddonModLessonProvider {
if (lesson.grade !== undefined && lesson.grade !== CoreGradeType.NONE) {
this.addMessage(messages, 'addon.mod_lesson.yourcurrentgradeisoutof', { $a: {
- grade: CoreTextUtils.roundToDecimals(gradeInfo.grade * lesson.grade / 100, 1),
+ grade: CoreText.roundToDecimals(gradeInfo.grade * lesson.grade / 100, 1),
total: lesson.grade,
} });
}
@@ -2920,7 +2920,7 @@ export class AddonModLessonProvider {
}
if (result.total) { // Not zero.
- result.grade = CoreTextUtils.roundToDecimals(result.earned * 100 / result.total, 5);
+ result.grade = CoreText.roundToDecimals(result.earned * 100 / result.total, 5);
}
return result;
diff --git a/src/addons/mod/lti/services/lti.ts b/src/addons/mod/lti/services/lti.ts
index f88d4c3b6..2e9df9064 100644
--- a/src/addons/mod/lti/services/lti.ts
+++ b/src/addons/mod/lti/services/lti.ts
@@ -21,7 +21,7 @@ import { CoreCourseLogHelper } from '@features/course/services/log-helper';
import { CoreFile } from '@services/file';
import { CorePlatform } from '@services/platform';
import { CoreSites, CoreSitesCommonWSOptions } from '@services/sites';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUrl } from '@singletons/url';
import { CoreUtils } from '@services/utils/utils';
import { CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws';
@@ -64,9 +64,9 @@ export class AddonModLtiProvider {
if (p.name == 'ext_submit') {
text += ' \n';
+ text += ' value="' + CoreText.escapeHTML(p.value) + '"/>\n';
});
text += '\n';
diff --git a/src/addons/mod/page/components/index/index.ts b/src/addons/mod/page/components/index/index.ts
index 5e78527f1..ad368ca1f 100644
--- a/src/addons/mod/page/components/index/index.ts
+++ b/src/addons/mod/page/components/index/index.ts
@@ -15,7 +15,7 @@
import { Component, OnInit, Optional } from '@angular/core';
import { CoreCourseModuleMainResourceComponent } from '@features/course/classes/main-resource-component';
import { CoreCourseContentsPage } from '@features/course/pages/contents/contents';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUtils } from '@services/utils/utils';
import { AddonModPagePage, AddonModPage } from '../../services/page';
import { AddonModPageHelper } from '../../services/page-helper';
@@ -89,7 +89,7 @@ export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComp
// Check if description and timemodified should be displayed.
if (this.page.displayoptions) {
const options: Record =
- CoreTextUtils.unserialize(this.page.displayoptions) || {};
+ CoreText.unserialize(this.page.displayoptions) || {};
this.displayDescription = options.printintro === undefined ||
CoreUtils.isTrueOrOne(options.printintro);
diff --git a/src/addons/mod/page/services/page-helper.ts b/src/addons/mod/page/services/page-helper.ts
index 406c71bcd..bb07c18c0 100644
--- a/src/addons/mod/page/services/page-helper.ts
+++ b/src/addons/mod/page/services/page-helper.ts
@@ -14,7 +14,7 @@
import { Injectable } from '@angular/core';
import { CoreError } from '@classes/errors/error';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreUrl } from '@singletons/url';
import { CoreFile } from '@services/file';
import { CoreSites } from '@services/sites';
import { CoreFilepool } from '@services/filepool';
@@ -54,7 +54,7 @@ export class AddonModPageHelperProvider {
// Add the folders without the leading slash.
key = content.filepath.substring(1) + key;
}
- paths[CoreTextUtils.decodeURIComponent(key)] = url;
+ paths[CoreUrl.decodeURIComponent(key)] = url;
}
});
diff --git a/src/addons/mod/quiz/components/index/index.ts b/src/addons/mod/quiz/components/index/index.ts
index 0e2d76bd3..3d6208f26 100644
--- a/src/addons/mod/quiz/components/index/index.ts
+++ b/src/addons/mod/quiz/components/index/index.ts
@@ -22,7 +22,7 @@ import { CoreQuestionBehaviourDelegate } from '@features/question/services/behav
import { IonContent } from '@ionic/angular';
import { CoreNavigator } from '@services/navigator';
import { CoreDomUtils } from '@services/utils/dom';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUtils } from '@services/utils/utils';
import { Translate } from '@singletons';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
@@ -192,7 +192,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
if (warnings?.length) {
// Show warnings and delete them so they aren't shown again.
- CoreDomUtils.showErrorModal(CoreTextUtils.buildMessage(warnings));
+ CoreDomUtils.showErrorModal(CoreText.buildMessage(warnings));
await AddonModQuizSync.setSyncWarnings(quiz.id, []);
}
diff --git a/src/addons/mod/quiz/services/handlers/prefetch.ts b/src/addons/mod/quiz/services/handlers/prefetch.ts
index 9f30d9406..14fd3ad8e 100644
--- a/src/addons/mod/quiz/services/handlers/prefetch.ts
+++ b/src/addons/mod/quiz/services/handlers/prefetch.ts
@@ -23,7 +23,7 @@ import { CoreCourses } from '@features/courses/services/courses';
import { CoreQuestionHelper } from '@features/question/services/question-helper';
import { CoreFilepool } from '@services/filepool';
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUtils } from '@services/utils/utils';
import { CoreWSFile } from '@services/ws';
import { makeSingleton } from '@singletons';
@@ -334,7 +334,7 @@ export class AddonModQuizPrefetchHandlerService extends CoreCourseActivityPrefet
if (canStart && (!attempt || AddonModQuiz.isAttemptCompleted(attempt.state))) {
// Check if the user can attempt the quiz.
if (attemptAccessInfo.preventnewattemptreasons.length) {
- throw new CoreError(CoreTextUtils.buildMessage(attemptAccessInfo.preventnewattemptreasons));
+ throw new CoreError(CoreText.buildMessage(attemptAccessInfo.preventnewattemptreasons));
}
startAttempt = true;
diff --git a/src/addons/mod/quiz/services/quiz-helper.ts b/src/addons/mod/quiz/services/quiz-helper.ts
index 4026938fb..d1461a886 100644
--- a/src/addons/mod/quiz/services/quiz-helper.ts
+++ b/src/addons/mod/quiz/services/quiz-helper.ts
@@ -42,6 +42,7 @@ import { CoreGroups } from '@services/groups';
import { CoreTimeUtils } from '@services/utils/time';
import { CoreModals } from '@services/modals';
import { CoreLoadings } from '@services/loadings';
+import { convertTextToHTMLElement } from '@/core/utils/create-html-element';
/**
* Helper service that provides some features for quiz.
@@ -301,7 +302,7 @@ export class AddonModQuizHelperProvider {
* @returns Question's mark.
*/
getQuestionMarkFromHtml(html: string): string | undefined {
- const element = CoreDomUtils.convertToElement(html);
+ const element = convertTextToHTMLElement(html);
return CoreDomUtils.getContentsOfElement(element, '.grade');
}
diff --git a/src/addons/mod/quiz/services/quiz.ts b/src/addons/mod/quiz/services/quiz.ts
index 07604d170..7384b276e 100644
--- a/src/addons/mod/quiz/services/quiz.ts
+++ b/src/addons/mod/quiz/services/quiz.ts
@@ -29,7 +29,7 @@ import {
} from '@features/question/services/question';
import { CoreQuestionDelegate } from '@features/question/services/question-delegate';
import { CoreSites, CoreSitesCommonWSOptions, CoreSitesReadingStrategy } from '@services/sites';
-import { CoreDomUtils } from '@services/utils/dom';
+import { convertTextToHTMLElement } from '@/core/utils/create-html-element';
import { CoreTimeUtils } from '@services/utils/time';
import { CoreUtils } from '@services/utils/utils';
import { CoreStatusWithWarningsWSResponse, CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws';
@@ -1588,7 +1588,7 @@ export class AddonModQuizProvider {
* @returns Whether it's blocked.
*/
isQuestionBlocked(question: CoreQuestionQuestionParsed): boolean {
- const element = CoreDomUtils.convertToElement(question.html);
+ const element = convertTextToHTMLElement(question.html);
return !!element.querySelector('.mod_quiz-blocked_question_warning');
}
diff --git a/src/addons/mod/resource/components/index/index.ts b/src/addons/mod/resource/components/index/index.ts
index 46e30f194..27f536692 100644
--- a/src/addons/mod/resource/components/index/index.ts
+++ b/src/addons/mod/resource/components/index/index.ts
@@ -24,7 +24,7 @@ import { CoreFileHelper } from '@services/file-helper';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreMimetypeUtils } from '@services/utils/mimetype';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUtils, OpenFileAction } from '@services/utils/utils';
import { NgZone, Translate } from '@singletons';
import { Subscription } from 'rxjs';
@@ -118,7 +118,7 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource
const resource = await AddonModResource.getResourceData(this.courseId, this.module.id);
this.description = resource.intro || '';
const options: AddonModResourceCustomData =
- resource.displayoptions ? CoreTextUtils.unserialize(resource.displayoptions) : {};
+ resource.displayoptions ? CoreText.unserialize(resource.displayoptions) : {};
this.displayDescription = options.printintro === undefined || !!options.printintro;
this.dataRetrieved.emit(resource);
@@ -172,11 +172,11 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource
if ('contentsinfo' in this.module && this.module.contentsinfo) {
mimetype = this.module.contentsinfo.mimetypes[0];
- this.readableSize = CoreTextUtils.bytesToSize(this.module.contentsinfo.filessize, 1);
+ this.readableSize = CoreText.bytesToSize(this.module.contentsinfo.filessize, 1);
this.timemodified = this.module.contentsinfo.lastmodified * 1000;
} else {
mimetype = await CoreUtils.getMimeTypeFromUrl(CoreFileHelper.getFileUrl(contents[0]));
- this.readableSize = CoreTextUtils.bytesToSize(contents[0].filesize, 1);
+ this.readableSize = CoreText.bytesToSize(contents[0].filesize, 1);
this.timemodified = contents[0].timemodified * 1000;
}
diff --git a/src/addons/mod/resource/services/resource-helper.ts b/src/addons/mod/resource/services/resource-helper.ts
index 66afb0a74..0f9fc20b6 100644
--- a/src/addons/mod/resource/services/resource-helper.ts
+++ b/src/addons/mod/resource/services/resource-helper.ts
@@ -29,7 +29,7 @@ import { makeSingleton, Translate } from '@singletons';
import { CorePath } from '@singletons/path';
import { AddonModResource, AddonModResourceCustomData } from './resource';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreTimeUtils } from '@services/utils/time';
import { ADDON_MOD_RESOURCE_COMPONENT } from '../constants';
import { CoreLoadings } from '@services/loadings';
@@ -235,15 +235,15 @@ export class AddonModResourceHelperProvider {
*/
protected async getModuleOptions(module: CoreCourseModuleData, courseId: number): Promise {
if (module.customdata !== undefined) {
- const customData: { displayoptions: string } | string = CoreTextUtils.parseJSON(module.customdata);
+ const customData: { displayoptions: string } | string = CoreText.parseJSON(module.customdata);
const displayOptions = typeof customData === 'object' ? customData.displayoptions : customData;
- return CoreTextUtils.unserialize(displayOptions);
+ return CoreText.unserialize(displayOptions);
}
// Get the resource data. Legacy version (from 3.5 to 3.6.6)
const info = await AddonModResource.getResourceData(courseId, module.id);
- const options: AddonModResourceCustomData = CoreTextUtils.unserialize(info.displayoptions);
+ const options: AddonModResourceCustomData = CoreText.unserialize(info.displayoptions);
if (!module.contents?.[0] || options.filedetails !== undefined) {
// Contents attribute should be loaded at this point and it's needed to get mainFile.
@@ -303,7 +303,7 @@ export class AddonModResourceHelperProvider {
const extra: string[] = [];
if (options.showsize && details.size) {
- extra.push(CoreTextUtils.bytesToSize(details.size, 1));
+ extra.push(CoreText.bytesToSize(details.size, 1));
}
if (options.showtype) {
diff --git a/src/addons/mod/scorm/services/scorm-offline.ts b/src/addons/mod/scorm/services/scorm-offline.ts
index 4fd951ce9..9ba1aa1f5 100644
--- a/src/addons/mod/scorm/services/scorm-offline.ts
+++ b/src/addons/mod/scorm/services/scorm-offline.ts
@@ -16,7 +16,7 @@ import { Injectable } from '@angular/core';
import { CoreUser } from '@features/user/services/user';
import { CoreSites } from '@services/sites';
import { CoreSync } from '@services/sync';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreTimeUtils } from '@services/utils/time';
import { CoreUtils } from '@services/utils/utils';
import { makeSingleton } from '@singletons';
@@ -297,7 +297,7 @@ export class AddonModScormOfflineProvider {
case 'cmi.core.score.raw':
case 'cmi.score.raw':
- formatted.score_raw = CoreTextUtils.roundToDecimals(Number(value), 2); // Round to 2 decimals max.
+ formatted.score_raw = CoreText.roundToDecimals(Number(value), 2); // Round to 2 decimals max.
break;
case 'cmi.core.session_time':
@@ -813,7 +813,7 @@ export class AddonModScormOfflineProvider {
protected parseAttempt(attempt: AddonModScormAttemptDBRecord): AddonModScormOfflineAttempt {
return {
...attempt,
- snapshot: attempt.snapshot ? CoreTextUtils.parseJSON(attempt.snapshot) : null,
+ snapshot: attempt.snapshot ? CoreText.parseJSON(attempt.snapshot) : null,
};
}
@@ -826,7 +826,7 @@ export class AddonModScormOfflineProvider {
protected parseTracks(tracks: AddonModScormTrackDBRecord[]): AddonModScormOfflineTrack[] {
return tracks.map((track) => ({
...track,
- value: track.value ? CoreTextUtils.parseJSON(track.value) : null,
+ value: track.value ? CoreText.parseJSON(track.value) : null,
}));
}
diff --git a/src/addons/mod/scorm/services/scorm.ts b/src/addons/mod/scorm/services/scorm.ts
index f0025d62e..8cc9af793 100644
--- a/src/addons/mod/scorm/services/scorm.ts
+++ b/src/addons/mod/scorm/services/scorm.ts
@@ -21,7 +21,7 @@ import { CoreCourseLogHelper } from '@features/course/services/log-helper';
import { CoreFilepool } from '@services/filepool';
import { CoreSites, CoreSitesCommonWSOptions, CoreSitesReadingStrategy } from '@services/sites';
import { CoreSync } from '@services/sync';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreTimeUtils } from '@services/utils/time';
import { CoreUrl } from '@singletons/url';
import { CoreUtils } from '@services/utils/utils';
@@ -400,7 +400,7 @@ export class AddonModScormProvider {
if (scorm.grademethod !== AddonModScormGradingMethod.GRADESCOES && scorm.maxgrade) {
grade = (grade / scorm.maxgrade) * 100;
- return Translate.instant('core.percentagenumber', { $a: CoreTextUtils.roundToDecimals(grade, 2) });
+ return Translate.instant('core.percentagenumber', { $a: CoreText.roundToDecimals(grade, 2) });
}
return String(grade);
diff --git a/src/addons/mod/survey/components/index/index.ts b/src/addons/mod/survey/components/index/index.ts
index c1adf5b53..c87fba5f6 100644
--- a/src/addons/mod/survey/components/index/index.ts
+++ b/src/addons/mod/survey/components/index/index.ts
@@ -20,7 +20,7 @@ import { CoreCourseContentsPage } from '@features/course/pages/contents/contents
import { IonContent } from '@ionic/angular';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { Translate } from '@singletons';
import { CoreEvents } from '@singletons/events';
import { getPrefetchHandlerInstance } from '../../services/handlers/prefetch';
@@ -156,7 +156,7 @@ export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityCo
if (question.multiArray && !question.multiArray.length && question.parent === 0 && question.type > 0) {
// Options shown in a select. Remove all HTML.
- question.optionsArray = question.optionsArray?.map((option) => CoreTextUtils.cleanTags(option));
+ question.optionsArray = question.optionsArray?.map((option) => CoreText.cleanTags(option));
}
});
}
diff --git a/src/addons/mod/survey/services/survey-offline.ts b/src/addons/mod/survey/services/survey-offline.ts
index e049d3abc..82cc63e4c 100644
--- a/src/addons/mod/survey/services/survey-offline.ts
+++ b/src/addons/mod/survey/services/survey-offline.ts
@@ -14,7 +14,7 @@
import { Injectable } from '@angular/core';
import { CoreSites } from '@services/sites';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { makeSingleton } from '@singletons';
import { AddonModSurveyAnswersDBRecord, SURVEY_TABLE } from './database/survey';
import { AddonModSurveySubmitAnswerData } from './survey';
@@ -51,7 +51,7 @@ export class AddonModSurveyOfflineProvider {
const entries = await site.getDb().getAllRecords(SURVEY_TABLE);
return entries.map((entry) => Object.assign(entry, {
- answers: CoreTextUtils.parseJSON(entry.answers),
+ answers: CoreText.parseJSON(entry.answers),
}));
}
@@ -91,7 +91,7 @@ export class AddonModSurveyOfflineProvider {
);
return Object.assign(entry, {
- answers: CoreTextUtils.parseJSON(entry.answers),
+ answers: CoreText.parseJSON(entry.answers),
});
}
diff --git a/src/addons/mod/url/components/index/index.ts b/src/addons/mod/url/components/index/index.ts
index b6f70314c..9c2cc0463 100644
--- a/src/addons/mod/url/components/index/index.ts
+++ b/src/addons/mod/url/components/index/index.ts
@@ -19,7 +19,7 @@ import { CoreCourseModuleMainResourceComponent } from '@features/course/classes/
import { CoreCourseContentsPage } from '@features/course/pages/contents/contents';
import { CoreCourse } from '@features/course/services/course';
import { CoreMimetypeUtils } from '@services/utils/mimetype';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { AddonModUrl, AddonModUrlDisplayOptions, AddonModUrlUrl } from '../../services/url';
import { AddonModUrlHelper } from '../../services/url-helper';
import { ADDON_MOD_URL_COMPONENT } from '../../constants';
@@ -90,7 +90,7 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo
this.dataRetrieved.emit(url);
if (url.displayoptions) {
- const unserialized = CoreTextUtils.unserialize(url.displayoptions);
+ const unserialized = CoreText.unserialize(url.displayoptions);
this.displayDescription = unserialized.printintro === undefined || !!unserialized.printintro;
}
diff --git a/src/addons/mod/wiki/pages/edit/edit.ts b/src/addons/mod/wiki/pages/edit/edit.ts
index e6a7ca15a..c9d11962f 100644
--- a/src/addons/mod/wiki/pages/edit/edit.ts
+++ b/src/addons/mod/wiki/pages/edit/edit.ts
@@ -21,7 +21,7 @@ import { CoreNavigator } from '@services/navigator';
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
import { CoreSync } from '@services/sync';
import { CoreDomUtils } from '@services/utils/dom';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUtils } from '@services/utils/utils';
import { CoreWSFile } from '@services/ws';
import { Translate } from '@singletons';
@@ -33,6 +33,7 @@ import { AddonModWikiSync } from '../../services/wiki-sync';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { ADDON_MOD_WIKI_COMPONENT, ADDON_MOD_WIKI_PAGE_CREATED_EVENT, ADDON_MOD_WIKI_RENEW_LOCK_TIME } from '../../constants';
import { CoreLoadings } from '@services/loadings';
+import { CoreFileHelper } from '@services/file-helper';
/**
* Page that allows adding or editing a wiki page.
@@ -94,7 +95,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave {
this.userId = CoreNavigator.getRouteNumberParam('userId');
const pageTitle = CoreNavigator.getRouteParam('pageTitle');
- this.originalTitle = pageTitle ? CoreTextUtils.cleanTags(pageTitle.replace(/\+/g, ' '), { singleLine: true }) : '';
+ this.originalTitle = pageTitle ? CoreText.cleanTags(pageTitle.replace(/\+/g, ' '), { singleLine: true }) : '';
this.canEditTitle = !this.originalTitle;
this.title = this.originalTitle ?
@@ -182,7 +183,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave {
const editContents = await AddonModWiki.getPageForEditing(this.pageId, this.section);
// Get the original page contents, treating file URLs if needed.
- const content = CoreTextUtils.replacePluginfileUrls(editContents.content || '', this.subwikiFiles);
+ const content = CoreFileHelper.replacePluginfileUrls(editContents.content || '', this.subwikiFiles);
this.contentControl.setValue(content);
this.originalContent = content;
@@ -365,8 +366,8 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave {
const modal = await CoreLoadings.show('core.sending', true);
- text = CoreTextUtils.restorePluginfileUrls(text, this.subwikiFiles);
- text = CoreTextUtils.formatHtmlLines(text);
+ text = CoreFileHelper.restorePluginfileUrls(text, this.subwikiFiles);
+ text = CoreText.formatHtmlLines(text);
try {
if (this.editing && this.pageId) {
diff --git a/src/addons/mod/workshop/components/assessment-strategy/assessment-strategy.ts b/src/addons/mod/workshop/components/assessment-strategy/assessment-strategy.ts
index 9f8344d89..2ae834566 100644
--- a/src/addons/mod/workshop/components/assessment-strategy/assessment-strategy.ts
+++ b/src/addons/mod/workshop/components/assessment-strategy/assessment-strategy.ts
@@ -22,7 +22,6 @@ import { CoreFileSession } from '@services/file-session';
import { CoreSites } from '@services/sites';
import { CoreSync } from '@services/sync';
import { CoreDomUtils } from '@services/utils/dom';
-import { CoreTextUtils } from '@services/utils/text';
import { CoreUtils } from '@services/utils/utils';
import { Translate } from '@singletons';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
@@ -260,7 +259,7 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit, OnDe
}
// Compare feedback text.
- const text = CoreTextUtils.restorePluginfileUrls(this.feedbackText, this.data.assessment?.feedbackcontentfiles || []);
+ const text = CoreFileHelper.restorePluginfileUrls(this.feedbackText, this.data.assessment?.feedbackcontentfiles || []);
if (this.originalData.text != text) {
return true;
}
@@ -334,7 +333,7 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit, OnDe
);
}
- const text = CoreTextUtils.restorePluginfileUrls(this.feedbackText, this.data.assessment?.feedbackcontentfiles || []);
+ const text = CoreFileHelper.restorePluginfileUrls(this.feedbackText, this.data.assessment?.feedbackcontentfiles || []);
let assessmentData: CoreFormFields;
try {
diff --git a/src/addons/mod/workshop/pages/assessment/assessment.ts b/src/addons/mod/workshop/pages/assessment/assessment.ts
index eb1601a86..53f591a08 100644
--- a/src/addons/mod/workshop/pages/assessment/assessment.ts
+++ b/src/addons/mod/workshop/pages/assessment/assessment.ts
@@ -22,7 +22,7 @@ import { CoreNavigator } from '@services/navigator';
import { CoreSites } from '@services/sites';
import { CoreSync } from '@services/sync';
import { CoreDomUtils } from '@services/utils/dom';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { Translate } from '@singletons';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { CoreForms } from '@singletons/form';
@@ -378,7 +378,7 @@ export class AddonModWorkshopAssessmentPage implements OnInit, OnDestroy, CanLea
const grade = inputData.grade >= 0 ? String(inputData.grade) : '';
// Add some HTML to the message if needed.
- const text = CoreTextUtils.formatHtmlLines(inputData.text);
+ const text = CoreText.formatHtmlLines(inputData.text);
try {
// Try to send it to server.
diff --git a/src/addons/mod/workshop/pages/edit-submission/edit-submission.ts b/src/addons/mod/workshop/pages/edit-submission/edit-submission.ts
index 27ebfea2e..8e884b84d 100644
--- a/src/addons/mod/workshop/pages/edit-submission/edit-submission.ts
+++ b/src/addons/mod/workshop/pages/edit-submission/edit-submission.ts
@@ -24,7 +24,7 @@ import { CoreNavigator } from '@services/navigator';
import { CoreSites } from '@services/sites';
import { CoreSync } from '@services/sync';
import { CoreDomUtils } from '@services/utils/dom';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUtils } from '@services/utils/utils';
import { Translate } from '@singletons';
import { CoreEvents } from '@singletons/events';
@@ -45,6 +45,7 @@ import {
AddonModWorkshopSubmissionType,
} from '@addons/mod/workshop/constants';
import { CoreLoadings } from '@services/loadings';
+import { CoreDom } from '@singletons/dom';
/**
* Page that displays the workshop edit submission.
@@ -343,7 +344,7 @@ export class AddonModWorkshopEditSubmissionPage implements OnInit, OnDestroy, Ca
throw new CoreError(Translate.instant('addon.mod_workshop.submissionrequiredtitle'));
}
- const noText = CoreTextUtils.htmlIsBlank(inputData.content);
+ const noText = CoreDom.htmlIsBlank(inputData.content);
const noFiles = !inputData.attachmentfiles.length;
if ((this.textRequired && noText) || (this.fileRequired && noFiles) || (noText && noFiles)) {
@@ -359,7 +360,7 @@ export class AddonModWorkshopEditSubmissionPage implements OnInit, OnDestroy, Ca
// Add some HTML to the message if needed.
if (this.textAvailable) {
- inputData.content = CoreTextUtils.formatHtmlLines(inputData.content);
+ inputData.content = CoreText.formatHtmlLines(inputData.content);
}
// Upload attachments first if any.
diff --git a/src/addons/mod/workshop/pages/submission/submission.ts b/src/addons/mod/workshop/pages/submission/submission.ts
index 295fc7903..d7977bab1 100644
--- a/src/addons/mod/workshop/pages/submission/submission.ts
+++ b/src/addons/mod/workshop/pages/submission/submission.ts
@@ -25,7 +25,7 @@ import { CoreNavigator } from '@services/navigator';
import { CoreSites } from '@services/sites';
import { CoreSync } from '@services/sync';
import { CoreDomUtils } from '@services/utils/dom';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { Translate } from '@singletons';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { CoreForms } from '@singletons/form';
@@ -521,7 +521,7 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy, CanLea
inputData.grade = Number(inputData.grade) >= 0 ? inputData.grade : '';
// Add some HTML to the message if needed.
- inputData.text = CoreTextUtils.formatHtmlLines(inputData.text);
+ inputData.text = CoreText.formatHtmlLines(inputData.text);
// Try to send it to server.
try {
diff --git a/src/addons/mod/workshop/services/workshop-helper.ts b/src/addons/mod/workshop/services/workshop-helper.ts
index 7dec857eb..54091eeed 100644
--- a/src/addons/mod/workshop/services/workshop-helper.ts
+++ b/src/addons/mod/workshop/services/workshop-helper.ts
@@ -19,7 +19,7 @@ import { FileEntry } from '@awesome-cordova-plugins/file/ngx';
import { CoreFile } from '@services/file';
import { CoreFileEntry } from '@services/file-helper';
import { CoreSites } from '@services/sites';
-import { CoreTextUtils, CoreTextFormat } from '@services/utils/text';
+import { CoreText, CoreTextFormat } from '@singletons/text';
import { CoreUtils } from '@services/utils/utils';
import { makeSingleton, Translate } from '@singletons';
import { CoreFormFields } from '@singletons/form';
@@ -564,7 +564,7 @@ export class AddonModWorkshopHelperProvider {
return '0';
}
- value = CoreTextUtils.roundToDecimals(max * value / 100, decimals);
+ value = CoreText.roundToDecimals(max * value / 100, decimals);
return CoreUtils.formatFloat(value);
}
diff --git a/src/addons/mod/workshop/services/workshop-offline.ts b/src/addons/mod/workshop/services/workshop-offline.ts
index c88c06e68..1b84c1f66 100644
--- a/src/addons/mod/workshop/services/workshop-offline.ts
+++ b/src/addons/mod/workshop/services/workshop-offline.ts
@@ -16,7 +16,7 @@ import { Injectable } from '@angular/core';
import { CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
import { CoreFile } from '@services/file';
import { CoreSites } from '@services/sites';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreTimeUtils } from '@services/utils/time';
import { makeSingleton } from '@singletons';
import { CoreFormFields } from '@singletons/form';
@@ -239,7 +239,7 @@ export class AddonModWorkshopOfflineProvider {
protected parseSubmissionRecord(record: AddonModWorkshopSubmissionDBRecord): AddonModWorkshopOfflineSubmission {
return {
...record,
- attachmentsid: CoreTextUtils.parseJSON(record.attachmentsid),
+ attachmentsid: CoreText.parseJSON(record.attachmentsid),
};
}
@@ -355,7 +355,7 @@ export class AddonModWorkshopOfflineProvider {
protected parseAssessmentRecord(record: AddonModWorkshopAssessmentDBRecord): AddonModWorkshopOfflineAssessment {
return {
...record,
- inputdata: CoreTextUtils.parseJSON(record.inputdata),
+ inputdata: CoreText.parseJSON(record.inputdata),
};
}
@@ -486,7 +486,7 @@ export class AddonModWorkshopOfflineProvider {
return {
...record,
published: Boolean(record.published),
- gradeover: CoreTextUtils.parseJSON(record.gradeover),
+ gradeover: CoreText.parseJSON(record.gradeover),
};
}
@@ -616,7 +616,7 @@ export class AddonModWorkshopOfflineProvider {
): AddonModWorkshopOfflineEvaluateAssessment {
return {
...record,
- gradinggradeover: CoreTextUtils.parseJSON(record.gradinggradeover),
+ gradinggradeover: CoreText.parseJSON(record.gradinggradeover),
};
}
diff --git a/src/addons/mod/workshop/services/workshop-sync.ts b/src/addons/mod/workshop/services/workshop-sync.ts
index a666badd4..645a3fa38 100644
--- a/src/addons/mod/workshop/services/workshop-sync.ts
+++ b/src/addons/mod/workshop/services/workshop-sync.ts
@@ -21,7 +21,7 @@ import { CoreNetwork } from '@services/network';
import { CoreFileEntry } from '@services/file-helper';
import { CoreSites } from '@services/sites';
import { CoreSync, CoreSyncResult } from '@services/sync';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreErrorHelper } from '@services/error-helper';
import { CoreUtils } from '@services/utils/utils';
import { Translate, makeSingleton } from '@singletons';
import { CoreEvents } from '@singletons/events';
@@ -373,7 +373,7 @@ export class AddonModWorkshopSyncProvider extends CoreSyncBaseProvider {
- note.content = CoreTextUtils.decodeHTML(note.content);
+ note.content = CoreText.decodeHTML(note.content);
});
await AddonNotes.setOfflineDeletedNotes(notesList, this.courseId);
@@ -301,7 +301,7 @@ export class AddonNotesListPage implements OnInit, OnDestroy {
* @param warnings the warnings
*/
protected showSyncWarnings(warnings: string[]): void {
- const message = CoreTextUtils.buildMessage(warnings);
+ const message = CoreText.buildMessage(warnings);
if (message) {
CoreDomUtils.showAlert(undefined, message);
diff --git a/src/addons/notes/services/notes-sync.ts b/src/addons/notes/services/notes-sync.ts
index bfe3aecd2..c60a6f431 100644
--- a/src/addons/notes/services/notes-sync.ts
+++ b/src/addons/notes/services/notes-sync.ts
@@ -26,7 +26,7 @@ import { AddonNotes, AddonNotesCreateNoteData } from './notes';
import { AddonNotesOffline } from './notes-offline';
import { CoreArray } from '@singletons/array';
import { CoreAnyError } from '@classes/errors/error';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreErrorHelper } from '@services/error-helper';
/**
* Service to sync notes.
@@ -235,7 +235,7 @@ export class AddonNotesSyncProvider extends CoreSyncBaseProvider
Translate.instant('addon.notes.warningnotenotsent', {
course: 'fullname' in course ? course.fullname : courseId, // @deprecated since 4.3.
- error: CoreTextUtils.getErrorMessageFromError(error),
+ error: CoreErrorHelper.getErrorMessageFromError(error),
}));
}
diff --git a/src/addons/notifications/pages/settings/settings.ts b/src/addons/notifications/pages/settings/settings.ts
index 6be1b7032..c6702431d 100644
--- a/src/addons/notifications/pages/settings/settings.ts
+++ b/src/addons/notifications/pages/settings/settings.ts
@@ -39,7 +39,7 @@ import { CoreNavigator } from '@services/navigator';
import { CoreTime } from '@singletons/time';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { Translate } from '@singletons';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreErrorHelper } from '@services/error-helper';
import { CoreLoadings } from '@services/loadings';
/**
@@ -122,7 +122,7 @@ export class AddonNotificationsSettingsPage implements OnInit, OnDestroy {
this.logView();
} catch (error) {
if (error.errorcode === 'nopermissions') {
- this.warningMessage.set(CoreTextUtils.getErrorMessageFromError(error));
+ this.warningMessage.set(CoreErrorHelper.getErrorMessageFromError(error));
return;
}
diff --git a/src/addons/notifications/services/notifications.ts b/src/addons/notifications/services/notifications.ts
index 49ed2a791..542b24e7b 100644
--- a/src/addons/notifications/services/notifications.ts
+++ b/src/addons/notifications/services/notifications.ts
@@ -16,7 +16,7 @@ import { Injectable } from '@angular/core';
import { CoreSites, CoreSitesCommonWSOptions } from '@services/sites';
import { CoreWSExternalWarning } from '@services/ws';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreTimeUtils } from '@services/utils/time';
import { CoreUser, USER_NOREPLY_USER } from '@features/user/services/user';
import { CoreSite } from '@classes/sites/site';
@@ -120,7 +120,7 @@ export class AddonNotificationsProvider {
notification.read = notification.timeread > 0;
if (typeof notification.customdata === 'string') {
- notification.customdata = CoreTextUtils.parseJSON>(notification.customdata, {});
+ notification.customdata = CoreText.parseJSON>(notification.customdata, {});
}
// Try to set courseid the notification belongs to.
diff --git a/src/addons/privatefiles/pages/index/index.ts b/src/addons/privatefiles/pages/index/index.ts
index a241b1474..0dc3cc720 100644
--- a/src/addons/privatefiles/pages/index/index.ts
+++ b/src/addons/privatefiles/pages/index/index.ts
@@ -18,7 +18,7 @@ import { Md5 } from 'ts-md5/dist/md5';
import { CoreNetwork } from '@services/network';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { Translate } from '@singletons';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import {
@@ -214,8 +214,8 @@ export class AddonPrivateFilesIndexPage implements OnInit, OnDestroy {
// Get the info to calculate the available size.
this.filesInfo = await AddonPrivateFiles.getPrivateFilesInfo();
- this.spaceUsed = CoreTextUtils.bytesToSize(this.filesInfo.filesizewithoutreferences, 1);
- this.userQuotaReadable = CoreTextUtils.bytesToSize(this.userQuota, 1);
+ this.spaceUsed = CoreText.bytesToSize(this.filesInfo.filesizewithoutreferences, 1);
+ this.userQuotaReadable = CoreText.bytesToSize(this.userQuota, 1);
} else {
// User quota isn't useful, delete it.
delete this.userQuota;
diff --git a/src/addons/qtype/calculated/services/handlers/calculated.ts b/src/addons/qtype/calculated/services/handlers/calculated.ts
index 58f6ac24a..5bc610356 100644
--- a/src/addons/qtype/calculated/services/handlers/calculated.ts
+++ b/src/addons/qtype/calculated/services/handlers/calculated.ts
@@ -16,7 +16,7 @@ import { Injectable, Type } from '@angular/core';
import { CoreQuestionQuestionParsed, CoreQuestionsAnswers } from '@features/question/services/question';
import { CoreQuestionHandler } from '@features/question/services/question-delegate';
-import { CoreDomUtils } from '@services/utils/dom';
+import { convertTextToHTMLElement } from '@/core/utils/create-html-element';
import { CoreUtils } from '@services/utils/utils';
import { makeSingleton } from '@singletons';
import { AddonQtypeCalculatedComponent } from '../../component/calculated';
@@ -53,7 +53,7 @@ export class AddonQtypeCalculatedHandlerService implements CoreQuestionHandler {
*/
hasSeparateUnitField(question: CoreQuestionQuestionParsed): boolean {
if (!question.parsedSettings) {
- const element = CoreDomUtils.convertToElement(question.html);
+ const element = convertTextToHTMLElement(question.html);
return !!(element.querySelector('select[name*=unit]') || element.querySelector('input[type="radio"]'));
}
diff --git a/src/addons/qtype/ddwtos/classes/ddwtos.ts b/src/addons/qtype/ddwtos/classes/ddwtos.ts
index e34999889..ab7fb7641 100644
--- a/src/addons/qtype/ddwtos/classes/ddwtos.ts
+++ b/src/addons/qtype/ddwtos/classes/ddwtos.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import { CoreFormatTextDirective } from '@directives/format-text';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreDirectivesRegistry } from '@singletons/directives-registry';
import { CoreCoordinates, CoreDom } from '@singletons/dom';
import { CoreEventObserver } from '@singletons/events';
@@ -490,7 +490,7 @@ export class AddonQtypeDdwtosQuestion {
}
groupItems.forEach((item) => {
- item.innerHTML = CoreTextUtils.decodeHTML(item.innerHTML);
+ item.innerHTML = CoreText.decodeHTML(item.innerHTML);
});
// Wait to render in order to calculate size.
diff --git a/src/addons/qtype/essay/component/essay.ts b/src/addons/qtype/essay/component/essay.ts
index 197389dbd..8859c2264 100644
--- a/src/addons/qtype/essay/component/essay.ts
+++ b/src/addons/qtype/essay/component/essay.ts
@@ -19,7 +19,7 @@ import { FileEntry } from '@awesome-cordova-plugins/file/ngx';
import { CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
import { AddonModQuizEssayQuestion, CoreQuestionBaseComponent } from '@features/question/classes/base-question-component';
import { CoreQuestionHelper } from '@features/question/services/question-helper';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreFileSession } from '@services/file-session';
import { CoreQuestion } from '@features/question/services/question';
import { CoreFileEntry } from '@services/file-helper';
@@ -71,7 +71,7 @@ export class AddonQtypeEssayComponent extends CoreQuestionBaseComponent maxWords) {
return Translate.instant('addon.qtype_essay.maxwordlimitboundary', { $a: { limit: maxWords, count: count } });
} else if (count < minWords) {
@@ -292,7 +293,7 @@ export class AddonQtypeEssayHandlerService implements CoreQuestionHandler {
siteId?: string,
): Promise {
- const element = CoreDomUtils.convertToElement(question.html);
+ const element = convertTextToHTMLElement(question.html);
const attachmentsInput = element.querySelector('.attachments input[name*=_attachments]');
// Search the textarea to get its name.
@@ -374,7 +375,7 @@ export class AddonQtypeEssayHandlerService implements CoreQuestionHandler {
siteId?: string,
): Promise {
- const element = CoreDomUtils.convertToElement(question.html);
+ const element = convertTextToHTMLElement(question.html);
const attachmentsInput = element.querySelector('.attachments input[name*=_attachments]');
if (attachmentsInput) {
@@ -386,7 +387,7 @@ export class AddonQtypeEssayHandlerService implements CoreQuestionHandler {
return;
}
- const attachmentsData: CoreFileUploaderStoreFilesResult = CoreTextUtils.parseJSON(
+ const attachmentsData: CoreFileUploaderStoreFilesResult = CoreText.parseJSON(
answers.attachments_offline,
{
online: [],
@@ -438,7 +439,7 @@ export class AddonQtypeEssayHandlerService implements CoreQuestionHandler {
// Restore draftfile URLs.
const site = await CoreSites.getSite(siteId);
- answers[textarea.name] = CoreTextUtils.restoreDraftfileUrls(
+ answers[textarea.name] = CoreFileHelper.restoreDraftfileUrls(
site.getURL(),
answers[textarea.name],
question.html,
@@ -453,13 +454,13 @@ export class AddonQtypeEssayHandlerService implements CoreQuestionHandler {
isPlainText = question.parsedSettings.responseformat == 'monospaced' ||
question.parsedSettings.responseformat == 'plain';
} else {
- const questionEl = CoreDomUtils.convertToElement(question.html);
+ const questionEl = convertTextToHTMLElement(question.html);
isPlainText = !!questionEl.querySelector('.qtype_essay_monospaced') || !!questionEl.querySelector('.qtype_essay_plain');
}
if (!isPlainText) {
// Add some HTML to the text if needed.
- answers[textarea.name] = CoreTextUtils.formatHtmlLines( answers[textarea.name] || '');
+ answers[textarea.name] = CoreText.formatHtmlLines( answers[textarea.name] || '');
}
}
diff --git a/src/addons/userprofilefield/social/services/handlers/social.ts b/src/addons/userprofilefield/social/services/handlers/social.ts
index ca7335ef5..73f837bfe 100644
--- a/src/addons/userprofilefield/social/services/handlers/social.ts
+++ b/src/addons/userprofilefield/social/services/handlers/social.ts
@@ -16,7 +16,7 @@ import { Injectable, Type } from '@angular/core';
import { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from '@features/user/services/user-profile-field-delegate';
import { AddonUserProfileFieldSocialComponent } from '../../component/social';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { AuthEmailSignupProfileField } from '@features/login/services/login-helper';
import { CoreUserProfileField } from '@features/user/services/user';
import { makeSingleton } from '@singletons';
@@ -60,7 +60,7 @@ export class AddonUserProfileFieldSocialHandlerService implements CoreUserProfil
return {
type: 'social',
name: name,
- value: CoreTextUtils.cleanTags( formValues[name]),
+ value: CoreText.cleanTags( formValues[name]),
};
}
diff --git a/src/addons/userprofilefield/text/services/handlers/text.ts b/src/addons/userprofilefield/text/services/handlers/text.ts
index 72ff5c7a1..15e94f1d7 100644
--- a/src/addons/userprofilefield/text/services/handlers/text.ts
+++ b/src/addons/userprofilefield/text/services/handlers/text.ts
@@ -16,7 +16,7 @@ import { Injectable, Type } from '@angular/core';
import { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from '@features/user/services/user-profile-field-delegate';
import { AddonUserProfileFieldTextComponent } from '../../component/text';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { AuthEmailSignupProfileField } from '@features/login/services/login-helper';
import { CoreUserProfileField } from '@features/user/services/user';
import { makeSingleton } from '@singletons';
@@ -60,7 +60,7 @@ export class AddonUserProfileFieldTextHandlerService implements CoreUserProfileF
return {
type: 'text',
name: name,
- value: CoreTextUtils.cleanTags( formValues[name]),
+ value: CoreText.cleanTags( formValues[name]),
};
}
diff --git a/src/addons/userprofilefield/textarea/services/handlers/textarea.ts b/src/addons/userprofilefield/textarea/services/handlers/textarea.ts
index e0bb9d578..98a1fb2e7 100644
--- a/src/addons/userprofilefield/textarea/services/handlers/textarea.ts
+++ b/src/addons/userprofilefield/textarea/services/handlers/textarea.ts
@@ -16,7 +16,7 @@ import { Injectable, Type } from '@angular/core';
import { CoreUserProfileFieldHandler, CoreUserProfileFieldHandlerData } from '@features/user/services/user-profile-field-delegate';
import { AddonUserProfileFieldTextareaComponent } from '../../component/textarea';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { AuthEmailSignupProfileField } from '@features/login/services/login-helper';
import { CoreUserProfileField } from '@features/user/services/user';
import { makeSingleton } from '@singletons';
@@ -60,7 +60,7 @@ export class AddonUserProfileFieldTextareaHandlerService implements CoreUserProf
if (formValues[name]) {
let text = formValues[name] || '';
// Add some HTML to the message in case the user edited with textarea.
- text = CoreTextUtils.formatHtmlLines(text);
+ text = CoreText.formatHtmlLines(text);
return {
type: 'textarea',
diff --git a/src/core/classes/base-sync.ts b/src/core/classes/base-sync.ts
index d25a0797b..587151c81 100644
--- a/src/core/classes/base-sync.ts
+++ b/src/core/classes/base-sync.ts
@@ -15,11 +15,12 @@
import { CoreNetwork } from '@services/network';
import { CoreSites } from '@services/sites';
import { CoreSync } from '@services/sync';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreTimeUtils } from '@services/utils/time';
import { Translate } from '@singletons';
import { CoreLogger } from '@singletons/logger';
import { CoreAnyError, CoreError } from '@classes/errors/error';
+import { CoreErrorHelper } from '@services/error-helper';
/**
* Blocked sync error.
@@ -120,7 +121,7 @@ export class CoreSyncBaseProvider {
return Translate.instant('core.warningofflinedatadeleted', {
component: this.componentTranslate,
name: name,
- error: CoreTextUtils.getErrorMessageFromError(error),
+ error: CoreErrorHelper.getErrorMessageFromError(error),
});
}
@@ -195,7 +196,7 @@ export class CoreSyncBaseProvider {
try {
const entry = await CoreSync.getSyncRecord(this.component, id, siteId);
- return CoreTextUtils.parseJSON(entry.warnings, []);
+ return CoreText.parseJSON(entry.warnings, []);
} catch {
return [];
}
diff --git a/src/core/classes/errors/error.ts b/src/core/classes/errors/error.ts
index e046ea3d1..cccd985ba 100644
--- a/src/core/classes/errors/error.ts
+++ b/src/core/classes/errors/error.ts
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import { CoreTextErrorObject } from '@services/utils/text';
+import { CoreErrorObject } from '@services/error-helper';
/**
* Base Error class.
@@ -34,4 +34,4 @@ export class CoreError extends Error {
}
-export type CoreAnyError = string | CoreError | CoreTextErrorObject | null | undefined;
+export type CoreAnyError = string | CoreError | CoreErrorObject | null | undefined;
diff --git a/src/core/classes/sites/authenticated-site.ts b/src/core/classes/sites/authenticated-site.ts
index a40896b9e..f396bbd77 100644
--- a/src/core/classes/sites/authenticated-site.ts
+++ b/src/core/classes/sites/authenticated-site.ts
@@ -22,7 +22,7 @@ import {
CoreWSTypeExpected,
} from '@services/ws';
import { CoreToasts, ToastDuration } from '@services/toasts';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUtils } from '@services/utils/utils';
import { CoreConstants } from '@/core/constants';
import { CoreError } from '@classes/errors/error';
@@ -418,7 +418,7 @@ export class CoreAuthenticatedSite extends CoreUnauthenticatedSite {
splitRequest: preSets.splitRequest,
};
- if (wsPreSets.cleanUnicode && CoreTextUtils.hasUnicodeData(data)) {
+ if (wsPreSets.cleanUnicode && CoreText.hasUnicodeData(data)) {
// Data will be cleaned, notify the user.
CoreToasts.show({
message: 'core.unicodenotsupported',
@@ -676,7 +676,7 @@ export class CoreAuthenticatedSite extends CoreUnauthenticatedSite {
error.message = Translate.instant('core.policy.sitepolicynotagreederror');
throw new CoreSilentError(error);
- } else if (error.errorcode === 'dmlwriteexception' && CoreTextUtils.hasUnicodeData(data)) {
+ } else if (error.errorcode === 'dmlwriteexception' && CoreText.hasUnicodeData(data)) {
if (!this.cleanUnicode) {
// Try again cleaning unicode.
this.cleanUnicode = true;
@@ -961,7 +961,7 @@ export class CoreAuthenticatedSite extends CoreUnauthenticatedSite {
// Request not executed, enqueue again.
this.enqueueRequest(request);
} else if (response.error) {
- const rejectReason = CoreTextUtils.parseJSON(response.exception || '') as Error | undefined;
+ const rejectReason = CoreText.parseJSON(response.exception || '') as Error | undefined;
request.deferred.reject(rejectReason);
CoreErrorLogs.addErrorLog({
method: request.method,
@@ -971,7 +971,7 @@ export class CoreAuthenticatedSite extends CoreUnauthenticatedSite {
data: request.data,
});
} else {
- let responseData = response.data ? CoreTextUtils.parseJSON(response.data) : {};
+ let responseData = response.data ? CoreText.parseJSON(response.data) : {};
// Match the behaviour of CoreWSProvider.call when no response is expected.
const responseExpected = wsPresets.responseExpected === undefined || wsPresets.responseExpected;
if (!responseExpected && (responseData == null || responseData === '')) {
@@ -1097,7 +1097,7 @@ export class CoreAuthenticatedSite extends CoreUnauthenticatedSite {
}
return {
- response: CoreTextUtils.parseJSON(entry.data, {}),
+ response: CoreText.parseJSON(entry.data, {}),
expirationIgnored: forceCache,
expirationTime,
};
diff --git a/src/core/classes/sites/site.ts b/src/core/classes/sites/site.ts
index 5cd6d6d5d..58eb7ad71 100644
--- a/src/core/classes/sites/site.ts
+++ b/src/core/classes/sites/site.ts
@@ -25,7 +25,6 @@ import {
CoreWSUploadFileResult,
} from '@services/ws';
import { CoreDomUtils } from '@services/utils/dom';
-import { CoreTextUtils } from '@services/utils/text';
import { CoreTimeUtils } from '@services/utils/time';
import { CoreUrl } from '@singletons/url';
import { CoreUtils, CoreUtilsOpenInBrowserOptions } from '@services/utils/utils';
@@ -166,7 +165,7 @@ export class CoreSite extends CoreAuthenticatedSite {
*/
setConfig(config: CoreSiteConfig): void {
if (config) {
- config.tool_mobile_disabledfeatures = CoreTextUtils.treatDisabledFeatures(config.tool_mobile_disabledfeatures);
+ config.tool_mobile_disabledfeatures = this.treatDisabledFeatures(config.tool_mobile_disabledfeatures);
}
this.config = config;
diff --git a/src/core/classes/sites/unauthenticated-site.ts b/src/core/classes/sites/unauthenticated-site.ts
index 3d4a5e3fa..8ce6df346 100644
--- a/src/core/classes/sites/unauthenticated-site.ts
+++ b/src/core/classes/sites/unauthenticated-site.ts
@@ -16,7 +16,7 @@ import { CoreConstants } from '@/core/constants';
import { CoreError } from '@classes/errors/error';
import { CoreLoginHelper } from '@features/login/services/login-helper';
import { CoreSitesReadingStrategy } from '@services/sites';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUrl, CoreUrlPartNames } from '@singletons/url';
import { CoreWS, CoreWSAjaxPreSets, CoreWSExternalWarning } from '@services/ws';
import { CorePath } from '@singletons/path';
@@ -30,6 +30,55 @@ export class CoreUnauthenticatedSite {
protected publicConfig?: CoreSitePublicConfigResponse;
+ // List of regular expressions to convert the old nomenclature to new nomenclature for disabled features.
+ protected static readonly DISABLED_FEATURES_COMPAT_REGEXPS: { old: RegExp; new: string }[] = [
+ { old: /\$mmLoginEmailSignup/g, new: 'CoreLoginEmailSignup' },
+ { old: /\$mmSideMenuDelegate/g, new: 'CoreMainMenuDelegate' },
+ { old: /\$mmCoursesDelegate/g, new: 'CoreCourseOptionsDelegate' },
+ { old: /\$mmUserDelegate/g, new: 'CoreUserDelegate' },
+ { old: /\$mmCourseDelegate/g, new: 'CoreCourseModuleDelegate' },
+ { old: /_mmCourses/g, new: '_CoreCourses' },
+ { old: /_mmaFrontpage/g, new: '_CoreSiteHome' },
+ { old: /_mmaGrades/g, new: '_CoreGrades' },
+ { old: /_mmaCompetency/g, new: '_AddonCompetency' },
+ { old: /_mmaNotifications/g, new: '_AddonNotifications' },
+ { old: /_mmaMessages/g, new: '_AddonMessages' },
+ { old: /_mmaCalendar/g, new: '_AddonCalendar' },
+ { old: /_mmaFiles/g, new: '_AddonPrivateFiles' },
+ { old: /_mmaParticipants/g, new: '_CoreUserParticipants' },
+ { old: /_mmaCourseCompletion/g, new: '_AddonCourseCompletion' },
+ { old: /_mmaNotes/g, new: '_AddonNotes' },
+ { old: /_mmaBadges/g, new: '_AddonBadges' },
+ { old: /files_privatefiles/g, new: 'AddonPrivateFilesPrivateFiles' },
+ { old: /files_sitefiles/g, new: 'AddonPrivateFilesSiteFiles' },
+ { old: /files_upload/g, new: 'AddonPrivateFilesUpload' },
+ { old: /_mmaModAssign/g, new: '_AddonModAssign' },
+ { old: /_mmaModBigbluebuttonbn/g, new: '_AddonModBBB' },
+ { old: /_mmaModBook/g, new: '_AddonModBook' },
+ { old: /_mmaModChat/g, new: '_AddonModChat' },
+ { old: /_mmaModChoice/g, new: '_AddonModChoice' },
+ { old: /_mmaModData/g, new: '_AddonModData' },
+ { old: /_mmaModFeedback/g, new: '_AddonModFeedback' },
+ { old: /_mmaModFolder/g, new: '_AddonModFolder' },
+ { old: /_mmaModForum/g, new: '_AddonModForum' },
+ { old: /_mmaModGlossary/g, new: '_AddonModGlossary' },
+ { old: /_mmaModH5pactivity/g, new: '_AddonModH5PActivity' },
+ { old: /_mmaModImscp/g, new: '_AddonModImscp' },
+ { old: /_mmaModLabel/g, new: '_AddonModLabel' },
+ { old: /_mmaModLesson/g, new: '_AddonModLesson' },
+ { old: /_mmaModLti/g, new: '_AddonModLti' },
+ { old: /_mmaModPage/g, new: '_AddonModPage' },
+ { old: /_mmaModQuiz/g, new: '_AddonModQuiz' },
+ { old: /_mmaModResource/g, new: '_AddonModResource' },
+ { old: /_mmaModScorm/g, new: '_AddonModScorm' },
+ { old: /_mmaModSurvey/g, new: '_AddonModSurvey' },
+ { old: /_mmaModUrl/g, new: '_AddonModUrl' },
+ { old: /_mmaModWiki/g, new: '_AddonModWiki' },
+ { old: /_mmaModWorkshop/g, new: '_AddonModWorkshop' },
+ { old: /remoteAddOn_/g, new: 'sitePlugin_' },
+ { old: /AddonNotes:addNote/g, new: 'AddonNotes:notes' },
+ ];
+
/**
* Create a site.
*
@@ -160,10 +209,10 @@ export class CoreUnauthenticatedSite {
return false;
}
- const siteUrl = CoreTextUtils.addEndingSlash(
+ const siteUrl = CoreText.addEndingSlash(
CoreUrl.removeUrlParts(this.siteUrl, [CoreUrlPartNames.Protocol, CoreUrlPartNames.WWWInDomain]),
);
- url = CoreTextUtils.addEndingSlash(CoreUrl.removeUrlParts(url, [CoreUrlPartNames.Protocol, CoreUrlPartNames.WWWInDomain]));
+ url = CoreText.addEndingSlash(CoreUrl.removeUrlParts(url, [CoreUrlPartNames.Protocol, CoreUrlPartNames.WWWInDomain]));
return url.indexOf(siteUrl) == 0;
}
@@ -207,7 +256,7 @@ export class CoreUnauthenticatedSite {
*/
setPublicConfig(publicConfig: CoreSitePublicConfigResponse): void {
publicConfig.tool_mobile_disabledfeatures =
- CoreTextUtils.treatDisabledFeatures(publicConfig.tool_mobile_disabledfeatures ?? '');
+ this.treatDisabledFeatures(publicConfig.tool_mobile_disabledfeatures ?? '');
this.publicConfig = publicConfig;
}
@@ -330,7 +379,7 @@ export class CoreUnauthenticatedSite {
return false;
}
- const regEx = new RegExp('(,|^)' + CoreTextUtils.escapeForRegex(name) + '(,|$)', 'g');
+ const regEx = new RegExp('(,|^)' + CoreText.escapeForRegex(name) + '(,|$)', 'g');
return !!disabledFeatures.match(regEx);
}
@@ -344,6 +393,26 @@ export class CoreUnauthenticatedSite {
return this.publicConfig?.tool_mobile_disabledfeatures;
}
+ /**
+ * Treat the list of disabled features, replacing old nomenclature with the new one.
+ *
+ * @param features List of disabled features.
+ * @returns Treated list.
+ */
+ protected treatDisabledFeatures(features: string): string {
+ if (!features) {
+ return '';
+ }
+
+ for (let i = 0; i < CoreUnauthenticatedSite.DISABLED_FEATURES_COMPAT_REGEXPS.length; i++) {
+ const entry = CoreUnauthenticatedSite.DISABLED_FEATURES_COMPAT_REGEXPS[i];
+
+ features = features.replace(entry.old, entry.new);
+ }
+
+ return features;
+ }
+
}
/**
diff --git a/src/core/components/attachments/attachments.ts b/src/core/components/attachments/attachments.ts
index c844e8206..54895fa55 100644
--- a/src/core/components/attachments/attachments.ts
+++ b/src/core/components/attachments/attachments.ts
@@ -17,7 +17,7 @@ import { FileEntry } from '@awesome-cordova-plugins/file/ngx';
import { CoreFileUploader, CoreFileUploaderTypeList } from '@features/fileuploader/services/fileuploader';
import { CoreSites } from '@services/sites';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { Translate } from '@singletons';
import { CoreNetwork } from '@services/network';
import { CoreDomUtils } from '@services/utils/dom';
@@ -74,7 +74,7 @@ export class CoreAttachmentsComponent implements OnInit {
if (this.maxSize === 0) {
await this.getMaxSizeOfArea();
} else if (this.maxSize > 0) {
- this.maxSizeReadable = CoreTextUtils.bytesToSize(this.maxSize, 2);
+ this.maxSizeReadable = CoreText.bytesToSize(this.maxSize, 2);
} else if (this.maxSize === -1) {
this.maxSizeReadable = Translate.instant('core.unlimited');
} else {
@@ -110,7 +110,7 @@ export class CoreAttachmentsComponent implements OnInit {
if (course?.maxbytes) {
this.maxSize = course.maxbytes;
- this.maxSizeReadable = CoreTextUtils.bytesToSize(this.maxSize, 2);
+ this.maxSizeReadable = CoreText.bytesToSize(this.maxSize, 2);
return;
}
@@ -122,7 +122,7 @@ export class CoreAttachmentsComponent implements OnInit {
if (siteInfo?.usermaxuploadfilesize) {
this.maxSize = siteInfo.usermaxuploadfilesize;
- this.maxSizeReadable = CoreTextUtils.bytesToSize(this.maxSize, 2);
+ this.maxSizeReadable = CoreText.bytesToSize(this.maxSize, 2);
} else {
this.maxSizeReadable = Translate.instant('core.unknown');
}
diff --git a/src/core/components/file/file.ts b/src/core/components/file/file.ts
index e42544666..574a138e2 100644
--- a/src/core/components/file/file.ts
+++ b/src/core/components/file/file.ts
@@ -22,7 +22,7 @@ import { CoreDomUtils } from '@services/utils/dom';
import { CoreMimetypeUtils } from '@services/utils/mimetype';
import { CoreUrl } from '@singletons/url';
import { CoreUtils, CoreUtilsOpenFileOptions, OpenFileAction } from '@services/utils/utils';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { DownloadStatus } from '@/core/constants';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
import { CoreWSFile } from '@services/ws';
@@ -90,7 +90,7 @@ export class CoreFileComponent implements OnInit, OnDestroy {
this.openButtonLabel = this.defaultIsOpenWithPicker ? 'core.openfile' : 'core.openwith';
if (this.showSize && this.fileSize && this.fileSize >= 0) {
- this.fileSizeReadable = CoreTextUtils.bytesToSize(this.fileSize, 2);
+ this.fileSizeReadable = CoreText.bytesToSize(this.fileSize, 2);
}
this.showTime = this.showTime && this.timemodified > 0;
diff --git a/src/core/components/local-file/local-file.ts b/src/core/components/local-file/local-file.ts
index 2c9c70e97..89cc829fe 100644
--- a/src/core/components/local-file/local-file.ts
+++ b/src/core/components/local-file/local-file.ts
@@ -21,7 +21,7 @@ import { CoreFileHelper } from '@services/file-helper';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreMimetypeUtils } from '@services/utils/mimetype';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreTimeUtils } from '@services/utils/time';
import { CoreUtils, CoreUtilsOpenFileOptions, OpenFileAction } from '@services/utils/utils';
import { CoreForms } from '@singletons/form';
@@ -78,7 +78,7 @@ export class CoreLocalFileComponent implements OnInit {
// Get the size and timemodified.
const metadata = await CoreFile.getMetadata(this.file);
if (metadata.size >= 0) {
- this.size = CoreTextUtils.bytesToSize(metadata.size, 2);
+ this.size = CoreText.bytesToSize(metadata.size, 2);
}
this.timemodified = CoreTimeUtils.userDate(metadata.modificationTime.getTime(), 'core.strftimedatetimeshort');
diff --git a/src/core/components/mark-required/mark-required.ts b/src/core/components/mark-required/mark-required.ts
index e6ed33d3e..da40c2940 100644
--- a/src/core/components/mark-required/mark-required.ts
+++ b/src/core/components/mark-required/mark-required.ts
@@ -15,7 +15,7 @@
import { toBoolean } from '@/core/transforms/boolean';
import { Component, Input, AfterViewInit, ElementRef } from '@angular/core';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { Translate } from '@singletons';
/**
@@ -53,7 +53,7 @@ export class CoreMarkRequiredComponent implements AfterViewInit {
if (this.coreMarkRequired) {
// Add the "required" to the aria-label.
const ariaLabel = this.hostElement.getAttribute('aria-label') ||
- CoreTextUtils.cleanTags(this.hostElement.innerHTML, { singleLine: true });
+ CoreText.cleanTags(this.hostElement.innerHTML, { singleLine: true });
if (ariaLabel) {
this.hostElement.setAttribute('aria-label', ariaLabel + '. ' + this.requiredLabel);
}
diff --git a/src/core/components/message/message.ts b/src/core/components/message/message.ts
index b8982f723..99b4d9b2e 100644
--- a/src/core/components/message/message.ts
+++ b/src/core/components/message/message.ts
@@ -17,7 +17,6 @@ import { Component, EventEmitter, HostBinding, Input, OnInit, Output } from '@an
import { CoreAnimations } from '@components/animations';
import { CoreSites } from '@services/sites';
import { CoreText } from '@singletons/text';
-import { CoreTextUtils } from '@services/utils/text';
import { CoreUserWithAvatar } from '@components/user-avatar/user-avatar';
import { toBoolean } from '@/core/transforms/boolean';
@@ -103,7 +102,7 @@ export class CoreMessageComponent implements OnInit {
* Copy message to clipboard.
*/
copyMessage(): void {
- CoreText.copyToClipboard(CoreTextUtils.decodeHTMLEntities(this.text));
+ CoreText.copyToClipboard(CoreText.decodeHTMLEntities(this.text));
}
}
diff --git a/src/core/components/mod-icon/mod-icon.ts b/src/core/components/mod-icon/mod-icon.ts
index ab8727dfe..3c10b5eb2 100644
--- a/src/core/components/mod-icon/mod-icon.ts
+++ b/src/core/components/mod-icon/mod-icon.ts
@@ -28,7 +28,7 @@ import {
import { CoreCourse } from '@features/course/services/course';
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
import { CoreSites } from '@services/sites';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUrl } from '@singletons/url';
const assetsPath = 'assets/img/';
@@ -150,7 +150,7 @@ export class CoreModIconComponent implements OnInit, OnChanges {
return;
}
- this.iconUrl.update(value => CoreTextUtils.decodeHTMLEntities(value));
+ this.iconUrl.update(value => CoreText.decodeHTMLEntities(value));
if (this.brandedClass !== undefined) {
return;
}
diff --git a/src/core/components/send-message-form/send-message-form.ts b/src/core/components/send-message-form/send-message-form.ts
index 8f0a4be5b..63e0cde37 100644
--- a/src/core/components/send-message-form/send-message-form.ts
+++ b/src/core/components/send-message-form/send-message-form.ts
@@ -16,7 +16,7 @@ import { Component, Input, Output, EventEmitter, ViewChild, ElementRef } from '@
import { CoreConfig } from '@services/config';
import { CoreEvents } from '@singletons/events';
import { CoreSites } from '@services/sites';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreConstants } from '@/core/constants';
import { CoreForms } from '@singletons/form';
import { CorePlatform } from '@services/platform';
@@ -88,7 +88,7 @@ export class CoreSendMessageFormComponent {
CoreForms.triggerFormSubmittedEvent(this.formElement, false, CoreSites.getCurrentSiteId());
- value = CoreTextUtils.replaceNewLines(value, ' ');
+ value = CoreText.replaceNewLines(value, ' ');
this.onSubmit.emit(value);
}
diff --git a/src/core/components/show-password/show-password.ts b/src/core/components/show-password/show-password.ts
index 88bdcc81f..12ca321ca 100644
--- a/src/core/components/show-password/show-password.ts
+++ b/src/core/components/show-password/show-password.ts
@@ -14,7 +14,7 @@
import { Component, AfterViewInit, Input, ContentChild, ViewEncapsulation } from '@angular/core';
import { IonInput } from '@ionic/angular';
-import { CoreDomUtils } from '@services/utils/dom';
+import { convertTextToHTMLElement } from '@/core/utils/create-html-element';
import { CoreUtils } from '@services/utils/utils';
import { CoreLogger } from '@singletons/logger';
@@ -84,7 +84,7 @@ export class CoreShowPasswordComponent implements AfterViewInit {
return;
}
- const toggle = CoreDomUtils.convertToElement(' ');
+ const toggle = convertTextToHTMLElement(' ');
input.parentElement?.appendChild(toggle.children[0]);
}
diff --git a/src/core/core.module.ts b/src/core/core.module.ts
index 234a8f7c3..554e088fc 100644
--- a/src/core/core.module.ts
+++ b/src/core/core.module.ts
@@ -34,6 +34,7 @@ export async function getCoreServices(): Promise[]> {
const { CoreCustomURLSchemesProvider } = await import('@services/urlschemes');
const { CoreDbProvider } = await import('@services/db');
const { CoreDomUtilsProvider } = await import('@services/utils/dom');
+ const { CoreErrorHelperService } = await import('@services/error-helper');
const { CoreToastsService } = await import('@services/toasts');
const { CoreFileHelperProvider } = await import('@services/file-helper');
const { CoreFilepoolProvider } = await import('@services/filepool');
@@ -51,6 +52,7 @@ export async function getCoreServices(): Promise[]> {
const { CoreScreenService } = await import('@services/screen');
const { CoreSitesProvider } = await import('@services/sites');
const { CoreSyncProvider } = await import('@services/sync');
+ // eslint-disable-next-line deprecation/deprecation
const { CoreTextUtilsProvider } = await import('@services/utils/text');
const { CoreTimeUtilsProvider } = await import('@services/utils/time');
const { CoreUpdateManagerProvider } = await import('@services/update-manager');
@@ -68,6 +70,7 @@ export async function getCoreServices(): Promise[]> {
CoreCustomURLSchemesProvider,
CoreDbProvider,
CoreDomUtilsProvider,
+ CoreErrorHelperService,
CoreFileHelperProvider,
CoreFilepoolProvider,
CoreFileProvider,
diff --git a/src/core/directives/external-content.ts b/src/core/directives/external-content.ts
index 94c02458b..4ed81c8a4 100644
--- a/src/core/directives/external-content.ts
+++ b/src/core/directives/external-content.ts
@@ -39,7 +39,7 @@ import { AsyncDirective } from '@classes/async-directive';
import { CoreDirectivesRegistry } from '@singletons/directives-registry';
import { CorePromisedValue } from '@classes/promised-value';
import { CorePlatform } from '@services/platform';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreArray } from '@singletons/array';
import { CoreMimetypeUtils } from '@services/utils/mimetype';
import { FileEntry } from '@awesome-cordova-plugins/file/ngx';
@@ -301,7 +301,7 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges, O
const finalUrl = await CoreFilepool.getSrcByUrl(siteId, url, this.component, this.componentId, 0, true, true);
this.logger.debug('Using URL ' + finalUrl + ' for ' + url + ' in inline styles');
- inlineStyles = inlineStyles.replace(new RegExp(CoreTextUtils.escapeForRegex(url), 'gi'), finalUrl);
+ inlineStyles = inlineStyles.replace(new RegExp(CoreText.escapeForRegex(url), 'gi'), finalUrl);
});
try {
diff --git a/src/core/directives/format-text.ts b/src/core/directives/format-text.ts
index 9c4f4691b..f762b426e 100644
--- a/src/core/directives/format-text.ts
+++ b/src/core/directives/format-text.ts
@@ -31,7 +31,7 @@ import {
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreIframeUtils, CoreIframeUtilsProvider } from '@services/utils/iframe';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUtils } from '@services/utils/utils';
import { CoreSite } from '@classes/sites/site';
import { NgZone, Translate } from '@singletons';
@@ -291,7 +291,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec
button.innerHTML = ` `;
button.addEventListener('click', (e: Event) => {
- const imgSrc = CoreTextUtils.escapeHTML(img.getAttribute('data-original-src') || img.getAttribute('src'));
+ const imgSrc = CoreText.escapeHTML(img.getAttribute('data-original-src') || img.getAttribute('src'));
e.preventDefault();
e.stopPropagation();
diff --git a/src/core/directives/link.ts b/src/core/directives/link.ts
index ec463b819..3bb1f30d3 100644
--- a/src/core/directives/link.ts
+++ b/src/core/directives/link.ts
@@ -20,7 +20,6 @@ import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreUrl } from '@singletons/url';
import { CoreUtils } from '@services/utils/utils';
-import { CoreTextUtils } from '@services/utils/text';
import { CoreConstants } from '@/core/constants';
import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper';
import { CoreCustomURLSchemes } from '@services/urlschemes';
@@ -96,7 +95,7 @@ export class CoreLinkDirective implements OnInit {
const openIn = this.element.getAttribute('data-open-in');
if (this.capture) {
- const treated = await CoreContentLinksHelper.handleLink(CoreTextUtils.decodeURI(href), undefined, true, true);
+ const treated = await CoreContentLinksHelper.handleLink(CoreUrl.decodeURI(href), undefined, true, true);
if (!treated) {
this.navigate(href, openIn);
diff --git a/src/core/features/block/classes/base-block-component.ts b/src/core/features/block/classes/base-block-component.ts
index cfb6402c0..86f74c90b 100644
--- a/src/core/features/block/classes/base-block-component.ts
+++ b/src/core/features/block/classes/base-block-component.ts
@@ -16,7 +16,7 @@ import { OnInit, Input, Component, Optional, Inject, OnChanges, SimpleChanges }
import { CoreLogger } from '@singletons/logger';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreCourseBlock } from '../../course/services/course';
import { Params } from '@angular/router';
import { ContextLevel } from '@/core/constants';
@@ -75,7 +75,7 @@ export abstract class CoreBlockBaseComponent implements OnInit, OnChanges, ICore
}
this.block.configs.forEach((config) => {
- config.value = CoreTextUtils.parseJSON(config.value);
+ config.value = CoreText.parseJSON(config.value);
});
this.block.configsRecord = CoreUtils.arrayToObject(this.block.configs, 'name');
diff --git a/src/core/features/block/components/side-blocks/side-blocks.ts b/src/core/features/block/components/side-blocks/side-blocks.ts
index 23eee8649..92b20b1b8 100644
--- a/src/core/features/block/components/side-blocks/side-blocks.ts
+++ b/src/core/features/block/components/side-blocks/side-blocks.ts
@@ -20,7 +20,6 @@ import { CoreBlockHelper } from '../../services/block-helper';
import { CoreBlockComponent } from '../block/block';
import { CoreUtils } from '@services/utils/utils';
import { CoreCoursesDashboard } from '@features/courses/services/dashboard';
-import { CoreTextUtils } from '@services/utils/text';
import { CoreDom } from '@singletons/dom';
import { ContextLevel } from '@/core/constants';
import { CoreWait } from '@singletons/wait';
@@ -110,7 +109,7 @@ export class CoreBlockSideBlocksComponent implements OnInit {
}
this.blocks = this.blocks.filter(block =>
- block.name !== 'html' || (block.contents && !CoreTextUtils.htmlIsBlank(block.contents.content)));
+ block.name !== 'html' || (block.contents && !CoreDom.htmlIsBlank(block.contents.content)));
}
/**
diff --git a/src/core/features/comments/pages/viewer/viewer.ts b/src/core/features/comments/pages/viewer/viewer.ts
index 01a1322b8..5879bbb9d 100644
--- a/src/core/features/comments/pages/viewer/viewer.ts
+++ b/src/core/features/comments/pages/viewer/viewer.ts
@@ -33,7 +33,7 @@ import { NgZone, Translate } from '@singletons';
import { CoreUtils } from '@services/utils/utils';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreUser } from '@features/user/services/user';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreError } from '@classes/errors/error';
import { CoreCommentsOffline } from '@features/comments/services/comments-offline';
import { CoreCommentsDBRecord } from '@features/comments/services/database/comments';
@@ -274,7 +274,7 @@ export class CoreCommentsViewerPage implements OnInit, OnDestroy {
* @param warnings the warnings
*/
private showSyncWarnings(warnings: string[]): void {
- const message = CoreTextUtils.buildMessage(warnings);
+ const message = CoreText.buildMessage(warnings);
if (message) {
CoreDomUtils.showAlert(undefined, message);
}
diff --git a/src/core/features/course/classes/main-resource-component.ts b/src/core/features/course/classes/main-resource-component.ts
index 6434c503b..6e67b5d1e 100644
--- a/src/core/features/course/classes/main-resource-component.ts
+++ b/src/core/features/course/classes/main-resource-component.ts
@@ -19,7 +19,6 @@ import { CoreNetwork } from '@services/network';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
-import { CoreTextErrorObject, CoreTextUtils } from '@services/utils/text';
import { CoreUtils } from '@services/utils/utils';
import { Translate } from '@singletons';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
@@ -35,13 +34,14 @@ import { CoreUrl } from '@singletons/url';
import { CoreTime } from '@singletons/time';
import { CoreText } from '@singletons/text';
import { CoreModals } from '@services/modals';
+import { CoreErrorHelper, CoreErrorObject } from '@services/error-helper';
/**
* Result of a resource download.
*/
export type CoreCourseResourceDownloadResult = {
failed?: boolean; // Whether the download has failed.
- error?: string | CoreTextErrorObject; // The error in case it failed.
+ error?: string | CoreErrorObject; // The error in case it failed.
};
/**
@@ -220,7 +220,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
* @returns Whether the error is a "module not found" error.
*/
protected isNotFoundError(error: CoreAnyError): boolean {
- return CoreTextUtils.getErrorMessageFromError(error) === Translate.instant('core.course.modulenotfound');
+ return CoreErrorHelper.getErrorMessageFromError(error) === Translate.instant('core.course.modulenotfound');
}
/**
@@ -255,14 +255,14 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
* @param multiLine Whether to put each message in a different paragraph or in a single line.
* @returns Error text message.
*/
- protected getErrorDownloadingSomeFilesMessage(error: string | CoreTextErrorObject, multiLine?: boolean): string {
+ protected getErrorDownloadingSomeFilesMessage(error: string | CoreErrorObject, multiLine?: boolean): string {
if (multiLine) {
- return CoreTextUtils.buildSeveralParagraphsMessage([
+ return CoreErrorHelper.buildSeveralParagraphsMessage([
Translate.instant('core.errordownloadingsomefiles'),
error,
]);
} else {
- error = CoreTextUtils.getErrorMessageFromError(error) || '';
+ error = CoreErrorHelper.getErrorMessageFromError(error) || '';
return Translate.instant('core.errordownloadingsomefiles') + (error ? ' ' + error : '');
}
@@ -273,7 +273,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
*
* @param error The specific error.
*/
- protected showErrorDownloadingSomeFiles(error: string | CoreTextErrorObject): void {
+ protected showErrorDownloadingSomeFiles(error: string | CoreErrorObject): void {
CoreDomUtils.showErrorModal(this.getErrorDownloadingSomeFilesMessage(error, true));
}
diff --git a/src/core/features/course/services/course-helper.ts b/src/core/features/course/services/course-helper.ts
index df9b29f42..11ae811e7 100644
--- a/src/core/features/course/services/course-helper.ts
+++ b/src/core/features/course/services/course-helper.ts
@@ -60,7 +60,7 @@ import { CoreNetwork } from '@services/network';
import { CoreSite } from '@classes/sites/site';
import { CoreFile } from '@services/file';
import { CoreUrl } from '@singletons/url';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreTimeUtils } from '@services/utils/time';
import { CoreFilterHelper } from '@features/filter/services/filter-helper';
import { CoreNetworkError } from '@classes/errors/network-error';
@@ -1342,7 +1342,7 @@ export class CoreCourseHelperProvider {
// Treat stored size.
const size = results[0];
- const sizeReadable = CoreTextUtils.bytesToSize(results[0], 2);
+ const sizeReadable = CoreText.bytesToSize(results[0], 2);
// Treat module status.
const status = results[1];
diff --git a/src/core/features/course/services/handlers/course-tag-area.ts b/src/core/features/course/services/handlers/course-tag-area.ts
index 9c52f9b9a..e6b1de384 100644
--- a/src/core/features/course/services/handlers/course-tag-area.ts
+++ b/src/core/features/course/services/handlers/course-tag-area.ts
@@ -14,7 +14,7 @@
import { Injectable, Type } from '@angular/core';
-import { CoreDomUtils } from '@services/utils/dom';
+import { convertTextToHTMLElement } from '@/core/utils/create-html-element';
import { CoreTagAreaHandler } from '@features/tag/services/tag-area-delegate';
import { CoreCourseTagAreaComponent } from '../../components/tag-area/tag-area';
import { makeSingleton } from '@singletons';
@@ -45,7 +45,7 @@ export class CoreCourseTagAreaHandlerService implements CoreTagAreaHandler {
*/
parseContent(content: string): CoreCouseTagItems[] {
const items: CoreCouseTagItems[] = [];
- const element = CoreDomUtils.convertToElement(content);
+ const element = convertTextToHTMLElement(content);
Array.from(element.querySelectorAll('div.coursebox')).forEach((coursebox) => {
const courseId = parseInt(coursebox.getAttribute('data-courseid') || '', 10);
diff --git a/src/core/features/course/services/log-helper.ts b/src/core/features/course/services/log-helper.ts
index 5a41c4e97..41deadb21 100644
--- a/src/core/features/course/services/log-helper.ts
+++ b/src/core/features/course/services/log-helper.ts
@@ -16,7 +16,7 @@ import { Injectable } from '@angular/core';
import { CoreNetwork } from '@services/network';
import { CoreSites } from '@services/sites';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreTimeUtils } from '@services/utils/time';
import { CoreUtils } from '@services/utils/utils';
import { makeSingleton } from '@singletons';
@@ -338,7 +338,7 @@ export class CoreCourseLogHelperProvider {
*/
protected async syncLogs(logs: CoreCourseActivityLogDBRecord[], siteId: string): Promise {
await Promise.all(logs.map(async (log) => {
- const data = CoreTextUtils.parseJSON>(log.data || '{}', {});
+ const data = CoreText.parseJSON>(log.data || '{}', {});
try {
await this.logOnline(log.ws, data, siteId);
diff --git a/src/core/features/course/services/sync.ts b/src/core/features/course/services/sync.ts
index c8ff8fe15..e38e3ad56 100644
--- a/src/core/features/course/services/sync.ts
+++ b/src/core/features/course/services/sync.ts
@@ -19,7 +19,7 @@ import { CoreSyncBaseProvider } from '@classes/base-sync';
import { CoreSites } from '@services/sites';
import { CoreNetwork } from '@services/network';
import { CoreUtils } from '@services/utils/utils';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreErrorHelper } from '@services/error-helper';
import { CoreCourseOffline } from './course-offline';
import { CoreCourse } from './course';
import { CoreCourseLogHelper } from './log-helper';
@@ -227,7 +227,7 @@ export class CoreCourseSyncProvider extends CoreSyncBaseProvider= 300) {
- // Request failed. Try to get the respnse.
+ // Request failed. Try to get the response.
const body = xhr.response ? await this.blobToText(xhr.response) : '';
const error = new FileTransferErrorMock(-1, source, target, xhr.status, body, '');
diff --git a/src/core/features/fileuploader/services/fileuploader-helper.ts b/src/core/features/fileuploader/services/fileuploader-helper.ts
index 19a2e8008..c79a55413 100644
--- a/src/core/features/fileuploader/services/fileuploader-helper.ts
+++ b/src/core/features/fileuploader/services/fileuploader-helper.ts
@@ -23,7 +23,7 @@ import { CoreNetwork } from '@services/network';
import { CoreFile, CoreFileProvider, CoreFileProgressEvent } from '@services/file';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreMimetypeUtils } from '@services/utils/mimetype';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUtils } from '@services/utils/utils';
import { makeSingleton, Translate, Camera, ActionSheetController } from '@singletons';
import { CoreLogger } from '@singletons/logger';
@@ -140,7 +140,7 @@ export class CoreFileUploaderHelperProvider {
if (size < 0) {
return CoreDomUtils.showConfirm(Translate.instant('core.fileuploader.confirmuploadunknownsize'));
} else if (size >= wifiThreshold || (CoreNetwork.isNetworkAccessLimited() && size >= limitedThreshold)) {
- const readableSize = CoreTextUtils.bytesToSize(size, 2);
+ const readableSize = CoreText.bytesToSize(size, 2);
return CoreDomUtils.showConfirm(
Translate.instant('core.fileuploader.confirmuploadfile', { size: readableSize }),
@@ -247,7 +247,7 @@ export class CoreFileUploaderHelperProvider {
return new CoreError(Translate.instant('core.fileuploader.maxbytesfile', {
$a: {
file: fileName,
- size: CoreTextUtils.bytesToSize(maxSize, 2),
+ size: CoreText.bytesToSize(maxSize, 2),
},
}));
}
diff --git a/src/core/features/filter/services/filter.ts b/src/core/features/filter/services/filter.ts
index c64e70c7f..e85e46edd 100644
--- a/src/core/features/filter/services/filter.ts
+++ b/src/core/features/filter/services/filter.ts
@@ -18,7 +18,7 @@ import { CoreNetwork } from '@services/network';
import { CoreSites, CoreSitesCommonWSOptions, CoreSitesReadingStrategy } from '@services/sites';
import { CoreSite } from '@classes/sites/site';
import { CoreWSExternalWarning } from '@services/ws';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreFilterDelegate } from './filter-delegate';
import { makeSingleton } from '@singletons';
import { CoreEvents, CoreEventSiteData } from '@singletons/events';
@@ -253,15 +253,15 @@ export class CoreFilterProvider {
}
if (options.clean) {
- text = CoreTextUtils.cleanTags(text, { singleLine: options.singleLine });
+ text = CoreText.cleanTags(text, { singleLine: options.singleLine });
}
if (options.shortenLength && options.shortenLength > 0) {
- text = CoreTextUtils.shortenText(text, options.shortenLength);
+ text = CoreText.shortenText(text, options.shortenLength);
}
if (options.highlight) {
- text = CoreTextUtils.highlightText(text, options.highlight);
+ text = CoreText.highlightText(text, options.highlight);
}
return text;
diff --git a/src/core/features/grades/services/grades-helper.ts b/src/core/features/grades/services/grades-helper.ts
index 1c05c1571..e7a6b6632 100644
--- a/src/core/features/grades/services/grades-helper.ts
+++ b/src/core/features/grades/services/grades-helper.ts
@@ -33,7 +33,7 @@ import {
CoreGradesTableLeaderColumn,
CoreGradesTableRow,
} from '@features/grades/services/grades';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUrl } from '@singletons/url';
import { CoreMenuItem, CoreUtils } from '@services/utils/utils';
import { CoreDomUtils } from '@services/utils/dom';
@@ -44,6 +44,7 @@ import { CoreCourseHelper } from '@features/course/services/course-helper';
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
import { CoreCourseAccess } from '@features/course/services/course-options-delegate';
import { CoreLoadings } from '@services/loadings';
+import { convertTextToHTMLElement } from '@/core/utils/create-html-element';
export const GRADES_PAGE_NAME = 'grades';
export const GRADES_PARTICIPANTS_PAGE_NAME = 'participant-grades';
@@ -111,7 +112,7 @@ export class CoreGradesHelperProvider {
}
content = content.replace(/<\/span>/gi, '\n');
- content = CoreTextUtils.cleanTags(content, { trim: true });
+ content = CoreText.cleanTags(content, { trim: true });
name = 'gradeitem';
} else if (name === 'grade') {
// Add the pass/fail class if present.
@@ -119,7 +120,7 @@ export class CoreGradesHelperProvider {
(column.class.includes('gradefail') ? 'text-danger' : '');
if (content.includes('action-menu')) {
- content = CoreTextUtils.processHTML(content, (element) => {
+ content = CoreText.processHTML(content, (element) => {
element.querySelector('.action-menu')?.parentElement?.remove();
});
}
@@ -127,14 +128,14 @@ export class CoreGradesHelperProvider {
if (content.includes('fa-check')) {
row.gradeIcon = 'fas-check';
row.gradeIconAlt = Translate.instant('core.grades.pass');
- content = CoreTextUtils.cleanTags(content);
+ content = CoreText.cleanTags(content);
} else if (content.includes('fa-times') || content.includes('fa-xmark')) {
row.gradeIcon = 'fas-xmark';
row.gradeIconAlt = Translate.instant('core.grades.fail');
- content = CoreTextUtils.cleanTags(content);
+ content = CoreText.cleanTags(content);
}
} else {
- content = CoreTextUtils.replaceNewLines(content, ' ');
+ content = CoreText.replaceNewLines(content, ' ');
}
if (row.itemtype !== 'category') {
@@ -573,7 +574,7 @@ export class CoreGradesHelperProvider {
const modname = module?.[1];
if (modname !== undefined) {
- const modicon = CoreDomUtils.convertToElement(text).querySelector('img')?.getAttribute('src') ?? undefined;
+ const modicon = convertTextToHTMLElement(text).querySelector('img')?.getAttribute('src') ?? undefined;
row.itemtype = 'mod';
row.itemmodule = modname;
diff --git a/src/core/features/h5p/classes/content-validator.ts b/src/core/features/h5p/classes/content-validator.ts
index df57b88d1..e7b923a94 100644
--- a/src/core/features/h5p/classes/content-validator.ts
+++ b/src/core/features/h5p/classes/content-validator.ts
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUtils } from '@services/utils/utils';
import { CoreH5P } from '@features/h5p/services/h5p';
import { Translate } from '@singletons';
@@ -165,7 +165,7 @@ export class CoreH5PContentValidator {
text = this.filterXss(text, tags, stylePatterns);
} else {
// Filter text to plain text.
- text = CoreTextUtils.escapeHTML(text, false);
+ text = CoreText.escapeHTML(text, false);
}
// Check if string is within allowed length.
@@ -271,7 +271,7 @@ export class CoreH5PContentValidator {
if (strict && !optional && !options[value]) {
delete select[key];
} else {
- select[key] = CoreTextUtils.escapeHTML(value, false);
+ select[key] = CoreText.escapeHTML(value, false);
}
}
} else {
@@ -283,7 +283,7 @@ export class CoreH5PContentValidator {
if (strict && !optional && !options[select]) {
select = ( semantics.options![0]).value || '';
}
- select = CoreTextUtils.escapeHTML(select, false);
+ select = CoreText.escapeHTML(select, false);
}
return select;
@@ -363,9 +363,9 @@ export class CoreH5PContentValidator {
}
// Make sure path and mime does not have any special chars
- file.path = CoreTextUtils.escapeHTML(file.path, false);
+ file.path = CoreText.escapeHTML(file.path, false);
if (file.mime) {
- file.mime = CoreTextUtils.escapeHTML(file.mime, false);
+ file.mime = CoreText.escapeHTML(file.mime, false);
}
// Remove attributes that should not exist, they may contain JSON escape code.
@@ -386,7 +386,7 @@ export class CoreH5PContentValidator {
}
if (file.codecs) {
- file.codecs = CoreTextUtils.escapeHTML(file.codecs, false);
+ file.codecs = CoreText.escapeHTML(file.codecs, false);
}
if (typeof file.bitrate == 'string') {
@@ -399,7 +399,7 @@ export class CoreH5PContentValidator {
} else {
this.filterParams(file.quality, ['level', 'label']);
file.quality.level = Number(file.quality.level);
- file.quality.label = CoreTextUtils.escapeHTML(file.quality.label, false);
+ file.quality.label = CoreText.escapeHTML(file.quality.label, false);
}
}
@@ -845,10 +845,10 @@ export class CoreH5PContentValidator {
filterXssBadProtocol(str: string, decode: boolean = true): string {
// Get the plain text representation of the attribute value (i.e. its meaning).
if (decode) {
- str = CoreTextUtils.decodeHTMLEntities(str);
+ str = CoreText.decodeHTMLEntities(str);
}
- return CoreTextUtils.escapeHTML(this.stripDangerousProtocols(str), false);
+ return CoreText.escapeHTML(this.stripDangerousProtocols(str), false);
}
/**
diff --git a/src/core/features/h5p/classes/core.ts b/src/core/features/h5p/classes/core.ts
index 45132b185..bae63e9fc 100644
--- a/src/core/features/h5p/classes/core.ts
+++ b/src/core/features/h5p/classes/core.ts
@@ -15,7 +15,7 @@
import { Md5 } from 'ts-md5/dist/md5';
import { CoreSites } from '@services/sites';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUtils } from '@services/utils/utils';
import { CoreH5P } from '@features/h5p/services/h5p';
import { CoreH5PFileStorage } from './file-storage';
@@ -270,7 +270,7 @@ export class CoreH5PCore {
const params = {
library: CoreH5PCore.libraryToString(content.library),
- params: CoreTextUtils.parseJSON(content.params, false),
+ params: CoreText.parseJSON(content.params, false),
};
if (!params.params) {
diff --git a/src/core/features/h5p/classes/file-storage.ts b/src/core/features/h5p/classes/file-storage.ts
index 1be91e3c1..9b949b889 100644
--- a/src/core/features/h5p/classes/file-storage.ts
+++ b/src/core/features/h5p/classes/file-storage.ts
@@ -16,7 +16,7 @@ import { CoreFile } from '@services/file';
import { CoreFilepool } from '@services/filepool';
import { CoreSites } from '@services/sites';
import { CoreMimetypeUtils } from '@services/utils/mimetype';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUtils } from '@services/utils/utils';
import { CorePath } from '@singletons/path';
import {
@@ -118,7 +118,7 @@ export class CoreH5PFileStorage {
const assetPathFolder = CoreFile.getFileAndDirectoryFromPath(assetPath).directory;
fileContent = fileContent.replace(
- new RegExp(CoreTextUtils.escapeForRegex(match), 'g'),
+ new RegExp(CoreText.escapeForRegex(match), 'g'),
'url("' + CorePath.changeRelativePath(assetPathFolder, url, newFolder) + '")',
);
});
diff --git a/src/core/features/h5p/classes/framework.ts b/src/core/features/h5p/classes/framework.ts
index f3d70b2bc..e2b3c6afb 100644
--- a/src/core/features/h5p/classes/framework.ts
+++ b/src/core/features/h5p/classes/framework.ts
@@ -13,7 +13,7 @@
// limitations under the License.
import { CoreSites } from '@services/sites';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreH5P } from '@features/h5p/services/h5p';
import {
CoreH5PCore,
@@ -559,7 +559,7 @@ export class CoreH5PFramework {
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any
- const params = CoreTextUtils.parseJSON(contentData.jsoncontent);
+ const params = CoreText.parseJSON(contentData.jsoncontent);
if (!params.metadata) {
params.metadata = {};
}
@@ -695,7 +695,7 @@ export class CoreH5PFramework {
*/
parseLibAddonData(library: LibraryAddonDBData): CoreH5PLibraryAddonData {
const parsedLib = library;
- parsedLib.addTo = CoreTextUtils.parseJSON(library.addTo, null);
+ parsedLib.addTo = CoreText.parseJSON(library.addTo, null);
return parsedLib;
}
@@ -708,9 +708,9 @@ export class CoreH5PFramework {
*/
protected parseLibDBData(library: CoreH5PLibraryDBRecord): CoreH5PLibraryParsedDBRecord {
return Object.assign(library, {
- semantics: library.semantics ? CoreTextUtils.parseJSON(library.semantics, null) : null,
- addto: library.addto ? CoreTextUtils.parseJSON(library.addto, null) : null,
- metadatasettings: library.metadatasettings ? CoreTextUtils.parseJSON(library.metadatasettings, null) : null,
+ semantics: library.semantics ? CoreText.parseJSON(library.semantics, null) : null,
+ addto: library.addto ? CoreText.parseJSON(library.addto, null) : null,
+ metadatasettings: library.metadatasettings ? CoreText.parseJSON(library.metadatasettings, null) : null,
});
}
@@ -922,7 +922,7 @@ export class CoreH5PFramework {
// Add title to 'params' to be able to add it to metadata later.
if (typeof content.title === 'string') {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
- const params = CoreTextUtils.parseJSON(content.params || '{}');
+ const params = CoreText.parseJSON(content.params || '{}');
params.title = content.title;
content.params = JSON.stringify(params);
}
diff --git a/src/core/features/login/pages/email-signup/email-signup.ts b/src/core/features/login/pages/email-signup/email-signup.ts
index 6202eff90..35d1b1433 100644
--- a/src/core/features/login/pages/email-signup/email-signup.ts
+++ b/src/core/features/login/pages/email-signup/email-signup.ts
@@ -16,7 +16,7 @@ import { Component, ViewChild, ElementRef, OnInit, ChangeDetectorRef } from '@an
import { FormBuilder, FormGroup, Validators, FormControl } from '@angular/forms';
import { CoreDomUtils } from '@services/utils/dom';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreCountry, CoreUtils } from '@services/utils/utils';
import { CoreWS, CoreWSExternalWarning } from '@services/ws';
import { Translate } from '@singletons';
@@ -312,10 +312,10 @@ export class CoreLoginEmailSignupPage implements OnInit {
const params: SignupUserWSParams = {
username: this.signupForm.value.username.trim().toLowerCase(),
password: this.signupForm.value.password,
- firstname: CoreTextUtils.cleanTags(this.signupForm.value.firstname),
- lastname: CoreTextUtils.cleanTags(this.signupForm.value.lastname),
+ firstname: CoreText.cleanTags(this.signupForm.value.firstname),
+ lastname: CoreText.cleanTags(this.signupForm.value.lastname),
email: this.signupForm.value.email.trim(),
- city: CoreTextUtils.cleanTags(this.signupForm.value.city),
+ city: CoreText.cleanTags(this.signupForm.value.city),
country: this.signupForm.value.country,
};
@@ -380,7 +380,7 @@ export class CoreLoginEmailSignupPage implements OnInit {
* @returns Escaped mail.
*/
escapeMail(text: string): string {
- return CoreTextUtils.escapeForRegex(text);
+ return CoreText.escapeForRegex(text);
}
/**
diff --git a/src/core/features/login/pages/site/site.ts b/src/core/features/login/pages/site/site.ts
index 92ce70fa3..586c8761d 100644
--- a/src/core/features/login/pages/site/site.ts
+++ b/src/core/features/login/pages/site/site.ts
@@ -31,7 +31,7 @@ import { Translate } from '@singletons';
import { CoreUrl, CoreUrlPartNames } from '@singletons/url';
import { CoreNavigator } from '@services/navigator';
import { CoreCustomURLSchemes, CoreCustomURLSchemesHandleError } from '@services/urlschemes';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreErrorHelper } from '@services/error-helper';
import { CoreForms } from '@singletons/form';
import { AlertButton } from '@ionic/core';
import { CoreSiteError, CoreSiteErrorDebug } from '@classes/errors/siteerror';
@@ -620,7 +620,7 @@ export class CoreLoginSitePage implements OnInit {
}
// Now display the error.
- error.error = CoreTextUtils.addTextToError(
+ error.error = CoreErrorHelper.addTextToError(
error.error,
' ' + Translate.instant('core.login.youcanstillconnectwithcredentials'),
);
diff --git a/src/core/features/login/services/login-helper.ts b/src/core/features/login/services/login-helper.ts
index d3d9b416b..f1d37e681 100644
--- a/src/core/features/login/services/login-helper.ts
+++ b/src/core/features/login/services/login-helper.ts
@@ -22,7 +22,7 @@ import { CoreEvents, CoreEventSessionExpiredData, CoreEventSiteData } from '@sin
import { CoreSites, CoreLoginSiteInfo, CoreSiteBasicInfo } from '@services/sites';
import { CoreWS, CoreWSExternalWarning } from '@services/ws';
import { CoreDomUtils } from '@services/utils/dom';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUtils } from '@services/utils/utils';
import { CoreConstants } from '@/core/constants';
import { CoreSite } from '@classes/sites/site';
@@ -60,6 +60,7 @@ import { LazyRoutesModule } from '@/app/app-routing.module';
import { CoreSiteError, CoreSiteErrorDebug } from '@classes/errors/siteerror';
import { CoreQRScan } from '@services/qrscan';
import { CoreLoadings } from '@services/loadings';
+import { CoreErrorHelper } from '@services/error-helper';
/**
* Helper provider that provides some common features regarding authentication.
@@ -235,7 +236,7 @@ export class CoreLoginHelperProvider {
*
* @param config Site public config.
* @returns Disabled features.
- * @deprecated since 4.4. No longer needed.
+ * @deprecated since 4.4. Shoudn't be used since disabled features are not treated by this function anymore.
*/
getDisabledFeatures(config?: CoreSitePublicConfigResponse): string {
const disabledFeatures = config?.tool_mobile_disabledfeatures;
@@ -243,7 +244,7 @@ export class CoreLoginHelperProvider {
return '';
}
- return CoreTextUtils.treatDisabledFeatures(disabledFeatures);
+ return disabledFeatures;
}
/**
@@ -1161,16 +1162,16 @@ export class CoreLoginHelperProvider {
switch (errorCode) {
case 'forcepasswordchangenotice':
- this.openChangePassword(siteUrl, CoreTextUtils.getErrorMessageFromError(error) ?? '');
+ this.openChangePassword(siteUrl, CoreErrorHelper.getErrorMessageFromError(error) ?? '');
break;
case 'usernotconfirmed':
this.showNotConfirmedModal(siteUrl, undefined, username, password);
break;
case 'connecttomoodleapp':
- this.showMoodleAppNoticeModal(CoreTextUtils.getErrorMessageFromError(error) ?? '');
+ this.showMoodleAppNoticeModal(CoreErrorHelper.getErrorMessageFromError(error) ?? '');
break;
case 'connecttoworkplaceapp':
- this.showWorkplaceNoticeModal(CoreTextUtils.getErrorMessageFromError(error) ?? '');
+ this.showWorkplaceNoticeModal(CoreErrorHelper.getErrorMessageFromError(error) ?? '');
break;
case 'invalidlogin':
this.showInvalidLoginModal(error);
@@ -1193,7 +1194,7 @@ export class CoreLoginHelperProvider {
const serializedData = await CoreConfig.get(CoreConstants.LOGIN_LAUNCH_DATA);
- const data = CoreTextUtils.parseJSON(serializedData, null);
+ const data = CoreText.parseJSON(serializedData, null);
if (data === null) {
throw new CoreError('No launch data stored.');
}
@@ -1571,7 +1572,7 @@ export class CoreLoginHelperProvider {
protected async getPasswordResets(): Promise> {
const passwordResetsJson = await CoreConfig.get(CoreLoginHelperProvider.PASSWORD_RESETS_CONFIG_KEY, '{}');
- return CoreTextUtils.parseJSON>(passwordResetsJson, {});
+ return CoreText.parseJSON>(passwordResetsJson, {});
}
}
diff --git a/src/core/features/mainmenu/services/mainmenu.ts b/src/core/features/mainmenu/services/mainmenu.ts
index d48451d56..58ee9df22 100644
--- a/src/core/features/mainmenu/services/mainmenu.ts
+++ b/src/core/features/mainmenu/services/mainmenu.ts
@@ -19,7 +19,7 @@ import { CoreSites } from '@services/sites';
import { CoreConstants } from '@/core/constants';
import { CoreMainMenuDelegate, CoreMainMenuHandlerToDisplay } from './mainmenu-delegate';
import { Device, makeSingleton } from '@singletons';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreScreen } from '@services/screen';
import { CorePlatform } from '@services/platform';
@@ -211,7 +211,7 @@ export class CoreMainMenuProvider {
.filter(item => typeof item.label === 'string' || currentLang in item.label || fallbackLang in item.label)
.map(item => ({
...item,
- url: CoreTextUtils.replaceArguments(item.url, replacements, 'uri'),
+ url: CoreText.replaceArguments(item.url, replacements, 'uri'),
label: typeof item.label === 'string'
? item.label
: item.label[currentLang] ?? item.label[fallbackLang],
diff --git a/src/core/features/pushnotifications/services/pushnotifications.ts b/src/core/features/pushnotifications/services/pushnotifications.ts
index e27c7746a..f8bb9cfb3 100644
--- a/src/core/features/pushnotifications/services/pushnotifications.ts
+++ b/src/core/features/pushnotifications/services/pushnotifications.ts
@@ -21,7 +21,7 @@ import { CoreSites } from '@services/sites';
import { CorePushNotificationsDelegate } from './push-delegate';
import { CoreLocalNotifications } from '@services/local-notifications';
import { CoreUtils } from '@services/utils/utils';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreConfig } from '@services/config';
import { CoreConstants } from '@/core/constants';
import { CoreSite } from '@classes/sites/site';
@@ -461,7 +461,7 @@ export class CorePushNotificationsProvider {
title: notification.title,
message: notification.message,
customdata: typeof rawData.customdata == 'string' ?
- CoreTextUtils.parseJSON>(rawData.customdata, {}) : rawData.customdata,
+ CoreText.parseJSON>(rawData.customdata, {}) : rawData.customdata,
});
let site: CoreSite | undefined;
@@ -862,7 +862,7 @@ export class CorePushNotificationsProvider {
result.siteid,
result.siteurl,
result.token,
- { info: CoreTextUtils.parseJSON(result.info, null) || undefined },
+ { info: CoreText.parseJSON(result.info, null) || undefined },
);
await this.unregisterDeviceOnMoodle(tmpSite);
diff --git a/src/core/features/question/classes/base-question-component.ts b/src/core/features/question/classes/base-question-component.ts
index 78fc86068..2c7c13cd5 100644
--- a/src/core/features/question/classes/base-question-component.ts
+++ b/src/core/features/question/classes/base-question-component.ts
@@ -17,7 +17,7 @@ import { CoreFileHelper } from '@services/file-helper';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUrl } from '@singletons/url';
import { CoreWSFile } from '@services/ws';
import { CoreIonicColorNames } from '@singletons/colors';
@@ -25,6 +25,7 @@ import { CoreLogger } from '@singletons/logger';
import { CoreQuestionBehaviourButton, CoreQuestionHelper, CoreQuestionQuestion } from '../services/question-helper';
import { ContextLevel } from '@/core/constants';
import { toBoolean } from '@/core/transforms/boolean';
+import { convertTextToHTMLElement } from '@/core/utils/create-html-element';
/**
* Base class for components to render a question.
@@ -87,7 +88,7 @@ export class CoreQuestionBaseComponent('input[type="hidden"][name*=answerformat]');
- let content = CoreTextUtils.decodeHTML(textarea.innerHTML || '');
+ let content = CoreText.decodeHTML(textarea.innerHTML || '');
if (question.hasDraftFiles && question.responsefileareas) {
- content = CoreTextUtils.replaceDraftfileUrls(
+ content = CoreFileHelper.replaceDraftfileUrls(
CoreSites.getRequiredCurrentSite().getURL(),
content,
CoreQuestionHelper.getResponseFileAreaFiles(question, 'answer'),
@@ -433,7 +434,7 @@ export class CoreQuestionBaseComponent(contentSelector);
@@ -590,7 +591,7 @@ export class CoreQuestionBaseComponent Array.from(element.querySelectorAll(selector));
@@ -150,7 +151,7 @@ export class CoreQuestionHelperProvider {
* @returns Wether the certainty is found.
*/
extractQbehaviourCBM(question: CoreQuestionQuestion): boolean {
- const element = CoreDomUtils.convertToElement(question.html);
+ const element = convertTextToHTMLElement(question.html);
const labels = Array.from(element.querySelectorAll('.im-controls .certaintychoices label[for*="certainty"]'));
question.behaviourCertaintyOptions = [];
@@ -163,7 +164,7 @@ export class CoreQuestionHelperProvider {
id: input.id,
name: input.name,
value: input.value,
- text: CoreTextUtils.cleanTags(label.innerHTML),
+ text: CoreText.cleanTags(label.innerHTML),
disabled: input.disabled,
});
@@ -217,7 +218,7 @@ export class CoreQuestionHelperProvider {
* @returns Whether the seen input is found.
*/
extractQbehaviourSeenInput(question: CoreQuestionQuestion): boolean {
- const element = CoreDomUtils.convertToElement(question.html);
+ const element = convertTextToHTMLElement(question.html);
// Search the "seen" input.
const seenInput = element.querySelector('input[type="hidden"][name*=seen]');
@@ -273,7 +274,7 @@ export class CoreQuestionHelperProvider {
* @param attrName Name of the attribute to store the HTML in.
*/
protected extractQuestionLastElementNotInContent(question: CoreQuestionQuestion, selector: string, attrName: string): void {
- const element = CoreDomUtils.convertToElement(question.html);
+ const element = convertTextToHTMLElement(question.html);
const matches = Array.from(element.querySelectorAll(selector));
// Get the last element and check it's not in the question contents.
@@ -333,7 +334,7 @@ export class CoreQuestionHelperProvider {
initMatch = initMatch.substring(0, initMatch.length - 2);
// Try to convert it to an object and add it to the question.
- question.initObjects = CoreTextUtils.parseJSON(initMatch, null);
+ question.initObjects = CoreText.parseJSON(initMatch, null);
}
}
@@ -344,7 +345,7 @@ export class CoreQuestionHelperProvider {
if (amdMatch) {
// Try to convert the arguments to an array and add them to the question.
- question.amdArgs = CoreTextUtils.parseJSON('[' + amdMatch[1] + ']', null);
+ question.amdArgs = CoreText.parseJSON('[' + amdMatch[1] + ']', null);
}
});
}
@@ -358,7 +359,7 @@ export class CoreQuestionHelperProvider {
* @returns Object where the keys are the names.
*/
getAllInputNamesFromHtml(html: string): Record {
- const element = CoreDomUtils.convertToElement('');
+ const element = convertTextToHTMLElement('');
const form = element.children[0];
const answers: Record = {};
@@ -424,7 +425,7 @@ export class CoreQuestionHelperProvider {
* @returns Attachments.
*/
getQuestionAttachmentsFromHtml(html: string): CoreWSFile[] {
- const element = CoreDomUtils.convertToElement(html);
+ const element = convertTextToHTMLElement(html);
// Remove the filemanager (area to attach files to a question).
CoreDomUtils.removeElement(element, 'div[id*=filemanager]');
@@ -438,7 +439,7 @@ export class CoreQuestionHelperProvider {
// Check anchor is valid.
if (anchor.href && content) {
- content = CoreTextUtils.cleanTags(content, { singleLine: true, trim: true });
+ content = CoreText.cleanTags(content, { singleLine: true, trim: true });
attachments.push({
filename: content,
fileurl: anchor.href,
@@ -461,7 +462,7 @@ export class CoreQuestionHelperProvider {
}
// Search the input holding the sequencecheck.
- const element = CoreDomUtils.convertToElement(html);
+ const element = convertTextToHTMLElement(html);
const input = element.querySelector('input[name*=sequencecheck]');
if (!input || input.name === undefined || input.value === undefined) {
@@ -531,7 +532,7 @@ export class CoreQuestionHelperProvider {
* @returns Validation error message if present.
*/
getValidationErrorFromHtml(html: string): string | undefined {
- const element = CoreDomUtils.convertToElement(html);
+ const element = convertTextToHTMLElement(html);
return CoreDomUtils.getContentsOfElement(element, '.validationerror');
}
@@ -583,7 +584,7 @@ export class CoreQuestionHelperProvider {
* @param question Question.
*/
loadLocalAnswersInHtml(question: CoreQuestionQuestion): void {
- const element = CoreDomUtils.convertToElement('');
+ const element = convertTextToHTMLElement('');
const form = element.children[0];
// Search all input elements.
@@ -758,7 +759,7 @@ export class CoreQuestionHelperProvider {
* @returns Whether the button is found.
*/
protected searchBehaviourButton(question: CoreQuestionQuestion, htmlProperty: string, selector: string): boolean {
- const element = CoreDomUtils.convertToElement(question[htmlProperty]);
+ const element = convertTextToHTMLElement(question[htmlProperty]);
const button = element.querySelector(selector);
if (!button) {
diff --git a/src/core/features/question/services/question.ts b/src/core/features/question/services/question.ts
index 7fc1571ca..e7e88081d 100644
--- a/src/core/features/question/services/question.ts
+++ b/src/core/features/question/services/question.ts
@@ -16,7 +16,7 @@ import { Injectable } from '@angular/core';
import { CoreFile } from '@services/file';
import { CoreSites } from '@services/sites';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreTimeUtils } from '@services/utils/time';
import { CoreUtils } from '@services/utils/utils';
import { CoreWSExternalFile } from '@services/ws';
@@ -415,7 +415,7 @@ export class CoreQuestionProvider {
return;
}
- question.parsedSettings = CoreTextUtils.parseJSON(question.settings, null);
+ question.parsedSettings = CoreText.parseJSON(question.settings, null);
});
return parsedQuestions;
diff --git a/src/core/features/rating/services/rating-sync.ts b/src/core/features/rating/services/rating-sync.ts
index 2d89041d7..9e51a576f 100644
--- a/src/core/features/rating/services/rating-sync.ts
+++ b/src/core/features/rating/services/rating-sync.ts
@@ -18,7 +18,7 @@ import { CoreSyncBaseProvider } from '@classes/base-sync';
import { CoreNetworkError } from '@classes/errors/network-error';
import { CoreNetwork } from '@services/network';
import { CoreSites } from '@services/sites';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreErrorHelper } from '@services/error-helper';
import { CoreUtils } from '@services/utils/utils';
import { makeSingleton } from '@singletons';
import { CoreEvents } from '@singletons/events';
@@ -227,7 +227,7 @@ export class CoreRatingSyncProvider extends CoreSyncBaseProvider${Translate.instant('addon.mod_page.errorwhileloadingthepage')}
diff --git a/src/core/features/settings/services/settings-helper.ts b/src/core/features/settings/services/settings-helper.ts
index 25bc2400a..0936acaca 100644
--- a/src/core/features/settings/services/settings-helper.ts
+++ b/src/core/features/settings/services/settings-helper.ts
@@ -29,8 +29,9 @@ import { CoreCourse } from '@features/course/services/course';
import { makeSingleton, Translate } from '@singletons';
import { CoreError } from '@classes/errors/error';
import { Observable, Subject } from 'rxjs';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreErrorHelper } from '@services/error-helper';
import { CoreNavigator } from '@services/navigator';
+import { CoreHTMLClasses } from '@singletons/html-classes';
/**
* Object with space usage and cache entries that can be erased.
@@ -263,7 +264,7 @@ export class CoreSettingsHelperProvider {
try {
await syncPromise;
} catch (error) {
- throw CoreTextUtils.addTitleToError(error, Translate.instant('core.settings.sitesyncfailed'));
+ throw CoreErrorHelper.addTitleToError(error, Translate.instant('core.settings.sitesyncfailed'));
} finally {
delete this.syncPromises[siteId];
}
@@ -433,10 +434,10 @@ export class CoreSettingsHelperProvider {
* @param enable True to enable dark mode, false to disable.
*/
protected toggleDarkMode(enable: boolean = false): void {
- const isDark = CoreDomUtils.hasModeClass('dark');
+ const isDark = CoreHTMLClasses.hasModeClass('dark');
if (isDark !== enable) {
- CoreDomUtils.toggleModeClass('dark', enable);
+ CoreHTMLClasses.toggleModeClass('dark', enable);
this.darkModeObservable.next(enable);
CoreApp.setSystemUIColors();
diff --git a/src/core/features/sharedfiles/services/sharedfiles-helper.ts b/src/core/features/sharedfiles/services/sharedfiles-helper.ts
index 0c8613bcb..325bfd63a 100644
--- a/src/core/features/sharedfiles/services/sharedfiles-helper.ts
+++ b/src/core/features/sharedfiles/services/sharedfiles-helper.ts
@@ -106,9 +106,9 @@ export class CoreSharedFilesHelperProvider {
const result = await alert.onDidDismiss();
- if (result.role == 'rename') {
+ if (result.role === 'rename') {
return newName;
- } else if (result.role == 'replace') {
+ } else if (result.role === 'replace') {
return originalName;
} else {
// Canceled.
diff --git a/src/core/features/siteplugins/classes/call-ws-click-directive.ts b/src/core/features/siteplugins/classes/call-ws-click-directive.ts
index 6525b4366..bb656d9c3 100644
--- a/src/core/features/siteplugins/classes/call-ws-click-directive.ts
+++ b/src/core/features/siteplugins/classes/call-ws-click-directive.ts
@@ -15,7 +15,7 @@
import { Input, OnInit, ElementRef, Directive } from '@angular/core';
import { CoreDomUtils } from '@services/utils/dom';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreErrorHelper } from '@services/error-helper';
import { Translate } from '@singletons';
import { CoreSitePluginsPluginContentComponent } from '../components/plugin-content/plugin-content';
import { CoreSitePluginsCallWSBaseDirective } from './call-ws-directive';
@@ -77,7 +77,7 @@ export class CoreSitePluginsCallWSOnClickBaseDirective extends CoreSitePluginsCa
CoreDomUtils.showErrorModalDefault(
error,
Translate.instant('core.serverconnection', {
- details: CoreTextUtils.getErrorMessageFromError(error) ?? 'Unknown error',
+ details: CoreErrorHelper.getErrorMessageFromError(error) ?? 'Unknown error',
}),
);
}
diff --git a/src/core/features/siteplugins/services/siteplugins-init.ts b/src/core/features/siteplugins/services/siteplugins-init.ts
index e85dd2902..9df028152 100644
--- a/src/core/features/siteplugins/services/siteplugins-init.ts
+++ b/src/core/features/siteplugins/services/siteplugins-init.ts
@@ -37,7 +37,7 @@ import { CoreUserProfileFieldDelegate } from '@features/user/services/user-profi
import { CoreFilepool } from '@services/filepool';
import { CoreLang } from '@services/lang';
import { CoreSites } from '@services/sites';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUtils } from '@services/utils/utils';
import { CoreWS } from '@services/ws';
import { CoreEvents } from '@singletons/events';
@@ -344,7 +344,7 @@ export class CoreSitePluginsInitService {
this.logger.debug('Load site plugin:', plugin);
if (!plugin.parsedHandlers && plugin.handlers) {
- plugin.parsedHandlers = CoreTextUtils.parseJSON(
+ plugin.parsedHandlers = CoreText.parseJSON(
plugin.handlers,
null,
error => this.logger.error('Error parsing site plugin handlers', error),
@@ -352,7 +352,7 @@ export class CoreSitePluginsInitService {
}
if (!plugin.parsedLang && plugin.lang) {
- plugin.parsedLang = CoreTextUtils.parseJSON(
+ plugin.parsedLang = CoreText.parseJSON(
plugin.lang,
null,
error => this.logger.error('Error parsing site plugin lang', error),
diff --git a/src/core/features/siteplugins/services/siteplugins.ts b/src/core/features/siteplugins/services/siteplugins.ts
index 30deff990..0a6fb8e4b 100644
--- a/src/core/features/siteplugins/services/siteplugins.ts
+++ b/src/core/features/siteplugins/services/siteplugins.ts
@@ -22,7 +22,7 @@ import { CoreApp } from '@services/app';
import { CoreFilepool } from '@services/filepool';
import { CoreLang, CoreLangFormat } from '@services/lang';
import { CoreSites } from '@services/sites';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUtils } from '@services/utils/utils';
import { CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws';
import { makeSingleton } from '@singletons';
@@ -238,7 +238,7 @@ export class CoreSitePluginsProvider {
const value = otherData[name];
if (typeof value == 'string' && (value[0] == '{' || value[0] == '[')) {
- otherData[name] = CoreTextUtils.parseJSON(value);
+ otherData[name] = CoreText.parseJSON(value);
}
}
}
@@ -466,7 +466,7 @@ export class CoreSitePluginsProvider {
// Site plugin not disabled. Check if it has handlers.
if (!plugin.parsedHandlers) {
- plugin.parsedHandlers = CoreTextUtils.parseJSON(
+ plugin.parsedHandlers = CoreText.parseJSON(
plugin.handlers,
null,
error => this.logger.error('Error parsing site plugin handlers', error),
diff --git a/src/core/features/tag/pages/search/search.ts b/src/core/features/tag/pages/search/search.ts
index eeeed8eef..6cc3147de 100644
--- a/src/core/features/tag/pages/search/search.ts
+++ b/src/core/features/tag/pages/search/search.ts
@@ -16,7 +16,7 @@ import { Component, OnInit } from '@angular/core';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreUrl } from '@singletons/url';
import { CoreTagCloud, CoreTagCollection, CoreTagCloudTag, CoreTag } from '@features/tag/services/tag';
import { Translate } from '@singletons';
import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper';
@@ -120,7 +120,7 @@ export class CoreTagSearchPage implements OnInit {
* Go to tag index page.
*/
openTag(tag: CoreTagCloudTag): void {
- const url = CoreTextUtils.decodeURI(tag.viewurl);
+ const url = CoreUrl.decodeURI(tag.viewurl);
CoreContentLinksHelper.handleLink(url);
}
diff --git a/src/core/features/tag/services/tag-helper.ts b/src/core/features/tag/services/tag-helper.ts
index becd52a95..448dcfd59 100644
--- a/src/core/features/tag/services/tag-helper.ts
+++ b/src/core/features/tag/services/tag-helper.ts
@@ -14,7 +14,7 @@
import { makeSingleton } from '@singletons';
import { Injectable } from '@angular/core';
-import { CoreDomUtils } from '@services/utils/dom';
+import { convertTextToHTMLElement } from '@/core/utils/create-html-element';
/**
* Service with helper functions for tags.
@@ -30,7 +30,7 @@ export class CoreTagHelperProvider {
*/
parseFeedContent(content: string): CoreTagFeedElement[] {
const items: CoreTagFeedElement[] = [];
- const element = CoreDomUtils.convertToElement(content);
+ const element = convertTextToHTMLElement(content);
Array.from(element.querySelectorAll('ul.tag_feed > li')).forEach((itemElement) => {
const item: CoreTagFeedElement = { details: [] };
diff --git a/src/core/features/user/services/handlers/tag-area.ts b/src/core/features/user/services/handlers/tag-area.ts
index 37111a061..4e5c6761f 100644
--- a/src/core/features/user/services/handlers/tag-area.ts
+++ b/src/core/features/user/services/handlers/tag-area.ts
@@ -14,7 +14,7 @@
import { Injectable, Type } from '@angular/core';
-import { CoreDomUtils } from '@services/utils/dom';
+import { convertTextToHTMLElement } from '@/core/utils/create-html-element';
import { CoreTagAreaHandler } from '@features/tag/services/tag-area-delegate';
import { CoreUserTagAreaComponent } from '@features/user/components/tag-area/tag-area';
import { CoreTagFeedElement } from '@features/tag/services/tag-helper';
@@ -47,7 +47,7 @@ export class CoreUserTagAreaHandlerService implements CoreTagAreaHandler {
*/
parseContent(content: string): CoreUserTagFeedElement[] {
const items: CoreUserTagFeedElement[] = [];
- const element = CoreDomUtils.convertToElement(content);
+ const element = convertTextToHTMLElement(content);
Array.from(element.querySelectorAll('div.user-box')).forEach((userbox: HTMLElement) => {
const avatarLink = userbox.querySelector('a:first-child');
diff --git a/src/core/features/user/services/user-sync.ts b/src/core/features/user/services/user-sync.ts
index 31f6d0d9f..f67422292 100644
--- a/src/core/features/user/services/user-sync.ts
+++ b/src/core/features/user/services/user-sync.ts
@@ -15,7 +15,7 @@
import { Injectable } from '@angular/core';
import { CoreSites } from '@services/sites';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreErrorHelper } from '@services/error-helper';
import { CoreUtils } from '@services/utils/utils';
import { CoreSyncBaseProvider } from '@classes/base-sync';
import { makeSingleton } from '@singletons';
@@ -91,7 +91,7 @@ export class CoreUserSyncProvider extends CoreSyncBaseProvider {
await CoreUser.setUserPreference(preference.name, preference.value, siteId);
} catch (error) {
if (CoreUtils.isWebServiceError(error)) {
- const warning = CoreTextUtils.getErrorMessageFromError(error);
+ const warning = CoreErrorHelper.getErrorMessageFromError(error);
if (warning) {
warnings.push(warning);
}
diff --git a/src/core/pipes/bytes-to-size.ts b/src/core/pipes/bytes-to-size.ts
index 19b24b9d7..2ef3e2bf5 100644
--- a/src/core/pipes/bytes-to-size.ts
+++ b/src/core/pipes/bytes-to-size.ts
@@ -15,7 +15,7 @@
import { Pipe, PipeTransform } from '@angular/core';
import { CoreLogger } from '@singletons/logger';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
/**
* Pipe to turn a number in bytes to a human readable size (e.g. 5,25 MB).
@@ -49,7 +49,7 @@ export class CoreBytesToSizePipe implements PipeTransform {
value = numberValue;
}
- return CoreTextUtils.bytesToSize(value);
+ return CoreText.bytesToSize(value);
}
}
diff --git a/src/core/pipes/seconds-to-hms.ts b/src/core/pipes/seconds-to-hms.ts
index 9afc7c7ac..c4c284491 100644
--- a/src/core/pipes/seconds-to-hms.ts
+++ b/src/core/pipes/seconds-to-hms.ts
@@ -14,7 +14,7 @@
import { Pipe, PipeTransform } from '@angular/core';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreLogger } from '@singletons/logger';
import { CoreConstants } from '@/core/constants';
@@ -63,8 +63,8 @@ export class CoreSecondsToHMSPipe implements PipeTransform {
seconds -= minutes * CoreConstants.SECONDS_MINUTE;
return showHours
- ? CoreTextUtils.twoDigits(hours) + ':' + CoreTextUtils.twoDigits(minutes) + ':' + CoreTextUtils.twoDigits(seconds)
- : CoreTextUtils.twoDigits(minutes) + ':' + CoreTextUtils.twoDigits(seconds);
+ ? CoreText.twoDigits(hours) + ':' + CoreText.twoDigits(minutes) + ':' + CoreText.twoDigits(seconds)
+ : CoreText.twoDigits(minutes) + ':' + CoreText.twoDigits(seconds);
}
}
diff --git a/src/core/services/analytics.ts b/src/core/services/analytics.ts
index 91b6520e5..09be8156a 100644
--- a/src/core/services/analytics.ts
+++ b/src/core/services/analytics.ts
@@ -20,7 +20,7 @@ import { CoreEvents } from '@singletons/events';
import { CoreSites } from './sites';
import { CoreConfig, CoreConfigProvider } from './config';
import { CoreConstants } from '../constants';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUrl } from '@singletons/url';
/**
@@ -103,7 +103,7 @@ export class CoreAnalyticsService extends CoreDelegate {
};
if (treatedEvent.type === CoreAnalyticsEventType.VIEW_ITEM || treatedEvent.type === CoreAnalyticsEventType.VIEW_ITEM_LIST) {
- treatedEvent.name = CoreTextUtils.cleanTags(treatedEvent.name);
+ treatedEvent.name = CoreText.cleanTags(treatedEvent.name);
}
if ('url' in treatedEvent && treatedEvent.url) {
diff --git a/src/core/services/error-helper.ts b/src/core/services/error-helper.ts
new file mode 100644
index 000000000..daadb2382
--- /dev/null
+++ b/src/core/services/error-helper.ts
@@ -0,0 +1,176 @@
+// (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 { CoreAnyError, CoreError } from '@classes/errors/error';
+import { makeSingleton, Translate } from '@singletons';
+import { AlertButton } from '@ionic/angular';
+
+/**
+ * Provider to provide some helper functions regarding files and packages.
+ */
+@Injectable({ providedIn: 'root' })
+export class CoreErrorHelperService {
+
+ /**
+ * Add some text to an error message.
+ *
+ * @param error Error message or object.
+ * @param text Text to add.
+ * @returns Modified error.
+ */
+ addTextToError(error: string | CoreError | CoreErrorObject | undefined | null, text: string): string | CoreErrorObject {
+ if (typeof error === 'string') {
+ return error + text;
+ }
+
+ if (error instanceof CoreError) {
+ error.message += text;
+
+ return error;
+ }
+
+ if (!error) {
+ return text;
+ }
+
+ if (typeof error.message === 'string') {
+ error.message += text;
+ } else if (typeof error.error === 'string') {
+ error.error += text;
+ } else if (typeof error.content === 'string') {
+ error.content += text;
+ } else if (typeof error.body === 'string') {
+ error.body += text;
+ }
+
+ return error;
+ }
+
+ /**
+ * Add some title to an error message.
+ *
+ * @param error Error message or object.
+ * @param title Title to add.
+ * @returns Modified error.
+ */
+ addTitleToError(error: string | CoreError | CoreErrorObject | undefined | null, title: string): CoreErrorObject {
+ let improvedError: CoreErrorObject = {};
+
+ if (typeof error === 'string') {
+ improvedError.message = error;
+ } else if (error && 'message' in error) {
+ improvedError = error;
+ }
+
+ improvedError.title = improvedError.title || title;
+
+ return improvedError;
+ }
+
+ /**
+ * Build a message with several paragraphs.
+ *
+ * @param paragraphs List of paragraphs.
+ * @returns Built message.
+ */
+ buildSeveralParagraphsMessage(paragraphs: (string | CoreErrorObject)[]): string {
+ // Filter invalid messages, and convert them to messages in case they're errors.
+ const messages: string[] = [];
+
+ paragraphs.forEach(paragraph => {
+ // If it's an error, get its message.
+ const message = this.getErrorMessageFromError(paragraph);
+
+ if (paragraph && message) {
+ messages.push(message);
+ }
+ });
+
+ if (messages.length < 2) {
+ return messages[0] || '';
+ }
+
+ let builtMessage = messages[0];
+
+ for (let i = 1; i < messages.length; i++) {
+ builtMessage = Translate.instant('core.twoparagraphs', { p1: builtMessage, p2: messages[i] });
+ }
+
+ return builtMessage;
+ }
+
+ /**
+ * Get the error message from an error object.
+ *
+ * @param error Error.
+ * @returns Error message, undefined if not found.
+ */
+ getErrorMessageFromError(error?: CoreAnyError): string | undefined {
+ if (typeof error === 'string') {
+ return error;
+ }
+
+ if (error instanceof CoreError) {
+ return error.message;
+ }
+
+ if (!error) {
+ return undefined;
+ }
+
+ if (error.message || error.error || error.content) {
+ return error.message || error.error || error.content;
+ }
+
+ if (error.body) {
+ return this.getErrorMessageFromHTML(error.body);
+ }
+
+ return undefined;
+ }
+
+ /**
+ * Get the error message from an HTML error page.
+ *
+ * @param body HTML content.
+ * @returns Error message or empty string if not found.
+ */
+ getErrorMessageFromHTML(body: string): string {
+ // THe parser does not throw errors and scripts are not executed.
+ const parser = new DOMParser();
+ const doc = parser.parseFromString(body, 'text/html');
+
+ // Errors are rendered using the "errorbox" and "errormessage" classes since Moodle 2.0.
+ const element = doc.body.querySelector('.errorbox .errormessage');
+
+ return element?.innerText.trim() ?? '';
+ }
+
+}
+export const CoreErrorHelper = makeSingleton(CoreErrorHelperService);
+
+/**
+ * Different type of errors the app can treat.
+ */
+export type CoreErrorObject = {
+ message?: string;
+ error?: string;
+ content?: string;
+ body?: string;
+ debuginfo?: string;
+ backtrace?: string;
+ title?: string;
+ buttons?: AlertButton[];
+};
diff --git a/src/core/services/file-helper.ts b/src/core/services/file-helper.ts
index 91163f2ad..7089e27fe 100644
--- a/src/core/services/file-helper.ts
+++ b/src/core/services/file-helper.ts
@@ -31,6 +31,8 @@ import { CoreConfig } from './config';
import { CoreCanceledError } from '@classes/errors/cancelederror';
import { CoreMimetypeUtils } from '@services/utils/mimetype';
import { CorePlatform } from './platform';
+import { CorePath } from '@singletons/path';
+import { CoreText } from '@singletons/text';
/**
* Provider to provide some helper functions regarding files and packages.
@@ -496,6 +498,159 @@ export class CoreFileHelperProvider {
return path.split('\\').pop()?.split('/').pop();
}
+ /**
+ * Get the pluginfile URL to replace @@PLUGINFILE@@ wildcards.
+ *
+ * @param files Files to extract the URL from. They need to have the URL in a 'url' or 'fileurl' attribute.
+ * @returns Pluginfile URL, undefined if no files found.
+ */
+ getTextPluginfileUrl(files: CoreWSFile[]): string | undefined {
+ if (files?.length) {
+ const url = this.getFileUrl(files[0]);
+
+ // Remove text after last slash (encoded or not).
+ return url?.substring(0, Math.max(url.lastIndexOf('/'), url.lastIndexOf('%2F')));
+ }
+
+ return;
+ }
+
+ /**
+ * Replace draftfile URLs with the equivalent pluginfile URL.
+ *
+ * @param siteUrl URL of the site.
+ * @param text Text to treat, including draftfile URLs.
+ * @param files List of files of the area, using pluginfile URLs.
+ * @returns Treated text and map with the replacements.
+ */
+ replaceDraftfileUrls(
+ siteUrl: string,
+ text: string,
+ files: CoreWSFile[],
+ ): { text: string; replaceMap?: {[url: string]: string} } {
+
+ if (!text || !files || !files.length) {
+ return { text };
+ }
+
+ const draftfileUrl = CorePath.concatenatePaths(siteUrl, 'draftfile.php');
+ const matches = text.match(new RegExp(CoreText.escapeForRegex(draftfileUrl) + '[^\'" ]+', 'ig'));
+
+ if (!matches || !matches.length) {
+ return { text };
+ }
+
+ // Index the pluginfile URLs by file name.
+ const pluginfileMap: {[name: string]: string} = {};
+ files.forEach((file) => {
+ if (!file.filename) {
+ return;
+ }
+ pluginfileMap[file.filename] = CoreFileHelper.getFileUrl(file);
+ });
+
+ // Replace each draftfile with the corresponding pluginfile URL.
+ const replaceMap: {[url: string]: string} = {};
+ matches.forEach((url) => {
+ if (replaceMap[url]) {
+ // URL already treated, same file embedded more than once.
+ return;
+ }
+
+ // Get the filename from the URL.
+ let filename = url.substring(url.lastIndexOf('/') + 1);
+ if (filename.indexOf('?') != -1) {
+ filename = filename.substring(0, filename.indexOf('?'));
+ }
+
+ if (pluginfileMap[filename]) {
+ replaceMap[url] = pluginfileMap[filename];
+ text = text.replace(new RegExp(CoreText.escapeForRegex(url), 'g'), pluginfileMap[filename]);
+ }
+ });
+
+ return {
+ text,
+ replaceMap,
+ };
+ }
+
+ /**
+ * Replace @@PLUGINFILE@@ wildcards with the real URL in a text.
+ *
+ * @param text to treat.
+ * @param files Files to extract the pluginfile URL from. They need to have the URL in a url or fileurl attribute.
+ * @returns Treated text.
+ */
+ replacePluginfileUrls(text: string, files: CoreWSFile[]): string {
+ if (text && typeof text === 'string') {
+ const fileURL = this.getTextPluginfileUrl(files);
+ if (fileURL) {
+ return text.replace(/@@PLUGINFILE@@/g, fileURL);
+ }
+ }
+
+ return text;
+ }
+
+ /**
+ * Restore original draftfile URLs.
+ *
+ * @param siteUrl Site URL.
+ * @param treatedText Treated text with replacements.
+ * @param originalText Original text.
+ * @param files List of files to search and replace.
+ * @returns Treated text.
+ */
+ restoreDraftfileUrls(siteUrl: string, treatedText: string, originalText: string, files: CoreWSFile[]): string {
+ if (!treatedText || !files || !files.length) {
+ return treatedText;
+ }
+
+ const draftfileUrl = CorePath.concatenatePaths(siteUrl, 'draftfile.php');
+ const draftfileUrlRegexPrefix = CoreText.escapeForRegex(draftfileUrl) + '/[^/]+/[^/]+/[^/]+/[^/]+/';
+
+ files.forEach((file) => {
+ if (!file.filename) {
+ return;
+ }
+
+ // Search the draftfile URL in the original text.
+ const matches = originalText.match(
+ new RegExp(draftfileUrlRegexPrefix + CoreText.escapeForRegex(file.filename) + '[^\'" ]*', 'i'),
+ );
+
+ if (!matches || !matches[0]) {
+ return; // Original URL not found, skip.
+ }
+
+ treatedText = treatedText.replace(
+ new RegExp(CoreText.escapeForRegex(CoreFileHelper.getFileUrl(file)), 'g'),
+ matches[0],
+ );
+ });
+
+ return treatedText;
+ }
+
+ /**
+ * Replace pluginfile URLs with @@PLUGINFILE@@ wildcards.
+ *
+ * @param text Text to treat.
+ * @param files Files to extract the pluginfile URL from. They need to have the URL in a url or fileurl attribute.
+ * @returns Treated text.
+ */
+ restorePluginfileUrls(text: string, files: CoreWSFile[]): string {
+ if (text && typeof text == 'string') {
+ const fileURL = this.getTextPluginfileUrl(files);
+ if (fileURL) {
+ return text.replace(new RegExp(CoreText.escapeForRegex(fileURL), 'g'), '@@PLUGINFILE@@');
+ }
+ }
+
+ return text;
+ }
+
}
export const CoreFileHelper = makeSingleton(CoreFileHelperProvider);
diff --git a/src/core/services/file.ts b/src/core/services/file.ts
index 57be3a3e3..07c11679e 100644
--- a/src/core/services/file.ts
+++ b/src/core/services/file.ts
@@ -17,7 +17,6 @@ import { Injectable } from '@angular/core';
import { FileEntry, DirectoryEntry, Entry, Metadata, IFile } from '@awesome-cordova-plugins/file/ngx';
import { CoreMimetypeUtils } from '@services/utils/mimetype';
-import { CoreTextUtils } from '@services/utils/text';
import { CoreUtils } from '@services/utils/utils';
import { CoreConstants } from '@/core/constants';
import { CoreError } from '@classes/errors/error';
@@ -29,6 +28,7 @@ import { CoreText } from '@singletons/text';
import { CorePlatform } from '@services/platform';
import { CorePath } from '@singletons/path';
import { Zip } from '@features/native/plugins';
+import { CoreUrl } from '@singletons/url';
/**
* Progress event used when writing a file data into a file.
@@ -439,7 +439,7 @@ export class CoreFileProvider {
* @returns The file name normalized.
*/
normalizeFileName(filename: string): string {
- filename = CoreTextUtils.decodeURIComponent(filename);
+ filename = CoreUrl.decodeURIComponent(filename);
return filename;
}
@@ -481,7 +481,7 @@ export class CoreFileProvider {
return File.readAsArrayBuffer(folder, path);
case CoreFileFormat.FORMATJSON:
return File.readAsText(folder, path).then((text) => {
- const parsed = CoreTextUtils.parseJSON(text, null);
+ const parsed = CoreText.parseJSON(text, null);
if (parsed == null && text != null) {
throw new CoreError('Error parsing JSON file: ' + path);
@@ -512,7 +512,7 @@ export class CoreFileProvider {
if (event.target?.result !== undefined && event.target.result !== null) {
if (format == CoreFileFormat.FORMATJSON) {
// Convert to object.
- const parsed = CoreTextUtils.parseJSON( event.target.result, null);
+ const parsed = CoreText.parseJSON( event.target.result, null);
if (parsed == null) {
reject('Error parsing JSON file.');
@@ -1081,8 +1081,8 @@ export class CoreFileProvider {
let extension = CoreMimetypeUtils.getFileExtension(fileName) || defaultExt;
// Clean the file name.
- fileNameWithoutExtension = CoreTextUtils.removeSpecialCharactersForFiles(
- CoreTextUtils.decodeURIComponent(fileNameWithoutExtension),
+ fileNameWithoutExtension = CoreText.removeSpecialCharactersForFiles(
+ CoreUrl.decodeURIComponent(fileNameWithoutExtension),
);
// Index the files by name.
@@ -1100,7 +1100,7 @@ export class CoreFileProvider {
return this.calculateUniqueName(files, fileNameWithoutExtension + extension);
} catch (error) {
// Folder doesn't exist, name is unique. Clean it and return it.
- return CoreTextUtils.removeSpecialCharactersForFiles(CoreTextUtils.decodeURIComponent(fileName));
+ return CoreText.removeSpecialCharactersForFiles(CoreUrl.decodeURIComponent(fileName));
}
}
diff --git a/src/core/services/filepool.ts b/src/core/services/filepool.ts
index 7ec4147ca..e0417317f 100644
--- a/src/core/services/filepool.ts
+++ b/src/core/services/filepool.ts
@@ -24,7 +24,7 @@ import { CoreSites } from '@services/sites';
import { CoreWS, CoreWSExternalFile, CoreWSFile } from '@services/ws';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreMimetypeUtils } from '@services/utils/mimetype';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreTimeUtils } from '@services/utils/time';
import { CoreUrl, CoreUrlPartNames } from '@singletons/url';
import { CoreUtils, CoreUtilsOpenFileOptions } from '@services/utils/utils';
@@ -58,6 +58,7 @@ import { asyncInstance, AsyncInstance } from '../utils/async-instance';
import { CorePath } from '@singletons/path';
import { CorePromisedValue } from '@classes/promised-value';
import { CoreAnalytics, CoreAnalyticsEventType } from './analytics';
+import { convertTextToHTMLElement } from '../utils/create-html-element';
/*
* Factory for handling downloading files and retrieve downloaded files.
@@ -1147,7 +1148,7 @@ export class CoreFilepoolProvider {
extractDownloadableFilesFromHtml(html: string): string[] {
let urls: string[] = [];
- const element = CoreDomUtils.convertToElement(html);
+ const element = convertTextToHTMLElement(html);
const elements: AnchorOrMediaElement[] = Array.from(element.querySelectorAll('a, img, audio, video, source, track'));
for (let i = 0; i < elements.length; i++) {
@@ -1339,7 +1340,7 @@ export class CoreFilepoolProvider {
url = this.removeRevisionFromUrl(url);
// Decode URL.
- url = CoreTextUtils.decodeHTML(CoreTextUtils.decodeURIComponent(url));
+ url = CoreText.decodeHTML(CoreUrl.decodeURIComponent(url));
if (url.indexOf('/webservice/pluginfile') !== -1) {
// Remove attributes that do not matter.
@@ -1376,7 +1377,7 @@ export class CoreFilepoolProvider {
url = url.replace(/\/tokenpluginfile\.php\/[^/]+\//, '/webservice/pluginfile.php/');
// Decode URL.
- url = CoreTextUtils.decodeHTML(CoreTextUtils.decodeURIComponent(url));
+ url = CoreText.decodeHTML(CoreUrl.decodeURIComponent(url));
if (url.indexOf('/webservice/pluginfile') !== -1) {
// Remove attributes that do not matter.
@@ -2205,7 +2206,7 @@ export class CoreFilepoolProvider {
filename += '_' + hashes.join('_');
}
- return CoreTextUtils.removeSpecialCharactersForFiles(filename);
+ return CoreText.removeSpecialCharactersForFiles(filename);
}
/**
@@ -2299,7 +2300,7 @@ export class CoreFilepoolProvider {
return {
...entry,
- linksUnserialized: CoreTextUtils.parseJSON(entry.links, []),
+ linksUnserialized: CoreText.parseJSON(entry.links, []),
};
}
@@ -2653,7 +2654,7 @@ export class CoreFilepoolProvider {
return this.processQueueItem({
...item,
- linksUnserialized: CoreTextUtils.parseJSON(item.links, []),
+ linksUnserialized: CoreText.parseJSON(item.links, []),
});
} catch (err) {
throw CoreFilepoolProvider.ERR_QUEUE_IS_EMPTY;
@@ -3095,7 +3096,7 @@ export class CoreFilepoolProvider {
fileUrl = CoreFile.convertFileSrc(fileUrl);
if (fileUrl !== url) {
- cssCode = cssCode.replace(new RegExp(CoreTextUtils.escapeForRegex(url), 'g'), fileUrl);
+ cssCode = cssCode.replace(new RegExp(CoreText.escapeForRegex(url), 'g'), fileUrl);
updated = true;
}
} catch (error) {
@@ -3103,7 +3104,7 @@ export class CoreFilepoolProvider {
// If the URL is relative, store the absolute URL.
if (absoluteUrl !== url) {
- cssCode = cssCode.replace(new RegExp(CoreTextUtils.escapeForRegex(url), 'g'), absoluteUrl);
+ cssCode = cssCode.replace(new RegExp(CoreText.escapeForRegex(url), 'g'), absoluteUrl);
updated = true;
}
}
diff --git a/src/core/services/local-notifications.ts b/src/core/services/local-notifications.ts
index f297b21ce..77608781e 100644
--- a/src/core/services/local-notifications.ts
+++ b/src/core/services/local-notifications.ts
@@ -19,7 +19,7 @@ import { ILocalNotification } from '@awesome-cordova-plugins/local-notifications
import { CoreApp } from '@services/app';
import { CoreConfig } from '@services/config';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreQueueRunner } from '@classes/queue-runner';
import { CoreError } from '@classes/errors/error';
import { CoreConstants } from '@/core/constants';
@@ -522,7 +522,7 @@ export class CoreLocalNotificationsProvider {
if (!data) {
return {};
} else if (typeof data == 'string') {
- return CoreTextUtils.parseJSON(data, {});
+ return CoreText.parseJSON(data, {});
} else {
return data;
}
diff --git a/src/core/services/navigator.ts b/src/core/services/navigator.ts
index 407121ce9..caf6e3642 100644
--- a/src/core/services/navigator.ts
+++ b/src/core/services/navigator.ts
@@ -23,7 +23,7 @@ import { CoreObject } from '@singletons/object';
import { CoreSites } from '@services/sites';
import { CoreUtils } from '@services/utils/utils';
import { CoreUrl, CoreUrlPartNames } from '@singletons/url';
-import { CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { makeSingleton, NavController, Router } from '@singletons';
import { CoreScreen } from './screen';
import { CoreError } from '@classes/errors/error';
@@ -85,7 +85,7 @@ export class CoreNavigatorService {
* @returns Whether the active route is using the given path.
*/
isCurrent(path: string): boolean {
- return CoreTextUtils.matchesGlob(this.getCurrentPath(), path);
+ return CoreText.matchesGlob(this.getCurrentPath(), path);
}
/**
@@ -325,7 +325,7 @@ export class CoreNavigatorService {
// Try to retrieve the param from local storage in browser.
const storageParam = localStorage.getItem(value);
if (storageParam) {
- storedParam = CoreTextUtils.parseJSON(storageParam);
+ storedParam = CoreText.parseJSON(storageParam);
}
}
@@ -763,7 +763,7 @@ export class CoreNavigatorService {
*/
getRelativePathToParent(parentPath: string): string {
// Add an ending slash to avoid collisions with other routes (e.g. /foo and /foobar).
- parentPath = CoreTextUtils.addEndingSlash(parentPath);
+ parentPath = CoreText.addEndingSlash(parentPath);
const path = this.getCurrentPath();
const parentRouteIndex = path.indexOf(parentPath);
diff --git a/src/core/services/network.ts b/src/core/services/network.ts
index 3c8cc18f9..35597f4f5 100644
--- a/src/core/services/network.ts
+++ b/src/core/services/network.ts
@@ -17,7 +17,7 @@ import { CorePlatform } from '@services/platform';
import { Network } from '@awesome-cordova-plugins/network/ngx';
import { NgZone, makeSingleton } from '@singletons';
import { Observable, Subject, merge } from 'rxjs';
-import { CoreDomUtils } from './utils/dom';
+import { CoreHTMLClasses } from '@singletons/html-classes';
export enum CoreNetworkConnection {
UNKNOWN = 'unknown',
@@ -109,24 +109,24 @@ export class CoreNetworkService extends Network {
NgZone.run(() => {
const isOnline = this.isOnline();
- const hadOfflineMessage = CoreDomUtils.hasModeClass('core-offline');
+ const hadOfflineMessage = CoreHTMLClasses.hasModeClass('core-offline');
- CoreDomUtils.toggleModeClass('core-offline', !isOnline);
+ CoreHTMLClasses.toggleModeClass('core-offline', !isOnline);
if (isOnline && hadOfflineMessage) {
- CoreDomUtils.toggleModeClass('core-online', true);
+ CoreHTMLClasses.toggleModeClass('core-online', true);
setTimeout(() => {
- CoreDomUtils.toggleModeClass('core-online', false);
+ CoreHTMLClasses.toggleModeClass('core-online', false);
}, 3000);
} else if (!isOnline) {
- CoreDomUtils.toggleModeClass('core-online', false);
+ CoreHTMLClasses.toggleModeClass('core-online', false);
}
});
});
const isOnline = this.isOnline();
- CoreDomUtils.toggleModeClass('core-offline', !isOnline);
+ CoreHTMLClasses.toggleModeClass('core-offline', !isOnline);
}
/**
diff --git a/src/core/services/sites.ts b/src/core/services/sites.ts
index 8433311e9..0cbb0b27d 100644
--- a/src/core/services/sites.ts
+++ b/src/core/services/sites.ts
@@ -20,7 +20,6 @@ import { CoreApp, CoreStoreConfig } from '@services/app';
import { CoreEvents } from '@singletons/events';
import { CoreWS } from '@services/ws';
import { CoreDomUtils } from '@services/utils/dom';
-import { CoreTextUtils } from '@services/utils/text';
import { CoreUrl, CoreUrlPartNames } from '@singletons/url';
import { CoreUtils } from '@services/utils/utils';
import { CoreConstants } from '@/core/constants';
@@ -67,6 +66,7 @@ import { CoreSiteWSPreSets } from '@classes/sites/authenticated-site';
import { firstValueFrom } from 'rxjs';
import { CoreHTMLClasses } from '@singletons/html-classes';
import { CoreSiteErrorDebug } from '@classes/errors/siteerror';
+import { CoreErrorHelper } from './error-helper';
export const CORE_SITE_SCHEMAS = new InjectionToken('CORE_SITE_SCHEMAS');
export const CORE_SITE_CURRENT_SITE_ID_CONFIG = 'current_site_id';
@@ -315,9 +315,9 @@ export class CoreSitesProvider {
}
// Site doesn't exist. Return the error message.
- if (CoreTextUtils.getErrorMessageFromError(error)) {
+ if (CoreErrorHelper.getErrorMessageFromError(error)) {
throw error;
- } else if (CoreTextUtils.getErrorMessageFromError(secondError)) {
+ } else if (CoreErrorHelper.getErrorMessageFromError(secondError)) {
throw secondError;
} else {
throw new CoreError(Translate.instant('core.sitenotfoundhelp'));
@@ -361,7 +361,7 @@ export class CoreSitesProvider {
}
// App didn't receive a WS response, probably cannot connect. Prioritize first error if it's valid.
- if (CoreTextUtils.getErrorMessageFromError(error)) {
+ if (CoreErrorHelper.getErrorMessageFromError(error)) {
throw error;
} else {
throw secondError;
@@ -1256,8 +1256,8 @@ export class CoreSitesProvider {
* @returns Site.
*/
makeSiteFromSiteListEntry(entry: SiteDBEntry): CoreSite {
- const info = entry.info ? CoreTextUtils.parseJSON(entry.info) : undefined;
- const config = entry.config ? CoreTextUtils.parseJSON(entry.config) : undefined;
+ const info = entry.info ? CoreText.parseJSON(entry.info) : undefined;
+ const config = entry.config ? CoreText.parseJSON(entry.config) : undefined;
const site = CoreSitesFactory.makeSite(
entry.id,
@@ -1339,7 +1339,7 @@ export class CoreSitesProvider {
await Promise.all(sites.map(async (site) => {
if (!ids || ids.indexOf(site.id) > -1) {
- const siteInfo = site.info ? CoreTextUtils.parseJSON(site.info) : undefined;
+ const siteInfo = site.info ? CoreText.parseJSON(site.info) : undefined;
const siteInstance = CoreSitesFactory.makeSite(site.id, site.siteUrl, site.token, { info: siteInfo });
const siteName = await siteInstance.getSiteName();
@@ -1377,8 +1377,8 @@ export class CoreSitesProvider {
// Sort sites by site name, url and then fullname.
sites.sort((a, b) => {
// First compare by site name.
- let textA = CoreTextUtils.cleanTags(a.siteName).toLowerCase().trim();
- let textB = CoreTextUtils.cleanTags(b.siteName).toLowerCase().trim();
+ let textA = CoreText.cleanTags(a.siteName).toLowerCase().trim();
+ let textB = CoreText.cleanTags(b.siteName).toLowerCase().trim();
let compare = textA.localeCompare(textB);
if (compare !== 0) {
diff --git a/src/core/services/tests/utils/text.test.ts b/src/core/services/tests/utils/text.test.ts
deleted file mode 100644
index 793546903..000000000
--- a/src/core/services/tests/utils/text.test.ts
+++ /dev/null
@@ -1,156 +0,0 @@
-// (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 { CoreTextUtilsProvider } from '@services/utils/text';
-import { DomSanitizer } from '@singletons';
-
-import { mockSingleton } from '@/testing/utils';
-import { CorePlatform } from '@services/platform';
-
-describe('CoreTextUtilsProvider', () => {
-
- const config = { platform: 'android' };
- let textUtils: CoreTextUtilsProvider;
-
- beforeEach(() => {
- mockSingleton(CorePlatform, [], { isAndroid: () => config.platform === 'android' });
- mockSingleton(DomSanitizer, [], { bypassSecurityTrustUrl: url => url });
-
- textUtils = new CoreTextUtilsProvider();
- });
-
- it('adds ending slashes', () => {
- const originalUrl = 'https://moodle.org';
- const url = textUtils.addEndingSlash(originalUrl);
-
- expect(url).toEqual('https://moodle.org/');
- });
-
- it('doesn\'t add duplicated ending slashes', () => {
- const originalUrl = 'https://moodle.org/';
- const url = textUtils.addEndingSlash(originalUrl);
-
- expect(url).toEqual('https://moodle.org/');
- });
-
- it('builds address URL for Android platforms', () => {
- // Arrange
- const address = 'Moodle Spain HQ';
-
- config.platform = 'android';
-
- // Act
- const url = textUtils.buildAddressURL(address);
-
- // Assert
- expect(url).toEqual('geo:0,0?q=Moodle%20Spain%20HQ');
-
- expect(DomSanitizer.bypassSecurityTrustUrl).toHaveBeenCalled();
- expect(CorePlatform.isAndroid).toHaveBeenCalled();
- });
-
- it('builds address URL for non-Android platforms', () => {
- // Arrange
- const address = 'Moodle Spain HQ';
-
- config.platform = 'ios';
-
- // Act
- const url = textUtils.buildAddressURL(address);
-
- // Assert
- expect(url).toEqual('http://maps.google.com?q=Moodle%20Spain%20HQ');
-
- expect(DomSanitizer.bypassSecurityTrustUrl).toHaveBeenCalled();
- expect(CorePlatform.isAndroid).toHaveBeenCalled();
- });
-
- it('doesn\'t build address if it\'s already a URL', () => {
- const address = 'https://moodle.org';
-
- const url = textUtils.buildAddressURL(address);
-
- expect(url).toEqual(address);
-
- expect(DomSanitizer.bypassSecurityTrustUrl).toHaveBeenCalled();
- });
-
- it('matches glob patterns', () => {
- expect(textUtils.matchesGlob('/foo/bar', '/foo/bar')).toBe(true);
- expect(textUtils.matchesGlob('/foo/bar', '/foo/bar/')).toBe(false);
- expect(textUtils.matchesGlob('/foo', '/foo/*')).toBe(false);
- expect(textUtils.matchesGlob('/foo/', '/foo/*')).toBe(true);
- expect(textUtils.matchesGlob('/foo/bar', '/foo/*')).toBe(true);
- expect(textUtils.matchesGlob('/foo/bar/', '/foo/*')).toBe(false);
- expect(textUtils.matchesGlob('/foo/bar/baz', '/foo/*')).toBe(false);
- expect(textUtils.matchesGlob('/foo/bar/baz', '/foo/**')).toBe(true);
- expect(textUtils.matchesGlob('/foo/bar/baz/', '/foo/**')).toBe(true);
- expect(textUtils.matchesGlob('/foo/bar/baz', '**/baz')).toBe(true);
- expect(textUtils.matchesGlob('/foo/bar/baz', '**/bar')).toBe(false);
- expect(textUtils.matchesGlob('/foo/bar/baz', '/foo/ba?/ba?')).toBe(true);
- });
-
- it('replaces arguments', () => {
- // Arrange
- const url = 'http://campus.edu?device={{device}}&version={{version}}';
- const replacements = {
- device: 'iPhone or iPad',
- version: '1.2.3',
- };
-
- // Act
- const replaced = textUtils.replaceArguments(url, replacements, 'uri');
-
- // Assert
- expect(replaced).toEqual('http://campus.edu?device=iPhone%20or%20iPad&version=1.2.3');
- });
-
- it('counts words', () => {
- expect(textUtils.countWords('')).toEqual(0);
- expect(textUtils.countWords('one two three four')).toEqual(4);
- expect(textUtils.countWords('a\'b')).toEqual(1);
- expect(textUtils.countWords('1+1=2')).toEqual(1);
- expect(textUtils.countWords(' one-sided ')).toEqual(1);
- expect(textUtils.countWords('one two')).toEqual(2);
- expect(textUtils.countWords('email@example.com')).toEqual(1);
- expect(textUtils.countWords('first\\part second/part')).toEqual(2);
- expect(textUtils.countWords('one two three four
')).toEqual(4);
- expect(textUtils.countWords('one two three four
')).toEqual(4);
- expect(textUtils.countWords('one two three four
')).toEqual(4);
- expect(textUtils.countWords(' one ... three ')).toEqual(3);
- expect(textUtils.countWords('just...one')).toEqual(1);
- expect(textUtils.countWords(' one & three ')).toEqual(3);
- expect(textUtils.countWords('just&one')).toEqual(1);
- expect(textUtils.countWords('em—dash')).toEqual(2);
- expect(textUtils.countWords('en–dash')).toEqual(2);
- expect(textUtils.countWords('1³ £2 €3.45 $6,789')).toEqual(4);
- expect(textUtils.countWords('ブルース カンベッル')).toEqual(2);
- expect(textUtils.countWords('one two
three four
')).toEqual(4);
- expect(textUtils.countWords('one two
three four
')).toEqual(4);
- expect(textUtils.countWords('one
four.
')).toEqual(4);
- expect(textUtils.countWords('emphas is.
')).toEqual(1);
- expect(textUtils.countWords('emphas is.
')).toEqual(1);
- expect(textUtils.countWords('emphas is.
')).toEqual(1);
- expect(textUtils.countWords('emphas is.
')).toEqual(1);
- expect(textUtils.countWords('one\ntwo')).toEqual(2);
- expect(textUtils.countWords('one\rtwo')).toEqual(2);
- expect(textUtils.countWords('one\ttwo')).toEqual(2);
- expect(textUtils.countWords('one\vtwo')).toEqual(2);
- expect(textUtils.countWords('one\ftwo')).toEqual(2);
- expect(textUtils.countWords('SO4 2- ')).toEqual(1);
- expect(textUtils.countWords('4+4=8 i.e. O(1) a,b,c,d I’m black&blue_really')).toEqual(6);
- expect(textUtils.countWords('a b ')).toEqual(1);
- });
-
-});
diff --git a/src/core/services/urlschemes.ts b/src/core/services/urlschemes.ts
index 530c303c7..74a83f90b 100644
--- a/src/core/services/urlschemes.ts
+++ b/src/core/services/urlschemes.ts
@@ -27,7 +27,7 @@ import { CoreApp } from './app';
import { CoreNavigator, CoreRedirectPayload } from './navigator';
import { CoreSiteCheckResponse, CoreSites } from './sites';
import { CoreDomUtils } from './utils/dom';
-import { CoreTextErrorObject, CoreTextUtils } from './utils/text';
+import { CoreErrorHelper, CoreErrorObject } from './error-helper';
import { CoreUrl } from '@singletons/url';
import { CoreUtils } from './utils/utils';
import { CoreLoadings } from './loadings';
@@ -102,7 +102,7 @@ export class CoreCustomURLSchemesProvider {
}
this.lastUrls[url] = Date.now();
- url = CoreTextUtils.decodeURIComponent(url);
+ url = CoreUrl.decodeURIComponent(url);
// Wait for app to be ready.
await ApplicationInit.donePromise;
@@ -539,8 +539,8 @@ export class CoreCustomURLSchemesHandleError extends CoreError {
* @param error The error message or object.
* @param data Data obtained from the URL (if any).
*/
- constructor(public error: string | CoreError | CoreTextErrorObject | null, public data?: CoreCustomURLSchemesParams) {
- super(CoreTextUtils.getErrorMessageFromError(error));
+ constructor(public error: string | CoreError | CoreErrorObject | null, public data?: CoreCustomURLSchemesParams) {
+ super(CoreErrorHelper.getErrorMessageFromError(error));
}
}
diff --git a/src/core/services/utils/dom.ts b/src/core/services/utils/dom.ts
index f939451db..924ef2b8c 100644
--- a/src/core/services/utils/dom.ts
+++ b/src/core/services/utils/dom.ts
@@ -20,7 +20,7 @@ import { Md5 } from 'ts-md5';
import { CoreConfig } from '@services/config';
import { CoreFile } from '@services/file';
import { CoreWSExternalWarning } from '@services/ws';
-import { CoreTextUtils, CoreTextErrorObject } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreUrl, CoreUrlPartNames } from '@singletons/url';
import { CoreUtils } from '@services/utils/utils';
import { CoreConstants } from '@/core/constants';
@@ -54,6 +54,9 @@ import { CoreModals, OpenModalOptions } from '@services/modals';
import { CorePopovers, OpenPopoverOptions } from '@services/popovers';
import { CoreViewer } from '@features/viewer/services/viewer';
import { CoreLoadings } from '@services/loadings';
+import { CoreErrorHelper, CoreErrorObject } from '@services/error-helper';
+import { convertTextToHTMLElement } from '@/core/utils/create-html-element';
+import { CoreHTMLClasses } from '@singletons/html-classes';
/*
* "Utils" service with helper functions for UI, DOM elements and HTML code.
@@ -67,8 +70,6 @@ export class CoreDomUtilsProvider {
protected readonly INPUT_SUPPORT_KEYBOARD: string[] = ['date', 'datetime', 'datetime-local', 'email', 'month', 'number',
'password', 'search', 'tel', 'text', 'time', 'url', 'week'];
- protected template: HTMLTemplateElement = document.createElement('template'); // A template element to convert HTML to element.
-
protected matchesFunctionName?: string; // Name of the "matches" function to use when simulating a closest call.
protected debugDisplay = false; // Whether to display debug messages. Store it in a variable to make it synchronous.
protected displayedAlerts: Record = {}; // To prevent duplicated alerts.
@@ -109,7 +110,7 @@ export class CoreDomUtilsProvider {
limitedThreshold?: number,
alwaysConfirm?: boolean,
): Promise {
- const readableSize = CoreTextUtils.bytesToSize(size.size, 2);
+ const readableSize = CoreText.bytesToSize(size.size, 2);
const getAvailableBytes = async (): Promise => {
const availableBytes = await CoreFile.calculateFreeSpace();
@@ -131,7 +132,7 @@ export class CoreDomUtilsProvider {
if (availableBytes === null) {
return '';
} else {
- const availableSize = CoreTextUtils.bytesToSize(availableBytes, 2);
+ const availableSize = CoreText.bytesToSize(availableBytes, 2);
if (CorePlatform.isAndroid() && size.size > availableBytes - CoreConstants.MINIMUM_FREE_SPACE) {
throw new CoreError(
@@ -195,12 +196,11 @@ export class CoreDomUtilsProvider {
*
* @param html Text to convert.
* @returns Element.
+ *
+ * @deprecated since 4.5. Use convertTextToHTMLElement directly instead.
*/
convertToElement(html: string): HTMLElement {
- // Add a div to hold the content, that's the element that will be returned.
- this.template.innerHTML = '' + html + '
';
-
- return this.template.content.children[0];
+ return convertTextToHTMLElement(html);
}
/**
@@ -262,24 +262,23 @@ export class CoreDomUtilsProvider {
* @returns Fixed HTML text.
*/
fixHtml(html: string): string {
- this.template.innerHTML = html;
+ return CoreText.processHTML(html, (element) => {
+ // eslint-disable-next-line no-control-regex
+ const attrNameRegExp = /[^\x00-\x20\x7F-\x9F"'>/=]+/;
+ const fixElement = (element: Element): void => {
+ // Remove attributes with an invalid name.
+ Array.from(element.attributes).forEach((attr) => {
+ if (!attrNameRegExp.test(attr.name)) {
+ element.removeAttributeNode(attr);
+ }
+ });
- // eslint-disable-next-line no-control-regex
- const attrNameRegExp = /[^\x00-\x20\x7F-\x9F"'>/=]+/;
- const fixElement = (element: Element): void => {
- // Remove attributes with an invalid name.
- Array.from(element.attributes).forEach((attr) => {
- if (!attrNameRegExp.test(attr.name)) {
- element.removeAttributeNode(attr);
- }
- });
+ Array.from(element.children).forEach(fixElement);
+ };
Array.from(element.children).forEach(fixElement);
- };
+ });
- Array.from(this.template.content.children).forEach(fixElement);
-
- return this.template.innerHTML;
}
/**
@@ -388,7 +387,7 @@ export class CoreDomUtilsProvider {
* @returns Attribute value.
*/
getHTMLElementAttribute(html: string, attribute: string): string | null {
- return this.convertToElement(html).children[0].getAttribute(attribute);
+ return convertTextToHTMLElement(html).children[0].getAttribute(attribute);
}
/**
@@ -409,7 +408,7 @@ export class CoreDomUtilsProvider {
* @param error Error object.
* @returns True if the message error is a network error, false otherwise.
*/
- protected isNetworkError(message: string, error?: CoreError | CoreTextErrorObject | string): boolean {
+ protected isNetworkError(message: string, error?: CoreError | CoreErrorObject | string): boolean {
return message == Translate.instant('core.networkerrormsg') ||
message == Translate.instant('core.fileuploader.errormustbeonlinetoupload') ||
error instanceof CoreNetworkError;
@@ -423,7 +422,7 @@ export class CoreDomUtilsProvider {
*/
protected isSiteUnavailableError(message: string): boolean {
let siteUnavailableMessage = Translate.instant('core.siteunavailablehelp', { site: 'SITEURLPLACEHOLDER' });
- siteUnavailableMessage = CoreTextUtils.escapeForRegex(siteUnavailableMessage);
+ siteUnavailableMessage = CoreText.escapeForRegex(siteUnavailableMessage);
siteUnavailableMessage = siteUnavailableMessage.replace('SITEURLPLACEHOLDER', '.*');
return new RegExp(siteUnavailableMessage).test(message);
@@ -436,7 +435,7 @@ export class CoreDomUtilsProvider {
* @param needsTranslate Whether the error needs to be translated.
* @returns Error message, null if no error should be displayed.
*/
- getErrorMessage(error: CoreError | CoreTextErrorObject | string, needsTranslate?: boolean): string | null {
+ getErrorMessage(error: CoreError | CoreErrorObject | string, needsTranslate?: boolean): string | null {
if (typeof error != 'string' && !error) {
return null;
}
@@ -448,11 +447,11 @@ export class CoreDomUtilsProvider {
if (this.debugDisplay) {
// Get the debug info. Escape the HTML so it is displayed as it is in the view.
if ('debuginfo' in error && error.debuginfo) {
- extraInfo = ' ' + CoreTextUtils.escapeHTML(error.debuginfo, false);
+ extraInfo = ' ' + CoreText.escapeHTML(error.debuginfo, false);
}
if ('backtrace' in error && error.backtrace) {
- extraInfo += ' ' + CoreTextUtils.replaceNewLines(
- CoreTextUtils.escapeHTML(error.backtrace, false),
+ extraInfo += ' ' + CoreText.replaceNewLines(
+ CoreText.escapeHTML(error.backtrace, false),
' ',
);
}
@@ -467,7 +466,7 @@ export class CoreDomUtilsProvider {
}
// We received an object instead of a string. Search for common properties.
- errorMessage = CoreTextUtils.getErrorMessageFromError(error);
+ errorMessage = CoreErrorHelper.getErrorMessageFromError(error);
CoreErrorLogs.addErrorLog({ message: JSON.stringify(error), type: errorMessage || '', time: new Date().getTime() });
if (!errorMessage) {
// No common properties found, just stringify it.
@@ -484,7 +483,7 @@ export class CoreDomUtilsProvider {
errorMessage = error;
}
- let message = CoreTextUtils.decodeHTML(needsTranslate ? Translate.instant(errorMessage) : errorMessage);
+ let message = CoreText.decodeHTML(needsTranslate ? Translate.instant(errorMessage) : errorMessage);
if (extraInfo) {
message += extraInfo;
@@ -649,7 +648,7 @@ export class CoreDomUtilsProvider {
* @returns HTML without the element.
*/
removeElementFromHtml(html: string, selector: string, removeAll?: boolean): string {
- const element = this.convertToElement(html);
+ const element = convertTextToHTMLElement(html);
if (removeAll) {
const selected = element.querySelectorAll(selector);
@@ -697,7 +696,7 @@ export class CoreDomUtilsProvider {
paths: {[url: string]: string},
anchorFn?: (anchor: HTMLElement, href: string) => void,
): string {
- const element = this.convertToElement(html);
+ const element = convertTextToHTMLElement(html);
// Treat elements with src (img, audio, video, ...).
const media = Array.from(element.querySelectorAll('img, video, audio, source, track, iframe, embed'));
@@ -705,7 +704,7 @@ export class CoreDomUtilsProvider {
const currentSrc = media.getAttribute('src');
const newSrc = currentSrc ?
paths[CoreUrl.removeUrlParts(
- CoreTextUtils.decodeURIComponent(currentSrc),
+ CoreUrl.decodeURIComponent(currentSrc),
[CoreUrlPartNames.Query, CoreUrlPartNames.Fragment],
)] :
undefined;
@@ -717,7 +716,7 @@ export class CoreDomUtilsProvider {
// Treat video posters.
const currentPoster = media.getAttribute('poster');
if (media.tagName == 'VIDEO' && currentPoster) {
- const newPoster = paths[CoreTextUtils.decodeURIComponent(currentPoster)];
+ const newPoster = paths[CoreUrl.decodeURIComponent(currentPoster)];
if (newPoster !== undefined) {
media.setAttribute('poster', newPoster);
}
@@ -730,7 +729,7 @@ export class CoreDomUtilsProvider {
const currentHref = anchor.getAttribute('href');
const newHref = currentHref ?
paths[CoreUrl.removeUrlParts(
- CoreTextUtils.decodeURIComponent(currentHref),
+ CoreUrl.decodeURIComponent(currentHref),
[CoreUrlPartNames.Query, CoreUrlPartNames.Fragment],
)] :
undefined;
@@ -838,7 +837,7 @@ export class CoreDomUtilsProvider {
? options.message
: options.message?.value || '';
- const hasHTMLTags = CoreTextUtils.hasHTMLTags(message);
+ const hasHTMLTags = CoreText.hasHTMLTags(message);
if (hasHTMLTags && !CoreSites.getCurrentSite()?.isVersionGreaterEqualThan('3.7')) {
// Treat multilang.
@@ -1022,7 +1021,7 @@ export class CoreDomUtilsProvider {
* @returns Promise resolved with the alert modal.
*/
async showErrorModal(
- error: CoreError | CoreTextErrorObject | string,
+ error: CoreError | CoreErrorObject | string,
needsTranslate?: boolean,
autocloseTime?: number,
): Promise {
@@ -1117,7 +1116,7 @@ export class CoreDomUtilsProvider {
let errorMessage = error || undefined;
if (error && typeof error != 'string') {
- errorMessage = CoreTextUtils.getErrorMessageFromError(error);
+ errorMessage = CoreErrorHelper.getErrorMessageFromError(error);
}
return this.showErrorModal(
@@ -1400,7 +1399,7 @@ export class CoreDomUtilsProvider {
* @returns Same text converted to HTMLCollection.
*/
toDom(text: string): HTMLCollection {
- const element = this.convertToElement(text);
+ const element = convertTextToHTMLElement(text);
return element.children;
}
@@ -1611,18 +1610,22 @@ export class CoreDomUtilsProvider {
*
* @param className Class name.
* @returns Whether the CSS class is set.
+ *
+ * @deprecated since 4.5. Use CoreHTMLClasses.hasModeClass instead.
*/
hasModeClass(className: string): boolean {
- return document.documentElement.classList.contains(className);
+ return CoreHTMLClasses.hasModeClass(className);
}
/**
* Get active mode CSS classes.
*
* @returns Mode classes.
+ *
+ * @deprecated since 4.5. Use CoreHTMLClasses.getModeClasses instead.
*/
getModeClasses(): string[] {
- return Array.from(document.documentElement.classList);
+ return CoreHTMLClasses.getModeClasses();
}
/**
@@ -1630,12 +1633,14 @@ export class CoreDomUtilsProvider {
*
* @param className Class name.
* @param enable Whether to add or remove the class.
+ *
+ * @deprecated since 4.5. Use CoreHTMLClasses.toggleModeClass instead.
*/
toggleModeClass(
className: string,
enable = false,
): void {
- document.documentElement.classList.toggle(className, enable);
+ CoreHTMLClasses.toggleModeClass(className, enable);
}
}
diff --git a/src/core/services/utils/text.ts b/src/core/services/utils/text.ts
index 834394e74..78a3b5386 100644
--- a/src/core/services/utils/text.ts
+++ b/src/core/services/utils/text.ts
@@ -16,105 +16,33 @@ import { Injectable } from '@angular/core';
import { SafeUrl } from '@angular/platform-browser';
import { CoreAnyError, CoreError } from '@classes/errors/error';
-import { DomSanitizer, makeSingleton, Translate } from '@singletons';
+import { makeSingleton } from '@singletons';
import { CoreWSFile } from '@services/ws';
-import { Locutus } from '@singletons/locutus';
import { CoreFileHelper } from '@services/file-helper';
import { CoreUrl } from '@singletons/url';
-import { AlertButton } from '@ionic/angular';
-import { CorePath } from '@singletons/path';
-import { CorePlatform } from '@services/platform';
import { CoreDom } from '@singletons/dom';
import { CoreText } from '@singletons/text';
import { CoreViewer, CoreViewerTextOptions } from '@features/viewer/services/viewer';
+import { CoreErrorHelper, CoreErrorObject } from '@services/error-helper';
/**
- * Different type of errors the app can treat.
- */
-export type CoreTextErrorObject = {
- message?: string;
- error?: string;
- content?: string;
- body?: string;
- debuginfo?: string;
- backtrace?: string;
- title?: string;
- buttons?: AlertButton[];
-};
-
-/*
* "Utils" service with helper functions for text.
-*/
+ *
+ * @deprecated since 4.5. Some of the functions have been moved to CoreText but not all of them, check function deprecation message.
+ */
@Injectable({ providedIn: 'root' })
export class CoreTextUtilsProvider {
- // List of regular expressions to convert the old nomenclature to new nomenclature for disabled features.
- protected readonly DISABLED_FEATURES_COMPAT_REGEXPS: { old: RegExp; new: string }[] = [
- { old: /\$mmLoginEmailSignup/g, new: 'CoreLoginEmailSignup' },
- { old: /\$mmSideMenuDelegate/g, new: 'CoreMainMenuDelegate' },
- { old: /\$mmCoursesDelegate/g, new: 'CoreCourseOptionsDelegate' },
- { old: /\$mmUserDelegate/g, new: 'CoreUserDelegate' },
- { old: /\$mmCourseDelegate/g, new: 'CoreCourseModuleDelegate' },
- { old: /_mmCourses/g, new: '_CoreCourses' },
- { old: /_mmaFrontpage/g, new: '_CoreSiteHome' },
- { old: /_mmaGrades/g, new: '_CoreGrades' },
- { old: /_mmaCompetency/g, new: '_AddonCompetency' },
- { old: /_mmaNotifications/g, new: '_AddonNotifications' },
- { old: /_mmaMessages/g, new: '_AddonMessages' },
- { old: /_mmaCalendar/g, new: '_AddonCalendar' },
- { old: /_mmaFiles/g, new: '_AddonPrivateFiles' },
- { old: /_mmaParticipants/g, new: '_CoreUserParticipants' },
- { old: /_mmaCourseCompletion/g, new: '_AddonCourseCompletion' },
- { old: /_mmaNotes/g, new: '_AddonNotes' },
- { old: /_mmaBadges/g, new: '_AddonBadges' },
- { old: /files_privatefiles/g, new: 'AddonPrivateFilesPrivateFiles' },
- { old: /files_sitefiles/g, new: 'AddonPrivateFilesSiteFiles' },
- { old: /files_upload/g, new: 'AddonPrivateFilesUpload' },
- { old: /_mmaModAssign/g, new: '_AddonModAssign' },
- { old: /_mmaModBigbluebuttonbn/g, new: '_AddonModBBB' },
- { old: /_mmaModBook/g, new: '_AddonModBook' },
- { old: /_mmaModChat/g, new: '_AddonModChat' },
- { old: /_mmaModChoice/g, new: '_AddonModChoice' },
- { old: /_mmaModData/g, new: '_AddonModData' },
- { old: /_mmaModFeedback/g, new: '_AddonModFeedback' },
- { old: /_mmaModFolder/g, new: '_AddonModFolder' },
- { old: /_mmaModForum/g, new: '_AddonModForum' },
- { old: /_mmaModGlossary/g, new: '_AddonModGlossary' },
- { old: /_mmaModH5pactivity/g, new: '_AddonModH5PActivity' },
- { old: /_mmaModImscp/g, new: '_AddonModImscp' },
- { old: /_mmaModLabel/g, new: '_AddonModLabel' },
- { old: /_mmaModLesson/g, new: '_AddonModLesson' },
- { old: /_mmaModLti/g, new: '_AddonModLti' },
- { old: /_mmaModPage/g, new: '_AddonModPage' },
- { old: /_mmaModQuiz/g, new: '_AddonModQuiz' },
- { old: /_mmaModResource/g, new: '_AddonModResource' },
- { old: /_mmaModScorm/g, new: '_AddonModScorm' },
- { old: /_mmaModSurvey/g, new: '_AddonModSurvey' },
- { old: /_mmaModUrl/g, new: '_AddonModUrl' },
- { old: /_mmaModWiki/g, new: '_AddonModWiki' },
- { old: /_mmaModWorkshop/g, new: '_AddonModWorkshop' },
- { old: /remoteAddOn_/g, new: 'sitePlugin_' },
- { old: /AddonNotes:addNote/g, new: 'AddonNotes:notes' },
- ];
-
- protected template: HTMLTemplateElement = document.createElement('template'); // A template element to convert HTML to element.
-
/**
* Add ending slash from a path or URL.
*
* @param text Text to treat.
* @returns Treated text.
+ *
+ * @deprecated since 4.5. Use CoreText.addEndingSlash instead.
*/
addEndingSlash(text: string): string {
- if (!text) {
- return '';
- }
-
- if (text.slice(-1) != '/') {
- return text + '/';
- }
-
- return text;
+ return CoreText.addEndingSlash(text);
}
/**
@@ -123,33 +51,14 @@ export class CoreTextUtilsProvider {
* @param error Error message or object.
* @param text Text to add.
* @returns Modified error.
+ *
+ * @deprecated since 4.5. Use CoreErrorHelper.addTextToError instead.
*/
- addTextToError(error: string | CoreError | CoreTextErrorObject | undefined | null, text: string): string | CoreTextErrorObject {
- if (typeof error == 'string') {
- return error + text;
- }
-
- if (error instanceof CoreError) {
- error.message += text;
-
- return error;
- }
-
- if (!error) {
- return text;
- }
-
- if (typeof error.message == 'string') {
- error.message += text;
- } else if (typeof error.error == 'string') {
- error.error += text;
- } else if (typeof error.content == 'string') {
- error.content += text;
- } else if (typeof error.body == 'string') {
- error.body += text;
- }
-
- return error;
+ addTextToError(
+ error: string | CoreError | CoreErrorObject | undefined | null,
+ text: string,
+ ): string | CoreErrorObject {
+ return CoreErrorHelper.addTextToError(error, text);
}
/**
@@ -158,19 +67,11 @@ export class CoreTextUtilsProvider {
* @param error Error message or object.
* @param title Title to add.
* @returns Modified error.
+ *
+ * @deprecated since 4.5. Use CoreErrorHelper.addTitleToError instead.
*/
- addTitleToError(error: string | CoreError | CoreTextErrorObject | undefined | null, title: string): CoreTextErrorObject {
- let improvedError: CoreTextErrorObject = {};
-
- if (typeof error === 'string') {
- improvedError.message = error;
- } else if (error && 'message' in error) {
- improvedError = error;
- }
-
- improvedError.title = improvedError.title || title;
-
- return improvedError;
+ addTitleToError(error: string | CoreError | CoreErrorObject | undefined | null, title: string): CoreErrorObject {
+ return CoreErrorHelper.addTitleToError(error, title);
}
/**
@@ -178,16 +79,11 @@ export class CoreTextUtilsProvider {
*
* @param address The address.
* @returns URL to view the address.
+ *
+ * @deprecated since 4.5. Use CoreUrl.buildAddressURL instead.
*/
buildAddressURL(address: string): SafeUrl {
- const parsedUrl = CoreUrl.parse(address);
- if (parsedUrl?.protocol) {
- // It's already a URL, don't convert it.
- return DomSanitizer.bypassSecurityTrustUrl(address);
- }
-
- return DomSanitizer.bypassSecurityTrustUrl((CorePlatform.isAndroid() ? 'geo:0,0?q=' : 'http://maps.google.com?q=') +
- encodeURIComponent(address));
+ return CoreUrl.buildAddressURL(address);
}
/**
@@ -195,17 +91,11 @@ export class CoreTextUtilsProvider {
*
* @param messages Messages to show.
* @returns Message with all the messages.
+ *
+ * @deprecated since 4.5. Use CoreText.buildMessage instead.
*/
buildMessage(messages: string[]): string {
- let result = '';
-
- messages.forEach((message) => {
- if (message) {
- result += `${message}
`;
- }
- });
-
- return result;
+ return CoreText.buildMessage(messages);
}
/**
@@ -213,31 +103,11 @@ export class CoreTextUtilsProvider {
*
* @param paragraphs List of paragraphs.
* @returns Built message.
+ *
+ * @deprecated since 4.5. Use CoreErrorHelper.buildSeveralParagraphsMessage instead.
*/
- buildSeveralParagraphsMessage(paragraphs: (string | CoreTextErrorObject)[]): string {
- // Filter invalid messages, and convert them to messages in case they're errors.
- const messages: string[] = [];
-
- paragraphs.forEach(paragraph => {
- // If it's an error, get its message.
- const message = this.getErrorMessageFromError(paragraph);
-
- if (paragraph && message) {
- messages.push(message);
- }
- });
-
- if (messages.length < 2) {
- return messages[0] || '';
- }
-
- let builtMessage = messages[0];
-
- for (let i = 1; i < messages.length; i++) {
- builtMessage = Translate.instant('core.twoparagraphs', { p1: builtMessage, p2: messages[i] });
- }
-
- return builtMessage;
+ buildSeveralParagraphsMessage(paragraphs: (string | CoreErrorObject)[]): string {
+ return CoreErrorHelper.buildSeveralParagraphsMessage(paragraphs);
}
/**
@@ -246,30 +116,11 @@ export class CoreTextUtilsProvider {
* @param bytes Number of bytes to convert.
* @param precision Number of digits after the decimal separator.
* @returns Size in human readable format.
+ *
+ * @deprecated since 4.5. Use CoreText.bytesToSize instead.
*/
bytesToSize(bytes: number, precision: number = 2): string {
- if (bytes === undefined || bytes === null || bytes < 0) {
- return Translate.instant('core.notapplicable');
- }
-
- if (precision < 0) {
- precision = 2;
- }
-
- const keys = ['core.sizeb', 'core.sizekb', 'core.sizemb', 'core.sizegb', 'core.sizetb'];
- const units = Translate.instant(keys);
- let pos = 0;
-
- if (bytes >= 1024) {
- while (bytes >= 1024) {
- pos++;
- bytes = bytes / 1024;
- }
- // Round to "precision" decimals if needed.
- bytes = Number(Math.round(parseFloat(bytes + 'e+' + precision)) + 'e-' + precision);
- }
-
- return Translate.instant('core.humanreadablesize', { size: bytes, unit: units[keys[pos]] });
+ return CoreText.bytesToSize(bytes, precision);
}
/**
@@ -278,13 +129,11 @@ export class CoreTextUtilsProvider {
* @param text HTML string.
* @param process Method to process the HTML.
* @returns Processed HTML string.
+ *
+ * @deprecated since 4.5. Use CoreText.processHTML instead.
*/
processHTML(text: string, process: (element: HTMLElement) => unknown): string {
- const element = this.convertToElement(text);
-
- process(element);
-
- return element.innerHTML;
+ return CoreText.processHTML(text, process);
}
/**
@@ -295,36 +144,11 @@ export class CoreTextUtilsProvider {
* @param options.singleLine True if new lines should be removed (all the text in a single line).
* @param options.trim True if text should be trimmed.
* @returns Clean text.
+ *
+ * @deprecated since 4.5. Use CoreText.cleanTags instead.
*/
cleanTags(text: string | undefined, options: { singleLine?: boolean; trim?: boolean } = {}): string {
- if (!text) {
- return '';
- }
-
- // First, we use a regexpr.
- text = text.replace(/(<([^>]+)>)/ig, '');
- // Then, we rely on the browser. We need to wrap the text to be sure is HTML.
- text = this.convertToElement(text).textContent || '';
- // Trim text
- text = options.trim ? text.trim() : text;
- // Recover or remove new lines.
- text = this.replaceNewLines(text, options.singleLine ? ' ' : ' ');
-
- return text;
- }
-
- /**
- * Convert some HTML as text into an HTMLElement. This HTML is put inside a div or a body.
- * This function is the same as in DomUtils, but we cannot use that one because of circular dependencies.
- *
- * @param html Text to convert.
- * @returns Element.
- */
- protected convertToElement(html: string): HTMLElement {
- // Add a div to hold the content, that's the element that will be returned.
- this.template.innerHTML = '' + html + '
';
-
- return this.template.content.children[0];
+ return CoreText.cleanTags(text, options);
}
/**
@@ -333,37 +157,11 @@ export class CoreTextUtilsProvider {
*
* @param text Text to count.
* @returns Number of words.
+ *
+ * @deprecated since 4.5. Use CoreText.countWords instead.
*/
countWords(text?: string | null): number {
- if (!text || typeof text != 'string') {
- return 0;
- }
-
- // 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>|span>|strong>|sub>|sup>|u>)\w+>| | )/ig, '$1 ');
-
- // Now remove HTML tags.
- text = text.replace(/(<([^>]+)>)/ig, '');
- // Decode HTML entities.
- text = this.decodeHTMLEntities(text);
-
- // 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;
+ return CoreText.countWords(text);
}
/**
@@ -371,21 +169,11 @@ export class CoreTextUtilsProvider {
*
* @param text Text to decode.
* @returns Decoded text.
+ *
+ * @deprecated since 4.5. Use CoreText.decodeHTML instead.
*/
decodeHTML(text: string | number): string {
- if (text === undefined || text === null || (typeof text == 'number' && isNaN(text))) {
- return '';
- } else if (typeof text != 'string') {
- return '' + text;
- }
-
- return text
- .replace(/&/g, '&')
- .replace(/</g, '<')
- .replace(/>/g, '>')
- .replace(/"/g, '"')
- .replace(/'/g, '\'')
- .replace(/ /g, ' ');
+ return CoreText.decodeHTML(text);
}
/**
@@ -393,13 +181,11 @@ export class CoreTextUtilsProvider {
*
* @param text Text to decode.
* @returns Decoded text.
+ *
+ * @deprecated since 4.5. Use CoreText.decodeHTMLEntities instead.
*/
decodeHTMLEntities(text: string): string {
- if (text) {
- text = this.convertToElement(text).textContent || '';
- }
-
- return text;
+ return CoreText.decodeHTMLEntities(text);
}
/**
@@ -407,15 +193,11 @@ export class CoreTextUtilsProvider {
*
* @param uri URI to decode.
* @returns Decoded URI, or original URI if an exception is thrown.
+ *
+ * @deprecated since 4.5. Use CoreUrl.decodeURI instead.
*/
decodeURI(uri: string): string {
- try {
- return decodeURI(uri);
- } catch (ex) {
- // Error, use the original URI.
- }
-
- return uri;
+ return CoreUrl.decodeURI(uri);
}
/**
@@ -423,15 +205,11 @@ export class CoreTextUtilsProvider {
*
* @param uri URI to decode.
* @returns Decoded URI, or original URI if an exception is thrown.
+ *
+ * @deprecated since 4.5. Use CoreUrl.decodeURIComponent instead.
*/
decodeURIComponent(uri: string): string {
- try {
- return decodeURIComponent(uri);
- } catch (ex) {
- // Error, use the original URI.
- }
-
- return uri;
+ return CoreUrl.decodeURIComponent(uri);
}
/**
@@ -439,13 +217,11 @@ export class CoreTextUtilsProvider {
*
* @param text Text to escape.
* @returns Escaped text.
+ *
+ * @deprecated since 4.5. Use CoreText.escapeForRegex instead.
*/
escapeForRegex(text: string): string {
- if (!text || typeof text != 'string') {
- return '';
- }
-
- return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
+ return CoreText.escapeForRegex(text);
}
/**
@@ -454,25 +230,11 @@ export class CoreTextUtilsProvider {
* @param text Text to escape.
* @param doubleEncode If false, it will not convert existing html entities. Defaults to true.
* @returns Escaped text.
+ *
+ * @deprecated since 4.5. Use CoreText.escapeHTML instead.
*/
- escapeHTML(text?: string | number | null, doubleEncode: boolean = true): string {
- if (text === undefined || text === null || (typeof text == 'number' && isNaN(text))) {
- return '';
- } else if (typeof text != 'string') {
- return '' + text;
- }
-
- if (doubleEncode) {
- text = text.replace(/&/g, '&');
- } else {
- text = text.replace(/&(?!amp;)(?!lt;)(?!gt;)(?!quot;)(?!#039;)/g, '&');
- }
-
- return text
- .replace(//g, '>')
- .replace(/"/g, '"')
- .replace(/'/g, ''');
+ escapeHTML(text?: string | number | null, doubleEncode = true): string {
+ return CoreText.escapeHTML(text, doubleEncode);
}
/**
@@ -480,20 +242,11 @@ export class CoreTextUtilsProvider {
*
* @param text Text to format.
* @returns Formatted text.
+ *
+ * @deprecated since 4.5. Use CoreText.formatHtmlLines instead.
*/
formatHtmlLines(text: string): string {
- const hasHTMLTags = this.hasHTMLTags(text);
- if (text.indexOf('') == -1) {
- // Wrap the text in
tags.
- text = '
' + text + '
';
- }
-
- if (!hasHTMLTags) {
- // The text doesn't have HTML, replace new lines for .
- return this.replaceNewLines(text, ' ');
- }
-
- return text;
+ return CoreText.formatHtmlLines(text);
}
/**
@@ -501,46 +254,11 @@ export class CoreTextUtilsProvider {
*
* @param error Error.
* @returns Error message, undefined if not found.
+ *
+ * @deprecated since 4.5. Use CoreErrorHelper.getErrorMessageFromError instead.
*/
getErrorMessageFromError(error?: CoreAnyError): string | undefined {
- if (typeof error === 'string') {
- return error;
- }
-
- if (error instanceof CoreError) {
- return error.message;
- }
-
- if (!error) {
- return undefined;
- }
-
- if (error.message || error.error || error.content) {
- return error.message || error.error || error.content;
- }
-
- if (error.body) {
- return this.getErrorMessageFromHTML(error.body);
- }
-
- return undefined;
- }
-
- /**
- * Get the error message from an HTML error page.
- *
- * @param body HTML content.
- * @returns Error message or empty string if not found.
- */
- getErrorMessageFromHTML(body: string): string {
- // THe parser does not throw errors and scripts are not executed.
- const parser = new DOMParser();
- const doc = parser.parseFromString(body, 'text/html');
-
- // Errors are rendered using the "errorbox" and "errormessage" classes since Moodle 2.0.
- const element = doc.body.querySelector('.errorbox .errormessage');
-
- return element?.innerText.trim() ?? '';
+ return CoreErrorHelper.getErrorMessageFromError(error);
}
/**
@@ -548,11 +266,11 @@ export class CoreTextUtilsProvider {
*
* @param html HTML text.
* @returns Body HTML.
+ *
+ * @deprecated since 4.5. Use CoreDom.getHTMLBodyContent instead.
*/
getHTMLBodyContent(html: string): string {
- const matches = html.match(/([\s\S]*)<\/body>/im);
-
- return matches?.[1] ?? html;
+ return CoreDom.getHTMLBodyContent(html);
}
/**
@@ -560,16 +278,11 @@ export class CoreTextUtilsProvider {
*
* @param files Files to extract the URL from. They need to have the URL in a 'url' or 'fileurl' attribute.
* @returns Pluginfile URL, undefined if no files found.
+ *
+ * @deprecated since 4.5. Use CoreFileHelper.getTextPluginfileUrl instead.
*/
getTextPluginfileUrl(files: CoreWSFile[]): string | undefined {
- if (files?.length) {
- const url = CoreFileHelper.getFileUrl(files[0]);
-
- // Remove text after last slash (encoded or not).
- return url?.substring(0, Math.max(url.lastIndexOf('/'), url.lastIndexOf('%2F')));
- }
-
- return undefined;
+ return CoreFileHelper.getTextPluginfileUrl(files);
}
/**
@@ -577,9 +290,11 @@ export class CoreTextUtilsProvider {
*
* @param text Text to check.
* @returns Whether it has HTML tags.
+ *
+ * @deprecated since 4.5. Use CoreText.hasHTMLTags instead.
*/
hasHTMLTags(text: string): boolean {
- return /<[a-z][\s\S]*>/i.test(text);
+ return CoreText.hasHTMLTags(text);
}
/**
@@ -588,17 +303,11 @@ export class CoreTextUtilsProvider {
* @param text Full text.
* @param searchText Text to search and highlight.
* @returns Highlighted text.
+ *
+ * @deprecated since 4.5. Use CoreText.highlightText instead.
*/
highlightText(text: string, searchText: string): string {
- if (!text || typeof text != 'string') {
- return '';
- } else if (!searchText) {
- return text;
- }
-
- const regex = new RegExp('(' + searchText + ')', 'gi');
-
- return text.replace(regex, '$1 ');
+ return CoreText.highlightText(text, searchText);
}
/**
@@ -606,15 +315,11 @@ export class CoreTextUtilsProvider {
*
* @param content HTML content.
* @returns True if the string does not contain actual content: text, images, etc.
+ *
+ * @deprecated since 4.5. Use CoreDom.htmlIsBlank instead.
*/
htmlIsBlank(content: string): boolean {
- if (!content) {
- return true;
- }
-
- this.template.innerHTML = content;
-
- return !CoreDom.elementHasContent(this.template.content);
+ return CoreDom.htmlIsBlank(content);
}
/**
@@ -623,15 +328,11 @@ export class CoreTextUtilsProvider {
*
* @param text Text to check.
* @returns True if has Unicode chars, false otherwise.
+ *
+ * @deprecated since 4.5. Use CoreText.hasUnicode instead.
*/
hasUnicode(text: string): boolean {
- for (let x = 0; x < text.length; x++) {
- if (text.charCodeAt(x) > 55295) {
- return true;
- }
- }
-
- return false;
+ return CoreText.hasUnicode(text);
}
/**
@@ -639,23 +340,11 @@ export class CoreTextUtilsProvider {
*
* @param data Object to be checked.
* @returns If the data has any long Unicode char on it.
+ *
+ * @deprecated since 4.5. Use CoreText.hasUnicodeData instead.
*/
hasUnicodeData(data: Record): boolean {
- for (const el in data) {
- if (typeof data[el] == 'object') {
- if (this.hasUnicodeData(data[el] as Record)) {
- return true;
- }
-
- continue;
- }
-
- if (typeof data[el] == 'string' && this.hasUnicode(data[el] as string)) {
- return true;
- }
- }
-
- return false;
+ return CoreText.hasUnicodeData(data);
}
/**
@@ -664,21 +353,11 @@ export class CoreTextUtilsProvider {
* @param text Text to match against.
* @param pattern Glob pattern.
* @returns Whether the pattern matches.
+ *
+ * @deprecated since 4.5. Use CoreText.matchesGlob instead.
*/
matchesGlob(text: string, pattern: string): boolean {
- pattern = pattern
- .replace(/\*\*/g, '%RECURSIVE_MATCH%')
- .replace(/\*/g, '%LOCAL_MATCH%')
- .replace(/\?/g, '%CHARACTER_MATCH%');
-
- pattern = this.escapeForRegex(pattern);
-
- pattern = pattern
- .replace(/%RECURSIVE_MATCH%/g, '.*')
- .replace(/%LOCAL_MATCH%/g, '[^/]*')
- .replace(/%CHARACTER_MATCH%/g, '[^/]');
-
- return new RegExp(`^${pattern}$`).test(text);
+ return CoreText.matchesGlob(text, pattern);
}
/**
@@ -688,23 +367,11 @@ export class CoreTextUtilsProvider {
* @param defaultValue Default value to return if the parse fails. Defaults to the original value.
* @param logErrorFn An error to call with the exception to log the error. If not supplied, no error.
* @returns JSON parsed as object or what it gets.
+ *
+ * @deprecated since 4.5. Use CoreText.parseJSON instead.
*/
parseJSON(json: string, defaultValue?: T, logErrorFn?: (error?: Error) => void): T {
- try {
- return JSON.parse(json);
- } catch (error) {
- // Error, log the error if needed.
- if (logErrorFn) {
- logErrorFn(error);
- }
- }
-
- // Error parsing, return the default value or the original value.
- if (defaultValue !== undefined) {
- return defaultValue;
- }
-
- throw new CoreError('JSON cannot be parsed and not default value has been provided') ;
+ return CoreText.parseJSON(json, defaultValue, logErrorFn);
}
/**
@@ -712,13 +379,11 @@ export class CoreTextUtilsProvider {
*
* @param text Text to treat.
* @returns Treated text.
+ *
+ * @deprecated since 4.5. Use CoreText.removeSpecialCharactersForFiles instead.
*/
removeSpecialCharactersForFiles(text: string): string {
- if (!text || typeof text != 'string') {
- return '';
- }
-
- return text.replace(/[#:/?\\]+/g, '_');
+ return CoreText.removeSpecialCharactersForFiles(text);
}
/**
@@ -728,19 +393,11 @@ export class CoreTextUtilsProvider {
* @param replacements Argument values.
* @param encoding Encoding to use in values.
* @returns Treated text.
+ *
+ * @deprecated since 4.5. Use CoreText.replaceArguments instead.
*/
replaceArguments(text: string, replacements: Record = {}, encoding?: 'uri'): string {
- let match: RegExpMatchArray | null = null;
-
- while ((match = text.match(/\{\{([^}]+)\}\}/))) {
- const argument = match[1].trim();
- const value = replacements[argument] ?? '';
- const encodedValue = encoding ? encodeURIComponent(value) : value;
-
- text = text.replace(`{{${argument}}}`, encodedValue);
- }
-
- return text;
+ return CoreText.replaceArguments(text, replacements, encoding);
}
/**
@@ -749,13 +406,11 @@ export class CoreTextUtilsProvider {
* @param text The text to be treated.
* @param newValue Text to use instead of new lines.
* @returns Treated text.
+ *
+ * @deprecated since 4.5. Use CoreText.replaceNewLines instead.
*/
replaceNewLines(text: string, newValue: string): string {
- if (!text || typeof text != 'string') {
- return '';
- }
-
- return text.replace(/(?:\r\n|\r|\n)/g, newValue);
+ return CoreText.replaceNewLines(text, newValue);
}
/**
@@ -765,57 +420,15 @@ export class CoreTextUtilsProvider {
* @param text Text to treat, including draftfile URLs.
* @param files List of files of the area, using pluginfile URLs.
* @returns Treated text and map with the replacements.
+ *
+ * @deprecated since 4.5. Use CoreFileHelper.replaceDraftfileUrls instead.
*/
replaceDraftfileUrls(
siteUrl: string,
text: string,
files: CoreWSFile[],
): { text: string; replaceMap?: {[url: string]: string} } {
-
- if (!text || !files || !files.length) {
- return { text };
- }
-
- const draftfileUrl = CorePath.concatenatePaths(siteUrl, 'draftfile.php');
- const matches = text.match(new RegExp(this.escapeForRegex(draftfileUrl) + '[^\'" ]+', 'ig'));
-
- if (!matches || !matches.length) {
- return { text };
- }
-
- // Index the pluginfile URLs by file name.
- const pluginfileMap: {[name: string]: string} = {};
- files.forEach((file) => {
- if (!file.filename) {
- return;
- }
- pluginfileMap[file.filename] = CoreFileHelper.getFileUrl(file);
- });
-
- // Replace each draftfile with the corresponding pluginfile URL.
- const replaceMap: {[url: string]: string} = {};
- matches.forEach((url) => {
- if (replaceMap[url]) {
- // URL already treated, same file embedded more than once.
- return;
- }
-
- // Get the filename from the URL.
- let filename = url.substring(url.lastIndexOf('/') + 1);
- if (filename.indexOf('?') != -1) {
- filename = filename.substring(0, filename.indexOf('?'));
- }
-
- if (pluginfileMap[filename]) {
- replaceMap[url] = pluginfileMap[filename];
- text = text.replace(new RegExp(this.escapeForRegex(url), 'g'), pluginfileMap[filename]);
- }
- });
-
- return {
- text,
- replaceMap,
- };
+ return CoreFileHelper.replaceDraftfileUrls(siteUrl, text, files);
}
/**
@@ -824,16 +437,11 @@ export class CoreTextUtilsProvider {
* @param text to treat.
* @param files Files to extract the pluginfile URL from. They need to have the URL in a url or fileurl attribute.
* @returns Treated text.
+ *
+ * @deprecated since 4.5. Use CoreFileHelper.replacePluginfileUrls instead.
*/
replacePluginfileUrls(text: string, files: CoreWSFile[]): string {
- if (text && typeof text == 'string') {
- const fileURL = this.getTextPluginfileUrl(files);
- if (fileURL) {
- return text.replace(/@@PLUGINFILE@@/g, fileURL);
- }
- }
-
- return text;
+ return CoreFileHelper.replacePluginfileUrls(text, files);
}
/**
@@ -844,33 +452,11 @@ export class CoreTextUtilsProvider {
* @param originalText Original text.
* @param files List of files to search and replace.
* @returns Treated text.
+ *
+ * @deprecated since 4.5. Use CoreFileHelper.restoreDraftfileUrls instead.
*/
restoreDraftfileUrls(siteUrl: string, treatedText: string, originalText: string, files: CoreWSFile[]): string {
- if (!treatedText || !files || !files.length) {
- return treatedText;
- }
-
- const draftfileUrl = CorePath.concatenatePaths(siteUrl, 'draftfile.php');
- const draftfileUrlRegexPrefix = this.escapeForRegex(draftfileUrl) + '/[^/]+/[^/]+/[^/]+/[^/]+/';
-
- files.forEach((file) => {
- if (!file.filename) {
- return;
- }
-
- // Search the draftfile URL in the original text.
- const matches = originalText.match(
- new RegExp(draftfileUrlRegexPrefix + this.escapeForRegex(file.filename) + '[^\'" ]*', 'i'),
- );
-
- if (!matches || !matches[0]) {
- return; // Original URL not found, skip.
- }
-
- treatedText = treatedText.replace(new RegExp(this.escapeForRegex(CoreFileHelper.getFileUrl(file)), 'g'), matches[0]);
- });
-
- return treatedText;
+ return CoreFileHelper.restoreDraftfileUrls(siteUrl, treatedText, originalText, files);
}
/**
@@ -879,16 +465,11 @@ export class CoreTextUtilsProvider {
* @param text Text to treat.
* @param files Files to extract the pluginfile URL from. They need to have the URL in a url or fileurl attribute.
* @returns Treated text.
+ *
+ * @deprecated since 4.5. Use CoreFileHelper.restorePluginfileUrls instead.
*/
restorePluginfileUrls(text: string, files: CoreWSFile[]): string {
- if (text && typeof text == 'string') {
- const fileURL = this.getTextPluginfileUrl(files);
- if (fileURL) {
- return text.replace(new RegExp(this.escapeForRegex(fileURL), 'g'), '@@PLUGINFILE@@');
- }
- }
-
- return text;
+ return CoreFileHelper.restorePluginfileUrls(text, files);
}
/**
@@ -900,11 +481,11 @@ export class CoreTextUtilsProvider {
* @param num Number to round.
* @param decimals Number of decimals. By default, 2.
* @returns Rounded number.
+ *
+ * @deprecated since 4.5. Use CoreText.roundToDecimals instead.
*/
roundToDecimals(num: number, decimals: number = 2): number {
- const multiplier = Math.pow(10, decimals);
-
- return Math.round(num * multiplier) / multiplier;
+ return CoreText.roundToDecimals(num, decimals);
}
/**
@@ -915,13 +496,11 @@ export class CoreTextUtilsProvider {
*
* @param text Text to treat.
* @returns Treated text.
+ *
+ * @deprecated since 4.5. Use CoreText.s instead.
*/
s(text: string): string {
- if (!text) {
- return '';
- }
-
- return this.escapeHTML(text).replace(/&#(\d+|x[0-9a-f]+);/i, '$1;');
+ return CoreText.s(text);
}
/**
@@ -930,20 +509,11 @@ export class CoreTextUtilsProvider {
* @param text The text to be shortened.
* @param length The desired length.
* @returns Shortened text.
+ *
+ * @deprecated since 4.5. Use CoreText.shortenText instead.
*/
shortenText(text: string, length: number): string {
- if (text.length > length) {
- text = text.substring(0, length);
-
- // Now, truncate at the last word boundary (if exists).
- const lastWordPos = text.lastIndexOf(' ');
- if (lastWordPos > 0) {
- text = text.substring(0, lastWordPos);
- }
- text += '…';
- }
-
- return text;
+ return CoreText.shortenText(text, length);
}
/**
@@ -952,16 +522,11 @@ export class CoreTextUtilsProvider {
*
* @param text Text to check.
* @returns Without the Unicode chars.
+ *
+ * @deprecated since 4.5. Use CoreText.stripUnicode instead.
*/
stripUnicode(text: string): string {
- let stripped = '';
- for (let x = 0; x < text.length; x++) {
- if (text.charCodeAt(x) <= 55295) {
- stripped += text.charAt(x);
- }
- }
-
- return stripped;
+ return CoreText.stripUnicode(text);
}
/**
@@ -973,9 +538,11 @@ export class CoreTextUtilsProvider {
* @param length Length of the portion of string which is to be replaced. If negative, it represents the number of characters
* from the end of string at which to stop replacing. If not provided, replace until the end of the string.
* @returns Treated string.
+ *
+ * @deprecated since 4.5. Use CoreText.substrReplace instead.
*/
substrReplace(str: string, replace: string, start: number, length?: number): string {
- return Locutus.substrReplace(str, replace, start, length);
+ return CoreText.substrReplace(str, replace, start, length);
}
/**
@@ -983,18 +550,10 @@ export class CoreTextUtilsProvider {
*
* @param features List of disabled features.
* @returns Treated list.
+ *
+ * @deprecated since 4.5. Shoudn't be used since disabled features are not treated by this function anymore.
*/
treatDisabledFeatures(features: string): string {
- if (!features) {
- return '';
- }
-
- for (let i = 0; i < this.DISABLED_FEATURES_COMPAT_REGEXPS.length; i++) {
- const entry = this.DISABLED_FEATURES_COMPAT_REGEXPS[i];
-
- features = features.replace(entry.old, entry.new);
- }
-
return features;
}
@@ -1004,12 +563,11 @@ export class CoreTextUtilsProvider {
* @param text Text to treat.
* @param character Character to remove.
* @returns Treated text.
+ *
+ * @deprecated since 4.5. Use CoreText.trimCharacter instead.
*/
trimCharacter(text: string, character: string): string {
- const escaped = this.escapeForRegex(character);
- const regExp = new RegExp(`^${escaped}+|${escaped}+$`, 'g');
-
- return text.replace(regExp, '');
+ return CoreText.trimCharacter(text, character);
}
/**
@@ -1017,13 +575,11 @@ export class CoreTextUtilsProvider {
*
* @param num Number to convert.
* @returns Number with leading zeros.
+ *
+ * @deprecated since 4.5. Use CoreText.twoDigits instead.
*/
twoDigits(num: string | number): string {
- if (Number(num) < 10) {
- return '0' + num;
- } else {
- return '' + num; // Convert to string for coherence.
- }
+ return CoreText.twoDigits(num);
}
/**
@@ -1042,9 +598,11 @@ export class CoreTextUtilsProvider {
*
* @param data String to unserialize.
* @returns Unserialized data.
+ *
+ * @deprecated since 4.5. Use CoreText.unserialize instead.
*/
unserialize(data: string): T {
- return Locutus.unserialize(data);
+ return CoreText.unserialize(data);
}
/**
@@ -1061,24 +619,5 @@ export class CoreTextUtilsProvider {
}
}
+// eslint-disable-next-line deprecation/deprecation
export const CoreTextUtils = makeSingleton(CoreTextUtilsProvider);
-
-/**
- * Options for viewText.
- *
- * @deprecated since 4.5. Use CoreViewerTextOptions instead.
- */
-export type CoreTextUtilsViewTextOptions = CoreViewerTextOptions;
-
-/**
- * Define text formatting types.
- */
-export enum CoreTextFormat {
- FORMAT_MOODLE = 0, // Does all sorts of transformations and filtering.
- FORMAT_HTML = 1, // Plain HTML (with some tags stripped). Use it by default.
- FORMAT_PLAIN = 2, // Plain text (even tags are printed in full).
- // FORMAT_WIKI is deprecated since 2005...
- FORMAT_MARKDOWN = 4, // Markdown-formatted text http://daringfireball.net/projects/markdown/
-}
-
-export const defaultTextFormat = CoreTextFormat.FORMAT_HTML;
diff --git a/src/core/services/utils/utils.ts b/src/core/services/utils/utils.ts
index 3bb3ba823..05fe91cec 100644
--- a/src/core/services/utils/utils.ts
+++ b/src/core/services/utils/utils.ts
@@ -20,7 +20,6 @@ import { CoreFile } from '@services/file';
import { CoreLang, CoreLangFormat } from '@services/lang';
import { CoreWS } from '@services/ws';
import { CoreMimetypeUtils } from '@services/utils/mimetype';
-import { CoreTextUtils } from '@services/utils/text';
import { makeSingleton, InAppBrowser, FileOpener, WebIntent, Translate, NgZone } from '@singletons';
import { CoreLogger } from '@singletons/logger';
import { CoreFileEntry } from '@services/file-helper';
@@ -38,6 +37,7 @@ import { CoreArray } from '@singletons/array';
import { CoreText } from '@singletons/text';
import { CoreWait, CoreWaitOptions } from '@singletons/wait';
import { CoreQRScan } from '@services/qrscan';
+import { CoreErrorHelper } from '@services/error-helper';
export type TreeNode = T & { children: TreeNode[] };
@@ -65,7 +65,7 @@ export class CoreUtilsProvider {
* @returns New error message.
*/
addDataNotDownloadedError(error: Error | string, defaultError?: string): string {
- const errorMessage = CoreTextUtils.getErrorMessageFromError(error) || defaultError || '';
+ const errorMessage = CoreErrorHelper.getErrorMessageFromError(error) || defaultError || '';
if (this.isWebServiceError(error)) {
return errorMessage;
diff --git a/src/core/services/ws.ts b/src/core/services/ws.ts
index 004da122c..3b9ce5954 100644
--- a/src/core/services/ws.ts
+++ b/src/core/services/ws.ts
@@ -24,7 +24,7 @@ import { CoreNativeToAngularHttpResponse } from '@classes/native-to-angular-http
import { CoreNetwork } from '@services/network';
import { CoreFile, CoreFileFormat } from '@services/file';
import { CoreMimetypeUtils } from '@services/utils/mimetype';
-import { CoreTextErrorObject, CoreTextUtils } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreConstants } from '@/core/constants';
import { CoreError } from '@classes/errors/error';
import { CoreInterceptor } from '@classes/interceptor';
@@ -43,6 +43,8 @@ import { CoreUserGuestSupportConfig } from '@features/user/classes/support/guest
import { CoreSites } from '@services/sites';
import { CoreLang, CoreLangFormat } from './lang';
import { CoreErrorLogs } from '@singletons/error-logs';
+import { CoreErrorHelper, CoreErrorObject } from './error-helper';
+import { CoreDom } from '@singletons/dom';
/**
* This service allows performing WS calls and download/upload files.
@@ -174,9 +176,9 @@ export class CoreWSProvider {
if (value == null) {
return null;
}
- } else if (typeof value == 'string') {
+ } else if (typeof value === 'string') {
if (stripUnicode) {
- const stripped = CoreTextUtils.stripUnicode(value);
+ const stripped = CoreText.stripUnicode(value);
if (stripped != value && stripped.trim().length == 0) {
return null;
}
@@ -532,45 +534,45 @@ export class CoreWSProvider {
options.debug = {
code: 'invalidcertificate',
details: Translate.instant('core.certificaterror', {
- details: CoreTextUtils.getErrorMessageFromError(data.error) ?? 'Invalid certificate',
+ details: CoreErrorHelper.getErrorMessageFromError(data.error) ?? 'Invalid certificate',
}),
};
break;
case NativeHttp.ErrorCode.SERVER_NOT_FOUND:
options.debug = {
code: 'servernotfound',
- details: CoreTextUtils.getErrorMessageFromError(data.error) ?? 'Server could not be found',
+ details: CoreErrorHelper.getErrorMessageFromError(data.error) ?? 'Server could not be found',
};
break;
case NativeHttp.ErrorCode.TIMEOUT:
options.debug = {
code: 'requesttimeout',
- details: CoreTextUtils.getErrorMessageFromError(data.error) ?? 'Request timed out',
+ details: CoreErrorHelper.getErrorMessageFromError(data.error) ?? 'Request timed out',
};
break;
case NativeHttp.ErrorCode.UNSUPPORTED_URL:
options.debug = {
code: 'unsupportedurl',
- details: CoreTextUtils.getErrorMessageFromError(data.error) ?? 'Url not supported',
+ details: CoreErrorHelper.getErrorMessageFromError(data.error) ?? 'Url not supported',
};
break;
case NativeHttp.ErrorCode.NOT_CONNECTED:
options.debug = {
code: 'connectionerror',
- details: CoreTextUtils.getErrorMessageFromError(data.error)
+ details: CoreErrorHelper.getErrorMessageFromError(data.error)
?? 'Connection error, is network available?',
};
break;
case NativeHttp.ErrorCode.ABORTED:
options.debug = {
code: 'requestaborted',
- details: CoreTextUtils.getErrorMessageFromError(data.error) ?? 'Request aborted',
+ details: CoreErrorHelper.getErrorMessageFromError(data.error) ?? 'Request aborted',
};
break;
case NativeHttp.ErrorCode.POST_PROCESSING_FAILED:
options.debug = {
code: 'requestprocessingfailed',
- details: CoreTextUtils.getErrorMessageFromError(data.error) ?? 'Request processing failed',
+ details: CoreErrorHelper.getErrorMessageFromError(data.error) ?? 'Request processing failed',
};
break;
}
@@ -587,7 +589,7 @@ export class CoreWSProvider {
};
break;
default: {
- const details = CoreTextUtils.getErrorMessageFromError(data.error) ?? 'Unknown error';
+ const details = CoreErrorHelper.getErrorMessageFromError(data.error) ?? 'Unknown error';
options.debug = {
code: 'serverconnectionajax',
@@ -836,7 +838,7 @@ export class CoreWSProvider {
debug: {
code: 'invalidcertificate',
details: Translate.instant('core.certificaterror', {
- details: CoreTextUtils.getErrorMessageFromError(error) ?? 'Unknown error',
+ details: CoreErrorHelper.getErrorMessageFromError(error) ?? 'Unknown error',
}),
},
});
@@ -845,7 +847,7 @@ export class CoreWSProvider {
}
throw new CoreError(Translate.instant('core.serverconnection', {
- details: CoreTextUtils.getErrorMessageFromError(error) ?? 'Unknown error',
+ details: CoreErrorHelper.getErrorMessageFromError(error) ?? 'Unknown error',
}));
}).catch(err => {
CoreErrorLogs.addErrorLog({
@@ -965,7 +967,7 @@ export class CoreWSProvider {
}
// Treat response.
- data = CoreTextUtils.parseJSON(data);
+ data = CoreText.parseJSON(data);
// Some moodle web services return null.
// If the responseExpected value is set then so long as no data is returned, we create a blank object.
@@ -1057,7 +1059,7 @@ export class CoreWSProvider {
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
- const data = CoreTextUtils.parseJSON(
+ const data = CoreText.parseJSON(
success.response,
null,
error => this.logger.error('Error parsing response from upload', success.response, error),
@@ -1119,12 +1121,12 @@ export class CoreWSProvider {
* @param status Status code (if any).
* @returns CoreHttpError.
*/
- protected createHttpError(error: CoreTextErrorObject, status: number): CoreHttpError {
- const message = CoreTextUtils.buildSeveralParagraphsMessage([
+ protected createHttpError(error: CoreErrorObject, status: number): CoreHttpError {
+ const message = CoreErrorHelper.buildSeveralParagraphsMessage([
CoreSites.isLoggedIn()
? Translate.instant('core.siteunavailablehelp', { site: CoreSites.getCurrentSite()?.siteUrl })
: Translate.instant('core.sitenotfoundhelp'),
- CoreTextUtils.getHTMLBodyContent(CoreTextUtils.getErrorMessageFromError(error) || ''),
+ CoreDom.getHTMLBodyContent(CoreErrorHelper.getErrorMessageFromError(error) || ''),
]);
return new CoreHttpError(message, status);
diff --git a/src/core/singletons/dom.ts b/src/core/singletons/dom.ts
index 8f962cc87..c35b87ed6 100644
--- a/src/core/singletons/dom.ts
+++ b/src/core/singletons/dom.ts
@@ -17,6 +17,7 @@ import { CoreUtils } from '@services/utils/utils';
import { CoreEventObserver } from '@singletons/events';
import { CorePlatform } from '@services/platform';
import { CoreWait } from './wait';
+import { convertTextToHTMLElement } from '../utils/create-html-element';
/**
* Singleton with helper functions for dom.
@@ -74,6 +75,19 @@ export class CoreDom {
).length > 0;
}
+ /**
+ * Given some HTML code, return the HTML code inside tags. If there are no body tags, return the whole HTML.
+ *
+ * @param html HTML text.
+ * @returns Body HTML.
+ */
+ static getHTMLBodyContent(html: string): string {
+ const doc = new DOMParser().parseFromString(html, 'text/html');
+ const bodyContent = doc.body.innerHTML;
+
+ return bodyContent ?? html;
+ }
+
/**
* Retrieve the position of a element relative to another element.
*
@@ -93,6 +107,22 @@ export class CoreDom {
};
}
+ /**
+ * Check if HTML content is blank.
+ *
+ * @param content HTML content.
+ * @returns True if the string does not contain actual content: text, images, etc.
+ */
+ static htmlIsBlank(content: string): boolean {
+ if (!content) {
+ return true;
+ }
+
+ const element = convertTextToHTMLElement(content);
+
+ return !CoreDom.elementHasContent(element);
+ }
+
/**
* Check whether an element has been added to the DOM.
*
diff --git a/src/core/singletons/html-classes.ts b/src/core/singletons/html-classes.ts
index ff74fda5b..9803f1e19 100644
--- a/src/core/singletons/html-classes.ts
+++ b/src/core/singletons/html-classes.ts
@@ -14,29 +14,31 @@
import { CoreSiteInfo, CoreSiteInfoResponse } from '@classes/sites/unauthenticated-site';
import { CoreSites } from '@services/sites';
-import { CoreDomUtils } from '@services/utils/dom';
import { CoreUrl } from './url';
import { CoreConstants } from '../constants';
import { ScrollDetail } from '@ionic/angular';
import { CoreDom } from './dom';
-const MOODLE_SITE_URL_PREFIX = 'url-';
-const MOODLE_VERSION_PREFIX = 'version-';
-const MOODLEAPP_VERSION_PREFIX = 'moodleapp-';
-const MOODLE_SITE_THEME_PREFIX = 'theme-site-';
-
/**
* Singleton with helper functions to manage HTML classes.
*/
export class CoreHTMLClasses {
+ protected static readonly MOODLE_SITE_URL_PREFIX = 'url-';
+ protected static readonly MOODLE_VERSION_PREFIX = 'version-';
+ protected static readonly MOODLEAPP_VERSION_PREFIX = 'moodleapp-';
+ protected static readonly MOODLE_SITE_THEME_PREFIX = 'theme-site-';
+
/**
* Initialize HTML classes.
*/
static initialize(): void {
- CoreDomUtils.toggleModeClass('ionic8', true);
- CoreDomUtils.toggleModeClass('development', CoreConstants.BUILD.isDevelopment);
- CoreHTMLClasses.addVersionClass(MOODLEAPP_VERSION_PREFIX, CoreConstants.CONFIG.versionname.replace('-dev', ''));
+ CoreHTMLClasses.toggleModeClass('ionic8', true);
+ CoreHTMLClasses.toggleModeClass('development', CoreConstants.BUILD.isDevelopment);
+ CoreHTMLClasses.addVersionClass(
+ CoreHTMLClasses.MOODLEAPP_VERSION_PREFIX,
+ CoreConstants.CONFIG.versionname.replace('-dev', ''),
+ );
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const win = window;
@@ -72,9 +74,9 @@ export class CoreHTMLClasses {
parts[1] = parts[1] || '0';
parts[2] = parts[2] || '0';
- CoreDomUtils.toggleModeClass(prefix + parts[0], true);
- CoreDomUtils.toggleModeClass(prefix + parts[0] + '-' + parts[1], true);
- CoreDomUtils.toggleModeClass(prefix + parts[0] + '-' + parts[1] + '-' + parts[2], true);
+ CoreHTMLClasses.toggleModeClass(prefix + parts[0], true);
+ CoreHTMLClasses.toggleModeClass(prefix + parts[0] + '-' + parts[1], true);
+ CoreHTMLClasses.toggleModeClass(prefix + parts[0] + '-' + parts[1] + '-' + parts[2], true);
}
/**
@@ -83,12 +85,12 @@ export class CoreHTMLClasses {
* @param prefixes Prefixes of the class mode to be removed.
*/
protected static removeModeClasses(prefixes: string[]): void {
- for (const modeClass of CoreDomUtils.getModeClasses()) {
+ for (const modeClass of CoreHTMLClasses.getModeClasses()) {
if (!prefixes.some((prefix) => modeClass.startsWith(prefix))) {
continue;
}
- CoreDomUtils.toggleModeClass(modeClass, false);
+ CoreHTMLClasses.toggleModeClass(modeClass, false);
}
}
@@ -101,11 +103,11 @@ export class CoreHTMLClasses {
// Add version classes to html tag.
this.removeSiteClasses();
- this.addVersionClass(MOODLE_VERSION_PREFIX, CoreSites.getReleaseNumber(siteInfo.release || ''));
+ this.addVersionClass(CoreHTMLClasses.MOODLE_VERSION_PREFIX, CoreSites.getReleaseNumber(siteInfo.release || ''));
this.addSiteUrlClass(siteInfo.siteurl);
if (siteInfo.theme) {
- CoreDomUtils.toggleModeClass(MOODLE_SITE_THEME_PREFIX + siteInfo.theme, true);
+ CoreHTMLClasses.toggleModeClass(CoreHTMLClasses.MOODLE_SITE_THEME_PREFIX + siteInfo.theme, true);
}
}
@@ -115,7 +117,11 @@ export class CoreHTMLClasses {
static removeSiteClasses(): void {
// Remove version classes from html tag.
this.removeModeClasses(
- [MOODLE_VERSION_PREFIX, MOODLE_SITE_URL_PREFIX, MOODLE_SITE_THEME_PREFIX],
+ [
+ CoreHTMLClasses.MOODLE_VERSION_PREFIX,
+ CoreHTMLClasses.MOODLE_SITE_URL_PREFIX,
+ CoreHTMLClasses.MOODLE_SITE_THEME_PREFIX,
+ ],
);
}
@@ -157,7 +163,39 @@ export class CoreHTMLClasses {
static addSiteUrlClass(siteUrl: string): void {
const className = this.urlToClassName(siteUrl);
- CoreDomUtils.toggleModeClass(MOODLE_SITE_URL_PREFIX + className, true);
+ CoreHTMLClasses.toggleModeClass(CoreHTMLClasses.MOODLE_SITE_URL_PREFIX + className, true);
+ }
+
+ /**
+ * Check whether a CSS class indicating an app mode is set.
+ *
+ * @param className Class name.
+ * @returns Whether the CSS class is set.
+ */
+ static hasModeClass(className: string): boolean {
+ return document.documentElement.classList.contains(className);
+ }
+
+ /**
+ * Get active mode CSS classes.
+ *
+ * @returns Mode classes.
+ */
+ static getModeClasses(): string[] {
+ return Array.from(document.documentElement.classList);
+ }
+
+ /**
+ * Toggle a CSS class in the root element used to indicate app modes.
+ *
+ * @param className Class name.
+ * @param enable Whether to add or remove the class.
+ */
+ static toggleModeClass(
+ className: string,
+ enable = false,
+ ): void {
+ document.documentElement.classList.toggle(className, enable);
}
}
diff --git a/src/core/singletons/tests/text.test.ts b/src/core/singletons/tests/text.test.ts
index b50f0db7d..f66221be5 100644
--- a/src/core/singletons/tests/text.test.ts
+++ b/src/core/singletons/tests/text.test.ts
@@ -16,6 +16,20 @@ import { CoreText } from '@singletons/text';
describe('CoreText singleton', () => {
+ it('adds ending slashes', () => {
+ const originalUrl = 'https://moodle.org';
+ const url = CoreText.addEndingSlash(originalUrl);
+
+ expect(url).toEqual('https://moodle.org/');
+ });
+
+ it('doesn\'t add duplicated ending slashes', () => {
+ const originalUrl = 'https://moodle.org/';
+ const url = CoreText.addEndingSlash(originalUrl);
+
+ expect(url).toEqual('https://moodle.org/');
+ });
+
it('adds a starting slash if needed', () => {
expect(CoreText.addStartingSlash('')).toEqual('/');
expect(CoreText.addStartingSlash('foo')).toEqual('/foo');
@@ -36,4 +50,71 @@ describe('CoreText singleton', () => {
expect(CoreText.removeStartingSlash('//foo')).toEqual('/foo');
});
+ it('matches glob patterns', () => {
+ expect(CoreText.matchesGlob('/foo/bar', '/foo/bar')).toBe(true);
+ expect(CoreText.matchesGlob('/foo/bar', '/foo/bar/')).toBe(false);
+ expect(CoreText.matchesGlob('/foo', '/foo/*')).toBe(false);
+ expect(CoreText.matchesGlob('/foo/', '/foo/*')).toBe(true);
+ expect(CoreText.matchesGlob('/foo/bar', '/foo/*')).toBe(true);
+ expect(CoreText.matchesGlob('/foo/bar/', '/foo/*')).toBe(false);
+ expect(CoreText.matchesGlob('/foo/bar/baz', '/foo/*')).toBe(false);
+ expect(CoreText.matchesGlob('/foo/bar/baz', '/foo/**')).toBe(true);
+ expect(CoreText.matchesGlob('/foo/bar/baz/', '/foo/**')).toBe(true);
+ expect(CoreText.matchesGlob('/foo/bar/baz', '**/baz')).toBe(true);
+ expect(CoreText.matchesGlob('/foo/bar/baz', '**/bar')).toBe(false);
+ expect(CoreText.matchesGlob('/foo/bar/baz', '/foo/ba?/ba?')).toBe(true);
+ });
+
+ it('replaces arguments', () => {
+ // Arrange
+ const url = 'http://campus.edu?device={{device}}&version={{version}}';
+ const replacements = {
+ device: 'iPhone or iPad',
+ version: '1.2.3',
+ };
+
+ // Act
+ const replaced = CoreText.replaceArguments(url, replacements, 'uri');
+
+ // Assert
+ expect(replaced).toEqual('http://campus.edu?device=iPhone%20or%20iPad&version=1.2.3');
+ });
+
+ it('counts words', () => {
+ expect(CoreText.countWords('')).toEqual(0);
+ expect(CoreText.countWords('one two three four')).toEqual(4);
+ expect(CoreText.countWords('a\'b')).toEqual(1);
+ expect(CoreText.countWords('1+1=2')).toEqual(1);
+ expect(CoreText.countWords(' one-sided ')).toEqual(1);
+ expect(CoreText.countWords('one two')).toEqual(2);
+ expect(CoreText.countWords('email@example.com')).toEqual(1);
+ expect(CoreText.countWords('first\\part second/part')).toEqual(2);
+ expect(CoreText.countWords('one two three four
')).toEqual(4);
+ expect(CoreText.countWords('one two three four
')).toEqual(4);
+ expect(CoreText.countWords('one two three four
')).toEqual(4);
+ expect(CoreText.countWords(' one ... three ')).toEqual(3);
+ expect(CoreText.countWords('just...one')).toEqual(1);
+ expect(CoreText.countWords(' one & three ')).toEqual(3);
+ expect(CoreText.countWords('just&one')).toEqual(1);
+ expect(CoreText.countWords('em—dash')).toEqual(2);
+ expect(CoreText.countWords('en–dash')).toEqual(2);
+ expect(CoreText.countWords('1³ £2 €3.45 $6,789')).toEqual(4);
+ expect(CoreText.countWords('ブルース カンベッル')).toEqual(2);
+ expect(CoreText.countWords('one two
three four
')).toEqual(4);
+ expect(CoreText.countWords('one two
three four
')).toEqual(4);
+ expect(CoreText.countWords('one
four.
')).toEqual(4);
+ expect(CoreText.countWords('emphas is.
')).toEqual(1);
+ expect(CoreText.countWords('emphas is.
')).toEqual(1);
+ expect(CoreText.countWords('emphas is.
')).toEqual(1);
+ expect(CoreText.countWords('emphas is.
')).toEqual(1);
+ expect(CoreText.countWords('one\ntwo')).toEqual(2);
+ expect(CoreText.countWords('one\rtwo')).toEqual(2);
+ expect(CoreText.countWords('one\ttwo')).toEqual(2);
+ expect(CoreText.countWords('one\vtwo')).toEqual(2);
+ expect(CoreText.countWords('one\ftwo')).toEqual(2);
+ expect(CoreText.countWords('SO4 2- ')).toEqual(1);
+ expect(CoreText.countWords('4+4=8 i.e. O(1) a,b,c,d I’m black&blue_really')).toEqual(6);
+ expect(CoreText.countWords('a b ')).toEqual(1);
+ });
+
});
diff --git a/src/core/singletons/tests/url.test.ts b/src/core/singletons/tests/url.test.ts
index 0e4980e05..7903de55f 100644
--- a/src/core/singletons/tests/url.test.ts
+++ b/src/core/singletons/tests/url.test.ts
@@ -12,12 +12,63 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import { mock } from '@/testing/utils';
+import { mock, mockSingleton } from '@/testing/utils';
import { CoreSite } from '@classes/sites/site';
import { CoreUrl, CoreUrlPartNames } from '@singletons/url';
+import { CorePlatform } from '@services/platform';
+import { DomSanitizer } from '@singletons';
describe('CoreUrl singleton', () => {
+ const config = { platform: 'android' };
+
+ beforeEach(() => {
+ mockSingleton(CorePlatform, [], { isAndroid: () => config.platform === 'android' });
+ mockSingleton(DomSanitizer, [], { bypassSecurityTrustUrl: url => url });
+ });
+
+ it('builds address URL for Android platforms', () => {
+ // Arrange
+ const address = 'Moodle Spain HQ';
+
+ config.platform = 'android';
+
+ // Act
+ const url = CoreUrl.buildAddressURL(address);
+
+ // Assert
+ expect(url).toEqual('geo:0,0?q=Moodle%20Spain%20HQ');
+
+ expect(DomSanitizer.bypassSecurityTrustUrl).toHaveBeenCalled();
+ expect(CorePlatform.isAndroid).toHaveBeenCalled();
+ });
+
+ it('builds address URL for non-Android platforms', () => {
+ // Arrange
+ const address = 'Moodle Spain HQ';
+
+ config.platform = 'ios';
+
+ // Act
+ const url = CoreUrl.buildAddressURL(address);
+
+ // Assert
+ expect(url).toEqual('http://maps.google.com?q=Moodle%20Spain%20HQ');
+
+ expect(DomSanitizer.bypassSecurityTrustUrl).toHaveBeenCalled();
+ expect(CorePlatform.isAndroid).toHaveBeenCalled();
+ });
+
+ it('doesn\'t build address if it\'s already a URL', () => {
+ const address = 'https://moodle.org';
+
+ const url = CoreUrl.buildAddressURL(address);
+
+ expect(url).toEqual(address);
+
+ expect(DomSanitizer.bypassSecurityTrustUrl).toHaveBeenCalled();
+ });
+
it('adds www if missing', () => {
const originalUrl = 'https://moodle.org';
const url = CoreUrl.addOrRemoveWWW(originalUrl);
diff --git a/src/core/singletons/text.ts b/src/core/singletons/text.ts
index 0a60c5a48..482ce044a 100644
--- a/src/core/singletons/text.ts
+++ b/src/core/singletons/text.ts
@@ -12,8 +12,11 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import { Clipboard } from '@singletons';
+import { Clipboard, Translate } from '@singletons';
import { CoreToasts } from '@services/toasts';
+import { Locutus } from './locutus';
+import { CoreError } from '@classes/errors/error';
+import { convertTextToHTMLElement } from '../utils/create-html-element';
/**
* Singleton with helper functions for text manipulation.
@@ -25,6 +28,24 @@ export class CoreText {
// Nothing to do.
}
+ /**
+ * Add ending slash from a path or URL.
+ *
+ * @param text Text to treat.
+ * @returns Treated text.
+ */
+ static addEndingSlash(text: string): string {
+ if (!text) {
+ return '';
+ }
+
+ if (text.slice(-1) != '/') {
+ return text + '/';
+ }
+
+ return text;
+ }
+
/**
* Add starting slash to a string if needed.
*
@@ -39,6 +60,391 @@ export class CoreText {
return '/' + text;
}
+ /**
+ * Given a list of sentences, build a message with all of them wrapped in .
+ *
+ * @param messages Messages to show.
+ * @returns Message with all the messages.
+ */
+ static buildMessage(messages: string[]): string {
+ let result = '';
+
+ messages.forEach((message) => {
+ if (message) {
+ result += `
${message}
`;
+ }
+ });
+
+ return result;
+ }
+
+ /**
+ * Convert size in bytes into human readable format
+ *
+ * @param bytes Number of bytes to convert.
+ * @param precision Number of digits after the decimal separator.
+ * @returns Size in human readable format.
+ */
+ static bytesToSize(bytes: number, precision: number = 2): string {
+ if (bytes === undefined || bytes === null || bytes < 0) {
+ return Translate.instant('core.notapplicable');
+ }
+
+ if (precision < 0) {
+ precision = 2;
+ }
+
+ const keys = ['core.sizeb', 'core.sizekb', 'core.sizemb', 'core.sizegb', 'core.sizetb'];
+ const units = Translate.instant(keys);
+ let pos = 0;
+
+ if (bytes >= 1024) {
+ while (bytes >= 1024) {
+ pos++;
+ bytes = bytes / 1024;
+ }
+ // Round to "precision" decimals if needed.
+ bytes = Number(Math.round(parseFloat(bytes + 'e+' + precision)) + 'e-' + precision);
+ }
+
+ return Translate.instant('core.humanreadablesize', { size: bytes, unit: units[keys[pos]] });
+ }
+
+ /**
+ * Copies a text to clipboard and shows a toast message.
+ *
+ * @param text Text to be copied
+ */
+ static async copyToClipboard(text: string): Promise {
+ try {
+ await Clipboard.copy(text);
+ } catch {
+ // Use HTML Copy command.
+ const virtualInput = document.createElement('textarea');
+ virtualInput.innerHTML = text;
+ virtualInput.select();
+ virtualInput.setSelectionRange(0, 99999);
+ document.execCommand('copy'); // eslint-disable-line deprecation/deprecation
+ }
+
+ // Show toast using ionicLoading.
+ CoreToasts.show({
+ message: 'core.copiedtoclipboard',
+ translateMessage: true,
+ });
+ }
+
+ /**
+ * Count words in a text.
+ * This function is based on Moodle's count_words.
+ *
+ * @param text Text to count.
+ * @returns Number of words.
+ */
+ static countWords(text?: string | null): number {
+ if (!text || typeof text != 'string') {
+ return 0;
+ }
+
+ // 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>|span>|strong>|sub>|sup>|u>)\w+>| | )/ig, '$1 ');
+
+ // Now remove HTML tags.
+ text = text.replace(/(<([^>]+)>)/ig, '');
+ // Decode HTML entities.
+ text = CoreText.decodeHTMLEntities(text);
+
+ // 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;
+ }
+
+ /**
+ * Clean HTML tags.
+ *
+ * @param text The text to be cleaned.
+ * @param options Processing options.
+ * @param options.singleLine True if new lines should be removed (all the text in a single line).
+ * @param options.trim True if text should be trimmed.
+ * @returns Clean text.
+ */
+ static cleanTags(text: string | undefined, options: { singleLine?: boolean; trim?: boolean } = {}): string {
+ if (!text) {
+ return '';
+ }
+
+ // First, we use a regexpr.
+ text = text.replace(/(<([^>]+)>)/ig, '');
+ // Then, we rely on the browser. We need to wrap the text to be sure is HTML.
+ text = convertTextToHTMLElement(text).textContent || '';
+ // Trim text
+ text = options.trim ? text.trim() : text;
+ // Recover or remove new lines.
+ text = CoreText.replaceNewLines(text, options.singleLine ? ' ' : ' ');
+
+ return text;
+ }
+
+ /**
+ * Decode an escaped HTML text. This implementation is based on PHP's htmlspecialchars_decode.
+ *
+ * @param text Text to decode.
+ * @returns Decoded text.
+ */
+ static decodeHTML(text: string | number): string {
+ if (text === undefined || text === null || (typeof text === 'number' && isNaN(text))) {
+ return '';
+ } else if (typeof text != 'string') {
+ return '' + text;
+ }
+
+ return text
+ .replace(/&/g, '&')
+ .replace(/</g, '<')
+ .replace(/>/g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, '\'')
+ .replace(/ /g, ' ');
+ }
+
+ /**
+ * Decode HTML entities in a text. Equivalent to PHP html_entity_decode.
+ *
+ * @param text Text to decode.
+ * @returns Decoded text.
+ */
+ static decodeHTMLEntities(text: string): string {
+ if (text) {
+ text = convertTextToHTMLElement(text).textContent || '';
+ }
+
+ return text;
+ }
+
+ /**
+ * Escapes some characters in a string to be used as a regular expression.
+ *
+ * @param text Text to escape.
+ * @returns Escaped text.
+ */
+ static escapeForRegex(text: string): string {
+ if (!text || typeof text !== 'string') {
+ return '';
+ }
+
+ return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
+ }
+
+ /**
+ * Escape an HTML text. This implementation is based on PHP's htmlspecialchars.
+ *
+ * @param text Text to escape.
+ * @param doubleEncode If false, it will not convert existing html entities. Defaults to true.
+ * @returns Escaped text.
+ */
+ static escapeHTML(text?: string | number | null, doubleEncode = true): string {
+ if (text === undefined || text === null || (typeof text === 'number' && isNaN(text))) {
+ return '';
+ } else if (typeof text !== 'string') {
+ return '' + text;
+ }
+
+ if (doubleEncode) {
+ text = text.replace(/&/g, '&');
+ } else {
+ text = text.replace(/&(?!amp;)(?!lt;)(?!gt;)(?!quot;)(?!#039;)/g, '&');
+ }
+
+ return text
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+ .replace(/'/g, ''');
+ }
+
+ /**
+ * Formats a text, in HTML replacing new lines by correct html new lines.
+ *
+ * @param text Text to format.
+ * @returns Formatted text.
+ */
+ static formatHtmlLines(text: string): string {
+ const hasHTMLTags = CoreText.hasHTMLTags(text);
+ if (text.indexOf('') == -1) {
+ // Wrap the text in
tags.
+ text = '
' + text + '
';
+ }
+
+ if (!hasHTMLTags) {
+ // The text doesn't have HTML, replace new lines for .
+ return CoreText.replaceNewLines(text, ' ');
+ }
+
+ return text;
+ }
+
+ /**
+ * Check if a text contains HTML tags.
+ *
+ * @param text Text to check.
+ * @returns Whether it has HTML tags.
+ */
+ static hasHTMLTags(text: string): boolean {
+ return /<[a-z][\s\S]*>/i.test(text);
+ }
+
+ /**
+ * Check if a text contains Unicode long chars.
+ * Using as threshold Hex value D800
+ *
+ * @param text Text to check.
+ * @returns True if has Unicode chars, false otherwise.
+ */
+ static hasUnicode(text: string): boolean {
+ for (let x = 0; x < text.length; x++) {
+ if (text.charCodeAt(x) > 55295) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Check if an object has any long Unicode char.
+ *
+ * @param data Object to be checked.
+ * @returns If the data has any long Unicode char on it.
+ */
+ static hasUnicodeData(data: Record): boolean {
+ for (const el in data) {
+ if (typeof data[el] === 'object') {
+ if (CoreText.hasUnicodeData(data[el] as Record)) {
+ return true;
+ }
+
+ continue;
+ }
+
+ if (typeof data[el] === 'string' && CoreText.hasUnicode(data[el] as string)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Highlight all occurrences of a certain text inside another text. It will add some HTML code to highlight it.
+ *
+ * @param text Full text.
+ * @param searchText Text to search and highlight.
+ * @returns Highlighted text.
+ */
+ static highlightText(text: string, searchText: string): string {
+ if (!text || typeof text !== 'string') {
+ return '';
+ } else if (!searchText) {
+ return text;
+ }
+
+ const regex = new RegExp('(' + searchText + ')', 'gi');
+
+ return text.replace(regex, '$1 ');
+ }
+
+ /**
+ * Check whether the given text matches a glob pattern.
+ *
+ * @param text Text to match against.
+ * @param pattern Glob pattern.
+ * @returns Whether the pattern matches.
+ */
+ static matchesGlob(text: string, pattern: string): boolean {
+ pattern = pattern
+ .replace(/\*\*/g, '%RECURSIVE_MATCH%')
+ .replace(/\*/g, '%LOCAL_MATCH%')
+ .replace(/\?/g, '%CHARACTER_MATCH%');
+
+ pattern = CoreText.escapeForRegex(pattern);
+
+ pattern = pattern
+ .replace(/%RECURSIVE_MATCH%/g, '.*')
+ .replace(/%LOCAL_MATCH%/g, '[^/]*')
+ .replace(/%CHARACTER_MATCH%/g, '[^/]');
+
+ return new RegExp(`^${pattern}$`).test(text);
+ }
+
+ /**
+ * Same as Javascript's JSON.parse, but it will handle errors.
+ *
+ * @param json JSON text.
+ * @param defaultValue Default value to return if the parse fails. Defaults to the original value.
+ * @param logErrorFn An error to call with the exception to log the error. If not supplied, no error.
+ * @returns JSON parsed as object or what it gets.
+ */
+ static parseJSON(json: string, defaultValue?: T, logErrorFn?: (error?: Error) => void): T {
+ try {
+ return JSON.parse(json);
+ } catch (error) {
+ // Error, log the error if needed.
+ if (logErrorFn) {
+ logErrorFn(error);
+ }
+ }
+
+ // Error parsing, return the default value or the original value.
+ if (defaultValue !== undefined) {
+ return defaultValue;
+ }
+
+ throw new CoreError('JSON cannot be parsed and not default value has been provided');
+ }
+
+ /**
+ * Process HTML string.
+ *
+ * @param text HTML string.
+ * @param process Method to process the HTML.
+ * @returns Processed HTML string.
+ */
+ static processHTML(text: string, process: (element: HTMLElement) => unknown): string {
+ const element = convertTextToHTMLElement(text);
+
+ process(element);
+
+ return element.innerHTML;
+ }
+
+ /**
+ * Replace all characters that cause problems with files in Android and iOS.
+ *
+ * @param text Text to treat.
+ * @returns Treated text.
+ */
+ static removeSpecialCharactersForFiles(text: string): string {
+ if (!text || typeof text !== 'string') {
+ return '';
+ }
+
+ return text.replace(/[#:/?\\]+/g, '_');
+ }
+
/**
* Remove ending slash from a path or URL.
*
@@ -72,27 +478,155 @@ export class CoreText {
}
/**
- * Copies a text to clipboard and shows a toast message.
+ * Replace {{ARGUMENT}} arguments in the text.
*
- * @param text Text to be copied
+ * @param text Text to treat.
+ * @param replacements Argument values.
+ * @param encoding Encoding to use in values.
+ * @returns Treated text.
*/
- static async copyToClipboard(text: string): Promise {
- try {
- await Clipboard.copy(text);
- } catch {
- // Use HTML Copy command.
- const virtualInput = document.createElement('textarea');
- virtualInput.innerHTML = text;
- virtualInput.select();
- virtualInput.setSelectionRange(0, 99999);
- document.execCommand('copy'); // eslint-disable-line deprecation/deprecation
+ static replaceArguments(text: string, replacements: Record = {}, encoding?: 'uri'): string {
+ let match: RegExpMatchArray | null = null;
+
+ while ((match = text.match(/\{\{([^}]+)\}\}/))) {
+ const argument = match[1].trim();
+ const value = replacements[argument] ?? '';
+ const encodedValue = encoding ? encodeURIComponent(value) : value;
+
+ text = text.replace(`{{${argument}}}`, encodedValue);
}
- // Show toast using ionicLoading.
- CoreToasts.show({
- message: 'core.copiedtoclipboard',
- translateMessage: true,
- });
+ return text;
+ }
+
+ /**
+ * Replace all the new lines on a certain text.
+ *
+ * @param text The text to be treated.
+ * @param newValue Text to use instead of new lines.
+ * @returns Treated text.
+ */
+ static replaceNewLines(text: string, newValue: string): string {
+ if (!text || typeof text !== 'string') {
+ return '';
+ }
+
+ return text.replace(/(?:\r\n|\r|\n)/g, newValue);
+ }
+
+ /**
+ * Rounds a number to use a certain amout of decimals or less.
+ * Difference between this function and float's toFixed:
+ * 7.toFixed(2) -> 7.00
+ * roundToDecimals(7, 2) -> 7
+ *
+ * @param num Number to round.
+ * @param decimals Number of decimals. By default, 2.
+ * @returns Rounded number.
+ */
+ static roundToDecimals(num: number, decimals: number = 2): number {
+ const multiplier = Math.pow(10, decimals);
+
+ return Math.round(num * multiplier) / multiplier;
+ }
+
+ /**
+ * Add quotes to HTML characters.
+ *
+ * Returns text with HTML characters (like "<", ">", etc.) properly quoted.
+ * Based on Moodle's s() function.
+ *
+ * @param text Text to treat.
+ * @returns Treated text.
+ */
+ static s(text: string): string {
+ if (!text) {
+ return '';
+ }
+
+ return CoreText.escapeHTML(text).replace(/&#(\d+|x[0-9a-f]+);/i, '$1;');
+ }
+
+ /**
+ * Shortens a text to length and adds an ellipsis.
+ *
+ * @param text The text to be shortened.
+ * @param length The desired length.
+ * @returns Shortened text.
+ */
+ static shortenText(text: string, length: number): string {
+ if (text.length > length) {
+ text = text.substring(0, length);
+
+ // Now, truncate at the last word boundary (if exists).
+ const lastWordPos = text.lastIndexOf(' ');
+ if (lastWordPos > 0) {
+ text = text.substring(0, lastWordPos);
+ }
+ text += '…';
+ }
+
+ return text;
+ }
+
+ /**
+ * Strip Unicode long char of a given text.
+ * Using as threshold Hex value D800
+ *
+ * @param text Text to check.
+ * @returns Without the Unicode chars.
+ */
+ static stripUnicode(text: string): string {
+ let stripped = '';
+ for (let x = 0; x < text.length; x++) {
+ if (text.charCodeAt(x) <= 55295) {
+ stripped += text.charAt(x);
+ }
+ }
+
+ return stripped;
+ }
+
+ /**
+ * Replace text within a portion of a string. Equivalent to PHP's substr_replace.
+ *
+ * @param str The string to treat.
+ * @param replace The value to put inside the string.
+ * @param start The index where to start putting the new string. If negative, it will count from the end of the string.
+ * @param length Length of the portion of string which is to be replaced. If negative, it represents the number of characters
+ * from the end of string at which to stop replacing. If not provided, replace until the end of the string.
+ * @returns Treated string.
+ */
+ static substrReplace(str: string, replace: string, start: number, length?: number): string {
+ return Locutus.substrReplace(str, replace, start, length);
+ }
+
+ /**
+ * Remove all ocurrences of a certain character from the start and end of a string.
+ *
+ * @param text Text to treat.
+ * @param character Character to remove.
+ * @returns Treated text.
+ */
+ static trimCharacter(text: string, character: string): string {
+ const escaped = CoreText.escapeForRegex(character);
+ const regExp = new RegExp(`^${escaped}+|${escaped}+$`, 'g');
+
+ return text.replace(regExp, '');
+ }
+
+ /**
+ * If a number has only 1 digit, add a leading zero to it.
+ *
+ * @param num Number to convert.
+ * @returns Number with leading zeros.
+ */
+ static twoDigits(num: string | number): string {
+ if (Number(num) < 10) {
+ return '0' + num;
+ } else {
+ return '' + num; // Convert to string for coherence.
+ }
}
/**
@@ -105,4 +639,27 @@ export class CoreText {
return text.charAt(0).toUpperCase() + text.slice(1);
}
+ /**
+ * Unserialize Array from PHP.
+ *
+ * @param data String to unserialize.
+ * @returns Unserialized data.
+ */
+ static unserialize(data: string): T {
+ return Locutus.unserialize(data);
+ }
+
}
+
+/**
+ * Define text formatting types.
+ */
+export enum CoreTextFormat {
+ FORMAT_MOODLE = 0, // Does all sorts of transformations and filtering.
+ FORMAT_HTML = 1, // Plain HTML (with some tags stripped). Use it by default.
+ FORMAT_PLAIN = 2, // Plain text (even tags are printed in full).
+ // FORMAT_WIKI is deprecated since 2005...
+ FORMAT_MARKDOWN = 4, // Markdown-formatted text http://daringfireball.net/projects/markdown/
+}
+
+export const defaultTextFormat = CoreTextFormat.FORMAT_HTML;
diff --git a/src/core/singletons/url.ts b/src/core/singletons/url.ts
index e9080b1e3..dafbe1c3b 100644
--- a/src/core/singletons/url.ts
+++ b/src/core/singletons/url.ts
@@ -17,10 +17,11 @@ import { CorePath } from './path';
import { CoreText } from './text';
import { CorePlatform } from '@services/platform';
-import { CoreTextUtils } from '@services/utils/text';
import { CoreConstants } from '../constants';
import { CoreMedia } from './media';
import { CoreLang, CoreLangFormat } from '@services/lang';
+import { DomSanitizer } from '@singletons';
+import { SafeUrl } from '@angular/platform-browser';
/**
* Parts contained within a url.
@@ -91,6 +92,23 @@ export class CoreUrl {
// Nothing to do.
}
+ /**
+ * Given an address as a string, return a URL to open the address in maps.
+ *
+ * @param address The address.
+ * @returns URL to view the address.
+ */
+ static buildAddressURL(address: string): SafeUrl {
+ const parsedUrl = CoreUrl.parse(address);
+ if (parsedUrl?.protocol) {
+ // It's already a URL, don't convert it.
+ return DomSanitizer.bypassSecurityTrustUrl(address);
+ }
+
+ return DomSanitizer.bypassSecurityTrustUrl((CorePlatform.isAndroid() ? 'geo:0,0?q=' : 'http://maps.google.com?q=') +
+ encodeURIComponent(address));
+ }
+
/**
* Parse parts of a url, using an implicit protocol if it is missing from the url.
*
@@ -455,6 +473,38 @@ export class CoreUrl {
!CoreMedia.sourceUsesJavascriptPlayer({ src: url });
}
+ /**
+ * Same as Javascript's decodeURI, but if an exception is thrown it will return the original URI.
+ *
+ * @param uri URI to decode.
+ * @returns Decoded URI, or original URI if an exception is thrown.
+ */
+ static decodeURI(uri: string): string {
+ try {
+ return decodeURI(uri);
+ } catch {
+ // Error, use the original URI.
+ }
+
+ return uri;
+ }
+
+ /**
+ * Same as Javascript's decodeURIComponent, but if an exception is thrown it will return the original URI.
+ *
+ * @param uri URI to decode.
+ * @returns Decoded URI, or original URI if an exception is thrown.
+ */
+ static decodeURIComponent(uri: string): string {
+ try {
+ return decodeURIComponent(uri);
+ } catch {
+ // Error, use the original URI.
+ }
+
+ return uri;
+ }
+
/**
* Extracts the parameters from a URL and stores them in an object.
*
@@ -479,7 +529,7 @@ export class CoreUrl {
}
urlAndHash[0].replace(regex, (match: string, key: string, value: string): string => {
- params[key] = value !== undefined ? CoreTextUtils.decodeURIComponent(value) : '';
+ params[key] = value !== undefined ? CoreUrl.decodeURIComponent(value) : '';
if (subParams) {
params[key] = params[key].replace(subParamsPlaceholder, subParams);
@@ -525,7 +575,7 @@ export class CoreUrl {
}
// Check if is a valid URL (contains the pluginfile endpoint) and belongs to the site.
- if (!CoreUrl.isPluginFileUrl(url) || url.indexOf(CoreTextUtils.addEndingSlash(siteUrl)) !== 0) {
+ if (!CoreUrl.isPluginFileUrl(url) || url.indexOf(CoreText.addEndingSlash(siteUrl)) !== 0) {
return url;
}
@@ -584,7 +634,7 @@ export class CoreUrl {
let videoId = '';
const params: CoreUrlParams = {};
- url = CoreTextUtils.decodeHTML(url);
+ url = CoreText.decodeHTML(url);
// Get the video ID.
let match = url.match(/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/);
@@ -925,7 +975,7 @@ export class CoreUrl {
url = url.replace(/&/g, '&');
// It site URL is supplied, check if the URL belongs to the site.
- if (siteUrl && url.indexOf(CoreTextUtils.addEndingSlash(siteUrl)) !== 0) {
+ if (siteUrl && url.indexOf(CoreText.addEndingSlash(siteUrl)) !== 0) {
return url;
}
diff --git a/src/core/utils/create-html-element.ts b/src/core/utils/create-html-element.ts
new file mode 100644
index 000000000..076d6bbc4
--- /dev/null
+++ b/src/core/utils/create-html-element.ts
@@ -0,0 +1,28 @@
+// (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.
+
+/**
+ * Convert some HTML as text into an HTMLElement. This HTML is put inside a div.
+ *
+ * @param html Text to convert.
+ * @returns Element.
+ */
+export function convertTextToHTMLElement(html: string): HTMLElement {
+ const element = document.createElement('template');
+
+ // Add a div to hold the content, that's the element that will be returned.
+ element.innerHTML = '' + html + '
';
+
+ return element.content.children[0];
+}
diff --git a/src/testing/utils.ts b/src/testing/utils.ts
index 1cbe0ea39..633f6f6b2 100644
--- a/src/testing/utils.ts
+++ b/src/testing/utils.ts
@@ -20,7 +20,7 @@ import { sep } from 'path';
import { CORE_SITE_SCHEMAS } from '@services/sites';
import { ApplicationInit, CoreSingletonProxy, Translate } from '@singletons';
-import { CoreTextUtilsProvider } from '@services/utils/text';
+import { CoreText } from '@singletons/text';
import { CoreExternalContentDirectiveStub } from './stubs/directives/core-external-content';
import { CoreNetwork } from '@services/network';
@@ -44,7 +44,6 @@ abstract class WrapperComponent {
type ServiceInjectionToken = AbstractType | Type | string;
let testBedInitialized = false;
-const textUtils = new CoreTextUtilsProvider();
const DEFAULT_SERVICE_SINGLETON_MOCKS: [CoreSingletonProxy, unknown][] = [
[Translate, mock({
instant: key => key,
@@ -479,7 +478,7 @@ export async function renderWrapperComponent(
): Promise> {
const inputAttributes = Object
.entries(inputs)
- .map(([name, value]) => `[${name}]="${textUtils.escapeHTML(JSON.stringify(value)).replace(/\//g, '\\/')}"`)
+ .map(([name, value]) => `[${name}]="${CoreText.escapeHTML(JSON.stringify(value)).replace(/\//g, '\\/')}"`)
.join(' ');
return renderTemplate(component, `<${tag} ${inputAttributes}>${tag}>`, config);