diff --git a/src/addons/mod/data/components/index/addon-mod-data-index.html b/src/addons/mod/data/components/index/addon-mod-data-index.html
index d71ce8244..d521f8e60 100644
--- a/src/addons/mod/data/components/index/addon-mod-data-index.html
+++ b/src/addons/mod/data/components/index/addon-mod-data-index.html
@@ -75,9 +75,7 @@
-
-
-
+
diff --git a/src/addons/mod/data/pages/edit/edit.html b/src/addons/mod/data/pages/edit/edit.html
index df520ec87..c27206afb 100644
--- a/src/addons/mod/data/pages/edit/edit.html
+++ b/src/addons/mod/data/pages/edit/edit.html
@@ -21,10 +21,9 @@
[courseId]="database?.course" />
-
-
diff --git a/src/addons/mod/data/pages/entry/entry.html b/src/addons/mod/data/pages/entry/entry.html
index 0c8d7026e..a29f4fe43 100644
--- a/src/addons/mod/data/pages/entry/entry.html
+++ b/src/addons/mod/data/pages/entry/entry.html
@@ -28,9 +28,8 @@
[courseId]="courseId" />
-
-
-
+
tag.
@@ -23,6 +24,7 @@ import { Component, ElementRef, Input, OnChanges } from '@angular/core';
* Example:
*
*
+ * @deprecated since 4.5.0. Not needed anymore, core-compile-html accepts now CSS code.
*/
@Component({
selector: 'core-style',
@@ -41,37 +43,11 @@ export class CoreStyleComponent implements OnChanges {
ngOnChanges(): void {
if (this.element && this.element.nativeElement) {
const style = document.createElement('style');
- style.innerHTML = this.prefixCSS(this.css, this.prefix);
+ style.innerHTML = CoreDom.prefixCSS(this.css, this.prefix);
this.element.nativeElement.innerHTML = '';
this.element.nativeElement.appendChild(style);
}
}
- /**
- * Add a prefix to all rules in a CSS string.
- *
- * @param css CSS code to be prefixed.
- * @param prefix Prefix css selector.
- * @returns Prefixed CSS.
- */
- protected prefixCSS(css: string, prefix: string): string {
- if (!css) {
- return '';
- }
-
- if (!prefix) {
- return css;
- }
-
- // Remove comments first.
- let regExp = /\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm;
- css = css.replace(regExp, '');
-
- // Add prefix.
- regExp = /([^]*?)({[^]*?}|,)/g;
-
- return css.replace(regExp, prefix + ' $1 $2');
- }
-
}
diff --git a/src/core/features/compile/components/compile-html/compile-html.ts b/src/core/features/compile/components/compile-html/compile-html.ts
index b657d3d8e..10c0c3d2a 100644
--- a/src/core/features/compile/components/compile-html/compile-html.ts
+++ b/src/core/features/compile/components/compile-html/compile-html.ts
@@ -38,6 +38,8 @@ import { CorePromisedValue } from '@classes/promised-value';
import { CoreCompile } from '@features/compile/services/compile';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils';
+import { CoreWS } from '@services/ws';
+import { CoreDom } from '@singletons/dom';
/**
* This component has a behaviour similar to $compile for AngularJS. Given an HTML code, it will compile it so all its
@@ -64,6 +66,8 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy, DoCheck {
@Input() text!: string; // The HTML text to display.
@Input() javascript?: string; // The Javascript to execute in the component.
@Input() jsData?: Record; // Data to pass to the fake component.
+ @Input() cssCode?: string; // The styles to apply.
+ @Input() stylesPath?: string; // The styles URL to apply (only if cssCode is not set).
@Input() extraImports: unknown[] = []; // Extra import modules.
@Input() extraProviders: Type[] = []; // Extra providers.
@Input() forceCompile = false; // Set it to true to force compile even if the text/javascript hasn't changed.
@@ -101,12 +105,13 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy, DoCheck {
// Check if there's any change in the jsData object.
const changes = this.differ.diff(this.jsData || {});
- if (changes) {
- this.setInputData();
+ if (!changes) {
+ return;
+ }
+ this.setInputData();
- if (this.componentInstance.ngOnChanges) {
- this.componentInstance.ngOnChanges(CoreDomUtils.createChangesFromKeyValueDiff(changes));
- }
+ if (this.componentInstance.ngOnChanges) {
+ this.componentInstance.ngOnChanges(CoreDomUtils.createChangesFromKeyValueDiff(changes));
}
}
@@ -116,7 +121,8 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy, DoCheck {
async ngOnChanges(changes: Record): Promise {
// Only compile if text/javascript has changed or the forceCompile flag has been set to true.
if (this.text === undefined ||
- !(changes.text || changes.javascript || (changes.forceCompile && CoreUtils.isTrueOrOne(this.forceCompile)))) {
+ !(changes.text || changes.javascript || changes.cssCode || changes.stylesPath ||
+ (changes.forceCompile && CoreUtils.isTrueOrOne(this.forceCompile)))) {
return;
}
@@ -132,11 +138,14 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy, DoCheck {
// Create the component.
if (this.container) {
+ await this.loadCSSCode();
+
this.componentRef = await CoreCompile.createAndCompileComponent(
this.text,
componentClass,
this.container,
this.extraImports,
+ this.cssCode,
);
this.element.addEventListener('submit', (event) => {
@@ -163,6 +172,29 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy, DoCheck {
this.componentRef?.destroy();
}
+ /**
+ * Retrieve the CSS code from the stylesPath if not loaded yet.
+ */
+ protected async loadCSSCode(): Promise {
+ // Do not allow (yet) to load CSS code to a component that doesn't have text.
+ if (!this.text) {
+ this.cssCode = '';
+
+ return;
+ }
+
+ if (this.stylesPath && !this.cssCode) {
+ this.cssCode = await CoreUtils.ignoreErrors(CoreWS.getText(this.stylesPath));
+ }
+
+ // Prepend all CSS rules with :host to avoid conflicts.
+ if (!this.cssCode || this.cssCode.includes(':host')) {
+ return;
+ }
+
+ this.cssCode = CoreDom.prefixCSS(this.cssCode, ':host ::ng-deep', ':host');
+ }
+
/**
* Get a class that defines the dynamic component.
*
diff --git a/src/core/features/compile/services/compile.ts b/src/core/features/compile/services/compile.ts
index 2d084e4ab..63849d3d0 100644
--- a/src/core/features/compile/services/compile.ts
+++ b/src/core/features/compile/services/compile.ts
@@ -175,6 +175,7 @@ export class CoreCompileProvider {
* @param componentClass The JS class of the component.
* @param viewContainerRef View container reference to inject the component.
* @param extraImports Extra imported modules if needed and not imported by this class.
+ * @param styles CSS code to apply to the component.
* @returns Promise resolved with the component reference.
*/
async createAndCompileComponent(
@@ -182,12 +183,17 @@ export class CoreCompileProvider {
componentClass: Type,
viewContainerRef: ViewContainerRef,
extraImports: any[] = [], // eslint-disable-line @typescript-eslint/no-explicit-any
+ styles?: string,
): Promise | undefined> {
// Import the Angular compiler to be able to compile components in runtime.
await import('@angular/compiler');
// Create the component using the template and the class.
- const component = Component({ template, host: { 'compiled-component-id': String(this.componentId++) } })(componentClass);
+ const component = Component({
+ template,
+ host: { 'compiled-component-id': String(this.componentId++) },
+ styles,
+ })(componentClass);
const lazyImports = await Promise.all(this.LAZY_IMPORTS.map(getModules => getModules()));
const imports = [
diff --git a/src/core/features/siteplugins/classes/call-ws-directive.ts b/src/core/features/siteplugins/classes/call-ws-directive.ts
index f908c4ed4..f17b7d8a4 100644
--- a/src/core/features/siteplugins/classes/call-ws-directive.ts
+++ b/src/core/features/siteplugins/classes/call-ws-directive.ts
@@ -127,7 +127,7 @@ export class CoreSitePluginsCallWSBaseDirective implements OnInit, OnDestroy {
}
/**
- * Directive destroyed.
+ * @inheritdoc
*/
ngOnDestroy(): void {
this.invalidateObserver?.unsubscribe();
diff --git a/src/core/features/siteplugins/constants.ts b/src/core/features/siteplugins/constants.ts
index c971aa1e8..a215c83e0 100644
--- a/src/core/features/siteplugins/constants.ts
+++ b/src/core/features/siteplugins/constants.ts
@@ -13,3 +13,7 @@
// limitations under the License.
export const CORE_SITE_PLUGINS_UPDATE_COURSE_CONTENT = 'siteplugins_update_course_content';
+
+export const CORE_SITE_PLUGINS_COMPONENT = 'CoreSitePlugins';
+
+export const CORE_SITE_PLUGINS_PATH = 'siteplugins';
diff --git a/src/core/features/siteplugins/services/siteplugins-helper.ts b/src/core/features/siteplugins/services/siteplugins-init.ts
similarity index 96%
rename from src/core/features/siteplugins/services/siteplugins-helper.ts
rename to src/core/features/siteplugins/services/siteplugins-init.ts
index 3417db1e3..fc4854919 100644
--- a/src/core/features/siteplugins/services/siteplugins-helper.ts
+++ b/src/core/features/siteplugins/services/siteplugins-init.ts
@@ -62,7 +62,6 @@ import {
CoreSitePluginsContent,
CoreSitePluginsPlugin,
CoreSitePluginsHandlerData,
- CoreSitePluginsProvider,
CoreSitePluginsCourseOptionHandlerData,
CoreSitePluginsMainMenuHandlerData,
CoreSitePluginsCourseModuleHandlerData,
@@ -89,8 +88,7 @@ import { CoreUrlUtils } from '@services/utils/url';
import { CorePath } from '@singletons/path';
import { CoreEnrolAction, CoreEnrolDelegate } from '@features/enrol/services/enrol-delegate';
import { CoreSitePluginsEnrolHandler } from '../classes/handlers/enrol-handler';
-
-const HANDLER_DISABLED = 'core_site_plugins_helper_handler_disabled';
+import { CORE_SITE_PLUGINS_COMPONENT } from '../constants';
/**
* Helper service to provide functionalities regarding site plugins. It basically has the features to load and register site
@@ -102,7 +100,7 @@ const HANDLER_DISABLED = 'core_site_plugins_helper_handler_disabled';
* inside the host DOM element?
*/
@Injectable({ providedIn: 'root' })
-export class CoreSitePluginsHelperProvider {
+export class CoreSitePluginsInitService {
protected logger: CoreLogger;
protected courseRestrictHandlers: Record = {};
+ protected static readonly HANDLER_DISABLED = 'core_site_plugins_helper_handler_disabled';
+
constructor() {
- this.logger = CoreLogger.getInstance('CoreSitePluginsHelperProvider');
+ this.logger = CoreLogger.getInstance('CoreSitePluginsInit');
}
/**
* Initialize.
*/
- initialize(): void {
+ init(): void {
// Fetch the plugins on login.
CoreEvents.on(CoreEvents.LOGIN, async (data) => {
try {
const plugins = await CoreUtils.ignoreErrors(CoreSitePlugins.getPlugins(data.siteId));
// Plugins fetched, check that site hasn't changed.
- if (data.siteId != CoreSites.getCurrentSiteId() || !plugins?.length) {
+ if (data.siteId !== CoreSites.getCurrentSiteId() || !plugins?.length) {
return;
}
@@ -145,7 +145,7 @@ export class CoreSitePluginsHelperProvider {
// Re-load plugins restricted for courses when the list of user courses changes.
CoreEvents.on(CoreCoursesProvider.EVENT_MY_COURSES_CHANGED, (data) => {
- if (data && data.siteId && data.siteId == CoreSites.getCurrentSiteId() && data.added && data.added.length) {
+ if (data.siteId && data.siteId === CoreSites.getCurrentSiteId() && data.added.length) {
this.reloadCourseRestrictHandlers();
}
});
@@ -160,7 +160,7 @@ export class CoreSitePluginsHelperProvider {
* @param siteId Site ID. If not provided, current site.
* @returns Promise resolved with the CSS code.
*/
- async downloadStyles(
+ protected async downloadStyles(
plugin: CoreSitePluginsPlugin,
handlerName: string,
handlerSchema: CoreSitePluginsHandlerData,
@@ -184,7 +184,7 @@ export class CoreSitePluginsHelperProvider {
// Remove the CSS files for this handler that aren't used anymore. Don't block the call for this.
const files = await CoreUtils.ignoreErrors(
- CoreFilepool.getFilesByComponent(site.getId(), CoreSitePluginsProvider.COMPONENT, componentId),
+ CoreFilepool.getFilesByComponent(site.getId(), CORE_SITE_PLUGINS_COMPONENT, componentId),
);
files?.forEach((file) => {
@@ -209,7 +209,7 @@ export class CoreSitePluginsHelperProvider {
site.getId(),
url,
false,
- CoreSitePluginsProvider.COMPONENT,
+ CORE_SITE_PLUGINS_COMPONENT,
componentId,
0,
undefined,
@@ -270,7 +270,7 @@ export class CoreSitePluginsHelperProvider {
// Create a "fake" instance to hold all the libraries.
const instance = {
// eslint-disable-next-line @typescript-eslint/naming-convention
- HANDLER_DISABLED: HANDLER_DISABLED,
+ HANDLER_DISABLED: CoreSitePluginsInitService.HANDLER_DISABLED,
};
await CoreCompile.loadLibraries();
@@ -285,7 +285,7 @@ export class CoreSitePluginsHelperProvider {
// Now execute the javascript using this instance.
result.jsResult = CoreCompile.executeJavascript(instance, result.javascript);
- if (result.jsResult == HANDLER_DISABLED) {
+ if (result.jsResult === CoreSitePluginsInitService.HANDLER_DISABLED) {
// The "disabled" field was added in 3.8, this is a workaround for previous versions.
result.disabled = true;
}
@@ -311,10 +311,10 @@ export class CoreSitePluginsHelperProvider {
* Given an addon name and the key of a string, return the full string key (prefixed).
*
* @param addon Name of the addon (plugin.addon).
- * @param key The key of the string.
+ * @param key The key of the string. Defaults to pluginname.
* @returns Full string key.
*/
- protected getPrefixedString(addon: string, key: string): string {
+ protected getPrefixedString(addon: string, key = 'pluginname'): string {
return this.getPrefixForStrings(addon) + key;
}
@@ -323,7 +323,7 @@ export class CoreSitePluginsHelperProvider {
*
* @param plugin Data of the plugin.
*/
- loadLangStrings(plugin: CoreSitePluginsPlugin): void {
+ protected loadLangStrings(plugin: CoreSitePluginsPlugin): void {
if (!plugin.parsedLang) {
return;
}
@@ -339,9 +339,8 @@ export class CoreSitePluginsHelperProvider {
* Load a site plugin.
*
* @param plugin Data of the plugin.
- * @returns Promise resolved when loaded.
*/
- async loadSitePlugin(plugin: CoreSitePluginsPlugin): Promise {
+ protected async loadSitePlugin(plugin: CoreSitePluginsPlugin): Promise {
this.logger.debug('Load site plugin:', plugin);
if (!plugin.parsedHandlers && plugin.handlers) {
@@ -378,9 +377,8 @@ export class CoreSitePluginsHelperProvider {
* Load site plugins.
*
* @param plugins The plugins to load.
- * @returns Promise resolved when loaded.
*/
- async loadSitePlugins(plugins: CoreSitePluginsPlugin[]): Promise {
+ protected async loadSitePlugins(plugins: CoreSitePluginsPlugin[]): Promise {
this.courseRestrictHandlers = {};
await CoreUtils.allPromises(plugins.map(async (plugin) => {
@@ -401,7 +399,7 @@ export class CoreSitePluginsHelperProvider {
* @param version Styles version.
* @param siteId Site ID. If not provided, current site.
*/
- loadStyles(
+ protected loadStyles(
plugin: CoreSitePluginsPlugin,
handlerName: string,
fileUrl: string,
@@ -436,7 +434,7 @@ export class CoreSitePluginsHelperProvider {
// Styles have been loaded, now treat the CSS.
CoreUtils.ignoreErrors(
- CoreFilepool.treatCSSCode(siteId, fileUrl, cssCode, CoreSitePluginsProvider.COMPONENT, uniqueName, version),
+ CoreFilepool.treatCSSCode(siteId, fileUrl, cssCode, CORE_SITE_PLUGINS_COMPONENT, uniqueName, version),
);
}
@@ -446,9 +444,8 @@ export class CoreSitePluginsHelperProvider {
* @param plugin Data of the plugin.
* @param handlerName Name of the handler in the plugin.
* @param handlerSchema Data about the handler.
- * @returns Promise resolved when done.
*/
- async registerHandler(
+ protected async registerHandler(
plugin: CoreSitePluginsPlugin,
handlerName: string,
handlerSchema: CoreSitePluginsHandlerData,
@@ -700,7 +697,7 @@ export class CoreSitePluginsHelperProvider {
const uniqueName = CoreSitePlugins.getHandlerUniqueName(plugin, handlerName);
const blockName = (handlerSchema.moodlecomponent || plugin.component).replace('block_', '');
- const titleString = handlerSchema.displaydata?.title ?? 'pluginname';
+ const titleString = handlerSchema.displaydata?.title;
const prefixedTitle = this.getPrefixedString(plugin.addon, titleString);
CoreBlockDelegate.registerHandler(
@@ -761,7 +758,7 @@ export class CoreSitePluginsHelperProvider {
// Create and register the handler.
const uniqueName = CoreSitePlugins.getHandlerUniqueName(plugin, handlerName);
- const prefixedTitle = this.getPrefixedString(plugin.addon, handlerSchema.displaydata.title || 'pluginname');
+ const prefixedTitle = this.getPrefixedString(plugin.addon, handlerSchema.displaydata.title);
const handler = new CoreSitePluginsCourseOptionHandler(
uniqueName,
prefixedTitle,
@@ -873,7 +870,7 @@ export class CoreSitePluginsHelperProvider {
// Create and register the handler.
const uniqueName = CoreSitePlugins.getHandlerUniqueName(plugin, handlerName);
- const prefixedTitle = this.getPrefixedString(plugin.addon, handlerSchema.displaydata.title || 'pluginname');
+ const prefixedTitle = this.getPrefixedString(plugin.addon, handlerSchema.displaydata.title);
CoreMainMenuDelegate.registerHandler(
new CoreSitePluginsMainMenuHandler(uniqueName, prefixedTitle, plugin, handlerSchema, initResult),
@@ -908,7 +905,7 @@ export class CoreSitePluginsHelperProvider {
// Create and register the handler.
const uniqueName = CoreSitePlugins.getHandlerUniqueName(plugin, handlerName);
- const prefixedTitle = this.getPrefixedString(plugin.addon, handlerSchema.displaydata.title || 'pluginname');
+ const prefixedTitle = this.getPrefixedString(plugin.addon, handlerSchema.displaydata.title);
const processorName = (handlerSchema.moodlecomponent || plugin.component).replace('message_', '');
AddonMessageOutputDelegate.registerHandler(
@@ -1074,7 +1071,7 @@ export class CoreSitePluginsHelperProvider {
// Create and register the handler.
const uniqueName = CoreSitePlugins.getHandlerUniqueName(plugin, handlerName);
- const prefixedTitle = this.getPrefixedString(plugin.addon, handlerSchema.displaydata.title || 'pluginname');
+ const prefixedTitle = this.getPrefixedString(plugin.addon, handlerSchema.displaydata.title);
CoreSettingsDelegate.registerHandler(
new CoreSitePluginsSettingsHandler(uniqueName, prefixedTitle, plugin, handlerSchema, initResult),
@@ -1109,7 +1106,7 @@ export class CoreSitePluginsHelperProvider {
// Create and register the handler.
const uniqueName = CoreSitePlugins.getHandlerUniqueName(plugin, handlerName);
- const prefixedTitle = this.getPrefixedString(plugin.addon, handlerSchema.displaydata.title || 'pluginname');
+ const prefixedTitle = this.getPrefixedString(plugin.addon, handlerSchema.displaydata.title);
const handler = new CoreSitePluginsUserProfileHandler(uniqueName, prefixedTitle, plugin, handlerSchema, initResult);
CoreUserDelegate.registerHandler(handler);
@@ -1182,8 +1179,6 @@ export class CoreSitePluginsHelperProvider {
/**
* Reload the handlers that are restricted to certain courses.
- *
- * @returns Promise resolved when done.
*/
protected async reloadCourseRestrictHandlers(): Promise {
if (!Object.keys(this.courseRestrictHandlers).length) {
@@ -1240,7 +1235,7 @@ export class CoreSitePluginsHelperProvider {
// Create and register the handler.
const uniqueName = CoreSitePlugins.getHandlerUniqueName(plugin, handlerName);
- const prefixedTitle = this.getPrefixedString(plugin.addon, handlerSchema.displaydata.title || 'pluginname');
+ const prefixedTitle = this.getPrefixedString(plugin.addon, handlerSchema.displaydata.title);
CoreMainMenuHomeDelegate.registerHandler(
new CoreSitePluginsMainMenuHomeHandler(uniqueName, prefixedTitle, plugin, handlerSchema, initResult),
@@ -1251,4 +1246,4 @@ export class CoreSitePluginsHelperProvider {
}
-export const CoreSitePluginsHelper = makeSingleton(CoreSitePluginsHelperProvider);
+export const CoreSitePluginsInit = makeSingleton(CoreSitePluginsInitService);
diff --git a/src/core/features/siteplugins/services/siteplugins.ts b/src/core/features/siteplugins/services/siteplugins.ts
index 014e0faa8..30deff990 100644
--- a/src/core/features/siteplugins/services/siteplugins.ts
+++ b/src/core/features/siteplugins/services/siteplugins.ts
@@ -34,9 +34,7 @@ import { CorePlatform } from '@services/platform';
import { CoreEnrolAction, CoreEnrolInfoIcon } from '@features/enrol/services/enrol-delegate';
import { CoreSiteWSPreSets } from '@classes/sites/authenticated-site';
import { CoreUserProfileHandlerType } from '@features/user/services/user-delegate';
-import { CORE_SITE_PLUGINS_UPDATE_COURSE_CONTENT } from '../constants';
-
-const ROOT_CACHE_KEY = 'CoreSitePlugins:';
+import { CORE_SITE_PLUGINS_COMPONENT, CORE_SITE_PLUGINS_UPDATE_COURSE_CONTENT } from '../constants';
/**
* Service to provide functionalities regarding site plugins.
@@ -44,7 +42,14 @@ const ROOT_CACHE_KEY = 'CoreSitePlugins:';
@Injectable({ providedIn: 'root' })
export class CoreSitePluginsProvider {
- static readonly COMPONENT = 'CoreSitePlugins';
+ protected static readonly ROOT_CACHE_KEY = 'CoreSitePlugins:';
+ /**
+ * @deprecated since 4.5.0. Use CORE_SITE_PLUGINS_COMPONENT instead.
+ */
+ static readonly COMPONENT = CORE_SITE_PLUGINS_COMPONENT;
+ /**
+ * @deprecated since 4.5.0. Use CORE_SITE_PLUGINS_UPDATE_COURSE_CONTENT instead.
+ */
static readonly UPDATE_COURSE_CONTENT = CORE_SITE_PLUGINS_UPDATE_COURSE_CONTENT;
protected logger: CoreLogger;
@@ -183,7 +188,7 @@ export class CoreSitePluginsProvider {
* @returns Cache key.
*/
protected getCallWSCommonCacheKey(method: string): string {
- return ROOT_CACHE_KEY + 'ws:' + method;
+ return CoreSitePluginsProvider.ROOT_CACHE_KEY + 'ws:' + method;
}
/**
@@ -250,7 +255,8 @@ export class CoreSitePluginsProvider {
* @returns Cache key.
*/
protected getContentCacheKey(component: string, method: string, args: Record): string {
- return ROOT_CACHE_KEY + 'content:' + component + ':' + method + ':' + CoreUtils.sortAndStringify(args);
+ return CoreSitePluginsProvider.ROOT_CACHE_KEY + 'content:' + component + ':' + method +
+ ':' + CoreUtils.sortAndStringify(args);
}
/**
@@ -322,7 +328,7 @@ export class CoreSitePluginsProvider {
* @returns Cache key.
*/
protected getPluginsCacheKey(): string {
- return ROOT_CACHE_KEY + 'plugins';
+ return CoreSitePluginsProvider.ROOT_CACHE_KEY + 'plugins';
}
/**
diff --git a/src/core/features/siteplugins/siteplugins.module.ts b/src/core/features/siteplugins/siteplugins.module.ts
index 8a58ea4bb..187e36337 100644
--- a/src/core/features/siteplugins/siteplugins.module.ts
+++ b/src/core/features/siteplugins/siteplugins.module.ts
@@ -20,12 +20,13 @@ import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-ro
import { CoreMainMenuHomeRoutingModule } from '@features/mainmenu/mainmenu-home-routing.module';
import { CoreSitePreferencesRoutingModule } from '@features/settings/settings-site-routing.module';
import { CoreSitePluginsComponentsModule } from './components/components.module';
-import { CoreSitePluginsHelper } from './services/siteplugins-helper';
+import { CoreSitePluginsInit } from './services/siteplugins-init';
import { CoreSharedModule } from '@/core/shared.module';
import { CoreSitePluginsPluginPage } from '@features/siteplugins/pages/plugin/plugin';
import { canLeaveGuard } from '@guards/can-leave';
import { CoreSitePluginsCourseOptionPage } from '@features/siteplugins/pages/course-option/course-option';
import { CoreSitePluginsModuleIndexPage } from '@features/siteplugins/pages/module-index/module-index';
+import { CORE_SITE_PLUGINS_PATH } from './constants';
/**
* Get site plugins exported objects.
@@ -65,7 +66,7 @@ export async function getSitePluginsExportedObjects(): Promise {
- CoreSitePluginsHelper.initialize();
+ CoreSitePluginsInit.init();
},
},
],
diff --git a/src/core/services/platform.ts b/src/core/services/platform.ts
index c08089d06..61b1d7194 100644
--- a/src/core/services/platform.ts
+++ b/src/core/services/platform.ts
@@ -22,6 +22,8 @@ import { Device, makeSingleton } from '@singletons';
@Injectable({ providedIn: 'root' })
export class CorePlatformService extends Platform {
+ private static cssNesting?: boolean;
+
/**
* Get platform major version number.
*
@@ -117,6 +119,38 @@ export class CorePlatformService extends Platform {
return 'WebAssembly' in window;
}
+ /**
+ * Check if the browser supports CSS nesting.
+ *
+ * @returns Whether the browser supports CSS nesting.
+ */
+ supportsCSSNesting(): boolean {
+ if (CorePlatformService.cssNesting !== undefined) {
+ return CorePlatformService.cssNesting;
+ }
+
+ // Add nested CSS to DOM and check if it's supported.
+ const style = document.createElement('style');
+ style.innerHTML = 'div.nested { &.css { color: red; } }';
+ document.head.appendChild(style);
+
+ // Add an element to check if the nested CSS is applied.
+ const div = document.createElement('div');
+ div.className = 'nested css';
+ document.body.appendChild(div);
+
+ const color = window.getComputedStyle(div).color;
+
+ // Check if color is red.
+ CorePlatformService.cssNesting = color === 'rgb(255, 0, 0)';
+
+ // Clean the DOM.
+ document.head.removeChild(style);
+ document.body.removeChild(div);
+
+ return CorePlatformService.cssNesting;
+ }
+
}
export const CorePlatform = makeSingleton(CorePlatformService);
diff --git a/src/core/singletons/dom.ts b/src/core/singletons/dom.ts
index f52d8e5f5..ff22077c0 100644
--- a/src/core/singletons/dom.ts
+++ b/src/core/singletons/dom.ts
@@ -16,6 +16,7 @@ import { CoreCancellablePromise } from '@classes/cancellable-promise';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreUtils } from '@services/utils/utils';
import { CoreEventObserver } from '@singletons/events';
+import { CorePlatform } from '@services/platform';
/**
* Singleton with helper functions for dom.
@@ -684,6 +685,44 @@ export class CoreDom {
return element;
}
+ /**
+ * Prefix CSS rules.
+ *
+ * @param css CSS code.
+ * @param prefix Prefix to add to CSS rules.
+ * @param prefixIfNested Prefix to add to CSS rules if nested. It may happend we need different prefixes.
+ * Ie: If nested is supported ::ng-deep is not needed.
+ * @returns Prefixed CSS.
+ */
+ static prefixCSS(css: string, prefix: string, prefixIfNested?: string): string {
+ if (!css) {
+ return '';
+ }
+
+ if (!prefix) {
+ return css;
+ }
+
+ // Check if browser supports CSS nesting.
+ const supportsNesting = CorePlatform.supportsCSSNesting();
+ if (supportsNesting) {
+ prefixIfNested = prefixIfNested ?? prefix;
+
+ // Wrap the CSS with the prefix.
+ return `${prefixIfNested} { ${css} }`;
+ }
+
+ // Fallback.
+ // Remove comments first.
+ let regExp = /\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*$/gm;
+ css = css.replace(regExp, '');
+
+ // Add prefix.
+ regExp = /([^]*?)({[^]*?}|,)/g;
+
+ return css.replace(regExp, prefix + ' $1 $2');
+ }
+
}
/**