Good in-line code editor similar to TinyMCE?
Added by Mark Travis 4 months ago
I'm working on a request to allow users to type code (Lua in particular) into a chat-style format, and have the code syntax colored.
Kind of like this forum when you use the "<>" button to insert code
auto someCPPCode = this;
I found a plugin called CodeSample that is a TinyMCE plugin. But that requires double-clicking to open the sub-editor, and the code isn't syntax highlighted until you close it. https://www.tiny.cloud/docs/tinymce/latest/codesample/
Ideally, they want something to work in-line the way TinyMCE works with WTextEdit.
There is https://github.com/codemirror/dev but I'm not sure, yet, what kind of effort that will be to implement via WTextEdit.
Another requirement is to run all code locally with no outside interenet connection, so any library I find needs to be able to run in a local net environment.
Replies (20)
RE: Good in-line code editor similar to TinyMCE? - Added by Matthias Van Ceulebroeck 4 months ago
Hey Mark,
while I cannot chime in on an alternative, as I've not used one. I can add some information on your remark.
but I'm not sure, yet, what kind of effort that will be to implement via WTextEdit.
I would not override WTextEdit
, but go to that file's base class WTextArea
. You can then also create the JS implementation file, similar to WTextEdit.js
.
The documentation of CodeMirror seems pretty good. Allowing its state to be managed by JS calls.
A rough outline would be to then:
- initialise the widget, loading the JS file, and setting its JS members
- call the JS constructor, which defines the extensions to be used
- the
updateDom
function will then cal the CodeMirror API on any changes the application performed
You may want to see if the API offers some hooks you should be aware of (content changes, plugin changes, renders, ...), which can be listened to as JSignal
s.
Personally I don't think it's a huge amount of work (but of course, I work with Wt a lot, and I am biased), and it will be a teaching experience on many Wt concepts.
If you do take this route, and encounter issues, let me know!
If you do take this route, and do NOT encounter issues, still let me know, as I find this interesting! ;)
Best,
Matthias
RE: Good in-line code editor similar to TinyMCE? - Added by Mark Travis 3 months ago
I'll give it a shot. I'll also contribute the basic implementation back to the community if it works.
Is there a naming convention for Wt? Wt::WCodeMirror? Wt::WCodeEdit?
RE: Good in-line code editor similar to TinyMCE? - Added by Matthias Van Ceulebroeck 3 months ago
I don't think this really has a convention, since WTextEdit
doesn't mention the specific library it uses at all.
I have a slight preference for WCodeMirrorEdit
, since that both mentions the used library and makes clear what the widget's task will be.
This is only a personal preference, so feel free to take up whatever name you see fit.
I'll give it a shot. I'll also contribute the basic implementation back to the community if it works.
That is a wonderful idea. I have a ticket pending to provide more tutorials, so that people can more easily start working with Wt, and have some blueprints for certain approached. This would fit great in there! So if you could provide that to me, that would be fantastic!
RE: Good in-line code editor similar to TinyMCE? - Added by Mark Travis 3 months ago
LOL! Great minds think alike. WCodeMirrorEdit is exactly what I called it.
I look forward to seeing your tutorials! I've been thinking of writing a Wt book if I can find the time. It really deserves to be much more widely used than it is.
RE: Good in-line code editor similar to TinyMCE? - Added by Wim Dumon 3 months ago
Hey Mark,
CodeMirror works well with Wt, I've seen it used in a couple of projects. About 100 lines of integration effort, I guess?
Wim.
RE: Good in-line code editor similar to TinyMCE? - Added by Mark Travis about 1 month ago
ok, this is driving me crazy. I've been looking through all of the documentation as well as all of the code in WTextEdit.c, WTextEdit.js, WLeafletMap.C, WLeafletMap.js, and WGoogleMap.c (which has no corresponding .js).
Everything but WGoogleMap seems to use quite a few "internal use only" macros and APIs. I feel like I'm on the verge of getting it working, then nothing. Meaning everything appears to work, but it doesn't.
I have a CodeMirror 6 package that I created with npm rollup that contains everything including the css it works when I test it in a browser using very simple html. (See working HTML at the end of this post.)
There is no "setup" per se as everything is in the rollup. Things like "dispatch" need to be invoked to do anything to the EditorState or EditorView.
Here's what I've got for a WTextArea sub-class for wt_codemirror.c:
// declaration of class is -----> class wt_codemirror : public Wt::WTextArea
Wt::WApplication *app = Wt::WApplication::instance();
std::string codeMirrorURL;
app->readConfigurationProperty("codeMirrorURL", codeMirrorURL);
setInline(false);
app->require(codeMirrorURL, "codemirror");
Wt::WStringStream jsStream;
// The function entry0 (o) below is just some sample text to initialize in the editor
// and if it works, it should be highlighted appropriately.
jsStream << "codemirror.createEditorState('function entry0 (o)');";
this->setJavaScriptMember("editorState", jsStream.str());
jsStream.clear();
jsStream << "codemirror.createEditorView(" << jsRef() <<".editorState, document.getElementById('" << id() << "'));";
this->setJavaScriptMember("editorView", jsStream.str());
jsStream.clear();
jsStream << jsRef() << ".editorView.dispatch({ changes: { from: 0, insert:'// Programmatically add' }});";
//
this->doJavaScript(jsStream.str());
I never see the "function entry0 (o)" line appear in the WTextArea, or the comment "//Programmatically add" in the first line.
I do see the following streaming from the browser developer tools:
var j27=Wt4_11_3.$('o69');
var j28=document.createElement('div');
j27.parentNode.replaceChild(j28,j27);
j28.setAttribute('id', 'o69');
j28.style.display='none';
Wt4_11_3.setHtml(j28,'<textarea id="o6a" cols="20" name="o6a" rows="5" class="form-control" style="display:block;"></textarea>');
Wt4_11_3.$('o6a').editorState=codemirror.createEditorState('function foo() {console.log(123);}');;
Wt4_11_3.$('o6a').eidtorView=codemirror.createEditorView(Wt4_11_3.$('o6a').editorState, document.getElementById('o6a'));;
setTimeout(function() {var o = Wt4_11_3.$('o6a');if (o) {if (!o.classList.contains('disabled')) {try { o.focus();} catch (e) {}}}}, 10);
And later in that stream:
Wt4_11_3.$('o6a').onkeydown = function() {var e=event||window.event,o=this;if(e.keyCode && (e.keyCode == 13 && !e.shiftKey)){var g=this.onchange;this.onchange=function(){this.onchange=g;};Wt._p_.update(o,'sc6',e,true);};};
Wt4_11_3.$('o6a').oninput = function() {var e=event||window.event,o=this;Wt._p_.update(o,'sc5',e,true);;};
The "Enter" and "Shift Enter" capture events above trigger the appropriate signal when I type something in the editor and hit enter. But CodeMirror itself does not seem connected to Wt.
<textarea id="o6a" cols="20" name="o6a" rows="5" class="form-control" style="display:block;">
<div class="cm-editor ͼ1 ͼ2 ͼ4">
<div class="cm-announced" aria-live="polite"></div>
<div tabindex="-1" class="cm-scroller">
<div class="cm-gutters" aria-hidden="true" style="min-height: 14px; position: sticky;">
<div class="cm-gutter cm-lineNumbers">
<div class="cm-gutterElement" style="height: 0px; visibility: hidden; pointer-events: none;">9</div>
<div class="cm-gutterElement cm-activeLineGutter" style="height: 14px;">1</div>
</div>
<div class="cm-gutter cm-foldGutter">
<div class="cm-gutterElement" style="height: 0px; visibility: hidden; pointer-events: none;">
<span title="Unfold line">›</span>
</div>
<div class="cm-gutterElement cm-activeLineGutter" style="height: 14px;"></div>
</div>
</div>
<div spellcheck="false" autocorrect="off" autocapitalize="off" writingsuggestions="false" translate="no" contenteditable="true" class="cm-content" role="textbox" aria-multiline="true" data-language="lua" style="tab-size: 4;">
<div class="cm-activeLine cm-line">// Programmatically addfunction entry0 (o)</div>
</div>
<div class="cm-layer cm-layer-above cm-cursorLayer" aria-hidden="true" style="z-index: 150; animation-duration: 1200ms;"></div>
<div class="cm-layer cm-selectionLayer" aria-hidden="true" style="z-index: -2;"></div>
</div>
</div>
</textarea>
Here's the code that works fine in straight HTML.
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Full Featured CodeMirror with Lua</title>
<link rel="icon" href="data:;base64,=">
<style>
/* Set editor dimensions */
#editor {
height: 500px;
width: 50%;
}
/* Stretch editor to fit inside its containing div */
.cm-editor {
height: 100%;
width: 100%;
}
</style>
</head>
<body>
<!-- Editor goes in here -->
<div id="editor"></div>
<!-- CodeMirror 6 -->
<script src="codemirror.bundlelua.js"></script>
<script>
// Create an initial state for the view
const initialState = codemirror.createEditorState("function foo() {\n console.log(123);\n}");
const view = codemirror.createEditorView(initialState, document.getElementById("editor"));
// Programmatically change editor contents
console.log("Before:\n", view.state.doc.toString())
view.dispatch({ changes: { from: 0, insert: "// Programmatically add a comment to the first line\n" } })
console.log("After:\n", view.state.doc.toString())
</script>
</body>
</html>
What am I missing? I know I'm missing something. I have a feeling it's in my updateDOM method that I initially "borrowed" from WTextEdit, however, WGoogleMap does not implement any of the DOM methods. (Though Matthias DOES suggest a couple of posts before this that it is what I need to do.
void wt_codemirror::updateDom(Wt::DomElement& element, bool all)
{
WTextArea::updateDom(element, all);
// we are creating the actual element
// if (all && element.type() == Wt::DomElementType::TEXTAREA) {
// std::stringstream config;
// config <<
// "{ init_instance_callback: obj.init"
// "}";
//
Wt::DomElement dummy(Wt::DomElement::Mode::Update, Wt::DomElementType::TABLE);
//
// // element.callJavaScript("(function() { "
// // """var obj = " + jsRef() + ".wtObj;"
// // """obj.render(" + config.str() + ","
// // + jsStringLiteral(dummy.cssStyle()) + ","
// // + (changed().isConnected() ? "true" : "false")
// // + ");"
// // "})();");
//
// contentChanged_ = false;
// }
//
// if (!all && contentChanged_) {
// element.callJavaScript(jsRef() + ".ed.load();");
// contentChanged_ = false;
// }
}
I feel like the answer is staring me in the face, but I'm missing some fundamental piece of knowledge to connect these last dots.
Any hints would be GREATLY appreciated.
RE: Good in-line code editor similar to TinyMCE? - Added by Matthias Van Ceulebroeck about 1 month ago
Hi Mark,
I believe you are nearly there. From the generated Wt JS code, it seems that the doJavaScript
is not executed? So the editorView.dispatch
does not take place?
I never see the "function entry0 (o)" line appear in the WTextArea, or the comment "//Programmatically add" in the first line.
I do see:
Wt4_11_3.$('o6a').editorState=codemirror.createEditorState('function foo() {console.log(123);}');;
That should come from a setJavaScriptMember
, so perhaps a missing in between compile?
I do not know CodeMirror's API, but from your code it seems all state is manipulated through viewElement.dispatch(JSON)
.
As I said, most of the logic here seems to be in place to work correctly. Only that element is missing.
Best,
Matthias
RE: Good in-line code editor similar to TinyMCE? - Added by Mark Travis about 1 month ago
I'm about to start back on this particular object. I'll report back success or obstacles.
RE: Good in-line code editor similar to TinyMCE? - Added by Mark Travis 28 days ago
I couldn't get the editor to show up, even though I could see it in the HTML and DOM tree and it was processing.
On a whim, I dragged the "div" from the nested position of the textarea to just above and it magically appeared.
So, it looks like those two "div"s need to be one "div", not nested "div"s.
(See attached screencast to see what I mean by dragging the div outside of the nested div.)
RE: Good in-line code editor similar to TinyMCE? - Added by Mark Travis 26 days ago
There is one piece of missing information here.
I am trying to write this class as part of the application I'm working on, and not from within Wt.
However, I'm starting to think with all of the "Internal Use Only" apis I've run into that perhaps this can ONLY be done from within Wt.
I can't seem to get CodeMirror to show up in the inherited WTextArea, even though it is clearly in the DOM and is getting updates. I only see it if I pull it out of the WTextArea created "Div" as it's own element.
WGoogleMaps uses a WCompositeWidget with the implementation going to a WContainerWidget and that seems to avoid the need for a local js implementation file. If I did that, however, I'd loose the WTextArea and WFormWidget capabilities.
RE: Good in-line code editor similar to TinyMCE? - Added by Matthias Van Ceulebroeck 26 days ago
Quick sanity check, could it be the form-control
class interfering with CodeMirror, since it seems to only be a visual bug now?
RE: Good in-line code editor similar to TinyMCE? - Added by Mark Travis 26 days ago
Let me check. If it is, would I need to inherit from WCompositeWidget and re-implement any of the WFormWidget and WTextArea methods necessary?
RE: Good in-line code editor similar to TinyMCE? - Added by Matthias Van Ceulebroeck 26 days ago
No, if that is the culprit, I think you should easily get around it by calling removeStyleClass("form-control")
on the widget, you can do this in its updateDom
. Doing this in the ctor is probably too soon, I suspect (since the apply of the Bootstrap theme should have happened upon HTML element generation, namely rendering out the view).
RE: Good in-line code editor similar to TinyMCE? - Added by Mark Travis 26 days ago
I tried a quick test by just deleting "form-control" directly from the textarea class= browser HTML. That doesn't seem to do it.
Here's what it looks like with form-control deleted AND promoting cm-editor to a higher div level.
I'm using a WStackedWidget to swap between tiny and codemirror, and I notice on tiny that the textarea div is closed before tiny is placed as a child div with a "role=application" tag. However, the codemirror section opens the textarea tag that fully encloses the cm-editor child before closing the textarea tag.
Screenshot from 2025-04-07 08-58-00.png (5.83 KB) Screenshot from 2025-04-07 08-58-00.png | form-control styleclass deleted | ||
Screenshot from 2025-04-07 08-57-40.png (11.7 KB) Screenshot from 2025-04-07 08-57-40.png | cm-editor placed at textarea hierarchical level | ||
Screenshot from 2025-04-07 08-55-55.png (48.1 KB) Screenshot from 2025-04-07 08-55-55.png | tiny with application role | ||
Screenshot from 2025-04-07 08-57-16.png (51.4 KB) Screenshot from 2025-04-07 08-57-16.png | cm-editor html at higher level | ||
Screenshot from 2025-04-07 09-15-31.png (5.05 KB) Screenshot from 2025-04-07 09-15-31.png | No code completion or highlighting from CodeMirror |
RE: Good in-line code editor similar to TinyMCE? - Added by Matthias Van Ceulebroeck 26 days ago
I quickly went over the CodeMirror example. I believe the issue is the wrapping textarea
. The CodeMirror framework doesn't seem to like that.
This should apparently be rendered as a simple div
.
If you want to keep FormWidget
functionality, I think this can be done by:
void wt_codemirror::updateDom(Wt::DomElement& element, bool all)
{
// Ensure the element is rendered like a <div>, not a <textarea>
element.setType(DomElementType::DIV);
WTextArea::updateDom(element, all);
}
Does that resolve the issue?
RE: Good in-line code editor similar to TinyMCE? - Added by Mark Travis 26 days ago
WooHoo! Thank you, Matthias! That worked. I've got to adjust some css to get it to lay out properly, but it looks like I'm in business.
I've only got one thing left in my updateDom method, which is:
Wt::DomElement dummy(Wt::DomElement::Mode::Update, Wt::DomElementType::TABLE);
I know these are internal use only methods so I don't want to delete something that is still useful for the WTextArea or WFormWidget callbacks. Leave it in? Or take it out.
Again, thank you! I've spend more than a handfull of days trying to make this work. I'd like to learn more about how all of this works once I finish this deliverable.
I found a great website during this process: https://javascript.info I hope I can connect the dots and really understand how Wt builds the actual javascript elements.
RE: Good in-line code editor similar to TinyMCE? - Added by Matthias Van Ceulebroeck 26 days ago
Alright, good! I'm happy to hear it's working now.
The dummy
is essentially just for the element.callJavaScript
below it, to ensure that a correct height is set for the widget, based on how Wt would render a <table>
in that spot.
Since the next lines are commented out in the code you provided, it serves no purpose and can be removed.
RE: Good in-line code editor similar to TinyMCE? - Added by Mark Travis 26 days ago
I spoke too fast.
Seems that WTextArea needs the "textarea" to trade content back and forth. The ->text() method pulls back empty().
I'll look over the javascript methods to see if there is a way to get around that.
RE: Good in-line code editor similar to TinyMCE? - Added by Mark Travis 26 days ago
Just noticed the log has this line now ---> wt says (unhandled type)
RE: Good in-line code editor similar to TinyMCE? - Added by Mark Travis 25 days ago
Regarding the prior message, I realized that's an internal log message. The actual return code from Wt was a 200 and 47.
In perusing the CodeMirror forums, I just ran across this post: https://discuss.codemirror.net/t/aria-label-from-our-textarea-not-propagating-to-cm-textarea/4344
where he mentions in CM6, there is no CodeMirror-from-textarea function.
Then I ran across another thread on the CodeMirror forums from Paul Norman: https://discuss.codemirror.net/t/codemirror-6-quickstart-and-learn-by-examples/5375/17
He's got some great examples of how to work with the new npx/npm/rollup concepts with CodeMirror 6, and they all use "textarea".
https://github.com/paul-norman/codemirror6-prebuilt
Particularly this wonderful little Base class: https://github.com/paul-norman/codemirror6-prebuilt/blob/main/src/_base.js
So, I got rid of the element.setType(DomElementType::DIV); from the previous post so that the "textarea" was back in place.
I created a new rollup of CodeMirror based on Paul's examples, did a require() on that library, and now I'm down to the following single line of code that makes it work:
this->doJavaScript(codemirror.load().textarea(document.getElementById('" + id() + "'));");
I still have some housekeeping javascript to write, particularly setText() method that doesn't work using the WTextArea method because there is no dispatch method on the other side.
Thanks Mattias for the back and forth on this. I'm not sure how much longer it would have taken for me to stumble upon the solution!