Asynchronous updates.
[cocanwiki.git] / html / _js / editor.js
index ad33c6e..1bf5b82 100644 (file)
@@ -1,26 +1,30 @@
 /* Javascript for OCAMLWIKI.
  * Copyright (C) 2004 Merjis Ltd.
- * $Id: editor.js,v 1.1 2004/09/07 10:14:09 rich Exp $
+ * $Id: editor.js,v 1.6 2004/12/20 11:57:27 rich Exp $
  */
 
-var delay = 1000               // Delay in milliseconds before updating.
+// Delay in milliseconds between updates.
+var delay = 1000;
 
-function update_preview (content_id, preview_id)
+// This function is called when the content has changed (eg. from a keypress).
+function content_changed (content_id, preview_id)
 {
-  // Updating is quite expensive, so only update after a period of apparent
-  // inactivity.
   var preview = document.getElementById (preview_id);
-  if (preview.timer) clearTimeout (preview.timer);
-  preview.timer =
-    setTimeout ("update_preview_now ('" + content_id + "', '" +
-               preview_id + "')", delay);
+
+  // Set a flag to indicate that the content has changed.
+  preview.updated = true;
+
+  // If there's not an asynchronous preview in progress at the moment,
+  // then start one.
+  start_update (content_id, preview_id, preview);
 }
 
-function update_preview_now (content_id, preview_id)
+function start_update (content_id, preview_id, preview)
 {
-  // Remove the timer.
-  var preview = document.getElementById (preview_id);
-  if (preview.timer) preview.timer = null;
+  if (!preview) preview = document.getElementById (preview_id);
+
+  // Do nothing if a preview is already in progress.
+  if (preview.in_progress) return;
 
   // Get the Wiki-markup content from the content textarea.
   var content = document.getElementById (content_id).value;
@@ -30,11 +34,26 @@ function update_preview_now (content_id, preview_id)
     new ActiveXObject ('Microsoft.XMLHTTP') : new XMLHttpRequest ();
   if (http)
     {
-      http.open ('POST', '/_bin/preview.cmo', false);
+      // Set a handler for state changes.
+      http.onreadystatechange = function () {
+       preview_state_change (content_id, preview_id, preview, http);
+      };
+      // 'true' argument means we want to do this asynchronously.
+      http.open ('POST', '/_bin/preview.cmo', true);
       http.setRequestHeader ('Content-Type',
                             'application/x-www-form-urlencoded');
       http.send ('content=' + encodeURIComponent (content));
 
+      // A preview is "in progress".
+      preview.in_progress = true;
+      preview.updated = false;
+    }
+}
+
+function preview_state_change (content_id, preview_id, preview, http)
+{
+  if (http.readyState == 4) {  // If state is "loaded".
+    if (http.status == 200) {  // Request status is 200 OK.
       var xhtml = http.responseText;
 
       // Next line fails with my copy of IE if the text contains a
@@ -42,4 +61,190 @@ function update_preview_now (content_id, preview_id)
       // "Unknown runtime error"
       preview.innerHTML = xhtml;
     }
+
+    // A preview is not in progress now.
+    preview.in_progress = false;
+
+    // Have there been further updates since this preview started?  If so
+    // then we need to start another preview.
+    if (preview.updated) start_update (content_id, preview_id, preview);
+  }
+}
+
+// Update the preview field synchronously.
+function synch_update (content_id, preview_id)
+{
+  var preview = document.getElementById (preview_id);
+
+  // Get the Wiki-markup content from the content textarea.
+  var content = document.getElementById (content_id).value;
+
+  // Send the Wiki-markup to the server to be turned into XHTML.
+  var http = document.all ?
+    new ActiveXObject ('Microsoft.XMLHTTP') : new XMLHttpRequest ();
+  if (http)
+    {
+      http.open ('POST', '/_bin/preview.cmo', false);
+      http.setRequestHeader ('Content-Type',
+                            'application/x-www-form-urlencoded');
+      http.send ('content=' + encodeURIComponent (content));
+
+      if (http.readyState == 4 && http.status == 200) {
+       var xhtml = http.responseText;
+       preview.innerHTML = xhtml;
+      }
+    }
+}
+
+// Initialise the edit_buttons for a section.
+// We do this in Javascript so that non-Javascript users won't see
+// any buttons (they wouldn't be functional for them anyway).
+function init_edit_buttons (div_id, content_id, preview_id)
+{
+  // HTML for the edit buttons.
+  var args = "'" + content_id + "', '" + preview_id + "'";
+  var edit_buttons_html =
+    "<a onmousedown=\"link(" + args + "); return false;\" title=\"Select some text and click this to make a link\"><u>Link</u></a>" +
+    "<span class=\"spacer\"></span>" +
+    "<a onmousedown=\"bold(" + args + "); return false;\" title=\"Select some text and click this to make it bold\"><strong>Bold</strong></a>" +
+    "<a onmousedown=\"italic(" + args + "); return false;\" title=\"Select some text and click this to make it italic\"><em>Italic</em></a>" +
+    "<a onmousedown=\"strikeout(" + args + "); return false;\" title=\"Select some text and click this to strike through it\"><s>Strike</s></a>" +
+    "<span class=\"spacer\"></span>" +
+    "<a onmousedown=\"bullet(" + args + "); return false;\" title=\"Select lines of text and click this to make it a bullet list\"><big>&#x2219;&#8212;</big></a>" +
+    "<a onmousedown=\"numbered(" + args + "); return false;\" title=\"Select lines of text and click this to make it a numbered list\">1&#8212;</a>" +
+    "<br/>"
+    ;
+  var div = document.getElementById (div_id);
+  div.innerHTML = edit_buttons_html;
+}
+
+function replace_selection (content_id, fn, warning)
+{
+  var textarea = document.getElementById (content_id);
+
+  if (textarea.setSelectionRange) { // Mozilla
+    var start = textarea.selectionStart;
+    var end = textarea.selectionEnd;
+    if (start != end) {
+      var text = textarea.value.substring (start, end);
+      var replacement = fn (text);
+      textarea.value = textarea.value.substring (0, start) +
+       replacement + textarea.value.substring (end);
+    } else
+      if (warning) alert (warning);
+  } else if (document.selection) { // IE
+    var range = document.selection.createRange ();
+    if (range.parentElement () == textarea && range.text != "") {
+      var text = range.text;
+      var len = text.length;
+
+      // IE6 bug workaround: If the end of the selection is the end of
+      // a line, then we must remember to append a \n character after
+      // doing the replacement.  Note the incredible lengths we have to
+      // go to here because IE is a piece of shit.
+      var dup = range.duplicate ();
+      dup.moveEnd ("character", 2);
+      append =
+       dup.text.substring (dup.text.length-3, dup.text.length-1) == "\r\n";
+
+      // Replace \r\n with just \n.
+      text = text.replace ("\r\n", "\n");
+
+      // IE's selections often include a trailing space or carriage
+      // return.  Ignore this whitespace when calling the replacement
+      // function.
+      var replacement;
+      if ((ar = text.match (/(\s+)$/))) {
+       var ws = ar[1];
+       text = text.substring (0, len - ws.length);
+       replacement = fn (text);
+       replacement = replacement + ws;
+      } else
+       replacement = fn (range.text);
+
+      // See IE bug workaround above.
+      if (append) replacement += "\n";
+
+      range.text = replacement;
+    } else
+       if (warning) alert (warning);
+  }
+}
+
+function link (content_id, preview_id)
+{
+  var fn = function (text) {
+    if (ar = text.match (/^\[\[(.*)\]\]$/))
+      return ar[1];
+    else
+      return "[[" + text + "]]";
+  };
+  var warning = "Select an area of text first.";
+  replace_selection (content_id, fn, warning);
+  start_update (content_id, preview_id);
+}
+
+function bold (content_id, preview_id)
+{
+  var fn = function (text) {
+    if (ar = text.match (/^<b>(.*)<\/b>$/))
+      return ar[1];
+    else
+      return "<b>" + text + "</b>";
+  };
+  var warning = "Select an area of text first.";
+  replace_selection (content_id, fn, warning);
+  start_update (content_id, preview_id);
+}
+
+function italic (content_id, preview_id)
+{
+  var fn = function (text) {
+    if (ar = text.match (/^<i>(.*)<\/i>$/))
+      return ar[1];
+    else
+      return "<i>" + text + "</i>";
+  };
+  var warning = "Select an area of text first.";
+  replace_selection (content_id, fn, warning);
+  start_update (content_id, preview_id);
+}
+
+function strikeout (content_id, preview_id)
+{
+  var fn = function (text) {
+    if (ar = text.match (/^<s>(.*)<\/s>$/))
+      return ar[1];
+    else
+      return "<s>" + text + "</s>";
+  };
+  var warning = "Select an area of text first.";
+  replace_selection (content_id, fn, warning);
+  start_update (content_id, preview_id);
+}
+
+function bullet (content_id, preview_id)
+{
+  var fn = function (text) {
+    if (text.match (/^\*/))
+      return text.replace (/^\* /gm, "");
+    else
+      return "* " + text.replace (/\n/g, "\n* ");
+  };
+  var warning = "Select some whole lines of text first.";
+  replace_selection (content_id, fn, warning);
+  start_update (content_id, preview_id);
+}
+
+function numbered (content_id, preview_id)
+{
+  var fn = function (text) {
+    if (text.match (/^#/))
+      return text.replace (/^# /gm, "");
+    else
+      return "# " + text.replace (/\n/g, "\n# ");
+  };
+  var warning = "Select some whole lines of text first.";
+  replace_selection (content_id, fn, warning);
+  start_update (content_id, preview_id);
 }