| 64 | | /* QuickPress */ |
| 65 | | quickPressLoad = function() { |
| 66 | | var act = $('#quickpost-action'), t; |
| 67 | | |
| 68 | | $( '#quick-press .submit input[type="submit"], #quick-press .submit input[type="reset"]' ).prop( 'disabled' , false ); |
| 69 | | |
| 70 | | t = $('#quick-press').submit( function( e ) { |
| 71 | | e.preventDefault(); |
| 72 | | $('#dashboard_quick_press #publishing-action .spinner').show(); |
| 73 | | $('#quick-press .submit input[type="submit"], #quick-press .submit input[type="reset"]').prop('disabled', true); |
| 74 | | |
| 75 | | $.post( t.attr( 'action' ), t.serializeArray(), function( data ) { |
| 76 | | // Replace the form, and prepend the published post. |
| 77 | | $('#dashboard_quick_press .inside').html( data ); |
| 78 | | $('#quick-press').removeClass('initial-form'); |
| 79 | | quickPressLoad(); |
| 80 | | highlightLatestPost(); |
| 81 | | $('#title').focus(); |
| 82 | | }); |
| 83 | | |
| 84 | | function highlightLatestPost () { |
| 85 | | var latestPost = $('.drafts ul li').first(); |
| 86 | | latestPost.css('background', '#fffbe5'); |
| 87 | | setTimeout(function () { |
| 88 | | latestPost.css('background', 'none'); |
| 89 | | }, 1000); |
| 90 | | } |
| 91 | | } ); |
| 92 | | |
| 93 | | $('#publish').click( function() { act.val( 'post-quickpress-publish' ); } ); |
| 94 | | |
| 95 | | $('#title, #tags-input, #content').each( function() { |
| 96 | | var input = $(this), prompt = $('#' + this.id + '-prompt-text'); |
| 97 | | |
| 98 | | if ( '' === this.value ) { |
| 99 | | prompt.removeClass('screen-reader-text'); |
| 100 | | } |
| 101 | | |
| 102 | | prompt.click( function() { |
| 103 | | $(this).addClass('screen-reader-text'); |
| 104 | | input.focus(); |
| 105 | | }); |
| 106 | | |
| 107 | | input.blur( function() { |
| 108 | | if ( '' === this.value ) { |
| 109 | | prompt.removeClass('screen-reader-text'); |
| 110 | | } |
| 111 | | }); |
| 112 | | |
| 113 | | input.focus( function() { |
| 114 | | prompt.addClass('screen-reader-text'); |
| 115 | | }); |
| 116 | | }); |
| 117 | | |
| 118 | | $('#quick-press').on( 'click focusin', function() { |
| 119 | | wpActiveEditor = 'content'; |
| 120 | | }); |
| 121 | | |
| 122 | | autoResizeTextarea(); |
| 123 | | }; |
| 124 | | quickPressLoad(); |
| 125 | | |
| | 127 | autoResizeTextarea(); |
| | 128 | |
| | 129 | }); |
| | 130 | |
| | 131 | wp.api.loadPromise.done( function() { |
| | 132 | var $ = jQuery, |
| | 133 | QuickPress = {}, |
| | 134 | draftsCollection; |
| | 135 | |
| | 136 | /** |
| | 137 | * Models |
| | 138 | */ |
| | 139 | |
| | 140 | QuickPress.Models = {}; |
| | 141 | |
| | 142 | QuickPress.Models.Draft = wp.api.models.Post.extend( { |
| | 143 | initialize: function( attributes ) { |
| | 144 | if ( attributes ) { |
| | 145 | this.set( this.normalizeAttributes( attributes ) ); |
| | 146 | } |
| | 147 | }, |
| | 148 | |
| | 149 | parse: function( response ) { |
| | 150 | return this.normalizeAttributes( response ); |
| | 151 | }, |
| | 152 | |
| | 153 | normalizeAttributes: function( attributes ) { |
| | 154 | var date; |
| | 155 | |
| | 156 | if ( ! attributes ) { |
| | 157 | return attributes; |
| | 158 | } |
| | 159 | |
| | 160 | // Post entities from the REST API include the content and title in |
| | 161 | // nested objects, but our new form model will assign as a string, |
| | 162 | // so we normalize to simplify display logic |
| | 163 | |
| | 164 | if ( 'object' === typeof attributes.content ) { |
| | 165 | attributes.content = attributes.content.rendered; |
| | 166 | } |
| | 167 | |
| | 168 | if ( 'object' === typeof attributes.title ) { |
| | 169 | attributes.title = attributes.title.rendered; |
| | 170 | } |
| | 171 | |
| | 172 | attributes.formattedContent = wp.formatting.trimWords( attributes.content, 10 ); |
| | 173 | |
| | 174 | // We can format dates using newer browser i18n features, but also |
| | 175 | // provide a fallback to the not-as-nice Date#toLocaleDateString |
| | 176 | date = new Date( wp.api.utils.parseISO8601( attributes.date ) ); |
| | 177 | date.setTime( date.getTime() + ( date.getTimezoneOffset() * 60 * 1000 ) ); |
| | 178 | if ( 'undefined' !== typeof Intl && Intl.DateTimeFormat ) { |
| | 179 | attributes.formattedDate = new Intl.DateTimeFormat( undefined, { |
| | 180 | month: 'long', |
| | 181 | day: 'numeric', |
| | 182 | year: 'numeric' |
| | 183 | } ).format( date ); |
| | 184 | } else { |
| | 185 | attributes.formattedDate = date.toLocaleDateString(); |
| | 186 | } |
| | 187 | |
| | 188 | return attributes; |
| | 189 | }, |
| | 190 | |
| | 191 | validate: function( attributes ) { |
| | 192 | if ( ! attributes.title && ! attributes.content ) { |
| | 193 | return 'no-content'; |
| | 194 | } |
| | 195 | } |
| | 196 | } ); |
| | 197 | |
| | 198 | /** |
| | 199 | * Collections |
| | 200 | */ |
| | 201 | |
| | 202 | QuickPress.Collections = {}; |
| | 203 | |
| | 204 | QuickPress.Collections.Drafts = wp.api.collections.Posts.extend( { |
| | 205 | model: QuickPress.Models.Draft, |
| | 206 | |
| | 207 | comparator: function( a, b ) { |
| | 208 | // Sort by date descending, date is an ISO8601 string and can be |
| | 209 | // compared lexicographically |
| | 210 | return a.get( 'date' ) < b.get( 'date' ); |
| | 211 | } |
| | 212 | } ); |
| | 213 | |
| | 214 | /** |
| | 215 | * Collections |
| | 216 | */ |
| | 217 | |
| | 218 | QuickPress.Views = {}; |
| | 219 | |
| | 220 | QuickPress.Views.Form = wp.Backbone.View.extend( { |
| | 221 | events: { |
| | 222 | 'click :input': 'hidePromptAndFocus', |
| | 223 | 'focus :input': 'hidePrompt', |
| | 224 | 'blur :input': 'showPrompt', |
| | 225 | reset: 'showAllPrompts', |
| | 226 | click: 'setActiveEditor', |
| | 227 | focusin: 'setActiveEditor', |
| | 228 | submit: 'submit' |
| | 229 | }, |
| | 230 | |
| | 231 | initialize: function() { |
| | 232 | this.showAllPrompts(); |
| | 233 | |
| | 234 | this.listenTo( this.model, 'invalid', this.render ); |
| | 235 | this.listenTo( this.model, 'error', this.showSyncError ); |
| | 236 | }, |
| | 237 | |
| | 238 | togglePrompt: function( element, visible ) { |
| | 239 | var $input = $( element ), |
| | 240 | hasContent = $input.val().length > 0; |
| | 241 | |
| | 242 | $( element ).siblings( '.prompt' ).toggleClass( 'screen-reader-text', ! visible || hasContent ); |
| | 243 | }, |
| | 244 | |
| | 245 | showAllPrompts: function() { |
| | 246 | this.$el.find( ':input' ).each( _.bind( function( i, input ) { |
| | 247 | // Prompt toggling must be deferred because the reset event is |
| | 248 | // fired before the input values have been cleared |
| | 249 | _.defer( _.bind( this.togglePrompt, this, input, true ) ); |
| | 250 | }, this ) ); |
| | 251 | }, |
| | 252 | |
| | 253 | showPrompt: function( event ) { |
| | 254 | this.togglePrompt( event.target, true ); |
| | 255 | }, |
| | 256 | |
| | 257 | hidePrompt: function( event ) { |
| | 258 | this.togglePrompt( event.target, false ); |
| | 259 | }, |
| | 260 | |
| | 261 | hidePromptAndFocus: function( event ) { |
| | 262 | this.togglePrompt( event.target, false ); |
| | 263 | $( ':input', event.target ).focus(); |
| | 264 | }, |
| | 265 | |
| | 266 | setActiveEditor: function() { |
| | 267 | wpActiveEditor = 'content'; |
| | 268 | }, |
| | 269 | |
| | 270 | showSyncError: function() { |
| | 271 | this.syncError = true; |
| | 272 | this.render(); |
| | 273 | }, |
| | 274 | |
| | 275 | submit: function( event ) { |
| | 276 | var values; |
| | 277 | |
| | 278 | delete this.syncError; |
| | 279 | event.preventDefault(); |
| | 280 | |
| | 281 | // jQuery's serializeArray returns an array of field tuples, which |
| | 282 | // we need to transform into an object before sending to API |
| | 283 | values = _.reduce( this.$el.serializeArray(), function( memo, field ) { |
| | 284 | memo[ field.name ] = field.value; |
| | 285 | return memo; |
| | 286 | }, {} ); |
| | 287 | |
| | 288 | // Ensure that by setting these fields on model that it is valid |
| | 289 | // before proceeding with save |
| | 290 | this.model.set( values ); |
| | 291 | if ( ! this.model.isValid() ) { |
| | 292 | return; |
| | 293 | } |
| | 294 | |
| | 295 | // Show a spinner during the callback. |
| | 296 | this.$el.addClass( 'is-saving' ); |
| | 297 | |
| | 298 | this.model.save() |
| | 299 | .always( _.bind( function() { |
| | 300 | this.$el.removeClass( 'is-saving' ); |
| | 301 | }, this ) ) |
| | 302 | .success( _.bind( function() { |
| | 303 | this.collection.add( this.model ); |
| | 304 | this.model = new QuickPress.Models.Draft(); |
| | 305 | this.el.reset(); |
| | 306 | }, this ) ); |
| | 307 | }, |
| | 308 | |
| | 309 | render: function() { |
| | 310 | var $error = this.$el.find( '.error' ), |
| | 311 | errorText; |
| | 312 | |
| | 313 | if ( this.model.validationError ) { |
| | 314 | // Error via submission validation |
| | 315 | errorText = quickPress.l10n[ this.model.validationError ]; |
| | 316 | } else if ( this.syncError ) { |
| | 317 | // Error via API save failure |
| | 318 | errorText = quickPress.l10n.error; |
| | 319 | } |
| | 320 | |
| | 321 | // Error notice is only visible if error text determined |
| | 322 | $error.toggle( !! errorText ); |
| | 323 | if ( errorText ) { |
| | 324 | $error.html( $( '<p />', { text: errorText } ) ); |
| | 325 | } |
| | 326 | } |
| | 327 | } ); |
| | 328 | |
| | 329 | QuickPress.Views.DraftList = wp.Backbone.View.extend( { |
| | 330 | initialize: function() { |
| | 331 | this.listenTo( this.collection, 'sync', this.onDraftsLoaded ); |
| | 332 | }, |
| | 333 | |
| | 334 | onDraftsLoaded: function() { |
| | 335 | this.listenTo( this.collection, 'add', this.renderNew ); |
| | 336 | this.render(); |
| | 337 | }, |
| | 338 | |
| | 339 | renderNew: function() { |
| | 340 | // Display highlight effect to first (added) item for one second |
| | 341 | var $newEl = this.render().$el.find( 'li:first' ).addClass( 'is-new' ); |
| | 342 | setTimeout( function() { |
| | 343 | $newEl.removeClass( 'is-new' ); |
| | 344 | }, 1000 ); |
| | 345 | }, |
| | 346 | |
| | 347 | render: function() { |
| | 348 | // Though we request only four drafts initially, since more will be |
| | 349 | // added through the form, render only the first four sorted |
| | 350 | var slicedCollection = this.collection.slice( 0, 4 ); |
| | 351 | |
| | 352 | // Hide drafts list if no drafts exist |
| | 353 | this.$el.toggle( this.collection.length > 0 ); |
| | 354 | |
| | 355 | // "View All" link is visible if 4 or more drafts, since we only |
| | 356 | // show a maximum of 4 drafts in the list |
| | 357 | this.$el.find( '.view-all' ).toggle( slicedCollection.length > 3 ); |
| | 358 | |
| | 359 | // If after drafts load, this could be the first render, so remove |
| | 360 | // placeholder effect and render the first four drafts |
| | 361 | this.$el.find( '.drafts-list' ) |
| | 362 | .removeClass( 'is-placeholder' ) |
| | 363 | .html( _.map( slicedCollection, function( draft ) { |
| | 364 | return new QuickPress.Views.DraftListItem( { |
| | 365 | model: draft |
| | 366 | } ).render().el; |
| | 367 | } ) ); |
| | 368 | |
| | 369 | return this; |
| | 370 | } |
| | 371 | } ); |
| | 372 | |
| | 373 | QuickPress.Views.DraftListItem = wp.Backbone.View.extend( { |
| | 374 | tagName: 'li', |
| | 375 | |
| | 376 | template: wp.template( 'item-quick-press-draft' ), |
| | 377 | |
| | 378 | render: function() { |
| | 379 | this.$el.html( this.template( this.model.attributes ) ); |
| | 380 | |
| | 381 | return this; |
| | 382 | } |
| | 383 | } ); |
| | 384 | |
| | 385 | /** |
| | 386 | * Initialize |
| | 387 | */ |
| | 388 | |
| | 389 | // Fetch drafts |
| | 390 | draftsCollection = new QuickPress.Collections.Drafts(); |
| | 391 | draftsCollection.fetch( { |
| | 392 | data: { |
| | 393 | status: 'draft', |
| | 394 | author: quickPress.currentUserId, |
| | 395 | per_page: 4 |
| | 396 | } |
| | 397 | } ); |
| | 398 | |
| | 399 | // Drafts list is initialized but not rendered until drafts load |
| | 400 | new QuickPress.Views.DraftList( { |
| | 401 | el: '#quick-press-drafts', |
| | 402 | collection: draftsCollection |
| | 403 | } ); |
| | 404 | |
| | 405 | new QuickPress.Views.Form( { |
| | 406 | el: '#quick-press', |
| | 407 | model: new QuickPress.Models.Draft(), |
| | 408 | collection: draftsCollection |
| | 409 | } ).render(); |