Dynamic updates. The Javascript code estimates the time taken to
[cocanwiki.git] / html / _js / editor.js
1 /* Javascript for OCAMLWIKI.
2  * Copyright (C) 2004 Merjis Ltd.
3  * $Id: editor.js,v 1.5 2004/11/14 14:23:27 rich Exp $
4  */
5
6 // Delay in milliseconds before updating.
7 // We will adjust this during editing to take into account the actual
8 // average round trip time measured during update_preview_now.
9 var delay = 1000;
10
11 // This is used to measure the average round trip time.
12 var rtt_sum = 0;
13 var rtt_n = 0;
14 var rtt = 0;                    // Actual average RTT measured (ms).
15
16 function update_preview (content_id, preview_id)
17 {
18   // Updating is quite expensive, so only update after a period of apparent
19   // inactivity.
20   var preview = document.getElementById (preview_id);
21   if (preview.timer) clearTimeout (preview.timer);
22   preview.timer =
23     setTimeout ("update_preview_now ('" + content_id + "', '" +
24                 preview_id + "')", delay);
25 }
26
27 function update_preview_now (content_id, preview_id)
28 {
29   // Remove the timer.
30   var preview = document.getElementById (preview_id);
31   if (preview.timer) preview.timer = null;
32
33   // Get the Wiki-markup content from the content textarea.
34   var content = document.getElementById (content_id).value;
35
36   // Time how long it takes to send the document to the server.
37   var start_time = (new Date()).getTime();
38
39   // Send the Wiki-markup to the server to be turned into XHTML.
40   var http = document.all ?
41     new ActiveXObject ('Microsoft.XMLHTTP') : new XMLHttpRequest ();
42   if (http)
43     {
44       http.open ('POST', '/_bin/preview.cmo', false);
45       http.setRequestHeader ('Content-Type',
46                              'application/x-www-form-urlencoded');
47       http.send ('content=' + encodeURIComponent (content));
48
49       var xhtml = http.responseText;
50
51       // Next line fails with my copy of IE if the text contains a
52       // link (ie. <a href...>).  It is unclear why.  The error is:
53       // "Unknown runtime error"
54       preview.innerHTML = xhtml;
55     }
56
57   // Finish timer and recompute RTT.
58   var end_time = (new Date()).getTime();
59   rtt_sum += end_time - start_time;
60   rtt_n++;
61   rtt = rtt_sum / rtt_n;
62
63   // Recompute the next delay period.
64   delay = rtt * 2;
65 }
66
67 // Initialise the edit_buttons for a section.
68 // We do this in Javascript so that non-Javascript users won't see
69 // any buttons (they wouldn't be functional for them anyway).
70 function init_edit_buttons (div_id, content_id, preview_id)
71 {
72   // HTML for the edit buttons.
73   var args = "'" + content_id + "', '" + preview_id + "'";
74   var edit_buttons_html =
75     "<a onmousedown=\"link(" + args + "); return false;\" title=\"Select some text and click this to make a link\"><u>Link</u></a>" +
76     "<span class=\"spacer\"></span>" +
77     "<a onmousedown=\"bold(" + args + "); return false;\" title=\"Select some text and click this to make it bold\"><strong>Bold</strong></a>" +
78     "<a onmousedown=\"italic(" + args + "); return false;\" title=\"Select some text and click this to make it italic\"><em>Italic</em></a>" +
79     "<a onmousedown=\"strikeout(" + args + "); return false;\" title=\"Select some text and click this to strike through it\"><s>Strike</s></a>" +
80     "<span class=\"spacer\"></span>" +
81     "<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>" +
82     "<a onmousedown=\"numbered(" + args + "); return false;\" title=\"Select lines of text and click this to make it a numbered list\">1&#8212;</a>" +
83     "<br/>"
84     ;
85   var div = document.getElementById (div_id);
86   div.innerHTML = edit_buttons_html;
87 }
88
89 function replace_selection (content_id, fn, warning)
90 {
91   var textarea = document.getElementById (content_id);
92
93   if (textarea.setSelectionRange) { // Mozilla
94     var start = textarea.selectionStart;
95     var end = textarea.selectionEnd;
96     if (start != end) {
97       var text = textarea.value.substring (start, end);
98       var replacement = fn (text);
99       textarea.value = textarea.value.substring (0, start) +
100         replacement + textarea.value.substring (end);
101     } else
102       if (warning) alert (warning);
103   } else if (document.selection) { // IE
104     var range = document.selection.createRange ();
105     if (range.parentElement () == textarea && range.text != "") {
106       var text = range.text;
107       var len = text.length;
108
109       // IE6 bug workaround: If the end of the selection is the end of
110       // a line, then we must remember to append a \n character after
111       // doing the replacement.  Note the incredible lengths we have to
112       // go to here because IE is a piece of shit.
113       var dup = range.duplicate ();
114       dup.moveEnd ("character", 2);
115       append =
116         dup.text.substring (dup.text.length-3, dup.text.length-1) == "\r\n";
117
118       // Replace \r\n with just \n.
119       text = text.replace ("\r\n", "\n");
120
121       // IE's selections often include a trailing space or carriage
122       // return.  Ignore this whitespace when calling the replacement
123       // function.
124       var replacement;
125       if ((ar = text.match (/(\s+)$/))) {
126         var ws = ar[1];
127         text = text.substring (0, len - ws.length);
128         replacement = fn (text);
129         replacement = replacement + ws;
130       } else
131         replacement = fn (range.text);
132
133       // See IE bug workaround above.
134       if (append) replacement += "\n";
135
136       range.text = replacement;
137     } else
138         if (warning) alert (warning);
139   }
140 }
141
142 function link (content_id, preview_id)
143 {
144   var fn = function (text) {
145     if (ar = text.match (/^\[\[(.*)\]\]$/))
146       return ar[1];
147     else
148       return "[[" + text + "]]";
149   };
150   var warning = "Select an area of text first.";
151   replace_selection (content_id, fn, warning);
152   update_preview_now (content_id, preview_id);
153 }
154
155 function bold (content_id, preview_id)
156 {
157   var fn = function (text) {
158     if (ar = text.match (/^<b>(.*)<\/b>$/))
159       return ar[1];
160     else
161       return "<b>" + text + "</b>";
162   };
163   var warning = "Select an area of text first.";
164   replace_selection (content_id, fn, warning);
165   update_preview_now (content_id, preview_id);
166 }
167
168 function italic (content_id, preview_id)
169 {
170   var fn = function (text) {
171     if (ar = text.match (/^<i>(.*)<\/i>$/))
172       return ar[1];
173     else
174       return "<i>" + text + "</i>";
175   };
176   var warning = "Select an area of text first.";
177   replace_selection (content_id, fn, warning);
178   update_preview_now (content_id, preview_id);
179 }
180
181 function strikeout (content_id, preview_id)
182 {
183   var fn = function (text) {
184     if (ar = text.match (/^<s>(.*)<\/s>$/))
185       return ar[1];
186     else
187       return "<s>" + text + "</s>";
188   };
189   var warning = "Select an area of text first.";
190   replace_selection (content_id, fn, warning);
191   update_preview_now (content_id, preview_id);
192 }
193
194 function bullet (content_id, preview_id)
195 {
196   var fn = function (text) {
197     if (text.match (/^\*/))
198       return text.replace (/^\* /gm, "");
199     else
200       return "* " + text.replace (/\n/g, "\n* ");
201   };
202   var warning = "Select some whole lines of text first.";
203   replace_selection (content_id, fn, warning);
204   update_preview_now (content_id, preview_id);
205 }
206
207 function numbered (content_id, preview_id)
208 {
209   var fn = function (text) {
210     if (text.match (/^#/))
211       return text.replace (/^# /gm, "");
212     else
213       return "# " + text.replace (/\n/g, "\n# ");
214   };
215   var warning = "Select some whole lines of text first.";
216   replace_selection (content_id, fn, warning);
217   update_preview_now (content_id, preview_id);
218 }