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.