penguin/AstroJS

Javascript framework for my blog

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

raw ยท 8567 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.Net.ClassLoader} */
	var Loader                                  = __import( "System.Net.ClassLoader" );
	/** @type {System.utils} */
	var utils                                   = __import( "System.utils" );
	/** @type {System.utils.DataKey} */
	var DataKey                                 = __import( "System.utils.DataKey" );
	/** @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 moduleNs = "Astro.Blog.AstroEdit.SmartInput.CandidateAction.";
	var service_uri = Config.get( "ServiceUri" );

	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 LoadModule = function( mod, handler )
	{
		var ldr = new Loader( service_uri );
		ldr.load( moduleNs + mod, handler );
	};

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

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

		var Cands = CandidatesOvd || {
			"Article Reference": { module: "ArticleReference", desc: "Article reference link" }
			, "facts": { module: "Facts", desc: "Facts, a fact bubble popup when mouseover" }
			, "footnote": { module: "Footnote", desc: "Footnote, a footnote displayed at the end of article" }
			, "h1": { module: "Heading", options: 1, desc: "Heading, size 1" }
			, "h2": { module: "Heading", options: 2, desc: "Heading, size 2" }
			, "h3": { module: "Heading", options: 3, desc: "Heading, size 3" }
			, "h4": { module: "Heading", options: 4, desc: "Heading, size 4" }
			, "h5": { module: "Heading", options: 5, desc: "Heading, size 5" }
		};

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

			for( var i in Cands )
			{
				/** @param {Astro.Blog.AstroEdit.SmartInput.Definition} */
				var c = Cands[i];
				__cands.push( Dand.wrapc( "cn", [ i, Dand.wrapc( "desc", c.desc ) ], new DataKey( "key", 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 ModuleLoaded = function( e )
		{
			/** @type {Astro.Blog.AstroEdit.SmartInput.ICandidateAction} */
			var module = new ( __import( e ) )( visualizer );

			// XXX
		};

		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();
					insert = undefined;

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

					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 )
					{
						insert = undefined;
						sender.BindingBox.close();

						LoadModule( Cands[ selected.getDAttribute( "key" ) ].module, ModuleLoaded );
					}
					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.getDAttribute( "key" );

					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 = IDOMElement( c[i] );
						var t = Cand.getDAttribute( "key" );
						Cand.style.display = t.match( new RegExp( "^" + keyword, "i" ) ) ? "" : "none";

						// Highlight the exact match
						if( t.match( new RegExp( "^" + keyword + "$", "i" ) ) )
						{
							Cand.setAttribute( new DataKey( "selected", 1 ) );
						}
					}
			}
		};

		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 );
})();