Просмотр файла esoTalk-1.0.0g4/core/js/conversation.js

Размер файла: 34.84Kb
// Conversation JavaScript

var ETConversation = {

// Conversation details.
id: 0,
title: "",
channel: "",
slug: "",
startFrom: 0,
searchString: null,
postCount: 0,

updateInterval: null,
editingReply: false, // Are we typing a reply?
editingPosts: 0, // Number of posts being edited.

// Initialize:
init: function() {

	// If we're viewing an existing conversation...
	if (ET.conversation) {

		// Get the details.
		this.id = ET.conversation.conversationId;
		this.postCount = ET.conversation.countPosts;
		this.startFrom = ET.conversation.startFrom;
		this.channel = ET.conversation.channel;
		this.slug = ET.conversation.slug;
		this.searchString = ET.conversation.searchString;

		// Make the controls into a popup button.
		if ($("#conversationControls").length)
			$("#conversation .search").after($("#conversationControls").popup({
				alignment: "right",
				content: "<i class='icon-cog'></i> <span class='text'>"+T("Controls")+"</span> <i class='icon-caret-down'></i>"
			}).find(".button").addClass("big").end());

		// Set up the timeline scrubber.
		ETScrubber.body = $("#conversation");
		ETScrubber.scrubber = $("#conversation .scrubberContent");
		ETScrubber.items = $("#conversationPosts");
		ETScrubber.count = this.postCount;
		ETScrubber.perPage = ET.postsPerPage;
		ETScrubber.moreText = T("Load more posts");
		ETScrubber.startFrom = this.startFrom;

		// Set a callback that will load new post data.
		ETScrubber.loadItemsCallback = function(position, success, index) {
			if (position == Infinity) position = "999999"; // Kind of hackish? Meh...

			// If this "position" is an index in the timeline (eg. 201004), split it into year/month for the request.
			if (index) {
				position = (""+position).substr(0, 4)+"/"+(""+position).substr(4, 2);
			}

			// Make the request for post data.
			$.ETAjax({
				url: "conversation/index.ajax/"+ETConversation.id+"/"+position,
				data: {search: ETConversation.searchString},
				success: function(data) {
					var items = success(data);
					ETConversation.initPost(items);
					ETConversation.redisplayAvatars();
				},
				global: false
			});
		}

		// Set a callback that will run whenever we scroll to a specific index. We need it to change the URL.
		ETScrubber.scrollToIndexCallback = function(index) {
			var position;
			if (index == Infinity) position = "last";
			else position = (""+index).substr(0, 4)+"/"+(""+index).substr(4, 2);
			$.history.load(ETConversation.slug+"/"+position, true);
		}

		// Initialize the scrubber.
		ETScrubber.init();

		// When the "add a reply" button in the sidebar is clicked, we trigger a click on the "now" item in
		// the scrubber, and also on the reply textarea.
		$("#jumpToReply").click(function(e) {
			e.preventDefault();
			$(".scrubber-now a").click();
			setTimeout(function() {
				$("#reply textarea").click();
			}, 1);
		});

		// Start the automatic reload timeout.
		this.updateInterval = new ETIntervalCallback(this.update, ET.conversation.updateInterval);

		// Store the conversation title and add a click handler to edit it.
		this.title = $("#conversationTitle a").text() || $("#conversationTitle").text();
		$("#conversationTitle a").live("click", function(e) {
			e.preventDefault();
			ETConversation.editTitle();
		});

		// Add click handlers to the various conversation controls.
		$("#control-sticky").click(function(e) {
			e.preventDefault();
			ETConversation.toggleSticky();
		});
		$("#control-lock").click(function(e) {
			e.preventDefault();
			ETConversation.toggleLock();
		});
		$("#control-ignore").click(function(e) {
			e.preventDefault();
			ETConversation.toggleIgnore();
		});
		$("#control-delete").click(function(e) {
			// Pause all auto-updaters so that we don't try to update when we regain focus
			// on the window from the confirm popup.
			ETIntervalCallback.pause();

			if (!ETConversation.confirmDelete()) {
				e.preventDefault();
				ETIntervalCallback.resume();
			}
		});

		// Add tooltips to labels.
		$("#conversationHeader .label").tooltip();

		// Initialize the posts.
		this.initPosts();

		// If there's a post ID in the URL hash (eg. p1234), highlight that post, and scroll to it.
		var hash = window.location.hash.replace("#", "");
		if (hash.substr(0, 1) == "p" && $("#"+hash).length) {
			ETConversation.highlightPost($("#"+hash));
			setTimeout(function(){
				ETConversation.scrollTo($("#"+hash).offset().top - 10);
			}, 100);
		}

		// If we scroll below the header, pop the conversation title up in the forum header.
		$(window).scroll(function() {
			var y = $(this).scrollTop();
			var title = $("#forumTitle");
			if (y > $("#hdr").height()) {
				if (!title.data("old")) {
					title.data("old", title.html()).html("<a href='#'></a>").find("a").text(ETConversation.title);
					title.find("a").click(function(e) {
						e.preventDefault();
						$.scrollTo(0, "fast");
					});
				}
			} else if (title.data("old")) {
				title.html(title.data("old")).data("old", null);
			}
		});

	}

	// If we're starting a new conversation...
	else {

		// Auto-grow the title input.
		$("#conversationTitle input").autoGrowInput({
		    comfortZone: 30,
		    minWidth: 300,
		    maxWidth: 500
		}).trigger("update");

		$("#membersAllowedSheet").parents("form").remove();

		// Show the "change channel" sheet straight away!
		ETConversation.changeChannel();
	}

	// Add click handlers to change the channel and members allowed.
	$("#control-changeChannel").click(function(e) {
		e.preventDefault();
		ETConversation.changeChannel();
	});
	$("#control-changeMembersAllowed").click(function(e) {
		e.preventDefault();
		ETConversation.changeMembersAllowed();
	});

	// Make sure channels always have tooltips.
	$(".channels a").tooltip({alignment: "left", delay: 250, className: "withArrow withArrowBottom"});

	// Set up a members allowed popup if there's a "x others" link in the list.
	ETMembersAllowedTooltip.init($("#conversationPrivacy .allowedList .showMore"), function() {return ETConversation.id;}, true);

	// If there's a reply box, initilize it.
	if ($("#reply").length) ETConversation.initReply();

	// Add an onbeforeunload handler (to warn the user if they have an unsaved post/draft).
	$(window).bind("beforeunload.conversation", ETConversation.beforeUnload);
},


// Scroll to a specific position, applying an animation and taking the fixed conversation header into account.
scrollTo: function(position) {
	ETScrubber.scrollTo(position);
},

// On page exit, display a confirmation message if the user is editing posts or hasn't saved their reply.
beforeUnload: function onbeforeunload() {
	if (ETConversation.editingPosts > 0) return T("message.confirmLeave");
	else if (ETConversation.editingReply) return T("message.confirmDiscardPost");
},



//***** REPLY AREA

replyShowing: false,

// Initialize the reply section: disable/enable buttons, add click events, etc.
initReply: function() {

	var textarea = $("#reply textarea");
	ETConversation.editingReply = false;

	// Auto resize our reply textareas
	textarea.TextAreaExpander(200, 700);

	// Disable the "post reply" button if there's not a draft. Disable the save draft button regardless.
	if (!textarea.val()) $("#reply .postReply").disable();
	$("#reply .saveDraft").disable();

	// Add event handlers on the textarea to enable/disable buttons.
	textarea.keyup(function(e) {
		if (e.ctrlKey) return;
		$("#reply .postReply, #reply .saveDraft")[$(this).val() ? "enable" : "disable"]();
		ETConversation.editingReply = $(this).val() ? true : false;
	});

	if (ET.mentions) new ETAutoCompletePopup($("#reply textarea"), "@");

	// Add click events to the buttons.
	$("#reply .saveDraft").click(function(e){ ETConversation.saveDraft(); e.preventDefault(); });
	$("#reply .discardDraft").click(function(e){ ETConversation.discardDraft(); e.preventDefault(); });
	$("#reply .postReply").click(function(e){
		if (ETConversation.id) ETConversation.addReply();
		else ETConversation.startConversation();
		e.preventDefault();
	});

	// If the user can actually reply, condense the box so it expands when clicked on.
	if (!$("#reply").hasClass("logInToReply") && ETConversation.id) {

		// Make the reply box a placeholder.
		$("#reply").addClass("replyPlaceholder");

		$("#reply").click(function(e) {
			if (!ETConversation.replyShowing) {

				$(this).trigger("change");

				// Save the scroll position and then focus on the textarea.
				var scrollTop = $(document).scrollTop();
				$("#reply textarea").focus();
				$.scrollTo(scrollTop);

				// Scroll to the bottom of the reply area.
				$.scrollTo("#reply", "slow");
			}
			e.stopPropagation();
		});

		$("#reply").change(function(e) {
			if (!ETConversation.replyShowing) {
				ETConversation.replyShowing = true;
				$("#reply").removeClass("replyPlaceholder");

				// Put the cursor at the end of the textarea.
				var pos = textarea.val().length;
				textarea.selectRange(pos, pos);
			}
		});

		// If there's something in the reply textarea, show it.
		if ($("#reply textarea").val()) $("#reply").trigger("change");

		$(document).click(function(e) { ETConversation.hideReply(); });

	}

	$("#reply .controls a").tooltip();
	$("#reply .discardDraft").tooltip();

	// Register the Ctrl+Enter shortcut.
	textarea.keydown(function(e) {
		if (e.ctrlKey && e.which == 13 && !$("#reply .postReply").prop("disabled")) {
			$("#reply .postReply").click();
			e.preventDefault();
		}
	});
},

// Condense the reply box back into a placeholder.
hideReply: function() {
	if (!ETConversation.replyShowing || $("#reply textarea").val()) return;

	// Save the scroll top and height.
	var scrollTop = $(document).scrollTop();
	var oldHeight = $("#reply .postContent").height();

	// Condense it back into a placeholder.
	ETConversation.replyShowing = false;
	$("#reply").addClass("replyPlaceholder");

	// Animate the change and scroll back to where we were before.
	var newHeight = $("#reply .postContent").height();
	$("#reply .postContent").height(oldHeight).animate({height: newHeight}, "fast", function() {
		$(this).height("");
	});
	$.scrollTo(scrollTop);

	ETConversation.editingReply = false;
},

resetReply: function() {
	$("#reply textarea").val("");
	ETConversation.togglePreview("reply", false);
	ETConversation.hideReply();
},

// Add a reply.
addReply: function() {
	var content = $("#reply textarea").val();

	// Disable the reply/draft buttons.
	$("#reply .postReply, #reply .saveDraft").disable();

	// Make the ajax request.
	$.ETAjax({
		url: "conversation/reply.ajax/"+ETConversation.id,
		type: "post",
		data: {conversationId: ETConversation.id, content: content},
		success: function(data) {

			// If there are messages, enable the reply/draft buttons and don't continue.
			if (!data.postId) {
				$("#reply .postReply, #reply .saveDraft").enable();
				return;
			}

			// Hide messages which may have been previously triggered.
			ETMessages.hideMessage("waitToReply");
			ETMessages.hideMessage("emptyPost");

			// Hide the draft label, clear the textarea, and initialize the reply area again.
			$("#conversationHeader .labels .label-draft").remove();
			ETConversation.resetReply();

			ETConversation.postCount++;

			// Create a dud "more" block and then add the new post to it.
			var moreItem = $("<li></li>").appendTo("#conversationPosts");
			ETScrubber.count = ETConversation.postCount;
			var items = ETScrubber.addItems(ETConversation.postCount - 1, data.view, moreItem, true);
			ETConversation.redisplayAvatars();
			ETConversation.initPost(items);

			// Star the conversation if the user has the "star on reply" option on.
			if (data.starOnReply) {
				toggleStarState(ETConversation.id, true);
			}

			// Reset the post-checking timeout.
			ETConversation.updateInterval.reset(ET.conversationUpdateIntervalStart);

		},
		beforeSend: function() {
			createLoadingOverlay("reply", "reply");
		},
		complete: function() {
			hideLoadingOverlay("reply", false);
		}
	});
},

// Start a conversation.
startConversation: function(draft) {

	// Prepare the conversation data.
	var title = $("#conversationTitle input").val();
	var content = $("#reply textarea").val();
	var channel = $("#conversationHeader .channels :radio:checked").val();

	// Disable the post reply and save draft buttons.
	$("#reply .postReply, #reply .saveDraft").disable();

	// Make the ajax request.
	var data = {title: title, content: content, channel: channel};
	if (draft) data.saveDraft = "1";
	$.ETAjax({
		url: "conversation/start.ajax",
		type: "post",
		data: data,
		beforeSend: function() {
			createLoadingOverlay("reply", "reply");
			ETConversation.editingReply = false;
		},
		complete: function() {
			hideLoadingOverlay("reply", false);
			$("#reply .postReply, #reply .saveDraft").enable();
		},
		success: function(data) {
			// If there are messages, enable the reply/draft buttons and don't continue.
			if (data.messages) {
				if (data.messages.title) $("#conversationTitle input").focus();
				if (data.messages.content) $("#reply textarea").focus();
				return;
			}
		}
	});
},

// Save a draft.
saveDraft: function() {

	// If this is a new conversation, just use the startConversation function
	if (!ETConversation.id) {
		ETConversation.startConversation(true);
		return;
	}
	// Make the ajax request.
	$.ETAjax({
		url: "conversation/reply.ajax/"+ETConversation.id,
		type: "post",
		data: {saveDraft: true, content: $("#reply textarea").val()},
		beforeSend: function() {
			createLoadingOverlay("reply", "reply");
		},
		complete: function() {
			hideLoadingOverlay("reply", false);
		},
		success: function(data) {
			if (data.messages) return;
			ETMessages.hideMessage("emptyPost");

			// Show the draft label, disable the save draft button, and enable the discard draft button.
			$("#conversationHeader .labels").html(data.labels);
			$("#reply .saveDraft").disable();
			ETConversation.editingReply = false;
		}
	});
},

// Discard a draft.
discardDraft: function() {

	if (!confirm(T("message.confirmDiscardPost"))) return;

	// Disable the beforeUnload confirmation prompt, because the ajax request we make may
	// redirect us back to the home page.
	$(window).unbind("beforeunload.conversation");

	// Make the ajax request.
	$.ETAjax({
		url: "conversation/discard.ajax/" + ETConversation.id,
		type: "post",
		beforeSend: function() {
			createLoadingOverlay("reply", "reply");
		},
		complete: function() {
			hideLoadingOverlay("reply", false);
		},
		success: function(data) {

			// Hide the draft label and collapse the reply area.
			$("#conversationHeader .labels").html(data.labels);
			ETConversation.resetReply();

			$(window).bind("beforeunload.conversation", ETConversation.beforeUnload);

		}
	});
},


//***** POSTS

// Get new posts at the end of the conversation by comparing our post count with the server's.
update: function() {

	// Don't do this if we're searching, or if we haven't loaded the end of the conversation.
	if (ETConversation.searchString || ETScrubber.loadedItems.indexOf(ETConversation.postCount - 1) == -1) return;

	// Make the request for post data.
	$.ETAjax({
		url: "conversation/index.ajax/"+ETConversation.id+"/"+ETConversation.postCount,
		success: function(data) {

			// If there are new posts, add them.
			if (ETConversation.postCount < data.countPosts) {
				ETConversation.postCount = data.countPosts;

				// Create a dud "more" block and then add the new post to it.
				var moreItem = $("<li></li>").appendTo("#conversationPosts");
				ETScrubber.count = ETConversation.postCount;
				ETScrubber.addItems(data.startFrom, data.view, moreItem, true);

				var interval = ET.conversationUpdateIntervalStart;

			}

			// Otherwise, multiply the update interval by our config setting.
			else var interval = Math.min(ET.conversationUpdateIntervalLimit, ETConversation.updateInterval.interval * ET.conversationUpdateIntervalMultiplier);

			ETConversation.updateInterval.reset(interval);

		},
		global: false
	});

},

// Initialize the posts.
initPosts: function() {

	// Add tooltips to post controls.
	$("#conversationPosts .controls a").tooltip({alignment: "center"});
	$("#conversationPosts h3 a").tooltip({alignment: "left", className: "withArrow withArrowBottom"});
	$("#conversationPosts .time").tooltip({alignment: "left", className: "withArrow withArrowBottom"});
	$("#conversationPosts .online").tooltip({alignment: "left", offset: [-9, 0], className: "withArrow withArrowBottom"}).css("cursor", "pointer");

	// Add click handlers to the post controls.
	$("#conversationPosts .controls .control-edit").live("click", function(e) {
		var postId = $(this).parents(".post").data("id");
		ETConversation.editPost(postId);
		e.preventDefault();
	});

	$("#conversationPosts .controls .control-delete").live("click", function(e) {
		var postId = $(this).parents(".post").data("id");
		ETConversation.deletePost(postId);
		e.preventDefault();
	});

	$("#conversationPosts .controls .control-restore").live("click", function(e) {
		var postId = $(this).parents(".post").data("id");
		ETConversation.restorePost(postId);
		e.preventDefault();
	});

	$("#conversationPosts .post:not(.edit) .controls .control-quote").live("click", function(e) {
		var postId = $(this).parents(".post").data("id");
		ETConversation.quotePost(postId, e.shiftKey);
		e.preventDefault();
	});

	// Add a click handler to any "post links" to scroll back up to the right post, if it's loaded.
	$("#conversationPosts .postBody a[rel=post]").live("click", function(e) {
		var id = $(this).data("id");

		$("#conversationPosts .post").each(function() {
			if ($(this).data("id") == id) {
				ETConversation.scrollTo($(this).offset().top - 10);
				ETConversation.highlightPost($("#p"+id));
				e.preventDefault();
				return false;
			}
		});
	});

	ETConversation.initPost($("#conversationPosts .post"));
},

initPost: function(post) {
	ETConversation.collapseQuotes(post);
},

// Collapse quotes and add expand buttons.
collapseQuotes: function(items) {
	$(".postBody blockquote:not(.collapsed)", items)
		.addClass("collapsed")
		.each(function() {
			if ($(this)[0].scrollHeight <= $(this).innerHeight() + 20) {
				$(this).removeClass("collapsed");
				return;
			}

			var link = $("<a href='#' class='expand'><i class='icon-ellipsis-horizontal'></i></a>");
			link.click(function(e) {
				e.preventDefault();
				$(this).parents("blockquote").removeClass("collapsed");
				$(this).remove();
			});
			$(this).append(link);
		});
},

// Highlight a post.
highlightPost: function(post) {
	$("#conversationPosts .post.highlight").removeClass("highlight");
	$(post).addClass("highlight");
	setTimeout(function() {
		$(post).removeClass("highlight");
	}, 2000);
},

// Hide consecutive avatars from the same member.
redisplayAvatars: function() {

	// Loop through the avatars in the posts area and compare each one's src with the one before it.
	// If they're the same, hide it.
	var prevId = null;
	$("#conversationPosts > li").each(function() {
		if (prevId == $(this).find("div.post").data("memberid"))
			$(this).find("div.avatar").hide();
		else
			$(this).find("div.avatar").show();

		prevId = $(this).find("div.post").data("memberid");

	});

},

// Delete a post.
deletePost: function(postId) {

	$.hideToolTip();

	// Make the ajax request.
	$.ETAjax({
		url: "conversation/deletePost.ajax/" + postId,
		beforeSend: function() {
			createLoadingOverlay("p" + postId, "p" + postId);
		},
		complete: function() {
			hideLoadingOverlay("p" + postId, true);
		},
		success: function(data) {
			if (data.messages) return;
			$("#p"+postId).replaceWith(data.view);
			ETConversation.redisplayAvatars();
		}
	});
},

// Restore a post.
restorePost: function(postId) {

	$.hideToolTip();

	// Make the ajax request.
	$.ETAjax({
		url: "conversation/restorePost.ajax/" + postId,
		beforeSend: function() {
			createLoadingOverlay("p" + postId, "p" + postId);
		},
		complete: function() {
			hideLoadingOverlay("p" + postId, true);
		},
		success: function(data) {
			if (data.messages) return;
			$("#p"+postId).replaceWith(data.view);
			ETConversation.redisplayAvatars();
			ETConversation.collapseQuotes($("#p"+postId));

		}
	});
},

// Edit a post - make the post area into a textarea.
editPost: function(postId) {

	$.hideToolTip();
	var post = $("#p" + postId);

	$.ETAjax({
		url: "conversation/editPost.ajax/" + postId,
		type: "get",
		beforeSend: function() {
			createLoadingOverlay("p" + postId, "p" + postId);
		},
		complete: function() {
			hideLoadingOverlay("p" + postId, true);
		},
		success: function(data) {
			if (data.messages || data.modalMessage) return;
			ETConversation.updateEditPost(postId, data.view);
		}
	});
},

updateEditPost: function(postId, html) {
	var post = $("#p" + postId);

	ETConversation.editingPosts++;

	var startHeight = $(".postContent", post).height();

	// Replace the post HTML with the new stuff we just got.
	post.replaceWith($(html).find(".post"));
	var newPost = $("#p" + postId);
	var textarea = $("textarea", newPost);

	// Save the old post HTML for later.
	newPost.data("oldPost", post);

	// Set up the text area.
	var len = textarea.val().length;
	textarea.TextAreaExpander(200, 700).focus().selectRange(len, len);
	new ETAutoCompletePopup(textarea, "@");

	// Add click handlers to the cancel/submit buttons.
	$(".cancel", newPost).click(function(e) {
		e.preventDefault();
		ETConversation.cancelEditPost(postId);
	});
	$(".submit", newPost).click(function(e) {
		e.preventDefault();
		ETConversation.saveEditPost(postId, textarea.val());
	});

	// Animate the post's height.
	var newHeight = $(".postContent", newPost).height();
	$(".postContent", newPost).height(startHeight).animate({height: newHeight}, "fast", function() {
		$(this).height("");
	});

	ETConversation.redisplayAvatars();

	// Scroll to the bottom of the edit area if necessary.
	var scrollTo = newPost.offset().top + newHeight - $(window).height() + 10;
	if ($(document).scrollTop() < scrollTo) $.scrollTo(scrollTo, "slow");

	// Regsiter the Ctrl+Enter and Escape shortcuts on the post's textarea.
	textarea.keydown(function(e) {
		if (e.ctrlKey && e.which == 13) {
			ETConversation.saveEditPost(postId, this.value);
			e.preventDefault();
		}
		if (e.which == 27) {
			ETConversation.cancelEditPost(postId);
			e.preventDefault();
		}
	});
},

// Save an edited post to the database.
saveEditPost: function(postId, content) {

	// Disable the buttons.
	var post = $("#p" + postId);
	$(".button", post).disable();

	// Make the ajax request.
	$.ETAjax({
		url: "conversation/editPost.ajax/" + postId,
		type: "post",
		data: {content: content, save: true},
		beforeSend: function() {
			createLoadingOverlay("p" + postId, "p" + postId);
		},
		complete: function() {
			hideLoadingOverlay("p" + postId, true);
			$(".button", post).enable();
		},
		success: function(data) {
			if (data.messages) return;

			var startHeight = $(".postContent", post).height();

			// Replace the post HTML with the new post we just got.
			post.replaceWith(data.view);
			var newPost = $("#p" + postId);

			// Animate the post's height.
			var newHeight = $(".postContent", newPost).height();
			$(".postContent", newPost).height(startHeight).animate({height: newHeight}, "fast", function() {
				$(this).height("");
			});

			ETConversation.editingPosts--;
			ETConversation.redisplayAvatars();
			ETConversation.initPost(newPost);
		}
	});
},

// Cancel editing a post.
cancelEditPost: function(postId) {
	ETConversation.editingPosts--;
	var post = $("#p" + postId);

	var scrollTop = $(document).scrollTop();

	// Change the post control and body HTML back to what it was before.
	var startHeight = $(".postContent", post).height();
	post.replaceWith(post.data("oldPost"));
	var newPost = $("#p" + postId);

	// Animate the post's height.
	var newHeight = $(".postContent", newPost).height();
	$(".postContent", newPost).height(startHeight).animate({height: newHeight}, "fast", function() {
		$(this).height("");
	});

	ETConversation.initPost(newPost);

	$.scrollTo(scrollTop);
},

// Quote a post.
quotePost: function(postId, multi) {
	var selection = ""+$.getSelection();
	$.ETAjax({
		url: "conversation/quotePost.json/" + postId,
		success: function(data) {
			var top = $(document).scrollTop();
			ETConversation.quote("reply", selection ? selection : data.content, data.postId + ":" + data.member, null, true);

			// If we're "multi" quoting (i.e. shift is being held down), keep our scroll position static.
			// Otherwise, scroll down to the reply area.
			if (!multi) {
				$("#jumpToReply").click();
			} else {
				$("#reply").change();
				$.scrollTo(top);
			}
		},
		global: true
	});
},


//***** MEMBERS ALLOWED

initMembersAllowed: function() {

	// Show or hide the "Click on a member's name to remove them" text depending on if the conversation is private or not.
	$("#addMemberForm .help").toggle($("#membersAllowedSheet .allowedList .name").length ? true : false);

	var ac = new ETAutoCompletePopup($("#addMemberForm input[name=member]"), false, function(member) {
		ETConversation.addMember(member.name);
	});

	// Wrap a form around the add member form so that the submit event works properly.
	$("#addMemberForm").wrap("<form>");
	$("#addMemberForm").parent().submit(function(e) {
		ETConversation.addMember($("#addMemberForm input[name=member]").val());
		ac.stop();
		e.preventDefault();
	});

	// Add click handlers to each of the names, to remove them.
	$("#membersAllowedSheet .allowedList .name a").die("click").live("click", function(e) {
		e.preventDefault();
		ETConversation.removeMember($(this).data("type"), $(this).data("id"));
	});

	// Focus on the name input.
	$("#addMemberForm input[name=member]").focus();

},

// Open up the "chang members allowed" sheet.
changeMembersAllowed: function() {
	ETSheet.loadSheet("membersAllowedSheet", "conversation/membersAllowed.ajax/"+ETConversation.id, function() {
		ETConversation.initMembersAllowed();
	});
},

// Add a member to the conversation.
addMember: function(name) {
	if (!name) return;

	$.ETAjax({
		id: "addMember",
		url: "conversation/addMember.ajax/" + ETConversation.id,
		type: "post",
		data: {member: name},
		success: function(data) {

			// If there was an error, select the contents of the name input.
			if (data.messages) $("#addMemberForm input[name=member]").select();

			// Otherwise, show the help text, clear the name input, and update the members allowed summary,
			// list, and labels.
			else {
				$("#addMemberForm .help").show();
				$("#addMemberForm input[name=member]").val("");
				$("#conversationPrivacy .allowedList").html(data.allowedSummary);
				$("#membersAllowedSheet .allowedList").html(data.allowedList);
				$("#conversationHeader .labels").html(data.labels);
				ETMembersAllowedTooltip.init($("#conversationPrivacy .allowedList .showMore"), function() {return ETConversation.id;}, true);
			}
		}
	});
},

// Remove a member from the members allowed list.
removeMember: function(type, id) {

	var data = {};
	data[type] = id;
	$.ETAjax({
		id: "addMember",
		url: "conversation/removeMember.ajax/" + ETConversation.id,
		type: "post",
		data: data,
		success: function(data) {

			// Update the members allowed summary, list, and labels.
			$("#conversationPrivacy .allowedList").html(data.allowedSummary);
			$("#membersAllowedSheet .allowedList").html(data.allowedList);
			$("#addMemberForm .help").toggle($("#membersAllowedSheet .allowedList .name").length ? true : false);
			$("#conversationHeader .labels").html(data.labels);
			ETMembersAllowedTooltip.init($("#conversationPrivacy .allowedList .showMore"), function() {return ETConversation.id;}, true);

		}
	});

},


//***** CHANNELS

// Open the change channel sheet.
changeChannel: function() {
	ETSheet.loadSheet("changeChannelSheet", "conversation/changeChannel.view/"+ETConversation.id, function() {

		// Hide the radio buttons, and set up a handler for when they're changed.
		$("#changeChannelSheet .channelList input").hide().click(function() {
			$("#changeChannelSheet form").submit();
		});

		$("#changeChannelSheet .buttons").hide();

		// Add tooltips to channels that cannot be changed to.
		$("#changeChannelSheet .channelList li").tooltip({alignment: "left", offset: [20, 43], className: "hoverable"});

		// Add a submit event to the form.
		$("#changeChannelSheet form").submit(function(e) {
			e.preventDefault();
			var channelId = $(this).find("input:checked").val();
			$.ETAjax({
				url: "conversation/save.json/" + ETConversation.id,
				type: "post",
				data: {channel: channelId},
						success: function(data) {
					if (data.messages) return;

					// Update the members allowed summary.
					$("#conversationPrivacy .allowedList").html(data.allowedSummary);
					ETMembersAllowedTooltip.init($("#conversationPrivacy .allowedList .showMore"), function() {return ETConversation.id;}, true);

					// Replace the conversation's channel with the new one.
					ETConversation.channel = channelId;
					$("#conversationHeader .channels").replaceWith(data.channelPath);

					ETSheet.hideSheet("changeChannelSheet");
				}
			})
		});
	});
},


//***** CONVERSATION PROPERTIES

// Edit the title of the conversation.
editTitle: function() {
	if (!$("#conversationTitle").hasClass("editing")) {

		// Replace the title tag with an input.
		var title = $("#conversationTitle a").text().trim();
		$("#conversationTitle").html("<input type='text' class='text' maxlength='100'/>").addClass("editing");
		$("#conversationTitle input").val(title).autoGrowInput({
		    comfortZone: 30,
		    minWidth: 250,
		    maxWidth: 500
		}).trigger("update");

		// Add a key press and blur handler to the field.
		$("#conversationTitle input").select().blur(function() { ETConversation.saveTitle(); }).keydown(function(e) {
			if (e.which == 13) ETConversation.saveTitle(); // Enter
			if (e.which == 27) ETConversation.saveTitle(true); // Escape
		});
	}
},

// Save the conversation title.
saveTitle: function(cancel) {
	if ($("#conversationTitle").hasClass("editing")) {

		// Return the conversation title input back to normal.
		var title = $("#conversationTitle input").val();
		if (!title || cancel) title = ETConversation.title;
		var sanitized = $('<div/>').text(title).html();
		$("#conversationTitle").html("<a href='#'>"+sanitized+"</a>").removeClass("editing");

		// If we're cancelling, that's all we need to do.
		if (cancel || ETConversation.title == title) return;

		// Otherwise, update the document title with the new title.
		$(document).attr("title", $(document).attr("title").replace(ETConversation.title, title));
		ETConversation.title = title;

		// And save it.
		$.ETAjax({
			url: "conversation/save.json/" + ETConversation.id,
			type: "post",
			data: {title: title},
			global: true
		});
	}
},

// Toggle sticky.
toggleSticky: function() {
	$("#control-sticky span").html(T($("#control-sticky span").html() == T("Sticky") ? "Unsticky" : "Sticky"));
	$.ETAjax({
		url: "conversation/sticky.ajax/" + ETConversation.id,
		success: function(data) {
			$("#conversationHeader .labels").html(data.labels);
		}
	});
},

// Toggle lock.
toggleLock: function() {
	$("#control-lock span").html(T($("#control-lock span").html() == T("Lock") ? "Unlock" : "Lock"));
	$.ETAjax({
		url: "conversation/lock.ajax/" + ETConversation.id,
		success: function(data) {
			$("#conversationHeader .labels").html(data.labels);
		}
	});
},

// Toggle lock.
toggleIgnore: function() {
	$("#control-ignore span").html(T($("#control-ignore span").html() == T("Ignore conversation") ? "Unignore conversation" : "Ignore conversation"));
	$.ETAjax({
		url: "conversation/ignore.ajax/" + ETConversation.id,
		success: function(data) {
			$("#conversationHeader .labels").html(data.labels);
		}
	});
},

// Confirm deletion of the conversation.
confirmDelete: function() {
	return confirm(T("message.confirmDelete"));
},


//***** POST FORMATTING

// Add a quote to a textarea.
quote: function(id, quote, name, postId, insert) {
	var argument = postId || name ? (postId ? postId + ":" : "") + (name ? name : "Name") : "";
	var startTag = "[quote" + (argument ? "=" + argument : "") + "]" + (quote ? quote : "");
	var endTag = "[/quote]";

	// If we're inserting the quote, add it to the end of the textarea.
	if (insert) ETConversation.insertText($("#" + id + " textarea"), startTag + endTag + "\n");

	// Otherwise, wrap currently selected text with the quote.
	else ETConversation.wrapText($("#" + id + " textarea"), startTag, endTag);
},

// Add text to the reply area at the very end, and move the cursor to the very end.
insertText: function(textarea, text) {
	textarea = $(textarea);
	textarea.focus();
	textarea.val(textarea.val() + text);
	textarea.focus();

	// Trigger the textarea's keyup to emulate typing.
	textarea.trigger("keyup");
},

// Add text to the reply area, with the options of wrapping it around a selection and selecting a part of it when it's inserted.
wrapText: function(textarea, tagStart, tagEnd, selectArgument, defaultArgumentValue) {

	textarea = $(textarea);

	// Save the scroll position of the textarea.
	var scrollTop = textarea.scrollTop();

	// Work out what text is currently selected.
	var selectionInfo = textarea.getSelection();
	if (textarea.val().substring(selectionInfo.start, selectionInfo.start + 1).match(/ /)) selectionInfo.start++;
	if (textarea.val().substring(selectionInfo.end - 1, selectionInfo.end).match(/ /)) selectionInfo.end--;
	var selection = textarea.val().substring(selectionInfo.start, selectionInfo.end);

	// Work out the text to insert over the selection.
	selection = selection ? selection : (defaultArgumentValue ? defaultArgumentValue : "");
	var text = tagStart + selection + (typeof tagEnd != "undefined" ? tagEnd : tagStart);

	// Replace the textarea's value.
	textarea.val(textarea.val().substr(0, selectionInfo.start) + text + textarea.val().substr(selectionInfo.end));

	// Scroll back down and refocus on the textarea.
	textarea.scrollTo(scrollTop);
	textarea.focus();

	// If a selectArgument was passed, work out where it is and select it. Otherwise, select the text that was selected
	// before this function was called.
	if (selectArgument) {
		var newStart = selectionInfo.start + tagStart.indexOf(selectArgument);
		var newEnd = newStart + selectArgument.length;
	} else {
		var newStart = selectionInfo.start + tagStart.length;
		var newEnd = newStart + selection.length;
	}
	textarea.selectRange(newStart, newEnd);

	// Trigger the textarea's keyup to emulate typing.
	textarea.trigger("keyup");
},

// Toggle preview on an editing area.
togglePreview: function(id, preview) {

	// If the preview box is checked...
	if (preview) {

		// Hide the formatting buttons.
		$("#" + id + " .formattingButtons").hide();
		$("#" + id + "-preview").html("");

		// Get the formatted post and show it.
		$.ETAjax({
			url: "conversation/preview.ajax",
			type: "post",
			data: {content: $("#" + id + " textarea").val()},
				success: function(data) {

				// Keep the minimum height.
				$("#" + id + "-preview").css("min-height", $("#" + id + "-textarea").innerHeight());

				// Hide the textarea, and show the preview.
				$("#" + id + " textarea").hide();
				$("#" + id + "-preview").show()
				$("#" + id + "-preview").html(data.content);
			}
		});
	}

	// The preview box isn't checked...
	else {
		// Show the formatting buttons and the textarea; hide the preview area.
		$("#" + id + " .formattingButtons").show();
		$("#" + id + " textarea").show();
		$("#" + id + "-preview").hide();

		// Make sure the preview checkbox is unchecked again.
		$("#reply-previewCheckbox").prop("checked", false);
	}
}

};

$(function() {
	ETConversation.init();
});