penguin/AstroJS

Javascript framework for my blog

commit 82ef981d0a1b1ccdc5ea3fab707d7001b3853e67

author斟酌 鵬兄 <tgckpg@gmail.com>
date2016-02-21T19:08:07Z
subjectSmartInput for back quoted text
commit 82ef981d0a1b1ccdc5ea3fab707d7001b3853e67
Author: 斟酌 鵬兄 <tgckpg@gmail.com>
Date:   2016-02-21T19:08:07Z

    SmartInput for back quoted text
---
 botanjs/src/Astro/Blog/AstroEdit/SiteLibrary.js    |  38 +++--
 botanjs/src/Astro/Blog/AstroEdit/SmartInput.css    |  12 ++
 botanjs/src/Astro/Blog/AstroEdit/SmartInput.js     | 165 +++++++++++++++++++++
 .../Blog/AstroEdit/Visualizer/Snippet/Code.css     |  12 ++
 .../Blog/AstroEdit/Visualizer/Snippet/Code.js      |  40 +++--
 .../Blog/AstroEdit/Visualizer/Snippet/Image.js     |   4 +-
 .../Blog/AstroEdit/Visualizer/Snippet/Link.js      |   6 +-
 .../src/Astro/Blog/AstroEdit/Visualizer/_this.js   |  32 ++--
 botanjs/src/Astro/Blog/AstroEdit/_this.js          |   8 +-
 botanjs/src/Astro/Blog/Components/Entry/Blog.css   |   6 +
 botanjs/src/Astro/Blog/Components/Section.css      |   4 +-
 botanjs/src/Astro/Blog/Layout/Login.js             |  11 +-
 botanjs/src/Astro/Build/Page/Index.css             |   5 +-
 .../src/externs/Astro.Blog.AstroEdit.Visualizer.js |   4 +
 14 files changed, 297 insertions(+), 50 deletions(-)

diff --git a/botanjs/src/Astro/Blog/AstroEdit/SiteLibrary.js b/botanjs/src/Astro/Blog/AstroEdit/SiteLibrary.js
index ebf699b..62dd35d 100644
--- a/botanjs/src/Astro/Blog/AstroEdit/SiteLibrary.js
+++ b/botanjs/src/Astro/Blog/AstroEdit/SiteLibrary.js
@@ -223,10 +223,11 @@
 
 			// General file, list view
 			/** @param {_AstJson_.AJaxGetFiles} */
-			, buildGeneralCanvas = function (e)
+			, buildGeneralCanvas = function ( e )
 			{
+				if( !e ) return;
 				var f = e.files, dateStamp;
-				resetCanvas("canvasGeneral");
+				resetCanvas( "canvasGeneral" );
 
 				for(var i in f)
 				{
@@ -527,9 +528,13 @@
 
 			, requestInsert = function(confirmed)
 			{
-				if(confirmed)
+				if( confirmed )
 				{
-					var selectedAlbum = IDOMElement(IDOMElement(this.albumList).first(1, function(elem) { return elem.hasAttribute("selected"); })).getDAttribute("id");
+					var selectedAlbum = IDOMElement(
+						IDOMElement( this.albumList ).first( 1, function(elem) {
+							return elem.hasAttribute("selected");
+						} ) ).getDAttribute( "id" );
+
 					postData(
 						processorSet
 						, { "group": "insert", "id": selectedAlbum , "files": this.collection.join(",") }
@@ -541,17 +546,17 @@
 			, delCollection = function (confirmed)
 			{
 				var album_id = IDOMElement(this).getDAttribute("id");
-				if(confirmed)
+				if( confirmed )
 				{
-					postData(processorGet, { group: "get", aid: album_id }, getFilestoDelete.bind(this), loadFailed);
+					postData( processorGet, { group: "get", aid: album_id }, getFilestoDelete.bind( this ), loadFailed );
 				}
 				else
 				{
-					postData(processorSet, { group: "remove", id: album_id }, refreshCanvas, loadFailed);
+					postData( processorSet, { group: "remove", id: album_id }, refreshCanvas, loadFailed );
 				}
 			}
 
-			, getFilestoDelete = function (e)
+			, getFilestoDelete = function ( e )
 			{
 				var l = Object.keys(e.files).length
 					, fc = 0
@@ -646,20 +651,29 @@
 						, f = e.files, first;
 
 					// button mime/type ( counts )
-					for(var i in f)
+					for( var i in f )
 					{
-						var elem = Dand.wrap("span", null, "asl_menuItem", (i + "(" + f[i] + ")"), new DataKey("name", i));
+						var elem = Dand.wrap(
+								"span", null, "asl_menuItem"
+								, i + "(" + f[i] + ")"
+								, new DataKey( "name", i ) );
 
 						view_index[view_index.length] = [i, elem];
 						menuBar.appendChild(elem);
 
 						IDOMElement(elem).addEventListener(new EventKey("Click", switchView.bind(IDOMElement(elem))));
 
-						if(!first) first = [i, elem];
+						if( !first ) first = [ i, elem ];
+					}
+
+					if( !first )
+					{
+						debug.Info( "No files" );
+						return;
 					}
 
 					// begin load index
-					refreshCanvas(first[0]);
+					refreshCanvas( first[0] );
 					// Set style
 					first[1].setAttribute("current", 1);
 				}
diff --git a/botanjs/src/Astro/Blog/AstroEdit/SmartInput.css b/botanjs/src/Astro/Blog/AstroEdit/SmartInput.css
new file mode 100644
index 0000000..6497d7c
--- /dev/null
+++ b/botanjs/src/Astro/Blog/AstroEdit/SmartInput.css
@@ -0,0 +1,12 @@
+.smartbar-candidates {
+	padding: 0.5em;
+	background-color: rgba( 0, 0, 0, 0.2 );
+}
+
+.smartbar-candidates .cn {
+	padding: 0.2em 0.5em;
+}
+
+.smartbar-candidates .cn:hover {
+	background-color: rgba( 0, 0, 0, 0.2 );
+}
diff --git a/botanjs/src/Astro/Blog/AstroEdit/SmartInput.js b/botanjs/src/Astro/Blog/AstroEdit/SmartInput.js
new file mode 100644
index 0000000..bd1946d
--- /dev/null
+++ b/botanjs/src/Astro/Blog/AstroEdit/SmartInput.js
@@ -0,0 +1,165 @@
+(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 GetCandidates = function()
+	{
+		var c = [];
+		var Cands = {
+			"facts": "T"
+			, "text": "T"
+		};
+
+		for( var i in Cands )
+		{
+			c.push( Dand.wrapc( "cn", i ) );
+		}
+
+		return c;
+	};
+
+	/** @param {Astro.Blog.AstroEdit.Visualizer} */
+	var SmartInput = function ( visualizer )
+	{
+		var SBarPresent = false;
+		var insert = function() { return Dand.textNode( "" ); };
+
+		var HandleInput = function( sender, e )
+		{
+			e = e || window.event;
+			if ( e.keyCode ) code = e.keyCode;
+			else if ( e.which ) code = e.which;
+
+			// Don't handle if holding shift or ctrl key
+			if( e.shiftKey || e.ctrlKey ) 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 the code snippet with inline flag
+					visualizer.insertSnippet( "code", { "inline": "on", "lang": "plain", "value": v } );
+
+					sender.BindingBox.close( true );
+					break;
+				case 13: // Enter
+					// Not closing the quote, either a direct text or the first matched action
+					e.preventDefault();
+
+					// Check if matched an action first
+					if( false )
+					{
+					}
+					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;
+			}
+		};
+
+		var TestEmpty = function( sender, e )
+		{
+			if( sender.value == "" )
+			{
+				sender.BindingBox.close();
+			}
+		};
+
+		var ClosePanel = function( confirmed )
+		{
+			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";
+
+			var Command = Dand.wrap(
+				"input", null, "v_snippet_input_single"
+				, null, IKey.quickDef( "type", "text", "placeholder", "Command", "value", "`" )
+			);
+			var Candidates = Dand.wrap( "div", null, "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", function( e ) { HandleInput( Command, e ); } );
+			DCommand.addEventListener( "KeyUp", function( e ) { TestEmpty( Command, e ); } );
+		};
+
+		var BackQuoteBinding = function ( e )
+		{
+			e = e || window.event;
+			if ( e.keyCode ) code = e.keyCode;
+			else if ( e.which ) code = e.which;
+
+			if( !SBarPresent && code == 192 )
+			{
+				e.preventDefault();
+				ShowSmartBar();
+			}
+		};
+
+		IDOMObject( document ).addEventListener( "KeyDown", BackQuoteBinding, false );
+	};
+
+	ns[ NS_EXPORT ]( EX_CLASS, "SmartInput", SmartInput );
+})();
diff --git a/botanjs/src/Astro/Blog/AstroEdit/Visualizer/Snippet/Code.css b/botanjs/src/Astro/Blog/AstroEdit/Visualizer/Snippet/Code.css
new file mode 100644
index 0000000..bb52996
--- /dev/null
+++ b/botanjs/src/Astro/Blog/AstroEdit/Visualizer/Snippet/Code.css
@@ -0,0 +1,12 @@
+.v_boundary[data-inline="on"][data-type="Code"] {
+	display: inline-block;
+	background-color: #EEE;
+}
+
+.v_box[data-inline="on"] .v_description {
+	display: none;
+}
+
+.v_box[data-inline="on"] {
+	min-height: 0;
+}
diff --git a/botanjs/src/Astro/Blog/AstroEdit/Visualizer/Snippet/Code.js b/botanjs/src/Astro/Blog/AstroEdit/Visualizer/Snippet/Code.js
index 564ef6d..6271c37 100644
--- a/botanjs/src/Astro/Blog/AstroEdit/Visualizer/Snippet/Code.js
+++ b/botanjs/src/Astro/Blog/AstroEdit/Visualizer/Snippet/Code.js
@@ -14,6 +14,7 @@
 	var MessageBox                        = __import( "Components.MessageBox" );
 
 	var escapeStr = ns[ NS_INVOKE ]( "escapeStr" );
+	var compileProp = ns[ NS_INVOKE ]( "compileProp" );
 	
 	var code = function ( insertSnippet, snippetWrap, createContext, override )
 	{
@@ -34,7 +35,7 @@
 			, "JavaScript" , "js"
 			, "SQL"        , "sql"
 		)
-		
+
 		// Private methods
 		, compileListItems = function ()
 		{
@@ -55,7 +56,8 @@
 		{
 			// Input fields
 			var v_snippetInput = Dand.wrap( "textarea", null, "v_snippet_input" )
-			, v_codelang = Dand.wrap( "select", null, "v_select flsf", compileListItems() );
+				, v_codelang = Dand.wrap( "select", null, "v_select flsf", compileListItems() )
+				, input_inline = Dand.wrapna('input', new IKey("type", "checkbox") );
 			
 			if ( this._stage )
 			{
@@ -69,8 +71,9 @@
 						}
 					}
 				}
-				
+
 				v_snippetInput.value = this._content || "";
+				input_inline.checked = ( this._inline == "on" );
 			}
 			else
 			{
@@ -87,29 +90,31 @@
 					, [
 						Dand.wrapc( "v_instruction", v_codelang )
 						, v_snippetInput
+						, Dand.wrape([ input_inline, Dand.textNode( "Inline" ) ])
 					]
 				)
 				, "OK", "Cancel"
 				// Switcher
-				, visualizer.bind({ code:v_snippetInput, lang: v_codelang, stage: this._stage })
+				, visualizer.bind({ code:v_snippetInput, inline: input_inline, lang: v_codelang, stage: this._stage })
 			).show();
 		}
-	
-		
+
 		, visualizer = function ( submitted, override )
 		{
-			var lang, code
+			var lang, code, inline
 			, stage = this.stage;
 			
 			if ( override )
 			{
 				lang = override.lang;
 				code = override.value;
+				inline = override.inline;
 			}
 			else
 			{
 				lang = this.lang[this.pSnippeCodeChoice = this.lang.selectedIndex].value;
 				code = this.code.value;
+				inline = this.inline.checked ? "on" : "";
 			}
 
 			var langName;
@@ -124,7 +129,7 @@
 
 			if ( submitted && code )
 			{
-				if (!stage)
+				if ( !stage )
 				{
 					// Visualize component
 					temp =  Dand.wrapc(
@@ -136,8 +141,10 @@
 						, [
 							new DataKey( "value", code )
 							, new DataKey( "lang", lang )
+							, new DataKey( "inline", inline )
 						]
 					);
+
 					insertSnippet( j = snippetWrap( "Code", temp ), Boolean( override ) );
 				}
 				else
@@ -146,9 +153,10 @@
 						[
 							new DataKey( "value", code )
 							, new DataKey( "lang", lang )
+							, new DataKey( "inline", inline )
 						]
 					);
-					
+
 					temp = stage.firstChild;
 					temp.removeChild( temp.firstChild );
 					stage.firstChild.appendChild( Dand.textNode( code ) );
@@ -160,9 +168,12 @@
 					temp = stage;
 					
 				}
-				
-				i = { _lang: lang, _content: code, _stage: temp };
-				
+
+				i = { _inline: inline, _lang: lang, _content: code, _stage: temp };
+
+				// Set the inline style
+				IDOMElement( j ).setAttribute( new DataKey( "inline", inline ) );
+
 				// Set context menu
 				createContext( i, j, handler );
 			}
@@ -185,10 +196,11 @@
 	{
 		// [code lang=\"" + lang + "\"]" + code + "[/code]"
 		var element = IDOMElement( stage )
-		, lang = element.getDAttribute( "lang" );
+		, props= [ "lang", "inline" ];
 		
 		return "[code"
-			+ (lang ? (" lang=\"" + lang + "\"") : "") + "]"
+			+ compileProp( element, props )
+			+  "]"
 			+ escapeStr( element.getDAttribute( "value" ) )
 			+ "[/code]"
 			;
diff --git a/botanjs/src/Astro/Blog/AstroEdit/Visualizer/Snippet/Image.js b/botanjs/src/Astro/Blog/AstroEdit/Visualizer/Snippet/Image.js
index e387932..7f72294 100644
--- a/botanjs/src/Astro/Blog/AstroEdit/Visualizer/Snippet/Image.js
+++ b/botanjs/src/Astro/Blog/AstroEdit/Visualizer/Snippet/Image.js
@@ -31,11 +31,11 @@
 			, input_preferred = Dand.wrapna('input', new IKey("type", "checkbox"))
 			;
 			
-			if (this._stage)
+			if ( this._stage )
 			{
 				input_url.value = this._url;
 				input_a.value = this._href;
-				input_preferred.checked = (this._preferred == "on");
+				input_preferred.checked = ( this._preferred == "on" );
 			}
 			
 			// Popup MessageBox
diff --git a/botanjs/src/Astro/Blog/AstroEdit/Visualizer/Snippet/Link.js b/botanjs/src/Astro/Blog/AstroEdit/Visualizer/Snippet/Link.js
index 67039c3..3fe3c5b 100644
--- a/botanjs/src/Astro/Blog/AstroEdit/Visualizer/Snippet/Link.js
+++ b/botanjs/src/Astro/Blog/AstroEdit/Visualizer/Snippet/Link.js
@@ -37,8 +37,7 @@
 						, input_text
 						, Dand.wrapc("v_instruction flsf", "Link to:")
 						, input_a
-					]
-				)
+				])
 			, "OK", "Cancel", visualizer.bind({text:input_text, href:input_a, stage: this._stage})).show();
 		}
 		
@@ -100,8 +99,7 @@
 				// Set context menu
 				createContext(i, j, handler);
 			}
-		}
-		;
+		};
 		
 		if (override)
 		{
diff --git a/botanjs/src/Astro/Blog/AstroEdit/Visualizer/_this.js b/botanjs/src/Astro/Blog/AstroEdit/Visualizer/_this.js
index b4fcced..9b7c23e 100644
--- a/botanjs/src/Astro/Blog/AstroEdit/Visualizer/_this.js
+++ b/botanjs/src/Astro/Blog/AstroEdit/Visualizer/_this.js
@@ -165,6 +165,7 @@
 
 		var _resSelection = function ()
 		{
+			contentDiv.focus();
 			if ( selRange )
 			{
 				if ( window.getSelection )
@@ -186,17 +187,18 @@
 		{
 			if( !Dand.id( "v_linebreak" ) )
 			{
-				insertSnippet( lastLine, true );
+				insertElement( lastLine, true );
 			}
 			else
 			{
-				insertSnippet( contentDiv.removeChild( lastLine ), true );
+				insertElement( contentDiv.removeChild( lastLine ), true );
 			}
 		};
 
-		var insertAtCaret = function( element )
+		var insertAtCaret = function( element, newLine )
 		{
 			var sel, range;
+			if( newLine == undefined ) newLine = true;
 			
 			sel = window.getSelection();
 			if ( sel.getRangeAt && sel.rangeCount )
@@ -212,10 +214,11 @@
 			}
 			
 			e_document.updateContent();
-			ensureLastLinebreak();
+
+			if( newLine ) ensureLastLinebreak();
 		};
 
-		var insertSnippet = function( element, override )
+		var insertElement = function( element, override )
 		{
 			if ( override )
 			{
@@ -223,7 +226,6 @@
 				return;
 			}
 			
-			contentDiv.focus();
 			_resSelection();
 			insertAtCaret( element );
 		};
@@ -283,7 +285,7 @@
 			if ( _module )
 			{
 				// Visualize snippet
-				new ( _module )( insertSnippet, snippetWrap, createSnippetMenu, temp );
+				new ( _module )( insertElement, snippetWrap, createSnippetMenu, temp );
 			}
 			else
 			{
@@ -293,7 +295,7 @@
 				token.setAttribute( "class", token.getAttribute( "class" ) + " flsf" );
 				snippetQueue( type ).push([ token, temp ]);
 
-				insertSnippet( token, true );
+				insertElement( token, true );
 			}
 		};
 
@@ -397,7 +399,7 @@
 
 				snippetControls.appendChild( temp );
 				snippetControls.appendChild( Dand.textNode( "\t" ) );
-				temp.onclick = new ( _module )( insertSnippet, snippetWrap, createSnippetMenu );
+				temp.onclick = new ( _module )( insertElement, snippetWrap, createSnippetMenu );
 
 				var queue = snippetQueue( mod_name, _module.alias );
 				for( var i in queue )
@@ -479,6 +481,18 @@
 
 		this.saveSelection = _savSelection;
 		this.restoreSelection = _resSelection;
+		this.insertAtCaret = insertAtCaret;
+
+		this.insertSnippet = function( type, options )
+		{
+			var _module = loadedModule[ type ];
+
+			new ( _module )(
+				// Disables the override
+				function( e ) { insertElement( e ); }
+				, snippetWrap, createSnippetMenu, options
+			);
+		};
 
 		article.invoke(this);
 
diff --git a/botanjs/src/Astro/Blog/AstroEdit/_this.js b/botanjs/src/Astro/Blog/AstroEdit/_this.js
index 6fe4e24..91e4ff1 100644
--- a/botanjs/src/Astro/Blog/AstroEdit/_this.js
+++ b/botanjs/src/Astro/Blog/AstroEdit/_this.js
@@ -24,6 +24,9 @@
 	/** @type {Astro.Blog.AstroEdit.SiteLibrary} */
 	var SiteLibrary                                = __import( "Astro.Blog.AstroEdit.SiteLibrary" );
 
+	// calls the smart bar
+	var SmartInput                                 = __import( "Astro.Blog.AstroEdit.SmartInput" );
+
 	var wh, ww, cw, html, article;
 
 	var init = function ()
@@ -56,12 +59,15 @@
 
 		// Article modules
 		new Draft( article, a_conf.paths.get_drafts );
-		new Visualizer(
+
+		var Vis = new Visualizer(
 			article
 			, Dand.id( "ae_visual_snippets" )
 			, Config.get( "ServiceUri" )
 		);
 
+		new SmartInput( Vis );
+
 		// Independent modules
 		new Uploader( a_conf.paths.set_file );
 		new SiteLibrary(
diff --git a/botanjs/src/Astro/Blog/Components/Entry/Blog.css b/botanjs/src/Astro/Blog/Components/Entry/Blog.css
index aad0417..2eb560c 100644
--- a/botanjs/src/Astro/Blog/Components/Entry/Blog.css
+++ b/botanjs/src/Astro/Blog/Components/Entry/Blog.css
@@ -2,6 +2,12 @@
 	max-width: 100%;
 }
 
+.inline-code {
+	font-family: monospace;
+	background-color: #EEE;
+	padding: 0.2em 0.5em;
+}
+
 .b_entry {
 	background-color: #FAFAFA;
 	/* box-shadow: 0 0 8px -2px black; */
diff --git a/botanjs/src/Astro/Blog/Components/Section.css b/botanjs/src/Astro/Blog/Components/Section.css
index 12fab2f..b6c0018 100644
--- a/botanjs/src/Astro/Blog/Components/Section.css
+++ b/botanjs/src/Astro/Blog/Components/Section.css
@@ -11,7 +11,7 @@
     opacity: 0.5;
 }
 
-.section-buttons > a:hover, .section-buttons > a[active]:hover {
+.section-buttons > a:hover, .section-buttons > a[data-active="1"]:hover {
     opacity: 0.8;
     text-decoration: none;
 }
@@ -50,7 +50,7 @@
 .section-buttons > a > span {
     padding: 0.75em 0.5em;
 }
-.section-buttons > a[active] {
+.section-buttons > a[data-active="1"] {
     opacity: 1;
 }
 .section-buttons > a > .count {
diff --git a/botanjs/src/Astro/Blog/Layout/Login.js b/botanjs/src/Astro/Blog/Layout/Login.js
index 1cf1d8b..133b775 100644
--- a/botanjs/src/Astro/Blog/Layout/Login.js
+++ b/botanjs/src/Astro/Blog/Layout/Login.js
@@ -26,11 +26,12 @@
 
 	var init = function ()
 	{
+		var username;
 		var form = Dand.wrapne('form'
 			, [
 				// Basic login and password fields
 				, Dand.wrapc("flsf", "Name:")
-				, Dand.wrapna('input', [ field_username, new IKey("type", "text") ] )
+				, username = Dand.wrapna('input', [ field_username, new IKey("type", "text") ] )
 
 				, Dand.wrapc("flsf", "Password:")
 				, Dand.wrapna('input', [ field_password, new IKey("type", "password") ] )
@@ -42,13 +43,13 @@
 				new IKey('action', formAction)
 				, new IKey('method', 'POST')
 			]
-		)
-		;
-
+		);
 
 		var mbox = new MessageBox( "Blog.Astro", form, "Login", false, submitForm.bind( form ) ).show();
 
-			// Handle enter button
+		username.focus();
+
+		// Handle enter button
 		mbox.onkeyup = function( _e )
 		{
 			if ( submitForm.bind( form )( _e.which == 13 ) )
diff --git a/botanjs/src/Astro/Build/Page/Index.css b/botanjs/src/Astro/Build/Page/Index.css
index a448192..6e7945f 100644
--- a/botanjs/src/Astro/Build/Page/Index.css
+++ b/botanjs/src/Astro/Build/Page/Index.css
@@ -1,7 +1,10 @@
 .site-news { font-family: monospace; }
 .site-news > span { display: block; }
 
-.rbuilds > a { display: block; }
+.rbuilds > a {
+	display: block;
+	font-family: monospace;
+}
 .rbuilds > a > span {
     padding: 0 0.5em;
 }
diff --git a/botanjs/src/externs/Astro.Blog.AstroEdit.Visualizer.js b/botanjs/src/externs/Astro.Blog.AstroEdit.Visualizer.js
index 4cf2549..e36379c 100644
--- a/botanjs/src/externs/Astro.Blog.AstroEdit.Visualizer.js
+++ b/botanjs/src/externs/Astro.Blog.AstroEdit.Visualizer.js
@@ -9,4 +9,8 @@ Astro.Blog.AstroEdit.Visualizer.visualizeData;
 /** @type {Function} */
 Astro.Blog.AstroEdit.Visualizer.saveSelection;
 /** @type {Function} */
+Astro.Blog.AstroEdit.Visualizer.insertAtCaret;
+/** @type {Function} */
+Astro.Blog.AstroEdit.Visualizer.insertSnippet;
+/** @type {Function} */
 Astro.Blog.AstroEdit.Visualizer.restoreSelection;