[ Index ] |
PHP Cross Reference of BuddyPress |
[Summary view] [Print] [Text view]
1 /* global BP_Nouveau, _, Backbone */ 2 /* @since 3.0.0 */ 3 /* @version 8.0.0 */ 4 window.wp = window.wp || {}; 5 window.bp = window.bp || {}; 6 7 ( function( bp, $ ) { 8 bp.Nouveau = bp.Nouveau || {}; 9 10 // Bail if not set 11 if ( typeof bp.Nouveau.Activity === 'undefined' || 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 /** 22 * [Activity description] 23 * @type {Object} 24 */ 25 bp.Nouveau.Activity.postForm = { 26 start: function() { 27 this.views = new Backbone.Collection(); 28 this.ActivityObjects = new bp.Collections.ActivityObjects(); 29 this.buttons = new Backbone.Collection(); 30 31 this.postFormView(); 32 }, 33 34 postFormView: function() { 35 // Do not carry on if the main element is not available. 36 if ( ! $( '#bp-nouveau-activity-form' ).length ) { 37 return; 38 } 39 40 // Create the BuddyPress Uploader 41 var postForm = new bp.Views.PostForm(); 42 43 // Add it to views 44 this.views.add( { id: 'post_form', view: postForm } ); 45 46 // Display it 47 postForm.inject( '#bp-nouveau-activity-form' ); 48 } 49 }; 50 51 if ( typeof bp.View === 'undefined' ) { 52 // Extend wp.Backbone.View with .prepare() and .inject() 53 bp.View = bp.Backbone.View.extend( { 54 inject: function( selector ) { 55 this.render(); 56 $(selector).html( this.el ); 57 this.views.ready(); 58 }, 59 60 prepare: function() { 61 if ( ! _.isUndefined( this.model ) && _.isFunction( this.model.toJSON ) ) { 62 return this.model.toJSON(); 63 } else { 64 return {}; 65 } 66 } 67 } ); 68 } 69 70 /** Models ****************************************************************/ 71 72 // The Activity to post 73 bp.Models.Activity = Backbone.Model.extend( { 74 defaults: { 75 user_id: 0, 76 item_id: 0, 77 object: '', 78 content: '' 79 } 80 } ); 81 82 // Object, the activity is attached to (group or blog or any other) 83 bp.Models.ActivityObject = Backbone.Model.extend( { 84 defaults: { 85 id : 0, 86 name : '', 87 avatar_url : '', 88 object_type : 'group' 89 } 90 } ); 91 92 /** Collections ***********************************************************/ 93 94 // Objects, the activity can be attached to (groups or blogs or any others) 95 bp.Collections.ActivityObjects = Backbone.Collection.extend( { 96 model: bp.Models.ActivityObject, 97 98 sync: function( method, model, options ) { 99 100 if ( 'read' === method ) { 101 options = options || {}; 102 options.context = this; 103 options.data = _.extend( options.data || {}, { 104 action: 'bp_nouveau_get_activity_objects' 105 } ); 106 107 return bp.ajax.send( options ); 108 } 109 }, 110 111 parse: function( resp ) { 112 if ( ! _.isArray( resp ) ) { 113 resp = [resp]; 114 } 115 116 return resp; 117 } 118 119 } ); 120 121 /** Views *****************************************************************/ 122 123 // Feedback messages 124 bp.Views.activityFeedback = bp.View.extend( { 125 tagName : 'div', 126 id : 'message', 127 template : bp.template( 'activity-post-form-feedback' ), 128 129 initialize: function() { 130 this.model = new Backbone.Model(); 131 132 if ( this.options.value ) { 133 this.model.set( 'message', this.options.value, { silent: true } ); 134 } 135 136 this.type = 'info'; 137 138 if ( ! _.isUndefined( this.options.type ) && 'info' !== this.options.type ) { 139 this.type = this.options.type; 140 } 141 142 this.el.className = 'bp-messages bp-feedback ' + this.type ; 143 } 144 } ); 145 146 // Regular input 147 bp.Views.ActivityInput = bp.View.extend( { 148 tagName : 'input', 149 150 attributes: { 151 type : 'text' 152 }, 153 154 initialize: function() { 155 if ( ! _.isObject( this.options ) ) { 156 return; 157 } 158 159 _.each( this.options, function( value, key ) { 160 this.$el.prop( key, value ); 161 }, this ); 162 } 163 } ); 164 165 // The content of the activity 166 bp.Views.WhatsNew = bp.View.extend( { 167 tagName : 'textarea', 168 className : 'bp-suggestions', 169 id : 'whats-new', 170 171 attributes: { 172 name : 'whats-new', 173 cols : '50', 174 rows : '4', 175 placeholder : BP_Nouveau.activity.strings.whatsnewPlaceholder, 176 'aria-label' : BP_Nouveau.activity.strings.whatsnewLabel 177 }, 178 179 initialize: function() { 180 this.on( 'ready', this.adjustContent, this ); 181 182 this.options.activity.on( 'change:content', this.resetContent, this ); 183 }, 184 185 adjustContent: function() { 186 187 // First adjust layout 188 this.$el.css( { 189 resize: 'none', 190 height: '50px' 191 } ); 192 193 // Check for mention 194 var mention = bp.Nouveau.getLinkParams( null, 'r' ) || null; 195 196 if ( ! _.isNull( mention ) ) { 197 this.$el.text( '@' + _.escape( mention ) + ' ' ); 198 this.$el.focus(); 199 } 200 }, 201 202 resetContent: function( activity ) { 203 if ( _.isUndefined( activity ) ) { 204 return; 205 } 206 207 this.$el.val( activity.get( 'content' ) ); 208 } 209 } ); 210 211 bp.Views.WhatsNewPostIn = bp.View.extend( { 212 tagName: 'select', 213 id: 'whats-new-post-in', 214 215 attributes: { 216 name : 'whats-new-post-in', 217 'aria-label' : BP_Nouveau.activity.strings.whatsnewpostinLabel 218 }, 219 220 events: { 221 change: 'change' 222 }, 223 224 keys: [], 225 226 initialize: function() { 227 this.model = new Backbone.Model(); 228 229 this.filters = this.options.filters || {}; 230 231 // Build `<option>` elements. 232 this.$el.html( _.chain( this.filters ).map( function( filter, value ) { 233 return { 234 el: $( '<option></option>' ).val( value ).html( filter.text )[0], 235 priority: filter.priority || 50 236 }; 237 }, this ).sortBy( 'priority' ).pluck( 'el' ).value() ); 238 }, 239 240 change: function() { 241 var filter = this.filters[ this.el.value ]; 242 if ( filter ) { 243 this.model.set( { 'selected': this.el.value, 'placeholder': filter.autocomplete_placeholder } ); 244 } 245 } 246 } ); 247 248 bp.Views.Item = bp.View.extend( { 249 tagName: 'li', 250 className: 'bp-activity-object', 251 template: bp.template( 'activity-target-item' ), 252 253 attributes: { 254 role: 'checkbox' 255 }, 256 257 initialize: function() { 258 if ( this.model.get( 'selected' ) ) { 259 this.el.className += ' selected'; 260 } 261 }, 262 263 events: { 264 click : 'setObject' 265 }, 266 267 setObject:function( event ) { 268 event.preventDefault(); 269 270 if ( true === this.model.get( 'selected' ) ) { 271 this.model.clear(); 272 } else { 273 this.model.set( 'selected', true ); 274 } 275 } 276 } ); 277 278 bp.Views.AutoComplete = bp.View.extend( { 279 tagName : 'ul', 280 id : 'whats-new-post-in-box-items', 281 282 events: { 283 keyup : 'autoComplete' 284 }, 285 286 initialize: function() { 287 var autocomplete = new bp.Views.ActivityInput( { 288 type : 'text', 289 id : 'activity-autocomplete', 290 placeholder : this.options.placeholder || '' 291 } ).render(); 292 293 this.$el.prepend( $( '<li></li>' ).html( autocomplete.$el ) ); 294 295 this.on( 'ready', this.setFocus, this ); 296 this.collection.on( 'add', this.addItemView, this ); 297 this.collection.on( 'reset', this.cleanView, this ); 298 }, 299 300 setFocus: function() { 301 this.$el.find( '#activity-autocomplete' ).focus(); 302 }, 303 304 addItemView: function( item ) { 305 this.views.add( new bp.Views.Item( { model: item } ) ); 306 }, 307 308 autoComplete: function() { 309 var search = $( '#activity-autocomplete' ).val(); 310 311 // Reset the collection before starting a new search 312 this.collection.reset(); 313 314 if ( 2 > search.length ) { 315 return; 316 } 317 318 this.collection.fetch( { 319 data: { 320 type : this.options.type, 321 search : search, 322 nonce : BP_Nouveau.nonces.activity 323 }, 324 success : _.bind( this.itemFetched, this ), 325 error : _.bind( this.itemFetched, this ) 326 } ); 327 }, 328 329 itemFetched: function( items ) { 330 if ( ! items.length ) { 331 this.cleanView(); 332 } 333 }, 334 335 cleanView: function() { 336 _.each( this.views._views[''], function( view ) { 337 view.remove(); 338 } ); 339 } 340 } ); 341 342 bp.Views.FormAvatar = bp.View.extend( { 343 tagName : 'div', 344 id : 'whats-new-avatar', 345 template : bp.template( 'activity-post-form-avatar' ), 346 347 initialize: function() { 348 this.model = new Backbone.Model( _.pick( BP_Nouveau.activity.params, [ 349 'user_id', 350 'avatar_url', 351 'avatar_width', 352 'avatar_height', 353 'avatar_alt', 354 'user_domain' 355 ] ) ); 356 357 if ( this.model.has( 'avatar_url' ) ) { 358 this.model.set( 'display_avatar', true ); 359 } 360 } 361 } ); 362 363 bp.Views.FormContent = bp.View.extend( { 364 tagName : 'div', 365 id : 'whats-new-content', 366 367 initialize: function() { 368 this.$el.html( $('<div></div>' ).prop( 'id', 'whats-new-textarea' ) ); 369 this.views.set( '#whats-new-textarea', new bp.Views.WhatsNew( { activity: this.options.activity } ) ); 370 } 371 } ); 372 373 bp.Views.FormOptions = bp.View.extend( { 374 tagName : 'div', 375 id : 'whats-new-options', 376 template : bp.template( 'activity-post-form-options' ) 377 } ); 378 379 bp.Views.BeforeFormInputs = bp.View.extend( { 380 tagName : 'div', 381 template : bp.template( 'activity-before-post-form-inputs' ) 382 } ); 383 384 bp.Views.FormTarget = bp.View.extend( { 385 tagName : 'div', 386 id : 'whats-new-post-in-box', 387 className : 'in-profile', 388 389 initialize: function() { 390 var select = new bp.Views.WhatsNewPostIn( { filters: BP_Nouveau.activity.params.objects } ); 391 this.views.add( select ); 392 393 select.model.on( 'change', this.attachAutocomplete, this ); 394 bp.Nouveau.Activity.postForm.ActivityObjects.on( 'change:selected', this.postIn, this ); 395 }, 396 397 attachAutocomplete: function( model ) { 398 if ( 0 !== bp.Nouveau.Activity.postForm.ActivityObjects.models.length ) { 399 bp.Nouveau.Activity.postForm.ActivityObjects.reset(); 400 } 401 402 // Clean up views 403 _.each( this.views._views[''], function( view ) { 404 if ( ! _.isUndefined( view.collection ) ) { 405 view.remove(); 406 } 407 } ); 408 409 if ( 'profile' !== model.get( 'selected') ) { 410 this.views.add( new bp.Views.AutoComplete( { 411 collection: bp.Nouveau.Activity.postForm.ActivityObjects, 412 type: model.get( 'selected' ), 413 placeholder : model.get( 'placeholder' ) 414 } ) ); 415 416 // Set the object type 417 this.model.set( 'object', model.get( 'selected' ) ); 418 419 } else { 420 this.model.set( { object: 'user', item_id: 0 } ); 421 } 422 423 this.updateDisplay(); 424 }, 425 426 postIn: function( model ) { 427 if ( _.isUndefined( model.get( 'id' ) ) ) { 428 // Reset the item id 429 this.model.set( 'item_id', 0 ); 430 431 // When the model has been cleared, Attach Autocomplete! 432 this.attachAutocomplete( new Backbone.Model( { selected: this.model.get( 'object' ) } ) ); 433 return; 434 } 435 436 // Set the item id for the selected object 437 this.model.set( 'item_id', model.get( 'id' ) ); 438 439 // Set the view to the selected object 440 this.views.set( '#whats-new-post-in-box-items', new bp.Views.Item( { model: model } ) ); 441 }, 442 443 updateDisplay: function() { 444 if ( 'user' !== this.model.get( 'object' ) ) { 445 this.$el.removeClass( ); 446 } else if ( ! this.$el.hasClass( 'in-profile' ) ) { 447 this.$el.addClass( 'in-profile' ); 448 } 449 } 450 } ); 451 452 /** 453 * Now build the buttons! 454 * @type {[type]} 455 */ 456 bp.Views.FormButtons = bp.View.extend( { 457 tagName : 'div', 458 id : 'whats-new-actions', 459 460 initialize: function() { 461 this.views.add( new bp.View( { tagName: 'ul', id: 'whats-new-buttons' } ) ); 462 463 _.each( this.collection.models, function( button ) { 464 this.addItemView( button ); 465 }, this ); 466 467 this.collection.on( 'change:active', this.isActive, this ); 468 }, 469 470 addItemView: function( button ) { 471 this.views.add( '#whats-new-buttons', new bp.Views.FormButton( { model: button } ) ); 472 }, 473 474 isActive: function( button ) { 475 // Clean up views 476 _.each( this.views._views[''], function( view, index ) { 477 if ( 0 !== index ) { 478 view.remove(); 479 } 480 } ); 481 482 // Then loop threw all buttons to update their status 483 if ( true === button.get( 'active' ) ) { 484 _.each( this.views._views['#whats-new-buttons'], function( view ) { 485 if ( view.model.get( 'id') !== button.get( 'id' ) ) { 486 // Silently update the model 487 view.model.set( 'active', false, { silent: true } ); 488 489 // Remove the active class 490 view.$el.removeClass( 'active' ); 491 492 // Trigger an even to let Buttons reset 493 // their modifications to the activity model 494 this.collection.trigger( 'reset:' + view.model.get( 'id' ), this.model ); 495 } 496 }, this ); 497 498 // Tell the active Button to load its content 499 this.collection.trigger( 'display:' + button.get( 'id' ), this ); 500 501 // Trigger an even to let Buttons reset 502 // their modifications to the activity model 503 } else { 504 this.collection.trigger( 'reset:' + button.get( 'id' ), this.model ); 505 } 506 } 507 } ); 508 509 bp.Views.FormButton = bp.View.extend( { 510 tagName : 'li', 511 className : 'whats-new-button', 512 template : bp.template( 'activity-post-form-buttons' ), 513 514 events: { 515 click : 'setActive' 516 }, 517 518 setActive: function( event ) { 519 var isActive = this.model.get( 'active' ) || false; 520 521 // Stop event propagation 522 event.preventDefault(); 523 524 if ( false === isActive ) { 525 this.$el.addClass( 'active' ); 526 this.model.set( 'active', true ); 527 } else { 528 this.$el.removeClass( 'active' ); 529 this.model.set( 'active', false ); 530 } 531 } 532 } ); 533 534 bp.Views.FormSubmit = bp.View.extend( { 535 tagName : 'div', 536 id : 'whats-new-submit', 537 className : 'in-profile', 538 539 initialize: function() { 540 var reset = new bp.Views.ActivityInput( { 541 type : 'reset', 542 id : 'aw-whats-new-reset', 543 className : 'text-button small', 544 value : BP_Nouveau.activity.strings.cancelButton 545 } ); 546 547 var submit = new bp.Views.ActivityInput( { 548 type : 'submit', 549 id : 'aw-whats-new-submit', 550 className : 'button', 551 name : 'aw-whats-new-submit', 552 value : BP_Nouveau.activity.strings.postUpdateButton 553 } ); 554 555 this.views.set( [ submit, reset ] ); 556 557 this.model.on( 'change:object', this.updateDisplay, this ); 558 }, 559 560 updateDisplay: function( model ) { 561 if ( _.isUndefined( model ) ) { 562 return; 563 } 564 565 if ( 'user' !== model.get( 'object' ) ) { 566 this.$el.removeClass( 'in-profile' ); 567 } else if ( ! this.$el.hasClass( 'in-profile' ) ) { 568 this.$el.addClass( 'in-profile' ); 569 } 570 } 571 } ); 572 573 bp.Views.PostForm = bp.View.extend( { 574 tagName : 'form', 575 className : 'activity-form', 576 id : 'whats-new-form', 577 578 attributes: { 579 name : 'whats-new-form', 580 method : 'post' 581 }, 582 583 events: { 584 'focus #whats-new' : 'displayFull', 585 'reset' : 'resetForm', 586 'submit' : 'postUpdate', 587 'keydown' : 'postUpdate' 588 }, 589 590 initialize: function() { 591 this.model = new bp.Models.Activity( _.pick( 592 BP_Nouveau.activity.params, 593 ['user_id', 'item_id', 'object' ] 594 ) ); 595 this.options.backcompat = BP_Nouveau.activity.params.backcompat; 596 var staticViews = [ 597 new bp.Views.FormAvatar(), 598 new bp.Views.FormContent( { activity: this.model } ) 599 ]; 600 601 // Backcompat to take the `bp_before_activity_post_form` action in account. 602 if ( true === this.options.backcompat.before_post_form ) { 603 staticViews.unshift( new bp.Views.BeforeFormInputs() ); 604 } 605 606 // Clone the model to set the resetted one 607 this.resetModel = this.model.clone(); 608 609 this.views.set( staticViews ); 610 611 this.model.on( 'change:errors', this.displayFeedback, this ); 612 }, 613 614 displayFull: function( event ) { 615 var numStaticViews = true === this.options.backcompat.before_post_form ? 3 : 2; 616 617 // Remove feedback. 618 this.cleanFeedback(); 619 620 if ( numStaticViews !== this.views._views[''].length ) { 621 return; 622 } 623 624 $( event.target ).css( { 625 resize : 'vertical', 626 height : 'auto' 627 } ); 628 629 this.$el.addClass( 'activity-form-expanded' ); 630 631 // Add the container view for buttons or custom fields. 632 if ( true === this.options.backcompat.post_form_options ) { 633 this.views.add( new bp.Views.FormOptions( { model: this.model } ) ); 634 } else { 635 this.views.add( new bp.View( { id: 'whats-new-options' } ) ); 636 } 637 638 // Attach buttons 639 if ( ! _.isUndefined( BP_Nouveau.activity.params.buttons ) ) { 640 // Global 641 bp.Nouveau.Activity.postForm.buttons.set( BP_Nouveau.activity.params.buttons ); 642 this.views.add( '#whats-new-options', new bp.Views.FormButtons( { collection: bp.Nouveau.Activity.postForm.buttons, model: this.model } ) ); 643 } 644 645 // Select box for the object 646 if ( ! _.isUndefined( BP_Nouveau.activity.params.objects ) && 1 < _.keys( BP_Nouveau.activity.params.objects ).length ) { 647 this.views.add( '#whats-new-options', new bp.Views.FormTarget( { model: this.model } ) ); 648 } 649 650 this.views.add( '#whats-new-options', new bp.Views.FormSubmit( { model: this.model } ) ); 651 }, 652 653 resetForm: function() { 654 var self = this, indexStaticViews = self.options.backcompat.before_post_form ? 2 : 1; 655 656 _.each( this.views._views[''], function( view, index ) { 657 if ( index > indexStaticViews ) { 658 view.remove(); 659 } 660 } ); 661 662 $( '#whats-new' ).css( { 663 resize : 'none', 664 height : '50px' 665 } ); 666 667 this.$el.removeClass( 'activity-form-expanded' ); 668 669 // Reset the model 670 this.model.clear(); 671 this.model.set( this.resetModel.attributes ); 672 }, 673 674 cleanFeedback: function() { 675 this.model.unset( 'errors', { silent: true } ); 676 _.each( this.views._views[''], function( view ) { 677 if ( 'message' === view.$el.prop( 'id' ) ) { 678 view.remove(); 679 } 680 } ); 681 }, 682 683 displayFeedback: function( model ) { 684 if ( _.isUndefined( this.model.get( 'errors' ) ) ) { 685 this.cleanFeedback(); 686 } else { 687 this.views.add( new bp.Views.activityFeedback( model.get( 'errors' ) ) ); 688 } 689 }, 690 691 postUpdate: function( event ) { 692 var self = this, 693 meta = {}; 694 695 if ( event ) { 696 if ( 'keydown' === event.type && ( 13 !== event.keyCode || ! event.ctrlKey ) ) { 697 return event; 698 } 699 700 event.preventDefault(); 701 } 702 703 // Set the content and meta 704 _.each( this.$el.serializeArray(), function( pair ) { 705 pair.name = pair.name.replace( '[]', '' ); 706 if ( 'whats-new' === pair.name ) { 707 self.model.set( 'content', pair.value ); 708 } else if ( -1 === _.indexOf( ['aw-whats-new-submit', 'whats-new-post-in'], pair.name ) ) { 709 if ( _.isUndefined( meta[ pair.name ] ) ) { 710 meta[ pair.name ] = pair.value; 711 } else { 712 if ( ! _.isArray( meta[ pair.name ] ) ) { 713 meta[ pair.name ] = [ meta[ pair.name ] ]; 714 } 715 716 meta[ pair.name ].push( pair.value ); 717 } 718 } 719 } ); 720 721 // Silently add meta 722 this.model.set( meta, { silent: true } ); 723 724 var data = { 725 '_wpnonce_post_update': BP_Nouveau.activity.params.post_nonce 726 }; 727 728 // Add the Akismet nonce if it exists. 729 if ( $('#_bp_as_nonce').val() ) { 730 data._bp_as_nonce = $('#_bp_as_nonce').val(); 731 } 732 733 bp.ajax.post( 'post_update', _.extend( data, this.model.attributes ) ).done( function( response ) { 734 var store = bp.Nouveau.getStorage( 'bp-activity' ), 735 searchTerms = $( '[data-bp-search="activity"] input[type="search"]' ).val(), matches = {}, 736 toPrepend = false; 737 738 // Look for matches if the stream displays search results. 739 if ( searchTerms ) { 740 searchTerms = new RegExp( searchTerms, 'im' ); 741 matches = response.activity.match( searchTerms ); 742 } 743 744 /** 745 * Before injecting the activity into the stream, we need to check the filter 746 * and search terms are consistent with it when posting from a single item or 747 * from the Activity directory. 748 */ 749 if ( ( ! searchTerms || matches ) ) { 750 toPrepend = ! store.filter || 0 === parseInt( store.filter, 10 ) || 'activity_update' === store.filter; 751 } 752 753 /** 754 * In the Activity directory, we also need to check the active scope. 755 * eg: An update posted in a private group should only show when the 756 * "My Groups" tab is active. 757 */ 758 if ( toPrepend && response.is_directory ) { 759 toPrepend = ( 'all' === store.scope && ( 'user' === self.model.get( 'object' ) || false === response.is_private ) ) || ( self.model.get( 'object' ) + 's' === store.scope ); 760 } 761 762 // Reset the form 763 self.resetForm(); 764 765 // Display a successful feedback if the acticity is not consistent with the displayed stream. 766 if ( ! toPrepend ) { 767 self.views.add( new bp.Views.activityFeedback( { value: response.message, type: 'updated' } ) ); 768 769 // Inject the activity into the stream only if it hasn't been done already (HeartBeat). 770 } else if ( ! $( '#activity-' + response.id ).length ) { 771 772 // It's the very first activity, let's make sure the container can welcome it! 773 if ( ! $( '#activity-stream ul.activity-list').length ) { 774 $( '#activity-stream' ).html( $( '<ul></ul>').addClass( 'activity-list item-list bp-list' ) ); 775 } 776 777 // Prepend the activity. 778 bp.Nouveau.inject( '#activity-stream ul.activity-list', response.activity, 'prepend' ); 779 } 780 } ).fail( function( response ) { 781 self.model.set( 'errors', { type: 'error', value: response.message } ); 782 } ); 783 } 784 } ); 785 786 bp.Nouveau.Activity.postForm.start(); 787 788 } )( 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 |