How to get and replace text with HTML inside double curly braces


#1

I want to create an input where users can write plain text only, but where I can get part of that text and replace it with some HTML.

So, for example, I want to convert a text like Lorem Ipsum {{dolor}} to Lorem Ipsum <some html with data from JS object>

I have an object with some data.

var data = {
	dolor: {
		icon: 'https://placehold.it/16x16/',
		text: 'Dolor text'
	}
}

I want to use that to get the custom text and image, so
{{dolor}} should convert to something like

<span class="image">
	<img src="https://placehold.it/16x16/">
	Dolor text
</span>

enter image description here

My approach was to use CodeMirror, define a new mode and detect the curly braces, but now I don’t know how to get the content between those braces and I don’t know how to modify the outputted HTML (as CodeMirror only adds a cm-NameOfMode CSS class to the text).

This is what I have

http://jsfiddle.net/qmtxL6dg/

var data = {
	dolor: {
		icon: 'https://placehold.it/16x16/',
		text: 'Dolor text'
	}
}

CodeMirror.defineMode( 'uoVariable', function( config, parserConfig ){
	var uoVariable = {
		token: function( stream, state ){
			var character;

			// console.log( state.inString );

			if ( stream.match( '{{' ) ){
				while ( ( character = stream.next() ) != null ){
					if ( character == '}' && stream.next() == '}' ){
						stream.eat( '}' );
						return 'uoVariable';
					}
				}
			}

			while ( stream.next() != null && ! stream.match( '{{', false ) ){}

			return null;
		}
	};

	return CodeMirror.overlayMode( CodeMirror.getMode( config, parserConfig.backdrop ), uoVariable );
});

$(document).ready(function(){

	// Do for each one
	let form__variable_cd_mirror = document.getElementById( 'form-variable__cd-text' ),
		form__variable_cd		 = document.getElementById( 'form-variable__cd' );

	let editor = CodeMirror( form__variable_cd, {
		mode: 'uoVariable',
		value: form__variable_cd_mirror.innerText,
		lineWrapping: true,
		scrollbarStyle: null,
		extraKeys: {
			Tab: function( cm ){
				return false;
			},
		},
	}).on( 'beforeChange', function( cm, changeObj ){
		// Typed new line
		var typedNewLine = changeObj.origin == '+input' && typeof changeObj.text == 'object' && changeObj.text.join('') == '';
		
		if ( typedNewLine ){
			return changeObj.cancel();
		}

		// Pasted new line
		var pastedNewLine = changeObj.origin == 'paste' && typeof changeObj.text == 'object' && changeObj.text.length > 1;
		
		if ( pastedNewLine ) {
			var newText = changeObj.text.join(' ');
			return changeObj.update( null, null, [ newText ] );
		}

		return null;
	});
});
body {
	font-size: 15px;
	font-family: -apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
}

/* New CSS */

#form-variable__cd .CodeMirror {
	padding: 8px 12px;
	transition: all 150ms ease-in-out;
	border-radius: 3px;
	border: 1px solid #cdcdcd;
	box-shadow: 0 2px 5px 0 rgba(0,0,0,.1);
	outline: none;

	height: auto;
	font-size: 15px;
	font-family: inherit !important;
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;

	margin-top: 10px;
}

#form-variable__cd .cm-uoVariable {
	background: #4f4f4f;
	color: #fff;
	padding: 2px 5px;
	border-radius: 3px;

	white-space: nowrap;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.38.0/codemirror.css" rel="stylesheet"/>

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.38.0/codemirror.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.38.0/addon/mode/overlay.js"></script>

<div id="form-variable__cd-text" style="display: none">Lorem ipsum {{dolor}}</div>
<div id="form-variable__cd"></div>

So… any ideas about how can I archive this?

Thanks!


#2

A mode can indeed only add classes. To replace text with an arbitrary DOM node you need to write an addon that scans the document for the patterns you are looking for (either re-scanning it on every change or lazily updating only the changed region based on change events) and calls markText to insert the widgets.