da366f2c941c86e33907dfc30617992906e7130d
[Packages/TYPO3.CMS.git] / typo3 / sysext / t3editor / jslib / t3editor.js
1 /***************************************************************
2 * Copyright notice
3 *
4 * (c) 2007 Tobias Liebig <mail_typo3@etobi.de>
5 * All rights reserved
6 *
7 * This script is part of the TYPO3 project. The TYPO3 project is
8 * free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
12 *
13 * The GNU General Public License can be found at
14 * http://www.gnu.org/copyleft/gpl.html.
15 * A copy is found in the textfile GPL.txt and important notices to the license
16 * from the author is found in LICENSE.txt distributed with these scripts.
17 *
18 *
19 * This script is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
23 *
24 * This copyright notice MUST APPEAR in all copies of the script!
25 ***************************************************************/
26 /* t3editor.js uses the Codemirror editor.
27 */
28
29
30 // collection of all t3editor instances on the current page
31 var t3e_instances = {};
32
33 // path to the editor ext dir
34 // can be overwritten in class.tx_t3editor.php
35 var PATH_t3e = "../../../sysext/t3editor/";
36
37
38
39
40 /* Demonstration of embedding CodeMirror in a bigger application. The
41 * interface defined here is a mess of prompts and confirms, and
42 * should probably not be used in a real project.
43 */
44
45 function T3editor(textarea) {
46 var self = this;
47
48 // memorize the textarea
49 this.textarea = $(textarea);
50
51 // outer wrap around the whole t3editor
52 this.outerdiv = new Element("DIV", {
53 "class": "t3e_wrap"
54 });
55
56 // place the div before the textarea
57 this.textarea.parentNode.insertBefore(this.outerdiv, $(this.textarea));
58
59
60 // an overlay that covers the whole editor
61 this.modalOverlay = new Element("DIV", {
62 "class": "t3e_modalOverlay",
63 "id": "t3e_modalOverlay_wait"
64 });
65
66 this.modalOverlay.hide();
67 this.modalOverlay.setStyle(this.outerdiv.getDimensions());
68 this.modalOverlay.setStyle({
69 opacity: 0.8
70 });
71 this.outerdiv.appendChild(this.modalOverlay);
72
73 /*
74 // wrapping the Toolbar
75 this.toolbar_wrap = new Element("DIV", {
76 "class": "t3e_toolbar_wrap"
77 });
78 this.outerdiv.appendChild(this.toolbar_wrap);
79 */
80
81 // wrapping the linenumbers
82 this.linenum_wrap = new Element("DIV", {
83 "class": "t3e_linenum_wrap"
84 });
85 // the "linenumber" list itself
86 this.linenum = new Element("DL", {
87 "class": "t3e_linenum"
88 });
89 this.linenum_wrap.appendChild(this.linenum);
90 this.outerdiv.appendChild(this.linenum_wrap);
91
92 // wrapping the iframe
93 this.mirror_wrap = new Element("DIV", {
94 "class": "t3e_iframe_wrap"
95 });
96 this.outerdiv.appendChild(this.mirror_wrap);
97
98 // wrapping the statusbar
99 this.statusbar_wrap = new Element("DIV", {
100 "class": "t3e_statusbar_wrap"
101 });
102 this.outerdiv.appendChild(this.statusbar_wrap);
103
104 this.statusbar_title = new Element("SPAN", {
105 "class": "t3e_statusbar_title"
106 });
107 this.statusbar_wrap.appendChild(this.statusbar_title);
108 this.statusbar_title.update( this.textarea.readAttribute('alt') );
109
110 this.t3e_statusbar_status = new Element("SPAN", {
111 "class": "t3e_statusbar_status"
112 });
113 this.statusbar_wrap.appendChild(this.t3e_statusbar_status);
114 this.t3e_statusbar_status.update( '' );
115
116 var textareaDim = $(this.textarea).getDimensions();
117
118 this.linenum_wrap.setStyle({
119 height: (textareaDim.height) + 'px'
120 });
121
122 // setting options
123 var options = {
124 height: (
125 textareaDim.height
126 ) + 'px',
127 width: (
128 textareaDim.width
129 - 40 // line numbers
130 ) + 'px',
131 content: $(this.textarea).value,
132 parserfile: ["tokenizetyposcript.js", "parsetyposcript.js"],
133 stylesheet: PATH_t3e + "css/t3editor_inner.css",
134 path: PATH_t3e + "jslib/codemirror/",
135 outerEditor: this,
136 saveFunction: this.saveFunction.bind(this),
137 initCallback: this.init.bind(this)
138 };
139
140 // get the editor
141 this.mirror = new CodeMirror(this.mirror_wrap, options);
142
143 }
144
145 T3editor.prototype = {
146
147 init: function() {
148 var textareaDim = $(this.textarea).getDimensions();
149 // hide the textarea
150 this.textarea.hide();
151
152 this.resize(textareaDim.width, textareaDim.height );
153 },
154
155 // indicates is content is modified and not safed yet
156 textModified: false,
157
158 // check if code in editor has been modified since last saving
159 checkTextModified: function() {
160 if (!this.textModified) {
161 this.textModified = true;
162 this.updateLinenum();
163 }
164 },
165
166 // scroll the line numbers
167 scroll: function() {
168 var scrOfX = 0,
169 scrOfY = 0;
170 if (typeof(this.mirror.editor.win.pageYOffset) == 'number') {
171 // Netscape compliant
172 scrOfY = this.mirror.editor.win.pageYOffset;
173 scrOfX = this.mirror.editor.win.pageXOffset;
174 } else if (this.mirror.editor.doc.body && (this.mirror.editor.doc.body.scrollLeft || this.mirror.editor.doc.body.scrollTop)) {
175 // DOM compliant
176 scrOfY = this.mirror.editor.doc.body.scrollTop;
177 scrOfX = this.mirror.editor.doc.body.scrollLeft;
178 } else if (this.mirror.editor.doc.documentElement
179 && (this.mirror.editor.doc.documentElement.scrollLeft
180 || this.mirror.editor.doc.documentElement.scrollTop)) {
181 // IE6 standards compliant mode
182 scrOfY = this.mirror.editor.doc.documentElement.scrollTop;
183 scrOfX = this.mirror.editor.doc.documentElement.scrollLeft;
184 }
185 this.linenum_wrap.scrollTop = scrOfY;
186 },
187
188
189 // update the line numbers
190 updateLinenum: function(code) {
191 var theMatch;
192 if (!code) {
193 code = this.mirror.editor.container.innerHTML;
194 theMatch = code.match(/<br/gi);
195 } else {
196 theMatch = code.match(/\n/gi);
197 }
198
199 if (!theMatch) {
200 theMatch = [1];
201 } else if (Prototype.Browser.IE) {
202 theMatch.push('1');
203 }
204
205 var bodyContentLineCount = theMatch.length;
206 disLineCount = this.linenum.childNodes.length;
207 while (disLineCount != bodyContentLineCount) {
208 if (disLineCount > bodyContentLineCount) {
209 this.linenum.removeChild(this.linenum.lastChild);
210 disLineCount--;
211 } else if (disLineCount < bodyContentLineCount) {
212 ln = $(document.createElement('dt'));
213 ln.update(disLineCount + 1 + '.');
214 ln.addClassName(disLineCount % 2 == 1 ? 'even': 'odd');
215 ln.setAttribute('id', 'ln' + (disLineCount + 1));
216 this.linenum.appendChild(ln);
217 disLineCount++;
218 }
219 }
220
221 this.t3e_statusbar_status.update(
222 (this.textModified ? ' <span alt="document has been modified">*</span> ': '') + bodyContentLineCount + ' lines');
223 },
224
225 saveFunction: function() {
226 this.modalOverlay.show();
227 this.textarea.value = this.mirror.editor.getCode();
228 $('submitAjax').value = '1';
229 Form.request($(this.textarea.form), {
230 onComplete: this.saveFunctionComplete.bind(this)
231 });
232 },
233
234 // callback if ajax saving was successful
235 saveFunctionComplete: function(ajaxrequest) {
236
237 if (ajaxrequest.status == 200
238 && ajaxrequest.headerJSON.result == true) {
239
240 this.textModified = false;
241 this.updateLinenum();
242 } else {
243 alert("An error occured while saving the data.");
244 };
245 $('submitAjax').value = '0';
246 this.modalOverlay.hide();
247
248 },
249
250 // find matching bracket
251 checkBracketAtCursor: function() {
252 var cursor = this.mirror.editor.win.select.markSelection(this.mirror.editor.win);
253
254 if (!cursor || !cursor.start) return;
255
256 this.cursorObj = cursor.start;
257
258 // remove current highlights
259 Selector.findChildElements(this.mirror.editor.doc,
260 $A(['.highlight-bracket', '.error-bracket'])
261 ).each(function(item) {
262 item.className = item.className.replace(' highlight-bracket', '');
263 item.className = item.className.replace(' error-bracket', '');
264 });
265
266 if (!cursor.start || !cursor.start.node || !cursor.start.node.parentNode || !cursor.start.node.parentNode.className) {
267 return;
268 }
269
270 // if cursor is behind an bracket, we search for the matching one
271
272 // we have an opening bracket, search forward for a closing bracket
273 if (cursor.start.node.parentNode.className.indexOf('curly-bracket-open') != -1) {
274 var maybeMatch = cursor.start.node.parentNode.nextSibling;
275 var skip = 0;
276 while (maybeMatch) {
277 if (maybeMatch.className.indexOf('curly-bracket-open') != -1) {
278 skip++;
279 }
280 if (maybeMatch.className.indexOf('curly-bracket-close') != -1) {
281 if (skip > 0) {
282 skip--;
283 } else {
284 maybeMatch.className += ' highlight-bracket';
285 cursor.start.node.parentNode.className += ' highlight-bracket';
286 break;
287 }
288 }
289 maybeMatch = maybeMatch.nextSibling;
290 }
291 }
292
293 // we have a closing bracket, search backward for an opening bracket
294 if (cursor.start.node.parentNode.className.indexOf('curly-bracket-close') != -1) {
295 var maybeMatch = cursor.start.node.parentNode.previousSibling;
296 var skip = 0;
297 while (maybeMatch) {
298 if (maybeMatch.className.indexOf('curly-bracket-close') != -1) {
299 skip++;
300 }
301 if (maybeMatch.className.indexOf('curly-bracket-open') != -1) {
302 if (skip > 0) {
303 skip--;
304 } else {
305 maybeMatch.className += ' highlight-bracket';
306 cursor.start.node.parentNode.className += ' highlight-bracket';
307 break;
308 }
309 }
310 maybeMatch = maybeMatch.previousSibling;
311 }
312 }
313
314 if (cursor.start.node.parentNode.className.indexOf('curly-bracket-') != -1
315 && maybeMatch == null) {
316 cursor.start.node.parentNode.className += ' error-bracket';
317 }
318 },
319
320 // close an opend bracket
321 autoCloseBracket: function(prevNode) {
322 if (prevNode && prevNode.className.indexOf('curly-bracket-open') != -1) {
323 this.mirror.editor.win.select.insertNewlineAtCursor(this.mirror.editor.win);
324 this.mirror.editor.win.select.insertTextAtCursor(this.mirror.editor.win, "}");
325 }
326 },
327
328 // click event. Refresh cursor object.
329 click: function() {
330 this.refreshCursorObj();
331 this.checkBracketAtCursor();
332 },
333
334
335 refreshCursorObj: function() {
336 var cursor = this.mirror.editor.win.select.markSelection(this.mirror.editor.win);
337 this.cursorObj = cursor.start;
338 },
339
340 // toggle between the textarea and t3editor
341 toggleView: function(checkboxEnabled) {
342 if (checkboxEnabled) {
343 this.textarea.value = this.mirror.editor.getCode();
344 this.outerdiv.hide();
345 this.textarea.show();
346 /* this.saveButtons.each(function(button) {
347 Event.stopObserving(button, 'click', this.saveAjaxEvent);
348 }.bind(this));
349 */
350 } else {
351 this.mirror.editor.importCode(this.textarea.value);
352 this.textarea.hide();
353 this.outerdiv.show();
354 /* this.saveButtons.each(function(button) {
355 Event.observe(button, 'click', this.saveAjaxEvent);
356 }.bind(this));
357 */
358 }
359 },
360
361
362 resize: function(width, height) {
363 if (this.outerdiv) {
364 newheight = (height - 1);
365 newwidth = (width + 11);
366 if (Prototype.Browser.IE) newwidth = newwidth + 8;
367
368 $(this.outerdiv).setStyle({
369 height: newheight + 'px',
370 width: newwidth + 'px'
371 });
372
373 this.linenum_wrap.setStyle({
374 height: (height - 22) + 'px' // less footer height
375 });
376
377 numwwidth = this.linenum_wrap.getWidth();
378
379 if (Prototype.Browser.IE) numwwidth = numwwidth - 17;
380 if (!Prototype.Browser.IE) numwwidth = numwwidth - 11;
381
382 $(this.mirror_wrap.firstChild).setStyle({
383 'height': ((height - 22) + 'px'),
384 'width': ((width - numwwidth) + 'px')
385 });
386
387 $(this.modalOverlay).setStyle(this.outerdiv.getDimensions());
388
389 }
390
391 },
392
393 // toggle between normal view and fullscreen mode
394 toggleFullscreen: function() {
395 if (this.outerdiv.hasClassName('t3e_fullscreen')) {
396 // turn fullscreen off
397
398 // unhide the scrollbar of the body
399 this.outerdiv.offsetParent.setStyle({
400 overflow: ''
401 });
402
403 this.outerdiv.removeClassName('t3e_fullscreen');
404 h = this.textarea.getDimensions().height;
405 w = this.textarea.getDimensions().width;
406
407 } else {
408 // turn fullscreen on
409 this.outerdiv.addClassName('t3e_fullscreen');
410 h = this.outerdiv.offsetParent.getHeight();
411 w = this.outerdiv.offsetParent.getWidth();
412
413 // less scrollbar width
414 w = w - 13;
415
416 // hide the scrollbar of the body
417 this.outerdiv.offsetParent.setStyle({
418 overflow: 'hidden'
419 });
420 this.outerdiv.offsetParent.scrollTop = 0;
421 }
422 this.resize(w, h);
423 }
424
425 } // T3editor.prototype
426
427
428 // fix prototype issue: ajax request do not respect charset of the page and screw up code
429 if (document.characterSet != "UTF-8") {
430 encodeURIComponent = escape;
431 }
432
433
434 // ------------------------------------------------------------------------
435
436
437 /**
438 * toggle between enhanced editor (t3editor) and simple textarea
439 */
440 function t3editor_toggleEditor(checkbox, index) {
441 if (!Prototype.Browser.MobileSafari
442 && !Prototype.Browser.IE
443 && !Prototype.Browser.WebKit) {
444
445 if (index == undefined) {
446 $$('textarea.t3editor').each(
447 function(textarea, i) {
448 t3editor_toggleEditor(checkbox, i);
449 });
450 } else {
451 if (t3e_instances[index] != undefined) {
452 var t3e = t3e_instances[index];
453 t3e.toggleView(checkbox.checked);
454 } else if (!checkbox.checked) {
455 var t3e = new T3editor($$('textarea.t3editor')[index], index);
456 t3e_instances[index] = t3e;
457 }
458 }
459 }
460 }
461
462 // ------------------------------------------------------------------------
463
464
465 if (!Prototype.Browser.MobileSafari
466 // && !Prototype.Browser.IE
467 && !Prototype.Browser.WebKit) {
468
469 // everything ready: turn textarea's into fancy editors
470 Event.observe(window, 'load',
471 function() {
472 $$('textarea.t3editor').each(
473 function(textarea, i) {
474 if ($('t3editor_disableEditor_' + (i + 1) + '_checkbox')
475 && !$('t3editor_disableEditor_' + (i + 1) + '_checkbox').checked) {
476 var t3e = new T3editor(textarea);
477 t3e_instances[i] = t3e;
478 }
479 }
480 );
481 }
482 );
483 }
484