AstroJS/botanjs/src/Astro/Blog/AstroEdit/Visualizer/_this.js
2015-08-14 21:06:23 +08:00

497 lines
12 KiB
JavaScript

(function(){
var ns = __namespace( "Astro.Blog.AstroEdit" );
/** @type {Dandelion} */
var Dand = __import( "Dandelion" );
/** @type {Dandelion.IDOMElement} */
var IDOMElement = __import( "Dandelion.IDOMElement" );
/** @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 {System.Net.ClassLoader} */
var Loader = __import( "System.Net.ClassLoader" );
/** @type {Components.MessageBox} */
var MessageBox = __import( "Components.MessageBox" );
/** @type {Components.Mouse.ContextMenu} */
var ContextMenu = __import( "Components.Mouse.ContextMenu" );
/** @type {Astro.Blog.Config} */
var Config = __import( "Astro.Blog.Config" );
var snippetList = IKey.quickDef(
"Code" , "background: white; color: cornflowerblue;"
, "Image" , "background: #ff0084;"
, "Sound" , "background: YellowGreen;"
, "Video" , "background: Crimson;"
, "Spoiler" , "background: cornflowerblue;"
, "Swf" , "background: #333;"
, "Link" , "background: blue;"
, "AcquireLib" , "background: black;"
, "Html" , "background: coral;"
, "SiteFile" , "background: royalblue;"
);
var snippetNs = "Astro.Blog.AstroEdit.Visualizer.Snippet.";
//// Document Visualizer visualize snippets, used in AstroEdit and comments
var Visualizer = function ( e_document, snippetControls, service_uri )
{
var Article = ns[ NS_INVOKE ]( "Article" );
var loadedModule = {};
//// Constants
var article;
// "[^]" does not work in IE
var stackMatch = /\[([a-z][0-9a-z]*?)([\s\S]*?)\]([\s\S]*?)\[\/\1\]/ig;
var typeMatch = /([a-z][0-9a-z]*)\=\"([^"]+)\"/ig;
var snippetTokenQueue = {};
if ( e_document instanceof Article )
{
article = e_document;
// Allow Html snippet
}
else if ( e_document instanceof CeDocument )
{
article = e_document;
}
else return;
////// Variables
var contentDiv = Dand.wrap()
var selRange;
var snippetExists = false;
var lastOffset = 0;
var raw;
///// private methods
var snippetWrap = function( snippetType, element, editable, _with )
{
var snippet = Dand.wrap(
_with || "div"
, Perf.uuid
, "comp v_boundary"
, element
, [
new IKey( "contentEditable", editable ? "true" : "false" )
, new DataKey( "type", snippetType )
]
);
return snippet;
};
var appendLinebreak = function ()
{
this.parentNode.insertBefore(Dand.wrap("br"), this.nextSibling);
}
var insertLinebreak = function ()
{
this.parentNode.insertBefore(Dand.wrap("br"), this);
};
var snippetDelete = function ()
{
this.parentNode.removeChild(this);
};
var snippetBBCode = function()
{
var snippet = IDOMElement( this );
var type = snippet.getDAttribute( "type" ).toLowerCase();
/** @type {Astro.Blog.AstroEdit.Visualizer.Snippet.Model} */
var module = loadedModule[ type ];
var title = "An error occurred";
var message = "No such module";
if( module )
{
title = "BBCode for Snippet: " + type;
message = module.compile( this.firstChild );
}
new MessageBox( title, Dand.wrapne( "pre", message ) ).show();
};
var createSnippetMenu = function ( snippetProp, snippet, editHandler, snippetActions )
{
var contextItems = [
editHandler && new EventKey( "Edit", editHandler.bind( snippetProp ) )
, new EventKey( "Insert a linebreak", insertLinebreak.bind( snippet ) )
, new EventKey( "Append a linebreak", appendLinebreak.bind( snippet ) )
, new EventKey( "Delete", snippetDelete.bind( snippet ) )
, new EventKey( "View BBCode", snippetBBCode.bind( snippet ) )
];
e_document.updateContent();
if ( snippetActions instanceof Array )
{
contextItems = snippetActions.concat( contextItems );
}
return new ContextMenu( snippet, contextItems, "RMB" );
};
var _savSelection = function ()
{
contentDiv.focus();
if ( window.getSelection )
{
var sel = window.getSelection();
if ( sel.getRangeAt && sel.rangeCount )
{
selRange = sel.getRangeAt(0);
}
}
else if ( document.selection && document.selection.createRange )
{
selRange = document.selection.createRange();
}
else
{
selRange = null;
}
};
var _resSelection = function ()
{
if ( selRange )
{
if ( window.getSelection )
{
var sel = window.getSelection();
sel.removeAllRanges();
sel.addRange( selRange );
}
else if ( document.selection && selRange.select )
{
selRange.select();
}
}
};
var lastLine = Dand.wrap( "br", "v_linebreak" );
var ensureLastLinebreak = function ()
{
if( !Dand.id( "v_linebreak" ) )
{
insertSnippet( lastLine, true );
}
else
{
insertSnippet( contentDiv.removeChild( lastLine ), true );
}
};
var insertAtCaret = function( element )
{
var sel, range;
sel = window.getSelection();
if ( sel.getRangeAt && sel.rangeCount )
{
range = sel.getRangeAt(0);
range.deleteContents();
range.insertNode( element );
range.setStartAfter( element );
range.setEndAfter( element );
range.collapse( false );
sel.removeAllRanges();
sel.addRange( range );
}
e_document.updateContent();
ensureLastLinebreak();
};
var insertSnippet = function( element, override )
{
if ( override )
{
contentDiv.appendChild( element );
return;
}
contentDiv.focus();
_resSelection();
insertAtCaret( element );
};
var replaceToken = function( element )
{
contentDiv.insertBefore( element, this.token );
contentDiv.removeChild( this.token );
};
var parseKeys = function ( match, name, value )
{
this[ name ] = value;
};
var snippetQueue = function( type, alias )
{
var q = snippetTokenQueue[ type ] || [];
if( alias )
{
if( !( alias instanceof Array ) ) alias = [ alias ];
for( var i in alias )
{
q = snippetTokenQueue[ alias[i] ] || [];
if( q.length ) return q;
}
}
return ( snippetTokenQueue[ type ] = q );
};
var parseSnippet = function( match, type, properties, _value, offset )
{
var temp, i, j;
// Texts goes first
if ( lastOffset != offset )
{
// innerText does not work in firefox:(
temp = Dand.wrape( raw.substr( lastOffset, offset - lastOffset ) );
// innerHTML will escape html entities, now replace linebreaks to br
temp.innerHTML = temp.innerHTML.replace(/[\r\n]/g, "<br>");
IDOMElement( contentDiv ).lootChildren( temp );
}
lastOffset = offset + match.length;
// Snippet Args
temp = { value: _value };
// to ekeys
properties.replace( typeMatch, parseKeys.bind( temp ) );
// Now deal with snippet
/** @type {Astro.Blog.AstroEdit.Visualizer.Snippet.Model} */
var _module = loadedModule[ type ];
if ( _module )
{
// Visualize snippet
new ( _module )( insertSnippet, snippetWrap, createSnippetMenu, temp );
}
else
{
// no such module, or module is not loaded yet
debug.Info( "[Visualizer] Queueing: " + type );
var token = snippetWrap( "token", type, false );
token.setAttribute( "class", token.getAttribute( "class" ) + " flsf" );
snippetQueue( type ).push([ token, temp ]);
insertSnippet( token, true );
}
};
var linebreakNodes = ["P", "DIV"];
var linebreakNode = function( name )
{
if( -1 < linebreakNodes.indexOf( name.toUpperCase() ) ) return "\n";
return "";
};
var ensureGoodness = function ()
{
_savSelection();
ensureLastLinebreak();
};
var stepContent = function ( element, level )
{
var j , snippetType
, elements = element.childNodes
, output = "";
for (var i = 0, l = elements.length; i < l; i ++)
{
j = elements[i];
// do nothing on lastline
if (j == lastLine) continue;
if (j.nodeType == 1)
{
snippetType = IDOMElement( j ).getDAttribute( "type" );
if( snippetType && loadedModule[ snippetType = snippetType.toLowerCase() ] )
{
output += loadedModule[ snippetType ].compile( j.firstChild );
}
else if ( j.firstChild )
{
// Text
output += linebreakNode( j.nodeName ) + stepContent(j, ++level);
}
else
{
// Handle special <div><br></div> => one line break
if(l == 1 && j.nodeName.toUpperCase() == "BR") continue;
// Line breaks
output += "\n";
}
}
// Text
else if (j.nodeType == 3)
{
output += j.nodeValue;
}
}
return output;
};
///// Initializations
var snippetReady = function ( e )
{
// Initialize modules
var mod_name = e.substr( e.lastIndexOf(".") + 1, e.length ).toLowerCase()
/** @type {Astro.Blog.AstroEdit.Visualizer.Snippet.Model} */
var _module = __import( e );
var alias = _module.alias;
// Implement module if valid and available
if ( _module && _module.compile )
{
loadedModule[ mod_name ] = _module;
debug.Info( "[Visualizer] Module loaded: " + mod_name );
// Set alias
if ( alias )
{
alias = ( alias instanceof Array ) ? alias : [ alias ];
for ( var k in alias )
{
loadedModule[ alias[k] ] = _module;
debug.Info( "[Visualizer] Alias: " + mod_name + " => " + alias );
}
}
// Append module"s control
temp = Dand.wrapne(
"span"
, mod_name
, new IKey(
"style"
, utils.objSearch(
snippetList
/** @param {System.utils.IKey} a */
, function( a ) { return a.keyName.toLowerCase() == mod_name }
, "keyValue"
)
)
);
snippetControls.appendChild( temp );
snippetControls.appendChild( Dand.textNode( "\t" ) );
temp.onclick = new ( _module )( insertSnippet, snippetWrap, createSnippetMenu );
var queue = snippetQueue( mod_name, _module.alias );
for( var i in queue )
{
var token = queue[i];
new ( _module )( replaceToken.bind({ token: token[0] }), snippetWrap, createSnippetMenu, token[1] );
delete queue[i];
}
}
else
{
if ( _module )
{
delete loadedModule[ mod_name ];
debug.Info( "[Visualizer] Module discarded: invalid module \"" + mod_name + "\"" );
}
else
{
debug.Info( "[Visualizer] Module \"" + mod_name + "\" does not exists" );
}
}
};
this.setContentDiv = function( element )
{
contentDiv = element;
// listeners that return values cannot use addEventListener
contentDiv.onkeypress = function(e)
{
if (e.which == 13)
{
insertAtCaret( Dand.wrap( "br" ) );
_savSelection();
return false;
}
return true;
};
temp = IDOMElement( contentDiv );
temp.addEventListener( "Input", ensureGoodness );
temp.addEventListener( "KeyUp", ensureGoodness );
temp.addEventListener( "Click", ensureGoodness );
contentDiv.focus();
ensureGoodness();
try {
document.execCommand( "insertBrOnReturn", false, "true" );
} catch(e) {
debug.Info( "Not firefox" );
}
};
this.saveRaw = function ()
{
return stepContent( contentDiv, 0 );
};
this.visualizeData = function ( data )
{
if ( !data ) return;
lastOffset = 0;
// clear content
contentDiv.innerHTML = "";
( raw = data ).replace( stackMatch, parseSnippet );
if ( lastOffset != raw.length )
{
// innerText does not work in firefox:(
temp = Dand.wrape( raw.substr( lastOffset, raw.length - lastOffset ) );
// innerHTML will escape html entities, now replace linebreaks to br
temp.innerHTML = temp.innerHTML.replace( /[\r\n]/g, "<br>" );
IDOMElement( contentDiv ).lootChildren( temp );
}
temp = null;
raw = null;
};
this.saveSelection = _savSelection;
this.restoreSelection = _resSelection;
article.invoke(this);
var modules = [];
for( var i in snippetList )
{
modules[ modules.length ] = snippetNs + snippetList[i].keyName;
}
var ldr = new Loader( service_uri, "r" );
ldr.load( modules, snippetReady );
};
ns[ NS_EXPORT ]( EX_CLASS, "Visualizer", Visualizer );
})();