root/branches/athena/mt-static/js/edit.js @ 1092

Revision 1092, 31.0 kB (checked in by hachi, 2 years ago)

Merging release-15 to athena branch. svn merge -r59987:60375 http://svn.sixapart.com/repos/eng/movabletype/branches/release-15 .

Line 
1
2
3App.singletonConstructor =
4MT.App = new Class( MT.App, {
5
6
7    currentEditor: "content",
8    editorMode: "textarea",
9   
10
11    initComponents: function() {
12        arguments.callee.applySuper( this, arguments );
13
14        var cats;
15        if ( this.constructor.CategorySelector && ( cats = MT.App.categoryList ) ) {
16            /* cache all the categories */
17            this.catCache = new Cache( cats.length + 50 );
18            for ( var i = 0; i < cats.length; i++ )
19                this.catCache.setItem( 'cat:'+cats[ i ].id, cats[ i ] );
20            if ( DOM.getElement( "folder-selector" ) ) {
21                this.categorySelector = this.addComponent( new this.constructor.CategorySelector( "folder-selector", "categorySelectorList" ) );
22            } else
23                this.categorySelector = this.addComponent( new this.constructor.CategorySelector( "category-selector", "categorySelectorList" ) );
24        }
25
26        var editorContent;
27        if ( this.constructor.Editor && ( editorContent = DOM.getElement( "editor-content" ) ) ) {
28           
29            var mode = DOM.getElement( "convert_breaks" );
30            DOM.addEventListener( mode, "change", this.getIndirectEventListener( "setTextareaMode" ) );
31
32            /* special case */
33            window.cur_text_format = mode.value;
34
35            this.editorMode = ( mode.value == "richtext" ) ? "iframe" : "textarea";
36           
37            this.editor = this.addComponent( new this.constructor.Editor( "editor-content", this.editorMode ) );
38            this.editor.textarea.setTextMode( mode.value );
39
40            this.editorInput = {
41                content: DOM.getElement( "editor-input-content" ),
42                extended: DOM.getElement( "editor-input-extended" )
43            };
44
45            if ( this.editorInput.content.value )
46                this.editor.setHTML( this.editorInput.content.value );
47
48        }
49
50        if ( this.constructor.CategoryList && DOM.getElement( "category-list" ) ) {
51            this.catList = new this.constructor.CategoryList( "category-list" );
52            this.setDelegate( "categoryList", this.catList );
53            this.catList.redraw( this.catCache );
54        }
55       
56        if ( DOM.getElement( "calendar" ) )
57            this.calendar = new this.constructor.Calendar( "calendar", "calendar" );
58    },
59
60   
61    destroyObject: function() {
62        this.categorySelector = null;
63        this.catList = null;
64        this.editor = null;
65        this.editorInput = null;
66        arguments.callee.applySuper( this, arguments );
67    },
68
69   
70    eventBeforeUnload: function( event ) {
71        if ( this.editor ) {
72            if ( this.editor.changed ) 
73                this.changed = true;
74       
75            /* preserve what they changed ( if they hit back ) */
76            if ( this.changed )
77                this.saveHTML( false );
78        }
79
80        return arguments.callee.applySuper( this, arguments );
81    },
82
83
84    eventSubmit: function( event ) {
85        var r = arguments.callee.applySuper( this, arguments );
86        this.saveHTML( true );
87        return r;
88    },
89
90
91    eventClick: function( event ) {
92        var command = this.getMouseEventCommand( event );
93        switch( command ) {
94           
95            case "openCategorySelector":
96                this.categorySelector.open( null, Function.stub, event.commandElement );
97                break;
98           
99            /* editor commands */
100            case "setModeTextarea":
101                this.editor.setMode("textarea");
102                break;
103
104            case "setModeIframe":
105                this.editor.setMode("iframe");
106                break;
107
108            case "doRemoveItems":
109                /* only used for entry edit */
110                var form = DOM.getFirstAncestorByTagName( event.target, "form", true );
111                if ( !form )
112                    return;
113
114                var e = event.target;
115                if( !doRemoveItems(
116                        form,
117                        e.getAttribute( "mt:object-singular" ),
118                        e.getAttribute( "mt:object-plural" ),
119                        false,
120                        {
121                            'return_args': '__mode=list_'+e.getAttribute( "mt:object-type" )
122                                +'&amp;blog_id='+e.getAttribute( "mt:blog-id" )
123                        } ) )
124                    return event.stop();
125                break;
126
127            case "openCalendarCreatedOn":
128                this.calendar.open(
129                    {
130                        date: DOM.getElement( "created-on" ).value
131                    },
132                    this.getIndirectMethod( "handleCreatedOnDate" ),
133                    event.commandElement
134                );
135                break;
136
137            default:
138                return arguments.callee.applySuper( this, arguments );
139
140        }
141        return event.stop();
142    },
143
144
145    handleCreatedOnDate: function( date ) {
146        if ( !date )
147            return;
148
149        DOM.getElement( "created-on" ).value
150            = date.toISOString().replace( /^(.+)T.*/, "$1" );
151
152        this.changed = true;
153    },
154   
155   
156    saveHTML: function( resetChanged ) {
157        if ( !this.editor )
158            return;
159       
160        this.fixHTML();
161
162        this.editorInput[ this.currentEditor ].value = this.editor.getHTML();
163
164        if ( resetChanged )
165            this.changed = this.editor.changed = false;
166    },
167
168
169    fixHTML: function( inserted ) {
170        /* fix firefox's tendency to convert the src attribute to relative */
171        /* anchor tag is also similar */
172        if ( window.controllers && this.editor.mode == "iframe" ) {
173            log.debug('fixing relative path in image tags, and setting contentEditable false');
174            var imgs;
175            var ancs;
176            var forms = [];
177            if ( inserted && inserted.getElementsByTagName ) {
178                log.debug('using inserted method');
179                imgs = inserted.getElementsByTagName( "img" );
180                ancs = inserted.getElementsByTagName( "a" );
181                forms = inserted.getElementsByTagName( "form" );
182                if ( forms )
183                    forms = Array.fromPseudo( forms );
184                if ( inserted.tagName && inserted.tagName.toLowerCase() == "form" )
185                    forms.push( inserted );
186            } else {
187                log.debug('using iframe method');
188                imgs = this.editor.iframe.document.getElementsByTagName( "img" );
189                ancs = this.editor.iframe.document.getElementsByTagName( "a" );
190                forms = this.editor.iframe.document.getElementsByTagName( "form" );
191            }
192               
193            if ( imgs )
194                for ( var i = 0; i < imgs.length; i++ )
195                    imgs[ i ].src = imgs[ i ].src;
196               
197            if ( ancs )
198                for ( var i = 0; i < ancs.length; i++ )
199                    ancs[ i ].href = ancs[ i ].href;
200
201            if ( forms )
202                for ( var i = 0; i < forms.length; i++ )
203                    forms[ i ].setAttribute( "contentEditable", false );
204        }
205    },
206
207
208    insertHTML: function( html, field ) {
209        /* field is ignored now, we have one editor */
210        this.fixHTML( this.editor.insertHTML( html ) );
211    },
212
213
214    setEditor: function( name ) {
215        this.saveHTML( false );
216        this.currentEditor = name;
217        this.editor.setHTML( this.editorInput[ this.currentEditor ].value );
218    },
219
220
221    autoSave: function() {
222        this.saveHTML( false );
223        return arguments.callee.applySuper( this, arguments );
224    },
225
226
227    resizeComplete: function( target, xStart, yStart, x, y, width, height ) {
228        arguments.callee.applySuper( this, arguments );
229
230        if ( target.id == "editor-content-enclosure" ) {
231            /* TODO remove this, and set the two fields to 100% height */
232            var es = [ "editor-content-textarea", "editor-content-iframe" ];
233            for ( var i = 0; i < es.length; i++ ) {
234                var element = DOM.getElement( es[ i ] );
235                if ( !element )
236                    continue;
237                DOM.setHeight( element, height );
238            }
239        }
240    },
241
242
243    setTextareaMode: function( event ) {
244        this.editor.textarea.setTextMode( event.target.value );
245    }
246
247
248} );
249
250
251MT.App.Editor = new Class( Editor, {
252   
253
254    setChanged: function() {
255        this.changed = true;
256        app.setDirty();
257    }
258
259
260} );
261   
262   
263MT.App.Editor.Toolbar = new Class( Editor.Toolbar, {
264       
265
266    eventClick: function( event ) {
267        var command = this.getMouseEventCommand( event );
268        if ( !command )
269            return event.stop();
270
271        switch( command ) {
272
273            case "insertEmail":
274                var link = this.editor.getSelectedLink();
275                if ( link ) 
276                    this.editEmail( link );
277                else 
278                    this.createEmailLink();
279                break;
280
281            case "openDialog":
282                app.openDialog( event.commandElement.getAttribute( "mt:dialog-params" ) );
283                break;
284
285            default:
286                return arguments.callee.applySuper( this, arguments );
287       
288        }
289
290        return event.stop();
291    },
292   
293   
294    editEmail: function( linkElement ) {
295        this.createEmailLink( linkElement.href, true, linkElement );
296    },
297
298
299    mailtoRegexp: /^mailto:/i,
300
301    createEmailLink: function( url, textSelected, anchor ) {
302        var linkedText = "";
303        if( !textSelected )
304            textSelected = this.editor.isTextSelected();
305        if( !url )
306            url = "";
307
308        url = url.replace( this.mailtoRegexp, "" );
309
310        url = prompt( Editor.strings.enterEmailAddress, url );
311        if( !url )
312            return false;
313        if( !textSelected ) 
314            linkedText = prompt( Editor.strings.enterTextToLinkTo, "" ); 
315
316        this.insertLink( { url: "mailto:" + url, linkedText: linkedText, anchor: anchor } );
317    }
318
319
320} );
321
322
323MT.App.Editor.Textarea = new Class( Editor.Textarea, {
324
325    currentTextMode: "_DEFAULT_",
326   
327
328    setTextMode: function( mode ) {
329        var editorContent = DOM.getElement( "editor-content" );
330        DOM.removeClassName( editorContent, /^editor-textmode-.*/ );
331        if ( this[ mode + "Command" ] )
332            DOM.addClassName( editorContent, "editor-textmode-" + mode.replace( /_/g, "-" ) );
333        this.currentTextMode = mode;
334    },
335
336
337    execCommand: function( command, userInterface, argument ) {
338        if ( this.currentTextMode && this[ this.currentTextMode + "Command" ] ) {
339            log('executeing command: ' + command + ' in mode: '+this.currentTextMode );
340            this[ this.currentTextMode + "Command" ].apply( this, arguments );
341            return;
342        }
343
344        var text = this.getSelectedText();
345        if ( !defined( text ) )
346            text = '';
347        switch ( command ) {
348           
349            case "fontSizeSmaller":
350                this.setSelection( "<small>" + text + "</small>" );
351                break;
352
353            case "fontSizeLarger":
354                this.setSelection( "<big>" + text + "</big>" );
355                break;
356   
357            default:
358                arguments.callee.applySuper( this, arguments );
359                break;       
360        }
361    },
362   
363
364
365    "markdown_with_smartypantsCommand": function() {
366        this.markdownCommand.apply( this, arguments );
367    },
368
369
370    markdownCommand: function( command, userInterface, argument ) {
371        var text = this.getSelectedText();
372        if ( !defined( text ) )
373            text = '';
374        switch ( command ) {
375           
376            case "bold":
377                this.setSelection( "**" + text + "**" );
378                break;
379
380            case "italic":
381                this.setSelection( "*" + text + "*" );
382                break;
383
384            case "createLink":
385                this.setSelection( "[" + text + "](" + argument + ")" );
386                break;
387           
388            case "indent":
389                var list = text.split( /\r?\n/ );
390                for ( var i = 0; i < list.length; i++ )
391                    list[ i ] = "> " + list[ i ];
392                this.setSelection( list.join( "\n" ) );
393                break;
394           
395            case "insertUnorderedList":
396            case "insertOrderedList":
397                var list = text.split( /\r?\n/ );
398                var ordered = ( command == "insertOrderedList" ) ? true : false;
399                for ( var i = 0; i < list.length; i++ )
400                    list[ i ] = " " + ( ordered ? ( ( i + 1 ) + ". " ) : "-" ) + " " + list[ i ];
401                this.setSelection( "\n" + list.join( "\n" ) + "\n" );
402                break;
403        }
404    },
405
406
407    "textile_2Command": function( command, userInterface, argument ) {
408        var text = this.getSelectedText();
409        if ( !defined( text ) )
410            text = '';
411        switch ( command ) {
412           
413            case "bold":
414                this.setSelection( "**" + text + "**" );
415                break;
416
417            case "italic":
418                this.setSelection( "_" + text + "_" );
419                break;
420
421            case "strikethrough":
422                this.setSelection( "-" + text + "-" );
423                break;
424           
425            case "createLink":
426                this.setSelection( '"' + text + '":' + argument );
427                break;
428           
429            case "indent":
430                this.setSelection( "bq. " + text );
431                break;
432           
433            case "insertUnorderedList":
434            case "insertOrderedList":
435                var list = text.split( /\r?\n/ );
436                var ordered = ( command == "insertOrderedList" ) ? true : false;
437                for ( var i = 0; i < list.length; i++ )
438                    list[ i ] = ( ordered ? "#" : "*" ) + " " + list[ i ];
439                this.setSelection( "\n" + list.join( "\n" ) + "\n" );
440                break;
441
442            case "justifyLeft":
443                this.setSelection( "< " + text );
444                break;
445
446            case "justifyCenter":
447                this.setSelection( "= " + text );
448                break;
449
450            case "justifyRight":
451                this.setSelection( "> " + text );
452                break;
453        }
454    }
455
456} );
457
458
459
460MT.App.Editor.Iframe = new Class( Editor.Iframe, {
461
462
463    initObject: function() {
464        arguments.callee.applySuper( this, arguments );
465        this.isWebKit = navigator.userAgent.toLowerCase().match(/webkit/);
466    },
467   
468   
469    eventFocusIn: function( event ) {
470       this.eventFocus( event );
471    },
472
473
474    eventFocus: function( event ) {
475        if ( this.editor.mode == "textarea" )
476            this.editor.focus();
477    },
478
479
480    eventClick: function( event ) {
481        /* for safari */
482        if ( this.isWebKit && event.target.nodeName == "A" )
483            return event.stop();
484        return arguments.callee.applySuper( this, arguments );
485    },
486   
487   
488    eventKeyPress: function( event ) {
489        /* safari forward delete */
490        if ( this.isWebKit && event.keyCode == 63272 )
491            return event.stop();
492    },
493   
494   
495    eventKeyDown: function( event ) {
496        /* safari forward delete */
497        if ( this.isWebKit && event.keyCode == 46 ) {
498            this.document.execCommand( "forwardDelete", false, true );
499            return false;
500        }
501    },
502
503
504    extendedExecCommand: function( command, userInterface, argument ) {
505        switch( command ) {
506
507            case "fontSizeSmaller":     
508                this.changeFontSizeOfSelection( false );
509                break;
510
511            case "fontSizeLarger":
512                this.changeFontSizeOfSelection( true );
513                break;
514           
515            default:
516                return arguments.callee.applySuper( this, arguments );
517       
518        }
519    },
520
521
522    mutateFontSize: function( element, bigger ) {
523        // Basic settings:
524        var goSmaller = 0.8;
525        var goBigger = 1.25;
526        var biggest = Math.pow( goBigger, 3 );
527        var smallest = Math.pow( goSmaller, 3);
528        var defaultSize = bigger ? goBigger + "em" : goSmaller + "em";
529
530        // Initial detection, rejection, adjusting:
531        var fontSize = element.style.fontSize.match( /([\d\.]+)(%|em|$)/ );
532        if( fontSize == null || isNaN( fontSize[ 1 ] ) ) // "px" sizes are rejected.
533            return defaultSize; // A browser problem or bad user data would lead to "NaN" fontSize.
534       
535        var size;
536        if( fontSize[ 2 ] == "%" )
537            size = fontSize[ 1 ] / 100; // Convert to "em" units.
538        else if( fontSize[ 2 ] == "em" || fontSize[ 2 ] == "" )
539            size = fontSize[ 1 ];
540       
541        // Mutation:
542        var factor = bigger ? goBigger : goSmaller;
543        size = size * factor;
544       
545        if( size > biggest ) 
546            size = biggest;
547        else if( size < smallest ) 
548            size = smallest;
549
550        return size + "em";                           
551    },
552
553   
554    changeFontSizeOfSelection: function( bigger ) {
555        var bogus = "-editor-proxy";
556        this.document.execCommand( "fontName", false, bogus );
557        var elements = this.document.getElementsByTagName( "font" );
558        for( var i = 0; i < elements.length; i++ ) {
559            var element = elements[ i ];
560            if( element.face == bogus ) {
561                element.removeAttribute( "face" );
562                element.style.fontSize = this.mutateFontSize( element, bigger );
563            }
564        }
565    },
566
567
568    getHTML: function() {
569        var html = this.document.body.innerHTML;
570
571        // cleanup innerHTML garbage browser regurgitates
572        // #1 - lowercase tag names (open and closing tags)
573        html = html.replace(/<\/?[A-Z]+[\s>]/g, function (m) {
574            return m.toLowerCase();
575        });
576
577        // #2 - lowercase attribute names
578        html = html.replace(/(<[\w\d]+\s+)([^>]+)>/g, function (x, m1, m2) {
579            return m1 + m2.replace(/\b([\w\d:-]+)\s*=\s*(?:'([^']*?)'|"([^"]*?)"|(\S+))/g, function (x, m1, m2, m3, m4) {
580                if ( !m2 ) m2 = ''; // for ie
581                if ( !m3 ) m3 = ''; // for ie
582                if ( !m4 ) m4 = ''; // for ie
583                return m1.toLowerCase() + '="' + m2 + m3 + m4 + '"';
584            }) + ">";
585        });
586
587        // #3 - close singlet tags for img, br, input, param, hr
588        html = html.replace(/<(br|img|input|param)([^>]+)?([^\/])?>/g, "<$1$2$3 />");
589        return html;
590    }
591    //
592    //
593    // getNodeXHTML: function( node ) {
594    //     var xhtml = '';
595    //     for ( var i = 0; i < node.childNodes.length; i++ ) {
596    //         var element = node.childNodes[ i ];
597    //
598    //         switch ( element.nodeType ) {
599    //
600    //             case Node.ELEMENT_NODE:
601    //                 var tag = element.nodeName.toLowerCase();
602    //                 var regex = new RegExp( '<' + tag + ' +?([^>]*?)>', 'i' );
603    //                 var m = element.parentNode.innerHTML.match( regex );
604    //                 xhtml += ( "<" + tag );
605    //                 if ( m )
606    //                     xhtml += ' ' + m[1].replace( /^\s+/, '' );
607    //                 // tags intentionally unsupported:
608    //                 //   spacer (proprietary)
609    //                 //   frame (only used in standalone documents; not an inline tag)
610    //                 //   bgsound (proprietary)
611    //                 //   wbr (invalid for xhtml)
612    //                 //   embed (proprietary; see http://www.alistapart.com/stories/flashsatay/)
613    //                 if ( element.childNodes.length == 0 && tag.match( /^(br|hr|img|input|param)$/ ) )
614    //                     xhtml += " />";
615    //                 else
616    //                     xhtml += '>' + this.getNodeXHTML( element ) + '</' + tag + '>';
617    //
618    //                 break;
619    //
620    //             case Node.TEXT_NODE:
621    //                 if ( !element.nodeValue.match( /^[\r\n\t ]+$/ ) )
622    //                     xhtml += element.nodeValue;
623    //                 break;
624    //
625    //             case 5: // ENTITY_REFERENCE_NODE
626    //                 xhtml += "&" + element.nodeName.toLowerCase() + ";"
627    //                 break;
628    //             
629    //         }
630    //     }
631    //     return xhtml;
632    // }
633
634} );
635
636
637
638MT.App.CategorySelector = new Class( Transient, {
639   
640
641    transitory: true,
642    opening: false,
643   
644   
645    initObject: function( element, template ) {
646        arguments.callee.applySuper( this, arguments );
647
648        this.catForm = DOM.getElement( "add-category-form" );
649        this.catInput = DOM.getElement( "add-category-input" );
650
651        this.list = this.addComponent( new List( element + '-list', template ) );
652        this.list.setOption( "checkboxSelection", true );
653        this.list.addObserver( this );
654
655        if ( element.match( /category/ ) ) {
656            this.type = "category";
657            this.list.setOption( "singleSelect", false );
658            this.list.setOption( "toggleSelect", true );
659        } else {
660            this.type = "folder";
661            this.list.setOption( "singleSelect", true );
662            this.list.setOption( "toggleSelect", false );
663        }
664
665        this.parentID = 0;
666        var cats = MT.App.categoryList;
667        var selcats = MT.App.selectedCategoryList;
668        var catlen = cats.length;
669        var selected = {};
670        for ( var i = 0; i < selcats; i++ )
671            selected[ selcats[ i ] ] = true;
672        for ( var i = 0; i < catlen; i++ )
673            this.list.addItem( cats[ i ], selected.hasOwnProperty( cats[ i ] ) );
674    },
675
676   
677    destroyObject: function() {
678        this.list = null;
679        this.catForm = null;
680        this.catInput = null;
681        this.catFormMovable = null;
682        arguments.callee.applySuper( this, arguments );
683    },
684
685
686    eventKeyDown: function( event ) {
687        if ( event.target.nodeName != "INPUT" )
688            return;
689
690        if ( event.keyCode == 13 ) {
691            this.createCategory();
692            return event.stop();
693        }
694    },
695   
696   
697    open: function() {
698        arguments.callee.applySuper( this, arguments );
699        /* hack to keep the broadcast from nuking our list */
700        this.opening = true;
701        this.list.resetSelection();
702        this.opening = false;
703        /* this keeps our list order if they made one a primary since the last open */
704        this.list.setSelection( MT.App.selectedCategoryList );
705    },
706
707
708    eventClick: function( event ) {
709        var command = this.getMouseEventCommand( event );
710        switch( command ) {
711
712            case "close":
713                this.removeMovable();
714                this.close( this.list.getSelectedIDs() );
715                break;
716           
717            case "showAddCategory":
718                this.removeMovable();
719                /* show the add category block inside the flyout */
720                var id = DOM.getMouseEventAttribute( event, "mt:id" );
721                if ( id ) {
722                    /* adding a sub cat/folder */
723                    this.catInput.value = '';
724                    DOM.addClassName( this.catForm, "hidden" );
725                    var item = this.list.getListElementFromTarget( event.target );
726                    this.catFormMovable = document.createElement( "div" );
727                    this.catFormMovable.innerHTML = Template.process( "categorySelectorAddForm", { div: this.catFormMovable } );
728                    this.list.content.insertBefore( this.catFormMovable, item.nextSibling );
729                    this.catInputMovable = DOM.getElement( "add-category-input-movable" );
730                    DOM.removeClassName( this.catFormMovable, "hidden" );
731                    this.parentID = id;
732                    this.catInputMovable.focus();
733                } else {
734                    DOM.removeClassName( this.catForm, "hidden" );
735                    this.catInput.focus();
736                }
737                break;
738
739            case "cancel":
740                this.removeMovable();
741                /* hide it */
742                DOM.addClassName( this.catForm, "hidden" );
743                break;
744
745            case "add":
746                /* add a category */
747                this.createCategory();
748                break;
749
750            default:
751                return;
752
753        }
754        return event.stop();
755    },
756
757
758    removeMovable: function() {
759        this.parentID = 0;
760        if ( !this.catFormMovable )
761            return;
762        this.catFormMovable.parentNode.removeChild( this.catFormMovable );
763        this.catFormMovable = undefined;
764    },
765
766
767    createCategory: function() {
768        var inputElement = ( this.parentID == 0 )
769            ? this.catInput : this.catInputMovable;
770        var name = inputElement.value;
771        if ( !name || name == "" || name.match( /^\s+$/ ) )
772            return;
773       
774        /* ignore the faded default text that could be in the box */
775        var defaultText = inputElement.getAttribute( "mt:default" );
776        if ( defaultText && name == defaultText )
777            return;
778
779        DOM.addClassName( this.catForm, "hidden" );
780        DOM.addClassName( this.catFormMovable, "hidden" );
781        this.catInput.value = '';
782       
783        var args = {
784            __mode: "js_add_category",
785            magic_token: app.form["magic_token"].value,
786            blog_id: app.form["blog_id"].value,
787            parent: parseInt( this.parentID ),
788            _type: this.type
789        };
790        args.label = name;
791       
792        /* hahah, safari crashes during the keydown */
793        new Timer( this.getIndirectMethod( "removeMovable" ), 20, 1 );
794       
795        TC.Client.call({
796            load: this.getIndirectMethod( "createCategoryComplete" ),
797            error: this.getIndirectMethod( "createCategoryError" ),
798            method: 'POST',
799            uri: app.form.action,
800            arguments: args,
801            label: name
802        });
803    },
804
805
806    createCategoryComplete: function( c, r, p ) {
807        /* {"error":null,"result":{"basename":"foobar","id":7}} */
808        log("create category complete "+r+' parent:'+p.arguments.parent );
809        if ( r.charAt( 0 ) != "{" )
810            return log.error( r );
811        var obj = eval( "(" + r + ")" );
812        if ( obj.error )
813            return log.error( obj.error );
814        if ( obj.result && obj.result.id )
815            this.addCategory( obj.result.id, p.label, p.arguments.parent );
816    },
817
818
819    createCategoryError: function( c, r ) {
820        log.error("error creating category");
821    },
822
823
824    addCategory: function( id, name, parent ) {
825        var cat = {
826            id: id,
827            label: name,
828            path: []
829        };
830        var catlist = MT.App.categoryList;
831        parent = parseInt( parent );
832
833        /* single selection, and we're about to select the new folder */
834        if ( this.type == 'folder' )
835            this.list.resetSelection();
836
837        if ( parent != 0 ) {
838            var idx;
839            for ( var i = 0; i < catlist.length; i++ )
840                if ( parseInt( catlist[ i ].id ) == parent ) {
841                    idx = i;
842                    parent = catlist[ i ];
843                    break;
844                }
845            if ( !defined( idx ) )
846                return log.error( "can't find parent id "+parent.id+" in category list");
847            /* get the parents path for our own, and add the parent */
848            /* use fromPseudo to copy this array, not take a ref to it */
849            cat.path = Array.fromPseudo( parent.path || [] );
850            cat.path.push( parent.id );
851            catlist.splice( idx, 0, cat );
852            /* update the cache */
853            app.catCache.setItem( "cat:" + cat.id, cat );
854            /* add puts the item at the bottom, so we hide it and move it */
855            this.list.addItem( cat, true, "list-item hidden" );
856            var div = this.list.getItem( cat.id );
857            div.parentNode.removeChild( div );
858            var parentItem = this.list.getItem( parent.id );
859            /* move it after the parent */
860            this.list.content.insertBefore( div, parentItem.nextSibling );
861            DOM.removeClassName( div, "hidden" );
862        } else {
863            catlist.push( cat );
864            /* update the cache */
865            app.catCache.setItem( "cat:" + cat.id, cat );
866            this.list.addItem( cat, true );
867        }
868        /* recheck selection */
869        this.listItemsSelected( this.list );
870    },
871
872
873    listItemsSelected: function( list, ids ) {
874        MT.App.selectedCategoryList = Array.fromPseudo( list.getSelectedIDs() );
875        app.catList.redraw();
876    },
877
878
879    listItemsUnSelected: function( list, ids ) {
880        if ( this.opening || this.type == "folder" )
881            return;
882        MT.App.selectedCategoryList = Array.fromPseudo( list.getSelectedIDs() );
883        app.catList.redraw();
884    }
885
886
887} );
888
889
890MT.App.CategoryList = new Class( Object, {
891
892   
893    init: function( element ) {
894        this.element = DOM.getElement( element );
895    },
896
897
898    destroy: function() {
899        this.element = null;
900    },
901
902       
903    redraw: function( catCache ) {
904        if ( !catCache )
905            catCache = app.catCache;
906        var el = DOM.getElement( "category-ids" );
907        if ( el )
908            el.value = MT.App.selectedCategoryList.join( "," );
909        this.element.innerHTML = Template.process( "categoryList", { items: MT.App.selectedCategoryList, cache: catCache } );
910    },
911
912
913    eventMouseOver: function( event ) {
914        var target;
915        if ( event.target &&
916            ( target = DOM.getFirstAncestorByAttribute( event.target, "mt:focus-hover" ) ) )
917                DOM.addClassName( target, "focus" );
918    },
919
920
921    eventMouseOut: function( event ) {
922        var target;
923        if ( event.target &&
924            ( target = DOM.getFirstAncestorByAttribute( event.target, "mt:focus-hover" ) ) )
925                DOM.removeClassName( target, "focus" );
926    },
927
928
929    eventClick: function( event ) {
930        var command = app.getMouseEventCommand( event );
931        var id = DOM.getMouseEventAttribute( event, "mt:id" );
932        switch( command ) {
933
934            case "primary":
935                if ( !defined( id ) )
936                    return;
937                id = parseInt( id );
938                /* make category primary */
939                var idx = MT.App.selectedCategoryList.indexOf( id );
940                if ( idx == -1 )
941                    return log.error('could not find cat id:'+id);
942                MT.App.selectedCategoryList.splice( idx, 1 );
943                MT.App.selectedCategoryList.splice( 0, 0, id );
944                this.redraw();
945                break;
946           
947            case "remove":
948                if ( !defined( id ) )
949                    return;
950                id = parseInt( id );
951                /* remove a category */
952                var idx = MT.App.selectedCategoryList.indexOf( parseInt( id ) );
953                if ( idx == -1 )
954                    return log.error('could not find cat id:'+id);
955                MT.App.selectedCategoryList.splice( idx, 1 );
956                this.redraw();
957                break;
958               
959            case "openCategorySelector":
960                app.categorySelector.open( null, Function.stub, event.commandElement );
961                break;
962
963            default:
964                return;
965
966        }
967        return event.stop();
968    }
969   
970
971} );
972
973
974Editor.strings.enterEmailAddress = trans('Enter email address:');
975Editor.strings.enterLinkAddress = trans('Enter the link address:');
976Editor.strings.enterTextToLinkTo = trans('Enter the text to link to:');
977
Note: See TracBrowser for help on using the browser.