Skip to content

Instantly share code, notes, and snippets.

@rwaldron
Forked from wagenet/js-text-editor.html
Created March 16, 2011 21:46

Revisions

  1. @wagenet wagenet revised this gist May 13, 2010. 1 changed file with 81 additions and 42 deletions.
    123 changes: 81 additions & 42 deletions js-text-editor.html
    Original file line number Diff line number Diff line change
    @@ -25,25 +25,6 @@
    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.PRINTABLE_KEYS = {
    32: ' ', 48:"0", 49:"1", 50:"2", 51:"3", 52:"4", 53:"5", 54:"6", 55:"7",
    56:"8", 57:"9", 59:";", 61:"=", 65:"a", 66:"b", 67:"c", 68:"d", 69:"e",
    70:"f", 71:"g", 72:"h", 73:"i", 74:"j", 75:"k", 76:"l", 77:"m", 78:"n",
    79:"o", 80:"p", 81:"q", 82:"r", 83:"s", 84:"t", 85:"u", 86:"v", 87:"w",
    88:"x", 89:"y", 90:"z", 107:"=", 109:"-", 110:".", 188:",", 190:".",
    191:"/", 192:"`", 219:"[", 220:"\\", 221:"]", 222:"'"
    };

    TE.PRINTABLE_SHIFT_KEYS = {
    '0': ')', '1': '!', '2': '@', '3': '#', '4': '$', '5': '%', '6': '^',
    '7': '&', '8': '*', '9': '(', 'a': 'A', 'b': 'B', 'c': 'C', 'd': 'D',
    'e': 'E', 'f': 'F', 'g': 'G', 'h': 'H', 'i': 'I', 'j': 'J', 'k': 'K',
    'l': 'L', 'm': 'M', 'n': 'N', 'o': 'O', 'p': 'P', 'q': 'Q', 'r': 'R',
    's': 'S', 't': 'T', 'u': 'U', 'v': 'V', 'w': 'W', 'x': 'X', 'y': 'Y',
    'z': 'Z', '=': '+', '-': '_', '.': '>', ',': '<', '[': '{', ']': '}',
    '\\': '|', '`': '~', '/': '?', "'": '"'
    };

    TE.Line = function(){
    @@ -53,9 +34,9 @@
    TE.TextEditor = function(id){
    this.id = id;
    this.lines = [new TE.Line];
    this.positionCursor();
    this.blinkCursor();
    this.watchKeyPress();
    this.isActive = false;
    this.render();
    this.watchEvents();
    };

    jQuery.extend(TE.TextEditor.prototype, {
    @@ -72,7 +53,26 @@
    return this.$().children('.body');
    },

    positionCursor: function(){
    $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;
    @@ -82,31 +82,36 @@
    this.$cursor().css({ top: lineHeight * (this.lines.length - 1), left: this.lines[this.lines.length-1].width });
    },

    blinkCursor: function(){
    _startCursorBlink: function(){
    if (this._cursorBlinkTimer) this._stopCursorBlink();
    var el = this.$cursor();
    setInterval(function(){
    this._cursorBlinkTimer = setInterval(function(){
    el.is(':hidden') ? el.show() : el.hide();
    }, 500);
    },

    watchKeyPress: function(){
    _stopCursorBlink: function(){
    if(this._cursorBlinkTimer) clearInterval(this._cursorBlinkTimer);
    this.$cursor().hide();
    },

    watchEvents: function(){
    var self = this;
    $(document).keydown(function(evt){ self.keyPressed(evt); });
    $(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;

    if (key = TE.PRINTABLE_KEYS[evt.which]) {
    if (evt.shiftKey) {
    var withShift = TE.PRINTABLE_SHIFT_KEYS[key];
    if (withShift) key = withShift;
    }
    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(/&[^;]+?;$/),
    @@ -117,6 +122,18 @@
    }
    },

    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],
    @@ -134,7 +151,7 @@

    body.html(bodyHtml+chr);

    this.positionCursor();
    this._positionCursor();
    },

    removeChar: function(entity) {
    @@ -153,12 +170,12 @@

    body.html(bodyHtml.slice(0,bodyHtml.length-entity.length));

    this.positionCursor();
    this._positionCursor();
    },

    _getCharWidth: function(chr){
    if (chr === ' ') chr = '&nbsp;';
    return $('#tester').html(chr).width();
    return this.$tester().html(chr).width();
    }

    });
    @@ -168,15 +185,37 @@
    });
    </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" style="width: 300px; height: 200px; border: solid 1px black; position: relative;">
    <div class="cursor" style="width: 2px; height: 15px; background-color: black; position: absolute;"></div>
    <div class="body" style="line-height: 18px x"></div>
    </div>

    <div id="tester" style="position: absolute; left: -1000px;"></div>
    <div id="editor" class="editor" style="width: 300px; height: 200px;"></div>

    </body>
    </html>
  2. @wagenet wagenet revised this gist May 13, 2010. 1 changed file with 65 additions and 18 deletions.
    83 changes: 65 additions & 18 deletions js-text-editor.html
    Original file line number Diff line number Diff line change
    @@ -14,14 +14,6 @@

    var TE = {};

    TE.TextEditor = function(id){
    this.id = id;
    this.cursorPos = { x: 0, y: 0 };
    this.positionCursor();
    this.blinkCursor();
    this.watchKeyPress();
    };

    TE.MODIFIER_KEYS = {
    16:'shift', 17:'ctrl', 18: 'alt'
    };
    @@ -54,6 +46,18 @@
    '\\': '|', '`': '~', '/': '?', "'": '"'
    };

    TE.Line = function(){
    this.width = 0;
    };

    TE.TextEditor = function(id){
    this.id = id;
    this.lines = [new TE.Line];
    this.positionCursor();
    this.blinkCursor();
    this.watchKeyPress();
    };

    jQuery.extend(TE.TextEditor.prototype, {

    $: function(){
    @@ -69,7 +73,13 @@
    },

    positionCursor: function(){
    this.$cursor().css({ top: this.cursorPos.y, left: this.cursorPos.x });
    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 });
    },

    blinkCursor: function(){
    @@ -97,16 +107,53 @@

    var entity = $('<div/>').text(key).html();

    body.html(html+entity);

    this.cursorPos.x += this._getCharWidth(entity);
    this.positionCursor();
    this.addChar(entity);
    } else if (TE.FUNCTION_KEYS[evt.which] == 'backspace' && html.length > 0) {
    var lastChar = html[html.length-1];
    body.html(html.slice(0,html.length-1));
    this.cursorPos.x -= this._getCharWidth(lastChar);
    this.positionCursor();
    var lastEntityMatch = html.match(/&[^;]+?;$/),
    lastEntity;
    if (lastEntityMatch) lastEntity = lastEntityMatch[0];
    else lastEntity = html[html.length-1];
    this.removeChar(lastEntity);
    }
    },

    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){
    @@ -126,7 +173,7 @@

    <div id="editor" style="width: 300px; height: 200px; border: solid 1px black; position: relative;">
    <div class="cursor" style="width: 2px; height: 15px; background-color: black; position: absolute;"></div>
    <div class="body"></div>
    <div class="body" style="line-height: 18px x"></div>
    </div>

    <div id="tester" style="position: absolute; left: -1000px;"></div>
  3. @wagenet wagenet revised this gist May 13, 2010. 1 changed file with 22 additions and 5 deletions.
    27 changes: 22 additions & 5 deletions js-text-editor.html
    Original file line number Diff line number Diff line change
    @@ -40,8 +40,18 @@
    56:"8", 57:"9", 59:";", 61:"=", 65:"a", 66:"b", 67:"c", 68:"d", 69:"e",
    70:"f", 71:"g", 72:"h", 73:"i", 74:"j", 75:"k", 76:"l", 77:"m", 78:"n",
    79:"o", 80:"p", 81:"q", 82:"r", 83:"s", 84:"t", 85:"u", 86:"v", 87:"w",
    88:"x", 89:"y", 90:"z", 107:"+", 109:"-", 110:".", 188:",", 190:".",
    191:"/", 192:"`", 219:"[", 220:"\\", 221:"]", 222:"\""
    88:"x", 89:"y", 90:"z", 107:"=", 109:"-", 110:".", 188:",", 190:".",
    191:"/", 192:"`", 219:"[", 220:"\\", 221:"]", 222:"'"
    };

    TE.PRINTABLE_SHIFT_KEYS = {
    '0': ')', '1': '!', '2': '@', '3': '#', '4': '$', '5': '%', '6': '^',
    '7': '&', '8': '*', '9': '(', 'a': 'A', 'b': 'B', 'c': 'C', 'd': 'D',
    'e': 'E', 'f': 'F', 'g': 'G', 'h': 'H', 'i': 'I', 'j': 'J', 'k': 'K',
    'l': 'L', 'm': 'M', 'n': 'N', 'o': 'O', 'p': 'P', 'q': 'Q', 'r': 'R',
    's': 'S', 't': 'T', 'u': 'U', 'v': 'V', 'w': 'W', 'x': 'X', 'y': 'Y',
    'z': 'Z', '=': '+', '-': '_', '.': '>', ',': '<', '[': '{', ']': '}',
    '\\': '|', '`': '~', '/': '?', "'": '"'
    };

    jQuery.extend(TE.TextEditor.prototype, {
    @@ -80,11 +90,18 @@
    key;

    if (key = TE.PRINTABLE_KEYS[evt.which]) {
    body.html(html+key);
    if (evt.shiftKey) {
    var withShift = TE.PRINTABLE_SHIFT_KEYS[key];
    if (withShift) key = withShift;
    }

    var entity = $('<div/>').text(key).html();

    body.html(html+entity);

    this.cursorPos.x += this._getCharWidth(key);
    this.cursorPos.x += this._getCharWidth(entity);
    this.positionCursor();
    } else if (TE.FUNCTION_KEYS[evt.which] == 'backspace') {
    } else if (TE.FUNCTION_KEYS[evt.which] == 'backspace' && html.length > 0) {
    var lastChar = html[html.length-1];
    body.html(html.slice(0,html.length-1));
    this.cursorPos.x -= this._getCharWidth(lastChar);
  4. @wagenet wagenet created this gist May 13, 2010.
    118 changes: 118 additions & 0 deletions js-text-editor.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,118 @@
    <!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.TextEditor = function(id){
    this.id = id;
    this.cursorPos = { x: 0, y: 0 };
    this.positionCursor();
    this.blinkCursor();
    this.watchKeyPress();
    };

    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.PRINTABLE_KEYS = {
    32: ' ', 48:"0", 49:"1", 50:"2", 51:"3", 52:"4", 53:"5", 54:"6", 55:"7",
    56:"8", 57:"9", 59:";", 61:"=", 65:"a", 66:"b", 67:"c", 68:"d", 69:"e",
    70:"f", 71:"g", 72:"h", 73:"i", 74:"j", 75:"k", 76:"l", 77:"m", 78:"n",
    79:"o", 80:"p", 81:"q", 82:"r", 83:"s", 84:"t", 85:"u", 86:"v", 87:"w",
    88:"x", 89:"y", 90:"z", 107:"+", 109:"-", 110:".", 188:",", 190:".",
    191:"/", 192:"`", 219:"[", 220:"\\", 221:"]", 222:"\""
    };

    jQuery.extend(TE.TextEditor.prototype, {

    $: function(){
    return $('#'+this.id);
    },

    $cursor: function(){
    return this.$().children('.cursor');
    },

    $body: function(){
    return this.$().children('.body');
    },

    positionCursor: function(){
    this.$cursor().css({ top: this.cursorPos.y, left: this.cursorPos.x });
    },

    blinkCursor: function(){
    var el = this.$cursor();
    setInterval(function(){
    el.is(':hidden') ? el.show() : el.hide();
    }, 500);
    },

    watchKeyPress: function(){
    var self = this;
    $(document).keydown(function(evt){ self.keyPressed(evt); });
    },

    keyPressed: function(evt){
    var body = this.$body(),
    html = body.html(),
    key;

    if (key = TE.PRINTABLE_KEYS[evt.which]) {
    body.html(html+key);

    this.cursorPos.x += this._getCharWidth(key);
    this.positionCursor();
    } else if (TE.FUNCTION_KEYS[evt.which] == 'backspace') {
    var lastChar = html[html.length-1];
    body.html(html.slice(0,html.length-1));
    this.cursorPos.x -= this._getCharWidth(lastChar);
    this.positionCursor();
    }
    },

    _getCharWidth: function(chr){
    if (chr === ' ') chr = '&nbsp;';
    return $('#tester').html(chr).width();
    }

    });

    $(document).ready(function(){
    TE.currentEditor = new TE.TextEditor('editor');
    });
    </script>

    </head>
    <body>

    <div id="editor" style="width: 300px; height: 200px; border: solid 1px black; position: relative;">
    <div class="cursor" style="width: 2px; height: 15px; background-color: black; position: absolute;"></div>
    <div class="body"></div>
    </div>

    <div id="tester" style="position: absolute; left: -1000px;"></div>

    </body>
    </html>