Skip to content

Commit

Permalink
Merge branch 'editor-context'
Browse files Browse the repository at this point in the history
  • Loading branch information
Colin Kuebler committed Nov 17, 2012
2 parents b093d80 + 266ba05 commit 14f3fc6
Show file tree
Hide file tree
Showing 9 changed files with 267 additions and 46 deletions.
116 changes: 82 additions & 34 deletions app/ContextMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
*/

// creates a right click menu
function ContextMenu( options ){
function ContextMenu( ){
var sections = arguments;
// generate the menu
var menu = document.createElement("ul");
menu.className = 'contextmenu';
Expand All @@ -18,38 +19,60 @@ function ContextMenu( options ){
return false;
};
var keyMap = {};
for( var i in options ){
var item = document.createElement("li");
// underlines the first character following an underscore
item.innerHTML = i.replace( /_(.)/, function(_,x){
// make this character a shortcut to this menu item
keyMap[x.toUpperCase()] = options[i];
return '<u>'+x+'</u>';
} );
item.onmousedown = function(e){
// prevent other mousedown events from firing
e = e || window.event;
e.cancelBubble = true;
e.stopPropagation && e.stopPropagation();
// don't highlight contextmenu text
return false;
};
// creates a closure to store callback
item.onclick = (function(callback){
return function(){
closeMenu();
callback();
var selection = null;
function generateMenuFragment( options ){
var menu = document.createDocumentFragment();
for( var i in options ){
var item = document.createElement("li");
// maintain which item was last under the mouse
item.onmouseover = (function( newSelection ){
return function(){
selection && (selection.className = '');
selection = newSelection;
selection.className = 'current';
};
})( item );
// don't do anything just yet, wait for a click event
item.onmousedown = function(e){
// prevent other mousedown events from firing
e = e || window.event;
e.cancelBubble = true;
e.stopPropagation && e.stopPropagation();
// don't highlight contextmenu text
return false;
};
})( options[i] );
menu.appendChild(item);
}
// creates a closure to store callback
item.onclick = (function(callback){
return function(){
closeMenu();
callback();
};
})( options[i] );
// underlines the first character following an underscore
item.innerHTML = i.replace( /_(.)/, function(_,x){
// make this character a shortcut to this menu item
keyMap[x.toUpperCase()] = item.onclick;
return '<u>'+x+'</u>';
} );
menu.appendChild(item);
}
return menu;
};
function openMenu(e){
// position the menu at the mouse position
e = e || window.event;
menu.style.left = ( e.pageX || e.clientX + document.body.scrollLeft
+ document.documentElement.scrollLeft )+'px';
menu.style.top = ( e.pageY || e.clientY + document.body.scrollTop
+ document.documentElement.scrollTop )+'px';
// update menu entries
menu.innerHTML = '';
for( var i in sections ){
var section = sections[i];
if( typeof(section) === 'function' ) section = section(e);
menu.appendChild( generateMenuFragment(section) );
};
// abort if there are no options
if( !menu.firstChild ) return;
// position the menu at the mouse position
// clientX/Y are relative to window (we don't care about scrolling)
menu.style.left = e.clientX + 'px';
menu.style.top = e.clientY + 'px';
document.body.appendChild(menu);
// hide the menu on the next click
document.onmousedown = closeMenu;
Expand All @@ -62,18 +85,43 @@ function ContextMenu( options ){
return false;
};
function closeMenu(){
if( selection ){
selection.className = '';
selection = null;
}
document.body.removeChild(menu);
document.onmousedown = null;
document.onkeydown = null;
};
// handles keyboard shortcuts in contextmenu
function shortcut(e){
e = e || window.event;
var key = String.fromCharCode( e.which || e.keyCode );
if( keyMap[key] ){
closeMenu();
keyMap[key]();
var key = e.which || e.keyCode;
switch( key ){
case 13: // enter
selection && selection.click();
break;
case 27: // escape
closeMenu();
break;
case 38: // up arrow
selection && (selection.className = '');
selection = (selection && selection.previousSibling)
|| menu.lastChild;
selection.className = 'current';
break;
case 40: // down arrow
selection && (selection.className = '');
selection = (selection && selection.nextSibling)
|| menu.firstChild;
selection.className = 'current';
break;
default: // possible hotkey
key = keyMap[ String.fromCharCode(key) ];
key && key();
}
// cancel the default action
return false;
};
return openMenu;
};
Expand Down
9 changes: 8 additions & 1 deletion app/Dictionary.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ function Dictionary(){

var last = '';

query.onkeyup = function(){
var update = query.onkeyup = function(){
if( query.value === last ) return;
last = query.value;
var regex = new RegExp( RegExp.escape(query.value), 'i' );
Expand All @@ -39,6 +39,13 @@ function Dictionary(){
}
};

update();

api.lookup = function(word){
query.value = word;
update();
};

return api;
};

21 changes: 20 additions & 1 deletion app/Editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,26 @@ function Editor( lexer, stage ){
tabs = tabBar.buttons;

var display = new TextareaDecorator( $("rta_in"), lexer );


/* context menu stuff */
var contextLayer = display.input.parentNode;
contextLayer.oncontextmenu = ContextMenu( function(e){
contextLayer.style.zIndex = -1;
var target = document.elementFromPoint( e.clientX, e.clientY );
contextLayer.style.zIndex = 0;
if( target && target.parentNode === display.output ){
// get info on the target
var text = target.textContent,
type = target.className;
return {
'Lookup Type':
function(){ koala.apps.dictionary.lookup(type); },
'Lookup Text':
function(){ koala.apps.dictionary.lookup(text); }
};
}
} );

panel.footer.makeButton( 'run', function(){
stage.interpret( compiler.compile( display.input.value ) );
} );
Expand Down
138 changes: 138 additions & 0 deletions app/Trie.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
/* Trie.js
* written by Colin Kuebler 2012
* Part of The Koala Project, licensed under GPLv3
* Implements a trie for efficient word completions and corrections
*/

function Trie( words ){
/* INTERNAL REPRESENTATION */
var self = this,
root = {},
size = 0;
/* INTERNAL METHODS */
// traverses the trie to the specified prefix
function _get( prefix ){
var node = root;
for( var i = 0; i < prefix.length; i++ ){
node = node[ prefix.charAt(i) ];
if( !node ) return;
}
return node;
};
// recursively determines necessary suffix
function _next( node ){
var path, count = 0;
for( path in node ) if( count++ ) return '';
if( node.leaf ) return ' ';
return path + _next( node[path] );
};
// recursively determines possible suffixes
function _list( node ){
var results = [];
for( var i in node ){
var suffix = _list( node[i] );
for( var j = 0; j < suffix.length; j++ ){
results.push( i + suffix[j] );
}
}
if( node.leaf ) results.push('');
return results;
};
// recursively determines words within an edit distance
function _edits( node, input, allowance ){
// shortcuts to the first, second, and remainder of input
var a = input.charAt(0), b = input.charAt(1), c = input.substr(1);
// iterator variable and result data structure
var i, results = {};
// merges results together, preserves the lowest cost on conflict
function merge( prefix, obj, cost ){
for( var i in obj ){
var key = prefix + i, value = obj[i] + cost;
results[key] = key in results ?
Math.min( value, results[key] ) : value;
}
};
// node is a result iff it is a leaf and there is no more input
if( node.leaf && !a ) results[''] = 0;
// MATCH: consume the current letter
if( node[a] ) merge( a, _edits( node[a], c, allowance ), 0 );
// attempt to undo possible edits iff we are within the edit allowance
if( allowance-- > 0 ){
// if there is no next character, only possible edit is deletion
if( a ){
// ADDITION: ignore current letter and continue
merge( '', _edits( node, c, allowance ), 1 );
for( i in node ){
// skip match, this is not an edit
if( a === i ) continue;
// DELETION: assume correct letter and continue
merge( i, _edits( node[i], input, allowance ), 1 );
// SUBSTITION: assume correct letter, ignore current
merge( i, _edits( node[i], c, allowance ), 1 );
}
// TRANSPOSITION: swap first and second letter
if( node[b] && a !== b )
merge( b, _edits( node[b], a+c.substr(1), allowance ), 1 );
} else {
// DELETION: assume correct letter
for( i in node )
merge( i, _edits( node[i], '', allowance ), 1 );
}
}
return results;
};
/* MODIFIERS */
self.add = function( word ){
var node = root;
for( var i = 0; i < word.length; i++ )
node = node[ word.charAt(i) ] = node[ word.charAt(i) ] || {};
if( node.leaf ) return true;
node.leaf = true;
size++;
};
self.remove = function( word ){
var node = root,
nodes = [],
last = word.length;
for( var i = 0; i < word.length; i++ ){
nodes.push( node );
node = node[ word.charAt(i) ];
if( !node ) return true;
}
if( !node.leaf ) return true;
delete node.leaf;
size--;
while( nodes.length ){
for( var i in node ) return;
node = nodes.pop();
delete node[ word[--last] ];
}
};
/* ACCESSORS */
self.all = function(){
return _list(root);
};
self.size = function(){
return size;
};
/* COOL SHIT */
self.next = function( prefix ){
var node = _get(prefix);
return node && _next(node);
};
self.completions = function( prefix ){
var node = _get(prefix);
return node && _list(node);
};
self.check = function( word ){
var node = _get(word);
return node && node.leaf;
};
self.corrections = function( word, distance ){
return _edits( root, word, distance );
};
/* INIT */
for( var i = 0; i < words.length; i++ ) self.add( words[i] );
return self;
};

9 changes: 3 additions & 6 deletions app/Utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,17 +75,14 @@ String.prototype.suffix = function(delim){
return this.substring( this.lastIndexOf(delim) + 1 || this.length );
};

String.quote || (String.prototype.quote = function(){
String.prototype.quote || (String.prototype.quote = function(){
return '"'+this.replace(/"/g,'\\"')+'"';
});

function toHex(s){
var output = "";
var b16 = "0123456789ABCDEF";
for( var i = 0; i < s.length; i++ ){
var c = s.charCodeAt(i);
output += b16.charAt(c>>4) + b16.charAt(c&15) + " ";
}
for( var i = 0; i < s.length; i++ )
output += ('0' + s.charCodeAt(i).toString(16)).slice(-2);
return output;
};

Expand Down
5 changes: 1 addition & 4 deletions static/elements.css
Original file line number Diff line number Diff line change
Expand Up @@ -356,9 +356,6 @@ ul.contextmenu {
background: Menu;
color: MenuText;
border: 1px solid ThreeDShadow;
box-shadow: 0 5px 18px rgba(0,0,0,0.6);
-moz-box-shadow: 0 5px 18px rgba(0,0,0,0.6);
-webkit-box-shadow: 0 5px 18px rgba(0,0,0,0.6);
z-index: 4;
}

Expand All @@ -369,7 +366,7 @@ ul.contextmenu > li {
cursor: default;
}

ul.contextmenu > li:hover {
ul.contextmenu > li.current {
background: Highlight;
color: HighlightText;
}
Expand Down
1 change: 1 addition & 0 deletions static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<link rel="stylesheet" type="text/css" href="../app/styles/TextareaDecorator.css">
<!-- <script src="koala.full.js" type="text/javascript"></script> -->
<script src="../app/Utils.js" type="text/javascript"></script>
<script src="../app/Trie.js" type="text/javascript"></script>
<script src="../app/ContextMenu.js" type="text/javascript"></script>
<script src="../app/Lexer.js" type="text/javascript"></script>
<script src="../app/Compiler.js" type="text/javascript"></script>
Expand Down
7 changes: 7 additions & 0 deletions static/themes/cozy.css
Original file line number Diff line number Diff line change
Expand Up @@ -272,3 +272,10 @@ button:active {
}
*/

/* context menu */
ul.contextmenu {
box-shadow: 0 4px 6px rgba(0,0,0,0.4);
-moz-box-shadow: 0 4px 6px rgba(0,0,0,0.4);
-webkit-box-shadow: 0 4px 6px rgba(0,0,0,0.4);
}

Loading

0 comments on commit 14f3fc6

Please sign in to comment.