// ---------------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------------
// Default options
var options = {};
options.chkRegExp = false;
options.chkCaseSens = false;
options.chkAnimate = true;
options.txtUserName = conf["user"];
options.chkSaveBackups = true;
options.chkAutoSave = false;
options.chkGenerateAnRssFeed = false;
options.chkSaveEmptyTemplate = false;

// Number of items in the RSS feed
var numRssItems = 20;

// Special tiddlers
var specialTiddlers = ["SiteTitle","SiteSubtitle","MainMenu","DefaultTiddlers","SaveChanges"];

// Starting up
function main()
{
    var css = getTiddlerText("StyleSheet");
    if(css)
        addStylesheet(css);
    loadOptionsCookie();
    setupOptionsPanel();
    setupRegexp();
    refreshAll();
    var start = getTiddlerText("DefaultTiddlers");
    if(window.location.hash) {	    
		    displayTiddlers(null,convertUTF8ToUnicode(decodeURI(window.location.hash.substr(1))),1,null,null);
		    
    } else if(start) {
        displayTiddlers(null,start,1,null,null);
    }
}


// ---------------------------------------------------------------------------------
// Tiddler functions
// ---------------------------------------------------------------------------------

// Display several tiddlers from a list of space separated titles
function displayTiddlers(src,titles,state,highlightText,highlightCaseSensitive,slowly)
{
    var wikiRegExp = new RegExp(wikiNamePatterns,"mg");
    var tiddlerNames = new Array();
    do {
        var linkMatch = wikiRegExp.exec(titles);
        if(linkMatch)
            {
            if(linkMatch[1]) // WikiName
                tiddlerNames.push(linkMatch[1]);
            else if(linkMatch[5]) // Bracketed link
                tiddlerNames.push(linkMatch[5]);
            }
    } while(linkMatch);
    for(var t = tiddlerNames.length-1;t>=0;t--)
        displayTiddler(src,tiddlerNames[t],state,highlightText,highlightCaseSensitive,slowly);
}

// Display a tiddler with animation and scrolling, as though a link to it has been clicked on
//  src = source element object (eg link) for animation effects and positioning
//  title = title of tiddler to display
//  state = 0 is default or current state, 1 is read only and 2 is edittable
//  highlightText = text to highlight in the displayed tiddler
//  highlightCaseSensitive = flag for whether the highlight text is case sensitive
function displayTiddler(src,title,state,highlightText,highlightCaseSensitive,slowly)
{
    var place = document.getElementById("tiddlerDisplay");
    var after = findContainingTiddler(src); // Which tiddler this one will be positioned after
    var before;
    if(after == null)
        before = place.firstChild;
    else if(after.nextSibling)
        before = after.nextSibling;
    else
        before = null;
    var theTiddler = createTiddler(place,before,title,state,highlightText,highlightCaseSensitive);
    if(src)
        {
        var floater = document.getElementById("floater");
        var floaterTitle = document.createTextNode(title);
        if(floater.firstChild)
            floater.replaceChild(floaterTitle,floater.firstChild);
        if(options.chkAnimate)
            {
            theTiddler.style.opacity = 0;
            startZoomer(floater,src,theTiddler,slowly);
            }
        else
            window.scrollTo(0,ensureVisible(theTiddler));
        }
}


// Create a tiddler if it doesn't exist (with no fancy animating)
//  place = parent element
//  before = node before which to create/move the tiddler
//  title = title of tiddler to display
//  state = 0 is default or current state, 1 is read only and 2 is edittable
//  highlightText = text to highlight in the displayed tiddler
//  highlightCaseSensitive = flag for whether the highlight text is case sensitive
function createTiddler(place,before,title,state,highlightText,highlightCaseSensitive)
{
    var theTiddler = createTiddlerSkeleton(place,before,title);
    createTiddlerTitle(title,highlightText,highlightCaseSensitive);
    var theViewer = document.getElementById("viewer" + title);
    var theEditor = document.getElementById("editor" + title);
    switch(state)
        {
        case 0:
            if(!theViewer && !theEditor)
                {
                createTiddlerToolbar(title,false);
                createTiddlerViewer(title,highlightText,highlightCaseSensitive);
                }
            break;
        case 1: // Viewer
            if(theViewer)
                theViewer.parentNode.removeChild(theViewer);
            if(theEditor)
                theEditor.parentNode.removeChild(theEditor);
            createTiddlerToolbar(title,false);
            createTiddlerViewer(title,highlightText,highlightCaseSensitive);
            break;
        case 2: // Editor
            if(!theEditor)
                {
                if(theViewer)
                    theViewer.parentNode.removeChild(theViewer);
                createTiddlerToolbar(title,true);
                createTiddlerEditor(title);
                }
            break;
        }
    return(theTiddler);
}

function createTiddlerSkeleton(place,before,title)
{
    var theTiddler = document.getElementById("tiddler" + title);
    if(!theTiddler)
        {
        theTiddler = createTiddlyElement(null,"div","tiddler" + title,"tiddler",null);
        theTiddler.onmouseover = onMouseOverTiddler;
        theTiddler.onmouseout = onMouseOutTiddler;
	if (conf["adminmode"]) {
           theTiddler.ondblclick = onDblClickTiddler;
	}
        var theInnerTiddler = createTiddlyElement(theTiddler,"div",null,"innerTiddler",null);
        var theTitle = createTiddlyElement(theInnerTiddler,"div","title" + title,"title",null);
        var theToolbar = createTiddlyElement(theInnerTiddler,"div","toolbar" + title,"toolbar", null);
        var theBody = createTiddlyElement(theInnerTiddler,"div","body" + title,"body",null);
        place.insertBefore(theTiddler,before);
        }
    return(theTiddler);
}

function createTiddlerTitle(title,highlightText,highlightCaseSensitive)
{
    var theTitle = document.getElementById("title" + title);
    if(theTitle)
        {
        removeChildren(theTitle);
        if(highlightText == "")
            highlightText = null;
        var highlightRegExp,highlightMatch;
        if(highlightText)
            {
            highlightRegExp = new RegExp(highlightText,highlightCaseSensitive ? "mg" : "img");
            highlightMatch = highlightRegExp.exec(title);
            }
        highlightMatch = subWikify(theTitle,title,0,title.length,highlightRegExp,highlightMatch);
        var subtitle = getTiddlerSubtitle(title);
        theTitle.title = subtitle;
        }
}


// Create a tiddler toolbar according to whether it's an editor or not
function createTiddlerToolbar(title,editor)
{
    var theToolbar = document.getElementById("toolbar" + title);
    if(theToolbar)
        {
        removeChildren(theToolbar);
        insertSpacer(theToolbar);
        if(!editor)
            {
            // Non-editor toolbar
                   
	    createTiddlyButton(theToolbar,
                           conf["close"],
                           conf["close_title"],
                           onClickToolbarClose);
	    insertSpacer(theToolbar);
	
	 if (conf["adminmode"]) {
		    createTiddlyButton(theToolbar,
                           conf["edit"],
                           conf["edit_title"],
                           onClickToolbarEdit);
	             insertSpacer(theToolbar);
	 }
	 
        /*createTiddlyButton(theToolbar,
                            conf["permalink"],
                            conf["permalink_title"],
                            onClickToolbarPermaLink);
        insertSpacer(theToolbar);
        createTiddlyButton(theToolbar,
                           conf["references"],
                           conf["references"],
                           onClickToolbarBackLink);*/
            }
        else
            {
            // Editor toolbar            
	    createTiddlyButton(theToolbar,
                           conf["done"],
                           conf["done_title"],
                           onClickToolbarSave);
	    insertSpacer(theToolbar);
	    createTiddlyButton(theToolbar,
                           conf["undo"],
                           conf["undo_title"],
                           onClickToolbarUndo);
	    insertSpacer(theToolbar);
            createTiddlyButton(theToolbar,
                           conf["delete"],
                           conf["delete_title"],
                           onClickToolbarDelete);
            }
        insertSpacer(theToolbar);
        }
}

// Create the body section of a read-only tiddler
function createTiddlerViewer(title,highlightText,highlightCaseSensitive)
{
    var theBody = document.getElementById("body" + title);
    if(theBody)
        {
        var tiddlerText = getTiddlerText(title);
        var tiddlerExists = (tiddlerText != null);
        if(!tiddlerExists)
            tiddlerText = conf["notexists"];
        var theViewer = createTiddlyElement(theBody,"div","viewer" + title,"viewer",null);
        if(!tiddlerExists)
            theViewer.style.fontStyle = "italic";
        wikify(tiddlerText,theViewer,highlightText,highlightCaseSensitive);
        }
}


// Create the body section of an edittable tiddler
function createTiddlerEditor(title)
{
    var theBody = document.getElementById("body" + title);
    if(theBody)
        {
        var tiddlerText = getTiddlerText(title);
        var tiddlerExists = (tiddlerText != null);
        if(!tiddlerExists)
            tiddlerText = conf["newtext"];
        var theEditor = createTiddlyElement(theBody,"div","editor" + title,"editor",null);
        theEditor.onkeypress = onEditKey;
        var theTitleBox = createTiddlyElement(theEditor,"input","editorTitle" + title,null,null);
        theTitleBox.setAttribute("type","text");
        theTitleBox.value = title;
        theTitleBox.setAttribute("size","40");
        var theBodyBox = createTiddlyElement(theEditor,"textarea","editorBody" + title,null,null);
        theBodyBox.value = tiddlerText;
        theBodyBox.setAttribute("rows","10");
        theBodyBox.style.width = "100%";
        //theBodyBox.setAttribute("cols","80");
        theBodyBox.focus();
        }
}

function saveTiddler(title)
{
    var theNewTitle = document.getElementById("editorTitle" + title).value;
    var theNewBody = document.getElementById("editorBody" + title).value;
    var theExisting = document.getElementById("store" + title);
    if(theExisting)
        theExisting.parentNode.removeChild(theExisting);
    if(title != theNewTitle)
       {
       theExisting = document.getElementById("store" + theNewTitle);
       if(theExisting)
            theExisting.parentNode.removeChild(theExisting);
        }
    var place = document.getElementById("storeArea");
    var storeItem = createTiddlyElement(place,"div","store" + theNewTitle,null,escapeTiddler(theNewBody));
    var now = new Date();
    storeItem.setAttribute("modified",ConvertToYYYYMMDDHHMM(now));
    storeItem.setAttribute("modifier",options.txtUserName);
    displayTiddler(null,theNewTitle,1,null,null,null,false);
    // Close the old tiddler if this is a rename
    if(title != theNewTitle)
        {
        var oldTiddler = document.getElementById("tiddler" + title);
        oldTiddler.parentNode.removeChild(oldTiddler);
        }
    refreshAll();
    // Autosave
    if(options.chkAutoSave)
        saveChanges();
}

function searchTiddlers(text,caseSensitive,useRegExp)
{
    var searchText;
    closeAllTiddlers();
    if (useRegExp)
        searchText = text;
    else
        searchText = escapeRegExp(text);
    if(document.getElementById("store" + text))
        displayTiddler(null,text,1,text,caseSensitive,false); // Special case of searching for a tiddler title
    var theTiddler = document.getElementById("tiddler" + text);
    var store = document.getElementById("storeArea").childNodes;
    var c = 0;
    var regExp = new RegExp(searchText,caseSensitive ? "m" : "im");
    for(var t = 0; t < store.length; t++)
        {
        var e = store[t];
        if(e.id)
            if(e.id.substr(0,5) == "store")
                {
                var tiddlerText = "";
                if(e.firstChild)
                    tiddlerText = unescapeTiddler(e.firstChild.nodeValue);
                if(regExp.exec(e.id.substr(5)) || regExp.exec(tiddlerText))
                    {
                    displayTiddler(null,e.id.substr(5),1,searchText,caseSensitive,false);
                    c++;
                    }
                }
        }
    var q = useRegExp ? "/" : "'";
    displayMessage(c + " "+ conf["searched"]+ " " + q  + text + q);
}

function selectTiddler(title)
{
    var e = document.getElementById("toolbar" + title);
    if(e != null)
        e.style.visibility = "visible";
}

function deselectTiddler(title)
{
    var e = document.getElementById("toolbar" + title);
    if(e != null)
        e.style.visibility = "hidden";
}

function deleteTiddler(title)
{
    closeTiddler(title,false);
    var tiddler = document.getElementById("store" + title);
    if(tiddler)
        tiddler.parentNode.removeChild(tiddler);
    refreshAll();
    // Autosave
    if(options.chkAutoSave)
        saveChanges();
}

function closeTiddler(title,slowly)
{
    var tiddler = document.getElementById("tiddler" + title);
    if(tiddler != null)
        {
        scrubIds(tiddler);
        if(options.chkAnimate)
            startSlider(tiddler,false,slowly,"all");
        else
            tiddler.parentNode.removeChild(tiddler);
        }
}

function scrubIds(e)
{
    if(e.id)
        e.id = null;
    var children = e.childNodes;
    for(var t=0; t<children.length; t++)
        {
        var c = children[t];
        if(c.id)
            c.id = null;
        }
}

function closeAllTiddlers()
{
    clearMessage();
    var place = document.getElementById("tiddlerDisplay");
    var tiddler = place.firstChild;
    var nextTiddler;
    while(tiddler)
        {
        nextTiddler = tiddler.nextSibling;
        if(tiddler.id)
            if(tiddler.id.substr(0,7) == "tiddler")
                {
                var title = tiddler.id.substr(7);
                if(!document.getElementById("editor" + title))
                    place.removeChild(tiddler);
                }
        tiddler = nextTiddler;
        }
}


// ---------------------------------------------------------------------------------
// Regular expression stuff
// ---------------------------------------------------------------------------------

var upperLetter = "[A-Z]";
var lowerLetter = "[a-z_0-9\\-]";
var anyLetter = "[A-Za-z_0-9\\-]";
var anyDigit = "[0-9]";
var anyNumberChar = "[0-9\\.E]";
var wikiNamePattern = "(?:" + upperLetter + "+" + lowerLetter + "+" + upperLetter + anyLetter + "*)|(?:" + upperLetter + "{2,}" + lowerLetter + "+)";
var urlPattern = "(?:http|https|mailto|ftp):[^\\s\"']*";
var explicitLinkPattern = "\\[\\[([^\\[\\]\\|]+)\\|([^\\[\\]\\|]+)\\]\\]";
var bracketNamePattern = "\\[\\[([^\\]]+)\\]\\]";

var wikiNamePatterns;
var wikiNameRegExp;
var structurePatterns;
var stylePatterns;
var tableRegExp;
var tableRowColRegExp;
var invalidPreWikiNamePattern;

function setupRegexp()
{
    // Table rows pattern
    var rowPattern = "^\\|([^\\n]*\\|)([fhc]?)$";
    tableRegExp = new RegExp(rowPattern,"mg");
    // Table columns pattern
    var elementPattern = "(?:(?:BGCOLOR|bgcolor)\\(([^\\)]+)\\):)?([^\\|]*)\\|";
    tableRowColRegExp = new RegExp(elementPattern,"g");
    // Link patterns
    wikiNamePatterns = "(" + wikiNamePattern + 
        ")|(" + urlPattern + 
        ")|(?:" + explicitLinkPattern + 
        ")|(?:" + bracketNamePattern + 
        ")";
    wikiNameRegExp = new RegExp(wikiNamePatterns,"mg");
    invalidPreWikiNamePattern = anyLetter;
    // Structural patterns
    var breakPattern = "\\n";
    var horizontalRulePattern = "^----$\\n?";
    var headerPattern = "^!{1,5}";
    var bulletListItemPattern = "^\\*+";
    var numberedListItemPattern = "^#+";
    var tablePattern = "(?:^\\|[^\\n]*$\\n?)+";
    var blockquotePattern = "(?:^>[^\\n]*$\\n?)+";
    var blockquotePattern2 = "^<<<\\n((?:^[^\\n]*\\n)+)(^<<<$\\n?)";
    var imagePattern = "\\[[Ii][Mm][Gg]\\[(?:([^\\|]+)\\|)?([^\\[\\]\\|]+)\\]\\]";
    var verbatimPattern = "^\\{\\{\\{\\n((?:^[^\\n]*\\n)+?)(^\\}\\}\\}$\\n?)";
    structurePatterns = "(" + breakPattern + 
        ")|(" + horizontalRulePattern + 
        ")|(" + headerPattern + 
        ")|(" + bulletListItemPattern + 
        ")|(" + numberedListItemPattern + 
        ")|(" + tablePattern + 
        ")|(" + blockquotePattern + 
        ")|(?:" + blockquotePattern2 + 
        ")|(?:" + imagePattern + 
        ")|(?:" + verbatimPattern + 
        ")";
    // Style patterns
    var boldPattern = "''([^']+)''";
    var strikePattern = "==([^=]+)==";
    var underlinePattern = "__([^_]+)__";
    var italicPattern = "//([^/]+)//";
    var supPattern = "\\^\\^([^\\^]+)\\^\\^";
    var subPattern = "~~([^~]+)~~";
    var monoPattern = "\\{\\{\\{(.*?)\\}\\}\\}";
    var colorPattern = "@@(?:color\\(([^\\)]+)\\):|bgcolor\\(([^\\)]+)\\):){0,2}([^@]+)@@";
    stylePatterns = "(?:" + boldPattern + 
        ")|(?:" + strikePattern + 
        ")|(?:" + underlinePattern + 
        ")|(?:" + italicPattern + 
        ")|(?:" + supPattern + 
        ")|(?:" + subPattern + 
        ")|(?:" + colorPattern + 
        ")|(?:" + monoPattern + 
        ")";
}

// Create child text nodes and link elements to represent a wiki-fied version of some text
function wikify(text,parent,highlightText,highlightCaseSensitive)
{
    // Prepare the regexp for the highlighted selection
    if(highlightText == "")
        highlightText = null;
    var highlightRegExp,highlightMatch;
    if(highlightText)
        {
        highlightRegExp = new RegExp(highlightText,highlightCaseSensitive ? "mg" : "img");
        highlightMatch = highlightRegExp.exec(text);
        }
    wikifyStructures(parent,text,text,0,text.length,highlightRegExp,highlightMatch);
}


function wikifyStructures(parent,text,targetText,startPos,endPos,highlightRegExp,highlightMatch)
{
    var body = parent;
    var structureRegExp = new RegExp(structurePatterns,"mg");
    var theList = new Array();  // theList[0]: don't use
    var isInListMode = false;
    var isInHeaderMode = false;
    var isNewline = false;
    // The start of the fragment of the text being considered
    var nextPos = 0;
    // Loop through the bits of the body text
    do {
        // Get the next formatting match
        var formatMatch = structureRegExp.exec(targetText);
        var matchPos = formatMatch ? formatMatch.index : targetText.length;
        // Subwikify the plain text before the match
        if(nextPos < matchPos)
            {
            isNewline = false;
            highlightMatch = wikifyStyles(body,text,targetText.substring(nextPos,matchPos),startPos+nextPos,startPos+matchPos,highlightRegExp,highlightMatch);
            }
        // Dump out the formatted match
        var level;
        var theBlockquote;
        if(formatMatch)
            {
            // Dump out the link itself in the appropriate format
            if(formatMatch[1])
                {
                if(isNewline && isInListMode)
                    {
                    theList = new Array();
                    body = parent;
                    isInListMode = false;
                    }
                else if(isInHeaderMode)
                    {
                    body = parent;
                    isInHeaderMode = false;
                    }
                else
                    {
                    isNewline = true;
                    body.appendChild(document.createElement("br"));
                    }
                }
            else if(formatMatch[2])
                {
                isNewline = false;
                body.appendChild(document.createElement("hr"));
                }
            else if(formatMatch[3])
                {
                level = formatMatch[3].length + 1;
                isNewline = false;
                isInHeaderMode = true;
                var theHeader = document.createElement("h" + level);
                parent.appendChild(theHeader);
                body = theHeader;
                }
            else if(formatMatch[4])
                {
                level = formatMatch[4].length;
                isNewline = false;
                isInListMode = true;
                if (theList[level] == null)
                    {
                    theList[level] = document.createElement("ul");
                    body.appendChild(theList[level]);
                    }
                theList = theList.slice(0,level + 1);
                body = document.createElement("li");
                theList[level].appendChild(body);
                }
            else if(formatMatch[5])
                {
                level = formatMatch[5].length;
                isNewline = false;
                isInListMode = true;
                if (theList[level] == null)
                    {
                    theList[level] = document.createElement("ol");
                    body.appendChild(theList[level]);
                    }
                theList = theList.slice(0,level + 1);
                body = document.createElement("li");
                theList[level].appendChild(body);
                }
            else if(formatMatch[6])
                {
                isNewline = false;
                highlightMatch = wikifyTable(body,text,formatMatch[6],startPos+matchPos,startPos+structureRegExp.lastIndex,highlightRegExp,highlightMatch);
                }
            else if(formatMatch[7])
                {
                isNewline = false;
                var quotedText = formatMatch[7].replace(new RegExp("^>(>*)","mg"),"$1");
                theBlockquote = document.createElement("blockquote");
                var newHighlightRegExp,newHighlightMatch;
                if (highlightRegExp) {
                    newHighlightRegExp = new RegExp(highlightRegExp.toString(), "img");
                    newHighlightMatch = newHighlightRegExp.exec(quotedText);
                }
                wikifyStructures(theBlockquote,quotedText,quotedText,0,quotedText.length,newHighlightRegExp,newHighlightMatch);
                body.appendChild(theBlockquote);
                }
            else if(formatMatch[8])
                {
                isNewline = false;
                theBlockquote = document.createElement("blockquote");
                highlightMatch = wikifyStructures(theBlockquote,text,formatMatch[8],startPos+matchPos+4,startPos+structureRegExp.lastIndex-formatMatch[9].length,highlightRegExp,highlightMatch);
                body.appendChild(theBlockquote);
                }
            else if(formatMatch[11])
                {
                isNewline = false;
                var theImage = document.createElement("img");
                theImage.alt = formatMatch[10];
                theImage.src = formatMatch[11];
                body.appendChild(theImage);
                }
            else if(formatMatch[12])
                {
                isNewline = false;
                var theVerbatim = document.createElement("pre");
                theVerbatim.appendChild(document.createTextNode(text.substr(startPos+matchPos+4,startPos+structureRegExp.lastIndex-formatMatch[13].length-startPos-matchPos-4)));
                body.appendChild(theVerbatim);
                }
            }
        // Move the next position past the formatting match
        nextPos = structureRegExp.lastIndex;
    } while(formatMatch);
    return highlightMatch;
}

function wikifyLinks(parent,text,targetText,startPos,endPos,highlightRegExp,highlightMatch)
{
    // The start of the fragment of the text being considered
    var nextPos = 0;
    // Loop through the bits of the body text
    var theLink;
    do {
        // Get the next formatting match
        var formatMatch = wikiNameRegExp.exec(targetText);
        var matchPos = formatMatch ? formatMatch.index : targetText.length;
        // Subwikify the plain text before the match
        if(nextPos < matchPos)
            highlightMatch = subWikify(parent,text,startPos+nextPos,startPos+matchPos,highlightRegExp,highlightMatch);
        // Dump out the formatted match
        if(formatMatch)
            {
            // Dump out the link itself in the appropriate format
            if(formatMatch[1])
                {
                if(matchPos > 0 && new RegExp(invalidPreWikiNamePattern,"").exec(targetText.charAt(matchPos - 1)))
                    {
                    theLink = parent;
                    }
                else
                    {
                     theLink = createTiddlyLink(parent,formatMatch[0],false);
                     }
                 highlightMatch = subWikify(theLink,text,startPos+matchPos,startPos+wikiNameRegExp.lastIndex,highlightRegExp,highlightMatch);
                }
            else if(formatMatch[2])
                {
                theLink = createExternalLink(parent,formatMatch[0]);
                highlightMatch = subWikify(theLink,text,startPos+matchPos,startPos+wikiNameRegExp.lastIndex,highlightRegExp,highlightMatch);
                }
            else if(formatMatch[3])
                {
                theLink = createExternalLink(parent,formatMatch[4]);
                highlightMatch = subWikify(theLink,text,startPos+matchPos+2,startPos+matchPos+2+formatMatch[3].length,highlightRegExp,highlightMatch);
                }
            else if(formatMatch[5])
                {
                theLink = createTiddlyLink(parent,formatMatch[5],false);
                highlightMatch = subWikify(theLink,text,startPos+matchPos+2,startPos+wikiNameRegExp.lastIndex-2,highlightRegExp,highlightMatch);
                }
            }
        // Move the next position past the formatting match
        nextPos = wikiNameRegExp.lastIndex;
    } while(formatMatch);
    return highlightMatch;
}

function wikifyStyles(parent,text,targetText,startPos,endPos,highlightRegExp,highlightMatch)
{
    var formatRegExp = new RegExp(stylePatterns,"mg");
    // The start of the fragment of the text being considered
    var nextPos = 0;
    // Loop through the bits of the body text
    do {
        // Get the next formatting match
        var formatMatch = formatRegExp.exec(targetText);
        var matchPos = formatMatch ? formatMatch.index : targetText.length;
        // Subwikify the plain text before the match
        if(nextPos < matchPos)
            highlightMatch = wikifyLinks(parent,text,targetText.substring(nextPos,matchPos),startPos+nextPos,startPos+matchPos,highlightRegExp,highlightMatch);
        // Dump out the formatted match
        if(formatMatch)
            {
            // Dump out the link itself in the appropriate format
            if(formatMatch[1])
                {
                var theBold = createTiddlyElement(parent,"b",null,null,null);
                highlightMatch = wikifyStyles(theBold,text,formatMatch[1],startPos+matchPos+2,startPos+formatRegExp.lastIndex-2,highlightRegExp,highlightMatch);
                }
            else if(formatMatch[2])
                {
                var theStrike = createTiddlyElement(parent,"strike",null,null,null);
                highlightMatch = wikifyStyles(theStrike,text,formatMatch[2],startPos+matchPos+2,startPos+formatRegExp.lastIndex-2,highlightRegExp,highlightMatch);
                }
            else if(formatMatch[3])
                {
                var theUnderline = createTiddlyElement(parent,"u",null,null,null);
                highlightMatch = wikifyStyles(theUnderline,text,formatMatch[3],startPos+matchPos+2,startPos+formatRegExp.lastIndex-2,highlightRegExp,highlightMatch);
                }
            else if(formatMatch[4])
                {
                var theItalic = createTiddlyElement(parent,"i",null,null,null);
                highlightMatch = wikifyStyles(theItalic,text,formatMatch[4],startPos+matchPos+2,startPos+formatRegExp.lastIndex-2,highlightRegExp,highlightMatch);
                }
            else if(formatMatch[5])
                {
                var theSup = createTiddlyElement(parent,"sup",null,null,null);
                highlightMatch = wikifyStyles(theSup,text,formatMatch[5],startPos+matchPos+2,startPos+formatRegExp.lastIndex-2,highlightRegExp,highlightMatch);
                }
            else if(formatMatch[6])
                {
                var theSub = createTiddlyElement(parent,"sub",null,null,null);
                highlightMatch = wikifyStyles(theSub,text,formatMatch[6],startPos+matchPos+2,startPos+formatRegExp.lastIndex-2,highlightRegExp,highlightMatch);
                }
            else if(formatMatch[9])
                {
                var theSpan;
                if (formatMatch[7] == "" && formatMatch[8] == "" )
                    {
                    theSpan = createTiddlyElement(parent,"span",null,"marked",null);
                    }
                    else
                    {
                    theSpan = createTiddlyElement(parent,"span",null,null,null);
                    if (formatMatch[7] != "") theSpan.style.color = formatMatch[7];
                    if (formatMatch[8] != "") theSpan.style.background = formatMatch[8];
                    }
                highlightMatch = wikifyStyles(theSpan,text,formatMatch[9],startPos+formatRegExp.lastIndex-2-formatMatch[9].length,startPos+formatRegExp.lastIndex-2,highlightRegExp,highlightMatch);
                }
            else if(formatMatch[10])
                {
                var theCode = createTiddlyElement(parent,"code",null,null,null);
                highlightMatch = wikifyStyles(theCode,text,formatMatch[10],startPos+matchPos+3,startPos+formatRegExp.lastIndex-3,highlightRegExp,highlightMatch);
                }
            }
        // Move the next position past the formatting match
        nextPos = formatRegExp.lastIndex;
    } while(formatMatch);
    return highlightMatch;
}

// Create a table 
function wikifyTable(parent,text,targetText,startPos,endPos,highlightRegExp,highlightMatch)
{
    // The start of the fragment of the text being considered
    var nextPos = 0;
    var theTable = document.createElement("table");
    var bodyRowLen = 0;
    var headRowLen = 0;
    var footRowLen = 0;
    var bodyRows = new Array();
    var headRows = new Array();
    var footRows = new Array();
    var theCaption = null;
    // Loop through the bits of the body text
    do {
        // Get the next formatting match
        var formatMatch = tableRegExp.exec(targetText);
        var matchPos = formatMatch ? formatMatch.index : targetText.length;
        // Dump out the formatted match
        if(formatMatch) {
            if (formatMatch[2] == "c") {
                var cap = formatMatch[1].substring(0,formatMatch[1].length-1);
                theCaption = document.createElement("caption");
                highlightMatch = wikifyStyles(theCaption,text,cap,startPos+matchPos+1,startPos+cap.length,highlightRegExp,highlightMatch);
                if (bodyRowLen == 0 && headRowLen == 0 && footRowLen == 0) {
                    theCaption.setAttribute("align", "top");
                } else {
                    theCaption.setAttribute("align", "bottom");
                }
            } else if (formatMatch[2] == "h") {
                highlightMatch = wikifyTableRow(headRows,headRowLen,text,formatMatch[1],startPos+matchPos,startPos+matchPos+formatMatch[1].length,highlightRegExp,highlightMatch);
                headRowLen++;
            } else if (formatMatch[2] == "f") {
                highlightMatch = wikifyTableRow(footRows,footRowLen,text,formatMatch[1],startPos+matchPos,startPos+matchPos+formatMatch[1].length,highlightRegExp,highlightMatch);
                footRowLen++;
            } else {
                highlightMatch = wikifyTableRow(bodyRows,bodyRowLen,text,formatMatch[1],startPos+matchPos,startPos+matchPos+formatMatch[1].length,highlightRegExp,highlightMatch);
                bodyRowLen++;
            }
        }
        nextPos = tableRegExp.lastIndex;
    } while(formatMatch);
    
    if (theCaption != null) {
        theTable.appendChild(theCaption);
    }
    
    if (headRowLen > 0) {
        var theTableHead = document.createElement("thead");
        createTableRows(headRows,theTableHead);
        theTable.appendChild(theTableHead);
    }
    
    if (bodyRowLen > 0) {
        var theTableBody = document.createElement("tbody");
        createTableRows(bodyRows,theTableBody);
        theTable.appendChild(theTableBody);
    }
    
    if (footRowLen > 0) {
        var theTableFoot = document.createElement("tfoot");
        createTableRows(footRows,theTableFoot);
        theTable.appendChild(theTableFoot);
    }
    
    parent.appendChild(theTable);
    return highlightMatch;
}

function wikifyTableRow(rows,rowIndex,text,targetText,startPos,endPos,highlightRegExp,highlightMatch)
{
    // The start of the fragment of the text being considered
    var eIndex = 0;
    var elements = new Array();
    // Loop through the bits of the body text
    do {
        // Get the next formatting match
        var formatMatch = tableRowColRegExp.exec(targetText);
        var matchPos = formatMatch ? formatMatch.index : targetText.length;
        if(formatMatch) {
            var eText = formatMatch[2];
            if (eText == "~" || eText == ">") {
                elements[eIndex] = eText;
            } else {
                var eTextLen = eText.length;
                var align = "";
                if (eTextLen >= 1 && eText.charAt(0) == " ") {
                    if (eTextLen >= 3 && eText.charAt(eTextLen - 1) == " ") {
                        align = "center";
                        eText = eText.substring(1,eTextLen - 1);
                        //eTextLen -= 2;
                        eTextLen--;
                    } else {
                        align = "right";
                        eText = eText.substring(1);
                        eTextLen--;
                    }
                } else if (eTextLen >= 2 && eText.charAt(eTextLen - 1) == " ") {
                    align = "left";
                    eText = eText.substring(0,eTextLen - 1);
                    //eTextLen--;
                }
                
                var theElement;
                if (eTextLen >= 1 && eText.charAt(0) == "!") {
                    theElement = document.createElement("th");
                    eText = eText.substring(1);
                    eTextLen--;
                } else {
                    theElement = document.createElement("td");
                }
                
                if (align != "") {
                    theElement.align = align;
                }
                
                if (formatMatch[1]) {
                    theElement.style.background = formatMatch[1];
                }
                
                highlightMatch = wikifyStyles(theElement,text,eText,startPos+tableRowColRegExp.lastIndex-eTextLen,startPos+tableRowColRegExp.lastIndex-1,highlightRegExp,highlightMatch);
                elements[eIndex] = theElement;
            }
            eIndex++;
        }
    } while(formatMatch);
    rows[rowIndex] = elements;
    return highlightMatch;
}

function createTableRows(rows,parent)
{
    var i, j, k, cols;
    for (i = 0; i < rows.length; i++) {
        cols = rows[i];
        var theRow = document.createElement("tr");
        for (j = 0; j < cols.length; j++) {
            if (cols[j] == "~") continue;
            
            var rowspan = 1;
            for (k = i+1; k < rows.length; k++) {
                if (rows[k][j] != "~") break;
                rowspan++;
            }
            
            var colspan = 1;
            for (; j < cols.length - 1; j++) {
                if (cols[j] != ">") break;
                colspan++;
            }
            
            var theElement = cols[j];
            if (rowspan > 1) {
                theElement.setAttribute("rowSpan",rowspan);
                theElement.setAttribute("rowspan",rowspan);
                theElement.valign = "center";
            }
            if (colspan > 1) {
                theElement.setAttribute("colSpan",colspan);
                theElement.setAttribute("colspan",colspan);
            }
            theRow.appendChild(theElement);
        }
        parent.appendChild(theRow);
    }
}

// Helper for wikify that handles highlights within runs of text
function subWikify(parent,text,startPos,endPos,highlightRegExp,highlightMatch)
{
    // Check for highlights
    while(highlightMatch && (highlightRegExp.lastIndex > startPos) && (highlightMatch.index < endPos) && (startPos < endPos))
        {
        // Deal with the plain text before the highlight
        if(highlightMatch.index > startPos)
            {
            parent.appendChild(document.createTextNode(text.substring(startPos,highlightMatch.index)));
            startPos = highlightMatch.index;
            }
        // Deal with the highlight
        var highlightEnd = Math.min(highlightRegExp.lastIndex,endPos);
        var theHighlight = createTiddlyElement(parent,"span",null,"highlight",text.substring(startPos,highlightEnd));
        startPos = highlightEnd;
        // Nudge along to the next highlight if we're done with this one
        if(startPos >= highlightRegExp.lastIndex)
            highlightMatch = highlightRegExp.exec(text);
        }
    // Do the unhighlighted text left over
    if(startPos < endPos)
        {
        parent.appendChild(document.createTextNode(text.substring(startPos,endPos)));
        //startPos = endPos;
        }
    return(highlightMatch);
}

function getTiddlerText(title)
{
    var tiddlerStore = document.getElementById("store" + title);
    if(tiddlerStore == null) {
	if (conf[title]) return conf[title];
        return(null);
    }
    else if(tiddlerStore.firstChild)
        {
        if(tiddlerStore.firstChild)
            return(unescapeTiddler(tiddlerStore.firstChild.nodeValue));
        else
            return("");
        }
    else
        return("");
}

var regexpBackSlashEn = new RegExp("\\\\n","mg");
var regexpSingleBackSlash = new RegExp("\\\\","mg");
var regexpDoubleBackSlash = new RegExp("\\\\\\\\","mg");
var regexpNewLine = new RegExp("\n","mg");
var regexpCarriageReturn = new RegExp("\r","mg");

// Convert "\n" to newlines
function unescapeTiddler(text)
{
    return(text.replace(regexpBackSlashEn,"\n").replace(regexpSingleBackSlash,"\\\\"));
}

// Convert newlines to "\n"
function escapeTiddler(text)
{
    return(text.replace(regexpDoubleBackSlash,"\\").replace(regexpNewLine,"\\n").replace(regexpCarriageReturn,""));
}

var regexpAmp = new RegExp("&","mg");
var regexpLessThan = new RegExp("<","mg");
var regexpGreaterThan = new RegExp(">","mg");
var regexpQuote = new RegExp("\"","mg");

// Convert & to "&amp;", < to "&lt;", > to "&gt;" and " to "&quot;"
function htmlEncode(text)
{
    return(text.replace(regexpAmp,"&amp;").replace(regexpLessThan,"&lt;").replace(regexpGreaterThan,"&gt;").replace(regexpQuote,"&quot;"));
}

function encodeTiddlyLink(title)
{
    var wikiRegExp = new RegExp("^" + wikiNamePattern + "$","");
    if(wikiRegExp.test(title))
        return(title);
    else
        return("[[" + title + "]]");
}


function getTiddlerSubtitle(title)
{
    var tiddlerStore = document.getElementById("store" + title);
    if(tiddlerStore != null)
        {
        var theModifier = tiddlerStore.getAttribute("modifier");
        if(!theModifier)
            theModifier = conf["unknown"];
        var theModified = tiddlerStore.getAttribute("modified");
        if(theModified)
            theModified = ConvertFromYYYYMMDDHHMM(theModified).toLocaleString();
        else
            theModified = conf["unknown"];
        return(theModifier + ", " + theModified);
        }
    else
        return(null);
}

// Return an array of a given number of tiddlers from the storeArea, sorted by "title", "date" or "author"
function getTiddlers(max,sortType)
{
    var allTiddlers = new Array(); // An array of 3-entry arrays, where entry 0 = name, 1 = date, 2 = author
    var storeNodes = document.getElementById("storeArea").childNodes;
    for (var t = 0; t < storeNodes.length; t++)
        {
        var n = storeNodes[t];
        if(n.id)
            if(n.id.substr(0,5) == "store" &&   isTiddlerOk(n))
                allTiddlers.push(new Array(n.id.substr(5),n.getAttribute("modified"),n.getAttribute("modifier")));
        }
    switch(sortType)
        {
        case "title":
            allTiddlers.sort(function (a,b) { if(a[0] == b[0]) return(0); else return (a[0] > b[0]) ? +1 : -1; });
            break;
        case "date":
            allTiddlers.sort(function (a,b) { if(a[1] == b[1]) return(1); else return (a[1] < b[1]) ? +1 : -1; });
            break;
        case "author":
            allTiddlers.sort(function (a,b) { if(a[2] == b[2]) return(1); else return (a[2] < b[2]) ? +1 : -1; });
            break;
        }
    if(max > 0)
        allTiddlers = allTiddlers.slice(0,max);
    return(allTiddlers);
}

function createTiddlyElement(theParent,theElement,theID,theClass,theText)
{
    var e = document.createElement(theElement);
    if(theClass != null)
        e.className = theClass;
    if(theID != null)
        e.setAttribute("id",theID);
    if(theText != null)
        e.appendChild(document.createTextNode(theText));
    if(theParent != null)
        theParent.appendChild(e);
    return(e);
}

function createTiddlyButton(theParent,theText,theTooltip,theAction)
{
    var theButton = document.createElement("a");
    if(theAction)
        {
        theButton.onclick = theAction;
        theButton.setAttribute("href","JavaScript:;");
        }
    theButton.setAttribute("title",theTooltip);
    if(theText)
        {
        theButton.appendChild(document.createTextNode(theText));
        }
    theParent.appendChild(theButton);
    return(theButton);
}

function createTiddlyLink(place,title,includeText)
{
    var text = includeText ? title : null;
    var subTitle = getTiddlerSubtitle(title);
    var theClass = subTitle ? "tiddlyLinkExisting" : "tiddlyLinkNonExisting";
    if(!subTitle)
        subTitle = title + " "+ conf["notexists_short"];
    var btn = createTiddlyButton(place,text,subTitle,onClickTiddlerLink);
    btn.className = theClass;
    btn.setAttribute("tiddlyLink",title);
    return(btn);
}

function createExternalLink(place,url)
{
    var theLink = document.createElement("a");
    theLink.className = "externalLink";
    theLink.href = url;
    theLink.title = conf["external_link"] + url;
    theLink.target = "_blank";
    place.appendChild(theLink);
    return(theLink);
}
// Find the tiddler instance (if any) containing a specified element
function findContainingTiddler(e)
{
    if(e == null)
        return(null);
    do {
        if(e != document)
            {
            if(e.id)
                if(e.id.substr(0,7) == "tiddler")
                    return(e);
            }
        e = e.parentNode;
    } while(e != document);
    return(null);
}

function displayMessage(text,linkText)
{
    var msgArea = document.getElementById("messageArea");
    var msg = createTiddlyElement(msgArea,"div",null,null,text);
    msgArea.style.display = "block";
    if(linkText)
        {
        var link = createTiddlyElement(msg,"a",null,null,linkText);
        link.href = linkText;
        link.target = "_blank";
        }
}

function clearMessage()
{
    var msgArea = document.getElementById("messageArea");
    while(msgArea.hasChildNodes())
        msgArea.removeChild(msgArea.firstChild);
    msgArea.style.display = "none";
}

// ---------------------------------------------------------------------------------
// Menu and sidebar functions
// ---------------------------------------------------------------------------------

var currentTab; // The id of the currently selected tab

function refreshAll()
{
    refreshHeader();
    refreshMenu();
    refreshSidebar();
}

// Refresh all parts of the header
function refreshHeader()
{
    var theTitle = getTiddlerText("SiteTitle");
    if(!theTitle) theTitle = "SiteTitle";
    var theSubtitle = getTiddlerText("SiteSubtitle");
    if(!theSubtitle) theSubtitle = "SiteSubtitle";
    document.title = theTitle + " - " + theSubtitle;
    var place = document.getElementById("siteTitle");
    while(place.firstChild != null)
        place.removeChild(place.firstChild);
    wikify(theTitle,place,null,null);
    place = document.getElementById("siteSubtitle");
    while(place.firstChild != null)
        place.removeChild(place.firstChild);
    wikify(theSubtitle,place,null,null);
}

function refreshMenu()
{
    var place = document.getElementById("mainMenu");
    while(place.firstChild != null)
        place.removeChild(place.firstChild);
    var menu = getTiddlerText("MainMenu");
    if(!menu) menu = "MainMenu";
    wikify(menu,place,null,null);
}

function refreshSidebar()
{
    var tabContent = document.getElementById("sidebarContent");
    switch(currentTab)
        {
            case "tabTimeline":
                tabContent.className = "tabContentTimeline";
                refreshTabTimeline();
                break;
            case "tabAll":
                tabContent.className = "tabContentAll";
                refreshTabAll();
                break;
            default:
                tabContent.className = "tabContentTimeline";
                refreshTabTimeline();
                break;
        }
}

function refreshTabTimeline()
{
    var allTiddlers = getTiddlers(-1,"date");
    var place = document.getElementById("sidebarContent");
    while(place.firstChild != null)
        place.removeChild(place.firstChild);
    var lastDay = "";
    for (t = 0; t < allTiddlers.length; t++)
        {
        var theDay = allTiddlers[t][1].substr(0,8);
        if(theDay != lastDay)
            {
            //var theDateElement = document.createElement("span");
            //var theDateCaption = ConvertFromYYYYMMDDHHMM(allTiddlers[t][1]).toLocaleDateString();
            //theDateElement.appendChild(document.createTextNode(theDateCaption));
            //theDateElement.className = "sidebarSubHeading";
            //place.appendChild(theDateElement);
            //place.appendChild(document.createElement("br")); 
            lastDay = theDay;
            }
        //place.appendChild(document.createTextNode(String.fromCharCode(160)));
        //place.appendChild(document.createTextNode(String.fromCharCode(160)));	
        createTiddlyLink(place,allTiddlers[t][0],true);		
        //place.appendChild(document.createElement("br"));
        }
}

function refreshTabAll()
{
    var allTiddlers = getTiddlers(-1,"title");
    var place = document.getElementById("sidebarContent");
    while(place.firstChild != null)
        place.removeChild(place.firstChild);
    for (t = 0; t < allTiddlers.length; t++)
        {
        createTiddlyLink(place,allTiddlers[t][0],true);
        place.appendChild(document.createElement("br"));
        }
}

// ---------------------------------------------------------------------------------
// Options stuff
// ---------------------------------------------------------------------------------

function setupOptionsPanel()
{
    for(var opt in options)
        {
        var e = document.getElementById(opt);
        switch(opt.substr(0,3))
            {
            case "txt":
                e.value = options[opt];
                break;
            case "chk":
                e.checked = options[opt];
                break;
            }
        }
}

function loadOptionsCookie()
{
    var cookies = document.cookie;
    if(cookies.length > 0)
        for(var opt in options)
            {
            var n = opt + "=";
            var p = cookies.indexOf(n);
            if(p > -1)
                {
                p += n.length;
                var e = cookies.indexOf(";",p);
                if(e == -1)
                    e = cookies.length;
                var v = cookies.substr(p,e-p);
                switch(opt.substr(0,3))
                    {
                    case "txt":
                        options[opt] = v;
                        break;
                    case "chk":
                        options[opt] = v == "true";
                        break;
                    }
                }
            }
}

function saveOptionCookie(name)
{
    var c = name + "=";
    switch(name.substr(0,3))
        {
        case "txt":
            c += escape(options[name].toString());
            break;
        case "chk":
            c += options[name] ? "true" : "false";
            break;
        }
    c += "; expires=Fri, 1 Jan 2038 12:00:00 UTC; path=/";
    document.cookie = c;
}

function onChangeOption(e)
{
    if (!e) var e = window.event;
    var opt = resolveTarget(e);
    if(opt.id)
        {
        switch(opt.id.substr(0,3))
            {
            case "txt":
                options[opt.id] = opt.value;
                break;
            case "chk":
                options[opt.id] = opt.checked;
                break;
            }
        saveOptionCookie(opt.id);
        changeOption(opt.id);
        }
    return(true);
}

// React to an option having been changed
function changeOption(name)
{
    switch(name)
        {
        case "chkRegExp":
            break;
        case "chkCaseSens":
            break;
        case "txtUserName":
            break;
        }
}


// ---------------------------------------------------------------------------------
// Event handlers
// ---------------------------------------------------------------------------------

function onEditKey(e)
{
    if (!e) var e = window.event;
    clearMessage();
    var consume = false;
    switch(e.keyCode)
        {
        case 13: // Ctrl-Enter
        case 10: // Ctrl-Enter on IE PC
        case 77: // Ctrl-Enter is "M" on some platforms
            if(e.ctrlKey && this.id && this.id.substr(0,6) == "editor")
                {
                saveTiddler(this.id.substr(6));
                consume = true;
                }
            break;
        case 27: // Escape
            if(this.id && this.id.substr(0,6) == "editor")
                {
                displayTiddler(null,this.id.substr(6),1,null,null,false);
                consume = true;
                }
            break;
        }
    e.cancelBubble = consume;
    if(consume)
        if (e.stopPropagation) e.stopPropagation();
    return(!consume);

}

// Event handler for clicking on a tiddly link
function onClickTiddlerLink(e)
{
    if (!e) var e = window.event;
    var theTarget = resolveTarget(e);
    var theLink = theTarget;
    var title = null;
    do {
        title = theLink.getAttribute("tiddlyLink");
        theLink = theLink.parentNode;
    } while(title == null && theLink != null);
    if(title)
        displayTiddler(theTarget,title,0,null,null,e.shiftKey || e.altKey);
    clearMessage();
}

// Event handler for mouse over a tiddler
function onMouseOverTiddler(e)
{
    var tiddler;
    if(this.id.substr(0,7) == "tiddler")
        tiddler = this.id.substr(7);
    if(tiddler)
        selectTiddler(tiddler);
}

// Event handler for mouse out of a tiddler
function onMouseOutTiddler(e)
{
    var tiddler;
    if(this.id.substr(0,7) == "tiddler")
        tiddler = this.id.substr(7);
    if(tiddler)
        deselectTiddler(tiddler);
}

// Event handler for double click on a tiddler
function onDblClickTiddler(e)
{
    clearMessage();
    if(document.selection)
        document.selection.empty();
    var tiddler;
    if(this.id.substr(0,7) == "tiddler")
        tiddler = this.id.substr(7);
    if(tiddler)
        displayTiddler(null,tiddler,2,null,null,false);
}

// Event handler for clicking on toolbar close
function onClickToolbarClose(e)
{
    if (!e) var e = window.event;
    clearMessage();
    if(this.parentNode.id)
        closeTiddler(this.parentNode.id.substr(7),e.shiftKey || e.altKey);
    e.cancelBubble = true;
    if (e.stopPropagation) e.stopPropagation();
    return(false);
}

// Event handler for clicking on toolbar permalink
function onClickToolbarPermaLink(e)
{
    if(this.parentNode.id)
        {
        var title = this.parentNode.id.substr(7);
        var t = encodeURIComponent(encodeTiddlyLink(title));
        if(window.location.hash != t)
            window.location.hash = t;
        }
}

// Event handler for clicking on toolbar close
function onClickToolbarDelete(e)
{
    clearMessage();
    if(this.parentNode.id)
        deleteTiddler(this.parentNode.id.substr(7));
}

// Event handler for clicking on the toolbar backlink
function onClickToolbarBackLink(e)
{
    clearMessage();
    if(this.parentNode.id)
        searchTiddlers(this.parentNode.id.substr(7),true,false);
}

// Event handler for clicking on toolbar close
function onClickToolbarEdit(e)
{
    clearMessage();
    if(this.parentNode.id)
        displayTiddler(null,this.parentNode.id.substr(7),2,null,null,false);
}

// Event handler for clicking on toolbar save
function onClickToolbarSave(e)
{
    if(this.parentNode.id)
        saveTiddler(this.parentNode.id.substr(7));
}

// Event handler for clicking on toolbar save
function onClickToolbarUndo(e)
{
    if(this.parentNode.id)
        displayTiddler(null,this.parentNode.id.substr(7),1,null,null,false);
}

var optionsOpen = false;

// Options button
function onClickOptions(e)
{
    if (!e) var e = window.event;
    var thePanel = document.getElementById("optionsPanel");
    optionsOpen = !optionsOpen;
    if(options.chkAnimate)
        startSlider(thePanel,optionsOpen,e.shiftKey || e.altKey,"none");
    else
        thePanel.style.display = optionsOpen ? "block" : "none";
    e.cancelBubble = true;
    if (e.stopPropagation) e.stopPropagation();
    return(false);
}

// Event handler for clicking on a tab
function onClickTab(e)
{
    if (!e) var e = window.event;
    var theTab = resolveTarget(e);
    currentTab = theTab.id;
    refreshSidebar();
}

var lastSearchText = ""; // For keeping track of when the search text actually changes

function onSearch(e)
{
    var text = document.getElementById("searchText").value;
    if((text.length > 2) && (text != lastSearchText))
        {
        searchTiddlers(text,options.chkCaseSens,options.chkRegExp);
        lastSearchText = text;
        }
}

function onClearSearch()
{
    var text = document.getElementById("searchText");
    text.value = "";
    clearMessage();
}

// Eek... it's bad that this is done via a function rather than a normal, copy-able href
function onClickPermaView()
{
    var wikiRegExp = new RegExp("^" + wikiNamePattern + "$","");
    var tiddlerDisplay = document.getElementById("tiddlerDisplay");
    var links = new Array();
    for(var t=0;t<tiddlerDisplay.childNodes.length;t++)
        {
        var tiddlerName = tiddlerDisplay.childNodes[t].id.substr(7);
        links.push(encodeTiddlyLink(tiddlerName));
        }
    window.location.hash = encodeURIComponent(links.join(" "));
}

// ---------------------------------------------------------------------------------
// Animation engine
// ---------------------------------------------------------------------------------

// Animation housekeeping
var animating = 0; // Incremented at start of each animation, decremented afterwards. If zero, the interval timer is disabled
var animaterID; // ID of the timer used for animating
// 'zoomer' module of the animation engine smoothly moves an element from the position/size of the start element to the target element
var zoomerElement = null; // Element being shifted; null if none
var zoomerStart; // Where we're shifting from
var zoomerTarget; // Where we're shifting to
var zoomerStartScroll; // Where we're scrolling from
var zoomerTargetScroll; // Where we're scrolling to
var zoomerProgress; // 0..1 of how far we are
var zoomerStep; // 0..1 of how much to shift each step
// 'slider' module of the animation engine slides an element smoothly open or closed
var sliderElement = null; // Element being slid open or closed; null if none
var sliderDeleteMode;
var sliderOpening; // True if the element is being opened
var sliderRealHeight; // Starting height
var sliderProgress; // 0..1 of how far we are
var sliderStep; // 0..1 of how much to shift each step

// Start animation engine
function startAnimating()
{
    if(animating++ == 0)
        animaterID = window.setInterval("doAnimate();",25);
}

// Stop animation engine
function stopAnimating()
{
    if(--animating == 0)
        window.clearInterval(animaterID)
}

// Perform an animation engine tick, calling each of the known animation modules
function doAnimate()
{
    if(zoomerElement)
        doZoomer();
    if(sliderElement)
        doSlider();
}

// Start moving the element 'e' from the position of the element 'start' to the position of the element 'target'
function startZoomer(e,start,target,slowly)
{
    stopZoomer();
    zoomerElement = e;
    zoomerStart = start;
    zoomerStartScroll = findScrollY();
    zoomerTargetScroll = ensureVisible(target);
    zoomerTarget = target;
    zoomerProgress = 0;
    zoomerStep = slowly ? 0.01 : 0.12;
    startAnimating();
}

// Stop any ongoing zoomer animation
function stopZoomer()
{
    if(zoomerElement)
        {
        stopAnimating();
        zoomerElement.style.display = "none";
        zoomerTarget.style.opacity = 1;
        window.scrollTo(0,zoomerTargetScroll);
        zoomerElement = null;
        }
}

// Perform a tick of the zoomer animation
function doZoomer()
{
    zoomerProgress += zoomerStep;
    if(zoomerProgress >= 1.0)
        stopZoomer();
    else
        {
        var f = slowInSlowOut(zoomerProgress);
        var zoomerStartLeft = findPosX(zoomerStart);
        var zoomerStartTop = findPosY(zoomerStart);
        var zoomerStartWidth = zoomerStart.offsetWidth;
        var zoomerStartHeight = zoomerStart.offsetHeight;
        var zoomerTargetLeft = findPosX(zoomerTarget);
        var zoomerTargetTop = findPosY(zoomerTarget);
        var zoomerTargetWidth = zoomerTarget.offsetWidth;
        var zoomerTargetHeight = zoomerTarget.offsetHeight;
        zoomerElement.style.left = zoomerStartLeft + (zoomerTargetLeft-zoomerStartLeft) * f;
        zoomerElement.style.top = zoomerStartTop + (zoomerTargetTop-zoomerStartTop) * f;
        zoomerElement.style.width = zoomerStartWidth + (zoomerTargetWidth-zoomerStartWidth) * f;
        zoomerElement.style.height = zoomerStartHeight + (zoomerTargetHeight-zoomerStartHeight) * f;
        zoomerElement.style.display = "block";
        zoomerTarget.style.opacity = zoomerProgress;
        window.scrollTo(0,zoomerStartScroll + (zoomerTargetScroll-zoomerStartScroll) * f);
        }
}

// Start sliding an element
// deleteMode - "none", "all" [delete target element and it's children], [only] "children" [but not the target element]
function startSlider(e,opening,slowly,deleteMode)
{
    stopSlider();
    sliderDeleteMode = deleteMode;
    sliderElement = e;
        sliderElement.style.display = "block";
    sliderElement.style.height = "auto";
    sliderRealHeight = sliderElement.offsetHeight;
    sliderOpening = opening;
    sliderStep = slowly ? 0.01 : 0.16;
    if(opening)
        {
        sliderProgress = 0;
        sliderElement.style.height = "2px";
        sliderElement.style.display = "block";
        }
    else
        {
        sliderStep = -sliderStep;
        sliderProgress = 1;
        }
    sliderElement.style.overflow = "hidden";
    startAnimating();
}

// Stop sliding the current element
function stopSlider()
{
    if(sliderElement)
        {
        stopAnimating();
        if(sliderOpening)
            sliderElement.style.height = "auto";
        else
            {
            switch(sliderDeleteMode)
                {
                case "none":
                    sliderElement.style.display = "none";
                    break;
                case "all":
                    sliderElement.parentNode.removeChild(sliderElement);
                    break;
                case "children":
                    while(sliderElement.hasChildNodes())
                        sliderElement.removeChild(sliderElement.firstChild);
                    break;
                }
            }
        sliderElement = null;
        }
}

// Perform a tick of the slider animation
function doSlider()
{
    sliderProgress += sliderStep;
    if((sliderProgress < 0) || (sliderProgress > 1.0))
        stopSlider();
    else
        {
        var f = slowInSlowOut(sliderProgress);
        var h = sliderRealHeight*f;
        sliderElement.style.height = (h <= 2) ? 2 : h;
        sliderElement.style.opacity = f;
        }
}


// ---------------------------------------------------------------------------------
// Standalone utility functions
// ---------------------------------------------------------------------------------

// Escape a string to ensure it doesn't have any RegExp special characters
function escapeRegExp(s)
{
    // Escape any special characters with that character preceded by a backslash
    return(s.replace(new RegExp("[\\\\\\^\\$\\*\\+\\?\\(\\)\\=\\!\\|\\,\\{\\}\\[\\]\\.]","g"),"\\$0"));
}

// Return a date in UTC YYYYMMDDHHMM format
function ConvertToYYYYMMDDHHMM(d)
{
    return(zeroPad(d.getFullYear(),4) + zeroPad(d.getMonth()+1,2) + zeroPad(d.getDate(),2) + zeroPad(d.getHours(),2) + zeroPad(d.getMinutes(),2));
}

// Left-pad a string with 0s to a certain width
function zeroPad(n,d)
{
    var s = n.toString();
    if(s.length < d)
        s = "000000000000000000000000000".substr(0,d-s.length) + s;
    return(s);
}

// Return a date in UTC YYYYMMDD.HHMMSSMMM format
function ConvertToYYYYMMDDHHMMSSMMM(d)
{
    return(zeroPad(d.getFullYear(),4) + zeroPad(d.getMonth()+1,2) + zeroPad(d.getDate(),2) + "." + zeroPad(d.getHours(),2) + zeroPad(d.getMinutes(),2) + zeroPad(d.getSeconds(),2) + zeroPad(d.getMilliseconds(),4));
}

// Convert a date in UTC YYYYMMDDHHMM format to date type
function ConvertFromYYYYMMDDHHMM(d)
{
    var theDate = new Date(parseInt(d.substr(0,4),10),
                            parseInt(d.substr(4,2),10)-1,
                            parseInt(d.substr(6,2),10),
                            parseInt(d.substr(8,2),10),
                            parseInt(d.substr(10,2),10),0,0);
    return(theDate);
}

// Map a 0..1 value to 0..1, but slow down at the start and end
function slowInSlowOut(progress)
{
    return(1-((Math.cos(progress * Math.PI)+1)/2));
}

// Resolve the target object of an event
function resolveTarget(e)
{
    var obj;
    if (e.target)
        obj = e.target;
    else if (e.srcElement)
        obj = e.srcElement;
    if (obj.nodeType == 3) // defeat Safari bug
        obj = obj.parentNode;
    return(obj);
}

// Get the scroll position for window.scrollTo necessary to scroll a given element into view
function ensureVisible(e)
{
    var posTop = findPosY(e);
    var posBot = posTop + e.offsetHeight;
    var winTop = findScrollY();
    var winHeight = findWindowHeight();
    var winBot = winTop + winHeight;
    if(posTop < winTop)
        return(posTop);
    else if(posBot > winBot)
        {
        if(e.offsetHeight < winHeight)
            return(posTop - (winHeight - e.offsetHeight));
        else
            return(posTop);
        }
    else
        return(winTop);
}

function findWindowHeight()
{
    return(window.innerHeight ? window.innerHeight : document.body.clientHeight);
}

function findScrollY()
{
    return(window.scrollY ? window.scrollY : document.body.scrollTop);
}

// From QuirksMode.com
function findPosX(obj)
{
    var curleft = 0;
    if (obj.offsetParent)
    {
        while (obj.offsetParent)
        {
            curleft += obj.offsetLeft;
            obj = obj.offsetParent;
        }
    }
    else if (obj.x)
        curleft += obj.x;
    return curleft;
}

// From QuirksMode.com
function findPosY(obj)
{
    var curtop = 0;
    if (obj.offsetParent)
    {
        while (obj.offsetParent)
        {
            curtop += obj.offsetTop;
            obj = obj.offsetParent;
        }
    }
    else if (obj.y)
        curtop += obj.y;
    return curtop;
}

// Create a non-breaking space
function insertSpacer(place)
{
    place.appendChild(document.createTextNode(String.fromCharCode(160)));
}

function removeChildren(e)
{
    while(e.hasChildNodes())
        e.removeChild(e.firstChild);
}


// Add a stylesheet
function addStylesheet(s)
{
    try
        {
        if(document.createStyleSheet)
            { 
            document.createStyleSheet("javascript:'" + escape(s) + "'"); 
            }
        else
            { 
            var n = document.createElement("link"); 
            n.rel = "stylesheet"; 
            n.href = "data:text/css," + escape(s); 
            document.getElementsByTagName("head")[0].appendChild(n);
            }
        }
    catch(e)
        {
        clearMessage();
        displayMessage("Error in StyleSheet: " + e.toString());
        }
}


// UTF-8 encoding rules:
// 0x0000 - 0x007F:	0xxxxxxx
// 0x0080 - 0x07FF:	110xxxxx 10xxxxxx
// 0x0800 - 0xFFFF:	1110xxxx 10xxxxxx 10xxxxxx

function convertUTF8ToUnicode(u)
{
    var s = "";
    var t = 0;
    var b1, b2, b3;
    while(t < u.length)
        {
        b1 = u.charCodeAt(t++);
        if(b1 < 0x80)
            s += String.fromCharCode(b1);
        else if(b1 < 0xE0)
            {
            b2 = u.charCodeAt(t++);
            s += String.fromCharCode(((b1 & 0x1F) << 6) | (b2 & 0x3F));
            }
        else
            {
            b2 = u.charCodeAt(t++);
            b3 = u.charCodeAt(t++);
            s += String.fromCharCode(((b1 & 0xF) << 12) | ((b2 & 0x3F) << 6) | (b3 & 0x3F));
            }
    }
    return(s);
}

function allTiddlersAsHtml()
{
    var savedTiddlers = new Array();
    var tiddlers = getTiddlers(-1,"date");
    for (var t = 0; t < tiddlers.length; t++)
        savedTiddlers.push("<div id=\"store" + tiddlers[t][0] + "\" modified=\"" +
                            tiddlers[t][1] + "\" modifier=\"" + tiddlers[t][2] + "\">" +
                            htmlEncode(escapeTiddler(getTiddlerText(tiddlers[t][0]))) + "</div>");
    return savedTiddlers.join(" ");
}

function convertUnicodeToUTF8(s)
{
    var u = "";
    for(var t=0;t<s.length;t++)
        {
        var c = s.charCodeAt(t);
        if(c <= 0x7F) 
            u += String.fromCharCode(c);
        else if(c <= 0x7FF)
            {
            u += String.fromCharCode((c >> 6) | 0xC0);
            u += String.fromCharCode((c & 0x3F) | 0x80);
            }
        else
            {
            u += String.fromCharCode((c >> 12) | 0xE0);
            u += String.fromCharCode(((c >> 6) & 0x3F) | 0x80);
            u += String.fromCharCode((c & 0x3F) | 0x80);
            }
        }
    return(u);
}

function generateRss()
{
    var s = new Array();
    s.p = function(t) {this.push(t);};
    var d = new Date();
    var u = getTiddlerText("SiteUrl");   
    var sitetitle=getTiddlerText("SiteTitle");    
    
    // Assemble the header
    s.p("<?xml version=\"1.0\" encoding=\"iso-8859-15\" ?>");
    s.p("<rss version=\"2.0\">");
    s.p("<channel>");
    
	s.p("<title>" + sitetitle + "</title>");
    s.p("<link>" + u + "</link>");
	s.p("<description>" + sitetitle + "</description>");
	s.p("<language>en-us</language>");
	s.p("<copyright>Copyright " + d.getFullYear() + " " + htmlEncode(options.txtUserName) + "</copyright>");
	s.p("<pubDate>" + d.toGMTString() + "</pubDate>");
	s.p("<lastBuildDate>" + d.toGMTString() + "</lastBuildDate>");
	s.p("<docs>http://blogs.law.harvard.edu/tech/rss</docs>");
	s.p("<generator>TiddlyWiki</generator>");
    // The body
    var tiddlers = getTiddlers(numRssItems,"date");
    for (var t = 0; t < tiddlers.length; t++)
        {
        s.p("<item>");
        s.p("<title>" + htmlEncode(tiddlers[t][0]) + "</title>");
        s.p("<description>" + htmlEncode(escapeTiddler(getTiddlerText(tiddlers[t][0]))) + "</description>");
        s.p("<link>" + u + "#" + encodeURI(encodeTiddlyLink(tiddlers[t][0])) + "</link>");
        s.p("<pubDate>" + ConvertFromYYYYMMDDHHMM(tiddlers[t][1]).toGMTString() + "</pubDate>");
        s.p("</item>");
        }
    // And footer
    s.p("</channel>");
	s.p("</rss>");
    // Save it all
    return s.join("\n");
}

// ---------------------------------------------------------------------------------
// End of scripts
// ---------------------------------------------------------------------------------


function isTiddlerOk(n) {
  return  (n.id != "storeSiteTitle"  && n.id != "storeSiteSubtitle"  &&  
           n.id != "storeMainMenu"  &&    n.id != "storeDefaultTiddlers"); 	
}

function makePermaView() {
	 var tiddlerDisplay = document.getElementById("tiddlerDisplay");
	 var links = new Array();
	 for(var t=0;t<tiddlerDisplay.childNodes.length;t++)
	 {
		 if (tiddlerDisplay.childNodes[t].id) {
		  var tiddlerName = tiddlerDisplay.childNodes[t].id.substr(7);
		  links.push(encodeTiddlyLink(tiddlerName));
		 }
        }
	return encodeURI(links.join(" "));       
}

function mySaveAll(hash) {
	
      // Jam in the current source
      var theTextBox = document.getElementById("datatext");      
      theTextBox.value = allTiddlersAsHtml();
      //window.document.getElementById("storeArea").innerHTML;
      
      //generate the RSS      
      var theRssTextBox = document.getElementById("rsstext");      
      theRssTextBox.value = generateRss();      
      
      theForm = document.getElementById("dataform");      
      theForm.action = "#"+hash;      
      theForm.submit();
}

function saveAll() {      
     var hash=makePermaView();
     mySaveAll(hash);
}

function newTitle() {
      //get the name
      var newTitleName = document.getElementById("newtitle_name").value;
      if (!newTitleName) newTitleName="[["+conf["newtext"]+"]]";
      displayTiddlers(null,newTitleName,1,null,null);    
}
