commit
6fe91f8aa1
|
@ -26,7 +26,6 @@
|
||||||
min-height: var(--course-storage-max-activity-height);
|
min-height: var(--course-storage-max-activity-height);
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@include position(0, 0, null, 0);
|
@include position(0, 0, null, 0);
|
||||||
background: -webkit-linear-gradient(top, rgba(var(--background-gradient-rgb), 0) calc(100% - 30px), rgba(var(--background-gradient-rgb), 1) calc(100% - 20px));
|
|
||||||
background: linear-gradient(to bottom, rgba(var(--background-gradient-rgb), 0) calc(100% - 30px), rgba(var(--background-gradient-rgb), 1) calc(100% - 20px));
|
background: linear-gradient(to bottom, rgba(var(--background-gradient-rgb), 0) calc(100% - 30px), rgba(var(--background-gradient-rgb), 1) calc(100% - 20px));
|
||||||
z-index: 6;
|
z-index: 6;
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
|
|
|
@ -90,8 +90,8 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo
|
||||||
|
|
||||||
protected element: HTMLElement;
|
protected element: HTMLElement;
|
||||||
protected emptyText = '';
|
protected emptyText = '';
|
||||||
protected contentSpan: HTMLElement;
|
protected domPromises: CoreCancellablePromise<void>[] = [];
|
||||||
protected domPromise?: CoreCancellablePromise<void>;
|
protected domElementPromise?: CoreCancellablePromise<void>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
element: ElementRef,
|
element: ElementRef,
|
||||||
|
@ -101,18 +101,10 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo
|
||||||
CoreComponentsRegistry.register(element.nativeElement, this);
|
CoreComponentsRegistry.register(element.nativeElement, this);
|
||||||
|
|
||||||
this.element = element.nativeElement;
|
this.element = element.nativeElement;
|
||||||
this.element.classList.add('core-format-text-loading'); // Hide contents until they're treated.
|
this.element.classList.add('core-loading'); // Hide contents until they're treated.
|
||||||
|
|
||||||
const placeholder = document.createElement('span');
|
|
||||||
placeholder.classList.add('core-format-text-loader');
|
|
||||||
this.element.appendChild(placeholder);
|
|
||||||
|
|
||||||
this.contentSpan = document.createElement('span');
|
|
||||||
this.contentSpan.classList.add('core-format-text-content');
|
|
||||||
this.element.appendChild(this.contentSpan);
|
|
||||||
|
|
||||||
this.emptyText = this.hideIfEmpty ? '' : ' ';
|
this.emptyText = this.hideIfEmpty ? '' : ' ';
|
||||||
this.contentSpan.innerHTML = this.emptyText;
|
this.element.innerHTML = this.emptyText;
|
||||||
|
|
||||||
this.afterRender = new EventEmitter<void>();
|
this.afterRender = new EventEmitter<void>();
|
||||||
|
|
||||||
|
@ -134,14 +126,15 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
ngOnDestroy(): void {
|
ngOnDestroy(): void {
|
||||||
this.domPromise?.cancel();
|
this.domElementPromise?.cancel();
|
||||||
|
this.domPromises.forEach((promise) => { promise.cancel();});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
async ready(): Promise<void> {
|
async ready(): Promise<void> {
|
||||||
if (!this.element.classList.contains('core-format-text-loading')) {
|
if (!this.element.classList.contains('core-loading')) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,7 +222,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo
|
||||||
* Add magnifying glass icons to view adapted images at full size.
|
* Add magnifying glass icons to view adapted images at full size.
|
||||||
*/
|
*/
|
||||||
async addMagnifyingGlasses(): Promise<void> {
|
async addMagnifyingGlasses(): Promise<void> {
|
||||||
const imgs = Array.from(this.contentSpan.querySelectorAll('.core-adapted-img-container > img'));
|
const imgs = Array.from(this.element.querySelectorAll('.core-adapted-img-container > img'));
|
||||||
if (!imgs.length) {
|
if (!imgs.length) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -311,7 +304,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo
|
||||||
*/
|
*/
|
||||||
protected async finishRender(): Promise<void> {
|
protected async finishRender(): Promise<void> {
|
||||||
// Show the element again.
|
// Show the element again.
|
||||||
this.element.classList.remove('core-format-text-loading');
|
this.element.classList.remove('core-loading');
|
||||||
|
|
||||||
await CoreUtils.nextTick();
|
await CoreUtils.nextTick();
|
||||||
|
|
||||||
|
@ -324,7 +317,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo
|
||||||
*/
|
*/
|
||||||
protected async formatAndRenderContents(): Promise<void> {
|
protected async formatAndRenderContents(): Promise<void> {
|
||||||
if (!this.text) {
|
if (!this.text) {
|
||||||
this.contentSpan.innerHTML = this.emptyText; // Remove current contents.
|
this.element.innerHTML = this.emptyText; // Remove current contents.
|
||||||
|
|
||||||
await this.finishRender();
|
await this.finishRender();
|
||||||
|
|
||||||
|
@ -342,10 +335,10 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo
|
||||||
// Disable media adapt to correctly calculate the height.
|
// Disable media adapt to correctly calculate the height.
|
||||||
this.element.classList.add('core-disable-media-adapt');
|
this.element.classList.add('core-disable-media-adapt');
|
||||||
|
|
||||||
this.contentSpan.innerHTML = ''; // Remove current contents.
|
this.element.innerHTML = ''; // Remove current contents.
|
||||||
|
|
||||||
// Move the children to the current element to be able to calculate the height.
|
// Move the children to the current element to be able to calculate the height.
|
||||||
CoreDomUtils.moveChildren(result.div, this.contentSpan);
|
CoreDomUtils.moveChildren(result.div, this.element);
|
||||||
|
|
||||||
await CoreUtils.nextTick();
|
await CoreUtils.nextTick();
|
||||||
|
|
||||||
|
@ -362,7 +355,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo
|
||||||
if (result.options.filter) {
|
if (result.options.filter) {
|
||||||
// Let filters handle HTML. We do it here because we don't want them to block the render of the text.
|
// Let filters handle HTML. We do it here because we don't want them to block the render of the text.
|
||||||
CoreFilterDelegate.handleHtml(
|
CoreFilterDelegate.handleHtml(
|
||||||
this.contentSpan,
|
this.element,
|
||||||
result.filters,
|
result.filters,
|
||||||
this.viewContainerRef,
|
this.viewContainerRef,
|
||||||
result.options,
|
result.options,
|
||||||
|
@ -557,9 +550,10 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo
|
||||||
* @return The width of the element in pixels.
|
* @return The width of the element in pixels.
|
||||||
*/
|
*/
|
||||||
protected async getElementWidth(): Promise<number> {
|
protected async getElementWidth(): Promise<number> {
|
||||||
this.domPromise = CoreDomUtils.waitToBeInDOM(this.element);
|
if (!this.domElementPromise) {
|
||||||
|
this.domElementPromise = CoreDomUtils.waitToBeInDOM(this.element);
|
||||||
await this.domPromise;
|
}
|
||||||
|
await this.domElementPromise;
|
||||||
|
|
||||||
let width = this.element.getBoundingClientRect().width;
|
let width = this.element.getBoundingClientRect().width;
|
||||||
if (!width) {
|
if (!width) {
|
||||||
|
@ -709,12 +703,15 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo
|
||||||
newUrl += `&h=${privacyHash}`;
|
newUrl += `&h=${privacyHash}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const domPromise = CoreDomUtils.waitToBeInDOM(iframe);
|
||||||
|
this.domPromises.push(domPromise);
|
||||||
|
|
||||||
|
await domPromise;
|
||||||
|
|
||||||
// Width and height are mandatory, we need to calculate them.
|
// Width and height are mandatory, we need to calculate them.
|
||||||
let width: string | number;
|
let width: string | number;
|
||||||
let height: string | number;
|
let height: string | number;
|
||||||
|
|
||||||
await CoreDomUtils.waitToBeInDOM(iframe, 5000);
|
|
||||||
|
|
||||||
if (iframe.width) {
|
if (iframe.width) {
|
||||||
width = iframe.width;
|
width = iframe.width;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -37,7 +37,7 @@ export class CoreOnAppearDirective implements OnInit, OnDestroy {
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
async ngOnInit(): Promise<void> {
|
async ngOnInit(): Promise<void> {
|
||||||
this.visiblePromise = CoreDomUtils.waitToBeVisible(this.element);
|
this.visiblePromise = CoreDomUtils.waitToBeInViewport(this.element);
|
||||||
|
|
||||||
await this.visiblePromise;
|
await this.visiblePromise;
|
||||||
|
|
||||||
|
|
|
@ -58,7 +58,7 @@ describe('CoreFormatTextDirective', () => {
|
||||||
);
|
);
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
const text = fixture.nativeElement.querySelector('core-format-text .core-format-text-content');
|
const text = fixture.nativeElement.querySelector('core-format-text');
|
||||||
expect(text).not.toBeNull();
|
expect(text).not.toBeNull();
|
||||||
expect(text.innerHTML).toEqual(sentence);
|
expect(text.innerHTML).toEqual(sentence);
|
||||||
});
|
});
|
||||||
|
|
|
@ -29,7 +29,9 @@
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<ion-label>
|
<ion-label>
|
||||||
<h1>{{ title }}</h1>
|
<h1>
|
||||||
|
<core-format-text [text]="title" contextLevel="course" [contextInstanceId]="course?.id"></core-format-text>
|
||||||
|
</h1>
|
||||||
<div class="core-course-progress" *ngIf="progress !== undefined">
|
<div class="core-course-progress" *ngIf="progress !== undefined">
|
||||||
<core-progress-bar [progress]="progress" a11yText="core.course.aria:sectionprogress">
|
<core-progress-bar [progress]="progress" a11yText="core.course.aria:sectionprogress">
|
||||||
</core-progress-bar>
|
</core-progress-bar>
|
||||||
|
|
|
@ -110,7 +110,7 @@
|
||||||
</button>
|
</button>
|
||||||
</ion-slide>
|
</ion-slide>
|
||||||
<ion-slide *ngIf="isPhone">
|
<ion-slide *ngIf="isPhone">
|
||||||
<button [title]="'core.editor.hidetoolbar' | translate" (click)="hideToolbar($event)" (keyup)="hideToolbar($event)"
|
<button [title]="'core.editor.hidetoolbar' | translate" (click)="hideToolbar($event, true)" (keyup)="hideToolbar($event, true)"
|
||||||
(mousedown)="downAction($event)" (keydown)="downAction($event)">
|
(mousedown)="downAction($event)" (keydown)="downAction($event)">
|
||||||
<ion-icon name="fas-times" aria-hidden="true"></ion-icon>
|
<ion-icon name="fas-times" aria-hidden="true"></ion-icon>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -107,6 +107,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
|
||||||
protected languageChangedSubscription?: Subscription;
|
protected languageChangedSubscription?: Subscription;
|
||||||
protected resizeListener?: CoreEventObserver;
|
protected resizeListener?: CoreEventObserver;
|
||||||
protected domPromise?: CoreCancellablePromise<void>;
|
protected domPromise?: CoreCancellablePromise<void>;
|
||||||
|
protected buttonsDomPromise?: CoreCancellablePromise<void>;
|
||||||
|
|
||||||
rteEnabled = false;
|
rteEnabled = false;
|
||||||
isPhone = false;
|
isPhone = false;
|
||||||
|
@ -176,8 +177,6 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
|
||||||
this.editorElement.oninput = this.onChange.bind(this);
|
this.editorElement.oninput = this.onChange.bind(this);
|
||||||
this.editorElement.onkeydown = this.moveCursor.bind(this);
|
this.editorElement.onkeydown = this.moveCursor.bind(this);
|
||||||
|
|
||||||
await CoreDomUtils.waitToBeVisible(this.editorElement);
|
|
||||||
|
|
||||||
// Use paragraph on enter.
|
// Use paragraph on enter.
|
||||||
document.execCommand('DefaultParagraphSeparator', false, 'p');
|
document.execCommand('DefaultParagraphSeparator', false, 'p');
|
||||||
|
|
||||||
|
@ -339,7 +338,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.isNullOrWhiteSpace(this.editorElement.innerText)) {
|
if (this.isNullOrWhiteSpace(this.editorElement.textContent)) {
|
||||||
this.clearText();
|
this.clearText();
|
||||||
} else {
|
} else {
|
||||||
// The textarea and the form control must receive the original URLs.
|
// The textarea and the form control must receive the original URLs.
|
||||||
|
@ -718,8 +717,17 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hide the toolbar in phone mode.
|
* Hide the toolbar in phone mode.
|
||||||
|
*
|
||||||
|
* @param event Event.
|
||||||
|
* @param force If true it will not check the target of the event.
|
||||||
*/
|
*/
|
||||||
hideToolbar(event: Event): void {
|
hideToolbar(event: Event, force = false): void {
|
||||||
|
if (!force && event.target && this.element.contains(event.target as HTMLElement)) {
|
||||||
|
// Do not hide if clicked inside the editor area, except forced.
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (event.type == 'keyup' && !this.isValidKeyboardKey(<KeyboardEvent>event)) {
|
if (event.type == 'keyup' && !this.isValidKeyboardKey(<KeyboardEvent>event)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -836,7 +844,10 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
|
||||||
|
|
||||||
const length = await this.toolbarSlides.length();
|
const length = await this.toolbarSlides.length();
|
||||||
|
|
||||||
await CoreDomUtils.waitToBeInDOM(this.toolbar.nativeElement, 5000);
|
// Cancel previous one, if any.
|
||||||
|
this.buttonsDomPromise?.cancel();
|
||||||
|
this.buttonsDomPromise = CoreDomUtils.waitToBeInDOM(this.toolbar.nativeElement);
|
||||||
|
await this.buttonsDomPromise;
|
||||||
|
|
||||||
const width = this.toolbar.nativeElement.getBoundingClientRect().width;
|
const width = this.toolbar.nativeElement.getBoundingClientRect().width;
|
||||||
|
|
||||||
|
@ -1097,6 +1108,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterContentIn
|
||||||
this.labelObserver?.disconnect();
|
this.labelObserver?.disconnect();
|
||||||
this.resizeListener?.off();
|
this.resizeListener?.off();
|
||||||
this.domPromise?.cancel();
|
this.domPromise?.cancel();
|
||||||
|
this.buttonsDomPromise?.cancel();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,10 +97,9 @@ export class CoreDomUtilsProvider {
|
||||||
* Wait an element to be in dom of another element.
|
* Wait an element to be in dom of another element.
|
||||||
*
|
*
|
||||||
* @param element Element to wait.
|
* @param element Element to wait.
|
||||||
* @param timeout If defined, timeout to wait before rejecting the promise.
|
|
||||||
* @return Cancellable promise.
|
* @return Cancellable promise.
|
||||||
*/
|
*/
|
||||||
waitToBeInDOM(element: HTMLElement, timeout?: number): CoreCancellablePromise<void> {
|
waitToBeInDOM(element: HTMLElement): CoreCancellablePromise<void> {
|
||||||
const root = element.getRootNode({ composed: true });
|
const root = element.getRootNode({ composed: true });
|
||||||
|
|
||||||
if (root === document) {
|
if (root === document) {
|
||||||
|
@ -109,33 +108,24 @@ export class CoreDomUtilsProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
let observer: MutationObserver;
|
let observer: MutationObserver;
|
||||||
let observerTimeout: number | undefined;
|
|
||||||
|
|
||||||
return new CoreCancellablePromise<void>(
|
return new CoreCancellablePromise<void>(
|
||||||
(resolve, reject) => {
|
(resolve) => {
|
||||||
observer = new MutationObserver(() => {
|
observer = new MutationObserver(() => {
|
||||||
const root = element.getRootNode({ composed: true });
|
const root = element.getRootNode({ composed: true });
|
||||||
|
|
||||||
if (root === document) {
|
if (root !== document) {
|
||||||
observer?.disconnect();
|
return;
|
||||||
observerTimeout && clearTimeout(observerTimeout);
|
|
||||||
resolve();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
observer?.disconnect();
|
||||||
|
resolve();
|
||||||
});
|
});
|
||||||
|
|
||||||
if (timeout) {
|
|
||||||
observerTimeout = window.setTimeout(() => {
|
|
||||||
observer?.disconnect();
|
|
||||||
|
|
||||||
reject(new Error('Waiting for DOM timeout reached'));
|
|
||||||
}, timeout);
|
|
||||||
}
|
|
||||||
|
|
||||||
observer.observe(document.body, { subtree: true, childList: true });
|
observer.observe(document.body, { subtree: true, childList: true });
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
observer?.disconnect();
|
observer?.disconnect();
|
||||||
observerTimeout && clearTimeout(observerTimeout);
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -151,6 +141,7 @@ export class CoreDomUtilsProvider {
|
||||||
|
|
||||||
let interval: number | undefined;
|
let interval: number | undefined;
|
||||||
|
|
||||||
|
// Mutations did not observe for visibility properties.
|
||||||
return new CoreCancellablePromise<void>(
|
return new CoreCancellablePromise<void>(
|
||||||
async (resolve) => {
|
async (resolve) => {
|
||||||
await domPromise;
|
await domPromise;
|
||||||
|
@ -175,6 +166,60 @@ export class CoreDomUtilsProvider {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait an element to be in dom and visible.
|
||||||
|
*
|
||||||
|
* @param element Element to wait.
|
||||||
|
* @param intersectionRatio Intersection ratio (From 0 to 1).
|
||||||
|
* @return Cancellable promise.
|
||||||
|
*/
|
||||||
|
waitToBeInViewport(element: HTMLElement, intersectionRatio = 1): CoreCancellablePromise<void> {
|
||||||
|
const visiblePromise = CoreDomUtils.waitToBeVisible(element);
|
||||||
|
|
||||||
|
let intersectionObserver: IntersectionObserver;
|
||||||
|
let interval: number | undefined;
|
||||||
|
|
||||||
|
return new CoreCancellablePromise<void>(
|
||||||
|
async (resolve) => {
|
||||||
|
await visiblePromise;
|
||||||
|
|
||||||
|
if (CoreDomUtils.isElementInViewport(element, intersectionRatio)) {
|
||||||
|
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
if ('IntersectionObserver' in window) {
|
||||||
|
intersectionObserver = new IntersectionObserver((observerEntries) => {
|
||||||
|
const isIntersecting = observerEntries
|
||||||
|
.some((entry) => entry.isIntersecting && entry.intersectionRatio >= intersectionRatio);
|
||||||
|
if (!isIntersecting) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
intersectionObserver?.disconnect();
|
||||||
|
});
|
||||||
|
|
||||||
|
intersectionObserver.observe(element);
|
||||||
|
} else {
|
||||||
|
interval = window.setInterval(() => {
|
||||||
|
if (!CoreDomUtils.isElementInViewport(element, intersectionRatio)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve();
|
||||||
|
window.clearInterval(interval);
|
||||||
|
}, 50);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
visiblePromise.cancel();
|
||||||
|
intersectionObserver?.disconnect();
|
||||||
|
window.clearInterval(interval);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Window resize is widely checked and may have many performance issues, debouce usage is needed to avoid calling it too much.
|
* Window resize is widely checked and may have many performance issues, debouce usage is needed to avoid calling it too much.
|
||||||
* This function helps setting up the debounce feature and remove listener easily.
|
* This function helps setting up the debounce feature and remove listener easily.
|
||||||
|
@ -880,10 +925,21 @@ export class CoreDomUtilsProvider {
|
||||||
return elementPoint > window.innerHeight || elementPoint < scrollTopPos;
|
return elementPoint > window.innerHeight || elementPoint < scrollTopPos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether an element has been added to the DOM.
|
||||||
|
*
|
||||||
|
* @param element Element.
|
||||||
|
* @return True if element has been added to the DOM, false otherwise.
|
||||||
|
*/
|
||||||
|
isElementInDom(element: HTMLElement): boolean {
|
||||||
|
return element.getRootNode({ composed: true }) === document;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check whether an element is visible or not.
|
* Check whether an element is visible or not.
|
||||||
*
|
*
|
||||||
* @param element Element.
|
* @param element Element.
|
||||||
|
* @return True if element is visible inside the DOM.
|
||||||
*/
|
*/
|
||||||
isElementVisible(element: HTMLElement): boolean {
|
isElementVisible(element: HTMLElement): boolean {
|
||||||
if (element.clientWidth === 0 || element.clientHeight === 0) {
|
if (element.clientWidth === 0 || element.clientHeight === 0) {
|
||||||
|
@ -895,7 +951,35 @@ export class CoreDomUtilsProvider {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return element.offsetParent !== null;
|
return CoreDomUtils.isElementInDom(element);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check whether an element is intersecting the intersectionRatio in viewport.
|
||||||
|
*
|
||||||
|
* @param element
|
||||||
|
* @param intersectionRatio Intersection ratio (From 0 to 1).
|
||||||
|
* @return True if in viewport.
|
||||||
|
*/
|
||||||
|
isElementInViewport(element: HTMLElement, intersectionRatio = 1): boolean {
|
||||||
|
const elementRectangle = element.getBoundingClientRect();
|
||||||
|
|
||||||
|
const elementArea = elementRectangle.width * elementRectangle.height;
|
||||||
|
if (elementArea == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const intersectionRectangle = {
|
||||||
|
top: Math.max(0, elementRectangle.top),
|
||||||
|
left: Math.max(0, elementRectangle.left),
|
||||||
|
bottom: Math.min(window.innerHeight, elementRectangle.bottom),
|
||||||
|
right: Math.min(window.innerWidth, elementRectangle.right),
|
||||||
|
};
|
||||||
|
|
||||||
|
const intersectionArea = (intersectionRectangle.right - intersectionRectangle.left) *
|
||||||
|
(intersectionRectangle.bottom - intersectionRectangle.top);
|
||||||
|
|
||||||
|
return intersectionArea / elementArea >= intersectionRatio;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -51,7 +51,7 @@ export class CoreIframeUtilsProvider {
|
||||||
protected waitAutoLoginDefer?: PromiseDefer<void>;
|
protected waitAutoLoginDefer?: PromiseDefer<void>;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.logger = CoreLogger.getInstance('CoreUtilsProvider');
|
this.logger = CoreLogger.getInstance('CoreIframeUtilsProvider');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -237,7 +237,7 @@ export class CoreIframeUtilsProvider {
|
||||||
contentDocument = 'contentDocument' in element && element.contentDocument
|
contentDocument = 'contentDocument' in element && element.contentDocument
|
||||||
? element.contentDocument
|
? element.contentDocument
|
||||||
: contentWindow && contentWindow.document;
|
: contentWindow && contentWindow.document;
|
||||||
} catch (ex) {
|
} catch {
|
||||||
// Ignore errors.
|
// Ignore errors.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,7 +250,7 @@ export class CoreIframeUtilsProvider {
|
||||||
// It's probably an <embed>. Try to get the window and the document.
|
// It's probably an <embed>. Try to get the window and the document.
|
||||||
try {
|
try {
|
||||||
contentDocument = element.getSVGDocument();
|
contentDocument = element.getSVGDocument();
|
||||||
} catch (ex) {
|
} catch {
|
||||||
// Ignore errors.
|
// Ignore errors.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -310,10 +310,10 @@ export class CoreIframeUtilsProvider {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (contentDocument) {
|
if (contentDocument.body) {
|
||||||
// Search sub frames.
|
// Search sub frames.
|
||||||
CoreIframeUtilsProvider.FRAME_TAGS.forEach((tag) => {
|
CoreIframeUtilsProvider.FRAME_TAGS.forEach((tag) => {
|
||||||
const elements = Array.from(contentDocument.querySelectorAll(tag));
|
const elements = Array.from(contentDocument.body.querySelectorAll(tag));
|
||||||
elements.forEach((subElement: CoreFrameElement) => {
|
elements.forEach((subElement: CoreFrameElement) => {
|
||||||
this.treatFrame(subElement, true);
|
this.treatFrame(subElement, true);
|
||||||
});
|
});
|
||||||
|
@ -333,6 +333,8 @@ export class CoreIframeUtilsProvider {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
element.classList.add('core-loading');
|
||||||
|
|
||||||
const treatElement = (sendResizeEvent: boolean = false) => {
|
const treatElement = (sendResizeEvent: boolean = false) => {
|
||||||
this.checkOnlineFrameInOffline(element, isSubframe);
|
this.checkOnlineFrameInOffline(element, isSubframe);
|
||||||
|
|
||||||
|
@ -348,9 +350,12 @@ export class CoreIframeUtilsProvider {
|
||||||
this.treatFrameLinks(element, document);
|
this.treatFrameLinks(element, document);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Iframe content has been loaded.
|
||||||
// Send a resize events to the iframe so it calculates the right size if needed.
|
// Send a resize events to the iframe so it calculates the right size if needed.
|
||||||
if (window && sendResizeEvent) {
|
if (window && sendResizeEvent) {
|
||||||
setTimeout(() => window.dispatchEvent(new Event('resize')), 1000);
|
element.classList.remove('core-loading');
|
||||||
|
|
||||||
|
setTimeout(() => window.dispatchEvent && window.dispatchEvent(new Event('resize')), 1000);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -75,7 +75,6 @@
|
||||||
height: 60px;
|
height: 60px;
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@include position(null, 0, 0, 0);
|
@include position(null, 0, 0, 0);
|
||||||
background: -webkit-linear-gradient(top, rgba(var(--background-gradient-rgb), 0) calc(100% - var(--gradient-size)), rgba(var(--background-gradient-rgb), 1) calc(100% - 4px));
|
|
||||||
background: linear-gradient(to bottom, rgba(var(--background-gradient-rgb), 0) calc(100% - var(--gradient-size)), rgba(var(--background-gradient-rgb), 1) calc(100% - 4px));
|
background: linear-gradient(to bottom, rgba(var(--background-gradient-rgb), 0) calc(100% - var(--gradient-size)), rgba(var(--background-gradient-rgb), 1) calc(100% - 4px));
|
||||||
z-index: 6;
|
z-index: 6;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,77 +5,38 @@
|
||||||
core-format-text {
|
core-format-text {
|
||||||
--core-format-text-background: var(--background, var(--ion-item-background));
|
--core-format-text-background: var(--background, var(--ion-item-background));
|
||||||
--core-format-text-viewer-icon-background: rgba(255, 255, 255, .5);
|
--core-format-text-viewer-icon-background: rgba(255, 255, 255, .5);
|
||||||
--core-format-text-loader-shine: 251,251,251;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body.dark core-format-text {
|
body.dark core-format-text {
|
||||||
--core-format-text-viewer-icon-background: rgba(0, 0, 0, .5);
|
--core-format-text-viewer-icon-background: rgba(0, 0, 0, .5);
|
||||||
--core-format-text-loader-shine: 90,90,90;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
core-format-text {
|
core-format-text {
|
||||||
display: contents;
|
display: contents;
|
||||||
|
user-select: text;
|
||||||
|
word-break: break-word;
|
||||||
|
word-wrap: break-word;
|
||||||
|
|
||||||
.core-format-text-loader {
|
@include core-transition(background-color color, 200ms);
|
||||||
opacity: 0;
|
&.core-loading {
|
||||||
@include core-transition(opacity, 200ms);
|
|
||||||
display: contents;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.core-format-text-loading {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
&:empty:before {
|
||||||
opacity: 1;
|
content: 'E'; // Set a minimum empty text to have a minimum height of one line.
|
||||||
background-color: rgba(0,0,0,.1);
|
|
||||||
overflow: hidden;
|
|
||||||
display: block;
|
|
||||||
border-radius: var(--small-radius);
|
|
||||||
|
|
||||||
.core-format-text-loader {
|
|
||||||
position: absolute;
|
|
||||||
left: -45%;
|
|
||||||
height: 100%;
|
|
||||||
width: 45%;
|
|
||||||
background-image: -webkit-linear-gradient(to left, rgba(var(--core-format-text-loader-shine), .05), rgba(var(--core-format-text-loader-shine), .3), rgba(var(--core-format-text-loader-shine), .6), rgba(var(--core-format-text-loader-shine), .3), rgba(var(--core-format-text-loader-shine), .05));
|
|
||||||
background-image: linear-gradient(to left, rgba(var(--core-format-text-loader-shine), .05), rgba(var(--core-format-text-loader-shine), .3), rgba(var(--core-format-text-loader-shine), .6), rgba(var(--core-format-text-loader-shine), .3), rgba(var(--core-format-text-loader-shine), .05));
|
|
||||||
animation: loading 1s infinite;
|
|
||||||
opacity: 1;
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.core-format-text-content {
|
|
||||||
opacity: 0;
|
opacity: 0;
|
||||||
display: inline;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.core-format-text-content {
|
|
||||||
opacity: 1;
|
|
||||||
@include core-transition(opacity, 200ms);
|
|
||||||
|
|
||||||
display: contents;
|
|
||||||
user-select: text;
|
|
||||||
word-break: break-word;
|
|
||||||
word-wrap: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.collapsible-item {
|
&.collapsible-item {
|
||||||
display: block;
|
display: block;
|
||||||
// This is to allow clicks in radio/checkbox content.
|
// This is to allow clicks in radio/checkbox content.
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
pointer-events: auto;
|
pointer-events: auto;
|
||||||
|
|
||||||
.core-format-text-content {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.collapsible-enabled {
|
&.collapsible-enabled {
|
||||||
.core-format-text-content {
|
display: block;
|
||||||
display: block;
|
max-height: none;
|
||||||
max-height: none;
|
|
||||||
}
|
&.collapsible-collapsed {
|
||||||
&.collapsible-collapsed .core-format-text-content {
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -97,7 +58,7 @@ core-format-text {
|
||||||
}
|
}
|
||||||
&.collapsible-item.inline {
|
&.collapsible-item.inline {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
&.collapsible-enabled .core-format-text-content {
|
&.collapsible-enabled {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -141,7 +102,7 @@ core-format-text {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disable clicks in links inside MathJax equations.
|
// Disable clicks in links inside MathJax equations.
|
||||||
.core-format-text-content .filter_mathjaxloader_equation .MathJax_Preview a {
|
.filter_mathjaxloader_equation .MathJax_Preview a {
|
||||||
pointer-events: none;
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,7 +204,7 @@ core-format-text {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
core-format-text .core-format-text-content,
|
core-format-text,
|
||||||
core-rich-text-editor .core-rte-editor {
|
core-rich-text-editor .core-rte-editor {
|
||||||
@include core-headings();
|
@include core-headings();
|
||||||
|
|
||||||
|
@ -657,14 +618,10 @@ core-rich-text-editor .core-rte-editor {
|
||||||
|
|
||||||
// h1 is too big and ugly, reduce size when loading.
|
// h1 is too big and ugly, reduce size when loading.
|
||||||
ion-header.ios h1 core-format-text {
|
ion-header.ios h1 core-format-text {
|
||||||
&.core-format-text-loading {
|
&.core-loading {
|
||||||
max-height: 30px;
|
max-height: 30px;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
}
|
}
|
||||||
&.core-format-text-content {
|
|
||||||
display: block;
|
|
||||||
margin-top: -10px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body.dark core-format-text select,
|
body.dark core-format-text select,
|
||||||
|
|
|
@ -120,15 +120,15 @@ body {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Correctly inherit ion-text-wrap onto labels.
|
// Correctly inherit ion-text-wrap onto labels.
|
||||||
.item ion-label core-format-text .core-format-text-content > *,
|
.item ion-label core-format-text > *,
|
||||||
.fake-ion-item core-format-text .core-format-text-content > * {
|
.fake-ion-item core-format-text > * {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.item.ion-text-wrap > ion-label core-format-text .core-format-text-content > *,
|
.item.ion-text-wrap > ion-label core-format-text > *,
|
||||||
.fake-ion-item.ion-text-wrap core-format-text .core-format-text-content > * {
|
.fake-ion-item.ion-text-wrap core-format-text > * {
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
overflow: inherit;
|
overflow: inherit;
|
||||||
}
|
}
|
||||||
|
@ -334,7 +334,7 @@ ion-button.button-solid {
|
||||||
--box-shadow: none;
|
--box-shadow: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
ion-button core-format-text .core-format-text-content {
|
ion-button core-format-text {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
@ -350,7 +350,7 @@ ion-button > * {
|
||||||
|
|
||||||
ion-button.ion-text-wrap {
|
ion-button.ion-text-wrap {
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
core-format-text .core-format-text-content {
|
core-format-text {
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
display: contents;
|
display: contents;
|
||||||
}
|
}
|
||||||
|
@ -1516,3 +1516,36 @@ ion-header.no-title {
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Loader animation.
|
||||||
|
.core-loading {
|
||||||
|
position: relative;
|
||||||
|
background-color: var(--loader-background-color);
|
||||||
|
color: transparent; // Hide contents.
|
||||||
|
overflow: hidden;
|
||||||
|
display: var(--loader-display);
|
||||||
|
border-radius: var(--loader-radius);
|
||||||
|
@include core-transition(all, 200ms);
|
||||||
|
min-height: 8px;
|
||||||
|
min-width: 50px;
|
||||||
|
|
||||||
|
// Hide contents.
|
||||||
|
> * {
|
||||||
|
opacity: 0;
|
||||||
|
@include core-transition(opacity, 200ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
left: -45%;
|
||||||
|
height: 100%;
|
||||||
|
width: 45%;
|
||||||
|
background-image: linear-gradient(to left, rgba(var(--loader-shine), .05), rgba(var(--loader-shine), .3), rgba(var(--loader-shine), .6), rgba(var(--loader-shine), .3), rgba(var(--loader-shine), .05));
|
||||||
|
animation: loading 1s infinite;
|
||||||
|
display: block;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -41,6 +41,8 @@
|
||||||
|
|
||||||
--contrast-background: var(--gray-900);
|
--contrast-background: var(--gray-900);
|
||||||
|
|
||||||
|
--loader-shine: 90, 90, 90;
|
||||||
|
|
||||||
--drop-shadow-color: 0, 0, 0, 1;
|
--drop-shadow-color: 0, 0, 0, 1;
|
||||||
--drop-shadow-top: 0px 2px 5px rgba(var(--drop-shadow-color));
|
--drop-shadow-top: 0px 2px 5px rgba(var(--drop-shadow-color));
|
||||||
--drop-shadow-bottom: 0px -2px 5px rgba(var(--drop-shadow-color));
|
--drop-shadow-bottom: 0px -2px 5px rgba(var(--drop-shadow-color));
|
||||||
|
|
|
@ -85,6 +85,11 @@
|
||||||
|
|
||||||
--contrast-background: white;
|
--contrast-background: white;
|
||||||
|
|
||||||
|
--loader-background-color: rgba(0, 0, 0, .1);
|
||||||
|
--loader-shine: 251, 251, 251;
|
||||||
|
--loader-radius: var(--small-radius);
|
||||||
|
--loader-display: block;
|
||||||
|
|
||||||
--drop-shadow-color: 0, 0, 0, 0.5;
|
--drop-shadow-color: 0, 0, 0, 0.5;
|
||||||
--drop-shadow-top: 0px 2px 5px rgba(var(--drop-shadow-color));
|
--drop-shadow-top: 0px 2px 5px rgba(var(--drop-shadow-color));
|
||||||
--drop-shadow-bottom: 0px -2px 5px rgba(var(--drop-shadow-color));
|
--drop-shadow-bottom: 0px -2px 5px rgba(var(--drop-shadow-color));
|
||||||
|
|
Loading…
Reference in New Issue