Skip to content

Instantly share code, notes, and snippets.

@wagenet
Created May 13, 2010 04:13
Show Gist options
  • Save wagenet/399484 to your computer and use it in GitHub Desktop.
Save wagenet/399484 to your computer and use it in GitHub Desktop.
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN"
"http://www.w3.org/TR/html4/strict.dtd">
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Text Editor</title>
<meta name="generator" content="TextMate http://macromates.com/">
<meta name="author" content="Peter Wagenet">
<!-- Date: 2010-05-12 -->
<script type="text/javascript" charset="utf-8" src="jquery-1.4.2.js"></script>
<script type="text/javascript" charset="utf-8">
var TE = {};
TE.MODIFIER_KEYS = {
16:'shift', 17:'ctrl', 18: 'alt'
};
TE.FUNCTION_KEYS = {
8: 'backspace', 9: 'tab', 13: 'return', 19: 'pause', 27: 'escape',
33: 'pageup', 34: 'pagedown', 35: 'end', 36: 'home',
37: 'left', 38: 'up', 39: 'right', 40: 'down', 44: 'printscreen',
45: 'insert', 46: 'delete', 112: 'f1', 113: 'f2', 114: 'f3', 115: 'f4',
116: 'f5', 117: 'f7', 119: 'f8', 120: 'f9', 121: 'f10', 122: 'f11',
123: 'f12', 144: 'numlock', 145: 'scrolllock'
};
TE.Line = function(){
this.width = 0;
};
TE.TextEditor = function(id){
this.id = id;
this.lines = [new TE.Line];
this.isActive = false;
this.render();
this.watchEvents();
};
jQuery.extend(TE.TextEditor.prototype, {
$: function(){
return $('#'+this.id);
},
$cursor: function(){
return this.$().children('.cursor');
},
$body: function(){
return this.$().children('.body');
},
$tester: function(){
return this.$().children('.tester');
},
$input: function(){
return this.$().children('.input');
},
render: function(){
var editor = this.$();
$('<div class="cursor"/>').hide().appendTo(editor);
$('<div class="body"></div>').appendTo(editor);
$('<div class="tester"></div>').appendTo(editor);
$('<input type="text" class="input" />').appendTo(editor);
this._positionCursor();
},
_positionCursor: function(){
var lineHeight = 18, // FIXME: Don't hardcode
lineTotals = 0,
idx;
for(idx=0; idx < this.lines; idx++) lineTotals += this.lines[idx].width;
this.$cursor().css({ top: lineHeight * (this.lines.length - 1), left: this.lines[this.lines.length-1].width });
},
_startCursorBlink: function(){
if (this._cursorBlinkTimer) this._stopCursorBlink();
var el = this.$cursor();
this._cursorBlinkTimer = setInterval(function(){
el.is(':hidden') ? el.show() : el.hide();
}, 500);
},
_stopCursorBlink: function(){
if(this._cursorBlinkTimer) clearInterval(this._cursorBlinkTimer);
this.$cursor().hide();
},
watchEvents: function(){
var self = this;
$(document).click(function(evt){ self.clicked(evt); });
this.$input().bind('textInput', function(evt){ self.keyPressed(evt); })
.bind('keydown', function(evt){ self.keyPressed(evt); })
.bind('blur', function(evt){ self.blurred(evt); });
},
keyPressed: function(evt){
var body = this.$body(),
html = body.html(),
key;
this.$input().val('');
if (key = evt.originalEvent.data) {
var entity = $('<div/>').text(key).html();
this.addChar(entity);
} else if (TE.FUNCTION_KEYS[evt.which] == 'backspace' && html.length > 0) {
var lastEntityMatch = html.match(/&[^;]+?;$/),
lastEntity;
if (lastEntityMatch) lastEntity = lastEntityMatch[0];
else lastEntity = html[html.length-1];
this.removeChar(lastEntity);
}
},
clicked: function(evt) {
this.isActive = true;
this.$input().focus();
this._startCursorBlink();
},
blurred: function(evt) {
if (!this.isActive) return;
this.isActive = false;
this._stopCursorBlink();
},
addChar: function(chr) {
var width = this._getCharWidth(chr),
lastLine = this.lines[this.lines.length-1],
body = this.$body(),
bodyHtml = body.html(),
bodyWidth = body.width();
if (lastLine.width + width > bodyWidth) {
bodyHtml += '&shy;';
lastLine = new TE.Line
this.lines.push(lastLine);
}
lastLine.width += width;
body.html(bodyHtml+chr);
this._positionCursor();
},
removeChar: function(entity) {
var width = this._getCharWidth(entity),
lastLine = this.lines[this.lines.length-1],
body = this.$body(),
bodyHtml = body.html();
if (lastLine.width - width < 0) {
if (this.lines.length > 1) this.lines = this.lines.splice(0, this.lines.length-1);
lastLine = this.lines[this.lines.length-1];
}
lastLine.width -= width;
if (lastLine.width < 0) lastLine.width = 0
body.html(bodyHtml.slice(0,bodyHtml.length-entity.length));
this._positionCursor();
},
_getCharWidth: function(chr){
if (chr === ' ') chr = '&nbsp;';
return this.$tester().html(chr).width();
}
});
$(document).ready(function(){
TE.currentEditor = new TE.TextEditor('editor');
});
</script>
<style type="text/css" media="screen">
.editor {
position: relative;
border: solid 1px black;
overflow-x: hidden;
overflow-y: scroll;
line-height: 18px;
font-family: Helvetica, Arial;
}
.editor .cursor {
width: 2px; height: 15px; background-color: black; position: absolute;
}
.editor .body {
}
.editor .tester {
position: absolute;
left: -1000px;
}
.editor .input {
position: absolute; left: -1000px;
}
</style>
</head>
<body>
<div id="editor" class="editor" style="width: 300px; height: 200px;"></div>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment