Add a simple raw HTML message renderer

This commit is contained in:
Brenden Tuck 2022-08-13 21:11:46 -04:00
parent a1427fdb84
commit 922e75ebb0
3 changed files with 413 additions and 0 deletions

View file

@ -0,0 +1,63 @@
/*
* Evennia server-side-generated "raw" HTML message handler plugin
*
* PLUGIN ORDER PREREQS:
* loaded after:
* webclient_gui.js
* option2.js
* loaded before:
*
*
* To use, at minimum, in evennia python code:
* target.msg( html="<div><span>...etc...</span></div>" )
*
* or, if you prefer tagged routing (RECOMMENDED):
* target.msg( html=("<div><span>...etc...</span></div>",{'type':'tag'}) )
*
*/
let html_plugin = (function () {
//
var html = function (args, kwargs) {
let options = window.options;
if( !("html" in options) || options["html"] === false ) { return; }
var mwins = window.plugins["goldenlayout"].routeMessage(args, kwargs);
mwins.forEach( function (mwin) {
mwin.append(args[0]);
mwin.scrollTop(mwin[0].scrollHeight);
});
}
//
var onOptionsUI = function (parentdiv) {
let options = window.options;
var checked;
checked = options["html"] ? "checked='checked'" : "";
var mmHtml = $( [ "<label>",
"<input type='checkbox' data-setting='html' " + checked + "'>",
" Prefer server-side generated direct-HTML messages over old-school ASCII text",
"</label>"
].join("") );
mmHtml.on("change", window.plugins["options2"].onOptionCheckboxChanged);
parentdiv.append(mmHtml);
}
//
// Mandatory plugin init function
var init = function () {
let options = window.options;
options["html"] = true;
let Evennia = window.Evennia;
Evennia.emitter.on("html", html); // capture "image" commands
console.log('HTML plugin initialized');
}
return {
init: init,
onOptionsUI: onOptionsUI,
}
})();
plugin_handler.add("html_plugin", html_plugin);

View file

@ -0,0 +1,348 @@
/*
*
* Evennia Webclient text2html component
*
* This is used in conjunction with the main evennia.js library, which
* handles all the communication with the Server.
*
*/
//
// Global Parser for text2html
//
var text2html_plugin = (function () {
"use strict"
let asciiESC = String.fromCharCode(27); // string literal for ASCII ESC bytes
let foreground = "color-102"; // state tracker for foreground css
let background = ""; // state tracker for background css
let underline = ""; // state tracker for underlines css
let pipecodes = /^(\|\[?[0-5][0-5][0-5])|(\|\[[0-9][0-9]?m)|(\|\[?=[a-z])|(\|[![]?[unrgybmcwxhRGYBMCWXH_/>*^-])/;
// example ^|000 or ^|[22m or ^|=a or ^|_
let csslookup = {
"|n": "normal",
"|r": "color-009",
"|g": "color-010",
"|y": "color-011",
"|b": "color-012",
"|m": "color-013",
"|c": "color-014",
"|w": "color-015",
"|x": "color-008",
"|R": "color-001",
"|G": "color-002",
"|Y": "color-003",
"|B": "color-004",
"|M": "color-005",
"|C": "color-006",
"|W": "color-007",
"|X": "color-000",
"|[r": "bgcolor-196",
"|[g": "bgcolor-046",
"|[y": "bgcolor-226",
"|[b": "bgcolor-021",
"|[m": "bgcolor-201",
"|[c": "bgcolor-051",
"|[w": "bgcolor-231",
"|[x": "bgcolor-102",
"|[R": "bgcolor-001",
"|[G": "bgcolor-002",
"|[Y": "bgcolor-003",
"|[B": "bgcolor-004",
"|[M": "bgcolor-005",
"|[C": "bgcolor-006",
"|[W": "bgcolor-007",
"|[X": "bgcolor-000",
"|=a": "color-016",
"|=b": "color-232",
"|=c": "color-233",
"|=d": "color-234",
"|=e": "color-235",
"|=f": "color-236",
"|=g": "color-237",
"|=h": "color-238",
"|=i": "color-239",
"|=j": "color-240",
"|=k": "color-241",
"|=l": "color-242",
"|=m": "color-243",
"|=n": "color-244",
"|=o": "color-245",
"|=p": "color-246",
"|=q": "color-247",
"|=r": "color-248",
"|=s": "color-249",
"|=t": "color-250",
"|=u": "color-251",
"|=v": "color-252",
"|=w": "color-253",
"|=x": "color-254",
"|=y": "color-255",
"|=z": "color-231",
"|[=a": "bgcolor-016",
"|[=b": "bgcolor-232",
"|[=c": "bgcolor-233",
"|[=d": "bgcolor-234",
"|[=e": "bgcolor-235",
"|[=f": "bgcolor-236",
"|[=g": "bgcolor-237",
"|[=h": "bgcolor-238",
"|[=i": "bgcolor-239",
"|[=j": "bgcolor-240",
"|[=k": "bgcolor-241",
"|[=l": "bgcolor-242",
"|[=m": "bgcolor-243",
"|[=n": "bgcolor-244",
"|[=o": "bgcolor-245",
"|[=p": "bgcolor-246",
"|[=q": "bgcolor-247",
"|[=r": "bgcolor-248",
"|[=s": "bgcolor-249",
"|[=t": "bgcolor-250",
"|[=u": "bgcolor-251",
"|[=v": "bgcolor-252",
"|[=w": "bgcolor-253",
"|[=x": "bgcolor-254",
"|[=y": "bgcolor-255",
"|[=z": "bgcolor-231",
// not sure what these nexts ones are actually supposed to map to
"|[0m": "normal",
"|[1m": "normal",
"|[22m": "normal",
"|[36m": "color-006",
"|[37m": "color-015",
}
function ascii (l) {
let a = new String(l); // force string
return a.charCodeAt(0);
}
// dumb convert any leading or trailing spaces/tabs to &nbsp; sequences
var handleSpaces = function (text) {
// TODO should probably get smart about replacing spaces inside "normal" text
return text.replace( /\t/g, "&nbsp;&nbsp;&nbsp;&nbsp;").replace( / /g, "&nbsp;");
}
// javascript doesn't have a native sprintf-like function
function zfill(string, size) {
while (string.length < size) string = "0" + string;
return string;
}
// given string starting with a pipecode |xx
// return the css (if any) and text remainder
var pipe2css = function(pipecode) {
let regx = "";
let css = "color-102";
regx = /^(\|\[?[nrgybmcwxhRGYBMCWX])/;
css = pipecode.match( regx );
if( css != null ) {
return csslookup[ css[1] ];
}
regx = /^(\|\[?=[a-z])/;
css = pipecode.match( regx );
if( css != null ) {
return csslookup[ css[1] ];
}
regx = /^(\|\[[0-9][0-9]?m)/;
css = pipecode.match( regx );
if( css != null ) {
return csslookup[ css[1] ];
}
regx = /^(\|n)/;
css = pipecode.match( regx );
if( css != null ) {
return "normal";
}
regx = /^(\|u)/;
css = pipecode.match( regx );
if( css != null ) {
return "underline";
}
regx = /^\|([0-5][0-5][0-5])/;
css = pipecode.match( regx );
if( css != null ) {
return "color-" + zfill( (parseInt(css[1], 6) + 16).toString(), 3);
}
regx = /^\|\[([0-5][0-5][0-5])/;
css = pipecode.match( regx );
if( css != null ) {
return "bgcolor-" + zfill( (parseInt(css[1], 6) + 16).toString(), 3);
}
return css;
}
// convert any HTML sensitive characters to &code; format
var htmlEscape = function (text) {
text = text.replace(/&/g, "&amp;");
text = text.replace(/</g, "&lt;");
text = text.replace(/>/g, "&gt;");
text = text.replace(/"/g, "&quot;");
text = text.replace(/'/g, "&apos;");
text = handleSpaces(text);
return text;
}
// track stateful CSS
var trackCSS = function (css) {
if( (typeof css !== 'string') && ! (css instanceof String) ) {
css = "";
}
if( css.startsWith( "color-" ) ) {
foreground = css;
}
if( css.startsWith( "bgcolor-" ) ) {
background = css;
}
if( css === "underline" ) {
underline = css;
}
if( css === "normal" ) {
foreground = "color-102";
background = "";
underline = "";
}
return foreground + " " + background + " " + underline ;
}
/*
*
*/
var parse2HTML = function (text) {
let html = "";
foreground = "color-102"; // state tracker for foreground css
background = ""; // state tracker for background css
underline = ""; // state tracker for underlines css
// HACK: parse TELNET ASCII byte-by-byte, convert ESC's to |'s -- serverside "raw" bug?
// Bug is further proven out by the fact that |'s don't come through as doubles.
let hack = new RegExp( String.fromCharCode(27) );
if( text.match( hack ) ) {
let chars = text.split(''); // to characters
for( let n=0; n<chars.length; n++ ) {
if( chars[n] === '|' ) {
console.log( 'Got Pipe' );
chars[n] = "&#124;";
}
if( ascii(chars[n]) === 27 ) {
chars[n] = '|';
}
}
text = chars.join(''); // from character strings
}
let strings = text.split( /(\n|\|[\/])/ ); // newline or pipe-slash
for( let x=0; x<strings.length; x++ ) {
// hack double pipes -- convert them temporarily to HTML -- &#124;
var string = strings[x].replace( /\|\|/g, "&#124;" );
// special case for blank lines, avoid <div>'s with no HTML height
if( string === "" ) {
html += "<div>&nbsp;</div>";
continue;
}
html += "<div>";
let spans = string.split( "|" );
// this split means there are 2 possible cases
// possibly-0-length leading span with "default" styling (may be the ONLY span)
let span = spans[0].replaceAll("&#124;", "|"); // avoid htmlEscape mangling literal |'s
if( span.length > 0 ) {
html += "<span class='color-102'>" + htmlEscape(span) + "</span>";
}
// "standard" span array of [xxxtext1, xxtext2, xxtext3], where x's are pipe-codes
for( let n=1; n<spans.length; n++ ) {
span = "|" + spans[n].replaceAll("&#124;", "|"); // avoid htmlEscape mangling |'s
let pipecode = "";
let remainder = span;
let tags = span.match( pipecodes ); // match against large pipecode regex
if( tags != null ) {
pipecode = tags[0]; // matched text is the pipecode
remainder = span.replace( pipecode, "" ); // everything but the pipecode
}
let css = trackCSS( pipe2css( pipecode ) ); // find css associated with pipe-code
html += "<span class='" + css + "'>" + htmlEscape(remainder) + "</span>";
}
html += "</div>";
}
return html;
}
// The Main text2html message capture fuction -- calls parse2HTML()
var text2html = function (args, kwargs) {
let options = window.options;
if( !("text2html" in options) || options["text2html"] === false ) { return; }
var mwins = window.plugins["goldenlayout"].routeMessage(args, kwargs);
mwins.forEach( function (mwin) {
mwin.append( parse2HTML( args[0]) ); // the heavy workload
mwin.scrollTop(mwin[0].scrollHeight);
});
}
//
var onOptionsUI = function (parentdiv) {
let options = window.options;
var checked;
checked = options["text2html"] ? "checked='checked'" : "";
var text2html = $( [ "<label>",
"<input type='checkbox' data-setting='text2html' " + checked + "'>",
" Enable client-side Evennia ASCII message rendering",
"</label>"
].join("") );
text2html.on("change", window.plugins["options2"].onOptionCheckboxChanged);
parentdiv.append(text2html);
}
//
// Mandatory plugin init function
var init = function () {
let options = window.options;
options["text2html"] = true;
let Evennia = window.Evennia;
Evennia.emitter.on("text2html", text2html); // capture "text2html" outfunc events
console.log('Text2Html plugin initialized');
}
return {
init: init,
onOptionsUI: onOptionsUI,
parse2HTML: parse2HTML,
}
})();
plugin_handler.add("text2html_plugin", text2html_plugin);

View file

@ -100,6 +100,8 @@ JQuery available.
<script src={% static "webclient/js/plugins/default_in.js" %} language="javascript" type="text/javascript"></script>
<script src={% static "webclient/js/plugins/default_out.js" %} language="javascript" type="text/javascript"></script>
<script src={% static "webclient/js/plugins/multimedia.js" %} language="javascript" type="text/javascript"></script>
<script src={% static "webclient/js/plugins/html.js" %} language="javascript" type="text/javascript"></script>
<script src={% static "webclient/js/plugins/text2html.js" %} language="javascript" type="text/javascript"></script>
{% endblock %}
<script src="https://cdn.rawgit.com/ejci/favico.js/master/favico-0.3.10.min.js" language="javascript" type="text/javascript" charset="utf-8"></script>