root/branches/release-39/mt-static/js/archetype_editor.js @ 2526

Revision 2526, 13.1 kB (checked in by ddavis, 18 months ago)

fixed: IE focuses the editor when saving; we refocus the active element afterwards. BugzID:79983

Line 
1/*
2# Movable Type (r) Open Source (C) 2001-2008 Six Apart, Ltd.
3# This program is distributed under the terms of the
4# GNU General Public License, version 2.
5#
6# $Id$
7*/
8
9App.singletonConstructor =
10MT.App = new Class( MT.App, {
11
12    initEditor: function() {
13        if ( this.constructor.Editor && DOM.getElement( "editor-content" ) ) {
14           
15            var mode = DOM.getElement( "convert_breaks" );
16            DOM.addEventListener( mode, "change", this.getIndirectEventListener( "setTextareaMode" ) );
17       
18            /* special case */
19            window.cur_text_format = mode.value;
20       
21            this.editorMode = ( mode.value == "richtext" ) ? "iframe" : "textarea";
22           
23            this.editor = this.addComponent( new MT.App.Editor( "editor-content", this.editorMode ) );
24            this.editor.textarea.setTextMode( mode.value );
25       
26            this.editorInput = {
27                content: DOM.getElement( "editor-input-content" ),
28                extended: DOM.getElement( "editor-input-extended" )
29            };
30       
31            if ( this.editorInput.content.value )
32                this.editor.setHTML( this.editorInput.content.value );
33        }
34    }
35
36} );
37
38
39MT.App.Editor = new Class( Editor, {
40   
41
42    setChanged: function() {
43        this.changed = true;
44        log('changed');
45        app.setDirty();
46    }
47
48
49} );
50   
51   
52MT.App.Editor.Toolbar = new Class( Editor.Toolbar, {
53       
54
55    eventClick: function( event ) {
56        var command = this.getMouseEventCommand( event );
57        if ( !command )
58            return event.stop();
59
60        switch( command ) {
61
62            case "insertEmail":
63                var link = this.editor.getSelectedLink();
64                if ( link ) 
65                    this.editEmail( link );
66                else 
67                    this.createEmailLink();
68                break;
69
70            case "openDialog":
71                app.openDialog( event.commandElement.getAttribute( "mt:dialog-params" ) );
72                break;
73
74            case "openFlyout":
75                var name = event.commandElement.getAttribute( "mt:flyout" );
76                var el = DOM.getElement( name );
77                if ( !defined( el ) )
78                    return;
79
80                app.closeFlyouts( event.target );
81
82                DOM.removeClassName( el, "hidden" );
83                app.targetElement = event.target;
84                app.applyAutolayouts( el );
85                app.targetElement = null;
86
87                app.openFlyouts.add( name );
88
89                break;
90
91            default:
92                return arguments.callee.applySuper( this, arguments );
93       
94        }
95
96        return event.stop();
97    },
98   
99   
100    editEmail: function( linkElement ) {
101        this.createEmailLink( linkElement.href, true, linkElement );
102    },
103
104
105    mailtoRegexp: /^mailto:/i,
106
107    createEmailLink: function( url, textSelected, anchor ) {
108        var linkedText = "";
109        if( !textSelected )
110            textSelected = this.editor.isTextSelected();
111        if( !url )
112            url = "";
113
114        url = url.replace( this.mailtoRegexp, "" );
115
116        url = prompt( Editor.strings.enterEmailAddress, url );
117        if( !url )
118            return false;
119        if( !textSelected ) 
120            linkedText = prompt( Editor.strings.enterTextToLinkTo, "" ); 
121
122        this.insertLink( { url: "mailto:" + url, linkedText: linkedText, anchor: anchor } );
123    }
124
125
126} );
127
128
129MT.App.Editor.Textarea = new Class( Editor.Textarea, {
130
131    currentTextMode: "_DEFAULT_",
132   
133
134    getHTML: function() {
135        /* we can refocus the last selected element,
136         * because the superClass getHTML will focus the editor (if IE) */
137        var refocus;
138        if ( document.activeElement )
139            refocus = document.activeElement;
140       
141        var html = arguments.callee.applySuper( this, arguments );
142       
143        try { if ( refocus ) refocus.focus(); } catch(e) { };
144
145        return html;
146    },
147
148
149    setTextMode: function( mode ) {
150        var editorContent = DOM.getElement( "editor-content" );
151        DOM.removeClassName( editorContent, /^editor-textmode-.*/ );
152        if ( this[ mode + "Command" ] )
153            DOM.addClassName( editorContent, "editor-textmode-" + mode.replace( /_/g, "-" ) );
154        this.currentTextMode = mode;
155    },
156
157
158    execCommand: function( command, userInterface, argument ) {
159        if ( this.currentTextMode && this[ this.currentTextMode + "Command" ] ) {
160            log('executeing command: ' + command + ' in mode: '+this.currentTextMode );
161            this[ this.currentTextMode + "Command" ].apply( this, arguments );
162            return;
163        }
164
165        var text = this.getSelectedText();
166        if ( !defined( text ) )
167            text = '';
168        switch ( command ) {
169           
170            case "fontSizeSmaller":
171                this.setSelection( "<small>" + text + "</small>" );
172                break;
173
174            case "fontSizeLarger":
175                this.setSelection( "<big>" + text + "</big>" );
176                break;
177   
178            default:
179                arguments.callee.applySuper( this, arguments );
180                break;       
181        }
182    },
183   
184
185
186    "markdown_with_smartypantsCommand": function() {
187        this.markdownCommand.apply( this, arguments );
188    },
189
190
191    markdownCommand: function( command, userInterface, argument ) {
192        var text = this.getSelectedText();
193        if ( !defined( text ) )
194            text = '';
195        switch ( command ) {
196           
197            case "bold":
198                this.setSelection( "**" + text + "**" );
199                break;
200
201            case "italic":
202                this.setSelection( "*" + text + "*" );
203                break;
204
205            case "createLink":
206                this.setSelection( "[" + text + "](" + argument + ")" );
207                break;
208           
209            case "indent":
210                var list = text.split( /\r?\n/ );
211                for ( var i = 0; i < list.length; i++ )
212                    list[ i ] = "> " + list[ i ];
213                this.setSelection( list.join( "\n" ) );
214                break;
215           
216            case "insertUnorderedList":
217            case "insertOrderedList":
218                var list = text.split( /\r?\n/ );
219                var ordered = ( command == "insertOrderedList" ) ? true : false;
220                for ( var i = 0; i < list.length; i++ )
221                    list[ i ] = " " + ( ordered ? ( ( i + 1 ) + ". " ) : "-" ) + " " + list[ i ];
222                this.setSelection( "\n" + list.join( "\n" ) + "\n" );
223                break;
224        }
225    },
226
227
228    "textile_2Command": function( command, userInterface, argument ) {
229        var text = this.getSelectedText();
230        if ( !defined( text ) )
231            text = '';
232        switch ( command ) {
233           
234            case "bold":
235                this.setSelection( "**" + text + "**" );
236                break;
237
238            case "italic":
239                this.setSelection( "_" + text + "_" );
240                break;
241
242            case "strikethrough":
243                this.setSelection( "-" + text + "-" );
244                break;
245           
246            case "createLink":
247                this.setSelection( '"' + text + '":' + argument );
248                break;
249           
250            case "indent":
251                this.setSelection( "bq. " + text );
252                break;
253           
254            case "underline":
255                this.setSelection( "<u>" + text + "</u>" );
256                break;
257           
258            case "insertUnorderedList":
259            case "insertOrderedList":
260                var list = text.split( /\r?\n/ );
261                var ordered = ( command == "insertOrderedList" ) ? true : false;
262                for ( var i = 0; i < list.length; i++ )
263                    list[ i ] = ( ordered ? "#" : "*" ) + " " + list[ i ];
264                this.setSelection( "\n" + list.join( "\n" ) + "\n" );
265                break;
266
267            case "justifyLeft":
268                this.setSelection( "p< " + text );
269                break;
270
271            case "justifyCenter":
272                this.setSelection( "p= " + text );
273                break;
274
275            case "justifyRight":
276                this.setSelection( "p> " + text );
277                break;
278           
279            case "fontSizeSmaller":
280                this.setSelection( "<small>" + text + "</small>" );
281                break;
282
283            case "fontSizeLarger":
284                this.setSelection( "<big>" + text + "</big>" );
285                break;
286        }
287    }
288
289} );
290
291
292
293MT.App.Editor.Iframe = new Class( Editor.Iframe, {
294
295
296    initObject: function() {
297        arguments.callee.applySuper( this, arguments );
298        this.isWebKit = navigator.userAgent.toLowerCase().match(/webkit/);
299    },
300   
301   
302    eventFocusIn: function( event ) {
303       this.eventFocus( event );
304    },
305
306
307    eventFocus: function( event ) {
308        if ( this.editor.mode == "textarea" )
309            this.editor.focus();
310    },
311
312
313    eventClick: function( event ) {
314        /* for safari */
315        if ( this.isWebKit && event.target.nodeName == "A" )
316            return event.stop();
317        return arguments.callee.applySuper( this, arguments );
318    },
319   
320   
321    eventKeyPress: function( event ) {
322        /* safari forward delete */
323        if ( this.isWebKit && event.keyCode == 63272 )
324            return event.stop();
325    },
326   
327   
328    eventKeyDown: function( event ) {
329        /* safari forward delete */
330        if ( this.isWebKit && event.keyCode == 46 ) {
331            this.document.execCommand( "forwardDelete", false, true );
332            return false;
333        }
334    },
335
336    eventKeyUp: function( event ) {
337        /* safari always makes this event. ignore for language input method */
338        if ( this.isWebKit ) {
339            return false;
340        }
341    },
342
343    extendedExecCommand: function( command, userInterface, argument ) {
344        switch( command ) {
345
346            case "fontSizeSmaller":     
347                this.changeFontSizeOfSelection( false );
348                break;
349
350            case "fontSizeLarger":
351                this.changeFontSizeOfSelection( true );
352                break;
353           
354            default:
355                return arguments.callee.applySuper( this, arguments );
356       
357        }
358    },
359
360
361    mutateFontSize: function( element, bigger ) {
362        // Basic settings:
363        var goSmaller = 0.8;
364        var goBigger = 1.25;
365        var biggest = Math.pow( goBigger, 3 );
366        var smallest = Math.pow( goSmaller, 3);
367        var defaultSize = bigger ? goBigger + "em" : goSmaller + "em";
368
369        // Initial detection, rejection, adjusting:
370        var fontSize = element.style.fontSize.match( /([\d\.]+)(%|em|$)/ );
371        if( fontSize == null || isNaN( fontSize[ 1 ] ) ) // "px" sizes are rejected.
372            return defaultSize; // A browser problem or bad user data would lead to "NaN" fontSize.
373       
374        var size;
375        if( fontSize[ 2 ] == "%" )
376            size = fontSize[ 1 ] / 100; // Convert to "em" units.
377        else if( fontSize[ 2 ] == "em" || fontSize[ 2 ] == "" )
378            size = fontSize[ 1 ];
379       
380        // Mutation:
381        var factor = bigger ? goBigger : goSmaller;
382        size = size * factor;
383       
384        if( size > biggest ) 
385            size = biggest;
386        else if( size < smallest ) 
387            size = smallest;
388
389        return size + "em";                           
390    },
391
392   
393    changeFontSizeOfSelection: function( bigger ) {
394        var bogus = "-editor-proxy";
395        this.document.execCommand( "fontName", false, bogus );
396        var elements = this.document.getElementsByTagName( "font" );
397        for( var i = 0; i < elements.length; i++ ) {
398            var element = elements[ i ];
399            if( element.face == bogus ) {
400                element.removeAttribute( "face" );
401                element.style.fontSize = this.mutateFontSize( element, bigger );
402            }
403        }
404    },
405
406
407    getHTML: function() {
408        var html = this.document.body.innerHTML;
409
410        // cleanup innerHTML garbage browser regurgitates
411        // #1 - lowercase tag names (open and closing tags)
412        html = html.replace(/<\/?[A-Z0-9]+[\s>]/g, function (m) {
413            return m.toLowerCase();
414        });
415
416        // #2 - lowercase attribute names
417        html = html.replace(/(<[\w\d]+\s+)([^>]+)>/g, function (x, m1, m2) {
418            return m1 + m2.replace(/\b([\w\d:-]+)\s*=\s*(?:'([^']*?)'|"([^"]*?)"|(\S+))/g, function (x, m1, m2, m3, m4) {
419                if ( !m2 ) m2 = ''; // for ie
420                if ( !m3 ) m3 = ''; // for ie
421                if ( !m4 ) m4 = ''; // for ie
422                return m1.toLowerCase() + '="' + m2 + m3 + m4 + '"';
423            }) + ">";
424        });
425
426        // #3 - close singlet tags for img, br, input, param, hr
427        html = html.replace(/<(br|img|input|param)([^>]+)?([^\/])?>/g, "<$1$2$3 />");
428
429        // #4 - get absolute path and delete from converted URL
430        this.document.body.innerHTML = '<a href="dummy.html"></a>';
431        var path = this.document.body.innerHTML;
432        path = path.toLowerCase();
433        path = path.replace(/<a href="(.*)dummy.html"><\/a>/, "$1");
434        var regex = new RegExp(path, "g");
435        html = html.replace(regex, "");
436        this.document.body.innerHTML = html;
437        return html;
438    }
439} );
Note: See TracBrowser for help on using the browser.