penguin/AstroJS

Javascript framework for my blog

botanjs/src/Astro/Blog/AstroEdit/SmartInput.js

raw ยท 7238 bytes

(function(){
	var ns = __namespace( "Astro.Blog.AstroEdit" );

	/** @type {Dandelion} */
	var Dand                                    = __import( "Dandelion" );
	/** @type {Dandelion.IDOMElement} */
	var IDOMElement                             = __import( "Dandelion.IDOMElement" );
	/** @type {Dandelion.IDOMObject} */
	var IDOMObject                              = __import( "Dandelion.IDOMObject" );
	/** @type {System.Cycle} */
	var Cycle                                   = __import( "System.Cycle" );
	/** @type {System.Debug} */
	var debug                                   = __import( "System.Debug" );
	/** @type {System.utils} */
	var utils                                   = __import( "System.utils" );
	/** @type {System.utils.Perf} */
	var Perf                                    = __import( "System.utils.Perf" );
	/** @type {System.utils.DataKey} */
	var DataKey                                 = __import( "System.utils.DataKey" );
	/** @type {System.utils.EventKey} */
	var EventKey                                = __import( "System.utils.EventKey" );
	/** @type {System.utils.IKey} */
	var IKey                                    = __import( "System.utils.IKey" );
	/** @type {Components.MessageBox} */
	var MessageBox                              = __import( "Components.MessageBox" );
	/** @type {Astro.Blog.Config} */
	var Config                                  = __import( "Astro.Blog.Config" );

	var __cands = [];
	var GetCandidates = function()
	{
		if( __cands.length ) return __cands;

		var Cands = {
			"Article Reference": "Ref"
			, "facts": "T"
			, "text": "T"
			, "h1": "h1"
			, "h2": "h2"
			, "h3": "h3"
			, "h4": "h4"
			, "h5": "h5"
		};

		for( var i in Cands )
		{
			__cands.push( Dand.wrapc( "cn", i, new DataKey( "key", Cands[i] ) ) );
		}

		return __cands;
	};

	var ResetCandidates = function()
	{
		var Cands = GetCandidates();
		for( var i in Cands )
		{
			var c = IDOMElement( Cands[i] );
			c.setAttribute( new DataKey( "selected", 0 ) );
		}
	};

	var FilteredCandidates = function()
	{
		var Cands = GetCandidates();
		var selected = [];
		for( var i in Cands )
		{
			var c = IDOMElement( Cands[i] );
			c.setAttribute( new DataKey( "selected", 0 ) );

			if( c.style.display != "none" )
			{
				selected.push( c );
			}
		}

		return selected;
	};

	var SelectedCandidate = function()
	{
		var Cands = GetCandidates();
		for( var i in Cands )
		{
			var c = IDOMElement( Cands[i] );
			if( c.getDAttribute( "selected" ) == "1" )
			{
				return c;
			}
		}

		return null;
	};

	var KeyHandler = function( sender, handler )
	{
		return function( e )
		{
			e = e || window.event;
			if ( e.keyCode ) code = e.keyCode;
			else if ( e.which ) code = e.which;

			handler( sender, e );
		};
	};

	var CandidateCycle = -1;
	var KeywordTyped = 0;
	var shiftTabbed = false;

	/** @param {Astro.Blog.AstroEdit.Visualizer} */
	var SmartInput = function ( visualizer )
	{
		var SBarPresent = false;
		var insert = function() { return Dand.textNode( "" ); };

		var HandleInput = function( sender, e )
		{
			// Don't handle if holding shift or ctrl key
			if( e.shiftKey || e.ctrlKey )
			{
				// Except the Shift + Tab, we need to cycle this
				shiftTabbed = e.shiftKey && e.keyCode == 9;

				if( !shiftTabbed ) return;
			}

			switch( e.keyCode )
			{
				case 192: // `
					// Closing the quote, that means this is a block-quoted text
					e.preventDefault();

					// Hitting ` twice escapes the ` character itself
					var v = sender.value.substr( 1 );
					if( v == "" ) insert = function() { return Dand.textNode( "`" ); };

					insert = undefined;
					sender.BindingBox.close( true );

					// Insert the code snippet with inline flag
					visualizer.insertSnippet( "code", { "inline": "on", "lang": "plain", "value": v } );
					break;
				case 13: // Enter
					// Not closing the quote, either a direct text or the first matched action
					e.preventDefault();

					var selected = SelectedCandidate();
					// Check if matched an action first
					if( selected )
					{

					}
					else
					{
						// Insert this text directly
						var v = Dand.textNode( sender.value.substr( 1 ) );
						insert = function() { return v; };
						sender.BindingBox.close( true );
					}
					break;
				case 27: // Esc
					sender.BindingBox.close();
					break;
				case 9: // Tab
					// Hitting tab will cycle around the candidates
					e.preventDefault();

					var c = FilteredCandidates();
					var l = c.length;
					if( !l ) break;

					CandidateCycle += e.shiftKey ? -1 : 1;

					if( CandidateCycle == l )
					{
						CandidateCycle = 0;
					}
					else if( CandidateCycle == -1 )
					{
						CandidateCycle = c.length - 1;
					}

					var ThisCandidate = c[ CandidateCycle ];
					ThisCandidate.setAttribute( new DataKey( "selected", 1 ) );

					var CyclingKeyword = ThisCandidate.element.textContent;

					sender.value = "`" + CyclingKeyword;
					sender.setSelectionRange( KeywordTyped, sender.value.length );
					break;
				default:
					FilteredCandidates();
					CandidateCycle = -1;
			}
		};

		var TestEmpty = function( sender, e )
		{
			if( sender.value == "" )
			{
				sender.BindingBox.close();
			}

			// Search exact matched Candidates
			switch( e.keyCode )
			{
				case 9: // Tab, do nothing
					break;
				case 16: // Shift, check if shiftTabbed
					if( shiftTabbed )
					{
						shiftTabbed = false;
						break;
					}
				default:
					if( CandidateCycle == -1 )
					{
						KeywordTyped = sender.value.length;
					}

					var c = GetCandidates();
					var keyword = sender.value.substr( 1 );
					for( var i in c )
					{
						var Cand = c[i];
						var t = Cand.textContent;
						Cand.style.display = t.match( new RegExp( "^" + keyword, "i" ) ) ? "" : "none";
					}
			}
		};

		var ClosePanel = function( confirmed )
		{
			ResetCandidates();

			Cycle.next( function() { SBarPresent = false; } );
			visualizer.restoreSelection();

			if( !confirmed ) return;

			if( insert != undefined )
				visualizer.insertAtCaret( insert() );
		};

		var ShowSmartBar = function()
		{
			if( SBarPresent ) return;
			visualizer.saveSelection();

			SBarPresent = true;

			var title = "Quick Access ( Prototype )";

			var Command = Dand.wrap(
				"input", null, "v_snippet_input_single"
				, null, IKey.quickDef( "type", "text", "placeholder", "Command", "value", "`" )
			);
			var Candidates = Dand.wrap( "div", null, "compx smartbar-candidates", GetCandidates() );

			Command.selectionStart = Command.selectionEnd = 1;

			Command.BindingBox = new MessageBox(
				title
				, Dand.wrape([ Command, Candidates ])
				, "Back", false
				, ClosePanel
			);

			Command.BindingBox.show();
			Command.focus();

			var DCommand = IDOMElement( Command );
			DCommand.addEventListener( "KeyDown", KeyHandler( Command, HandleInput ) );
			DCommand.addEventListener( "KeyUp", KeyHandler( Command, TestEmpty ) );
		};

		var BackQuoteBinding = function ( sender, e )
		{
			if( !SBarPresent && code == 192 )
			{
				e.preventDefault();
				ShowSmartBar();
			}
		};

		IDOMObject( document ).addEventListener( "KeyDown", KeyHandler( document, BackQuoteBinding ), false );
	};

	ns[ NS_EXPORT ]( EX_CLASS, "SmartInput", SmartInput );
})();