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(); |