I was thinking the same thing. But instead of writing some plugin, just present the monaco editor via a flow so that you can modify your templates wherever you can access your node-red editor...
[{"id":"4b90dbb857ce9468","type":"http in","z":"49f61d916c8f6022","name":"","url":"edit/:template","method":"get","upload":false,"swaggerDoc":"","x":1330,"y":680,"wires":[["25d0ef894bf7b598"]]},{"id":"14516dadee084355","type":"http response","z":"49f61d916c8f6022","name":"","statusCode":"","headers":{},"x":1970,"y":680,"wires":[]},{"id":"5ca67eaaa5619624","type":"template","z":"49f61d916c8f6022","name":"dynamic template editor","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"<html>\n <head>\n <script src=\"/vendor/vendor.js\"></script>\n <script src=\"/vendor/monaco/dist/editor.js\"></script>\n </head>\n <style>\n .parent {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n grid-template-rows: 1fr;\n grid-column-gap: 10px;\n grid-row-gap: 0px;\n }\n\n .div1 { grid-area: 1 / 1 / 2 / 2; }\n .div2 { grid-area: 1 / 2 / 2 / 3; }\n .div3 { grid-area: 1 / 3 / 2 / 4; }\n </style>\n</html>\n\n<div id=\"toolbar\" style=\"width: 99%; height: 60px\">\n <div class=\"parent\">\n <div class=\"div1\"> \n Template: <span id=\"name\"></span>\n </div>\n <div class=\"div2\"> \n Syntax: <select id=\"syntax\" style=\"width:110px; font-size: 10px !important; height: 24px; padding:0;\">\n <option value=\"handlebars\" selected>mustache</option>\n <option value=\"html\">HTML</option>\n <option value=\"css\">CSS</option>\n <option value=\"javascript\">JavaScript</option>\n <option value=\"json\">JSON</option>\n <option value=\"yaml\">YAML</option>\n <option value=\"markdown\">Markdown</option>\n <option value=\"python\">Python</option>\n <option value=\"sql\">SQL</option>\n <option value=\"text\">text</option>\n </select>\n </div>\n <div class=\"div3\">\n <button id=\"update\">Store</button>\n <button id=\"reset\">Reset</button>\n </div>\n </div>\n \n \n</div>\n<hr>\n<div id=\"editor\" style=\"width: 99%; height: calc(100% - 100px)\">\n\n</div>\n\n<script>\n const data = {{{payload}}}\n\n if(data.syntax === \"mustache\") { \n data.syntax = \"handlebars\" \n }\n\n let editor = monaco.editor.create(document.getElementById('editor'), {\n\t\tvalue: data.data,\n\t\tlanguage: data.syntax,\n\t\ttheme: 'vs-dark'\n\t});\n\n $(\"#syntax\").val(data.syntax)\n $(\"#name\").text(data.name)\n\n $(\"#reset\").on(\"click\", function() {\n const syntax = $(\"#syntax\").val()\n setEditor(data.data, syntax)\n })\n\n $(\"#update\").on(\"click\", function() {\n $.ajax({\n url: data.name, \n type: \"POST\",\n dataType: \"json\",\n contentType: \"application/json; charset=utf-8\",\n data: JSON.stringify({\n name: data.name, \n data: editor.getValue(),\n syntax: $(\"#syntax\").val()\n })\n }).done(function() {\n alert( \"success\" );\n }).fail(function( jqXHR, textStatus, errorThrown) {\n alert( textStatus );\n })\n })\n\n $(\"#syntax\").on(\"change\", function() {\n const syntax = $(\"#syntax\").val()\n setEditor(editor.getValue(), syntax)\n })\n \n function setEditor(data, syntax) {\n if(syntax === \"mustache\") { syntax = \"handlebars\" }\n\t\tconst oldModel = editor.getModel();\n\t\tconst newModel = monaco.editor.createModel(data, syntax);\n\t\teditor.setModel(newModel);\n\t\tif (oldModel) {\n\t\t\toldModel.dispose();\n\t\t}\n }\n</script>","output":"str","x":1770,"y":680,"wires":[["14516dadee084355"]]},{"id":"020f63da72b51be8","type":"http in","z":"49f61d916c8f6022","name":"","url":"edit/:template","method":"post","upload":false,"swaggerDoc":"","x":1330,"y":760,"wires":[["6e3505c96687f4d9"]]},{"id":"65b950515bd2f14c","type":"inject","z":"49f61d916c8f6022","name":"","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":1310,"y":880,"wires":[["4a81961f3656b579","de8d4d7ae546b88e"]]},{"id":"4a81961f3656b579","type":"template","z":"49f61d916c8f6022","name":"template1","field":"payload","fieldType":"msg","format":"handlebars","syntax":"mustache","template":"<h2>Test Form</h2>\n\n<form>\n <label for=\"fname\">First name:</label><br>\n <input type=\"text\" id=\"fname\" name=\"fname\" value=\"John\"><br>\n <label for=\"lname\">Last name:</label><br>\n <input type=\"text\" id=\"lname\" name=\"lname\" value=\"Doe\"><br><br>\n <input type=\"submit\" value=\"Submit\">\n</form> \n\n","output":"str","x":1480,"y":880,"wires":[["60c8e2915a038e0b"]]},{"id":"de8d4d7ae546b88e","type":"template","z":"49f61d916c8f6022","name":"template2","field":"payload","fieldType":"msg","format":"html","syntax":"mustache","template":"<table>\n <tr>\n <th>Company</th>\n <th>Contact</th>\n <th>Country</th>\n </tr>\n <tr>\n <td>Alfreds Futterkiste</td>\n <td>Maria Anders</td>\n <td>Germany</td>\n </tr>\n <tr>\n <td>Centro comercial Moctezuma</td>\n <td>Francisco Chang</td>\n <td>Mexico</td>\n </tr>\n</table>","output":"str","x":1480,"y":920,"wires":[["190cad5638779ac7"]]},{"id":"6e3505c96687f4d9","type":"function","z":"49f61d916c8f6022","name":"update template","func":"const templatePath = \"dynamic_templates.\" + msg.payload.name\nconst backupPath = \"dynamic_templates_backup.\" + msg.payload.name\nconst oldTemplate = flow.get(templatePath)\nflow.set(backupPath, oldTemplate)\nflow.set(templatePath, msg.payload)\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1660,"y":760,"wires":[["74cd6e5e4ac62882"]]},{"id":"74cd6e5e4ac62882","type":"http response","z":"49f61d916c8f6022","name":"","statusCode":"","headers":{},"x":1970,"y":760,"wires":[]},{"id":"25d0ef894bf7b598","type":"function","z":"49f61d916c8f6022","name":"get template","func":"const name = \"dynamic_templates.\" + msg.req.params.template\nconst data = flow.get(name) || { name: msg.req.params.template, data: \"\", syntax: \"mustache\" }\nmsg.payload = JSON.stringify(data)\nreturn msg;\n","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":1550,"y":680,"wires":[["5ca67eaaa5619624"]]},{"id":"0e9a6ce39a13cef7","type":"change","z":"49f61d916c8f6022","name":"setup dynamic template","rules":[{"t":"set","p":"dynamic_templates[msg.name]","pt":"flow","to":"{}","tot":"json"},{"t":"set","p":"dynamic_templates[msg.name].name","pt":"flow","to":"name","tot":"msg"},{"t":"set","p":"dynamic_templates[msg.name].data","pt":"flow","to":"payload","tot":"msg"},{"t":"set","p":"dynamic_templates[msg.name].syntax","pt":"flow","to":"syntax","tot":"msg"}],"action":"","property":"","from":"","to":"","reg":false,"x":1910,"y":900,"wires":[[]]},{"id":"190cad5638779ac7","type":"change","z":"49f61d916c8f6022","name":"set name and syntax","rules":[{"t":"set","p":"name","pt":"msg","to":"template2","tot":"str"},{"t":"set","p":"syntax","pt":"msg","to":"mustache","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1660,"y":920,"wires":[["0e9a6ce39a13cef7"]]},{"id":"60c8e2915a038e0b","type":"change","z":"49f61d916c8f6022","name":"set name and syntax","rules":[{"t":"set","p":"name","pt":"msg","to":"template1","tot":"str"},{"t":"set","p":"syntax","pt":"msg","to":"mustache","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":1660,"y":880,"wires":[["0e9a6ce39a13cef7"]]},{"id":"5bc7f07ad91df8a1","type":"comment","z":"49f61d916c8f6022","name":"The editor - access via http://localhost:port/edit/template1 or http://localhost:port/edit/template2","info":"","x":1560,"y":640,"wires":[]},{"id":"58778764d56bb3ca","type":"comment","z":"49f61d916c8f6022","name":"A POST endpoint for updating the CONTEXT store with new template data","info":"","x":1500,"y":720,"wires":[]},{"id":"52ca28a6c30cb14f","type":"comment","z":"49f61d916c8f6022","name":"Init dummy data (template1 and template2)","info":"","x":1400,"y":840,"wires":[]}]
Future improvements might include...
- generating a dropdown selector of templates (instead of typing the template name into the URL box)
- add a "delete" button
- saving to file (as well as context)