Asynchronous updates.
[cocanwiki.git] / html / _js / editor.js
1 /* Javascript for OCAMLWIKI.
2  * Copyright (C) 2004 Merjis Ltd.
3  * $Id: editor.js,v 1.6 2004/12/20 11:57:27 rich Exp $
4  */
5
6 // Delay in milliseconds between updates.
7 var delay = 1000;
8
9 // This function is called when the content has changed (eg. from a keypress).
10 function content_changed (content_id, preview_id)
11 {
12   var preview = document.getElementById (preview_id);
13
14   // Set a flag to indicate that the content has changed.
15   preview.updated = true;
16
17   // If there's not an asynchronous preview in progress at the moment,
18   // then start one.
19   start_update (content_id, preview_id, preview);
20 }
21
22 function start_update (content_id, preview_id, preview)
23 {
24   if (!preview) preview = document.getElementById (preview_id);
25
26   // Do nothing if a preview is already in progress.
27   if (preview.in_progress) return;
28
29   // Get the Wiki-markup content from the content textarea.
30   var content = document.getElementById (content_id).value;
31
32   // Send the Wiki-markup to the server to be turned into XHTML.
33   var http = document.all ?
34     new ActiveXObject ('Microsoft.XMLHTTP') : new XMLHttpRequest ();
35   if (http)
36     {
37       // Set a handler for state changes.
38       http.onreadystatechange = function () {
39         preview_state_change (content_id, preview_id, preview, http);
40       };
41       // 'true' argument means we want to do this asynchronously.
42       http.open ('POST', '/_bin/preview.cmo', true);
43       http.setRequestHeader ('Content-Type',
44                              'application/x-www-form-urlencoded');
45       http.send ('content=' + encodeURIComponent (content));
46
47       // A preview is "in progress".
48       preview.in_progress = true;
49       preview.updated = false;
50     }
51 }
52
53 function preview_state_change (content_id, preview_id, preview, http)
54 {
55   if (http.readyState == 4) {   // If state is "loaded".
56     if (http.status == 200) {   // Request status is 200 OK.
57       var xhtml = http.responseText;
58
59       // Next line fails with my copy of IE if the text contains a
60       // link (ie. <a href...>).  It is unclear why.  The error is:
61       // "Unknown runtime error"
62       preview.innerHTML = xhtml;
63     }
64
65     // A preview is not in progress now.
66     preview.in_progress = false;
67
68     // Have there been further updates since this preview started?  If so
69     // then we need to start another preview.
70     if (preview.updated) start_update (content_id, preview_id, preview);
71   }
72 }
73
74 // Update the preview field synchronously.
75 function synch_update (content_id, preview_id)
76 {
77   var preview = document.getElementById (preview_id);
78
79   // Get the Wiki-markup content from the content textarea.
80   var content = document.getElementById (content_id).value;
81
82   // Send the Wiki-markup to the server to be turned into XHTML.
83   var http = document.all ?
84     new ActiveXObject ('Microsoft.XMLHTTP') : new XMLHttpRequest ();
85   if (http)
86     {
87       http.open ('POST', '/_bin/preview.cmo', false);
88       http.setRequestHeader ('Content-Type',
89                              'application/x-www-form-urlencoded');
90       http.send ('content=' + encodeURIComponent (content));
91
92       if (http.readyState == 4 && http.status == 200) {
93         var xhtml = http.responseText;
94         preview.innerHTML = xhtml;
95       }
96     }
97 }
98
99 // Initialise the edit_buttons for a section.
100 // We do this in Javascript so that non-Javascript users won't see
101 // any buttons (they wouldn't be functional for them anyway).
102 function init_edit_buttons (div_id, content_id, preview_id)
103 {
104   // HTML for the edit buttons.
105   var args = "'" + content_id + "', '" + preview_id + "'";
106   var edit_buttons_html =
107     "<a onmousedown=\"link(" + args + "); return false;\" title=\"Select some text and click this to make a link\"><u>Link</u></a>" +
108     "<span class=\"spacer\"></span>" +
109     "<a onmousedown=\"bold(" + args + "); return false;\" title=\"Select some text and click this to make it bold\"><strong>Bold</strong></a>" +
110     "<a onmousedown=\"italic(" + args + "); return false;\" title=\"Select some text and click this to make it italic\"><em>Italic</em></a>" +
111     "<a onmousedown=\"strikeout(" + args + "); return false;\" title=\"Select some text and click this to strike through it\"><s>Strike</s></a>" +
112     "<span class=\"spacer\"></span>" +
113     "<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>" +
114     "<a onmousedown=\"numbered(" + args + "); return false;\" title=\"Select lines of text and click this to make it a numbered list\">1&#8212;</a>" +
115     "<br/>"
116     ;
117   var div = document.getElementById (div_id);
118   div.innerHTML = edit_buttons_html;
119 }
120
121 function replace_selection (content_id, fn, warning)
122 {
123   var textarea = document.getElementById (content_id);
124
125   if (textarea.setSelectionRange) { // Mozilla
126     var start = textarea.selectionStart;
127     var end = textarea.selectionEnd;
128     if (start != end) {
129       var text = textarea.value.substring (start, end);
130       var replacement = fn (text);
131       textarea.value = textarea.value.substring (0, start) +
132         replacement + textarea.value.substring (end);
133     } else
134       if (warning) alert (warning);
135   } else if (document.selection) { // IE
136     var range = document.selection.createRange ();
137     if (range.parentElement () == textarea && range.text != "") {
138       var text = range.text;
139       var len = text.length;
140
141       // IE6 bug workaround: If the end of the selection is the end of
142       // a line, then we must remember to append a \n character after
143       // doing the replacement.  Note the incredible lengths we have to
144       // go to here because IE is a piece of shit.
145       var dup = range.duplicate ();
146       dup.moveEnd ("character", 2);
147       append =
148         dup.text.substring (dup.text.length-3, dup.text.length-1) == "\r\n";
149
150       // Replace \r\n with just \n.
151       text = text.replace ("\r\n", "\n");
152
153       // IE's selections often include a trailing space or carriage
154       // return.  Ignore this whitespace when calling the replacement
155       // function.
156       var replacement;
157       if ((ar = text.match (/(\s+)$/))) {
158         var ws = ar[1];
159         text = text.substring (0, len - ws.length);
160         replacement = fn (text);
161         replacement = replacement + ws;
162       } else
163         replacement = fn (range.text);
164
165       // See IE bug workaround above.
166       if (append) replacement += "\n";
167
168       range.text = replacement;
169     } else
170         if (warning) alert (warning);
171   }
172 }
173
174 function link (content_id, preview_id)
175 {
176   var fn = function (text) {
177     if (ar = text.match (/^\[\[(.*)\]\]$/))
178       return ar[1];
179     else
180       return "[[" + text + "]]";
181   };
182   var warning = "Select an area of text first.";
183   replace_selection (content_id, fn, warning);
184   start_update (content_id, preview_id);
185 }
186
187 function bold (content_id, preview_id)
188 {
189   var fn = function (text) {
190     if (ar = text.match (/^<b>(.*)<\/b>$/))
191       return ar[1];
192     else
193       return "<b>" + text + "</b>";
194   };
195   var warning = "Select an area of text first.";
196   replace_selection (content_id, fn, warning);
197   start_update (content_id, preview_id);
198 }
199
200 function italic (content_id, preview_id)
201 {
202   var fn = function (text) {
203     if (ar = text.match (/^<i>(.*)<\/i>$/))
204       return ar[1];
205     else
206       return "<i>" + text + "</i>";
207   };
208   var warning = "Select an area of text first.";
209   replace_selection (content_id, fn, warning);
210   start_update (content_id, preview_id);
211 }
212
213 function strikeout (content_id, preview_id)
214 {
215   var fn = function (text) {
216     if (ar = text.match (/^<s>(.*)<\/s>$/))
217       return ar[1];
218     else
219       return "<s>" + text + "</s>";
220   };
221   var warning = "Select an area of text first.";
222   replace_selection (content_id, fn, warning);
223   start_update (content_id, preview_id);
224 }
225
226 function bullet (content_id, preview_id)
227 {
228   var fn = function (text) {
229     if (text.match (/^\*/))
230       return text.replace (/^\* /gm, "");
231     else
232       return "* " + text.replace (/\n/g, "\n* ");
233   };
234   var warning = "Select some whole lines of text first.";
235   replace_selection (content_id, fn, warning);
236   start_update (content_id, preview_id);
237 }
238
239 function numbered (content_id, preview_id)
240 {
241   var fn = function (text) {
242     if (text.match (/^#/))
243       return text.replace (/^# /gm, "");
244     else
245       return "# " + text.replace (/\n/g, "\n# ");
246   };
247   var warning = "Select some whole lines of text first.";
248   replace_selection (content_id, fn, warning);
249   start_update (content_id, preview_id);
250 }