forked from Botanical/BotanJS
497 lines
12 KiB
JavaScript
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 );
|
|
})();
|