Skip to content

Instantly share code, notes, and snippets.

@zushenyan
Last active December 25, 2015 16:29
Show Gist options
  • Save zushenyan/7005761 to your computer and use it in GitHub Desktop.
Save zushenyan/7005761 to your computer and use it in GitHub Desktop.
SRSHME stands for Simple Realtime Syntax Highlight Markdown Editor which is a showcase of how realtime syntax highlight is possible in Html/Css/JavaScript.
<!--
SRSHME stands for Simple Realtime Syntax Highlight Markdown Editor
which is a showcase of how realtime syntax highlight is possible in Html/Css/JavaScript.
Have tested on the latest version of Chrome, Firefox, Opera, Safari and IE 10.
Be aware of this code is not optimized. When you typed too many characters,
it will become more and more laggy because I choose to do it in a straight way,
and since it's just a showcase & tutorial, I will leave it as current state if there is no more critical bugs.
-->
<!DOCTYPE html>
<html>
<head>
<title>Simple Realtime Syntax Highlight Markdown Editor</title>
<style type="text/css">
* {
padding: 0;
margin: 0;
}
#editor {
position: absolute;
border: 1px solid green;
left: 50px;
top: 50px;
min-width: 200px;
min-height: 200px;
}
#measure {
position: absolute;
font: 16px/16px monospace;
display: inline-block;
color: transparent;
background: transparent;
white-space: pre;
top: 0;
left: -1000px;
}
#view, #input {
position: absolute;
width: 100%;
height: 100%;
font: 16px/16px monospace;
outline: none;
resize: none;
left: 0;
top: 0;
overflow: auto;
word-wrap: normal;
border: none;
}
#view {
z-index: 0;
}
#lines {
position: absolute;
white-space: pre;
top: 0;
left: 0;
z-index: 1;
}
#caret {
position: absolute;
font: 16px/16px monospace;
width: 1px;
height: 18px;
background: #000;
left: 0;
top: 0;
z-index: 2;
}
#input {
color: transparent;
background: transparent;
white-space: pre;
z-index: 100;
}
.line {
}
</style>
</head>
<body>
<div id="editor">
<div id="measure"></div>
<div id="view">
<div id="lines"></div>
<div id="caret"></div>
</div>
<textarea id="input" wrap="off" spellcheck="false" autocorrect="off" autocapitalize="off"></textarea>
</div>
</body>
<script>
var _input = document.getElementById("input");
var _view = document.getElementById("view");
var _lines = document.getElementById("lines");
var _caret = document.getElementById("caret");
var _measure = document.getElementById("measure");
var TAB_SIZE = 4;
var LINE_HEIGHT = 16;
_input.onkeydown = function(e){
if(e.keyCode === 9){
e.preventDefault();
var indentedStart = _input.selectionStart + TAB_SIZE;
_input.value = createLineDoc(_input.selectionStart, _input.selectionEnd, TAB_SIZE);
_input.selectionStart = _input.selectionEnd = indentedStart;
}
update();
};
_input.onkeyup = _input.onkeypress = _input.onclick = update;
_input.onscroll = updateScroll;
/*
Detect if it is IE, if so, hide our mimic caret, because textarea's caret in IE is not controllable with css.
*/
var agent = window.navigator.userAgent;
if(agent.indexOf("MSIE") > -1){
_caret.style.opacity = 0;
}
function update(){
generateDisplay();
projectCaretToHTML();
updateScroll();
}
function updateScroll(){
_view.scrollLeft = _input.scrollLeft;
_view.scrollTop = _input.scrollTop;
}
function generateDisplay(){
var rows = _input.value.split("\n");
_lines.innerHTML = "";
for(var i = 0; i < rows.length; i++){
var line = rows[i];
line = parseLinkSyntax(line, "green", "red");
line = parseListSyntax(line, "blue");
line = parseQuoteSyntax(line, "orange");
line = parseHeaderSyntax(line, "gold");
line = parseBottomlineHeaderSyntax(line, "cyan");
var newLine = line !== "" ? createLineHTML(line) : createLineHTML(" ");
_lines.appendChild(newLine);
}
}
function createLineDoc(start, end, tabSize){
return _input.value.substr(0, start) + createTab(tabSize) + _input.value.substr(end);
}
function createLineHTML(content){
var line = document.createElement("div");
line.class = "line";
line.innerHTML = content;
return line;
}
function createSpanString(content, color){
var span = document.createElement("span");
span.innerHTML = content;
span.style.color = color;
return span.outerHTML;
}
function createTab(tabSize){
return new Array(tabSize + 1).join(" ");
}
function getCaretInfo(){
var start = _input.selectionStart;
var end = _input.selectionEnd;
var rows = _input.value.substr(0, start).split("\n");
var remainRows = _input.value.substr(end).split("\n");
var row = rows.length - 1;
var col = rows[row].length;
var rowContent = rows[row] + remainRows[0];
var rowContentBefore = rows[row];
var rowContentAfter = remainRows[0];
return {
row: row,
col: col,
rowContent: rowContent,
rowContentBefore: rowContentBefore,
rowContentAfter: rowContentAfter
};
}
function measureSize(content){
_measure.innerHTML = "";
var line = createLineHTML(content);
_measure.appendChild(line);
var width = 0;
var height = LINE_HEIGHT;
if(content){
width = _measure.offsetWidth;
}
return {
width: width,
height: height
};
}
function projectCaretToHTML(){
var caretInfo = getCaretInfo();
var size = measureSize(caretInfo.rowContentBefore);
var height = size.height * caretInfo.row;
var width = size.width - _caret.getClientRects()[0].width;
width = width >= 0 ? width : 0;
_caret.style.top = height + "px";
_caret.style.left = width + "px";
}
/*
================================
==== parser functions ====
================================
*/
function singleLineSyntax(regexp, string, color){
var reg = regexp;
var result = reg.exec(string);
return result ? createSpanString(string, color) : string;
}
function linkSyntax(regexp, string, linkColor, urlColor){
var reg = regexp;
var newString = string;
var result;
while(result = reg.exec(string)){
var matchedString = result[0];
var linkSpan = createSpanString(result[1], linkColor);
var urlSpan = createSpanString(result[2], urlColor);
matchedString = matchedString.replace(result[1], linkSpan);
matchedString = matchedString.replace(result[2], urlSpan);
newString = newString.replace(result[0], matchedString);
}
return newString;
}
var parseLinkSyntax = function(string, linkColor, urlColor){ return linkSyntax(/\[((?:(?!\[|\]).)*)\]\((.*?)\)/g, string, linkColor, urlColor); };
var parseListSyntax = function(string, color){ return singleLineSyntax(/^(\s*)(((\d+\.)|\+|\*|-){1})(\s+)(.*)/g, string, color); };
var parseHeaderSyntax = function(string, color){ return singleLineSyntax(/^(\s*)(#+)(.*)/g, string, color); };
var parseBottomlineHeaderSyntax = function(string, color){ return singleLineSyntax(/^(\s*)((=|-)+)(?!(.))/g, string, color); };
var parseQuoteSyntax = function(string, color){ return singleLineSyntax(/^(\s*)(>+)(.*)/g, string, color); };
</script>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment