JSFiddle - React, Tailwind, and code Playground

by Dogbert

HTML

<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.9/angular.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootswatch/3.3.7/cosmo/bootstrap.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-sanitize/1.5.9/angular-sanitize.min.js"></script>
<div class="container-fluid" ng-app="blocks" ng-controller="MainCtrl">
  <div class="row">
    <div class="col-xs-6">
      <div id="editor" contenteditable>
        <div ng-repeat="block in blocks">
          <div ng-switch on="block.type">
            <div ng-switch-when="textblock" class="block textblock" editable ng-model="block.data"></div>
            <ul ng-switch-when="list" class="block list">
              <li ng-repeat="datum in block.data" class="block listitem" editable ng-model="datum.data"></li>
            </ul>
          </div>
        </div>
      </div>
    </div>
    <div class="col-xs-6">
      <pre>{{blocks | json}}</pre>
    </div>
  </div>
</div>

CSS

pre {
  font-size: 12px;
}

JavaScript

var app = angular.module('blocks', ['ngSanitize']);

app.directive('editable', ['$sce', function($sce) {
  return {
    restrict: 'A', // only activate on element attribute
    require: '?ngModel', // get a hold of NgModelController
    link: function(scope, element, attrs, ngModel) {
      if (!ngModel) return; // do nothing if no ng-model
      
      element.html(ngModel.$viewValue);

      // Specify how UI should be updated
      ngModel.$render = function() {
        element.html($sce.getTrustedHtml(ngModel.$viewValue || ''));
      };

      // Listen for change events to enable binding
      element.on('blur keyup change', function() {
        scope.$evalAsync(read);
      });
      read(); // initialize

      // Write data to the model
      function read() {
        var html = element.html();
        // When we clear the content editable the browser leaves a <br> behind
        // If strip-br attribute is provided then we strip this out
        if ( attrs.stripBr && html == '<br>' ) {
          html = '';
        }
        ngModel.$setViewValue(html);
      }
    }
  };
}]);

app.controller('MainCtrl', function($scope) {
  $scope.blocks = [{
    type: "textblock",
    data: "<b>this is</b> some content"
  }, {
    type: "list",
    data: [{
      type: "listitem",
      data: "<i>This is</i> a list item"
    }, {
      type: "listitem",
      data: "<i>This is</i> also a list item"
    }]
  }];
});