├── projects ├── demo │ ├── src │ │ ├── image.jpg │ │ ├── main.ts │ │ ├── polyfills.ts │ │ ├── typings.d.ts │ │ ├── index.html │ │ ├── app.module.ts │ │ └── app │ │ │ ├── froala.component.ts │ │ │ └── app.component.ts │ └── tsconfig.json └── library │ ├── src │ ├── view │ │ ├── index.ts │ │ ├── view.module.ts │ │ └── view.directive.ts │ ├── editor │ │ ├── index.ts │ │ ├── editor.module.ts │ │ └── editor.directive.ts │ ├── index.ts │ └── fe-root.module.ts │ ├── ng-package.json │ ├── package.json │ └── tsconfig.json ├── version.json ├── tsconfig.json ├── docker-compose.yml.template ├── Dockerfile ├── .gitignore ├── .travis.yml ├── package.json ├── publish-npm.sh ├── push-image-to-nexus.sh ├── angular.json ├── deploy_sdk.sh └── README.md /projects/demo/src/image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/froala/angular-froala-wysiwyg/HEAD/projects/demo/src/image.jpg -------------------------------------------------------------------------------- /projects/library/src/view/index.ts: -------------------------------------------------------------------------------- 1 | export { FroalaViewDirective } from './view.directive'; 2 | export { FroalaViewModule } from './view.module'; -------------------------------------------------------------------------------- /projects/library/src/editor/index.ts: -------------------------------------------------------------------------------- 1 | export { FroalaEditorDirective } from './editor.directive'; 2 | export { FroalaEditorModule } from './editor.module'; -------------------------------------------------------------------------------- /projects/demo/src/main.ts: -------------------------------------------------------------------------------- 1 | import './polyfills'; 2 | import { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; 3 | import { AppModule } from './app.module'; 4 | 5 | platformBrowserDynamic().bootstrapModule(AppModule); 6 | -------------------------------------------------------------------------------- /projects/demo/src/polyfills.ts: -------------------------------------------------------------------------------- 1 | // This file includes polyfills needed by Angular 2 and is loaded before 2 | // the app. You can add your own extra polyfills to this file. 3 | import 'zone.js'; 4 | import 'zone.js/testing'; 5 | 6 | import 'classlist.js'; 7 | -------------------------------------------------------------------------------- /projects/library/ng-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../../node_modules/ng-packagr/ng-package.schema.json", 3 | "dest": "../../dist", 4 | "deleteDestPath": true, 5 | "allowedNonPeerDependencies": [ 6 | "froala-editor" 7 | ], 8 | "lib": { 9 | "entryFile": "src/index.ts" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /projects/demo/src/typings.d.ts: -------------------------------------------------------------------------------- 1 | // Typings reference file, you can add your own global typings here 2 | // https://www.typescriptlang.org/docs/handbook/writing-declaration-files.html 3 | // tslint:disable 4 | declare const System: any; 5 | declare const ENV:string; 6 | 7 | // google code-prettify 8 | declare const PR:any; -------------------------------------------------------------------------------- /projects/library/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-froala-wysiwyg", 3 | "repository": { 4 | "type": "git", 5 | "url": "https://github.com/froala/angular-froala-wysiwyg.git", 6 | "directory":"projects/library" 7 | }, 8 | "version": "4.7.1", 9 | "dependencies": { 10 | "froala-editor": "4.7.1" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /projects/library/src/index.ts: -------------------------------------------------------------------------------- 1 | export { FroalaEditorDirective } from './editor/editor.directive'; 2 | export { FroalaEditorModule } from './editor/editor.module'; 3 | 4 | export { FroalaViewDirective } from './view/view.directive'; 5 | export { FroalaViewModule } from './view/view.module'; 6 | 7 | export { FERootModule } from './fe-root.module'; 8 | 9 | -------------------------------------------------------------------------------- /version.json: -------------------------------------------------------------------------------- 1 | { 2 | "comment-1": " Please update this file for any new branch with core libary name - package name and version uploaded to nexus", 3 | "name": "froala-editor-QA241122", 4 | "version": "4.0.16", 5 | "comment-2": "Please specify the maximum allowed deployments per environment", 6 | "dev": "1", 7 | "qa": "2", 8 | "qe": "2", 9 | "stg": "1" 10 | } 11 | -------------------------------------------------------------------------------- /projects/demo/src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Angular Froala WYSIWYG 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | Loading... 14 | 15 | 16 | -------------------------------------------------------------------------------- /projects/library/src/fe-root.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule } from '@angular/core'; 2 | import { FroalaEditorModule } from './editor/editor.module'; 3 | import { FroalaViewModule } from './view/view.module'; 4 | 5 | @NgModule({ 6 | imports: [ 7 | FroalaEditorModule.forRoot(), 8 | FroalaViewModule.forRoot() 9 | ], 10 | exports: [ 11 | FroalaEditorModule, 12 | FroalaViewModule 13 | ] 14 | }) 15 | export class FERootModule { 16 | 17 | } 18 | -------------------------------------------------------------------------------- /projects/library/src/view/view.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, ModuleWithProviders } from '@angular/core'; 2 | 3 | import { FroalaViewDirective } from './view.directive'; 4 | 5 | @NgModule({ 6 | declarations: [FroalaViewDirective], 7 | exports: [FroalaViewDirective] 8 | }) 9 | export class FroalaViewModule { 10 | public static forRoot(): ModuleWithProviders { 11 | return {ngModule: FroalaViewModule, providers: []}; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /projects/library/src/editor/editor.module.ts: -------------------------------------------------------------------------------- 1 | import { NgModule, ModuleWithProviders } from '@angular/core'; 2 | 3 | import { FroalaEditorDirective } from './editor.directive'; 4 | 5 | @NgModule({ 6 | declarations: [FroalaEditorDirective], 7 | exports: [FroalaEditorDirective] 8 | }) 9 | 10 | export class FroalaEditorModule { 11 | public static forRoot(): ModuleWithProviders { 12 | return {ngModule: FroalaEditorModule, providers: []}; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "./", 4 | "rootDir": ".", 5 | "downlevelIteration": true, 6 | "outDir": "./dist/out-tsc", 7 | "sourceMap": true, 8 | "declaration": false, 9 | "module": "esnext", 10 | "moduleResolution": "node", 11 | "importHelpers": true, 12 | "emitDecoratorMetadata": true, 13 | "skipLibCheck": true, 14 | "experimentalDecorators": true, 15 | "target": "es2020", 16 | "typeRoots": [ 17 | "node_modules/@types" 18 | ], 19 | "lib": [ 20 | "es2017", 21 | "dom" 22 | ] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /docker-compose.yml.template: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | services: 4 | ServiceName: 5 | restart: unless-stopped 6 | image: ImageName 7 | pull_policy: always 8 | container_name: ContainerName 9 | networks: 10 | - caddy 11 | # ports: 12 | # - "1500:4200" 13 | 14 | labels: 15 | caddy_0: UrlName 16 | caddy_0.reverse_proxy: "{{upstreams PortNum}}" 17 | caddy_0.tls.ca: https://acme-staging-v02.api.letsencrypt.org/directory 18 | # rate limit exceeded ; use letsencryot staging url :https://letsencrypt.org/docs/rate-limits/ 19 | 20 | networks: 21 | caddy: 22 | external: true -------------------------------------------------------------------------------- /projects/library/src/view/view.directive.ts: -------------------------------------------------------------------------------- 1 | import { Directive, ElementRef, Renderer2, Input } from '@angular/core'; 2 | 3 | @Directive({ 4 | selector: '[froalaView]', 5 | standalone: false 6 | }) 7 | export class FroalaViewDirective { 8 | 9 | private _element: HTMLElement; 10 | 11 | constructor(private renderer: Renderer2, element: ElementRef) { 12 | this._element = element.nativeElement; 13 | } 14 | 15 | // update content model as it comes 16 | @Input() set froalaView(content: string) { 17 | this._element.innerHTML = content; 18 | } 19 | 20 | ngAfterViewInit() { 21 | this.renderer.addClass(this._element, "fr-view"); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18 2 | 3 | LABEL maintainer="froala_git_travis_bot@idera.com" 4 | 5 | ARG PackageName 6 | ARG PackageVersion 7 | ARG NexusUser 8 | ARG NexusPassword 9 | 10 | COPY . /app 11 | WORKDIR /app/ 12 | 13 | RUN apt update -y \ 14 | && apt install -y jq unzip wget 15 | RUN echo "Dummy line" 16 | 17 | RUN echo "PackageName=$PackageName PackageVersion=$PackageVersion NexusUser=${NexusUser} NexusPassword=${NexusPassword}" 18 | RUN wget --no-check-certificate --user ${NexusUser} --password ${NexusPassword} https://nexus.tools.froala-infra.com/repository/Froala-npm/${PackageName}/-/${PackageName}-${PackageVersion}.tgz 19 | 20 | RUN npm install 21 | 22 | RUN npm run demo.build 23 | EXPOSE 4200 24 | CMD ["npm","run","start"] 25 | -------------------------------------------------------------------------------- /projects/demo/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "declaration": false, 5 | "emitDecoratorMetadata": true, 6 | "experimentalDecorators": true, 7 | "noEmitHelpers" :true, 8 | "lib": ["es6", "dom"], 9 | "types": [ 10 | "webpack" 11 | ], 12 | "module": "esnext", 13 | "moduleResolution": "node", 14 | "target": "ES2022", 15 | "baseUrl": ".", 16 | "sourceMap": true, 17 | "typeRoots": [ 18 | "../../node_modules/@types" 19 | ], 20 | "paths": { 21 | "angular-fraola-wysiwyg": [ 22 | "../library/src/index.ts" 23 | ] 24 | } 25 | }, 26 | "files": [ 27 | "src/main.ts" 28 | ], 29 | "include": [ 30 | "**/*.d.ts" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directory 2 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 3 | /node_modules 4 | /bower_components 5 | yarn.lock 6 | 7 | # IDEs and editors 8 | /.idea 9 | /.vscode 10 | .project 11 | .classpath 12 | *.launch 13 | .settings/ 14 | 15 | # misc 16 | /.sass-cache 17 | /connect.lock 18 | /coverage/* 19 | /libpeerconnection.log 20 | npm-debug.log 21 | 22 | # ignore build and dist for now 23 | /dist 24 | /temp 25 | /demo/dist 26 | /demo/temp 27 | /logs 28 | 29 | #System Files 30 | .DS_Store 31 | Thumbs.db 32 | 33 | 34 | /demo/e2e/*.js 35 | /demo/e2e/*.map 36 | src/**/*.js 37 | src/**/*.map 38 | scripts/**/*.js 39 | scripts/**/*.map 40 | 41 | # Typing # 42 | /src/typings/tsd/ 43 | /typings/** 44 | /tsd_typings/ 45 | 46 | package-lock.json -------------------------------------------------------------------------------- /projects/demo/src/app.module.ts: -------------------------------------------------------------------------------- 1 | import 'froala-editor/js/plugins.pkgd.min.js'; 2 | 3 | import { FormsModule, ReactiveFormsModule } from '@angular/forms'; 4 | 5 | import { AppComponent } from './app/app.component'; 6 | import { BrowserModule } from '@angular/platform-browser'; 7 | import { CommonModule } from '@angular/common'; 8 | import { FroalaComponent } from "./app/froala.component"; 9 | import { FroalaEditorModule } from 'angular-fraola-wysiwyg'; 10 | import { FroalaViewModule } from 'angular-fraola-wysiwyg'; 11 | import { NgModule } from '@angular/core'; 12 | 13 | @NgModule({ 14 | declarations: [AppComponent, FroalaComponent], 15 | imports: [BrowserModule, CommonModule, FormsModule, ReactiveFormsModule, FroalaEditorModule, FroalaViewModule], 16 | bootstrap: [AppComponent] 17 | }) 18 | export class AppModule { 19 | } 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | git: 2 | depth: false 3 | quiet: false 4 | language: generic 5 | dist: bionic 6 | sudo: required 7 | branches: 8 | only: 9 | - /dev*/ 10 | - /AO-dev*/ 11 | - /QA*/ 12 | - /QE*/ 13 | - /RC*/ 14 | - /Release-Master*/ 15 | env: 16 | - SHORT_COMMIT= `git rev-parse --short=7 ${TRAVIS_COMMIT}` 17 | before_install: 18 | - echo $TRAVIS_BRANCH 19 | - echo $PWD 20 | - echo $TRAVIS_COMMIT 21 | - echo $BUILD_REPO_NAME 22 | jobs: 23 | include: 24 | if: commit_message =~ /(deploy-yes)/ 25 | script: 26 | - chmod u+x push-image-to-nexus.sh && bash push-image-to-nexus.sh 27 | - chmod u+x deploy_sdk.sh && bash deploy_sdk.sh 28 | - chmod u+x publish-npm.sh && bash publish-npm.sh 29 | 30 | notifications: 31 | email: 32 | recipients: 33 | - harasunu.narayan@froala.com 34 | on_success: always 35 | on_failure: always 36 | -------------------------------------------------------------------------------- /projects/library/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "declaration": true, 5 | "module": "es2020", 6 | "target": "es2020", 7 | "baseUrl": "./", 8 | "stripInternal": true, 9 | "emitDecoratorMetadata": true, 10 | "experimentalDecorators": true, 11 | "moduleResolution": "node", 12 | "outDir": "../build", 13 | "rootDir": ".", 14 | "lib": [ 15 | "es2020", 16 | "dom" 17 | ], 18 | "skipLibCheck": true, 19 | "types": [] 20 | }, 21 | "angularCompilerOptions": { 22 | "compilationMode": "partial", 23 | "annotateForClosureCompiler": true, 24 | "strictMetadataEmit": true, 25 | "skipTemplateCodegen": true, 26 | "flatModuleOutFile": "angular-froala-wysiwyg.js", 27 | "flatModuleId": "angular-froala-wysiwyg" 28 | }, 29 | "files": [ 30 | "./src/index.ts" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /projects/demo/src/app/froala.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, forwardRef } from '@angular/core'; 2 | import {ControlValueAccessor, NG_VALUE_ACCESSOR} from "@angular/forms"; 3 | 4 | @Component({ 5 | selector: 'froala-component', 6 | template: ` 7 | 8 | `, 9 | providers: [ 10 | { 11 | provide: NG_VALUE_ACCESSOR, 12 | useExisting: forwardRef(() => FroalaComponent), 13 | multi: true 14 | } 15 | ], 16 | standalone: false 17 | }) 18 | export class FroalaComponent implements ControlValueAccessor { 19 | 20 | constructor() { 21 | 22 | } 23 | 24 | // Begin ControlValueAccesor methods. 25 | onChange = (_) => {}; 26 | onTouched = () => {}; 27 | 28 | // Form model content changed. 29 | writeValue(content: any): void { 30 | this.model = content; 31 | } 32 | 33 | registerOnChange(fn: (_: any) => void): void { this.onChange = fn; } 34 | registerOnTouched(fn: () => void): void { this.onTouched = fn; } 35 | // End ControlValueAccesor methods. 36 | 37 | model: any; 38 | 39 | config: Object = { 40 | charCounterCount: false 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "angular-froala-wysiwyg-base", 3 | "version": "4.7.1", 4 | "description": "Angular 19+ versions bindings for Froala WYSIWYG HTML rich text editor", 5 | "main": "bundles/angular-froala-wysiwyg.umd.js", 6 | "typings": "index.d.ts", 7 | "module": "index.js", 8 | "scripts": { 9 | "demo.serve": "ng serve --host 0.0.0.0 --disable-host-check angular-froala-wysiwyg-demo", 10 | "demo.build": "ng build angular-froala-wysiwyg-demo", 11 | "build": "ng build angular-froala-wysiwyg && cp README.md dist/", 12 | "start": "npm run demo.serve" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/froala/angular-froala-wysiwyg.git" 17 | }, 18 | "keywords": [ 19 | "angular19", 20 | "ng19", 21 | "froala", 22 | "html", 23 | "text", 24 | "editor", 25 | "wysiwyg", 26 | "rich editor", 27 | "rich text editor", 28 | "rte", 29 | "javascript" 30 | ], 31 | "author": "Froala Labs (https://www.froala.com/)", 32 | "license": "MIT", 33 | "bugs": { 34 | "url": "https://github.com/froala/angular-froala-wysiwyg/issues" 35 | }, 36 | "dependencies": { 37 | "font-awesome": "^4.7.0", 38 | "froala-editor": "^4.7.1", 39 | "tslib": "^2.8.1" 40 | }, 41 | "peerDependencies": {}, 42 | "devDependencies": { 43 | "@angular-devkit/build-angular": "^19.0.1", 44 | "@angular/cli": "^19.0.1", 45 | "@angular/common": "^19.0.0", 46 | "@angular/compiler": "^19.0.0", 47 | "@angular/compiler-cli": "^19.0.0", 48 | "@angular/core": "^19.0.0", 49 | "@angular/forms": "^19.0.0", 50 | "@angular/language-service": "^19.0.0", 51 | "@angular/platform-browser": "^19.0.0", 52 | "@angular/platform-browser-dynamic": "^19.0.0", 53 | "@angular/router": "^19.0.0", 54 | "@types/node": "^18.11.9", 55 | "@types/tapable": "^2.2.1", 56 | "@types/webpack": "^5.28.5", 57 | "classlist.js": "^1.1.20150312", 58 | "ng-packagr": "^19.0.1", 59 | "rxjs": "^7.8.1", 60 | "ts-helpers": "^1.1.2", 61 | "typescript": "~5.6.3", 62 | "zone.js": "~0.15.0" 63 | } 64 | } -------------------------------------------------------------------------------- /publish-npm.sh: -------------------------------------------------------------------------------- 1 | 2 | # 3 | # Publish to Nexus NPM repository if it's not a pull request 4 | # 5 | 6 | if [ ${TRAVIS_PULL_REQUEST} != "false" ]; then echo "Not publishing a pull request !!!" && exit 0; fi 7 | 8 | npm config set strict-ssl false 9 | npm install -g npm-cli-login 10 | npm-cli-login --scope @froala-org -u ${NEXUS_USER} -p ${NEXUS_USER_PWD} -e dummy-email@noemail@froala.com -r ${NEXUS_URL}/repository/Froala-npm 11 | 12 | 13 | echo "seting up nexus as default registry to publish ..." 14 | 15 | npm-cli-login -u ${NEXUS_USER} -p ${NEXUS_USER_PWD} -e dummy-email@noemail@froala.com -r ${NEXUS_URL}/repository/Froala-npm 16 | 17 | PACKAGE_NAME=`jq '.name' version.json | tr -d '"'` 18 | PACKAGE_VERSION=`jq '.version' version.json | tr -d '"'` 19 | echo "Package name : ${PACKAGE_NAME}" 20 | 21 | export DEFAULT_NAME=`cat projects/library/package.json | jq '.name '` 22 | export DEFAULT_NAME=`sed -e 's/^"//' -e 's/"$//' <<<"$DEFAULT_NAME"` 23 | echo ${DEFAULT_NAME} 24 | export ANGULAR_EDITOR_NAME=${DEFAULT_NAME}-${TRAVIS_BRANCH} 25 | 26 | wget --no-check-certificate --user ${NEXUS_USER} --password ${NEXUS_USER_PWD} https://nexus.tools.froala-infra.com/repository/Froala-npm/${PACKAGE_NAME}/-/${PACKAGE_NAME}-${PACKAGE_VERSION}.tgz 27 | 28 | npm install -f 29 | npm run build 30 | 31 | ls -la 32 | 33 | cd dist 34 | 35 | jq --arg newval "$ANGULAR_EDITOR_NAME" '.name |= $newval' package.json > tmp.json && mv tmp.json package.json 36 | jq --arg froalaeditor "file:${PACKAGE_NAME}-${PACKAGE_VERSION}.tgz" '.dependencies["froala-editor"] |= $froalaeditor' package.json > new.file && cat new.file > package.json && rm -f new.file 37 | jq '.publishConfig |= . + {"registry": "https://nexus.tools.froala-infra.com/repository/Froala-npm/" }' package.json > new.file && cat new.file > package.json && rm -f new.file 38 | 39 | echo " Angular demo package.json file: " && cat package.json 40 | 41 | npm publish 42 | 43 | echo "Published ${ANGULAR_EDITOR_NAME} to nexus " 44 | 45 | echo "Please create/verify branch update ${TRAVIS_BRANCH} for https://github.com/froala/ionic-froala-demo-mcs/blob/devops/version.json and update version.json with ${ANGULAR_EDITOR_NAME} and version " 46 | -------------------------------------------------------------------------------- /push-image-to-nexus.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | export BRANCH_NAME=`echo "${TRAVIS_BRANCH}" | tr '[:upper:]' '[:lower:]'` 3 | case "${BRANCH_NAME}" in 4 | dev*) echo "Branch ${TRAVIS_BRANCH} is eligible for CI/CD" ;; 5 | ao-dev*)echo "Branch ${TRAVIS_BRANCH} is eligible for CI/CD" ;; 6 | qa*) echo "Branch ${TRAVIS_BRANCH} is eligible for CI/CD" ;; 7 | qe*) echo "Branch ${TRAVIS_BRANCH} is eligible for CI/CD" ;; 8 | rc*) echo "Branch ${TRAVIS_BRANCH} is eligible for CI/CD" ;; 9 | release-master*) echo "Branch ${TRAVIS_BRANCH} is eligible for CI/CD" ;; 10 | ft*) echo "Branch ${TRAVIS_BRANCH} is eligible for CI" ;; 11 | bf*) echo "Branch ${TRAVIS_BRANCH} is eligible for CI" ;; 12 | *) echo "Not a valid branch name for CI/CD" && exit -1;; 13 | esac 14 | 15 | echo $TRAVIS_BRANCH 16 | echo ${DEPLOYMENT_SERVER} 17 | export SHORT_COMMIT=`git rev-parse --short=7 ${TRAVIS_COMMIT}` 18 | echo "short commit $SHORT_COMMIT" 19 | sudo apt-get update 20 | sudo apt-get install -y jq 21 | IMAGE_NAME=`echo "${BUILD_REPO_NAME}_${TRAVIS_BRANCH}" | tr '[:upper:]' '[:lower:]'` 22 | PACKAGE_NAME=`jq '.name' version.json | tr -d '"'` 23 | PACKAGE_VERSION=`jq '.version' version.json | tr -d '"'` 24 | echo "Package name : ${PACKAGE_NAME}" 25 | jq --arg froalaeditor "file:${PACKAGE_NAME}-${PACKAGE_VERSION}.tgz" '.dependencies["froala-editor"] |= $froalaeditor' package.json > new.file && cat new.file > package.json && rm -f new.file 26 | echo "verify package" 27 | cat package.json 28 | docker build -t ${IMAGE_NAME}:${SHORT_COMMIT} --build-arg PackageName=${PACKAGE_NAME} --build-arg PackageVersion=${PACKAGE_VERSION} --build-arg NexusUser=${NEXUS_USER} --build-arg NexusPassword=${NEXUS_USER_PWD} . 29 | sleep 3 30 | docker image ls 31 | echo "uploading to nexus ${PACKAGE_NAME}, if not a new PR" 32 | if [ ${TRAVIS_PULL_REQUEST} != "false" ]; then echo "Not publishing a pull request !!!" && exit 0; fi 33 | docker login -u ${NEXUS_USER} -p ${NEXUS_USER_PWD} ${NEXUS_CR_TOOLS_URL} 34 | docker tag ${IMAGE_NAME}:${SHORT_COMMIT} ${NEXUS_CR_TOOLS_URL}/froala-${IMAGE_NAME}:${PACKAGE_VERSION} 35 | docker push ${NEXUS_CR_TOOLS_URL}/froala-${IMAGE_NAME}:${PACKAGE_VERSION} -------------------------------------------------------------------------------- /angular.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json", 3 | "version": 1, 4 | "newProjectRoot": "projects", 5 | "projects": { 6 | "angular-froala-wysiwyg": { 7 | "root": "projects/library", 8 | "projectType": "library", 9 | "architect": { 10 | "build": { 11 | "builder": "@angular-devkit/build-angular:ng-packagr", 12 | "options": { 13 | "project": "projects/library/ng-package.json", 14 | "tsConfig": "projects/library/tsconfig.json" 15 | } 16 | } 17 | } 18 | }, 19 | "angular-froala-wysiwyg-demo": { 20 | "root": "projects/demo", 21 | "sourceRoot": "projects/demo/src", 22 | "projectType": "application", 23 | "architect": { 24 | "build": { 25 | "builder": "@angular-devkit/build-angular:browser", 26 | "options": { 27 | "allowedCommonJsDependencies":[ 28 | "froala-editor", 29 | "core-js", 30 | "zone.js" 31 | ], 32 | "outputPath": "dist-demo", 33 | "index": "projects/demo/src/index.html", 34 | "main": "projects/demo/src/main.ts", 35 | "tsConfig": "projects/demo/tsconfig.json", 36 | "assets": [ 37 | "projects/demo/src/image.jpg" 38 | ], 39 | "styles": [ 40 | "node_modules/froala-editor/css/froala_editor.pkgd.css" 41 | ], 42 | "scripts": [] 43 | }, 44 | "configurations": { 45 | "production": { 46 | "budgets": [ 47 | { 48 | "type": "anyComponentStyle", 49 | "maximumWarning": "6kb" 50 | } 51 | ], 52 | "optimization": false, 53 | "outputHashing": "all", 54 | "sourceMap": false, 55 | "namedChunks": false, 56 | "aot": true, 57 | "extractLicenses": true, 58 | "vendorChunk": false, 59 | "buildOptimizer": true 60 | } 61 | } 62 | }, 63 | "serve": { 64 | "builder": "@angular-devkit/build-angular:dev-server", 65 | "options": { 66 | "buildTarget": "angular-froala-wysiwyg-demo:build" 67 | }, 68 | "configurations": {} 69 | }, 70 | "extract-i18n": { 71 | "builder": "@angular-devkit/build-angular:extract-i18n", 72 | "options": { 73 | "buildTarget": "angular-froala-wysiwyg-demo:build" 74 | } 75 | }, 76 | "lint": { 77 | "builder": "@angular-devkit/build-angular:tslint", 78 | "options": { 79 | "tsConfig": [], 80 | "exclude": [] 81 | } 82 | } 83 | } 84 | }, 85 | "angular-froala-wysiwyg-demo-e2e": { 86 | "root": "demo/e2e", 87 | "sourceRoot": "demo/e2e", 88 | "projectType": "application" 89 | } 90 | }, 91 | 92 | "schematics": { 93 | "@schematics/angular:component": { 94 | "prefix": "", 95 | "style": "css" 96 | }, 97 | "@schematics/angular:directive": { 98 | "prefix": "" 99 | } 100 | }, 101 | "cli": { 102 | "analytics": false 103 | } 104 | } -------------------------------------------------------------------------------- /projects/demo/src/app/app.component.ts: -------------------------------------------------------------------------------- 1 | import { Component, OnInit } from '@angular/core'; 2 | import {FormControl, FormGroup, Validators} from '@angular/forms'; 3 | 4 | import FroalaEditor from 'froala-editor'; 5 | 6 | @Component({ 7 | selector: 'app-demo', 8 | template: ` 9 | 10 |

Angular adapter for the Froala WYSIWYG editor

11 |
12 |

Sample 1: Inline Edit

13 |
14 | 15 |
16 |
17 |

Sample 2: Full Editor

18 |
19 |

Rendered Content:

20 |
21 |
22 |
23 |

Sample 3: Two way binding

24 |
25 |
26 |
27 |
28 |

Sample 4: Manual Initialization

29 | 30 | 31 | 32 |
Check out the Froala Editor
33 |
34 |
35 |

Sample 5: Editor on 'img' tag. Two way binding.

36 | 37 | 38 |

Model Obj:

39 |
{{imgModel | json}}
40 |
41 |
42 |

Sample 6: Editor on 'button' tag

43 | 44 |

Model Obj:

45 |
{{buttonModel | json}}
46 |
47 |
48 |

Sample 7: Editor on 'input' tag

49 | 50 |

Model Obj:

51 |
{{inputModel | json}}
52 |
53 |
54 |

Sample 8: Editor on 'a' tag. Manual Initialization

55 | 56 | 57 |
58 | Froala Editor 59 |
60 |

Model Obj:

61 |
{{linkModel | json}}
62 |
63 | 64 |
65 |

Sample 9: Editor with reactive forms

66 |
67 |
Name is too short.
68 |

Textarea with formControlName and froalaModel

69 | 70 |

Rendered Content:

71 |
72 |

Textarea only with formControlName

73 | 74 | 75 |
76 | 77 |
78 | 79 |
80 |

Sample 10: Editor wrapped in a component with reactive forms

81 |
82 |
Name is too short.
83 | 84 | 85 | 86 | 87 |
88 | 89 |
90 | 91 |
92 |

Sample 11: Add Custom Button

93 |
94 |
95 | 96 | `, 97 | standalone: false 98 | }) 99 | 100 | export class AppComponent implements OnInit { 101 | 102 | ngOnInit () { 103 | FroalaEditor.DefineIcon('alert', { SVG_KEY: 'help' }); 104 | FroalaEditor.RegisterCommand('alert', { 105 | title: 'Hello', 106 | focus: false, 107 | undo: false, 108 | refreshAfterCallback: false, 109 | 110 | callback: function () { 111 | alert('Hello!'); 112 | } 113 | }); 114 | } 115 | 116 | // Sample 1 models 117 | public titleOptions: Object = { 118 | placeholderText: 'Edit Your Content Here!', 119 | charCounterCount: false, 120 | toolbarInline: true, 121 | events: { 122 | "initialized": () => { 123 | console.log('initialized'); 124 | }, 125 | "contentChanged": () => { 126 | console.log("content changed"); 127 | } 128 | } 129 | } 130 | public myTitle: string; 131 | onBlurMethod() 132 | { 133 | console.log(this.myTitle); 134 | } 135 | 136 | 137 | // Sample 2 model 138 | public content: string = 'My Document\'s Title'; 139 | 140 | 141 | // Sample 3 model 142 | public twoWayContent; 143 | 144 | // Sample 4 models 145 | public sample3Text; 146 | public initControls; 147 | public deleteAll; 148 | public initialize(initControls) { 149 | this.initControls = initControls; 150 | this.deleteAll = function() { 151 | this.initControls.getEditor().html.set(); 152 | this.initControls.getEditor().undo.reset(); 153 | this.initControls.getEditor().undo.saveStep(); 154 | }; 155 | } 156 | 157 | // Sample 5 model 158 | public imgModel: Object = { 159 | src: '/image.jpg' 160 | }; 161 | 162 | public imgOptions: Object = { 163 | angularIgnoreAttrs: ['style', 'ng-reflect-froala-editor', 'ng-reflect-froala-model'], 164 | immediateAngularModelUpdate: true, 165 | events: { 166 | "contentChanged": () => { 167 | } 168 | } 169 | } 170 | 171 | // Sample 6 model 172 | public buttonModel: Object = { 173 | innerHTML: 'Click Me' 174 | }; 175 | 176 | // Sample 7 models 177 | public inputModel: Object = { 178 | placeholder: 'I am an input!' 179 | }; 180 | 181 | // Sample 8 model 182 | linkInitControls; 183 | initializeLink(linkInitControls) { 184 | this.linkInitControls = linkInitControls; 185 | } 186 | 187 | public linkModel: Object = { 188 | href: 'https://www.froala.com/wysiwyg-editor' 189 | }; 190 | 191 | // Sample 9 192 | formControls = { 193 | formModel: new FormControl('Hello World', Validators.minLength(2)), 194 | }; 195 | form:any = new FormGroup(this.formControls); 196 | get formModel(): any { return this.form.get('formModel'); } 197 | onSubmit(): void { 198 | console.log(this.form.value); 199 | } 200 | setValue() { this.form.setValue({formModel: 'Default text'}); } 201 | 202 | // Sample 10 203 | form2 = new FormGroup({ 204 | formModel: new FormControl('Hello World', Validators.minLength(2)), 205 | }); 206 | get form2Model(): any { return this.form2.get('formModel'); } 207 | onSubmit2(): void { 208 | console.log(this.form2.value); 209 | } 210 | setValue2() { this.form2.setValue({formModel: 'Default text'}); } 211 | 212 | // Sample 11 213 | // Depending on your screen size you may want to use a specific toolbar dimension or all of them. 214 | 215 | public options: Object = { 216 | charCounterCount: true, 217 | toolbarButtons: ['bold', 'italic', 'underline', 'paragraphFormat','alert'], 218 | toolbarButtonsXS: ['bold', 'italic', 'underline', 'paragraphFormat','alert'], 219 | toolbarButtonsSM: ['bold', 'italic', 'underline', 'paragraphFormat','alert'], 220 | toolbarButtonsMD: ['bold', 'italic', 'underline', 'paragraphFormat','alert'], 221 | }; 222 | } 223 | -------------------------------------------------------------------------------- /deploy_sdk.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Steps 4 | # Identify the build agent. Check whether build agent is same as deployment server 5 | # Login to build server and build, run, check the new changes. 6 | # --force-recreate for docker-compose 7 | # --no-cache for docker build 8 | # -f for npm install 9 | # -v --rmi all for docker compose down 10 | 11 | if [ "${TRAVIS_PULL_REQUEST}" != "false" ]; then echo "Not deploying on a pull request !!!" && exit 0; fi 12 | 13 | # Define the global variables 14 | BRANCH_NAME=$(echo "${TRAVIS_BRANCH}" | tr '[:upper:]' '[:lower:]') 15 | PACKAGE_VERSION="$(jq '.version' version.json | tr -d '"')" 16 | IMAGE_NAME="$(echo "froala-${BUILD_REPO_NAME}_${TRAVIS_BRANCH}:${PACKAGE_VERSION}" | tr '[:upper:]' '[:lower:]')" 17 | BASE_DOMAIN="froala-infra.com" 18 | AO_IDENTIFIER="${TRAVIS_BRANCH}" 19 | BRANCH_LENGHT=$(echo "${TRAVIS_BRANCH}" |awk '{print length}') 20 | LW_REPO_NAME=$(echo "${BUILD_REPO_NAME}" | tr '[:upper:]' '[:lower:]' | sed -e 's/-//g' -e 's/\.//g' -e 's/_//g') 21 | CT_INDEX=0 22 | MAX_DEPLOYMENTS_NR=0 23 | SDK_ENVIRONMENT="" 24 | DEPLOYMENT_SERVER="" 25 | SERVICE_NAME="" 26 | CONTAINER_NAME="" 27 | OLDEST_CONTAINER="" 28 | 29 | # Copy the ssh key 30 | echo "${SSH_KEY}" | base64 --decode > /tmp/sshkey.pem 31 | chmod 400 /tmp/sshkey.pem 32 | 33 | # Select the deployment server based on the branch. 34 | case "${BRANCH_NAME}" in 35 | dev*) SDK_ENVIRONMENT="dev" && DEPLOYMENT_SERVER="${FROALA_SRV_DEV}";; 36 | ao-dev*) SDK_ENVIRONMENT="dev" && DEPLOYMENT_SERVER="${FROALA_SRV_DEV}";; 37 | qa*) SDK_ENVIRONMENT="qa" && DEPLOYMENT_SERVER="${FROALA_SRV_QA}";; 38 | qe*) SDK_ENVIRONMENT="qe" && DEPLOYMENT_SERVER="${FROALA_SRV_QE}";; 39 | rc*) SDK_ENVIRONMENT="stg" && DEPLOYMENT_SERVER="${FROALA_SRV_STAGING}";; 40 | release-master*) SDK_ENVIRONMENT="stg" && DEPLOYMENT_SERVER=${FROALA_SRV_STAGING};; 41 | ft*) echo "Building only on feature branch ${TRAVIS_BRANCH}... will not deploy..." && exit 0;; 42 | bf*) echo "Building only on bugfix branch ${TRAVIS_BRANCH}... will not deploy..." && exit 0;; 43 | *) echo "Not a deployment branch" && exit 1;; 44 | esac 45 | 46 | # Set the short branch name 47 | if [ "${BRANCH_LENGHT}" -lt 18 ]; then 48 | SHORT_TRAVIS_BRANCH="${TRAVIS_BRANCH}" 49 | else 50 | SHORT_TRAVIS_BRANCH="${TRAVIS_BRANCH:0:8}${TRAVIS_BRANCH: -8}" 51 | fi 52 | LW_SHORT_TRAVIS_BRANCH="$(echo "${SHORT_TRAVIS_BRANCH}" | sed -e 's/-//g' -e 's/\.//g' -e 's/_//g' | tr '[:upper:]' '[:lower:]')" 53 | 54 | # Get the maximum allowed deployment for given environment 55 | function max_allowed_deployment(){ 56 | echo "getting max deployments for environment ${SDK_ENVIRONMENT}" 57 | MAX_DEPLOYMENTS_NR=$(jq --arg sdkenvironment "${SDK_ENVIRONMENT}" '.[$sdkenvironment]' version.json | tr -d '"') 58 | echo "Max allowed deployments: ${MAX_DEPLOYMENTS_NR}" 59 | } 60 | max_allowed_deployment 61 | 62 | # Get the total numbers of deployed container for given environment 63 | function existing_deployments(){ 64 | echo "Checking the existing number of running container(s)" 65 | EXISTING_DEPLOYMENTS_NR=$(ssh -o "StrictHostKeyChecking no" -i /tmp/sshkey.pem "${SSH_USER}"@"${DEPLOYMENT_SERVER}" "sudo docker ps | grep -i ${LW_REPO_NAME}-${AO_IDENTIFIER}" | wc -l) 66 | echo "Number of existing deployment: ${EXISTING_DEPLOYMENTS_NR}" 67 | } 68 | existing_deployments 69 | 70 | # Get the old container name, no of deployments, and generate the new index and container name 71 | function generate_container_name(){ 72 | 73 | DEPL=$(ssh -o "StrictHostKeyChecking no" -i /tmp/sshkey.pem "${SSH_USER}"@"${DEPLOYMENT_SERVER}" sudo docker ps | grep -i "${LW_REPO_NAME}"-"${AO_IDENTIFIER}") 74 | echo "Containers running for ${AO_IDENTIFIER}: ${DEPL}" 75 | echo "${DEPL}" > file.txt 76 | 77 | echo "Getting indexes of oldest and latest deployed containers for ${AO_IDENTIFIER}" 78 | CT_LOWER_INDEX=$(awk -F'-' '{print $NF }' < file.txt | sort -nk1 | head -1) 79 | CT_HIGHER_INDEX=$(awk -F'-' '{print $NF }' < file.txt | sort -nk1 | tail -1) 80 | echo "Lowest index : ${CT_LOWER_INDEX} ; and Highest index : ${CT_HIGHER_INDEX}" 81 | 82 | if [ -z "${DEPL}" ]; then 83 | echo "First deployment. Setting the container name." 84 | CT_INDEX=1 85 | CONTAINER_NAME="${LW_REPO_NAME}-${AO_IDENTIFIER}-${CT_INDEX}" 86 | SERVICE_NAME="${LW_REPO_NAME}-${LW_SHORT_TRAVIS_BRANCH}" 87 | else 88 | echo "Multiple deployments detected. Setting the container name (old and new)" 89 | CT_INDEX=${CT_HIGHER_INDEX} && CT_INDEX=$((CT_INDEX+1)) 90 | OLDEST_CONTAINER="${LW_REPO_NAME}-${AO_IDENTIFIER}-${CT_LOWER_INDEX}" 91 | CONTAINER_NAME="${LW_REPO_NAME}-${AO_IDENTIFIER}-${CT_INDEX}" 92 | SERVICE_NAME="${LW_REPO_NAME}-${LW_SHORT_TRAVIS_BRANCH}-${CT_INDEX}" 93 | echo "New index: ${CT_INDEX}" 94 | fi 95 | } 96 | generate_container_name 97 | 98 | # Print useful details. 99 | echo -e "\n" 100 | echo "----------------------------------------------------------------------" 101 | echo " Selected environment: ${SDK_ENVIRONMENT}. " 102 | echo " Deployment server: ${DEPLOYMENT_SERVER}. " 103 | echo " Max allowed deployments: ${MAX_DEPLOYMENTS_NR}. " 104 | echo " Number of existing deployment: ${EXISTING_DEPLOYMENTS_NR} " 105 | echo " Oldest container name: ${OLDEST_CONTAINER} " 106 | echo " Container name for this deployment: ${CONTAINER_NAME} " 107 | echo "----------------------------------------------------------------------" 108 | echo -e "\n" 109 | 110 | # Set the deployment URL 111 | DEPLOYMENT_URL="${CONTAINER_NAME}.${SDK_ENVIRONMENT}.${BASE_DOMAIN}" 112 | 113 | # Modify the compose file and run the docker-compose. 114 | function deploy(){ 115 | 116 | # Copy the docker-compose template to docker-compose.yml 117 | cp docker-compose.yml.template docker-compose.yml 118 | 119 | # Replace the sample values 120 | sed -i "s/ImageName/${NEXUS_CR_TOOLS_URL}\/${IMAGE_NAME}/g" docker-compose.yml 121 | sed -i "s/UrlName/${DEPLOYMENT_URL}/g" docker-compose.yml 122 | sed -i "s/ServiceName/${SERVICE_NAME}/g" docker-compose.yml 123 | sed -i "s/PortNum/${CONTAINER_SERVICE_PORTNO}/g" docker-compose.yml 124 | sed -i "s/ContainerName/${CONTAINER_NAME}/g" docker-compose.yml 125 | 126 | echo -e "\n" 127 | echo "Below is the content of docker-compose.yml" 128 | echo "-------------------------------------------------" 129 | cat docker-compose.yml 130 | echo "-------------------------------------------------" 131 | echo -e "\n" 132 | 133 | # Remove the old docker-compose from deployment_server 134 | ssh -o "StrictHostKeyChecking no" -i /tmp/sshkey.pem "${SSH_USER}"@"${DEPLOYMENT_SERVER}" "if [ -d /services/${SERVICE_NAME} ]; then rm -rf /services/${SERVICE_NAME}; fi && mkdir /services/${SERVICE_NAME}" 135 | 136 | # Copy the latest docker-compose file to deployment_server 137 | scp -o "StrictHostKeyChecking no" -i /tmp/sshkey.pem docker-compose.yml "${SSH_USER}"@"${DEPLOYMENT_SERVER}":/services/"${SERVICE_NAME}"/docker-compose.yml 138 | 139 | # Run docker-compose pull on deployment_server 140 | ssh -o "StrictHostKeyChecking no" -i /tmp/sshkey.pem "${SSH_USER}"@"${DEPLOYMENT_SERVER}" "cd /services/${SERVICE_NAME}/ && sudo docker-compose pull" 141 | sleep 10 142 | 143 | # Run docker-compose up on deployment_server 144 | ssh -o "StrictHostKeyChecking no" -i /tmp/sshkey.pem "${SSH_USER}"@"${DEPLOYMENT_SERVER}" "cd /services/${SERVICE_NAME}/ && sudo docker-compose up -d --force-recreate" 145 | sleep 60 146 | 147 | RET_CODE=$(curl -k -s -o /tmp/notimportant.txt -w "%{http_code}" https://"${DEPLOYMENT_URL}") 148 | echo "validation code: $RET_CODE for https://${DEPLOYMENT_URL}" 149 | if [ "${RET_CODE}" -ne 200 ]; then 150 | echo "Deployment validation failed!!! Please check pipeline logs." 151 | exit 1 152 | else 153 | echo -e "\n\tService available at URL: https://${DEPLOYMENT_URL}\n" 154 | fi 155 | } 156 | 157 | # If existing deployment less than max deployment then just deploy don't remove old container. 158 | if [ "${EXISTING_DEPLOYMENTS_NR}" -lt "${MAX_DEPLOYMENTS_NR}" ]; then 159 | deploy 160 | fi 161 | 162 | # If existing deployment equals max deployment then delete oldest container. 163 | if [ "${EXISTING_DEPLOYMENTS_NR}" -ge "${MAX_DEPLOYMENTS_NR}" ]; then 164 | 165 | echo "Maximum deployments reached on ${SDK_ENVIRONMENT} environment for ${BUILD_REPO_NAME}." 166 | echo "Stopping container ${OLDEST_CONTAINER} ..." 167 | 168 | if ! ssh -o "StrictHostKeyChecking no" -i /tmp/sshkey.pem "${SSH_USER}"@"${DEPLOYMENT_SERVER}" sudo docker stop "${OLDEST_CONTAINER}"; then 169 | echo "Failed to stop the ${OLDEST_CONTAINER} container" 170 | fi 171 | echo "Successfully stopped the ${OLDEST_CONTAINER} container." 172 | 173 | if ! ssh -o "StrictHostKeyChecking no" -i /tmp/sshkey.pem "${SSH_USER}"@"${DEPLOYMENT_SERVER}" sudo docker rm -f "${OLDEST_CONTAINER}"; then 174 | echo "Failed to remove the ${OLDEST_CONTAINER} container" 175 | fi 176 | echo "Successfully removed the ${OLDEST_CONTAINER} container." 177 | 178 | echo "Deploying the service: ${SERVICE_NAME}" 179 | deploy && sleep 30 180 | echo "Deployment completed." 181 | fi 182 | 183 | -------------------------------------------------------------------------------- /projects/library/src/editor/editor.directive.ts: -------------------------------------------------------------------------------- 1 | import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms"; 2 | import { Directive, ElementRef, EventEmitter, forwardRef, Input, NgZone, Output, PLATFORM_ID, Inject } from '@angular/core'; 3 | import { isPlatformBrowser } from "@angular/common"; 4 | 5 | @Directive({ 6 | selector: '[froalaEditor]', 7 | exportAs: 'froalaEditor', 8 | providers: [ 9 | { 10 | provide: NG_VALUE_ACCESSOR, 11 | useExisting: forwardRef(() => FroalaEditorDirective), 12 | multi: true 13 | } 14 | ], 15 | standalone: false 16 | }) 17 | export class FroalaEditorDirective implements ControlValueAccessor { 18 | 19 | // editor options 20 | private _opts: any = { 21 | immediateAngularModelUpdate: false, 22 | angularIgnoreAttrs: null 23 | }; 24 | 25 | private _element: any; 26 | 27 | private SPECIAL_TAGS: string[] = ['img', 'button', 'input', 'a']; 28 | private INNER_HTML_ATTR: string = 'innerHTML'; 29 | private _hasSpecialTag: boolean = false; 30 | 31 | // editor element 32 | private _editor: any; 33 | 34 | // initial editor content 35 | private _model: string; 36 | 37 | private _editorInitialized: boolean = false; 38 | 39 | private _oldModel: string = null; 40 | 41 | constructor(el: ElementRef, private zone: NgZone, @Inject(PLATFORM_ID) private platformId: Object) { 42 | 43 | let element: any = el.nativeElement; 44 | 45 | // check if the element is a special tag 46 | if (this.SPECIAL_TAGS.indexOf(element.tagName.toLowerCase()) != -1) { 47 | this._hasSpecialTag = true; 48 | } 49 | this._element = element; 50 | 51 | this.zone = zone; 52 | } 53 | 54 | // Begin ControlValueAccesor methods. 55 | onChange = (_: any) => { 56 | }; 57 | onTouched = () => { 58 | }; 59 | 60 | // Form model content changed. 61 | writeValue(content: any): void { 62 | this.updateEditor(content); 63 | if(content){ 64 | this.setup(); 65 | } 66 | } 67 | 68 | registerOnChange(fn: (_: any) => void): void { 69 | this.onChange = fn; 70 | } 71 | 72 | registerOnTouched(fn: () => void): void { 73 | this.onTouched = fn; 74 | } 75 | 76 | // End ControlValueAccesor methods. 77 | 78 | // froalaEditor directive as input: store the editor options 79 | @Input() set froalaEditor(opts: any) { 80 | this._opts = this.clone( opts || this._opts); 81 | this._opts = {...this._opts}; 82 | } 83 | 84 | // TODO: replace clone method with better possible alternate 85 | private clone(item) { 86 | const me = this; 87 | if (!item) { return item; } // null, undefined values check 88 | 89 | let types = [ Number, String, Boolean ], 90 | result; 91 | 92 | // normalizing primitives if someone did new String('aaa'), or new Number('444'); 93 | types.forEach(function(type) { 94 | if (item instanceof type) { 95 | result = type( item ); 96 | } 97 | }); 98 | 99 | if (typeof result == "undefined") { 100 | if (Object.prototype.toString.call( item ) === "[object Array]") { 101 | result = []; 102 | item.forEach(function(child, index, array) { 103 | result[index] = me.clone( child ); 104 | }); 105 | } else if (typeof item == "object") { 106 | // testing that this is DOM 107 | if (item.nodeType && typeof item.cloneNode == "function") { 108 | result = item.cloneNode( true ); 109 | } else if (!item.prototype) { // check that this is a literal 110 | if (item instanceof Date) { 111 | result = new Date(item); 112 | } else { 113 | // it is an object literal 114 | result = {}; 115 | for (var i in item) { 116 | result[i] = me.clone( item[i] ); 117 | } 118 | } 119 | } else { 120 | if (false && item.constructor) { 121 | result = new item.constructor(); 122 | } else { 123 | result = item; 124 | } 125 | } 126 | } else { 127 | result = item; 128 | } 129 | } 130 | return result; 131 | } 132 | // froalaModel directive as input: store initial editor content 133 | @Input() set froalaModel(content: any) { 134 | this.updateEditor(content); 135 | } 136 | 137 | private stringify(obj) { 138 | let cache = []; 139 | let str = JSON.stringify(obj, function(key, value) { 140 | if (typeof value === "object" && value !== null) { 141 | if (cache.indexOf(value) !== -1) { 142 | // Circular reference found, discard key 143 | return; 144 | } 145 | // Store value in our collection 146 | cache.push(value); 147 | } 148 | return value; 149 | }); 150 | cache = null; // reset the cache 151 | return str; 152 | } 153 | 154 | // Update editor with model contents. 155 | private updateEditor(content: any) { 156 | if (this.stringify(this._oldModel) == this.stringify(content)) { 157 | return; 158 | } 159 | 160 | if (!this._hasSpecialTag) { 161 | this._oldModel = content; 162 | } else { 163 | this._model = content; 164 | } 165 | 166 | if (this._editorInitialized) { 167 | if (!this._hasSpecialTag) { 168 | this._editor.html.set(content); 169 | } else { 170 | this.setContent(); 171 | } 172 | } else { 173 | if (!this._hasSpecialTag) { 174 | this._element.innerHTML = content || ''; 175 | } else { 176 | this.setContent(); 177 | } 178 | } 179 | } 180 | 181 | // froalaModel directive as output: update model if editor contentChanged 182 | @Output() froalaModelChange: EventEmitter = new EventEmitter(); 183 | 184 | // froalaInit directive as output: send manual editor initialization 185 | @Output() froalaInit: EventEmitter = new EventEmitter(); 186 | 187 | // update model if editor contentChanged 188 | private updateModel() { 189 | this.zone.run(() => { 190 | 191 | let modelContent: any = null; 192 | 193 | if (this._hasSpecialTag) { 194 | 195 | let attributeNodes = this._element.attributes; 196 | let attrs = {}; 197 | 198 | for (let i = 0; i < attributeNodes.length; i++) { 199 | 200 | let attrName = attributeNodes[i].name; 201 | if (this._opts.angularIgnoreAttrs && this._opts.angularIgnoreAttrs.indexOf(attrName) != -1) { 202 | continue; 203 | } 204 | 205 | attrs[attrName] = attributeNodes[i].value; 206 | } 207 | 208 | if (this._element.innerHTML) { 209 | attrs[this.INNER_HTML_ATTR] = this._element.innerHTML; 210 | } 211 | 212 | modelContent = attrs; 213 | } else { 214 | 215 | let returnedHtml: any = this._editor.html.get(); 216 | if (typeof returnedHtml === 'string') { 217 | modelContent = returnedHtml; 218 | } 219 | } 220 | if (this._oldModel !== modelContent) { 221 | this._oldModel = modelContent; 222 | 223 | // Update froalaModel. 224 | this.froalaModelChange.emit(modelContent); 225 | 226 | // Update form model. 227 | this.onChange(modelContent); 228 | } 229 | 230 | }) 231 | } 232 | 233 | private registerEvent(eventName, callback) { 234 | if (!eventName || !callback) { 235 | return; 236 | } 237 | 238 | if (!this._opts.events) { 239 | this._opts.events = {}; 240 | } 241 | 242 | this._opts.events[eventName] = callback; 243 | } 244 | 245 | private initListeners() { 246 | let self = this; 247 | // Check if we have events on the editor. 248 | if (this._editor.events) { 249 | // bind contentChange and keyup event to froalaModel 250 | this._editor.events.on('contentChanged', function () { 251 | self.updateModel(); 252 | }); 253 | this._editor.events.on('mousedown', function () { 254 | setTimeout(function () { 255 | self.onTouched(); 256 | }, 0); 257 | }); 258 | 259 | if (this._opts.immediateAngularModelUpdate) { 260 | this._editor.events.on('keyup', function () { 261 | setTimeout(function () { 262 | self.updateModel(); 263 | }, 0); 264 | }); 265 | } 266 | } 267 | 268 | this._editorInitialized = true; 269 | } 270 | 271 | private createEditor() { 272 | if (this._editorInitialized) { 273 | return; 274 | } 275 | 276 | this.setContent(true); 277 | 278 | // init editor 279 | this.zone.runOutsideAngular(() => { 280 | // Add listeners on initialized event. 281 | if (!this._opts.events) this._opts.events = {}; 282 | 283 | // Register initialized event. 284 | this.registerEvent('initialized', this._opts.events && this._opts.events.initialized); 285 | const existingInitCallback = this._opts.events.initialized; 286 | // Default initialized event. 287 | if (!this._opts.events.initialized || !this._opts.events.initialized.overridden) { 288 | this._opts.events.initialized = () => { 289 | this.initListeners(); 290 | existingInitCallback && existingInitCallback.call(this._editor, this); 291 | }; 292 | this._opts.events.initialized.overridden = true; 293 | } 294 | 295 | import('froala-editor').then(({ default: FroalaEditor }) => { 296 | // Initialize the Froala Editor. 297 | this._editor = new FroalaEditor( 298 | this._element, 299 | this._opts 300 | ); 301 | }); 302 | }); 303 | } 304 | 305 | private setHtml() { 306 | this._editor.html.set(this._model || ""); 307 | 308 | // This will reset the undo stack everytime the model changes externally. Can we fix this? 309 | this._editor.undo.reset(); 310 | this._editor.undo.saveStep(); 311 | } 312 | 313 | private setContent(firstTime = false) { 314 | let self = this; 315 | 316 | // Set initial content 317 | if (this._model || this._model == '') { 318 | this._oldModel = this._model; 319 | if (this._hasSpecialTag) { 320 | 321 | let tags: Object = this._model; 322 | 323 | // add tags on element 324 | if (tags) { 325 | 326 | for (let attr in tags) { 327 | if (tags.hasOwnProperty(attr) && attr != this.INNER_HTML_ATTR) { 328 | this._element.setAttribute(attr, tags[attr]); 329 | } 330 | } 331 | 332 | if (tags.hasOwnProperty(this.INNER_HTML_ATTR)) { 333 | this._element.innerHTML = tags[this.INNER_HTML_ATTR]; 334 | } 335 | } 336 | } else { 337 | if (firstTime) { 338 | this.registerEvent('initialized', function () { 339 | self.setHtml(); 340 | }); 341 | } else { 342 | self.setHtml(); 343 | } 344 | } 345 | } 346 | } 347 | 348 | private destroyEditor() { 349 | if (this._editorInitialized) { 350 | this._editor.destroy(); 351 | this._editorInitialized = false; 352 | } 353 | } 354 | 355 | private getEditor() { 356 | if (this._element) { 357 | return this._editor; 358 | } 359 | 360 | return null; 361 | } 362 | 363 | // send manual editor initialization 364 | private generateManualController() { 365 | let controls = { 366 | initialize: this.createEditor.bind(this), 367 | destroy: this.destroyEditor.bind(this), 368 | getEditor: this.getEditor.bind(this), 369 | }; 370 | this.froalaInit.emit(controls); 371 | } 372 | 373 | // TODO not sure if ngOnInit is executed after @inputs 374 | ngAfterViewInit() { 375 | // check if output froalaInit is present. Maybe observers is private and should not be used?? TODO how to better test that an output directive is present. 376 | // Only allow initialization in browser 377 | if (isPlatformBrowser(this.platformId)) { 378 | this.setup(); 379 | } 380 | } 381 | 382 | private initialized = false; 383 | private setup() { 384 | if (this.initialized) { 385 | return; 386 | } 387 | this.initialized = true; 388 | if (!this.froalaInit.observers.length) { 389 | this.createEditor(); 390 | } else { 391 | this.generateManualController(); 392 | } 393 | } 394 | 395 | ngOnDestroy() { 396 | this.destroyEditor(); 397 | } 398 | 399 | setDisabledState(isDisabled: boolean): void { 400 | } 401 | } 402 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular Froala WYSIWYG Editor - [Demo](https://www.froala.com/wysiwyg-editor) 2 | 3 | [![npm](https://img.shields.io/npm/v/angular-froala-wysiwyg.svg)](https://www.npmjs.com/package/angular-froala-wysiwyg) 4 | [![npm](https://img.shields.io/npm/dm/angular-froala-wysiwyg.svg)](https://www.npmjs.com/package/angular-froala-wysiwyg) 5 | [![npm](https://img.shields.io/npm/l/angular-froala-wysiwyg.svg)](https://www.npmjs.com/package/angular-froala-wysiwyg) 6 | 7 | >Angular 19+ versions bindings for Froala WYSIWYG Editor. 8 | 9 | ![WYSIWYG HTML Editor](https://raw.githubusercontent.com/froala/wysiwyg-editor/v2/editor.jpg) 10 | 11 | ## Table of contents 12 | 1. [Installation instructions](#installation-instructions) 13 | 2. [Update editor instructions](#update-editor-instructions) 14 | 3. [Integration](#integration) 15 | - [angular-cli](#use-with-angular-cli) 16 | - [ionic v2 or v3](#use-with-ionic-v2-or-v3) 17 | - [webpack/starter](#use-with-webpack) 18 | - [System.js and JIT](#use-with-systemjs-and-jit) 19 | - [AOT](#use-with-aot) 20 | 4. [Usage](#usage) 21 | 5. [Manual Initialization](#manual-initialization) 22 | 6. [Displaying HTML](#displaying-html) 23 | 7. [License](#license) 24 | 8. [Development environment setup](#development-environment-setup) 25 | 26 | ## Installation instructions 27 | 28 | Install `angular-froala-wysiwyg` from `npm` 29 | 30 | ```bash 31 | npm install angular-froala-wysiwyg 32 | ``` 33 | 34 | You will need CSS styles 35 | 36 | ```html 37 | 38 | 39 | ``` 40 | 41 | >Note : In case you want to use font-awesome icons , you can use them by installing it. 42 | - Run ` npm install font-awesome ` 43 | - Or in `index.hml` add given cdn 44 | `` 45 | 46 | 47 | ## Update editor instructions 48 | 49 | ```bash 50 | npm update froala-editor --save 51 | ``` 52 | 53 | ## Integration 54 | 55 | ### Use with Angular CLI 56 | 57 | #### Installing @angular/cli 58 | 59 | *Note*: you can skip this part if you already have application generated. 60 | 61 | ```bash 62 | npm install -g @angular/cli 63 | ng new my-app 64 | cd my-app 65 | ``` 66 | 67 | #### Add angular-froala-wysiwyg 68 | 69 | - install `angular-froala-wysiwyg` 70 | 71 | ```bash 72 | npm install angular-froala-wysiwyg --save 73 | ``` 74 | 75 | - if you are adding Froala to an application that uses Server-side rendering, open `src/app/app.component.ts` and add 76 | 77 | ```typescript 78 | // Import helpers to detect browser context 79 | import { PLATFORM_ID, Inject } from '@angular/core'; 80 | import { isPlatformBrowser } from "@angular/common"; 81 | // Import Angular plugin. 82 | import { FroalaEditorModule, FroalaViewModule } from 'angular-froala-wysiwyg'; 83 | ... 84 | 85 | @Component({ 86 | ... 87 | imports: [FroalaEditorModule, FroalaViewModule ... ], 88 | ... 89 | }) 90 | 91 | export class AppComponent { 92 | ... 93 | constructor(@Inject(PLATFORM_ID) private platformId: Object) {} 94 | 95 | ngOnInit() { 96 | // Import Froala plugins dynamically only in the browser context 97 | if (isPlatformBrowser(this.platformId)) { 98 | // Import all Froala Editor plugins. 99 | // @ts-ignore 100 | // import('froala-editor/js/plugins.pkgd.min.js'); 101 | 102 | // Import a single Froala Editor plugin. 103 | // @ts-ignore 104 | // import('froala-editor/js/plugins/align.min.js'); 105 | 106 | // Import a Froala Editor language file. 107 | // @ts-ignore 108 | // import('froala-editor/js/languages/de.js'); 109 | 110 | // Import a third-party plugin. 111 | // @ts-ignore 112 | // import('froala-editor/js/third_party/font_awesome.min'); 113 | // @ts-ignore 114 | // import('froala-editor/js/third_party/image_tui.min'); 115 | // @ts-ignore 116 | // import('froala-editor/js/third_party/spell_checker.min'; 117 | // @ts-ignore 118 | // import('froala-editor/js/third_party/embedly.min'); 119 | } 120 | } 121 | ... 122 | } 123 | ``` 124 | 125 | - alternatively, for non-SSR applications, open `src/app/app.module.ts` and add 126 | 127 | ```typescript 128 | // Import all Froala Editor plugins. 129 | // import 'froala-editor/js/plugins.pkgd.min.js'; 130 | 131 | // Import a single Froala Editor plugin. 132 | // import 'froala-editor/js/plugins/align.min.js'; 133 | 134 | // Import a Froala Editor language file. 135 | // import 'froala-editor/js/languages/de.js'; 136 | 137 | // Import a third-party plugin. 138 | // import 'froala-editor/js/third_party/font_awesome.min'; 139 | // import 'froala-editor/js/third_party/image_tui.min'; 140 | // import 'froala-editor/js/third_party/spell_checker.min'; 141 | // import 'froala-editor/js/third_party/embedly.min'; 142 | 143 | // Import Angular plugin. 144 | import { FroalaEditorModule, FroalaViewModule } from 'angular-froala-wysiwyg'; 145 | ... 146 | 147 | @NgModule({ 148 | ... 149 | imports: [FroalaEditorModule.forRoot(), FroalaViewModule.forRoot() ... ], 150 | ... 151 | }) 152 | ``` 153 | 154 | - open `angular.json` file and insert a new entry into the `styles` array 155 | 156 | ```json 157 | "styles": [ 158 | "styles.css", 159 | "./node_modules/froala-editor/css/froala_editor.pkgd.min.css", 160 | "./node_modules/froala-editor/css/froala_style.min.css", 161 | ] 162 | ``` 163 | 164 | - open `src/app/app.component.html` and add 165 | 166 | ```html 167 |
Hello, Froala!
168 | ``` 169 | 170 | #### Run angular-cli 171 | ```bash 172 | ng serve 173 | ``` 174 | 175 | 176 | 177 | ### Use with `ionic v2 or v3` 178 | 179 | #### Create Ionic app 180 | 181 | *Note*: you can skip this part if you already have application generated. 182 | 183 | ```bash 184 | npm install -g cordova ionic 185 | ionic start sample blank 186 | cd sample 187 | ``` 188 | 189 | #### Add angular-froala-wysiwyg 190 | 191 | For v3 make sure that you use the latest version of ionic and also the latest version of angular. 192 | 193 | Installing Froala Wysiwyg Editor in Ionic is fairly easy, it can be done using npm: 194 | ```bash 195 | npm install angular-froala-wysiwyg --save 196 | ``` 197 | 198 | - Inside `src/app/app.component.html` add 199 | 200 | ```html 201 | 202 | 203 |
Hello, Froala!
204 |
205 | ``` 206 | 207 | 208 | - open `src/app/app.module.ts` and add 209 | 210 | ```typescript 211 | // Import all Froala Editor plugins. 212 | // import 'froala-editor/js/plugins.pkgd.min.js'; 213 | 214 | // Import a single Froala Editor plugin. 215 | // import 'froala-editor/js/plugins/align.min.js'; 216 | 217 | // Import a Froala Editor language file. 218 | // import 'froala-editor/js/languages/de.js'; 219 | 220 | // Import a third-party plugin. 221 | // import 'froala-editor/js/third_party/font_awesome.min'; 222 | // import 'froala-editor/js/third_party/image_tui.min'; 223 | // import 'froala-editor/js/third_party/spell_checker.min'; 224 | // import 'froala-editor/js/third_party/embedly.min'; 225 | 226 | // Import Angular2 plugin. 227 | import { FroalaEditorModule, FroalaViewModule } from 'angular-froala-wysiwyg'; 228 | ... 229 | 230 | ``` 231 | Replace 232 | ``` 233 | imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule] 234 | ``` 235 | with 236 | ``` 237 | imports: [BrowserModule, IonicModule.forRoot(), AppRoutingModule,FroalaEditorModule.forRoot(), FroalaViewModule.forRoot()] 238 | ``` 239 | 240 | - Inside `src/app/app-routing.module.ts` remove the line 241 | ``` 242 | { path: '', redirectTo: 'home', pathMatch: 'full' } 243 | ``` 244 | 245 | - Inside `src/index.html` 246 | 247 | ```html 248 | 249 | 250 | 251 | ``` 252 | 253 | - In `angular.json` change outpath of build to "outputPath": "src/assets" and insert following inside `assets`array of build: 254 | ```javascript 255 | "assets":[ 256 | ..., 257 | 258 | { 259 | "glob": "**/*", 260 | "input": "node_modules/froala-editor/css", 261 | "output": "css" 262 | }, 263 | { 264 | "glob": "**/*", 265 | "input": "node_modules/font-awesome/css", 266 | "output": "css" 267 | }, 268 | { 269 | "glob": "**/*", 270 | "input": "node_modules/font-awesome/fonts", 271 | "output": "fonts" 272 | }, 273 | { 274 | "glob": "**/*", 275 | "input": "node_modules/froala-editor/js", 276 | "output": "js" 277 | } 278 | ] 279 | ``` 280 | 281 | #### Run your App 282 | 283 | ```bash 284 | ionic build 285 | ionic serve 286 | ``` 287 | 288 | 289 | 290 | ### Use with `webpack/starter` 291 | 292 | #### Create webpack app 293 | 294 | *Note*: you can skip this part if you already have application generated. 295 | 296 | ```bash 297 | git clone --depth 1 https://github.com/AngularClass/angular-starter.git 298 | cd angular-starter 299 | npm install 300 | npm install rxjs@6.0.0 --save 301 | npm install @types/node@10.1.4 302 | ``` 303 | 304 | #### Add angular-froala-wysiwyg 305 | 306 | - install `angular-froala-wysiwyg` 307 | 308 | ```bash 309 | npm install angular-froala-wysiwyg --save 310 | ``` 311 | 312 | - open `src/app/app.module.ts` and add 313 | 314 | ```typescript 315 | // Import all Froala Editor plugins. 316 | // import 'froala-editor/js/plugins.pkgd.min.js'; 317 | 318 | // Import a single Froala Editor plugin. 319 | // import 'froala-editor/js/plugins/align.min.js'; 320 | 321 | // Import a Froala Editor language file. 322 | // import 'froala-editor/js/languages/de.js'; 323 | 324 | // Import a third-party plugin. 325 | // import 'froala-editor/js/third_party/font_awesome.min'; 326 | // import 'froala-editor/js/third_party/image_tui.min'; 327 | // import 'froala-editor/js/third_party/spell_checker.min'; 328 | // import 'froala-editor/js/third_party/embedly.min'; 329 | 330 | // Import Angular plugin. 331 | import { FroalaEditorModule, FroalaViewModule } from 'angular-froala-wysiwyg'; 332 | ... 333 | 334 | @NgModule({ 335 | ... 336 | imports: [FroalaEditorModule.forRoot(), FroalaViewModule.forRoot(), ... ], 337 | ... 338 | }) 339 | ``` 340 | 341 | - open `src/app/app.component.ts` and add to the template 342 | 343 | ```html 344 |
Hello, Froala!
345 | ``` 346 | 347 | - open `config/webpack.common.js` 348 | 349 | ```javascript 350 | var webpack = require('webpack'); 351 | ``` 352 | 353 | 354 | - open `config/webpack.common.js` and add the following to `CopyWebpackPlugin` 355 | 356 | ```javascript 357 | { 358 | from: 'node_modules/froala-editor/css/', 359 | to: 'assets/froala-editor/css/', 360 | }, 361 | ``` 362 | 363 | - open `config/head-config.common.js` and add a new entry to link 364 | 365 | ```javascript 366 | { rel: 'stylesheet', href: '/assets/froala-editor/css/froala_editor.pkgd.min.css' }, 367 | { rel: 'stylesheet', href: '/assets/froala-editor/css/froala_style.min.css' } 368 | ``` 369 | 370 | #### Run webpack app 371 | 372 | ```bash 373 | npm run start 374 | ``` 375 | 376 | ### Use with `system.js` and `JIT` 377 | 378 | #### Create Angular app 379 | 380 | *Note*: you can skip this part if you already have application generated. 381 | 382 | ```bash 383 | git clone https://github.com/froala/angular-froala-systemjs-demo 384 | cd angular-froala-systemjs-demo 385 | npm install 386 | ``` 387 | 388 | #### Add angular-froala-wysiwyg 389 | 390 | - install `angular-froala-wysiwyg` 391 | 392 | ```bash 393 | npm install angular-froala-wysiwyg --save 394 | ``` 395 | 396 | - open `src/index.html` and add 397 | 398 | ```html 399 | 400 | 401 | ``` 402 | 403 | - open `src/app/app.module.ts` and add 404 | 405 | ```typescript 406 | // Import all Froala Editor plugins. 407 | // import 'froala-editor/js/plugins.pkgd.min.js'; 408 | 409 | // Import a single Froala Editor plugin. 410 | // import 'froala-editor/js/plugins/align.min.js'; 411 | 412 | // Import a Froala Editor language file. 413 | // import 'froala-editor/js/languages/de.js'; 414 | 415 | // Import a third-party plugin. 416 | // import 'froala-editor/js/third_party/font_awesome.min'; 417 | // import 'froala-editor/js/third_party/image_tui.min'; 418 | // import 'froala-editor/js/third_party/spell_checker.min'; 419 | // import 'froala-editor/js/third_party/embedly.min'; 420 | 421 | // Import Angular2 plugin. 422 | import { FroalaEditorModule, FroalaViewModule } from 'angular-froala-wysiwyg'; 423 | ... 424 | 425 | @NgModule({ 426 | ... 427 | imports: [FroalaEditorModule.forRoot(), FroalaViewModule.forRoot(), ... ], 428 | ... 429 | }) 430 | ``` 431 | 432 | - open `src/app/app.component.ts` file and add to the template 433 | 434 | ```html 435 |
Hello, Froala!
436 | ``` 437 | 438 | #### Run app 439 | 440 | ```bash 441 | npm run start 442 | ``` 443 | 444 | 445 | 446 | ### Use with `aot` 447 | 448 | #### Create Angular app 449 | 450 | 1. ng new froala-aot 451 | 452 | ```javascript 453 | { 454 | "glob": "**/*", 455 | "input": "./node_modules/froala-editor", 456 | "output": "assets/froala-editor/" 457 | }, 458 | { 459 | "glob": "**/*", 460 | "input": "./node_modules/font-awesome", 461 | "output": "assets/font-awesome/" 462 | }, 463 | { 464 | "glob": "**/*", 465 | "input": "./node_modules/jquery", 466 | "output": "assets/jquery/" 467 | } 468 | ``` 469 | - Go to `package.json` and update `scripts.build` to `ng build --aot` and `scripts.start` to `ng serve --aot` 470 | 471 | #### Add angular-froala-wysiwyg 472 | 473 | - install `angular-froala-wysiwyg` 474 | 475 | In case you want to use font-awesome icons , you can use them by installing it. 476 | 477 | - Run ` npm install font-awesome ` and add in `app.module.ts` 478 | ``` 479 | import 'froala-editor/js/third_party/font_awesome.min'; 480 | ``` 481 | 482 | - Go to `angular.json` and change `architect.build.options.outputPath` to `src/dist` and add following json to `architect.build.options.assets array` 483 | 484 | ```bash 485 | npm install angular-froala-wysiwyg --save 486 | ``` 487 | 488 | - open `src/index.html` and add 489 | 490 | ```html 491 | 492 | 493 | ``` 494 | 495 | - open `src/app/app.module.ts` and add 496 | 497 | ```typescript 498 | // Import all Froala Editor plugins. 499 | // import 'froala-editor/js/plugins.pkgd.min.js'; 500 | 501 | // Import a single Froala Editor plugin. 502 | // import 'froala-editor/js/plugins/align.min.js'; 503 | 504 | // Import a Froala Editor language file. 505 | // import 'froala-editor/js/languages/de.js'; 506 | 507 | // Import a third-party plugin. 508 | // import 'froala-editor/js/third_party/image_tui.min'; 509 | // import 'froala-editor/js/third_party/spell_checker.min'; 510 | // import 'froala-editor/js/third_party/embedly.min'; 511 | 512 | // Import Angular2 plugin. 513 | import { FroalaEditorModule, FroalaViewModule } from 'angular-froala-wysiwyg'; 514 | ... 515 | 516 | @NgModule({ 517 | ... 518 | imports: [FroalaEditorModule.forRoot(), FroalaViewModule.forRoot(), ... ], 519 | ... 520 | }) 521 | ``` 522 | 523 | - open `src/app/app.component.ts` file and add to the template 524 | 525 | ```html 526 |
Hello, Froala!
527 | ``` 528 | 529 | #### Run app 530 | 531 | ```bash 532 | npm run build 533 | npm run start 534 | ``` 535 | 536 | 537 | ## Usage 538 | 539 | ### Options 540 | 541 | You can pass editor options as Input (optional). 542 | 543 | `[froalaEditor]='options'` 544 | 545 | You can pass any existing Froala option. Consult the [Froala documentation](https://www.froala.com/wysiwyg-editor/docs/options) to view the list of all the available options: 546 | 547 | ```typescript 548 | public options: Object = { 549 | placeholderText: 'Edit Your Content Here!', 550 | charCounterCount: false 551 | } 552 | ``` 553 | 554 | Additional option is used: 555 | * **immediateAngularModelUpdate**: (default: false) This option synchronizes the angular model as soon as a key is released in the editor. Note that it may affect performances. 556 | 557 | 558 | 559 | ### Events and Methods 560 | 561 | Events can be passed in with the options, with a key events and object where the key is the event name and the value is the callback function. 562 | 563 | ```typescript 564 | public options: Object = { 565 | placeholder: "Edit Me", 566 | events : { 567 | 'focus' : function(e, editor) { 568 | console.log(editor.selection.get()); 569 | } 570 | } 571 | } 572 | ``` 573 | 574 | Using the editor instance from the arguments of the callback you can call editor methods as described in the [method docs](http://froala.com/wysiwyg-editor/docs/methods). 575 | 576 | Froala events are described in the [events docs](https://froala.com/wysiwyg-editor/docs/events). 577 | 578 | 579 | 580 | ### Model 581 | 582 | The WYSIWYG HTML editor content model. 583 | 584 | `[(froalaModel)]="editorContent"` 585 | 586 | Pass initial content: 587 | 588 | ```typescript 589 | public editorContent: string = 'My Document\'s Title' 590 | ``` 591 | 592 | Use the content in other places: 593 | 594 | ```html 595 | 596 | 597 | ``` 598 | 599 | Other two way binding example: 600 | 601 | ```html 602 |
603 |
604 | ``` 605 | 606 | Use it with reactive forms: 607 | 608 | ```html 609 |
610 | 611 | 612 |
613 | ``` 614 | 615 | If you want to use two-way binding to display the form model in other places you must include `[(froalaModel)]`: 616 | 617 | ```html 618 |
619 | 620 |
621 | 622 |
623 | ``` 624 | 625 | If you want to wrap froalaEditor directive into a component that supports reactive forms please see [froala.component.ts](https://github.com/froala/angular-froala-wysiwyg/blob/master/projects/demo/src/app/froala.component.ts) from demo. 626 | 627 | ### Extend functionality 628 | 629 | You can extend the functionality by adding a custom button like bellow: 630 | 631 | ```typescript 632 | // Import Froala Editor. 633 | import FroalaEditor from 'froala-editor'; 634 | 635 | // We will make usage of the Init hook and make the implementation there. 636 | import { Component, OnInit } from '@angular/core'; 637 | 638 | @Component({ 639 | selector: 'app-demo', 640 | template: `
641 |

Sample 11: Add Custom Button

642 |
643 |
`, 644 | 645 | 646 | export class AppComponent implements OnInit{ 647 | 648 | ngOnInit () { 649 | FroalaEditor.DefineIcon('alert', {NAME: 'info'}); 650 | FroalaEditor.RegisterCommand('alert', { 651 | title: 'Hello', 652 | focus: false, 653 | undo: false, 654 | refreshAfterCallback: false, 655 | 656 | callback: () => { 657 | alert('Hello!', this); 658 | } 659 | }); 660 | } 661 | 662 | public options: Object = { 663 | charCounterCount: true, 664 | toolbarButtons: ['bold', 'italic', 'underline', 'paragraphFormat','alert'], 665 | toolbarButtonsXS: ['bold', 'italic', 'underline', 'paragraphFormat','alert'], 666 | toolbarButtonsSM: ['bold', 'italic', 'underline', 'paragraphFormat','alert'], 667 | toolbarButtonsMD: ['bold', 'italic', 'underline', 'paragraphFormat','alert'], 668 | }; 669 | } 670 | ``` 671 | 672 | 673 | ### Special tags 674 | Note: In order to use special tags in `app.module.ts` add 675 | ``` 676 | // Import all Froala Editor plugins. 677 | import 'froala-editor/js/plugins.pkgd.min.js'; 678 | ``` 679 | 680 | You may also use the editor on **img**, **button**, **input** and **a** tags: 681 | 682 | ```html 683 | 684 | ``` 685 | 686 | The model must be an object containing the attributes for your special tags. Example: 687 | 688 | ```typescript 689 | public imgObj: Object = { 690 | src: 'path/to/image.jpg' 691 | }; 692 | ``` 693 | 694 | The froalaModel will change as the attributes change during usage. 695 | 696 | * froalaModel can contain a special attribute named **innerHTML** which inserts innerHTML in the element: If you are using 'button' tag, you can specify the button text like this: 697 | 698 | ```typescript 699 | public buttonModel: Object = { 700 | innerHTML: 'Click Me' 701 | }; 702 | ``` 703 | As the button text is modified by the editor, the **innerHTML** attribute from buttonModel model will be modified too. 704 | 705 | 706 | 707 | ### Specific option for special tags 708 | 709 | * **angularIgnoreAttrs**: (default: null) This option is an array of attributes that you want to ignore when the editor updates the froalaModel: 710 | 711 | ```typescript 712 | public inputOptions: Object = { 713 | angularIgnoreAttrs: ['class', 'id'] 714 | }; 715 | ``` 716 | 717 | 718 | 719 | ## Manual Initialization 720 | 721 | Gets the functionality to operate on the editor: create, destroy and get editor instance. Use it if you want to manually initialize the editor. 722 | 723 | `(froalaInit)="initialize($event)"` 724 | 725 | Where `initialize` is the name of a function in your component which will receive an object with different methods to control the editor initialization process. 726 | 727 | ```typescript 728 | public initialize(initControls) { 729 | this.initControls = initControls; 730 | this.deleteAll = function() { 731 | this.initControls.getEditor()('html.set', ''); 732 | }; 733 | } 734 | ``` 735 | 736 | The object received by the function will contain the following methods: 737 | 738 | - **initialize**: Call this method to initialize the Froala Editor 739 | - **destroy**: Call this method to destroy the Froala Editor 740 | - **getEditor**: Call this method to retrieve the editor that was created. This method will return *null* if the editor was not yet created 741 | 742 | 743 | 744 | 745 | ## Displaying HTML 746 | 747 | To display content created with the froala editor use the froalaView directive. 748 | 749 | `[froalaView]="editorContent"` 750 | 751 | ```html 752 |
753 |
754 | ``` 755 | 756 | 757 | 758 | ## License 759 | 760 | The `angular-froala-wyswiyg` project is under MIT license. However, in order to use Froala WYSIWYG HTML Editor plugin you should purchase a license for it. 761 | 762 | Froala Editor has [3 different licenses](http://froala.com/wysiwyg-editor/pricing) for commercial use. 763 | For details please see [License Agreement](http://froala.com/wysiwyg-editor/terms). 764 | 765 | 766 | 767 | ## Development environment setup 768 | 769 | If you want to contribute to angular-froala-wyswiyg, you will first need to install the required tools to get the project going. 770 | 771 | #### Prerequisites 772 | 773 | * [Node Package Manager](https://npmjs.org/) (NPM) 774 | * [Git](http://git-scm.com/) 775 | 776 | #### Install dependencies 777 | 778 | $ npm install 779 | 780 | #### Build 781 | 782 | $ npm run demo.build 783 | 784 | #### Run Demo 785 | 786 | $ npm run start 787 | --------------------------------------------------------------------------------