-
diff --git a/src/components/rich-text-editor/rich-text-editor.scss b/src/components/rich-text-editor/rich-text-editor.scss
index a850981f3..366bfef2d 100644
--- a/src/components/rich-text-editor/rich-text-editor.scss
+++ b/src/components/rich-text-editor/rich-text-editor.scss
@@ -10,7 +10,6 @@ core-rich-text-editor {
display: flex;
flex-direction: column;
}
-
.core-rte-editor, .core-textarea {
padding: 2px;
margin: 2px;
@@ -51,21 +50,33 @@ core-rich-text-editor {
overflow-y: auto;
}
- div.formatOptions {
- background: $gray-dark;
- margin: 5px 1px 15px 1px;
+ div.core-rte-toolbar {
+ background: $gray-darker;
+ margin: 0px 1px 15px 1px;
text-align: center;
flex-grow: 0;
width: 100%;
z-index: 1;
- button {
- background: $gray-dark;
- color: $white;
- font-size: 1.1em;
- height: 35px;
- min-width: 30px;
- padding-left: 1px;
- padding-right: 1px;
+
+ .core-rte-buttons {
+ display: flex;
+ align-items: center;
+ flex-direction: row;
+ flex-wrap: wrap;
+ justify-content: space-evenly;
+
+ button {
+ background: $gray-darker;
+ color: $white;
+ font-size: 1.1em;
+ height: 35px;
+ min-width: 30px;
+ padding-left: 3px;
+ padding-right: 3px;
+ border-right: 1px solid $gray-dark;
+ border-bottom: 1px solid $gray-dark;
+ flex-grow: 1;
+ }
}
}
diff --git a/src/components/rich-text-editor/rich-text-editor.ts b/src/components/rich-text-editor/rich-text-editor.ts
index 4eac21804..2bc66557c 100644
--- a/src/components/rich-text-editor/rich-text-editor.ts
+++ b/src/components/rich-text-editor/rich-text-editor.ts
@@ -12,9 +12,13 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import { Component, Input, Output, EventEmitter, ViewChild, ElementRef, AfterContentInit, OnDestroy } from '@angular/core';
-import { TextInput } from 'ionic-angular';
+import { Component, Input, Output, EventEmitter, ViewChild, ElementRef, AfterContentInit, OnDestroy, Optional }
+ from '@angular/core';
+import { TextInput, Content } from 'ionic-angular';
+import { CoreSitesProvider } from '@providers/sites';
+import { CoreFilepoolProvider } from '@providers/filepool';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
+import { CoreUrlUtilsProvider } from '@providers/utils/url';
import { FormControl } from '@angular/forms';
import { Keyboard } from '@ionic-native/keyboard';
import { Subscription } from 'rxjs';
@@ -39,25 +43,32 @@ import { Subscription } from 'rxjs';
})
export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy {
// Based on: https://github.com/judgewest2000/Ionic3RichText/
- // @todo: Resize, images, anchor button, fullscreen...
+ // @todo: Anchor button, fullscreen...
@Input() placeholder = ''; // Placeholder to set in textarea.
@Input() control: FormControl; // Form control.
@Input() name = 'core-rich-text-editor'; // Name to set to the textarea.
+ @Input() component?: string; // The component to link the files to.
+ @Input() componentId?: number; // An ID to use in conjunction with the component.
@Output() contentChanged: EventEmitter
;
@ViewChild('editor') editor: ElementRef; // WYSIWYG editor.
@ViewChild('textarea') textarea: TextInput; // Textarea editor.
@ViewChild('decorate') decorate: ElementRef; // Buttons.
- rteEnabled = false;
- uniqueId = `rte{Math.floor(Math.random() * 1000000)}`;
- editorElement: HTMLDivElement;
+ protected element: HTMLDivElement;
+ protected editorElement: HTMLDivElement;
+ protected resizeFunction;
protected valueChangeSubscription: Subscription;
- constructor(private domUtils: CoreDomUtilsProvider, private keyboard: Keyboard) {
+ rteEnabled = false;
+
+ constructor(private domUtils: CoreDomUtilsProvider, private keyboard: Keyboard, private urlUtils: CoreUrlUtilsProvider,
+ private sitesProvider: CoreSitesProvider, private filepoolProvider: CoreFilepoolProvider,
+ @Optional() private content: Content, elementRef: ElementRef) {
this.contentChanged = new EventEmitter();
+ this.element = elementRef.nativeElement as HTMLDivElement;
}
/**
@@ -104,6 +115,58 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy
}
}
}
+
+ this.treatExternalContent();
+
+ this.resizeFunction = this.maximizeEditorSize.bind(this);
+ window.addEventListener('resize', this.resizeFunction);
+ setTimeout(this.resizeFunction, 1000);
+ }
+
+ /**
+ * Resize editor to maximize the space occupied.
+ */
+ protected maximizeEditorSize(): void {
+ this.content.resize();
+ const contentVisibleHeight = this.content.contentHeight;
+
+ // Editor is ready, adjust Height if needed.
+ if (contentVisibleHeight > 0) {
+ const height = this.getSurroundingHeight(this.element);
+ if (contentVisibleHeight > height) {
+ this.element.style.height = this.domUtils.formatPixelsSize(contentVisibleHeight - height);
+ } else {
+ this.element.style.height = '';
+ }
+ }
+ }
+
+ /**
+ * Get the height of the surrounding elements from the current to the top element.
+ *
+ * @param {any} element Directive DOM element to get surroundings elements from.
+ * @return {number} Surrounding height in px.
+ */
+ protected getSurroundingHeight(element: any): number {
+ let height = 0;
+
+ while (element.parentNode && element.parentNode.tagName != 'ION-CONTENT') {
+ const parent = element.parentNode;
+ if (element.tagName && element.tagName != 'CORE-LOADING') {
+ parent.childNodes.forEach((child) => {
+ if (child.tagName && child != element) {
+ height += this.domUtils.getElementHeight(child, false, true, true);
+ }
+ });
+ }
+ element = parent;
+ }
+
+ const cs = getComputedStyle(element);
+ height += this.domUtils.getComputedStyleMeasure(cs, 'paddingTop') +
+ this.domUtils.getComputedStyleMeasure(cs, 'paddingBottom');
+
+ return height;
}
/**
@@ -171,6 +234,30 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy
}, 1);
}
+ /**
+ * Treat elements that can contain external content.
+ * We only search for images because the editor should receive unfiltered text, so the multimedia filter won't be applied.
+ * Treating videos and audios in here is complex, so if a user manually adds one he won't be able to play it in the editor.
+ */
+ protected treatExternalContent(): void {
+ const elements = Array.from(this.editorElement.querySelectorAll('img')),
+ siteId = this.sitesProvider.getCurrentSiteId(),
+ canDownloadFiles = this.sitesProvider.getCurrentSite().canDownloadFiles();
+ elements.forEach((el) => {
+ const url = el.src;
+
+ if (!url || !this.urlUtils.isDownloadableUrl(url) || (!canDownloadFiles && this.urlUtils.isPluginFileUrl(url))) {
+ // Nothing to treat.
+ return;
+ }
+
+ // Check if it's downloaded.
+ return this.filepoolProvider.getSrcByUrl(siteId, url, this.component, this.componentId).then((finalUrl) => {
+ el.setAttribute('src', finalUrl);
+ });
+ });
+ }
+
/**
* Check if text is empty.
* @param {string} value text
@@ -215,5 +302,6 @@ export class CoreRichTextEditorComponent implements AfterContentInit, OnDestroy
*/
ngOnDestroy(): void {
this.valueChangeSubscription && this.valueChangeSubscription.unsubscribe();
+ window.removeEventListener('resize', this.resizeFunction);
}
}
diff --git a/src/lang/en.json b/src/lang/en.json
index 0a5d19c35..84768daed 100644
--- a/src/lang/en.json
+++ b/src/lang/en.json
@@ -227,6 +227,8 @@
"usernotfullysetup": "User not fully set-up",
"users": "Users",
"view": "View",
+ "viewcode": "View code",
+ "vieweditor": "View editor",
"viewprofile": "View profile",
"warningofflinedatadeleted": "Offline data of {{component}} '{{name}}' has been deleted. {{error}}",
"whatisyourage": "What is your age?",
diff --git a/src/providers/utils/dom.ts b/src/providers/utils/dom.ts
index e52e75121..c66f47855 100644
--- a/src/providers/utils/dom.ts
+++ b/src/providers/utils/dom.ts
@@ -365,13 +365,16 @@ export class CoreDomUtilsProvider {
let surround = 0;
if (usePadding) {
- surround += parseInt(computedStyle['padding' + priorSide], 10) + parseInt(computedStyle['padding' + afterSide], 10);
+ surround += this.getComputedStyleMeasure(computedStyle, 'padding' + priorSide) +
+ this.getComputedStyleMeasure(computedStyle, 'padding' + afterSide);
}
if (useMargin) {
- surround += parseInt(computedStyle['margin' + priorSide], 10) + parseInt(computedStyle['margin' + afterSide], 10);
+ surround += this.getComputedStyleMeasure(computedStyle, 'margin' + priorSide) +
+ this.getComputedStyleMeasure(computedStyle, 'margin' + afterSide);
}
if (useBorder) {
- surround += parseInt(computedStyle['border' + priorSide], 10) + parseInt(computedStyle['border' + afterSide], 10);
+ surround += this.getComputedStyleMeasure(computedStyle, 'border' + priorSide + 'Width') +
+ this.getComputedStyleMeasure(computedStyle, 'border' + afterSide + 'Width');
}
if (innerMeasure) {
measure = measure > surround ? measure - surround : 0;
@@ -381,7 +384,17 @@ export class CoreDomUtilsProvider {
}
return measure;
+ }
+ /**
+ * Returns the computed style measure or 0 if not found or NaN.
+ *
+ * @param {any} style Style from getComputedStyle.
+ * @param {string} measure Measure to get.
+ * @return {number} Result of the measure.
+ */
+ getComputedStyleMeasure(style: any, measure: string): number {
+ return parseInt(style[measure], 10) || 0;
}
/**