GetmeUK / ContentTools

A JS library for building WYSIWYG editors for HTML content.
http://getcontenttools.com
MIT License
3.96k stars 398 forks source link

Implement ContentTools in to Angular (2+) #380

Closed frankspin89 closed 6 years ago

frankspin89 commented 7 years ago

Is there any reference or guide available to implement ContentTools with Angular (2+)?

Found some guides to implement ContentTools with AngularJs, only is Angular (2+) so different then AngularJs that it's not workable.

Love to hear what my options are.

anthonyjb commented 7 years ago

Hi @frankspin89,

I'd also love to hear from anyone who has integrated ContentTools with Angular2. I know lots of devs use Angular2 but unfortunately I don't myself and I'm not in contact with anyone who does (admittedly it's a small circle but most of the devs I speak to frequently are React or Vue based).

Sorry I can't personally be of any help @frankspin89 - I'll send out a tweet also asking the same question.

frankspin89 commented 7 years ago

@anthonyjb Thanks for your replay. I'm now trying to implement it myself. I have the basics working now.

ContentToolsComponent

const ContentToolsLib = require('./contenttools.js');

import { NgModule, EventEmitter, AfterViewInit, Component, Output, Input, OnDestroy } from '@angular/core';

@Component({
  selector: 'app-content-tools',
  template: '<div class="content-tools"><ng-content></ng-content></div>',
})

@NgModule({
  declarations: [ContentToolsComponent],
  exports: [ContentToolsComponent]
})

export class ContentToolsComponent implements  AfterViewInit, OnDestroy {
  @Output() onSave = new EventEmitter<Object>();
  editor = ContentToolsLib.EditorApp.get();

  ngAfterViewInit() {
    this.editor.init('*[data-editable]', 'data-name');
    this.editor.addEventListener('saved', ev => {
      const regions = ev.detail().regions;
      this.onSave.emit(regions);
    });
  }

  ngOnDestroy() {
    this.editor.destroy();
  }
}

And then use the component like this

<app-content-tools (onSave)="onSave($event)">
  <div data-editable data-name="heading" [innerHTML]="post.heading">
  </div>
  <div data-editable data-name="content" [innerHTML]="post.content">
  </div>
</app-content-tools>

Far from pretty at the moment. Keep updating this thread if I made some progress. All help is welcome to implement this Awesome library in to Angular 2+

frankspin89 commented 7 years ago

@anthonyjb question: I'm trying to implement auto saving.

The problem that i'm facing is that angular is replacing the dom when the content is updated. That is resulting in the fact that you can't edit the updated section/region.

Is there a method that I can call after a successful save to reactive the editor in the updated section/region? Found: this.editor.start(); but that isn't working for me

anthonyjb commented 7 years ago

@frankspin89 so if you perform a passive save (e.g editor.save(true)) then CT wont update the DOM it will only collected the modified data, does that help?

frankspin89 commented 7 years ago

@anthonyjb I don't think that it makes any difference. I'm using a realtime database and want to create a option to collaborate on a single page in realtime. So after x seconds I save the data from ContentTools to the database. A listener is then updating the dom with the fresh data.

The saving part is working correctly at the moment. Only updating the dom with the fresh data is problematic.

To make the updated region editable again, I have to manually close the editor and start the editor again. So i'm looking for a function to call after I updated the dom with the fresh data.

anthonyjb commented 7 years ago

@frankspin89 ok that makes sense now, I think you're looking for editor.syncRegions('*[data-editable]', true).

This will resync the the editable regions with the DOM and if the editor is running it will do it live. We have to send the '*[data-editable]' argument to force the editor to pick up the replaced DOM elements, and the second argument is to let the editor know it's restoring e (not initialising) so it doesn't change the last modified date against the region (which I think here is what you want).

Let me know how you get on.

frankspin89 commented 7 years ago

@anthonyjb I think that .syncRegions is the right function. But I'm not sure why it's not working for me.

ContentToolsComponent

const ContentToolsLib = require('./contenttools.js');

import {
  NgModule, ViewChild, EventEmitter, AfterViewInit,
  ElementRef, Component, Output, Input, OnDestroy, OnChanges
} from '@angular/core';
import { Observable } from 'rxjs/Observable';

@Component({
  selector: 'app-content-tools',
  template: '<div #contenttools class="content-tools"><ng-content></ng-content></div>',
})

@NgModule({
  declarations: [ContentToolsComponent],
  exports: [ContentToolsComponent]
})

export class ContentToolsComponent implements AfterViewInit, OnDestroy, OnChanges {
  @Output() onSave = new EventEmitter<Object>();
  @Input() editorState;
  @ViewChild('contenttools') element: ElementRef;
  editor = ContentToolsLib.EditorApp.get();

  ngAfterViewInit() {
    this.editor.init('*[data-editable]', 'data-name');
    const that = this;

    this.editor.addEventListener('saved', function (ev) {
      const regions = ev.detail().regions;
      if (Object.keys(regions).length === 0) {
        return;
      }
      that.onSave.emit(regions);
    });

    this.editor.addEventListener('start', function (ev) {
      const that = this;
      function autoSave() {
        that.save(true);
      };
      this.autoSaveTimer = setInterval(autoSave, 5 * 1000);
    });
  }

  ngOnChanges() {
    if (this.editorState) {
      const state = this.editor.getState();
      const domRegions = this.editor.domRegions();

      console.log('state', state);
      console.log('doms', domRegions);

      this.editor.syncRegions('*[data-editable]', true);
      this.editorState = false;
    }
  }

  ngOnDestroy() {
    this.editor.destroy();
  }
}

Component that uses ContentToolsComponent

import { Component, OnInit } from '@angular/core';
import { ClientService } from 'app/shared';
import { ContentToolsComponent } from '../shared/contenttools/contenttools';
import { ProposalService } from '../shared/services/proposal.service';

@Component({
  selector: 'app-proposals',
  templateUrl: './proposals.component.html',
})
export class ProposalsComponent implements OnInit {

  private post: Object;
  private editorState = false;

  constructor(
    private proposalService: ProposalService
  ) { }

  ngOnInit() {
    this.proposalService.loadProposal().subscribe(
      proposal => this.post = proposal
    );
  }

  onSave(regions) {
    this.post = Object.assign({}, this.post, regions);
    this.proposalService.updateProposal(this.post).take(1).subscribe(data => this.editorState = true);
  }
}

Template

<h1>Proposals</h1>
<app-content-tools (onSave)="onSave($event)" [editorState]="editorState" *ngIf="post">
  <div data-editable data-name="heading" [innerHTML]="post.heading">
  </div>
  <div data-editable data-name="content" [innerHTML]="post.content">
  </div>
</app-content-tools>

DOM before update

<app-content-tools _ngcontent-fkp-20="" ng-reflect-editor-state="true">
  <div class="content-tools">
    <div _ngcontent-fkp-20="" data-editable="" data-name="content" ng-reflect-inner-h-t-m-l="<h2>&amp;#10;   The content of the page&amp;#10;</h2>&amp;#10;<p>&amp;#10;    &amp;#10;</p>">
      <h2 class="ce-element ce-element--type-text ce-element--focused" contenteditable="">The content of the page</h2>
      <p class="ce-element--empty ce-element ce-element--type-text"></p>
    </div>
  </div>
</app-content-tools>

DOM after update

<app-content-tools _ngcontent-fkp-20="" ng-reflect-editor-state="true">
  <div class="content-tools">
    <div _ngcontent-fkp-20="" data-editable="" data-name="content" ng-reflect-inner-h-t-m-l="<h2>&amp;#10;    The new content of the page&amp;#10;</h2>&amp;#10;<p>&amp;#10;    &amp;#10;</p>">
      <h2>The new content of the page</h2>
      <p></p>
    </div>
  </div>
</app-content-tools>
anthonyjb commented 7 years ago

Hi @frankspin89 ok so I think I perhaps we need to try a different approach, I hadn't fully comprehended what was going on before, so instead of syncRegions lets define a new function which will bring the inner content of each region in line...

function updateRegions() {
    var editor = ContentToolsLib.EditorApp.get();

    // For each editable region
    for (var name in editor.regions()) {
        var region = editor.regions()[name];

        // Set the content the editable content for the region to the inner contents
        //
        // NOTE: This isn't safe to do if the content of the region is still editable as    
        // you get editable > editable content - not a good thing.
        region.setContent(region.domElement());
    }
}

If you know the region updated from your query with the remote server you could just look up that region and and set it's inner contents, which would be far more efficient than updating all of the regions (if there are multiple). You can also just send setContent a HTML string which might be preferable.

Let me know how you get on - sorry for the slightly delayed reply.

ZsZs commented 6 years ago

Hi @frankspin89 , @al-on-github , Is there any publicly available result of this effort? I would love to use ContentTools with Angular 6 too.

iursevla commented 6 years ago

ping @frankspin89 @al-on-github @ZsZs any news on this?

ghost commented 5 years ago

ping @frankspin89 @al-on-github @ZsZs any news on this?

lixaotec commented 4 years ago

+1