MOBILE-4632 database: Load styles from site plugins
parent
41879a3526
commit
eb9935a1d7
|
@ -75,9 +75,7 @@
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<div class="addon-data-contents addon-data-entries addon-data-entries-{{database.id}}" *ngIf="!isEmpty && database">
|
<div class="addon-data-contents addon-data-entries addon-data-entries-{{database.id}}" *ngIf="!isEmpty && database">
|
||||||
<core-style [css]="database.csstemplate" prefix="div.addon-data-entries.addon-data-entries-{{database.id}}" />
|
<core-compile-html [text]="entriesRendered" [jsData]="jsData" [extraImports]="extraImports" [cssCode]="database.csstemplate" />
|
||||||
|
|
||||||
<core-compile-html [text]="entriesRendered" [jsData]="jsData" [extraImports]="extraImports" />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -21,10 +21,9 @@
|
||||||
[courseId]="database?.course" />
|
[courseId]="database?.course" />
|
||||||
|
|
||||||
<div class="addon-data-contents addon-data-edit-entry {{cssClass}}" *ngIf="database">
|
<div class="addon-data-contents addon-data-edit-entry {{cssClass}}" *ngIf="database">
|
||||||
<core-style [css]="database.csstemplate" prefix="div.addon-data-edit-entry.{{cssClass}}" />
|
|
||||||
|
|
||||||
<form (ngSubmit)="save($event)" [formGroup]="editForm" #editFormEl>
|
<form (ngSubmit)="save($event)" [formGroup]="editForm" #editFormEl>
|
||||||
<core-compile-html [text]="editFormRender" [jsData]="jsData" [extraImports]="extraImports" />
|
<core-compile-html [text]="editFormRender" [jsData]="jsData" [extraImports]="extraImports"
|
||||||
|
[cssCode]="database.csstemplate" />
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</core-loading>
|
</core-loading>
|
||||||
|
|
|
@ -28,9 +28,8 @@
|
||||||
[courseId]="courseId" />
|
[courseId]="courseId" />
|
||||||
|
|
||||||
<div class="addon-data-contents addon-data-entry addon-data-entries-{{database.id}}" *ngIf="database && entry">
|
<div class="addon-data-contents addon-data-entry addon-data-entries-{{database.id}}" *ngIf="database && entry">
|
||||||
<core-style [css]="database.csstemplate" prefix="div.addon-data-entry.addon-data-entries-{{database.id}}" />
|
<core-compile-html [text]="entryHtml" [jsData]="jsData" [extraImports]="extraImports" (compiling)="setRenderingEntry($event)"
|
||||||
|
[cssCode]="database.csstemplate" />
|
||||||
<core-compile-html [text]="entryHtml" [jsData]="jsData" [extraImports]="extraImports" (compiling)="setRenderingEntry($event)" />
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<core-rating-rate *ngIf="database && entry && ratingInfo && (!database.approval || entry.approved)" [ratingInfo]="ratingInfo"
|
<core-rating-rate *ngIf="database && entry && ratingInfo && (!database.approval || entry.approved)" [ratingInfo]="ratingInfo"
|
||||||
|
|
|
@ -100,6 +100,7 @@ import { CoreSitesListComponent } from './sites-list/sites-list';
|
||||||
CoreShowPasswordComponent,
|
CoreShowPasswordComponent,
|
||||||
CoreSitePickerComponent,
|
CoreSitePickerComponent,
|
||||||
CoreSplitViewComponent,
|
CoreSplitViewComponent,
|
||||||
|
// eslint-disable-next-line deprecation/deprecation
|
||||||
CoreStyleComponent,
|
CoreStyleComponent,
|
||||||
CoreSwipeSlidesComponent,
|
CoreSwipeSlidesComponent,
|
||||||
CoreTabComponent,
|
CoreTabComponent,
|
||||||
|
@ -155,6 +156,7 @@ import { CoreSitesListComponent } from './sites-list/sites-list';
|
||||||
CoreShowPasswordComponent,
|
CoreShowPasswordComponent,
|
||||||
CoreSitePickerComponent,
|
CoreSitePickerComponent,
|
||||||
CoreSplitViewComponent,
|
CoreSplitViewComponent,
|
||||||
|
// eslint-disable-next-line deprecation/deprecation
|
||||||
CoreStyleComponent,
|
CoreStyleComponent,
|
||||||
CoreSwipeSlidesComponent,
|
CoreSwipeSlidesComponent,
|
||||||
CoreTabComponent,
|
CoreTabComponent,
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Component, ElementRef, Input, OnChanges } from '@angular/core';
|
import { Component, ElementRef, Input, OnChanges } from '@angular/core';
|
||||||
|
import { CoreDom } from '@singletons/dom';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component to add a <style> tag.
|
* Component to add a <style> tag.
|
||||||
|
@ -23,6 +24,7 @@ import { Component, ElementRef, Input, OnChanges } from '@angular/core';
|
||||||
* Example:
|
* Example:
|
||||||
*
|
*
|
||||||
* <core-style [css]="'p { color: red; }'" prefix=".custom-rules"></core-style>
|
* <core-style [css]="'p { color: red; }'" prefix=".custom-rules"></core-style>
|
||||||
|
* @deprecated since 4.5.0. Not needed anymore, core-compile-html accepts now CSS code.
|
||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'core-style',
|
selector: 'core-style',
|
||||||
|
@ -41,37 +43,11 @@ export class CoreStyleComponent implements OnChanges {
|
||||||
ngOnChanges(): void {
|
ngOnChanges(): void {
|
||||||
if (this.element && this.element.nativeElement) {
|
if (this.element && this.element.nativeElement) {
|
||||||
const style = document.createElement('style');
|
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.innerHTML = '';
|
||||||
this.element.nativeElement.appendChild(style);
|
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');
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,6 +38,8 @@ import { CorePromisedValue } from '@classes/promised-value';
|
||||||
import { CoreCompile } from '@features/compile/services/compile';
|
import { CoreCompile } from '@features/compile/services/compile';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
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
|
* 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() text!: string; // The HTML text to display.
|
||||||
@Input() javascript?: string; // The Javascript to execute in the component.
|
@Input() javascript?: string; // The Javascript to execute in the component.
|
||||||
@Input() jsData?: Record<string, unknown>; // Data to pass to the fake component.
|
@Input() jsData?: Record<string, unknown>; // 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() extraImports: unknown[] = []; // Extra import modules.
|
||||||
@Input() extraProviders: Type<unknown>[] = []; // Extra providers.
|
@Input() extraProviders: Type<unknown>[] = []; // Extra providers.
|
||||||
@Input() forceCompile = false; // Set it to true to force compile even if the text/javascript hasn't changed.
|
@Input() forceCompile = false; // Set it to true to force compile even if the text/javascript hasn't changed.
|
||||||
|
@ -101,14 +105,15 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy, DoCheck {
|
||||||
|
|
||||||
// Check if there's any change in the jsData object.
|
// Check if there's any change in the jsData object.
|
||||||
const changes = this.differ.diff(this.jsData || {});
|
const changes = this.differ.diff(this.jsData || {});
|
||||||
if (changes) {
|
if (!changes) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.setInputData();
|
this.setInputData();
|
||||||
|
|
||||||
if (this.componentInstance.ngOnChanges) {
|
if (this.componentInstance.ngOnChanges) {
|
||||||
this.componentInstance.ngOnChanges(CoreDomUtils.createChangesFromKeyValueDiff(changes));
|
this.componentInstance.ngOnChanges(CoreDomUtils.createChangesFromKeyValueDiff(changes));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
|
@ -116,7 +121,8 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy, DoCheck {
|
||||||
async ngOnChanges(changes: Record<string, SimpleChange>): Promise<void> {
|
async ngOnChanges(changes: Record<string, SimpleChange>): Promise<void> {
|
||||||
// Only compile if text/javascript has changed or the forceCompile flag has been set to true.
|
// Only compile if text/javascript has changed or the forceCompile flag has been set to true.
|
||||||
if (this.text === undefined ||
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -132,11 +138,14 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy, DoCheck {
|
||||||
|
|
||||||
// Create the component.
|
// Create the component.
|
||||||
if (this.container) {
|
if (this.container) {
|
||||||
|
await this.loadCSSCode();
|
||||||
|
|
||||||
this.componentRef = await CoreCompile.createAndCompileComponent(
|
this.componentRef = await CoreCompile.createAndCompileComponent(
|
||||||
this.text,
|
this.text,
|
||||||
componentClass,
|
componentClass,
|
||||||
this.container,
|
this.container,
|
||||||
this.extraImports,
|
this.extraImports,
|
||||||
|
this.cssCode,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.element.addEventListener('submit', (event) => {
|
this.element.addEventListener('submit', (event) => {
|
||||||
|
@ -163,6 +172,29 @@ export class CoreCompileHtmlComponent implements OnChanges, OnDestroy, DoCheck {
|
||||||
this.componentRef?.destroy();
|
this.componentRef?.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the CSS code from the stylesPath if not loaded yet.
|
||||||
|
*/
|
||||||
|
protected async loadCSSCode(): Promise<void> {
|
||||||
|
// 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.
|
* Get a class that defines the dynamic component.
|
||||||
*
|
*
|
||||||
|
|
|
@ -175,6 +175,7 @@ export class CoreCompileProvider {
|
||||||
* @param componentClass The JS class of the component.
|
* @param componentClass The JS class of the component.
|
||||||
* @param viewContainerRef View container reference to inject 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 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.
|
* @returns Promise resolved with the component reference.
|
||||||
*/
|
*/
|
||||||
async createAndCompileComponent<T = unknown>(
|
async createAndCompileComponent<T = unknown>(
|
||||||
|
@ -182,12 +183,17 @@ export class CoreCompileProvider {
|
||||||
componentClass: Type<T>,
|
componentClass: Type<T>,
|
||||||
viewContainerRef: ViewContainerRef,
|
viewContainerRef: ViewContainerRef,
|
||||||
extraImports: any[] = [], // eslint-disable-line @typescript-eslint/no-explicit-any
|
extraImports: any[] = [], // eslint-disable-line @typescript-eslint/no-explicit-any
|
||||||
|
styles?: string,
|
||||||
): Promise<ComponentRef<T> | undefined> {
|
): Promise<ComponentRef<T> | undefined> {
|
||||||
// Import the Angular compiler to be able to compile components in runtime.
|
// Import the Angular compiler to be able to compile components in runtime.
|
||||||
await import('@angular/compiler');
|
await import('@angular/compiler');
|
||||||
|
|
||||||
// Create the component using the template and the class.
|
// 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 lazyImports = await Promise.all(this.LAZY_IMPORTS.map(getModules => getModules()));
|
||||||
const imports = [
|
const imports = [
|
||||||
|
|
|
@ -127,7 +127,7 @@ export class CoreSitePluginsCallWSBaseDirective implements OnInit, OnDestroy {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Directive destroyed.
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.invalidateObserver?.unsubscribe();
|
this.invalidateObserver?.unsubscribe();
|
||||||
|
|
|
@ -22,6 +22,8 @@ import { Device, makeSingleton } from '@singletons';
|
||||||
@Injectable({ providedIn: 'root' })
|
@Injectable({ providedIn: 'root' })
|
||||||
export class CorePlatformService extends Platform {
|
export class CorePlatformService extends Platform {
|
||||||
|
|
||||||
|
private static cssNesting?: boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get platform major version number.
|
* Get platform major version number.
|
||||||
*
|
*
|
||||||
|
@ -117,6 +119,38 @@ export class CorePlatformService extends Platform {
|
||||||
return 'WebAssembly' in window;
|
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);
|
export const CorePlatform = makeSingleton(CorePlatformService);
|
||||||
|
|
|
@ -16,6 +16,7 @@ import { CoreCancellablePromise } from '@classes/cancellable-promise';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { CoreEventObserver } from '@singletons/events';
|
import { CoreEventObserver } from '@singletons/events';
|
||||||
|
import { CorePlatform } from '@services/platform';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Singleton with helper functions for dom.
|
* Singleton with helper functions for dom.
|
||||||
|
@ -684,6 +685,44 @@ export class CoreDom {
|
||||||
return element;
|
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');
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue