diff --git a/src/components/components.module.ts b/src/components/components.module.ts
index a9b03da57..7d62c20f4 100644
--- a/src/components/components.module.ts
+++ b/src/components/components.module.ts
@@ -36,6 +36,7 @@ import { CoreLocalFileComponent } from './local-file/local-file';
import { CoreSitePickerComponent } from './site-picker/site-picker';
import { CoreTabsComponent } from './tabs/tabs';
import { CoreTabComponent } from './tabs/tab';
+import { CoreRichTextEditorComponent } from './rich-text-editor/rich-text-editor';
@NgModule({
declarations: [
@@ -57,7 +58,8 @@ import { CoreTabComponent } from './tabs/tab';
CoreLocalFileComponent,
CoreSitePickerComponent,
CoreTabsComponent,
- CoreTabComponent
+ CoreTabComponent,
+ CoreRichTextEditorComponent
],
entryComponents: [
CoreContextMenuPopoverComponent,
@@ -86,7 +88,8 @@ import { CoreTabComponent } from './tabs/tab';
CoreLocalFileComponent,
CoreSitePickerComponent,
CoreTabsComponent,
- CoreTabComponent
+ CoreTabComponent,
+ CoreRichTextEditorComponent
]
})
export class CoreComponentsModule {}
diff --git a/src/components/mark-required/mark-required.scss b/src/components/mark-required/mark-required.scss
index deacc173b..1e6bebc83 100644
--- a/src/components/mark-required/mark-required.scss
+++ b/src/components/mark-required/mark-required.scss
@@ -1,9 +1,7 @@
-*[core-mark-required] {
- .core-input-required-asterisk, .icon.core-input-required-asterisk {
- color: $red !important;
- font-size: 8px;
- padding-left: 4px;
- line-height: 100%;
- vertical-align: top;
- }
+.core-input-required-asterisk, .icon.core-input-required-asterisk {
+ color: $red !important;
+ font-size: 8px;
+ padding-left: 4px;
+ line-height: 100%;
+ vertical-align: top;
}
diff --git a/src/components/rich-text-editor/rich-text-editor.html b/src/components/rich-text-editor/rich-text-editor.html
new file mode 100644
index 000000000..72eee6e14
--- /dev/null
+++ b/src/components/rich-text-editor/rich-text-editor.html
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/components/rich-text-editor/rich-text-editor.scss b/src/components/rich-text-editor/rich-text-editor.scss
new file mode 100644
index 000000000..096779bb8
--- /dev/null
+++ b/src/components/rich-text-editor/rich-text-editor.scss
@@ -0,0 +1,71 @@
+core-rich-text-editor {
+ height: 40vh;
+ overflow: hidden;
+ min-height: 30vh;
+
+ > div {
+ height: 100%;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ }
+
+ .core-rte-editor, .core-textarea {
+ padding: 2px;
+ margin: 2px;
+ width: 100%;
+ resize: none;
+ background-color: $white;
+ flex-grow: 1;
+ * {
+ overflow: hidden;
+ }
+ }
+
+ .core-rte-editor {
+ -webkit-user-select: auto !important;
+ word-wrap: break-word;
+ overflow-x: hidden;
+ overflow-y: auto;
+ cursor: text;
+ img {
+ padding-left: 2px;
+ max-width: 95%;
+ }
+ &:empty:before {
+ content: attr(data-placeholder-text);
+ display: block;
+ color: $gray-light;
+ font-weight: bold;
+ }
+ }
+
+ .core-textarea textarea {
+ margin: 0 !important;
+ padding: 0;
+ height: 100% !important;
+ width: 100% !important;
+ resize: none;
+ overflow-x: hidden;
+ overflow-y: auto;
+ }
+
+ div.formatOptions {
+ background: $gray-dark;
+ margin: 5px 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;
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/components/rich-text-editor/rich-text-editor.ts b/src/components/rich-text-editor/rich-text-editor.ts
new file mode 100644
index 000000000..3f41f9f49
--- /dev/null
+++ b/src/components/rich-text-editor/rich-text-editor.ts
@@ -0,0 +1,194 @@
+// (C) Copyright 2015 Martin Dougiamas
+//
+// 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 { Component, Input, Output, EventEmitter, ViewChild, ElementRef } from '@angular/core';
+import { TextInput } from 'ionic-angular';
+import { CoreDomUtilsProvider } from '../../providers/utils/dom';
+import { FormControl } from '@angular/forms';
+import { Keyboard } from '@ionic-native/keyboard';
+
+/**
+ * Directive to display a rich text editor if enabled.
+ *
+* If enabled, this directive will show a rich text editor. Otherwise it'll show a regular textarea.
+ *
+ * This directive requires an OBJECT model. The text written in the editor or textarea will be stored inside
+ * a "text" property in that object. This is to ensure 2-way data-binding, since using a string as a model
+ * could be easily broken.
+ *
+ * Example:
+ *
+ *
+ *
+ * In the example above, the text written in the editor will be stored in newpost.text.
+ */
+@Component({
+ selector: 'core-rich-text-editor',
+ templateUrl: 'rich-text-editor.html'
+})
+export class CoreRichTextEditorComponent {
+ // Based on: https://github.com/judgewest2000/Ionic3RichText/
+ // @todo: Resize, images, anchor button, fullscreen...
+
+ @Input() placeholder?: string = ""; // Placeholder to set in textarea.
+ @Input() control: FormControl; // Form control.
+ @Output() public contentChanged: EventEmitter;
+
+ @ViewChild('editor') editor: ElementRef; // WYSIWYG editor.
+ @ViewChild('textarea') textarea: TextInput; // Textarea editor.
+ @ViewChild('decorate') decorate: ElementRef; // Buttons.
+
+ rteEnabled: boolean = false;
+ uniqueId = `rte{Math.floor(Math.random() * 1000000)}`;
+ editorElement: HTMLDivElement;
+
+ constructor(private domUtils: CoreDomUtilsProvider, private keyboard: Keyboard) {
+ this.contentChanged = new EventEmitter();
+ }
+
+ /**
+ * Init editor
+ */
+ ngAfterContentInit() {
+ this.domUtils.isRichTextEditorEnabled().then((enabled) => {
+ this.rteEnabled = !!enabled;
+ });
+
+ // Setup the editor.
+ this.editorElement = this.editor.nativeElement as HTMLDivElement;
+ this.editorElement.innerHTML = this.control.value;
+ this.textarea.value = this.control.value;
+ this.control.setValue(this.control.value);
+
+ this.editorElement.onchange = this.onChange.bind(this);
+ this.editorElement.onkeyup = this.onChange.bind(this);
+ this.editorElement.onpaste = this.onChange.bind(this);
+ this.editorElement.oninput = this.onChange.bind(this);
+
+ // Setup button actions.
+ let buttons = (this.decorate.nativeElement as HTMLDivElement).getElementsByTagName('button');
+ for (let i = 0; i < buttons.length; i++) {
+ let button = buttons[i],
+ command = button.getAttribute('data-command');
+
+ if (command) {
+ if (command.includes('|')) {
+ let parameter = command.split('|')[1];
+ command = command.split('|')[0];
+
+ button.addEventListener('click', ($event) => {
+ this.buttonAction($event, command, parameter);
+ });
+ } else {
+ button.addEventListener('click', ($event) => {
+ this.buttonAction($event, command);
+ });
+ }
+ }
+ }
+ }
+
+ /**
+ * On change function to sync with form data.
+ */
+ onChange($event) {
+ if (this.rteEnabled) {
+ if (this.isNullOrWhiteSpace(this.editorElement.innerText)) {
+ this.clearText();
+ } else {
+ this.control.setValue(this.editorElement.innerHTML);
+ }
+ } else {
+ if (this.isNullOrWhiteSpace(this.textarea.value)) {
+ this.clearText();
+ } else {
+ this.control.setValue(this.textarea.value);
+ }
+ }
+ this.contentChanged.emit(this.control.value);
+ }
+
+ /**
+ * Toggle from rte editor to textarea syncing values.
+ */
+ toggleEditor($event) {
+ $event.preventDefault();
+ $event.stopPropagation();
+
+ if (this.isNullOrWhiteSpace(this.control.value)) {
+ this.clearText();
+ } else {
+ this.editorElement.innerHTML = this.control.value;
+ this.textarea.value = this.control.value;
+ }
+
+ this.rteEnabled = !this.rteEnabled;
+
+ // Set focus and cursor at the end.
+ setTimeout(() => {
+ if (this.rteEnabled) {
+ this.editorElement.focus();
+ let range = document.createRange();
+ range.selectNodeContents(this.editorElement);
+ range.collapse(false);
+ let sel = window.getSelection();
+ sel.removeAllRanges();
+ sel.addRange(range);
+ } else {
+ this.textarea.setFocus();
+ }
+ setTimeout(() => {
+ this.keyboard.show();
+ }, 1);
+ }, 1);
+ }
+
+ /**
+ * Check if text is empty.
+ * @param {string} value text
+ */
+ private isNullOrWhiteSpace(value: string) {
+ if (value == null || typeof value == "undefined") {
+ return true;
+ }
+ value = value.replace(/[\n\r]/g, '');
+ value = value.split(' ').join('');
+
+ return value.length === 0;
+ }
+
+ /**
+ * Clear the text.
+ */
+ clearText() {
+ this.editorElement.innerHTML = '';
+ this.textarea.value = '';
+ this.control.setValue(null);
+ }
+
+ /**
+ * Execute an action over the selected text.
+ * API docs: https://developer.mozilla.org/en-US/docs/Web/API/Document/execCommand
+ *
+ * @param {any} $event Event data
+ * @param {string} command Command to execute.
+ * @param {any} [parameters] Parameters of the command.
+ */
+ private buttonAction($event: any, command: string, parameters: any = null) {
+ $event.preventDefault();
+ $event.stopPropagation();
+ document.execCommand(command, false, parameters);
+ }
+}
\ No newline at end of file