Here is a Custom Component to do Real Time Collaborative Editing

A client wanted a text editor in their dashboard for leaving notes and info for others. I thought of various ways to do it: Rich text editor tied to the database, an iFrame to a shared Google doc and Yjs but all were flawed in different ways. Then I found Firepad. Not perfect, but good enough and I could get it working without too much hassle.

This give you a Rich text editor component that is live with other users like having two or more edit a Google doc: You type and others see your cursor and changes in real time!

You need to set up a free Firebase Realtime Database, enable authentication and set up a web app. Then you can get the API Key, the Auth Domain and the Database URL.

Create a custom component with this iFrame code:

  <script src="https://www.gstatic.com/firebasejs/5.5.4/firebase.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.17.0/codemirror.js"></script>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.17.0/codemirror.css" />
  <link rel="stylesheet" href="https://firepad.io/releases/v1.5.9/firepad.css" />
  <script src="https://firepad.io/releases/v1.5.9/firepad.min.js"></script>

  <style>
    html { height: 100%; }
    body { margin: 0; height: 100%; position: relative; }
      /* Height / width / positioning can be customized for your use case.
         For demo purposes, we make firepad fill the entire browser. */
    #firepad-container {
      width: 100%;
      height: 100%;
    }
  </style>
</head>
<script>
  window.Retool.subscribe(function(model) {
    if (!model) { return }
    init(model)
  });


  function init(model) {
    var config = {
      apiKey: model.apiKey,
      authDomain: model.authDomain,
      databaseURL: model.databaseURL
    };
    firebase.initializeApp(config);

    //// Get Firebase Database reference.
    var firepadRef = getExampleRef(model.firepadRef);

    //// Create CodeMirror (with lineWrapping on).
    var codeMirror = CodeMirror(document.getElementById('firepad-container'), { lineWrapping: true });

    //// Create Firepad (with rich text toolbar and shortcuts enabled).
    var firepad = Firepad.fromCodeMirror(firepadRef, codeMirror,
        { richTextToolbar: true, richTextShortcuts: true });

    //// Initialize contents.
    firepad.on('ready', function() {
      if (firepad.isHistoryEmpty()) {
        firepad.setHtml('<span style="font-size: 24px;">Rich-text editing with <span style="color: red">Firepad!</span></span><br/><br/>Collaborative-editing made easy.\n');
      }
    });
  }

  // Helper to get hash from end of URL or generate a random one.
  function getExampleRef(firepadRef) {
    var ref = firebase.database().ref();
    var hash = firepadRef
      ref = ref.child(hash);
    if (typeof console !== 'undefined') {
      console.log('Firebase data: ', ref.toString());
    }
    return ref;
  }
</script>
<body onload="init()">
  <div id="firepad-container"></div>
</body>

Put this your model:

{
  "firepadRef": "234kljsdfj", // A unique name. All pads with this name will collab.
  "apiKey": "[Your Key]",
  "authDomain" : "[Your Domain]",
  "databaseURL": "[You DB]"
}

Set the Top level navigation and Storage and cookies permissions on the component and you are done.

If you want different "pads" then change the firepadRef. All pads with the same firepadRef will be shared.

4 Likes