Click here to Skip to main content
15,891,529 members
Articles / Web Development / ASP.NET

OpenCollective -- The Requirements Management Wiki

Rate me:
Please Sign up or sign in to vote.
4.41/5 (16 votes)
9 Nov 20044 min read 262.2K   1.8K   111  
An article on building a project oriented wiki for software development requirements management
/*
OpenCollective -  http://www.netbrick.net/ -- Version 0.73
Copyright (c) 2004
by Tyler Jensen ( tylerj@netbrick.net ) of NetBrick Inc. ( http://www.netbrick.net )

Permission is hereby granted, free of charge, to any person obtaining a copy 
of this software and associated documentation files (the "Software"), to deal 
in the Software without restriction, including without limitation the rights 
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 
of the Software, and to permit persons to whom the Software is furnished to do 
so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all 
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 
INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 
PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 
OR OTHER DEALINGS IN THE SOFTWARE.
*/
using System;
using System.Collections;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Web;
using System.Web.SessionState;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;
using OpenCollective.Data;
using OpenCollective.Engine;
using OpenCollective.Diff;
using OpenCollective.Edit;
using FreeTextBoxControls;

namespace OpenCollective.Web
{
	/// <summary>
	/// Summary description for wiki.
	/// </summary>
	public class wiki : System.Web.UI.Page
	{
		protected System.Web.UI.HtmlControls.HtmlGenericControl pgBody;
		protected System.Web.UI.HtmlControls.HtmlForm Form1;
		protected System.Web.UI.WebControls.Literal pageTitle;

		protected System.Web.UI.WebControls.Literal litPreImg;
		protected System.Web.UI.WebControls.Literal litPostImg;

		protected System.Web.UI.WebControls.Label lblWiki;
		protected System.Web.UI.WebControls.Label lblTopic;
		protected System.Web.UI.WebControls.HyperLink lnkText;
		protected System.Web.UI.WebControls.HyperLink lnkDiscuss;
		protected System.Web.UI.WebControls.HyperLink lnkEdit;
		protected System.Web.UI.WebControls.HyperLink lnkDiff;
		protected System.Web.UI.WebControls.HyperLink lnkHistory;
		protected System.Web.UI.WebControls.HyperLink lnkTrace;
		protected System.Web.UI.WebControls.HyperLink lnkLike;
		protected System.Web.UI.WebControls.HyperLink lnkWatch;
		protected System.Web.UI.WebControls.HyperLink lnkRecent;
		protected System.Web.UI.WebControls.HyperLink lnkProjects;
		protected System.Web.UI.WebControls.HyperLink lnkFiles;
		protected System.Web.UI.WebControls.HyperLink lnkIndex;
		protected System.Web.UI.WebControls.TextBox txtSearch;
		protected System.Web.UI.WebControls.ImageButton btnSearch;
		protected System.Web.UI.HtmlControls.HtmlImage imgLogo;
		protected System.Web.UI.HtmlControls.HtmlImage mnuBtn;
		protected System.Web.UI.WebControls.Literal litMenu;
		protected System.Web.UI.HtmlControls.HtmlGenericControl mnuPanel;
		protected System.Web.UI.HtmlControls.HtmlGenericControl styleLink;
		
		//edit controls
		protected System.Web.UI.WebControls.Literal litJavascript;
		protected System.Web.UI.WebControls.Literal litBody;
		protected System.Web.UI.HtmlControls.HtmlGenericControl divFiles;
		protected System.Web.UI.HtmlControls.HtmlGenericControl divEditButtons;
		protected System.Web.UI.WebControls.Button btnLock;
		protected System.Web.UI.WebControls.Button btnDelete;
		protected System.Web.UI.WebControls.Literal litEditMsg;
		//protected System.Web.UI.WebControls.TextBox txtBody;
		protected System.Web.UI.WebControls.TextBox txtSummary;
		protected System.Web.UI.WebControls.CheckBox cbIsMinor;
		protected System.Web.UI.WebControls.Literal litNote;
		protected System.Web.UI.WebControls.Button btnEditSave;
		protected System.Web.UI.WebControls.Button btnEditCancel;
		protected System.Web.UI.WebControls.Button btnEditPreview;
		//protected System.Web.UI.WebControls.DropDownList fileList;
		//protected System.Web.UI.WebControls.DropDownList imgList;
		protected FreeTextBoxControls.FreeTextBox FreeTextBox1;
		protected System.Web.UI.HtmlControls.HtmlGenericControl divEdit;
		//protected System.Web.UI.HtmlControls.HtmlGenericControl divWikiEdit;
		protected System.Web.UI.HtmlControls.HtmlGenericControl divFreeText;
		//protected System.Web.UI.WebControls.Button btnEditToggle;

		//file post controls
		protected System.Web.UI.WebControls.Button btnUpload;
		protected System.Web.UI.WebControls.Label lblMsg;
		protected System.Web.UI.WebControls.Label lblSummary;
		protected System.Web.UI.WebControls.DataList DataList1;
		protected System.Web.UI.HtmlControls.HtmlInputFile postFile;
		protected System.Web.UI.WebControls.LinkButton lnkRename;
		protected System.Web.UI.WebControls.LinkButton lnkDelete;
		protected System.Web.UI.WebControls.HyperLink lnkFileName;
		protected System.Web.UI.WebControls.Label lblDate;
		protected System.Web.UI.WebControls.Label lblTime;
		protected System.Web.UI.WebControls.Label lblSize;
		protected System.Web.UI.WebControls.Label lblUser;
		protected System.Web.UI.WebControls.LinkButton btnRename;
		protected System.Web.UI.WebControls.LinkButton btnCancel;
		protected System.Web.UI.WebControls.Label lblFileName;
		protected System.Web.UI.WebControls.Label lblRenameMsg;
		protected System.Web.UI.WebControls.TextBox txtNewFileName;

		//history controls
		protected System.Web.UI.WebControls.Literal litHistory;
		protected System.Web.UI.WebControls.Button btnCompare;
		protected System.Web.UI.HtmlControls.HtmlGenericControl divHistory;

		//watch controls
		protected System.Web.UI.HtmlControls.HtmlGenericControl divWatch;
		protected System.Web.UI.WebControls.Literal litWatchAddress;
		protected System.Web.UI.WebControls.TextBox txtWatchAddress;
		protected System.Web.UI.WebControls.Button btnUpdateWatchAddress;
		protected System.Web.UI.WebControls.DataList DataList2;
		protected System.Web.UI.WebControls.Label lblProjectWatch;
		protected System.Web.UI.WebControls.HyperLink lnkTopicWatch;
		protected System.Web.UI.WebControls.Label lblDateWatch;
		protected System.Web.UI.WebControls.HyperLink lnkAuthorWatch;
		protected System.Web.UI.WebControls.LinkButton btnToggleWatch;
		protected System.Web.UI.WebControls.Button btnWatchThis;

		//recent topic controls
		protected System.Web.UI.HtmlControls.HtmlGenericControl divRecent;
		protected System.Web.UI.WebControls.TextBox txtRecentDays;
		protected System.Web.UI.WebControls.Button btnRecent;
		protected System.Web.UI.WebControls.Button btnRecentAll;

		private string renameErrMsg = "";
		private string userName		= "";
		private string appUrlPath	= "";
		private string reqUrl		= "";
		private string pageCommand = "view"; //default is view
		private string commandParam = "";
		private string topicName	= "";
		private string wikiName		= "";
		private string watchWiki   = "";


		private void Page_Load(object sender, System.EventArgs e)
		{
			appUrlPath = Request.ApplicationPath.ToLower();
			if(Request.QueryString["url"] == null) //push back if not from wiki page
				Response.Redirect(appUrlPath + "/wiki/" + Config.DefaultWikiName + "/" + Config.HomePageName + ".wiki");

			PageSetup();

			if(reqUrl.IndexOf("/wiki/") < 0)
				Response.Redirect(appUrlPath + "/wiki/" + Config.DefaultWikiName + "/" + Config.HomePageName + ".wiki");

			ProcessPage();
		}

		#region Web Form Designer generated code
		override protected void OnInit(EventArgs e)
		{
			//
			// CODEGEN: This call is required by the ASP.NET Web Form Designer.
			//
			InitializeComponent();
			base.OnInit(e);
		}
		
		/// <summary>
		/// Required method for Designer support - do not modify
		/// the contents of this method with the code editor.
		/// </summary>
		private void InitializeComponent()
		{    
			this.btnSearch.Click += new System.Web.UI.ImageClickEventHandler(this.btnSearch_Click);
			this.btnCompare.Click += new System.EventHandler(this.btnCompare_Click);
			this.btnEditSave.Click += new System.EventHandler(this.btnEditSave_Click);
			this.btnEditCancel.Click += new System.EventHandler(this.btnEditCancel_Click);
			this.btnEditPreview.Click += new System.EventHandler(this.btnEditPreview_Click);
			this.btnLock.Click += new System.EventHandler(this.btnLock_Click);
			this.btnDelete.Click += new EventHandler(btnDelete_Click);
			this.btnWatchThis.Click += new System.EventHandler(this.btnWatchThis_Click);
			this.btnUpdateWatchAddress.Click += new System.EventHandler(this.btnUpdateWatchAddress_Click);
			this.DataList2.ItemCommand += new System.Web.UI.WebControls.DataListCommandEventHandler(this.DataList2_ItemCommand);
			this.DataList2.ItemDataBound += new System.Web.UI.WebControls.DataListItemEventHandler(this.DataList2_ItemDataBound);
			this.btnRecent.Click += new System.EventHandler(this.btnRecent_Click);
			this.btnRecentAll.Click += new System.EventHandler(this.btnRecentAll_Click);
			this.btnUpload.Click += new System.EventHandler(this.btnUpload_Click);
			this.DataList1.ItemCommand += new System.Web.UI.WebControls.DataListCommandEventHandler(this.DataList1_ItemCommand);
			this.DataList1.ItemDataBound += new System.Web.UI.WebControls.DataListItemEventHandler(this.DataList1_ItemDataBound);
			this.Load += new System.EventHandler(this.Page_Load);

		}
		#endregion

		private void ProcessPage()
		{
			SetupBackMenu();
			SetupTabs();

			//clear content elements
			this.HideFileList();
			this.HideEdit();
			this.HideHistory();
			this.HideWatch();
			this.HideRecent();
			litBody.Text = "";

			switch(pageCommand)
			{
				case "view":
					ShowViewTopic();
					break;
				case "talk":
					ShowTalk();
					break;
				case "edit":
				case "talkedit":
					ShowEdit();
					break;
				case "diff":
				case "talkdiff":
					ShowDiff();
					break;
				case "history":
				case "talkhistory":
					ShowHistory();
					break;
				case "trace":
					ShowTrace();
					break;
				//case "like":
				//	break;
				case "watch":
					ShowWatch();
					break;
				case "recent":
				case "recentall":
					ShowRecent();
					break;
				case "projects":
					ShowProjects();
					break;
				case "index":
					ShowIndex();
					break;
				case "files":
					FillFileList();
					break;
				case "search":
					ShowSearch();
					break;
				case "old":
				case "oldtalk":
					ShowOld();
					break;
			}


		}

		#region Setting Up the Page

		private void PageSetup()
		{
			userName = GetUserName();
			imgLogo.Src = appUrlPath + "/images/logo.gif";
			mnuBtn.Src  = appUrlPath + "/images/back.gif";
			
			litPreImg.Text = "<a href=\"" + appUrlPath + "/wiki/" + Config.DefaultWikiName + "/" + Config.HomePageName + ".wiki\" title=\"Return to home page.\" border=\"0\">";
			litPostImg.Text = "</a>";

			styleLink.Attributes["href"] = appUrlPath + "/wiki.css";

			btnSearch.ImageUrl = appUrlPath + "/images/gow.gif";
			reqUrl = Request.QueryString["url"];

			ParseUrl();
			lblWiki.Text = wikiName;
			lblTopic.Text = topicName;
			if(pageCommand.StartsWith("talk"))
				lblTopic.Text = "Talk: " + topicName;
			pageTitle.Text = lblWiki.Text + ": " + lblTopic.Text;

			//string origQry = (commandParam == "" && pageCommand != "view") ? "?" + pageCommand;
			string origQry = "";
			if(pageCommand != "view")
			{
				if(commandParam.Length == 0)
					origQry = "?" + pageCommand;
				else
					origQry = "?" + pageCommand + "=" + commandParam;
			}
			string origPage = appUrlPath + "/wiki/" + wikiName + "/" + topicName + ".wiki" + origQry;
			pgBody.Attributes["onload"] = "SetAction('" + reqUrl + "');";	 //remap the action tag to the original qry string to preserve navigation
			pgBody.Attributes["onclick"] = "return HideMenu();";
		}

		private string GetUserName()
		{
			string uname = User.Identity.Name;
			if(uname.Length == 0)
				throw new Exception("Anonymous access not allowed.");
			if(uname.IndexOf("\\") > 0) //remove domain string
			{
				int pos = uname.IndexOf("\\");
				uname = uname.Substring(pos + 1, uname.Length - pos - 1);
			}
			return uname;
		}

		private void ParseUrl()
		{
			//												 1			 2					 3		 4
			// reqUrl = /OpenCollective.Web/wiki/history/Main%20Page.wiki?command=58
			// 1 wikiName = ""
			// 2 topicName = Main Page
			// 3 pageCommand = "command"; //default is view
			// 4 commandParam = 58

			int lastSlash = reqUrl.LastIndexOf("/");
			string path = reqUrl.Substring(0, lastSlash);
			string pageQry = reqUrl.Substring(lastSlash + 1, reqUrl.Length - lastSlash - 1);
			
			topicName = Server.UrlDecode(pageQry.Substring(0, pageQry.Length - (pageQry.Length - pageQry.IndexOf(".wiki"))));

			lastSlash = path.LastIndexOf("/wiki/");
			wikiName  = Server.UrlDecode(path.Substring(lastSlash + 6, path.Length - lastSlash - 6));

			if(pageQry.IndexOf("?") > 0)
			{
				string[] prms = pageQry.Split('?');
				if(prms.Length > 1)
				{
					if(prms[1].IndexOf("=") > 0)
					{
						string[] pairs = prms[1].Split('=');
						pageCommand = pairs[0];
						commandParam = pairs[1];
					}
					else
						pageCommand = prms[1];
				}
			}

		}

		private void SetupBackMenu()
		{
			//Request.Cookies["t0"].Value
			string[] t = new string[10];
			if(Request.Cookies["t"] != null)
				t = Request.Cookies["t"].Value.Split('|');
			else
			{
				for(int i = 0; i < 10; i++) t[i] = "";
			}

			string[] w = new string[10];
			if(Request.Cookies["w"] != null)
				w = Request.Cookies["w"].Value.Split('|');
			else
			{
				for(int i = 0; i < 10; i++) w[i] = "";
			}

			//does this document exist in the array
			bool isFound = false;
			for(int i = 0; i < 10; i++)
			{
				if(w[i] == this.wikiName && t[i] == this.topicName)
				{
					isFound = true;
					break;
				}
			}

			if(!isFound) //document does not exist in menu
			{
				if(this.wikiName != w[0] || this.topicName != t[0])
				{
					for(int i = 9; i > 0; --i)
					{
						w[i] = w[i-1];
						t[i] = t[i-1];
					}
					w[0] = this.wikiName;
					t[0] = this.topicName;
				}
			}

			//set viewstate on this response
			Response.Cookies.Clear();
			
			StringBuilder sbt = new StringBuilder(3);
			StringBuilder sbw = new StringBuilder(3);
			for(int z = 0; z < 9; z++)
			{
				sbt.Append(t[z] + "|");
				sbw.Append(w[z] + "|");
			}
			sbt.Append(t[9]);
			sbw.Append(w[9]);
			
			Response.Cookies.Add(new HttpCookie("w", sbw.ToString()));
			Response.Cookies.Add(new HttpCookie("t", sbt.ToString()));

			StringBuilder sb = new StringBuilder(3);
			for(int x = 0; x < 10; x++)
			{
				if(w[x].Length > 0)
					sb.Append("<div class=\"menuitem\"><a onclick=\"return HideMenu();\" href=\"" + appUrlPath 
						+ "/wiki/" + w[x] + "/" + t[x] + ".wiki\">" + w[x] + "/" + t[x] + "</a></div>\n");
			}
			litMenu.Text = sb.ToString();
		}

		private void SetupTabs()
		{
			if(!Page.IsPostBack) this.txtSearch.Text = "";
			//eliminate tabs for now
			lnkLike.Visible = false;

			string page = appUrlPath + "/wiki/" + Server.HtmlEncode(wikiName) + "/" + Server.HtmlEncode(topicName) + ".wiki";
			if(pageCommand.StartsWith("talk"))
			{
				//links while viewing talk/discussion
				lnkEdit.NavigateUrl			= page + "?talkedit";
				lnkDiff.NavigateUrl			= page + "?talkdiff";
				lnkHistory.NavigateUrl		= page + "?talkhistory";
				lnkEdit.Text					= "edit talk";
				lnkDiff.Text					= "diff talk";
				lnkHistory.Text				= "talk history";
			}
			else
			{
				//standard links
				lnkEdit.NavigateUrl			= page + "?edit";
				lnkDiff.NavigateUrl			= page + "?diff";
				lnkHistory.NavigateUrl		= page + "?history";
				lnkEdit.Text					= "edit";
				lnkDiff.Text					= "diff";
				lnkHistory.Text				= "history";
			}
			//always the same
			lnkText.NavigateUrl			= page;
			lnkDiscuss.NavigateUrl		= page + "?talk";
			lnkTrace.NavigateUrl			= page + "?trace";
			lnkLike.NavigateUrl			= page + "?like";
			lnkWatch.NavigateUrl			= page + "?watch";
			lnkRecent.NavigateUrl		= page + "?recent";
			lnkProjects.NavigateUrl		= page + "?projects";
			lnkFiles.NavigateUrl			= page + "?files";
			lnkIndex.NavigateUrl			= page + "?index";

			string classOff = "tboff";
			string classOn  = "tbon";

			lnkText.CssClass				= classOff;
			lnkDiscuss.CssClass			= classOff;
			lnkEdit.CssClass				= classOff;
			lnkDiff.CssClass				= classOff;
			lnkHistory.CssClass			= classOff;
			lnkTrace.CssClass				= classOff;
			lnkLike.CssClass				= classOff;
			lnkWatch.CssClass				= classOff;
			lnkRecent.CssClass			= classOff;
			lnkProjects.CssClass			= classOff;
			lnkFiles.CssClass				= classOff;
			lnkIndex.CssClass				= classOff;

			switch(pageCommand)
			{
				case "view":
					lnkText.CssClass				= classOn;
					break;
				case "talk":
					lnkDiscuss.CssClass			= classOn;
					break;
				case "edit":
				case "talkedit":
					lnkEdit.CssClass				= classOn;
					break;
				case "diff":
				case "talkdiff":
					lnkDiff.CssClass				= classOn;
					break;
				case "history":
				case "talkhistory":
					lnkHistory.CssClass			= classOn;
					break;
				case "trace":
					lnkTrace.CssClass				= classOn;
					break;
				case "like":
					lnkLike.CssClass				= classOn;
					break;
				case "watch":
					lnkWatch.CssClass				= classOn;
					break;
				case "recent":
					lnkRecent.CssClass			= classOn;
					break;
				case "projects":
					lnkProjects.CssClass			= classOn;
					break;
				case "index":
					lnkIndex.CssClass				= classOn;
					break;
				case "files":
					lnkFiles.CssClass				= classOn;
					break;
			}
		}
		#endregion

		#region Search Wiki
		private void btnSearch_Click(object sender, System.Web.UI.ImageClickEventArgs e)
		{
			// TODO - handle search
			string searchText = this.txtSearch.Text;
			//this.litBody.Text = WikiSearch.GetSearchResults(this.appUrlPath, this.wikiName, searchText);
			Response.Redirect(appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?search=" + Server.UrlEncode(searchText));
		}

		private void ShowSearch()
		{

			this.litBody.Text = WikiSearch.GetSearchResults(this.appUrlPath, this.wikiName, this.commandParam);
		}

		#endregion

		#region Index Handling

		private void ShowIndex()
		{
			DataSet ds = DataAccess.GetTopicListDataSet();
			if(ds.Tables[0] != null && ds.Tables[0].Rows.Count > 0)
			{
				StringBuilder sb = new StringBuilder(13);

				//make ALL - and A-Z links (bold where a project and )
				// if(this.commandParam == "") then show letter of current project
				
				string letter = this.commandParam.Length > 0 ? this.commandParam.ToLower() : this.wikiName.Substring(0,1).ToLower();
				int letterIdx = 27; //default other
				string[] idx = new string[28];
				idx[0] = "<a href=\"" + this.appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?index=all\" class=\"bodylink\">ALL</a>";
				idx[1] = "A";
				idx[2] = "B";
				idx[3] = "C";
				idx[4] = "D";
				idx[5] = "E";
				idx[6] = "F";
				idx[7] = "G";
				idx[8] = "H";
				idx[9] = "I";
				idx[10] = "J";
				idx[11] = "K";
				idx[12] = "L";
				idx[13] = "M";
				idx[14] = "N";
				idx[15] = "O";
				idx[16] = "P";
				idx[17] = "Q";
				idx[18] = "R";
				idx[19] = "S";
				idx[20] = "T";
				idx[21] = "U";
				idx[22] = "V";
				idx[23] = "W";
				idx[24] = "X";
				idx[25] = "Y";
				idx[26] = "Z";
				idx[27] = "Other";

				string begletter = letter.Substring(0,1);
				for(int i = 1; i < 27; i++)
				{
					if(begletter == idx[i].ToLower())
					{
						letterIdx = i;
						break;
					}
				}
				if(this.commandParam.ToLower() == "all")
					letterIdx = 0;

				string beg = "";
				foreach(DataRow row in ds.Tables[0].Rows)
				{
					string proj = row["WikiName"].ToString().ToLower().Substring(0, 1);
					if(beg != proj)
					{
						beg = proj;
						switch(proj)
						{
							case "a":
								idx[1] = "<a href=\"" + this.appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?index=a\" class=\"bodylink\">A</a>";
								break;
							case "b":
								idx[2] = "<a href=\"" + this.appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?index=b\" class=\"bodylink\">B</a>";
								break;
							case "c":
								idx[3] = "<a href=\"" + this.appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?index=c\" class=\"bodylink\">C</a>";
								break;
							case "d":
								idx[4] = "<a href=\"" + this.appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?index=d\" class=\"bodylink\">D</a>";
								break;
							case "e":
								idx[5] = "<a href=\"" + this.appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?index=e\" class=\"bodylink\">E</a>";
								break;
							case "f":
								idx[6] = "<a href=\"" + this.appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?index=f\" class=\"bodylink\">F</a>";
								break;
							case "g":
								idx[7] = "<a href=\"" + this.appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?index=g\" class=\"bodylink\">G</a>";
								break;
							case "h":
								idx[8] = "<a href=\"" + this.appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?index=h\" class=\"bodylink\">H</a>";
								break;
							case "i":
								idx[9] = "<a href=\"" + this.appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?index=i\" class=\"bodylink\">I</a>";
								break;
							case "j":
								idx[10] = "<a href=\"" + this.appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?index=j\" class=\"bodylink\">J</a>";
								break;
							case "k":
								idx[11] = "<a href=\"" + this.appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?index=k\" class=\"bodylink\">K</a>";
								break;
							case "l":
								idx[12] = "<a href=\"" + this.appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?index=l\" class=\"bodylink\">L</a>";
								break;
							case "m":
								idx[13] = "<a href=\"" + this.appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?index=m\" class=\"bodylink\">M</a>";
								break;
							case "n":
								idx[14] = "<a href=\"" + this.appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?index=n\" class=\"bodylink\">N</a>";
								break;
							case "o":
								idx[15] = "<a href=\"" + this.appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?index=o\" class=\"bodylink\">O</a>";
								break;
							case "p":
								idx[16] = "<a href=\"" + this.appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?index=p\" class=\"bodylink\">P</a>";
								break;
							case "q":
								idx[17] = "<a href=\"" + this.appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?index=q\" class=\"bodylink\">Q</a>";
								break;
							case "r":
								idx[18] = "<a href=\"" + this.appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?index=r\" class=\"bodylink\">R</a>";
								break;
							case "s":
								idx[19] = "<a href=\"" + this.appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?index=s\" class=\"bodylink\">S</a>";
								break;
							case "t":
								idx[20] = "<a href=\"" + this.appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?index=t\" class=\"bodylink\">T</a>";
								break;
							case "u":
								idx[21] = "<a href=\"" + this.appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?index=u\" class=\"bodylink\">U</a>";
								break;
							case "v":
								idx[22] = "<a href=\"" + this.appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?index=v\" class=\"bodylink\">V</a>";
								break;
							case "w":
								idx[23] = "<a href=\"" + this.appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?index=w\" class=\"bodylink\">W</a>";
								break;
							case "x":
								idx[24] = "<a href=\"" + this.appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?index=x\" class=\"bodylink\">X</a>";
								break;
							case "y":
								idx[25] = "<a href=\"" + this.appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?index=y\" class=\"bodylink\">Y</a>";
								break;
							case "z":
								idx[26] = "<a href=\"" + this.appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?index=z\" class=\"bodylink\">Z</a>";
								break;
							default:
								idx[27] = "<a href=\"" + this.appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?index=other\" class=\"bodylink\">Other</a>";
								break;
						}
					}
				}
				idx[letterIdx] = "<b>[" + idx[letterIdx].Replace("bodylink", "nolink") + "]</b>";

				sb.Append("<div>Index:&nbsp;");
				for(int i = 0; i < 27; i++)
				{
					sb.Append(idx[i] + "&nbsp;|&nbsp;");
				}
				sb.Append(idx[27] + "</div><hr>");
				
				//letterIdx = 0, all; letterIdx = 27, Other; letterIdx 1-26 (do that letter)
				sb.Append("<hr><table width=\"600\" align=\"center\" border=\"0\" cellpadding=\"3\" cellspacing=\"0\">\n");
				sb.Append("<tr><td class=\"diffhd\" valign=\"bottom\"><b>Project</b></td><td class=\"diffhd\">&nbsp;</td><td class=\"diffhd\" valign=\"bottom\"><b>Document</b></td></tr>\n");
				beg = "";
				string prevProj = "";
				string leftlink = "";
				string rightlink = "";
				foreach(DataRow row in ds.Tables[0].Rows)
				{
					string proj = row["WikiName"].ToString();
					string topic = row["TopicTitle"].ToString();
					if(letterIdx == 0 || IsInSelectedProjectIndex(proj, letterIdx))
					{
						leftlink = "";
						if(prevProj.ToLower() != proj.ToLower())
						{
							//do left link project
							leftlink = "<a href=\"" + this.appUrlPath + "/wiki/" + proj + "/" + Config.HomePageName + ".wiki\">" + proj + "</a>";
							prevProj = proj;
						}

						rightlink = "<a href=\"" + this.appUrlPath + "/wiki/" + proj + "/" + topic + ".wiki\">" + topic + "</a>";

						sb.Append("<tr><td valign=\"bottom\" width=\"280\">"+ leftlink +"</td>"
							+ "<td valign=\"bottom\" width=\"40\">&nbsp;</td>"
							+ "<td valign=\"bottom\" width=\"280\">" + rightlink + "</td></tr>\n");
					}
				}
				sb.Append("</table>");
				litBody.Text = sb.ToString();
			}
			else
				litBody.Text = "<div>No documents found.</div>";
		}

		private bool IsInSelectedProjectIndex(string project, int letterIdx)
		{
			bool retval = false;
			string beg = project.Substring(0,1).ToLower();
			switch(beg)
			{
				case "a":
					if(letterIdx == 1) retval = true;
					break;
				case "b":
					if(letterIdx == 2) retval = true;
					break;
				case "c":
					if(letterIdx == 3) retval = true;
					break;
				case "d":
					if(letterIdx == 4) retval = true;
					break;
				case "e":
					if(letterIdx == 5) retval = true;
					break;
				case "f":
					if(letterIdx == 6) retval = true;
					break;
				case "g":
					if(letterIdx == 7) retval = true;
					break;
				case "h":
					if(letterIdx == 8) retval = true;
					break;
				case "i":
					if(letterIdx == 9) retval = true;
					break;
				case "j":
					if(letterIdx == 10) retval = true;
					break;
				case "k":
					if(letterIdx == 11) retval = true;
					break;
				case "l":
					if(letterIdx == 12) retval = true;
					break;
				case "m":
					if(letterIdx == 13) retval = true;
					break;
				case "n":
					if(letterIdx == 14) retval = true;
					break;
				case "o":
					if(letterIdx == 15) retval = true;
					break;
				case "p":
					if(letterIdx == 16) retval = true;
					break;
				case "q":
					if(letterIdx == 17) retval = true;
					break;
				case "r":
					if(letterIdx == 18) retval = true;
					break;
				case "s":
					if(letterIdx == 19) retval = true;
					break;
				case "t":
					if(letterIdx == 20) retval = true;
					break;
				case "u":
					if(letterIdx == 21) retval = true;
					break;
				case "v":
					if(letterIdx == 22) retval = true;
					break;
				case "w":
					if(letterIdx == 23) retval = true;
					break;
				case "x":
					if(letterIdx == 24) retval = true;
					break;
				case "y":
					if(letterIdx == 25) retval = true;
					break;
				case "z":
					if(letterIdx == 26) retval = true;
					break;
				default:
					if(letterIdx == 27) retval = true;
					break;
			}
			return retval;
		}

		#endregion

		#region File Handling

		private void HideFileList()
		{
			divFiles.Visible = false;
		}

		private void FillFileList()
		{
			divFiles.Visible = true;
			DataSet ds = DataAccess.GetWikiFileNames(this.wikiName);
			DataList1.DataSource = ds.Tables[0];
			DataList1.DataBind();
		}

		private void DataList1_ItemDataBound(object sender, System.Web.UI.WebControls.DataListItemEventArgs e)
		{
			ListItemType ItemType = e.Item.ItemType;
			if( (ItemType == ListItemType.Item) || (ItemType == ListItemType.AlternatingItem) )
			{
				DataRowView row = (DataRowView)e.Item.DataItem;
				LinkButton rename = (LinkButton)e.Item.FindControl("lnkRename");
				if(rename != null)
				{
					rename.CommandArgument = row["FileName"].ToString();
					rename.CommandName = "rename";
				}

				LinkButton delete = (LinkButton)e.Item.FindControl("lnkDelete");
				if(delete != null)
				{
					delete.CommandArgument = row["FileName"].ToString();
					delete.CommandName = "delete";
				}

				HyperLink filename = (HyperLink)e.Item.FindControl("lnkFileName");
				if(filename != null)
				{
					filename.Text = row["FileName"].ToString();
					filename.NavigateUrl = appUrlPath + "/getfile.aspx?w=" + Server.UrlEncode(this.wikiName) + "&f=" + Server.UrlEncode(row["FileName"].ToString());
				}

				Label date = (Label)e.Item.FindControl("lblDate");
				if(date != null)
				{
					DateTime update = (DateTime)row["FileDate"];
					date.Text = update.ToShortDateString();
				}
				
				Label tm = (Label)e.Item.FindControl("lblTime");
				if(tm != null)
				{
					DateTime uptime = (DateTime)row["FileDate"];
					tm.Text = uptime.ToShortTimeString().ToLower();
				}

				Label size = (Label)e.Item.FindControl("lblSize");
				if(size != null)
				{
					int filesize = (int)row["FileSize"];
					double sizekb = Convert.ToDouble(filesize) / 1024.0;
					size.Text = sizekb.ToString("###,###,###.0") + " KB";
				}

				Label user = (Label)e.Item.FindControl("lblUser");
				if(user != null)
				{
					user.Text = "<a href=\"" + appUrlPath + "/wiki/Users/" + row["Author"].ToString() + ".wiki\">" + row["Author"].ToString() + "</a>";
				}
			}

			if(ItemType == ListItemType.EditItem)
			{
				DataRowView row = (DataRowView)e.Item.DataItem;

				Label filename = (Label)e.Item.FindControl("lblFileName");
				if(filename != null)
				{
					filename.Text = row["FileName"].ToString();
				}				

				Label renamemsg = (Label)e.Item.FindControl("lblRenameMsg");
				if(renamemsg != null)
				{
					renamemsg.Text = renameErrMsg; 
				}

				LinkButton rename = (LinkButton)e.Item.FindControl("btnRename");
				if(rename != null)
				{
					rename.CommandArgument = row["FileName"].ToString();
					rename.CommandName = "saverename";
				}
				
				LinkButton cancel = (LinkButton)e.Item.FindControl("btnCancel");
				if(cancel != null)
				{
					cancel.CommandArgument = row["FileName"].ToString();
					cancel.CommandName = "cancelrename";
				}

				Label date = (Label)e.Item.FindControl("lblDate");
				if(date != null)
				{
					DateTime update = (DateTime)row["FileDate"];
					date.Text = update.ToShortDateString();
				}
				
				Label tm = (Label)e.Item.FindControl("lblTime");
				if(tm != null)
				{
					DateTime uptime = (DateTime)row["FileDate"];
					tm.Text = uptime.ToShortTimeString().ToLower();
				}

				Label size = (Label)e.Item.FindControl("lblSize");
				if(size != null)
				{
					int filesize = (int)row["FileSize"];
					double sizekb = Convert.ToDouble(filesize) / 1024.0;
					size.Text = sizekb.ToString("###,###,###.0") + " KB";
				}

				Label user = (Label)e.Item.FindControl("lblUser");
				if(user != null)
				{
					user.Text = "<a href=\"" + appUrlPath + "/wiki/Users/" + row["Author"].ToString() + ".wiki\">" + row["Author"].ToString() + "</a>";
				}
			}

		}

		private void DataList1_ItemCommand(object source, System.Web.UI.WebControls.DataListCommandEventArgs e)
		{
			//handle rename or delete - command name: rename, delete, saverename, cancelrename
			//command argument holds the file name
			switch(e.CommandName)
			{
				case "rename":
					DataList1.EditItemIndex = e.Item.ItemIndex;
					break;
				case "delete":
					DataAccess.DeleteWikiFile(this.wikiName, e.CommandArgument.ToString());
					break;
				case "saverename":
					DataList1.EditItemIndex = -1;
					TextBox newname = (TextBox)e.Item.FindControl("txtNewFileName");
					if(newname != null)
					{
						RenameFile(e.CommandArgument.ToString(), newname.Text);
						if(renameErrMsg != "")
							DataList1.EditItemIndex = e.Item.ItemIndex; //keep edit template open
					}
					break;
				case "cancelrename":
					DataList1.EditItemIndex = -1;
					break;
			}
			FillFileList();
		}

		private void RenameFile(string OldName, string NewName)
		{
			if(NewName.Trim().Length == 0)
			{
				renameErrMsg = "You must provide a filename. Rename failed.";
				return;
			}
			object result = DataAccess.RenameWikiFile(this.wikiName, OldName, NewName);
			string msg = result.ToString();
			if(msg == "OK")
				renameErrMsg = "";
			else if(msg == "EXISTS")
				renameErrMsg = "The file <b>" + NewName + "</b> already exists. Rename failed.";
			else
				renameErrMsg = "The file <b>" + OldName + "</b> cannot be found. Rename failed.";
		}

		private void btnUpload_Click(object sender, System.EventArgs e)
		{
			//handle uploaded file
			lblMsg.Text = "";

			if(postFile.PostedFile.FileName == "")
			{
				lblMsg.Text = "You must first select a file.";
				return;
			}

			int fileLen = postFile.PostedFile.ContentLength;
			if(fileLen > 4096000)
			{
				lblMsg.Text = "The file you're trying to upload exceeds the 4MB file upload size limit.";
				return;
			}

			int sPos = postFile.PostedFile.FileName.LastIndexOf( "\\" );
			string fileName = postFile.PostedFile.FileName.Substring( sPos + 1, postFile.PostedFile.FileName.Length - (sPos + 1) );
			if(fileName != "")
			{
				byte[] bfile = new byte[fileLen];
				postFile.PostedFile.InputStream.Read(bfile, 0, fileLen);

				object result = DataAccess.AddWikiFile(this.wikiName, fileName, bfile, this.userName);
				string msg = result.ToString();
				if(msg != "OK")
					lblMsg.Text += "The file already exists. Upload failed.<br>";
			}
			else
				lblMsg.Text += "You must select a file to upload.<br>";

			//last thing, refresh file list
			FillFileList();
		}

		#endregion

		#region Edit Handling

		private void HideEdit()
		{
			divEdit.Visible = false;
		}

		private void ShowEdit()
		{
			//TODO - deal with FreeTextBox1
			divEdit.Visible = true;
			System.Web.UI.WebControls.Unit unit = new Unit("100%");
			System.Web.UI.WebControls.Unit unitH = new Unit("500px");
			FreeTextBox1.Width = unit;
			FreeTextBox1.Height = unitH;
			if(!Page.IsPostBack)
			{
				LoadEditTopic();
				LoadIndentToolbar();
				LoadHtmlLinkList();
				LoadEditFileList();
			}


			//TODO - re-enable view in HTML first below
			/*
			divEdit.Visible = true;
			if(!Page.IsPostBack)
			{
				//TODO - deal with conversion FreeTextBox1 first
				divWikiEdit.Visible = true;
				divFreeText.Visible = false;
				litJavascript.Visible = true;
				btnEditToggle.Text = "Rich Edit";

				LoadEditTopic();
				LoadEditFileList();
				pgBody.Attributes["onload"] += "LoadTemplates();";
			}
			{
				//TODO - deal with conversion FreeTextBox1 first
				divWikiEdit.Visible = false;
				divFreeText.Visible = true;
				litJavascript.Visible = false;
				btnEditToggle.Text = "Text Edit";

				LoadEditTopic();
				LoadEditFileList();
			}
			*/
		}

		private void LoadIndentToolbar()
		{
			FreeTextBoxControls.Toolbar iBar = new FreeTextBoxControls.Toolbar();
			FreeTextBox1.Toolbars.Add(iBar);

			OpenCollective.Edit.IndentLevelZero tzero = new IndentLevelZero();
			OpenCollective.Edit.IndentLevelOne tone = new IndentLevelOne();
			OpenCollective.Edit.IndentLevelTwo ttwo = new IndentLevelTwo();
			OpenCollective.Edit.IndentLevelThree tthree = new IndentLevelThree();

			iBar.Items.Add(tzero);
			iBar.Items.Add(tone);
			iBar.Items.Add(ttwo);
			iBar.Items.Add(tthree);

		}

		private void LoadHtmlLinkList()
		{
			FreeTextBoxControls.Toolbar bar = new FreeTextBoxControls.Toolbar();
			FreeTextBox1.Toolbars.Add(bar);

			FreeTextBoxControls.InsertHtmlMenu list = new InsertHtmlMenu();
			list.Title = "Links";

			FreeTextBoxControls.ToolbarListItem item1 = new FreeTextBoxControls.ToolbarListItem("Document link in this project", Color.White);
			item1.Value = " [[ Document Name | caption text ]] ";
			list.Items.Add(item1);

			FreeTextBoxControls.ToolbarListItem item2 = new FreeTextBoxControls.ToolbarListItem("Document link in another project", Color.White);
			item2.Value = " [[ Project Name/Document Name | caption text ]] ";
			list.Items.Add(item2);
			
			FreeTextBoxControls.ToolbarListItem item3 = new FreeTextBoxControls.ToolbarListItem("Parent document link", Color.White);
			item3.Value = " ^[[ Document Name | caption text ]] ";
			list.Items.Add(item3);
			
			FreeTextBoxControls.ToolbarListItem item4 = new FreeTextBoxControls.ToolbarListItem("Child document link", Color.White);
			item4.Value = " +[[ Document Name | caption text ]] ";
			list.Items.Add(item4);
			
			FreeTextBoxControls.ToolbarListItem item5 = new FreeTextBoxControls.ToolbarListItem("Web page link", Color.White);
			item5.Value = " [[ http://www.msn.com | caption text ]] ";
			list.Items.Add(item5);

			FreeTextBoxControls.ToolbarListItem item6 = new FreeTextBoxControls.ToolbarListItem("Document link in this project", Color.White);
			item6.Value = " [[ mailto:user@domain.com | caption text ]] ";
			list.Items.Add(item6);

			FreeTextBoxControls.ToolbarListItem bar1 = new FreeTextBoxControls.ToolbarListItem("--- images ---", Color.White);
			bar1.Value = "";
			list.Items.Add(bar1);

			FreeTextBoxControls.ToolbarListItem item7 = new FreeTextBoxControls.ToolbarListItem("Image inline with text", Color.White);
			item7.Value = " [[image:imgfile.jpg]] ";
			list.Items.Add(item7);

			FreeTextBoxControls.ToolbarListItem item8 = new FreeTextBoxControls.ToolbarListItem("Image right aligned", Color.White);
			item8.Value = " [[image:imgfile.gif|right|100px]] ";
			list.Items.Add(item8);

			FreeTextBoxControls.ToolbarListItem item9 = new FreeTextBoxControls.ToolbarListItem("Image left aligned", Color.White);
			item9.Value = " [[image:imgfile.gif|left|100px]] ";
			list.Items.Add(item9);

			FreeTextBoxControls.ToolbarListItem item10 = new FreeTextBoxControls.ToolbarListItem("Image right aligned with caption", Color.White);
			item10.Value = " [[image:imgfile.gif|right|100px|frame|caption text]] ";
			list.Items.Add(item10);

			FreeTextBoxControls.ToolbarListItem item11 = new FreeTextBoxControls.ToolbarListItem("Image left aligned with caption", Color.White);
			item11.Value = " [[image:imgfile.gif|left|100px|frame|caption text]] ";
			list.Items.Add(item11);

			FreeTextBoxControls.ToolbarListItem bar2 = new FreeTextBoxControls.ToolbarListItem("--- image links ---", Color.White);
			bar2.Value = "";
			list.Items.Add(bar2);

			FreeTextBoxControls.ToolbarListItem item12 = new FreeTextBoxControls.ToolbarListItem("Image link inline with text", Color.White);
			item12.Value = " [[imagelink:imgfile.jpg|topic or url]] ";
			list.Items.Add(item12);

			FreeTextBoxControls.ToolbarListItem item13 = new FreeTextBoxControls.ToolbarListItem("Image link right aligned", Color.White);
			item13.Value = " [[imagelink:imgfile.gif|topic or url|right|100px]] ";
			list.Items.Add(item13);

			FreeTextBoxControls.ToolbarListItem item14 = new FreeTextBoxControls.ToolbarListItem("Image left aligned", Color.White);
			item14.Value = " [[imagelink:imgfile.gif|topic or url|left|100px]] ";
			list.Items.Add(item14);

			FreeTextBoxControls.ToolbarListItem item15 = new FreeTextBoxControls.ToolbarListItem("Image right aligned with caption", Color.White);
			item15.Value = " [[imagelink:imgfile.gif|topic or url|right|100px|frame|caption text]] ";
			list.Items.Add(item15);

			FreeTextBoxControls.ToolbarListItem item16 = new FreeTextBoxControls.ToolbarListItem("Image left aligned with caption", Color.White);
			item16.Value = " [[imagelink:imgfile.gif|topic or url|left|100px|frame|caption text]] ";
			list.Items.Add(item16);

			bar.Items.Add(list);

/*
list.Items[0]
{FreeTextBoxControls.ToolbarListItem}
    System.Object: {FreeTextBoxControls.ToolbarListItem}
    BackColor: {RGB=0x0}
    isTrackingViewState: false
    System.Web.UI.IStateManager.IsTrackingViewState: false
    Text: "Document link in this project"
    Value: " [[ Document Name | caption text ]] "
    ViewState: {System.Web.UI.StateBag}
    viewState: {System.Web.UI.StateBag}
list.BuiltInScript
"function FTB_InsertHtmlMenu(ftbName,name,value) {\r\n\tFTB_InsertText(ftbName,value);\r\n}"
list.CommandIdentifier
""
list.FunctionName
"FTB_InsertHtmlMenu"
list.ScriptBlock
""
list.Title
"Links"

*/
		}

		private void LoadEditFileList()
		{
			//protected FreeTextBoxControls.Toolbar Toolbar1;
			//protected FreeTextBoxControls.InsertHtmlMenu mnuFileList;

			//mnuFileList
			//fileList.Items.Clear();
			//imgList.Items.Clear();
			
			DataSet ds = DataAccess.GetWikiFileNames(this.wikiName);
			if(ds.Tables[0].Rows.Count < 1)
			{
				//TODO - add a no files found
			}
			else
			{
				FreeTextBoxControls.Toolbar Toolbar1 = new FreeTextBoxControls.Toolbar();
				FreeTextBox1.Toolbars.Add(Toolbar1);

				FreeTextBoxControls.InsertHtmlMenu mnuFileList = new InsertHtmlMenu();
				mnuFileList.Title = "Files";

				foreach(DataRow row in ds.Tables[0].Rows)
				{
					string file = row["FileName"].ToString();
					FreeTextBoxControls.ToolbarListItem item = new FreeTextBoxControls.ToolbarListItem(file, Color.White);
					item.Value = file;
					mnuFileList.Items.Add(item);
				}

				Toolbar1.Items.Add(mnuFileList);

			}
		}

		private void LoadEditTopic()
		{
			//TODO - deal with showing FreeTextBox1 first
			
			bool isTalk = (this.pageCommand.ToLower() == "talkedit");

			WikiTopic wikiTopic = null;
			if(isTalk)
				wikiTopic = TopicManager.GetWikiTopicTalk(this.wikiName, this.topicName);
			else
				wikiTopic = TopicManager.GetWikiTopic(this.wikiName, this.topicName);

			if(wikiTopic != null)
			{
				litNote.Text	= "Last edited " + wikiTopic.Updated.ToLongDateString() + "&nbsp;" 
					+ wikiTopic.Updated.ToShortTimeString().ToLower() + " by " 
					+ TopicManager.GetAuthorLink(this.appUrlPath, wikiTopic.Author, "userlink");

				if(!Page.IsPostBack)
				{
					//txtBody.Text	= wikiTopic.Text;
					ViewState["FirstEdit"] = false;
					//add HTML to FreeTextBox1
					FreeTextBox1.DesignModeCss = appUrlPath + "/wiki.css";
					FreeTextBox1.Text = wikiTopic.Text; //this.GetTopicHtmlWithWikiLinks(wikiTopic);
				}

				//check for lock and set lock UI elements
				bool isLocked = false;
				DateTime lockDate = DateTime.Now;
				string lockAuthor = "";
				DataSet ds = DataAccess.GetWikiTopicLock(wikiTopic.ID);
				if(ds.Tables[0] != null && ds.Tables[0].Rows.Count == 1)
				{
					DataRow row = ds.Tables[0].Rows[0];  //WikiTopicID, LockDate, Author 
					isLocked = true;
					lockDate = (DateTime)row["LockDate"];
					lockAuthor = row["Author"].ToString();
				}

				btnLock.CommandArgument = wikiTopic.ID.ToString();
				btnDelete.CommandArgument = wikiTopic.ID.ToString();
				bool isAdmin = Config.IsAdmin(this.userName);
				if(isAdmin) 
					btnDelete.Visible = true; 
				else 
					btnDelete.Visible = false;

				if(isLocked)
				{
					btnLock.Text = "Unlock";
					if(this.userName.ToLower() == lockAuthor || isAdmin)
					{
						divEditButtons.Visible = true;
						btnLock.Enabled = true;
						litEditMsg.Text = "";
					}
					else
					{
						if(isTalk)
						{
							divEditButtons.Visible = true;
							btnLock.Enabled = false;
							litEditMsg.Text = "";
						}
						else
						{
							divEditButtons.Visible = false;
							btnLock.Enabled = false;
							litEditMsg.Text = "This topic locked on " + lockDate.ToShortDateString() + " at " 
								+ lockDate.ToShortTimeString() + " by " + TopicManager.GetAuthorLink(this.appUrlPath, lockAuthor, "userlink");
						}
					}
				}
				else
				{
					divEditButtons.Visible = true;
					btnLock.Text = "Lock";
					btnLock.Enabled = true;
					litEditMsg.Text = "";
				}
			}
			else
			{
				//not found so we edit for the first time
				btnLock.Enabled = false;
				litEditMsg.Text = "";
				btnLock.Text = "Lock";
				divEditButtons.Visible = true;

				litNote.Text = "This is a new topic.";
				this.FreeTextBox1.Text = "Add your text for this topic here..."; //txtBody.Text = "Add your text for this topic here...";
				ViewState["FirstEdit"] = true;
			}
		}

		private void btnEditSave_Click(object sender, System.EventArgs e)
		{
			string wikiText = WikiConverter.FormatHtmlToLines(this.FreeTextBox1.Text);
			bool isTalk = (this.pageCommand.ToLower() == "talkedit");

			int wikiTopicID = TopicManager.SaveWiki(this.wikiName, this.topicName, this.userName, cbIsMinor.Checked, txtSummary.Text, wikiText, isTalk);

			//execute notifications
			string host = Request.Url.Host;

			WikiWatch watch = new WikiWatch();
			watch.SendUpdates(wikiTopicID, host, appUrlPath, this.wikiName, this.topicName, this.userName, 
				cbIsMinor.Checked, txtSummary.Text, wikiText, isTalk); 

			if(isTalk)
				Response.Redirect(appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?talk");
			else
				Response.Redirect(appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki");
		}

		private void btnEditCancel_Click(object sender, System.EventArgs e)
		{
			bool isTalk = (this.pageCommand.ToLower() == "talkedit");
			string cmdTalk = "";
			if(isTalk) cmdTalk = "?talk";

			//better handle cancel when first edit to return to previous topic - cookie that contains wiki, topic, istalk
			//handle cancel - must check to see if this is first edit
			bool firstedit = (bool)ViewState["FirstEdit"];
			if(firstedit)
			{
				string prevWiki = Request.Cookies["wiki"] != null ? Request.Cookies["wiki"].Value : Config.DefaultWikiName;
				string prevTopic = Request.Cookies["topic"] != null ? Request.Cookies["topic"].Value : Config.HomePageName;
				cmdTalk = (Request.Cookies["talk"] != null && Request.Cookies["topic"].Value == "true") ? "talk" : "";
				Response.Redirect(appUrlPath + "/wiki/" + prevWiki + "/" + prevTopic + ".wiki" + cmdTalk);
			}
			else
				Response.Redirect(appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki" + cmdTalk);
			
		}

		private void btnEditPreview_Click(object sender, System.EventArgs e)
		{
			PreViewTopic();
		}

		private void btnLock_Click(object sender, EventArgs e)
		{
			int WikiTopicID = Convert.ToInt32(btnLock.CommandArgument);
			DataAccess.ToggleWikiTopicLock(WikiTopicID, this.userName);
			Response.Redirect(appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?edit");
		}

		private void btnDelete_Click(object sender, EventArgs e)
		{
			bool isAdmin = Config.IsAdmin(this.userName);
			if(isAdmin) 
			{
				int WikiTopicID = Convert.ToInt32(btnLock.CommandArgument);
				DataAccess.DeleteTopicByID(WikiTopicID);
				WikiKey key = new WikiKey(this.wikiName, this.topicName);
				TopicManager.RemoveTopicFromIndex(key); //remove from TopicManager.TopicIndex
				Response.Redirect(appUrlPath + "/wiki/" + this.wikiName + "/" + Config.HomePageName + ".wiki");
			}
		}

		#endregion

		#region Topic View and Old

		private void PreViewTopic()
		{
			WikiTopic topic = new WikiTopic(0, this.wikiName, this.topicName, "", DateTime.Now, cbIsMinor.Checked, this.txtSummary.Text, this.FreeTextBox1.Text);
			litBody.Text = "<br>" + GetTopicHtml(topic);
		}

		private void ShowViewTopic()
		{
			WikiKey key = new WikiKey(this.wikiName, this.topicName);
			if(TopicManager.TopicIndex[key] != null)
			{
				WikiTopic topic = TopicManager.GetWikiTopic(this.wikiName, this.topicName);
				string tp = "<div>Last modified on <b>" + topic.Updated.ToShortDateString() + " " 
					+ topic.Updated.ToShortTimeString().ToLower() + "</b> by <b>" 
					+ TopicManager.GetAuthorLink(this.appUrlPath, topic.Author, "userlink") + "</b></div><hr>" ;
				litBody.Text = tp + GetTopicHtml(topic);

				//save topic viewed in cookies
				string isTalk = this.pageCommand.ToLower().StartsWith("talk") ? "true" : "false";
				Response.Cookies.Add(new HttpCookie("wiki", this.wikiName));
				Response.Cookies.Add(new HttpCookie("topic", this.topicName));
				Response.Cookies.Add(new HttpCookie("talk", isTalk));
			}
			else
				Response.Redirect(appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?edit");
		}

		private string GetTopicHtml(WikiTopic topic)
		{
			Parser p = new Parser();
			//return p.ConvertWikiTextToHTML(this.appUrlPath, this.wikiName, this.topicName, topic.Text);
			return p.ConvertWikiLinksToHTML(this.appUrlPath, this.wikiName, this.topicName, topic.Text);
		}

		private string GetTopicHtmlWithWikiLinks(WikiTopic topic)
		{
			Parser p = new Parser();
			return p.ConvertWikiTextToHTMLNoLinks(this.appUrlPath, this.wikiName, this.topicName, topic.Text);
		}

		private void ShowOld()
		{
			bool isTalk = (this.pageCommand.ToLower() == "oldtalk");

			WikiKey key = new WikiKey(this.wikiName, this.topicName);
			if(TopicManager.TopicIndex[key] != null)
			{
				int histID = Convert.ToInt32(this.commandParam);

				WikiTopic topic = null;
				if(isTalk)
					topic = TopicManager.GetOldWikiTalkTopic(histID);
				else
					topic = TopicManager.GetOldWikiTopic(histID);
				
				string talk = isTalk ? "Discussion " : "";
				string tp = "<div><b>Previous " + talk + "Version</b></div><hr><div>Modified on <b>" + topic.Updated.ToShortDateString() + " " 
					+ topic.Updated.ToShortTimeString().ToLower() + "</b> by <b>" + topic.Author + "</b></div><hr>" ;

				litBody.Text = tp + GetTopicHtml(topic);

			}
			else
				Response.Redirect(appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?edit");
		}

		#endregion

		#region Differences
		private void ShowDiff()
		{
			//TODO - fix bug blows on view if no talk saved

			string isTalk = "";
			WikiTopic topic = null;
			if(this.pageCommand.ToLower() == "talkdiff")
			{
				topic = TopicManager.GetWikiTopicTalk(this.wikiName, this.topicName); //talk
				isTalk = "Talk";
			}
			else
				topic = TopicManager.GetWikiTopic(this.wikiName, this.topicName); //regular

			string tp = "<div><b>" + isTalk + "Differences</b></div><hr>";
			litBody.Text = tp + WikiDiff.GetWikiDiffHtml(topic, appUrlPath, commandParam, (isTalk.Length > 0)) 
				+ "<hr><b>Current " + isTalk + " Version</b><hr>" + GetTopicHtml(topic);
		}

		#endregion

		#region Talking 
		
		private void ShowTalk()
		{
			WikiTopic topicTalk = TopicManager.GetWikiTopicTalk(this.wikiName, this.topicName);
			if(topicTalk != null)
			{
				string tp = "<div><b>Discussion of " + topicTalk.Topic + "</b><hr>Last modified on <b>" 
					+ topicTalk.Updated.ToShortDateString() + " " + topicTalk.Updated.ToShortTimeString().ToLower() 
					+ "</b> by <b>" + TopicManager.GetAuthorLink(this.appUrlPath, topicTalk.Author, "userlink") + "</b></div><hr>" ;
				litBody.Text = tp + GetTopicHtml(topicTalk);

				//save topic viewed in cookies
				Response.Cookies.Add(new HttpCookie("wiki", this.wikiName));
				Response.Cookies.Add(new HttpCookie("topic", this.topicName));
				Response.Cookies.Add(new HttpCookie("talk", "true"));
			}
			else
				Response.Redirect(appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?talkedit");

		}

		#endregion

		#region History Stuff 

		private void HideHistory()
		{
			this.divHistory.Visible = false;
		}

		private void ShowHistory()
		{
			this.divHistory.Visible = true;
			bool isTalk = (this.pageCommand.ToLower() == "talkhistory");
			bool isAdmin = Config.IsAdmin(this.userName);

			if(this.commandParam.ToLower().StartsWith("rb"))
			{
				//rollback if has authority
				if(isAdmin)
				{
					int histID = Convert.ToInt32(this.commandParam.Substring(2, this.commandParam.Length - 2));

					WikiTopic topic = null;
					if(isTalk)
						topic = TopicManager.GetOldWikiTalkTopic(histID);
					else
						topic = TopicManager.GetOldWikiTopic(histID);

					string wikiText = WikiConverter.FormatHtmlToLines(topic.Text);
					string summary = "Rolled back from " + topic.Updated.ToShortDateString() + " " + topic.Updated.ToShortTimeString().ToLower();
					int wikiTopicID = TopicManager.SaveWiki(this.wikiName, this.topicName, this.userName, false, summary, wikiText, isTalk);

					string host = Request.Url.Host;
					WikiWatch watch = new WikiWatch();
					watch.SendUpdates(wikiTopicID, host, appUrlPath, this.wikiName, this.topicName, this.userName, false, summary, wikiText, isTalk); 
				}
				//after doing rollback, go to standard history page
				Response.Redirect(appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?history");
			}


			int size = 50; //similat to limit (items per page)
			int page = 1; //similar to offset

			if(this.commandParam.Length > 0)
			{
				//set limit and offset =  ?history=s50p500 or ?talkhistory=s50p500 
				if(commandParam.ToLower().IndexOf("s") > -1 && commandParam.ToLower().IndexOf("p") > -1 )
				{
					string cmd = commandParam.ToLower();
					if(cmd.StartsWith("s"))
					{
						cmd = cmd.Substring(1, cmd.Length - 1);
						string[] scmds = cmd.Split('p');
						size = Convert.ToInt32(scmds[0]);
						page = Convert.ToInt32(scmds[1]);
					}
					else if(cmd.StartsWith("p"))
					{
						cmd = cmd.Substring(1, cmd.Length - 1);
						string[] pcmds = cmd.Split('s');
						page = Convert.ToInt32(pcmds[0]);
						size = Convert.ToInt32(pcmds[1]);
					}
				}
			}

			int histCount = 0;
			if(isTalk)
				histCount = DataAccess.GetTopicTalkHistoryCount(this.wikiName, this.topicName);
			else
				histCount = DataAccess.GetTopicHistoryCount(this.wikiName, this.topicName);

			string talk = (isTalk) ? "talk" : "";
			string lnkPrev = "";
			if(size < page)
			{
				int ppage = (page - size < 0) ? 0 : page - size;
				lnkPrev = "( <a href=\"" + appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?" 
					+ talk + "history?s" + size.ToString() + "p" + ppage.ToString() + "\">" + "Previous " + size.ToString() + "</a> )";
			}

			string lnkNext = "";
			if(page <= histCount)
			{
				int npage = page + size - 1;
				if(npage <= histCount)
					lnkNext = "( <a href=\"" + appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?" 
						+ talk + "history?s" + size.ToString() + "p" + npage.ToString() + "\">" + "Next " + size.ToString() + "</a> )";
			}

			string lnk20   = "( <a href=\"" + appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?" 
				+ talk + "history?s20p0\">20</a> )";

			string lnk50   = "";
			if(histCount > 20)
				lnk50 = "( <a href=\"" + appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?" 
					+ talk + "history?s20p0\">20</a> )";

			string lnk100  = "";
			if(histCount > 50)
				lnk100  = "( <a href=\"" + appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?" 
					+ talk + "history?s20p0\">20</a> )";

			string lnk250  = "";
			if(histCount > 100)
				lnk250  = "( <a href=\"" + appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?" 
					+ talk + "history?s20p0\">20</a> )";

			string lnk500  = "";
			if(histCount > 250)
				lnk500  = "( <a href=\"" + appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?" 
					+ talk + "history?s20p0\">20</a> )";

			string links = lnkPrev + " " + lnkNext + " " + lnk20 + " " + lnk50 + " " + lnk100 + " " + lnk250 + " " + lnk500;

			litHistory.Text = "<b>Revision History</b><br>View "+ links 
				+ "<br>To view the differences between two versions, select a \"to\" and \"from\" and click \"Compare Selected Versions\""; //header for history page

			litBody.Text = WikiHistory.GetTopicHistory(this.appUrlPath, this.wikiName, this.topicName, this.commandParam, isTalk, isAdmin, size, page);
		}

		private void btnCompare_Click(object sender, System.EventArgs e)
		{
			string talk = "";
			bool isTalk = (this.pageCommand.ToLower() == "talkhistory");
			if(isTalk) talk = "talk";

			//TODO - show diff after getting ID's of selected versions from form variables generated in ShowHistory

			string diffParam = "";   // =3v1  is 3=src, 1=des where these values are wikihistid  - 0v4 means most recent v 4
			string src = Request.Form["srcGroup"] != null ? Request.Form["srcGroup"] : "0";
			string dest = Request.Form["destGroup"] != null ? Request.Form["destGroup"] : "0";
			if(src != "0" || dest != "0")
				diffParam = "=" + src + "v" + dest;

			Response.Redirect(appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?" + talk + "diff" + diffParam);
		}

		#endregion


		#region Tracing 

		private void ShowTrace()
		{
			WikiTopic topic = TopicManager.GetWikiTopic(this.wikiName, this.topicName);
			if(topic != null)
				litBody.Text = WikiTrace.GetWikiTrace(this.appUrlPath, topic);
			else
				Response.Redirect(appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?edit");

		}

		#endregion


		#region Watch 

		private void HideWatch()
		{
			this.divWatch.Visible = false;
		}

		private void ShowWatch()
		{
			this.divWatch.Visible = true;
			string watchAddress = "";
			DataSet ds = DataAccess.GetWikiWatchAddress(this.userName);
			if(ds.Tables[0] != null && ds.Tables[0].Rows.Count > 0)
				watchAddress = ds.Tables[0].Rows[0]["WatchAddress"].ToString();

			this.litWatchAddress.Text = watchAddress == "" ? "none set - set your address now" : watchAddress;
			
			if(!Page.IsPostBack) this.txtWatchAddress.Text = watchAddress;

			int WikiTopicID = DataAccess.GetTopicID(this.wikiName, this.topicName);
			if(DataAccess.IsWikiWatched(WikiTopicID, this.userName))
			{
				this.btnWatchThis.Text = "Unwatch This Topic";
			}
			else
			{
				this.btnWatchThis.Text = "Watch This Topic";
			}
			watchWiki = ""; //reset this global
			this.FillWatchList();
		}

		private void FillWatchList()
		{
			DataSet ds = DataAccess.GetWikiWatches(this.userName);
			if(ds.Tables[0] != null && ds.Tables[0].Rows.Count > 0)
			{
				DataList2.Visible = true;
				DataList2.DataSource = ds.Tables[0];
				DataList2.DataBind();
			}
			else
				ShowNoWatchesFound();
		}

		private void ShowNoWatchesFound()
		{
			//TODO
			DataList2.Visible = false;
			this.litBody.Text = "<b>No watched topics found.</b>";
		}

		private void DataList2_ItemCommand(object source, System.Web.UI.WebControls.DataListCommandEventArgs e)
		{
			//handle rename or delete - command name: rename, delete, saverename, cancelrename
			//command argument holds the file name
			switch(e.CommandName)
			{
				case "toggle":
					this.ToggleWatch(e.CommandArgument.ToString());
					break;
			}
			this.ShowWatch();
		}

		private void DataList2_ItemDataBound(object sender, System.Web.UI.WebControls.DataListItemEventArgs e)
		{
			ListItemType ItemType = e.Item.ItemType;
			if( (ItemType == ListItemType.Item) || (ItemType == ListItemType.AlternatingItem) )
			{
				//t.WikiTopicID, t.WikiName, t.TopicTitle, t.Author, t.Updated 
				DataRowView row = (DataRowView)e.Item.DataItem;

				string wname = row["WikiName"].ToString();
				//lblProjectWatch = WikiName
				Label proj = (Label)e.Item.FindControl("lblProjectWatch");
				if(proj != null)
				{
					//watchWiki - global for wikiName change
					if(wname.ToLower() == watchWiki.ToLower())
					{
						proj.Text = ""; //don't show name if same as last time
					}
					else
					{
						proj.Text = "<b>" + wname + "</b>";
						watchWiki = wname;
					}
				}

				//lnkTopicWatch = TopicTitle 
				HyperLink wtopic = (HyperLink)e.Item.FindControl("lnkTopicWatch");
				if(wtopic != null)
				{
					wtopic.Text = row["TopicTitle"].ToString();
					wtopic.NavigateUrl = appUrlPath + "/wiki/" + wname + "/" + row["TopicTitle"].ToString() + ".wiki";
				}

				//lblDateWatch = Updated
				Label wdate = (Label)e.Item.FindControl("lblDateWatch");
				if(wdate != null)
				{
					DateTime update = (DateTime)row["Updated"];
					wdate.Text = update.ToShortDateString() + " " + update.ToShortTimeString().ToLower();
				}

				//lnkAuthorWatch = Author
				HyperLink wauthor = (HyperLink)e.Item.FindControl("lnkAuthorWatch");
				if(wauthor != null)
				{
					wauthor.Text = row["Author"].ToString();
					wauthor.NavigateUrl = appUrlPath + "/wiki/Users/" + row["Author"].ToString() + ".wiki";
				}

				//btnToggleWatch = WikiTopicID
				LinkButton btnwatch = (LinkButton)e.Item.FindControl("btnToggleWatch");
				if(btnwatch != null)
				{
					btnwatch.CommandArgument = row["WikiTopicID"].ToString();
					btnwatch.CommandName = "toggle";
				}
			}

		}

		private void ToggleWatch(string cmdArg)
		{
			int WikiTopicID = Convert.ToInt32(cmdArg);
			DataAccess.ToggleWikiWatch(WikiTopicID, this.userName);
			this.ShowWatch();
		}

		private void btnUpdateWatchAddress_Click(object sender, System.EventArgs e)
		{
			DataAccess.UpdateWikiWatchAddress(this.userName, txtWatchAddress.Text);
			this.ShowWatch();
		}

		private void btnWatchThis_Click(object sender, EventArgs e)
		{
			int WikiTopicID = DataAccess.GetTopicID(this.wikiName, this.topicName);
			DataAccess.ToggleWikiWatch(WikiTopicID, this.userName);
			this.ShowWatch();
		}

		#endregion

		#region Recent Updates

		private void HideRecent()
		{
			this.divRecent.Visible = false;
		}

		private void ShowRecent()
		{
			if(!Page.IsPostBack)
			{
				this.divRecent.Visible = true;
				int days = 1;
				if(this.commandParam.Length > 0)
				{
					days = Convert.ToInt32(commandParam);
					this.txtRecentDays.Text = days.ToString();
				}
				else
					this.txtRecentDays.Text = "1";

				this.litBody.Text = WikiRecent.GetRecentTopics(this.appUrlPath, this.pageCommand, this.commandParam, this.wikiName, days);
			}
		}

		private void btnRecent_Click(object sender, EventArgs e)
		{
			//handle get recent this project only
			string param = "";
			try
			{
				int days = Convert.ToInt32(this.txtRecentDays.Text);
				param = "=" + days.ToString();
			}
			catch
			{
				param = "";
			}
			Response.Redirect(appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?recent" + param);
		}

		private void btnRecentAll_Click(object sender, EventArgs e)
		{
			//handle get recent all projects
			string param = "";
			try
			{
				int days = Convert.ToInt32(this.txtRecentDays.Text);
				param = "=" + days.ToString();
			}
			catch
			{
				param = "";
			}
			Response.Redirect(appUrlPath + "/wiki/" + this.wikiName + "/" + this.topicName + ".wiki?recentall" + param);
		}

		#endregion

		#region Project List - Wiki List

		private void ShowProjects()
		{
			//split into two columns
			ArrayList list = new ArrayList(3);
			string prevWiki = "";
			DataSet ds = DataAccess.GetWikis();
			foreach(DataRow row in ds.Tables[0].Rows)
			{
				//prevWiki
				string name = row["WikiName"].ToString();
				if(name.ToLower() == prevWiki.ToLower())
					name = "";
				else
				{
					prevWiki = name;
					list.Add(name);
				}
			}

			if(list.Count == 0)
				litBody.Text = "<b>No projects found.</b>";
			else
			{

				StringBuilder sb = new StringBuilder(13);
				sb.Append("<hr><table width=\"600\" align=\"center\" border=\"0\" cellpadding=\"3\" cellspacing=\"0\">\n");
				sb.Append("<tr><td class=\"diffhd\" valign=\"bottom\" colspan=\"3\"><b>Projects</b></td></tr>\n");

				int itemCount = list.Count;
				bool isOdd = (itemCount % 2 != 0);

				int oddOut = 0;

				if(list.Count > 1)
				{
					int colCount = (isOdd) ? ((itemCount - 1) / 2) : (itemCount / 2);
					if(isOdd)
						oddOut = itemCount - colCount - 1;

					for(int i = 0; i < colCount; i++)
					{
						string left = list[i].ToString();
						string leftlink = "<a href=\"" + this.appUrlPath + "/wiki/" + left + "/" + Config.HomePageName + ".wiki\">" + left + "</a>";
						
						string right = "";
						string rightlink = "";
						if(i < colCount && isOdd)
						{
							right = list[i + colCount + 1].ToString();
							rightlink = "<a href=\"" + this.appUrlPath + "/wiki/" + right + "/" + Config.HomePageName + ".wiki\">" + right + "</a>";
						}
						else
						{
							right = list[i + colCount].ToString();
							rightlink = "<a href=\"" + this.appUrlPath + "/wiki/" + right + "/" + Config.HomePageName + ".wiki\">" + right + "</a>";
						}

						sb.Append("<tr><td valign=\"bottom\" width=\"280\">"+ leftlink +"</td>"
							+ "<td valign=\"bottom\" width=\"40\">&nbsp;</td>"
							+ "<td valign=\"bottom\" width=\"280\">" + rightlink + "</td></tr>\n");
					}
				}

				if(isOdd)
				{
					string odd = (oddOut == 0) ? list[list.Count - 1].ToString() : list[oddOut].ToString();
					string oddlink = "<a href=\"" + this.appUrlPath + "/wiki/" + odd + "/" + Config.HomePageName + ".wiki\">" + odd + "</a>";
					sb.Append("<tr><td valign=\"bottom\" width=\"280\">"+ oddlink +"</td>"
						+ "<td valign=\"bottom\" width=\"40\">&nbsp;</td>"
						+ "<td valign=\"bottom\" width=\"280\">&nbsp;</td></tr>\n");
				}

				sb.Append("</table>");
				litBody.Text = sb.ToString();
			}
		}

		#endregion

	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
Since 2001 I've been writing .NET applications in C# and architecting n-tier applications in the enterprise. Before that I worked as a tech writer for nine years. Don't bother doing the math. I'm old. Ever since I laid eyes on my first Commodore PET, I've been a technologist. I've worked in the software world for fifteen years. I started as a technical writer and learned to code from the best engineers as I worked with them in creating technical documentation. It was then that I learned that writing code was more fun and frankly easier than writing about code. I've been doing both ever since. You can visit my blog at http://www.tsjensen.com/blog.

Comments and Discussions