[ Index ] |
PHP Cross Reference of BuddyPress |
[Summary view] [Print] [Text view]
1 /* global wp, BP_Nouveau, _, Backbone, tinymce, tinyMCE */ 2 /* jshint devel: true */ 3 /* @since 3.0.0 */ 4 /* @version 10.3.0 */ 5 window.wp = window.wp || {}; 6 window.bp = window.bp || {}; 7 8 ( function( bp, $ ) { 9 10 // Bail if not set. 11 if ( typeof BP_Nouveau === 'undefined' ) { 12 return; 13 } 14 15 _.extend( bp, _.pick( wp, 'Backbone', 'ajax', 'template' ) ); 16 17 bp.Models = bp.Models || {}; 18 bp.Collections = bp.Collections || {}; 19 bp.Views = bp.Views || {}; 20 21 bp.Nouveau = bp.Nouveau || {}; 22 23 /** 24 * [Nouveau description] 25 * @type {Object} 26 */ 27 bp.Nouveau.Messages = { 28 /** 29 * [start description] 30 * @return {[type]} [description] 31 */ 32 start: function() { 33 this.views = new Backbone.Collection(); 34 this.threads = new bp.Collections.Threads(); 35 this.messages = new bp.Collections.Messages(); 36 this.router = new bp.Nouveau.Messages.Router(); 37 this.box = 'inbox'; 38 39 // Set up supported routes. 40 this.supportedRoutes = BP_Nouveau.messages.supportedRoutes; 41 42 this.setupNav(); 43 44 Backbone.history.start( { 45 pushState: true, 46 root: BP_Nouveau.messages.rootUrl 47 } ); 48 }, 49 50 setupNav: function() { 51 var self = this; 52 53 // First adapt the compose nav. 54 $( '#compose-personal-li' ).addClass( 'last' ); 55 56 // Then listen to nav click and load the appropriate view. 57 $( '#subnav a' ).on( 'click', function( event ) { 58 var view_id = $( event.target ).prop( 'id' ), 59 supportedView = _.keys( self.supportedRoutes ); 60 61 if ( -1 === _.indexOf( supportedView, view_id ) || 'unsupported' === self.box ) { 62 return event; 63 } 64 65 event.preventDefault(); 66 67 // Remove the editor to be sure it will be added dynamically later. 68 self.removeTinyMCE(); 69 70 // The compose view is specific (toggle behavior). 71 if ( 'compose' === view_id ) { 72 // If it exists, it means the user wants to remove it. 73 if ( ! _.isUndefined( self.views.get( 'compose' ) ) ) { 74 var form = self.views.get( 'compose' ); 75 form.get( 'view' ).remove(); 76 self.views.remove( { id: 'compose', view: form } ); 77 78 // Back to inbox. 79 if ( 'single' === self.box ) { 80 self.box = 'inbox'; 81 } 82 83 // Navigate back to current box. 84 self.router.navigate( self.supportedRoutes[ self.box ] + '/', { trigger: true } ); 85 86 // Otherwise load it. 87 } else { 88 self.router.navigate( self.supportedRoutes.compose + '/', { trigger: true } ); 89 } 90 91 // Other views are classic. 92 } else { 93 94 if ( self.box !== view_id || ! _.isUndefined( self.views.get( 'compose' ) ) ) { 95 self.clearViews(); 96 97 self.router.navigate( self.supportedRoutes[ view_id ] + '/', { trigger: true } ); 98 } 99 } 100 } ); 101 }, 102 103 updateNav: function( view ) { 104 var currentView = this.box; 105 106 if ( view ) { 107 currentView = view; 108 } 109 110 // Activate the appropriate nav. 111 $( '#subnav ul li' ).each( function( l, li ) { 112 $( li ).removeClass( 'current selected' ); 113 } ); 114 115 $( '#subnav a#' + currentView ).closest( 'li' ).addClass( 'current selected' ); 116 }, 117 118 removeTinyMCE: function() { 119 if ( typeof tinymce !== 'undefined' ) { 120 var editor = tinymce.get( 'message_content' ); 121 122 if ( editor !== null ) { 123 tinymce.EditorManager.execCommand( 'mceRemoveEditor', true, 'message_content' ); 124 } 125 } 126 }, 127 128 tinyMCEinit: function() { 129 if ( typeof window.tinyMCE === 'undefined' || window.tinyMCE.activeEditor === null || typeof window.tinyMCE.activeEditor === 'undefined' ) { 130 return; 131 } else { 132 // Mentions isn't available, so bail. 133 if ( _.isEmpty( bp.mentions ) ) { 134 return; 135 } 136 137 $( window.tinyMCE.activeEditor.contentDocument.activeElement ) 138 .atwho( 'setIframe', $( '#message_content_ifr' )[0] ) 139 .bp_mentions( { 140 data: [], 141 suffix: ' ' 142 } ); 143 } 144 }, 145 146 removeFeedback: function() { 147 var feedback; 148 149 if ( ! _.isUndefined( this.views.get( 'feedback' ) ) ) { 150 feedback = this.views.get( 'feedback' ); 151 feedback.get( 'view' ).remove(); 152 this.views.remove( { id: 'feedback', view: feedback } ); 153 } 154 }, 155 156 displayFeedback: function( message, type ) { 157 var feedback; 158 159 // Make sure to remove the feedbacks. 160 this.removeFeedback(); 161 162 if ( ! message ) { 163 return; 164 } 165 166 feedback = new bp.Views.Feedback( { 167 value: message, 168 type: type || 'info' 169 } ); 170 171 this.views.add( { id: 'feedback', view: feedback } ); 172 173 feedback.inject( '.bp-messages-feedback' ); 174 }, 175 176 clearViews: function() { 177 // Clear views. 178 if ( ! _.isUndefined( this.views.models ) ) { 179 _.each( this.views.models, function( model ) { 180 model.get( 'view' ).remove(); 181 }, this ); 182 183 this.views.reset(); 184 } 185 }, 186 187 composeView: function() { 188 // Remove all existing views. 189 this.clearViews(); 190 this.updateNav( 'compose' ); 191 192 // Create the loop view. 193 var form = new bp.Views.messageForm( { 194 model: new bp.Models.Message() 195 } ); 196 197 this.views.add( { id: 'compose', view: form } ); 198 199 form.inject( '.bp-messages-content' ); 200 }, 201 202 threadsView: function() { 203 this.updateNav(); 204 205 // Create the loop view. 206 var threads_list = new bp.Views.userThreads( { collection: this.threads, box: this.box } ); 207 208 this.views.add( { id: 'threads', view: threads_list } ); 209 210 threads_list.inject( '.bp-messages-content' ); 211 212 // Attach filters. 213 this.displayFilters( this.threads ); 214 }, 215 216 displayFilters: function( collection ) { 217 var filters_view; 218 219 // Create the model. 220 this.filters = new Backbone.Model( { 221 'page' : 1, 222 'total_page' : 0, 223 'search_terms' : '', 224 'box' : this.box 225 } ); 226 227 // Use it in the filters viex. 228 filters_view = new bp.Views.messageFilters( { model: this.filters, threads: collection } ); 229 230 this.views.add( { id: 'filters', view: filters_view } ); 231 232 filters_view.inject( '.bp-messages-filters' ); 233 }, 234 235 singleView: function( thread ) { 236 // Remove all existing views. 237 this.clearViews(); 238 239 this.box = 'single'; 240 241 // Create the single thread view. 242 var single_thread = new bp.Views.userMessages( { collection: this.messages, thread: thread } ); 243 244 this.views.add( { id: 'single', view: single_thread } ); 245 246 single_thread.inject( '.bp-messages-content' ); 247 } 248 }; 249 250 bp.Models.Message = Backbone.Model.extend( { 251 defaults: { 252 send_to : [], 253 subject : '', 254 message_content : '', 255 meta : {} 256 }, 257 258 sendMessage: function() { 259 var sent = bp.ajax.post( 260 'messages_send_message', 261 _.extend( 262 { nonce: BP_Nouveau.messages.nonces.send }, 263 this.attributes 264 ) 265 ); 266 267 return sent; 268 } 269 } ); 270 271 bp.Models.Thread = Backbone.Model.extend( { 272 defaults: { 273 id : 0, 274 message_id : 0, 275 subject : '', 276 excerpt : '', 277 content : '', 278 unread : true, 279 sender_name : '', 280 sender_link : '', 281 sender_avatar : '', 282 count : 0, 283 date : 0, 284 display_date : '', 285 recipients : [] 286 }, 287 288 updateReadState: function( options ) { 289 options = options || {}; 290 options.data = _.extend( 291 _.pick( this.attributes, ['id', 'message_id'] ), 292 { 293 action : 'messages_thread_read', 294 nonce : BP_Nouveau.nonces.messages 295 } 296 ); 297 298 return bp.ajax.send( options ); 299 } 300 } ); 301 302 bp.Models.messageThread = Backbone.Model.extend( { 303 defaults: { 304 id : 0, 305 content : '', 306 sender_id : 0, 307 sender_name : '', 308 sender_link : '', 309 sender_avatar : '', 310 date : 0, 311 display_date : '' 312 } 313 } ); 314 315 bp.Collections.Threads = Backbone.Collection.extend( { 316 model: bp.Models.Thread, 317 318 initialize : function() { 319 this.options = { page: 1, total_page: 0 }; 320 }, 321 322 sync: function( method, model, options ) { 323 options = options || {}; 324 options.context = this; 325 options.data = options.data || {}; 326 327 // Add generic nonce. 328 options.data.nonce = BP_Nouveau.nonces.messages; 329 330 if ( 'read' === method ) { 331 options.data = _.extend( options.data, { 332 action: 'messages_get_user_message_threads' 333 } ); 334 335 return bp.ajax.send( options ); 336 } 337 }, 338 339 parse: function( resp ) { 340 341 if ( ! _.isArray( resp.threads ) ) { 342 resp.threads = [resp.threads]; 343 } 344 345 _.each( resp.threads, function( value, index ) { 346 if ( _.isNull( value ) ) { 347 return; 348 } 349 350 resp.threads[index].id = value.id; 351 resp.threads[index].message_id = value.message_id; 352 resp.threads[index].subject = value.subject; 353 resp.threads[index].excerpt = value.excerpt; 354 resp.threads[index].content = value.content; 355 resp.threads[index].unread = value.unread; 356 resp.threads[index].sender_name = value.sender_name; 357 resp.threads[index].sender_link = value.sender_link; 358 resp.threads[index].sender_avatar = value.sender_avatar; 359 resp.threads[index].count = value.count; 360 resp.threads[index].date = new Date( value.date ); 361 resp.threads[index].display_date = value.display_date; 362 resp.threads[index].recipients = value.recipients; 363 resp.threads[index].star_link = value.star_link; 364 resp.threads[index].is_starred = value.is_starred; 365 } ); 366 367 if ( ! _.isUndefined( resp.meta ) ) { 368 this.options.page = resp.meta.page; 369 this.options.total_page = resp.meta.total_page; 370 } 371 372 if ( bp.Nouveau.Messages.box ) { 373 this.options.box = bp.Nouveau.Messages.box; 374 } 375 376 if ( ! _.isUndefined( resp.extraContent ) ) { 377 _.extend( this.options, _.pick( resp.extraContent, [ 378 'beforeLoop', 379 'afterLoop' 380 ] ) ); 381 } 382 383 return resp.threads; 384 }, 385 386 doAction: function( action, ids, options ) { 387 options = options || {}; 388 options.context = this; 389 options.data = options.data || {}; 390 391 options.data = _.extend( options.data, { 392 action: 'messages_' + action, 393 nonce : BP_Nouveau.nonces.messages, 394 id : ids 395 } ); 396 397 return bp.ajax.send( options ); 398 } 399 } ); 400 401 bp.Collections.Messages = Backbone.Collection.extend( { 402 model: bp.Models.messageThread, 403 options: {}, 404 405 sync: function( method, model, options ) { 406 options = options || {}; 407 options.context = this; 408 options.data = options.data || {}; 409 410 // Add generic nonce. 411 options.data.nonce = BP_Nouveau.nonces.messages; 412 413 if ( 'read' === method ) { 414 options.data = _.extend( options.data, { 415 action: 'messages_get_thread_messages' 416 } ); 417 418 return bp.ajax.send( options ); 419 } 420 421 if ( 'create' === method ) { 422 options.data = _.extend( options.data, { 423 action : 'messages_send_reply', 424 nonce : BP_Nouveau.messages.nonces.send 425 }, model || {} ); 426 427 return bp.ajax.send( options ); 428 } 429 }, 430 431 parse: function( resp ) { 432 433 if ( ! _.isArray( resp.messages ) ) { 434 resp.messages = [resp.messages]; 435 } 436 437 _.each( resp.messages, function( value, index ) { 438 if ( _.isNull( value ) ) { 439 return; 440 } 441 442 resp.messages[index].id = value.id; 443 resp.messages[index].content = value.content; 444 resp.messages[index].sender_id = value.sender_id; 445 resp.messages[index].sender_name = value.sender_name; 446 resp.messages[index].sender_link = value.sender_link; 447 resp.messages[index].sender_avatar = value.sender_avatar; 448 resp.messages[index].date = new Date( value.date ); 449 resp.messages[index].display_date = value.display_date; 450 resp.messages[index].star_link = value.star_link; 451 resp.messages[index].is_starred = value.is_starred; 452 } ); 453 454 if ( ! _.isUndefined( resp.thread ) ) { 455 this.options.thread_id = resp.thread.id; 456 this.options.thread_subject = resp.thread.subject; 457 this.options.recipients = resp.thread.recipients; 458 } 459 460 return resp.messages; 461 } 462 } ); 463 464 // Extend wp.Backbone.View with .prepare() and .inject(). 465 bp.Nouveau.Messages.View = bp.Backbone.View.extend( { 466 inject: function( selector ) { 467 this.render(); 468 $(selector).html( this.el ); 469 this.views.ready(); 470 }, 471 472 prepare: function() { 473 if ( ! _.isUndefined( this.model ) && _.isFunction( this.model.toJSON ) ) { 474 return this.model.toJSON(); 475 } else { 476 return {}; 477 } 478 } 479 } ); 480 481 // Feedback view. 482 bp.Views.Feedback = bp.Nouveau.Messages.View.extend( { 483 tagName: 'div', 484 className: 'bp-messages bp-user-messages-feedback', 485 template : bp.template( 'bp-messages-feedback' ), 486 487 initialize: function() { 488 this.model = new Backbone.Model( { 489 type: this.options.type || 'info', 490 message: this.options.value 491 } ); 492 } 493 } ); 494 495 // Hook view. 496 bp.Views.Hook = bp.Nouveau.Messages.View.extend( { 497 tagName: 'div', 498 template : bp.template( 'bp-messages-hook' ), 499 500 initialize: function() { 501 this.model = new Backbone.Model( { 502 extraContent: this.options.extraContent 503 } ); 504 505 this.el.className = 'bp-messages-hook'; 506 507 if ( this.options.className ) { 508 this.el.className += ' ' + this.options.className; 509 } 510 } 511 } ); 512 513 bp.Views.messageEditor = bp.Nouveau.Messages.View.extend( { 514 template : bp.template( 'bp-messages-editor' ), 515 516 initialize: function() { 517 this.on( 'ready', this.activateTinyMce, this ); 518 }, 519 520 activateTinyMce: function() { 521 if ( typeof tinymce !== 'undefined' ) { 522 tinymce.EditorManager.execCommand( 'mceAddEditor', true, 'message_content' ); 523 } 524 } 525 } ); 526 527 bp.Views.messageForm = bp.Nouveau.Messages.View.extend( { 528 tagName : 'form', 529 id : 'send_message_form', 530 className : 'standard-form', 531 template : bp.template( 'bp-messages-form' ), 532 533 events: { 534 'click #bp-messages-send' : 'sendMessage', 535 'click #bp-messages-reset' : 'resetForm' 536 }, 537 538 initialize: function() { 539 // Clone the model to set the resetted one. 540 this.resetModel = this.model.clone(); 541 542 // Add the editor view. 543 this.views.add( '#bp-message-content', new bp.Views.messageEditor() ); 544 545 this.model.on( 'change', this.resetFields, this ); 546 547 // Activate bp_mentions. 548 this.on( 'ready', this.addMentions, this ); 549 }, 550 551 addMentions: function() { 552 var sendToInput = $( this.el ).find( '#send-to-input' ), 553 mention = bp.Nouveau.getLinkParams( null, 'r' ) || null; 554 555 // Add autocomplete to send_to field. 556 sendToInput.bp_mentions( { 557 data: [], 558 suffix: ' ' 559 } ); 560 561 // Check for mention. 562 if ( ! _.isNull( mention ) ) { 563 sendToInput.val( '@' + _.escape( mention ) + ' ' ); 564 sendToInput.trigger( 'focus' ); 565 } 566 }, 567 568 resetFields: function( model ) { 569 // Clean inputs. 570 _.each( model.previousAttributes(), function( value, input ) { 571 if ( 'message_content' === input ) { 572 // tinyMce. 573 if ( undefined !== tinyMCE.activeEditor && null !== tinyMCE.activeEditor ) { 574 tinyMCE.activeEditor.setContent( '' ); 575 } 576 577 // All except meta or empty value. 578 } else if ( 'meta' !== input && false !== value ) { 579 $( 'input[name="' + input + '"]' ).val( '' ); 580 } 581 } ); 582 583 // Listen to this to eventually reset your custom inputs. 584 $( this.el ).trigger( 'message:reset', _.pick( model.previousAttributes(), 'meta' ) ); 585 }, 586 587 sendMessage: function( event ) { 588 var meta = {}, errors = [], self = this, 589 button = event.currentTarget; 590 591 event.preventDefault(); 592 593 bp.Nouveau.Messages.removeFeedback(); 594 $( button ).addClass( 'disabled' ).prop( 'disabled', true ); 595 596 // Set the content and meta. 597 _.each( this.$el.serializeArray(), function( pair ) { 598 pair.name = pair.name.replace( '[]', '' ); 599 600 // Group extra fields in meta. 601 if ( -1 === _.indexOf( ['send_to', 'subject', 'message_content'], pair.name ) ) { 602 if ( _.isUndefined( meta[ pair.name ] ) ) { 603 meta[ pair.name ] = pair.value; 604 } else { 605 if ( ! _.isArray( meta[ pair.name ] ) ) { 606 meta[ pair.name ] = [ meta[ pair.name ] ]; 607 } 608 609 meta[ pair.name ].push( pair.value ); 610 } 611 612 // Prepare the core model. 613 } else { 614 // Send to. 615 if ( 'send_to' === pair.name ) { 616 var usernames = pair.value.match( /(^|[^@\w\-])@([a-zA-Z0-9_\-]{1,50})\b/g ); 617 618 if ( ! usernames ) { 619 errors.push( 'send_to' ); 620 } else { 621 usernames = usernames.map( function( username ) { 622 username = username.trim(); 623 return username; 624 } ); 625 626 if ( ! usernames || ! _.isArray( usernames ) ) { 627 errors.push( 'send_to' ); 628 } 629 630 this.model.set( 'send_to', usernames, { silent: true } ); 631 } 632 633 // Subject and content. 634 } else { 635 // Message content. 636 if ( 'message_content' === pair.name && undefined !== tinyMCE.activeEditor ) { 637 pair.value = tinyMCE.activeEditor.getContent(); 638 } 639 640 if ( ! pair.value ) { 641 errors.push( pair.name ); 642 } else { 643 this.model.set( pair.name, pair.value, { silent: true } ); 644 } 645 } 646 } 647 648 }, this ); 649 650 if ( errors.length ) { 651 var feedback = ''; 652 _.each( errors, function( e ) { 653 feedback += BP_Nouveau.messages.errors[ e ] + '<br/>'; 654 } ); 655 656 bp.Nouveau.Messages.displayFeedback( feedback, 'error' ); 657 return; 658 } 659 660 // Prevents multiple frenetic clicks! 661 if ( true === this.model.get( 'sending' ) ) { 662 return; 663 } 664 665 // Set meta. 666 this.model.set( 667 { 668 sending: true, 669 meta: meta 670 }, 671 { 672 silent: true 673 } 674 ); 675 676 // Send the message. 677 this.model.sendMessage().done( function( response ) { 678 // Reset the model. 679 self.model.set( self.resetModel ); 680 681 bp.Nouveau.Messages.displayFeedback( response.feedback, response.type ); 682 683 // Remove tinyMCE. 684 bp.Nouveau.Messages.removeTinyMCE(); 685 686 // Remove the form view. 687 var form = bp.Nouveau.Messages.views.get( 'compose' ); 688 form.get( 'view' ).remove(); 689 bp.Nouveau.Messages.views.remove( { id: 'compose', view: form } ); 690 691 bp.Nouveau.Messages.router.navigate( bp.Nouveau.Messages.supportedRoutes.sentbox + '/', { trigger: true } ); 692 } ).fail( function( response ) { 693 if ( response.feedback ) { 694 bp.Nouveau.Messages.displayFeedback( response.feedback, response.type ); 695 } 696 } ).always( function() { 697 self.model.set( 'sending', false, { silent: true } ); 698 $( button ).removeClass( 'disabled' ).prop( 'disabled', false ); 699 } ); 700 }, 701 702 resetForm: function( event ) { 703 event.preventDefault(); 704 705 this.model.set( this.resetModel ); 706 } 707 } ); 708 709 bp.Views.userThreads = bp.Nouveau.Messages.View.extend( { 710 tagName : 'div', 711 712 events: { 713 'click .subject' : 'changePreview' 714 }, 715 716 initialize: function() { 717 var Views = [ 718 new bp.Nouveau.Messages.View( { tagName: 'ul', id: 'message-threads', className: 'message-lists' } ), 719 new bp.Views.previewThread( { collection: this.collection } ) 720 ]; 721 722 _.each( Views, function( view ) { 723 this.views.add( view ); 724 }, this ); 725 726 // Load threads for the active view. 727 this.requestThreads(); 728 729 this.collection.on( 'reset', this.cleanContent, this ); 730 this.collection.on( 'add', this.addThread, this ); 731 }, 732 733 requestThreads: function() { 734 this.collection.reset(); 735 736 bp.Nouveau.Messages.displayFeedback( BP_Nouveau.messages.loading, 'loading' ); 737 738 this.collection.fetch( { 739 data : _.pick( this.options, 'box' ), 740 success : _.bind( this.threadsFetched, this ), 741 error : this.threadsFetchError 742 } ); 743 }, 744 745 threadsFetched: function() { 746 bp.Nouveau.Messages.removeFeedback(); 747 748 // Display the bp_after_member_messages_loop hook. 749 if ( this.collection.options.afterLoop ) { 750 this.views.add( new bp.Views.Hook( { extraContent: this.collection.options.afterLoop, className: 'after-messages-loop' } ), { at: 1 } ); 751 } 752 753 // Display the bp_before_member_messages_loop hook. 754 if ( this.collection.options.beforeLoop ) { 755 this.views.add( new bp.Views.Hook( { extraContent: this.collection.options.beforeLoop, className: 'before-messages-loop' } ), { at: 0 } ); 756 } 757 758 // Inform the user about how to use the UI. 759 bp.Nouveau.Messages.displayFeedback( BP_Nouveau.messages.howto, 'info' ); 760 }, 761 762 threadsFetchError: function( collection, response ) { 763 bp.Nouveau.Messages.displayFeedback( response.feedback, response.type ); 764 }, 765 766 cleanContent: function() { 767 _.each( this.views._views['#message-threads'], function( view ) { 768 view.remove(); 769 } ); 770 }, 771 772 addThread: function( thread ) { 773 var selected = this.collection.findWhere( { active: true } ); 774 775 if ( _.isUndefined( selected ) ) { 776 thread.set( 'active', true ); 777 } 778 779 this.views.add( '#message-threads', new bp.Views.userThread( { model: thread } ) ); 780 }, 781 782 setActiveThread: function( active ) { 783 if ( ! active ) { 784 return; 785 } 786 787 _.each( this.collection.models, function( thread ) { 788 if ( thread.id === active ) { 789 thread.set( 'active', true ); 790 } else { 791 thread.unset( 'active' ); 792 } 793 }, this ); 794 }, 795 796 changePreview: function( event ) { 797 var target = $( event.currentTarget ); 798 799 event.preventDefault(); 800 bp.Nouveau.Messages.removeFeedback(); 801 802 // If the click is done on an active conversation, open it. 803 if ( target.closest( '.thread-item' ).hasClass( 'selected' ) ) { 804 bp.Nouveau.Messages.router.navigate( 805 'view/' + target.closest( '.thread-content' ).data( 'thread-id' ) + '/', 806 { trigger: true } 807 ); 808 809 // Otherwise activate the conversation and display its preview. 810 } else { 811 this.setActiveThread( target.closest( '.thread-content' ).data( 'thread-id' ) ); 812 813 $( '.message-action-view' ).focus(); 814 } 815 } 816 } ); 817 818 bp.Views.userThread = bp.Nouveau.Messages.View.extend( { 819 tagName : 'li', 820 template : bp.template( 'bp-messages-thread' ), 821 className : 'thread-item', 822 823 events: { 824 'click .message-check' : 'singleSelect' 825 }, 826 827 initialize: function() { 828 if ( this.model.get( 'active' ) ) { 829 this.el.className += ' selected'; 830 } 831 832 if ( this.model.get( 'unread' ) ) { 833 this.el.className += ' unread'; 834 } 835 836 if ( 'sentbox' === bp.Nouveau.Messages.box ) { 837 var recipientsCount = this.model.get( 'recipients' ).length, toOthers = ''; 838 839 if ( 2 === recipientsCount ) { 840 toOthers = BP_Nouveau.messages.toOthers.one; 841 } else if ( 2 < recipientsCount ) { 842 toOthers = BP_Nouveau.messages.toOthers.more.replace( '%d', Number( recipientsCount - 1 ) ); 843 } 844 845 this.model.set( { 846 recipientsCount: recipientsCount, 847 toOthers: toOthers 848 }, { silent: true } ); 849 } else if ( this.model.get( 'recipientsCount' ) ) { 850 this.model.unset( 'recipientsCount', { silent: true } ); 851 } 852 853 this.model.on( 'change:active', this.toggleClass, this ); 854 this.model.on( 'change:unread', this.updateReadState, this ); 855 this.model.on( 'change:checked', this.bulkSelect, this ); 856 this.model.on( 'remove', this.cleanView, this ); 857 }, 858 859 toggleClass: function( model ) { 860 if ( true === model.get( 'active' ) ) { 861 $( this.el ).addClass( 'selected' ); 862 } else { 863 $( this.el ).removeClass( 'selected' ); 864 } 865 }, 866 867 updateReadState: function( model, state ) { 868 if ( false === state ) { 869 $( this.el ).removeClass( 'unread' ); 870 } else { 871 $( this.el ).addClass( 'unread' ); 872 } 873 }, 874 875 bulkSelect: function( model ) { 876 if ( $( '#bp-message-thread-' + model.get( 'id' ) ).length ) { 877 $( '#bp-message-thread-' + model.get( 'id' ) ).prop( 'checked',model.get( 'checked' ) ); 878 } 879 }, 880 881 singleSelect: function( event ) { 882 var isChecked = $( event.currentTarget ).prop( 'checked' ); 883 884 // To avoid infinite loops. 885 this.model.set( 'checked', isChecked, { silent: true } ); 886 887 var hasChecked = false; 888 889 _.each( this.model.collection.models, function( model ) { 890 if ( true === model.get( 'checked' ) ) { 891 hasChecked = true; 892 } 893 } ); 894 895 if ( hasChecked ) { 896 $( '#user-messages-bulk-actions' ).closest( '.bulk-actions-wrap' ).removeClass( 'bp-hide' ); 897 898 // Inform the user about how to use the bulk actions. 899 bp.Nouveau.Messages.displayFeedback( BP_Nouveau.messages.howtoBulk, 'info' ); 900 } else { 901 $( '#user-messages-bulk-actions' ).closest( '.bulk-actions-wrap' ).addClass( 'bp-hide' ); 902 903 bp.Nouveau.Messages.removeFeedback(); 904 } 905 }, 906 907 cleanView: function() { 908 this.views.view.remove(); 909 } 910 } ); 911 912 bp.Views.previewThread = bp.Nouveau.Messages.View.extend( { 913 tagName: 'div', 914 id: 'thread-preview', 915 template : bp.template( 'bp-messages-preview' ), 916 917 events: { 918 'click .actions button' : 'doAction', 919 'click .actions a' : 'doAction' 920 }, 921 922 initialize: function() { 923 this.collection.on( 'change:active', this.setPreview, this ); 924 this.collection.on( 'change:is_starred', this.updatePreview, this ); 925 this.collection.on( 'reset', this.emptyPreview, this ); 926 this.collection.on( 'remove', this.emptyPreview, this ); 927 }, 928 929 render: function() { 930 // Only render if we have some content to render. 931 if ( _.isUndefined( this.model ) || true !== this.model.get( 'active' ) ) { 932 return; 933 } 934 935 bp.Nouveau.Messages.View.prototype.render.apply( this, arguments ); 936 }, 937 938 setPreview: function( model ) { 939 var self = this; 940 941 this.model = model; 942 943 if ( true === model.get( 'unread' ) ) { 944 this.model.updateReadState().done( function() { 945 self.model.set( 'unread', false ); 946 } ); 947 } 948 949 this.render(); 950 }, 951 952 updatePreview: function( model ) { 953 if ( true === model.get( 'active' ) ) { 954 this.render(); 955 } 956 }, 957 958 emptyPreview: function() { 959 $( this.el ).html( '' ); 960 }, 961 962 doAction: function( event ) { 963 var action = $( event.currentTarget ).data( 'bp-action' ), self = this, options = {}, mid, 964 feedback = BP_Nouveau.messages.doingAction; 965 966 if ( ! action ) { 967 return event; 968 } 969 970 event.preventDefault(); 971 972 var model = this.collection.findWhere( { active: true } ); 973 974 if ( ! model.get( 'id' ) ) { 975 return; 976 } 977 978 mid = model.get( 'id' ); 979 980 // Open the full conversation. 981 if ( 'view' === action ) { 982 bp.Nouveau.Messages.router.navigate( 983 'view/' + mid + '/', 984 { trigger: true } 985 ); 986 987 return; 988 989 // Star/Unstar actions needs to use a specific id and nonce. 990 } else if ( 'star' === action || 'unstar' === action ) { 991 options.data = { 992 'star_nonce' : model.get( 'star_nonce' ) 993 }; 994 995 mid = model.get( 'starred_id' ); 996 } 997 998 if ( ! _.isUndefined( feedback[ action ] ) ) { 999 bp.Nouveau.Messages.displayFeedback( feedback[ action ], 'loading' ); 1000 } 1001 1002 this.collection.doAction( action, mid, options ).done( function( response ) { 1003 // Remove previous feedback. 1004 bp.Nouveau.Messages.removeFeedback(); 1005 1006 bp.Nouveau.Messages.displayFeedback( response.feedback, response.type ); 1007 1008 if ( 'delete' === action || 'exit' === action || ( 'starred' === self.collection.options.box && 'unstar' === action ) ) { 1009 // Remove from the list of messages. 1010 self.collection.remove( model.get( 'id' ) ); 1011 1012 // And Requery. 1013 self.collection.fetch( { 1014 data : _.pick( self.collection.options, ['box', 'search_terms', 'page'] ) 1015 } ); 1016 } else if ( 'unstar' === action || 'star' === action ) { 1017 // Update the model attributes--updates the star icon. 1018 _.each( response.messages, function( updated ) { 1019 model.set( updated ); 1020 } ); 1021 model.set( _.first( response.messages ) ); 1022 } else if ( response.messages ) { 1023 model.set( _.first( response.messages ) ); 1024 } 1025 } ).fail( function( response ) { 1026 // Remove previous feedback. 1027 bp.Nouveau.Messages.removeFeedback(); 1028 1029 bp.Nouveau.Messages.displayFeedback( response.feedback, response.type ); 1030 } ); 1031 } 1032 } ); 1033 1034 bp.Views.Pagination = bp.Nouveau.Messages.View.extend( { 1035 tagName : 'li', 1036 className : 'last filter', 1037 template : bp.template( 'bp-messages-paginate' ) 1038 } ); 1039 1040 bp.Views.BulkActions = bp.Nouveau.Messages.View.extend( { 1041 tagName : 'div', 1042 template : bp.template( 'bp-bulk-actions' ), 1043 1044 events : { 1045 'click #user_messages_select_all' : 'bulkSelect', 1046 'click .bulk-apply' : 'doBulkAction' 1047 }, 1048 1049 bulkSelect: function( event ) { 1050 var isChecked = $( event.currentTarget ).prop( 'checked' ); 1051 1052 if ( isChecked ) { 1053 $( this.el ).find( '.bulk-actions-wrap' ).removeClass( 'bp-hide' ).addClass( 'bp-show' ); 1054 1055 // Inform the user about how to use the bulk actions. 1056 bp.Nouveau.Messages.displayFeedback( BP_Nouveau.messages.howtoBulk, 'info' ); 1057 } else { 1058 $( this.el ).find( '.bulk-actions-wrap' ).addClass( 'bp-hide' ); 1059 1060 bp.Nouveau.Messages.removeFeedback(); 1061 } 1062 1063 _.each( this.collection.models, function( model ) { 1064 model.set( 'checked', isChecked ); 1065 } ); 1066 }, 1067 1068 doBulkAction: function( event ) { 1069 var self = this, options = {}, ids, attr = 'id', 1070 feedback = BP_Nouveau.messages.doingAction; 1071 1072 event.preventDefault(); 1073 1074 var action = $( '#user-messages-bulk-actions' ).val(); 1075 1076 if ( ! action ) { 1077 return; 1078 } 1079 1080 var threads = this.collection.where( { checked: true } ); 1081 var thread_ids = _.map( threads, function( model ) { 1082 return model.get( 'id' ); 1083 } ); 1084 1085 // Default to thread ids. 1086 ids = thread_ids; 1087 1088 // We need to get the starred ids. 1089 if ( 'star' === action || 'unstar' === action ) { 1090 ids = _.map( threads, function( model ) { 1091 return model.get( 'starred_id' ); 1092 } ); 1093 1094 if ( 1 === ids.length ) { 1095 options.data = { 1096 'star_nonce' : threads[0].get( 'star_nonce' ) 1097 }; 1098 } 1099 1100 // Map with first message starred in the thread. 1101 attr = 'starred_id'; 1102 } 1103 1104 // Message id to Thread id. 1105 var m_tid = _.object( _.map( threads, function (model) { 1106 return [model.get( attr ), model.get( 'id' )]; 1107 } ) ); 1108 1109 if ( ! _.isUndefined( feedback[ action ] ) ) { 1110 bp.Nouveau.Messages.displayFeedback( feedback[ action ], 'loading' ); 1111 } 1112 1113 this.collection.doAction( action, ids, options ).done( function( response ) { 1114 // Remove previous feedback. 1115 bp.Nouveau.Messages.removeFeedback(); 1116 1117 bp.Nouveau.Messages.displayFeedback( response.feedback, response.type ); 1118 1119 if ( 'delete' === action || 'exit' === action || ( 'starred' === self.collection.options.box && 'unstar' === action ) ) { 1120 // Remove from the list of messages. 1121 self.collection.remove( thread_ids ); 1122 1123 // And Requery. 1124 self.collection.fetch( { 1125 data : _.pick( self.collection.options, ['box', 'search_terms', 'page'] ) 1126 } ); 1127 } else if ( response.messages ) { 1128 // Update each model attributes. 1129 _.each( response.messages, function( updated, id ) { 1130 var model = self.collection.get( m_tid[id] ); 1131 model.set( updated ); 1132 } ); 1133 } 1134 } ).fail( function( response ) { 1135 // Remove previous feedback. 1136 bp.Nouveau.Messages.removeFeedback(); 1137 1138 bp.Nouveau.Messages.displayFeedback( response.feedback, response.type ); 1139 } ); 1140 } 1141 } ); 1142 1143 bp.Views.messageFilters = bp.Nouveau.Messages.View.extend( { 1144 tagName: 'ul', 1145 template: bp.template( 'bp-messages-filters' ), 1146 1147 events : { 1148 'search #user_messages_search' : 'resetSearchTerms', 1149 'submit #user_messages_search_form' : 'setSearchTerms', 1150 'click #bp-messages-next-page' : 'nextPage', 1151 'click #bp-messages-prev-page' : 'prevPage' 1152 }, 1153 1154 initialize: function() { 1155 this.model.on( 'change', this.filterThreads, this ); 1156 this.options.threads.on( 'sync', this.addPaginatation, this ); 1157 }, 1158 1159 addPaginatation: function( collection ) { 1160 _.each( this.views._views, function( view ) { 1161 if ( ! _.isUndefined( view ) ) { 1162 _.first( view ).remove(); 1163 } 1164 } ); 1165 1166 this.views.add( new bp.Views.Pagination( { model: new Backbone.Model( collection.options ) } ) ); 1167 1168 this.views.add( '.user-messages-bulk-actions', new bp.Views.BulkActions( { 1169 model: new Backbone.Model( BP_Nouveau.messages.bulk_actions ), 1170 collection : collection 1171 } ) ); 1172 }, 1173 1174 filterThreads: function() { 1175 bp.Nouveau.Messages.displayFeedback( BP_Nouveau.messages.loading, 'loading' ); 1176 1177 this.options.threads.reset(); 1178 _.extend( this.options.threads.options, _.pick( this.model.attributes, ['box', 'search_terms'] ) ); 1179 1180 this.options.threads.fetch( { 1181 data : _.pick( this.model.attributes, ['box', 'search_terms', 'page'] ), 1182 success : this.threadsFiltered, 1183 error : this.threadsFilterError 1184 } ); 1185 }, 1186 1187 threadsFiltered: function() { 1188 bp.Nouveau.Messages.removeFeedback(); 1189 }, 1190 1191 threadsFilterError: function( collection, response ) { 1192 bp.Nouveau.Messages.displayFeedback( response.feedback, response.type ); 1193 }, 1194 1195 resetSearchTerms: function( event ) { 1196 event.preventDefault(); 1197 1198 if ( ! $( event.target ).val() ) { 1199 $( event.target ).closest( 'form' ).submit(); 1200 } else { 1201 $( event.target ).closest( 'form' ).find( '[type=submit]' ).addClass('bp-show').removeClass('bp-hide'); 1202 } 1203 }, 1204 1205 setSearchTerms: function( event ) { 1206 event.preventDefault(); 1207 1208 this.model.set( { 1209 'search_terms': $( event.target ).find( 'input[type=search]' ).val() || '', 1210 page: 1 1211 } ); 1212 }, 1213 1214 nextPage: function( event ) { 1215 event.preventDefault(); 1216 1217 this.model.set( 'page', this.model.get( 'page' ) + 1 ); 1218 }, 1219 1220 prevPage: function( event ) { 1221 event.preventDefault(); 1222 1223 this.model.set( 'page', this.model.get( 'page' ) - 1 ); 1224 } 1225 } ); 1226 1227 bp.Views.userMessagesHeader = bp.Nouveau.Messages.View.extend( { 1228 tagName : 'div', 1229 template : bp.template( 'bp-messages-single-header' ), 1230 1231 events: { 1232 'click .actions a' : 'doAction', 1233 'click .actions button' : 'doAction' 1234 }, 1235 1236 doAction: function( event ) { 1237 var action = $( event.currentTarget ).data( 'bp-action' ), self = this, options = {}, 1238 feedback = BP_Nouveau.messages.doingAction; 1239 1240 if ( ! action ) { 1241 return event; 1242 } 1243 1244 event.preventDefault(); 1245 1246 if ( ! this.model.get( 'id' ) ) { 1247 return; 1248 } 1249 1250 if ( 'star' === action || 'unstar' === action ) { 1251 var opposite = { 1252 'star' : 'unstar', 1253 'unstar' : 'star' 1254 }; 1255 1256 options.data = { 1257 'star_nonce' : this.model.get( 'star_nonce' ) 1258 }; 1259 1260 $( event.currentTarget ).addClass( 'bp-hide' ); 1261 $( event.currentTarget ).parent().find( '[data-bp-action="' + opposite[ action ] + '"]' ).removeClass( 'bp-hide' ); 1262 1263 } 1264 1265 if ( ! _.isUndefined( feedback[ action ] ) ) { 1266 bp.Nouveau.Messages.displayFeedback( feedback[ action ], 'loading' ); 1267 } 1268 1269 bp.Nouveau.Messages.threads.doAction( action, this.model.get( 'id' ), options ).done( function( response ) { 1270 // Remove all views 1271 if ( 'delete' === action || 'exit' === action ) { 1272 bp.Nouveau.Messages.clearViews(); 1273 } else if ( response.messages ) { 1274 self.model.set( _.first( response.messages ) ); 1275 } 1276 1277 // Remove previous feedback. 1278 bp.Nouveau.Messages.removeFeedback(); 1279 1280 // Display the feedback. 1281 bp.Nouveau.Messages.displayFeedback( response.feedback, response.type ); 1282 } ).fail( function( response ) { 1283 // Remove previous feedback. 1284 bp.Nouveau.Messages.removeFeedback(); 1285 1286 bp.Nouveau.Messages.displayFeedback( response.feedback, response.type ); 1287 } ); 1288 } 1289 } ); 1290 1291 bp.Views.userMessagesEntry = bp.Views.userMessagesHeader.extend( { 1292 tagName : 'li', 1293 template : bp.template( 'bp-messages-single-list' ), 1294 1295 events: { 1296 'click [data-bp-action]' : 'doAction' 1297 }, 1298 1299 initialize: function() { 1300 this.model.on( 'change:is_starred', this.updateMessage, this ); 1301 }, 1302 1303 updateMessage: function( model ) { 1304 if ( this.model.get( 'id' ) !== model.get( 'id' ) ) { 1305 return; 1306 } 1307 1308 this.render(); 1309 } 1310 } ); 1311 1312 bp.Views.userMessages = bp.Nouveau.Messages.View.extend( { 1313 tagName : 'div', 1314 template : bp.template( 'bp-messages-single' ), 1315 1316 initialize: function() { 1317 // Load Messages. 1318 this.requestMessages(); 1319 1320 // Init a reply. 1321 this.reply = new bp.Models.messageThread(); 1322 1323 this.collection.on( 'add', this.addMessage, this ); 1324 1325 // Add the editor view. 1326 this.views.add( '#bp-message-content', new bp.Views.messageEditor() ); 1327 }, 1328 1329 events: { 1330 'click #send_reply_button' : 'sendReply' 1331 }, 1332 1333 requestMessages: function() { 1334 var data = {}; 1335 1336 this.collection.reset(); 1337 1338 bp.Nouveau.Messages.displayFeedback( BP_Nouveau.messages.loading, 'loading' ); 1339 1340 if ( _.isUndefined( this.options.thread.attributes ) ) { 1341 data.id = this.options.thread.id; 1342 1343 } else { 1344 data.id = this.options.thread.get( 'id' ); 1345 data.js_thread = ! _.isEmpty( this.options.thread.get( 'subject' ) ); 1346 } 1347 1348 this.collection.fetch( { 1349 data: data, 1350 success : _.bind( this.messagesFetched, this ), 1351 error : this.messagesFetchError 1352 } ); 1353 }, 1354 1355 messagesFetched: function( collection, response ) { 1356 if ( ! _.isUndefined( response.thread ) ) { 1357 this.options.thread = new Backbone.Model( response.thread ); 1358 } 1359 1360 bp.Nouveau.Messages.removeFeedback(); 1361 1362 this.views.add( '#bp-message-thread-header', new bp.Views.userMessagesHeader( { model: this.options.thread } ) ); 1363 }, 1364 1365 messagesFetchError: function( collection, response ) { 1366 if ( response.feedback && response.type ) { 1367 bp.Nouveau.Messages.displayFeedback( response.feedback, response.type ); 1368 } 1369 }, 1370 1371 addMessage: function( message ) { 1372 this.views.add( '#bp-message-thread-list', new bp.Views.userMessagesEntry( { model: message } ) ); 1373 }, 1374 1375 addEditor: function() { 1376 // Load the Editor 1377 this.views.add( '#bp-message-content', new bp.Views.messageEditor() ); 1378 }, 1379 1380 sendReply: function( event ) { 1381 event.preventDefault(); 1382 1383 if ( true === this.reply.get( 'sending' ) ) { 1384 return; 1385 } 1386 1387 this.reply.set ( { 1388 thread_id : this.options.thread.get( 'id' ), 1389 content : tinyMCE.activeEditor.getContent(), 1390 sending : true 1391 } ); 1392 1393 this.collection.sync( 'create', _.pick( this.reply.attributes, ['thread_id', 'content' ] ), { 1394 success : _.bind( this.replySent, this ), 1395 error : _.bind( this.replyError, this ) 1396 } ); 1397 }, 1398 1399 replySent: function( response ) { 1400 var reply = this.collection.parse( response ); 1401 1402 // Reset the form. 1403 tinyMCE.activeEditor.setContent( '' ); 1404 this.reply.set( 'sending', false ); 1405 1406 this.collection.add( _.first( reply ) ); 1407 }, 1408 1409 replyError: function( response ) { 1410 if ( response.feedback && response.type ) { 1411 bp.Nouveau.Messages.displayFeedback( response.feedback, response.type ); 1412 } 1413 } 1414 } ); 1415 1416 bp.Nouveau.Messages.Router = Backbone.Router.extend( { 1417 routes: { 1418 'view/:id/' : 'viewMessage', 1419 '' : 'inboxView', 1420 '*unSupported': 'unSupported' 1421 }, 1422 1423 initialize: function() { 1424 var self = this; 1425 1426 _.each( BP_Nouveau.messages.supportedRoutes, function( route, slug ) { 1427 self.route( route + '/', slug + 'Route', function() { 1428 if ( 'compose' === slug ) { 1429 self.composeMessage(); 1430 } else if ( 'sentbox' === slug ) { 1431 self.sentboxView(); 1432 } else if ( 'starred' === slug ) { 1433 self.starredView(); 1434 } else if ( 'inbox' === slug ) { 1435 self.inboxView(); 1436 } 1437 } ); 1438 } ); 1439 }, 1440 1441 composeMessage: function() { 1442 bp.Nouveau.Messages.composeView(); 1443 }, 1444 1445 viewMessage: function( thread_id ) { 1446 if ( ! thread_id ) { 1447 return; 1448 } 1449 1450 // Try to get the corresponding thread. 1451 var thread = bp.Nouveau.Messages.threads.get( thread_id ); 1452 1453 if ( undefined === thread ) { 1454 thread = {}; 1455 thread.id = thread_id; 1456 } 1457 1458 bp.Nouveau.Messages.singleView( thread ); 1459 }, 1460 1461 sentboxView: function() { 1462 bp.Nouveau.Messages.box = 'sentbox'; 1463 bp.Nouveau.Messages.threadsView(); 1464 }, 1465 1466 starredView: function() { 1467 bp.Nouveau.Messages.box = 'starred'; 1468 bp.Nouveau.Messages.threadsView(); 1469 }, 1470 1471 unSupported: function() { 1472 bp.Nouveau.Messages.box = 'unsupported'; 1473 }, 1474 1475 inboxView: function() { 1476 bp.Nouveau.Messages.box = 'inbox'; 1477 bp.Nouveau.Messages.threadsView(); 1478 } 1479 } ); 1480 1481 // Launch BP Nouveau Groups. 1482 bp.Nouveau.Messages.start(); 1483 1484 } )( window.bp, jQuery );
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sat Dec 7 01:00:57 2024 | Cross-referenced by PHPXref 0.7.1 |