initial version
This commit is contained in:
commit
7e462ec372
3 changed files with 656 additions and 0 deletions
521
editor.html
Normal file
521
editor.html
Normal file
|
|
@ -0,0 +1,521 @@
|
|||
<!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>Editor</title>
|
||||
<meta name="author" content="Fabian Jakobs">
|
||||
|
||||
<style type="text/css" media="screen">
|
||||
|
||||
#container {
|
||||
position: absolute;
|
||||
width: 600px;
|
||||
}
|
||||
|
||||
#canvas {
|
||||
position: absolute;
|
||||
border: 1px solid black;
|
||||
margin: 4px;
|
||||
width: 590px;
|
||||
height: 400px;
|
||||
overflow-x: auto;
|
||||
overflow-y: auto;
|
||||
font-family: Courier New;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#canvas.focus {
|
||||
border: 1px solid #327fbd;;
|
||||
}
|
||||
|
||||
.composition {
|
||||
position: absolute;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.cursor {
|
||||
position: absolute;
|
||||
width: 1px;
|
||||
background: black;
|
||||
}
|
||||
|
||||
.line {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.line.odd {
|
||||
background: #FAFAFA;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div id="container">
|
||||
<div id="canvas"></div>
|
||||
</div>
|
||||
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
|
||||
function addListener(elem, type, callback) {
|
||||
if (elem.addEventListener) {
|
||||
return elem.addEventListener(type, callback, false);
|
||||
}
|
||||
if (elem.attachEvent) {
|
||||
elem.attachEvent("on" + type, function() {
|
||||
callback(window.event);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function setText(elem, text) {
|
||||
if (elem.innerText !== undefined) {
|
||||
elem.innerText = text;
|
||||
}
|
||||
if (elem.textContent !== undefined) {
|
||||
elem.textContent = text;
|
||||
}
|
||||
}
|
||||
|
||||
function stopEvent(e) {
|
||||
stopPropagation(e);
|
||||
preventDefault(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
function stopPropagation(e) {
|
||||
if (e.stopPropagation)
|
||||
e.stopPropagation();
|
||||
else
|
||||
e.cancelBubble = true;
|
||||
}
|
||||
|
||||
function preventDefault (e)
|
||||
{
|
||||
if (e.preventDefault)
|
||||
e.preventDefault();
|
||||
else
|
||||
e.returnValue = false;
|
||||
}
|
||||
|
||||
function TextInput(parentNode, host) {
|
||||
|
||||
var text = document.createElement("textarea");
|
||||
var style = text.style;
|
||||
style.position = "absolute";
|
||||
style.left = "-10000px";
|
||||
style.top = "-10000px";
|
||||
parentNode.appendChild(text);
|
||||
|
||||
var inCompostion = false;
|
||||
|
||||
var onTextInput = function(e) {
|
||||
setTimeout(function() {
|
||||
if (!inCompostion) {
|
||||
if (text.value) host.onTextInput(text.value);
|
||||
text.value = "";
|
||||
}
|
||||
}, 0)
|
||||
}
|
||||
|
||||
var onCompositionStart = function(e)
|
||||
{
|
||||
inCompostion = true;
|
||||
|
||||
if (text.value) host.onTextInput(text.value);
|
||||
text.value = "";
|
||||
|
||||
host.onCompositionStart();
|
||||
setTimeout(onCompositionUpdate, 0);
|
||||
}
|
||||
|
||||
var onCompositionUpdate = function() {
|
||||
host.onCompositionUpdate(text.value);
|
||||
}
|
||||
|
||||
var onCompositionEnd = function()
|
||||
{
|
||||
inCompostion = false;
|
||||
host.onCompositionEnd();
|
||||
onTextInput();
|
||||
}
|
||||
|
||||
addListener(text, "keypress", onTextInput, false);
|
||||
addListener(text, "textInput", onTextInput, false);
|
||||
addListener(text, "paste", onTextInput, false);
|
||||
addListener(text, "propertychange", onTextInput, false);
|
||||
|
||||
addListener(text, "compositionstart", onCompositionStart, false);
|
||||
addListener(text, "compositionupdate", onCompositionUpdate, false);
|
||||
addListener(text, "compositionend", onCompositionEnd, false);
|
||||
|
||||
addListener(text, "blur", function() {
|
||||
host.onBlur();
|
||||
}, false);
|
||||
|
||||
addListener(text, "focus", function() {
|
||||
host.onFocus();
|
||||
}, false);
|
||||
|
||||
|
||||
this.focus = function() {
|
||||
text.focus();
|
||||
}
|
||||
|
||||
this.blur = function() {
|
||||
this.blur();
|
||||
}
|
||||
};
|
||||
|
||||
var keys = {
|
||||
UP: 38,
|
||||
RIGHT: 39,
|
||||
DOWN: 40,
|
||||
LEFT: 37,
|
||||
POS1: 36,
|
||||
END: 35,
|
||||
DELETE: 46,
|
||||
BACKSPACE: 8,
|
||||
TAB: 9
|
||||
}
|
||||
|
||||
function KeyBinding(element, host)
|
||||
{
|
||||
addListener(element, "keydown", function(e)
|
||||
{
|
||||
var key = e.keyCode;
|
||||
|
||||
switch (key)
|
||||
{
|
||||
case keys.UP:
|
||||
host.moveUp();
|
||||
return stopEvent(e);
|
||||
|
||||
case keys.DOWN:
|
||||
host.moveDown();
|
||||
return stopEvent(e);
|
||||
|
||||
case keys.LEFT:
|
||||
host.moveLeft();
|
||||
return stopEvent(e);
|
||||
|
||||
case keys.RIGHT:
|
||||
host.moveRight();
|
||||
return stopEvent(e);
|
||||
|
||||
case keys.POS1:
|
||||
host.moveLineStart();
|
||||
return stopEvent(e);
|
||||
|
||||
case keys.END:
|
||||
host.moveLineEnd();
|
||||
return stopEvent(e);
|
||||
|
||||
case keys.DELETE:
|
||||
host.removeRight();
|
||||
return stopEvent(e);
|
||||
|
||||
case keys.BACKSPACE:
|
||||
host.removeLeft();
|
||||
return stopEvent(e);
|
||||
|
||||
case keys.TAB:
|
||||
host.onTextInput(" ");
|
||||
return stopEvent(e);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
function Editor(containerId, canvasId)
|
||||
{
|
||||
this.container = document.getElementById(containerId);
|
||||
this.canvas = document.getElementById(canvasId);
|
||||
|
||||
var textInput = new TextInput(container, this);
|
||||
new KeyBinding(this.container, this);
|
||||
|
||||
var self = this;
|
||||
addListener(container, "mousedown", function(e) {
|
||||
textInput.focus();
|
||||
self.placeCursorToMouse(e.pageX, e.pageY);
|
||||
return preventDefault(e);
|
||||
});
|
||||
|
||||
this.measureSizes();
|
||||
|
||||
this.composition = document.createElement("div");
|
||||
this.composition.className = "composition";
|
||||
this.composition.style.height = this.lineHeight + "px";
|
||||
|
||||
this.cursor = document.createElement("div");
|
||||
this.cursor.className = "cursor";
|
||||
this.cursor.style.height = this.lineHeight + "px";
|
||||
|
||||
this.row = 0;
|
||||
this.col = 0;
|
||||
this.lines = [""];
|
||||
|
||||
this.draw();
|
||||
}
|
||||
|
||||
Editor.prototype = {
|
||||
|
||||
measureSizes : function()
|
||||
{
|
||||
var measureNode = document.createElement("div");
|
||||
var style = measureNode.style;
|
||||
style.width = style.height = "auto";
|
||||
style.left = style.top = "-1000px";
|
||||
style.visibility = "hidden";
|
||||
style.position = "absolute";
|
||||
style.overflow = "visible";
|
||||
|
||||
measureNode.innerHTML = "X<br>X";
|
||||
this.canvas.appendChild(measureNode);
|
||||
|
||||
this.lineHeight = Math.round(measureNode.offsetHeight / 2);
|
||||
this.characterWidth = measureNode.offsetWidth;
|
||||
|
||||
this.canvas.removeChild(measureNode);
|
||||
},
|
||||
|
||||
draw : function()
|
||||
{
|
||||
if (this.cursor.parentNode) {
|
||||
this.canvas.removeChild(this.cursor);
|
||||
}
|
||||
|
||||
// TODO HACK
|
||||
var longestLine = this.canvas.clientWidth;
|
||||
for (var i=0; i < this.lines.length; i++) {
|
||||
longestLine = Math.max(longestLine, (this.lines[i].length * this.characterWidth));
|
||||
}
|
||||
|
||||
var html = [];
|
||||
for (var i=0; i < this.lines.length; i++)
|
||||
{
|
||||
html.push(
|
||||
"<div class='line ",
|
||||
i % 2 == 0 ? "even" : "odd",
|
||||
"' style='height:" + this.lineHeight + "px;",
|
||||
"width:", longestLine, "px'>",
|
||||
this.lines[i].
|
||||
replace(/&/g, "&").
|
||||
replace(/</g, "<").
|
||||
replace(/\s/g, " "),
|
||||
"</div>"
|
||||
);
|
||||
};
|
||||
this.canvas.innerHTML = html.join("");
|
||||
|
||||
this.updateCursor();
|
||||
},
|
||||
|
||||
updateCursor : function()
|
||||
{
|
||||
var left = this.col * this.characterWidth;
|
||||
var top = this.row * this.lineHeight;
|
||||
|
||||
this.cursor.style.left = left + "px";
|
||||
this.cursor.style.top = top + "px";
|
||||
|
||||
this.canvas.appendChild(this.cursor);
|
||||
|
||||
if (this.canvas.scrollLeft > left) {
|
||||
this.canvas.scrollLeft = left;
|
||||
}
|
||||
|
||||
if (this.canvas.scrollLeft + this.canvas.clientWidth < left + this.characterWidth) {
|
||||
this.canvas.scrollLeft = left + this.characterWidth - this.canvas.clientWidth;
|
||||
}
|
||||
|
||||
if (this.canvas.scrollTop > top) {
|
||||
this.canvas.scrollTop = top;
|
||||
}
|
||||
|
||||
if (this.canvas.scrollTop + this.canvas.clientHeight < top + this.lineHeight) {
|
||||
this.canvas.scrollTop = top + this.lineHeight - this.canvas.clientHeight;
|
||||
}
|
||||
},
|
||||
|
||||
onFocus : function() {
|
||||
this.canvas.className = "focus";
|
||||
},
|
||||
|
||||
onBlur : function() {
|
||||
this.canvas.className = "";
|
||||
},
|
||||
|
||||
placeCursorToMouse : function(pageX, pageY)
|
||||
{
|
||||
var canvas = this.canvas;
|
||||
var canvasPos = canvas.getBoundingClientRect();
|
||||
|
||||
var row = Math.floor((pageY + canvas.scrollTop - canvasPos.top) / this.lineHeight);
|
||||
var col = Math.floor((pageX + canvas.scrollLeft - canvasPos.left) / this.characterWidth);
|
||||
|
||||
this.moveTo(row, col)
|
||||
},
|
||||
|
||||
onTextInput: function(text)
|
||||
{
|
||||
var newLines = text.split(/[\n\r]/);
|
||||
|
||||
if (text == "\n")
|
||||
{
|
||||
var line = this.lines[this.row] || "";
|
||||
this.lines[this.row] = line.substring(0, this.col);
|
||||
this.lines.splice(this.row+1, 0, line.substring(this.col));
|
||||
|
||||
this.row += 1;
|
||||
this.col = 0;
|
||||
}
|
||||
else if (newLines.length == 1)
|
||||
{
|
||||
var line = this.lines[this.row] || "";
|
||||
this.lines[this.row] = line.substring(0, this.col) + text + line.substring(this.col);
|
||||
this.col += text.length;
|
||||
}
|
||||
else
|
||||
{
|
||||
var line = this.lines[this.row] || "";
|
||||
|
||||
this.lines[this.row] = line.substring(0, this.col) + newLines[0];
|
||||
this.lines[this.row+1] = newLines[newLines.length-1] + line.substring(this.col);
|
||||
|
||||
if (newLines.length > 2)
|
||||
{
|
||||
var args = [this.row + 1, 0]
|
||||
args.push.apply(args, newLines.slice(1, -1));
|
||||
this.lines.splice.apply(this.lines, args);
|
||||
}
|
||||
|
||||
this.row = this.row + newLines.length - 1;
|
||||
this.col = newLines[newLines.length-1].length;
|
||||
}
|
||||
|
||||
this.draw();
|
||||
},
|
||||
|
||||
removeRight : function()
|
||||
{
|
||||
var currentLine = this.lines[this.row];
|
||||
|
||||
if (this.col == currentLine.length)
|
||||
{
|
||||
this.lines[this.row] = currentLine + (this.lines[this.row+1] || "");
|
||||
this.lines.splice(this.row+1, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.lines[this.row] = currentLine.substring(0, this.col) + currentLine.substring(this.col + 1);
|
||||
}
|
||||
|
||||
this.draw();
|
||||
},
|
||||
|
||||
removeLeft : function()
|
||||
{
|
||||
var currentLine = this.lines[this.row] || "";
|
||||
|
||||
if (this.col == 0)
|
||||
{
|
||||
if (this.row !== 0)
|
||||
{
|
||||
var prevLine = this.lines[this.row-1] || ""
|
||||
this.lines[this.row-1] = currentLine + prevLine;
|
||||
this.lines.splice(this.row, 1);
|
||||
this.row -! 1;
|
||||
this.col = prevLine.length;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
this.lines[this.row] = currentLine.substring(0, this.col-1) + currentLine.substring(this.col);
|
||||
this.col -= 1;
|
||||
}
|
||||
|
||||
this.draw();
|
||||
},
|
||||
|
||||
onCompositionStart : function()
|
||||
{
|
||||
setText(this.composition, "");
|
||||
|
||||
this.composition.style.left = (this.col * this.characterWidth) + "px";
|
||||
this.composition.style.top = (this.row * this.lineHeight) + "px";
|
||||
|
||||
this.onTextInput(" ");
|
||||
|
||||
this.canvas.appendChild(this.composition);
|
||||
},
|
||||
|
||||
onCompositionUpdate : function(text) {
|
||||
setText(this.composition, text);
|
||||
},
|
||||
|
||||
onCompositionEnd : function() {
|
||||
if (this.composition.parentNode) {
|
||||
this.canvas.removeChild(this.composition);
|
||||
}
|
||||
|
||||
this.removeLeft();
|
||||
},
|
||||
|
||||
moveUp : function() {
|
||||
this.moveBy(-1, 0);
|
||||
},
|
||||
|
||||
moveDown : function() {
|
||||
this.moveBy(1, 0);
|
||||
},
|
||||
|
||||
moveLeft : function() {
|
||||
if (this.col == 0) {
|
||||
if (this.row > 0) {
|
||||
this.moveTo(this.row-1, this.lines[this.row-1].length);
|
||||
}
|
||||
}
|
||||
this.moveBy(0, -1);
|
||||
},
|
||||
|
||||
moveRight : function()
|
||||
{
|
||||
if (this.col == this.lines[this.row].length) {
|
||||
if (this.row < this.lines.length-1) {
|
||||
this.moveTo(this.row+1, 0);
|
||||
}
|
||||
} else {
|
||||
this.moveBy(0, 1);
|
||||
}
|
||||
},
|
||||
|
||||
moveLineStart : function() {
|
||||
this.moveTo(this.row, 0);
|
||||
},
|
||||
|
||||
moveLineEnd : function() {
|
||||
this.moveTo(this.row, this.lines[this.row].length);
|
||||
},
|
||||
|
||||
moveBy : function(rows, chars) {
|
||||
this.moveTo(this.row+rows, this.col+chars);
|
||||
},
|
||||
|
||||
moveTo : function(row, column)
|
||||
{
|
||||
this.row = Math.min(this.lines.length-1, Math.max(0, row));
|
||||
this.col = Math.min(this.lines[this.row].length, Math.max(0, column));
|
||||
this.updateCursor();
|
||||
}
|
||||
}
|
||||
|
||||
new Editor("container", "canvas", "text");
|
||||
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Add a link
Reference in a new issue