[ Index ]

WordPress Cross Reference

title

Body

[close]

/wp-includes/js/ -> autosave.js (source)

   1  /* global switchEditors, autosaveL10n, tinymce, ajaxurl, wpAjax, makeSlugeditClickable, wpCookies */
   2  var autosave, autosavePeriodical, fullscreen, doPreview,
   3      autosaveLast = '',
   4      autosaveDelayPreview = false,
   5      notSaved = true,
   6      blockSave = false,
   7      autosaveLockRelease = true;
   8  
   9  jQuery(document).ready( function($) {
  10  
  11      if ( $('#wp-content-wrap').hasClass('tmce-active') && typeof switchEditors != 'undefined' ) {
  12          autosaveLast = wp.autosave.getCompareString({
  13              post_title : $('#title').val() || '',
  14              content : switchEditors.pre_wpautop( $('#content').val() ) || '',
  15              excerpt : $('#excerpt').val() || ''
  16          });
  17      } else {
  18          autosaveLast = wp.autosave.getCompareString();
  19      }
  20  
  21      autosavePeriodical = $.schedule({time: autosaveL10n.autosaveInterval * 1000, func: function() { autosave(); }, repeat: true, protect: true});
  22  
  23      //Disable autosave after the form has been submitted
  24      $('#post').submit(function() {
  25          $.cancel(autosavePeriodical);
  26          autosaveLockRelease = false;
  27      });
  28  
  29      $('input[type="submit"], a.submitdelete', '#submitpost').click(function(){
  30          blockSave = true;
  31          window.onbeforeunload = null;
  32          $(':button, :submit', '#submitpost').each(function(){
  33              var t = $(this);
  34              if ( t.hasClass('button-primary') )
  35                  t.addClass('button-primary-disabled');
  36              else
  37                  t.addClass('button-disabled');
  38          });
  39          if ( $(this).attr('id') == 'publish' )
  40              $('#major-publishing-actions .spinner').show();
  41          else
  42              $('#minor-publishing .spinner').show();
  43      });
  44  
  45      window.onbeforeunload = function(){
  46          var editor = typeof(tinymce) != 'undefined' ? tinymce.activeEditor : false, compareString;
  47  
  48          if ( editor && ! editor.isHidden() ) {
  49              if ( editor.isDirty() )
  50                  return autosaveL10n.saveAlert;
  51          } else {
  52              if ( fullscreen && fullscreen.settings.visible ) {
  53                  compareString = wp.autosave.getCompareString({
  54                      post_title: $('#wp-fullscreen-title').val() || '',
  55                      content: $('#wp_mce_fullscreen').val() || '',
  56                      excerpt: $('#excerpt').val() || ''
  57                  });
  58              } else {
  59                  compareString = wp.autosave.getCompareString();
  60              }
  61  
  62              if ( compareString != autosaveLast )
  63                  return autosaveL10n.saveAlert;
  64          }
  65      };
  66  
  67      $(window).unload( function(e) {
  68          if ( ! autosaveLockRelease )
  69              return;
  70  
  71          // unload fires (twice) on removing the Thickbox iframe. Make sure we process only the main document unload.
  72          if ( e.target && e.target.nodeName != '#document' )
  73              return;
  74  
  75          $.ajax({
  76              type: 'POST',
  77              url: ajaxurl,
  78              async: false,
  79              data: {
  80                  action: 'wp-remove-post-lock',
  81                  _wpnonce: $('#_wpnonce').val(),
  82                  post_ID: $('#post_ID').val(),
  83                  active_post_lock: $('#active_post_lock').val()
  84              }
  85          });
  86      } );
  87  
  88      // preview
  89      $('#post-preview').click(function(){
  90          if ( $('#auto_draft').val() == '1' && notSaved ) {
  91              autosaveDelayPreview = true;
  92              autosave();
  93              return false;
  94          }
  95          doPreview();
  96          return false;
  97      });
  98  
  99      doPreview = function() {
 100          $('input#wp-preview').val('dopreview');
 101          $('form#post').attr('target', 'wp-preview').submit().attr('target', '');
 102  
 103          /*
 104           * Workaround for WebKit bug preventing a form submitting twice to the same action.
 105           * https://bugs.webkit.org/show_bug.cgi?id=28633
 106           */
 107          var ua = navigator.userAgent.toLowerCase();
 108          if ( ua.indexOf('safari') != -1 && ua.indexOf('chrome') == -1 ) {
 109              $('form#post').attr('action', function(index, value) {
 110                  return value + '?t=' + new Date().getTime();
 111              });
 112          }
 113  
 114          $('input#wp-preview').val('');
 115      };
 116  
 117      // This code is meant to allow tabbing from Title to Post content.
 118      $('#title').on('keydown.editor-focus', function(e) {
 119          var ed;
 120  
 121          if ( e.which != 9 )
 122              return;
 123  
 124          if ( !e.ctrlKey && !e.altKey && !e.shiftKey ) {
 125              if ( typeof(tinymce) != 'undefined' )
 126                  ed = tinymce.get('content');
 127  
 128              if ( ed && !ed.isHidden() ) {
 129                  $(this).one('keyup', function(){
 130                      $('#content_tbl td.mceToolbar > a').focus();
 131                  });
 132              } else {
 133                  $('#content').focus();
 134              }
 135  
 136              e.preventDefault();
 137          }
 138      });
 139  
 140      // autosave new posts after a title is typed but not if Publish or Save Draft is clicked
 141      if ( '1' == $('#auto_draft').val() ) {
 142          $('#title').blur( function() {
 143              if ( !this.value || $('#auto_draft').val() != '1' )
 144                  return;
 145              delayed_autosave();
 146          });
 147      }
 148  
 149      // When connection is lost, keep user from submitting changes.
 150      $(document).on('heartbeat-connection-lost.autosave', function( e, error, status ) {
 151          if ( 'timeout' === error || 503 == status ) {
 152              var notice = $('#lost-connection-notice');
 153              if ( ! wp.autosave.local.hasStorage ) {
 154                  notice.find('.hide-if-no-sessionstorage').hide();
 155              }
 156              notice.show();
 157              autosave_disable_buttons();
 158          }
 159      }).on('heartbeat-connection-restored.autosave', function() {
 160          $('#lost-connection-notice').hide();
 161          autosave_enable_buttons();
 162      });
 163  });
 164  
 165  function autosave_parse_response( response ) {
 166      var res = wpAjax.parseAjaxResponse(response, 'autosave'), post_id, sup;
 167  
 168      if ( res && res.responses && res.responses.length ) {
 169          if ( res.responses[0].supplemental ) {
 170              sup = res.responses[0].supplemental;
 171  
 172              jQuery.each( sup, function( selector, value ) {
 173                  if ( selector.match(/^replace-/) )
 174                      jQuery( '#' + selector.replace('replace-', '') ).val( value );
 175              });
 176          }
 177  
 178          // if no errors: add slug UI and update autosave-message
 179          if ( !res.errors ) {
 180              if ( post_id = parseInt( res.responses[0].id, 10 ) )
 181                  autosave_update_slug( post_id );
 182  
 183              if ( res.responses[0].data ) // update autosave message
 184                  jQuery('.autosave-message').text( res.responses[0].data );
 185          }
 186      }
 187  
 188      return res;
 189  }
 190  
 191  // called when autosaving pre-existing post
 192  function autosave_saved(response) {
 193      blockSave = false;
 194      autosave_parse_response(response); // parse the ajax response
 195      autosave_enable_buttons(); // re-enable disabled form buttons
 196  }
 197  
 198  // called when autosaving new post
 199  function autosave_saved_new(response) {
 200      blockSave = false;
 201      var res = autosave_parse_response(response), post_id;
 202  
 203      if ( res && res.responses.length && !res.errors ) {
 204          // An ID is sent only for real auto-saves, not for autosave=0 "keepalive" saves
 205          post_id = parseInt( res.responses[0].id, 10 );
 206  
 207          if ( post_id ) {
 208              notSaved = false;
 209              jQuery('#auto_draft').val('0'); // No longer an auto-draft
 210          }
 211  
 212          autosave_enable_buttons();
 213  
 214          if ( autosaveDelayPreview ) {
 215              autosaveDelayPreview = false;
 216              doPreview();
 217          }
 218      } else {
 219          autosave_enable_buttons(); // re-enable disabled form buttons
 220      }
 221  }
 222  
 223  function autosave_update_slug(post_id) {
 224      // create slug area only if not already there
 225      if ( 'undefined' != makeSlugeditClickable && jQuery.isFunction(makeSlugeditClickable) && !jQuery('#edit-slug-box > *').size() ) {
 226          jQuery.post( ajaxurl, {
 227                  action: 'sample-permalink',
 228                  post_id: post_id,
 229                  new_title: fullscreen && fullscreen.settings.visible ? jQuery('#wp-fullscreen-title').val() : jQuery('#title').val(),
 230                  samplepermalinknonce: jQuery('#samplepermalinknonce').val()
 231              },
 232              function(data) {
 233                  if ( data !== '-1' ) {
 234                      var box = jQuery('#edit-slug-box');
 235                      box.html(data);
 236                      if (box.hasClass('hidden')) {
 237                          box.fadeIn('fast', function () {
 238                              box.removeClass('hidden');
 239                          });
 240                      }
 241                      makeSlugeditClickable();
 242                  }
 243              }
 244          );
 245      }
 246  }
 247  
 248  function autosave_loading() {
 249      jQuery('.autosave-message').html(autosaveL10n.savingText);
 250  }
 251  
 252  function autosave_enable_buttons() {
 253      jQuery(document).trigger('autosave-enable-buttons');
 254      if ( ! wp.heartbeat || ! wp.heartbeat.hasConnectionError() ) {
 255          // delay that a bit to avoid some rare collisions while the DOM is being updated.
 256          setTimeout(function(){
 257              var parent = jQuery('#submitpost');
 258              parent.find(':button, :submit').removeAttr('disabled');
 259              parent.find('.spinner').hide();
 260          }, 500);
 261      }
 262  }
 263  
 264  function autosave_disable_buttons() {
 265      jQuery(document).trigger('autosave-disable-buttons');
 266      jQuery('#submitpost').find(':button, :submit').prop('disabled', true);
 267      // Re-enable 5 sec later. Just gives autosave a head start to avoid collisions.
 268      setTimeout( autosave_enable_buttons, 5000 );
 269  }
 270  
 271  function delayed_autosave() {
 272      setTimeout(function(){
 273          if ( blockSave )
 274              return;
 275          autosave();
 276      }, 200);
 277  }
 278  
 279  autosave = function() {
 280      var post_data = wp.autosave.getPostData(),
 281          compareString,
 282          successCallback;
 283  
 284      blockSave = true;
 285  
 286      // post_data.content cannot be retrieved at the moment
 287      if ( ! post_data.autosave )
 288          return false;
 289  
 290      // No autosave while thickbox is open (media buttons)
 291      if ( jQuery('#TB_window').css('display') == 'block' )
 292          return false;
 293  
 294      compareString = wp.autosave.getCompareString( post_data );
 295  
 296      // Nothing to save or no change.
 297      if ( compareString == autosaveLast )
 298          return false;
 299  
 300      autosaveLast = compareString;
 301      jQuery(document).triggerHandler('wpcountwords', [ post_data.content ]);
 302  
 303      // Disable buttons until we know the save completed.
 304      autosave_disable_buttons();
 305  
 306      if ( post_data.auto_draft == '1' ) {
 307          successCallback = autosave_saved_new; // new post
 308      } else {
 309          successCallback = autosave_saved; // pre-existing post
 310      }
 311  
 312      jQuery.ajax({
 313          data: post_data,
 314          beforeSend: autosave_loading,
 315          type: 'POST',
 316          url: ajaxurl,
 317          success: successCallback
 318      });
 319  
 320      return true;
 321  };
 322  
 323  // Autosave in localStorage
 324  // set as simple object/mixin for now
 325  window.wp = window.wp || {};
 326  wp.autosave = wp.autosave || {};
 327  
 328  (function($){
 329  // Returns the data for saving in both localStorage and autosaves to the server
 330  wp.autosave.getPostData = function() {
 331      var ed = typeof tinymce != 'undefined' ? tinymce.activeEditor : null, post_name, parent_id, cats = [],
 332          data = {
 333              action: 'autosave',
 334              autosave: true,
 335              post_id: $('#post_ID').val() || 0,
 336              autosavenonce: $('#autosavenonce').val() || '',
 337              post_type: $('#post_type').val() || '',
 338              post_author: $('#post_author').val() || '',
 339              excerpt: $('#excerpt').val() || ''
 340          };
 341  
 342      if ( ed && !ed.isHidden() ) {
 343          // Don't run while the tinymce spellcheck is on. It resets all found words.
 344          if ( ed.plugins.spellchecker && ed.plugins.spellchecker.active ) {
 345              data.autosave = false;
 346              return data;
 347          } else {
 348              if ( 'mce_fullscreen' == ed.id )
 349                  tinymce.get('content').setContent(ed.getContent({format : 'raw'}), {format : 'raw'});
 350  
 351              tinymce.triggerSave();
 352          }
 353      }
 354  
 355      if ( typeof fullscreen != 'undefined' && fullscreen.settings.visible ) {
 356          data.post_title = $('#wp-fullscreen-title').val() || '';
 357          data.content = $('#wp_mce_fullscreen').val() || '';
 358      } else {
 359          data.post_title = $('#title').val() || '';
 360          data.content = $('#content').val() || '';
 361      }
 362  
 363      /*
 364      // We haven't been saving tags with autosave since 2.8... Start again?
 365      $('.the-tags').each( function() {
 366          data[this.name] = this.value;
 367      });
 368      */
 369  
 370      $('input[id^="in-category-"]:checked').each( function() {
 371          cats.push(this.value);
 372      });
 373      data.catslist = cats.join(',');
 374  
 375      if ( post_name = $('#post_name').val() )
 376          data.post_name = post_name;
 377  
 378      if ( parent_id = $('#parent_id').val() )
 379          data.parent_id = parent_id;
 380  
 381      if ( $('#comment_status').prop('checked') )
 382          data.comment_status = 'open';
 383  
 384      if ( $('#ping_status').prop('checked') )
 385          data.ping_status = 'open';
 386  
 387      if ( $('#auto_draft').val() == '1' )
 388          data.auto_draft = '1';
 389  
 390      return data;
 391  };
 392  
 393  // Concatenate title, content and excerpt. Used to track changes when auto-saving.
 394  wp.autosave.getCompareString = function( post_data ) {
 395      if ( typeof post_data === 'object' ) {
 396          return ( post_data.post_title || '' ) + '::' + ( post_data.content || '' ) + '::' + ( post_data.excerpt || '' );
 397      }
 398  
 399      return ( $('#title').val() || '' ) + '::' + ( $('#content').val() || '' ) + '::' + ( $('#excerpt').val() || '' );
 400  };
 401  
 402  wp.autosave.local = {
 403  
 404      lastSavedData: '',
 405      blog_id: 0,
 406      hasStorage: false,
 407  
 408      // Check if the browser supports sessionStorage and it's not disabled
 409      checkStorage: function() {
 410          var test = Math.random(), result = false;
 411  
 412          try {
 413              sessionStorage.setItem('wp-test', test);
 414              result = sessionStorage.getItem('wp-test') == test;
 415              sessionStorage.removeItem('wp-test');
 416          } catch(e) {}
 417  
 418          this.hasStorage = result;
 419          return result;
 420      },
 421  
 422      /**
 423       * Initialize the local storage
 424       *
 425       * @return mixed False if no sessionStorage in the browser or an Object containing all post_data for this blog
 426       */
 427      getStorage: function() {
 428          var stored_obj = false;
 429          // Separate local storage containers for each blog_id
 430          if ( this.hasStorage && this.blog_id ) {
 431              stored_obj = sessionStorage.getItem( 'wp-autosave-' + this.blog_id );
 432  
 433              if ( stored_obj )
 434                  stored_obj = JSON.parse( stored_obj );
 435              else
 436                  stored_obj = {};
 437          }
 438  
 439          return stored_obj;
 440      },
 441  
 442      /**
 443       * Set the storage for this blog
 444       *
 445       * Confirms that the data was saved successfully.
 446       *
 447       * @return bool
 448       */
 449      setStorage: function( stored_obj ) {
 450          var key;
 451  
 452          if ( this.hasStorage && this.blog_id ) {
 453              key = 'wp-autosave-' + this.blog_id;
 454              sessionStorage.setItem( key, JSON.stringify( stored_obj ) );
 455              return sessionStorage.getItem( key ) !== null;
 456          }
 457  
 458          return false;
 459      },
 460  
 461      /**
 462       * Get the saved post data for the current post
 463       *
 464       * @return mixed False if no storage or no data or the post_data as an Object
 465       */
 466      getData: function() {
 467          var stored = this.getStorage(), post_id = $('#post_ID').val();
 468  
 469          if ( !stored || !post_id )
 470              return false;
 471  
 472          return stored[ 'post_' + post_id ] || false;
 473      },
 474  
 475      /**
 476       * Set (save or delete) post data in the storage.
 477       *
 478       * If stored_data evaluates to 'false' the storage key for the current post will be removed
 479       *
 480       * $param stored_data The post data to store or null/false/empty to delete the key
 481       * @return bool
 482       */
 483      setData: function( stored_data ) {
 484          var stored = this.getStorage(), post_id = $('#post_ID').val();
 485  
 486          if ( !stored || !post_id )
 487              return false;
 488  
 489          if ( stored_data )
 490              stored[ 'post_' + post_id ] = stored_data;
 491          else if ( stored.hasOwnProperty( 'post_' + post_id ) )
 492              delete stored[ 'post_' + post_id ];
 493          else
 494              return false;
 495  
 496          return this.setStorage(stored);
 497      },
 498  
 499      /**
 500       * Save post data for the current post
 501       *
 502       * Runs on a 15 sec. schedule, saves when there are differences in the post title or content.
 503       * When the optional data is provided, updates the last saved post data.
 504       *
 505       * $param data optional Object The post data for saving, minimum 'post_title' and 'content'
 506       * @return bool
 507       */
 508      save: function( data ) {
 509          var result = false, post_data, compareString;
 510  
 511          if ( ! data ) {
 512              post_data = wp.autosave.getPostData();
 513          } else {
 514              post_data = this.getData() || {};
 515              $.extend( post_data, data );
 516              post_data.autosave = true;
 517          }
 518  
 519          // Cannot get the post data at the moment
 520          if ( ! post_data.autosave )
 521              return false;
 522  
 523          compareString = wp.autosave.getCompareString( post_data );
 524  
 525          // If the content, title and excerpt did not change since the last save, don't save again
 526          if ( compareString == this.lastSavedData )
 527              return false;
 528  
 529          post_data.save_time = (new Date()).getTime();
 530          post_data.status = $('#post_status').val() || '';
 531          result = this.setData( post_data );
 532  
 533          if ( result )
 534              this.lastSavedData = compareString;
 535  
 536          return result;
 537      },
 538  
 539      // Initialize and run checkPost() on loading the script (before TinyMCE init)
 540      init: function( settings ) {
 541          var self = this;
 542  
 543          // Check if the browser supports sessionStorage and it's not disabled
 544          if ( ! this.checkStorage() )
 545              return;
 546  
 547          // Don't run if the post type supports neither 'editor' (textarea#content) nor 'excerpt'.
 548          if ( ! $('#content').length && ! $('#excerpt').length )
 549              return;
 550  
 551          if ( settings )
 552              $.extend( this, settings );
 553  
 554          if ( !this.blog_id )
 555              this.blog_id = typeof window.autosaveL10n != 'undefined' ? window.autosaveL10n.blog_id : 0;
 556  
 557          $(document).ready( function(){ self.run(); } );
 558      },
 559  
 560      // Run on DOM ready
 561      run: function() {
 562          var self = this;
 563  
 564          // Check if the local post data is different than the loaded post data.
 565          this.checkPost();
 566  
 567          // Set the schedule
 568          this.schedule = $.schedule({
 569              time: 15 * 1000,
 570              func: function() { wp.autosave.local.save(); },
 571              repeat: true,
 572              protect: true
 573          });
 574  
 575          $('form#post').on('submit.autosave-local', function() {
 576              var editor = typeof tinymce != 'undefined' && tinymce.get('content'), post_id = $('#post_ID').val() || 0;
 577  
 578              if ( editor && ! editor.isHidden() ) {
 579                  // Last onSubmit event in the editor, needs to run after the content has been moved to the textarea.
 580                  editor.onSubmit.add( function() {
 581                      wp.autosave.local.save({
 582                          post_title: $('#title').val() || '',
 583                          content: $('#content').val() || '',
 584                          excerpt: $('#excerpt').val() || ''
 585                      });
 586                  });
 587              } else {
 588                  self.save({
 589                      post_title: $('#title').val() || '',
 590                      content: $('#content').val() || '',
 591                      excerpt: $('#excerpt').val() || ''
 592                  });
 593              }
 594  
 595              wpCookies.set( 'wp-saving-post-' + post_id, 'check' );
 596          });
 597      },
 598  
 599      // Strip whitespace and compare two strings
 600      compare: function( str1, str2 ) {
 601  		function remove( string ) {
 602              return string.toString().replace(/[\x20\t\r\n\f]+/g, '');
 603          }
 604  
 605          return ( remove( str1 || '' ) == remove( str2 || '' ) );
 606      },
 607  
 608      /**
 609       * Check if the saved data for the current post (if any) is different than the loaded post data on the screen
 610       *
 611       * Shows a standard message letting the user restore the post data if different.
 612       *
 613       * @return void
 614       */
 615      checkPost: function() {
 616          var self = this, post_data = this.getData(), content, post_title, excerpt, notice,
 617              post_id = $('#post_ID').val() || 0, cookie = wpCookies.get( 'wp-saving-post-' + post_id );
 618  
 619          if ( ! post_data )
 620              return;
 621  
 622          if ( cookie ) {
 623              wpCookies.remove( 'wp-saving-post-' + post_id );
 624  
 625              if ( cookie == 'saved' ) {
 626                  // The post was saved properly, remove old data and bail
 627                  this.setData( false );
 628                  return;
 629              }
 630          }
 631  
 632          // There is a newer autosave. Don't show two "restore" notices at the same time.
 633          if ( $('#has-newer-autosave').length )
 634              return;
 635  
 636          content = $('#content').val() || '';
 637          post_title = $('#title').val() || '';
 638          excerpt = $('#excerpt').val() || '';
 639  
 640          if ( $('#wp-content-wrap').hasClass('tmce-active') && typeof switchEditors != 'undefined' )
 641              content = switchEditors.pre_wpautop( content );
 642  
 643          // cookie == 'check' means the post was not saved properly, always show #local-storage-notice
 644          if ( cookie != 'check' && this.compare( content, post_data.content ) && this.compare( post_title, post_data.post_title ) && this.compare( excerpt, post_data.excerpt ) ) {
 645              return;
 646          }
 647  
 648          this.restore_post_data = post_data;
 649          this.undo_post_data = {
 650              content: content,
 651              post_title: post_title,
 652              excerpt: excerpt
 653          };
 654  
 655          notice = $('#local-storage-notice');
 656          $('.wrap h2').first().after( notice.addClass('updated').show() );
 657  
 658          notice.on( 'click', function(e) {
 659              var target = $( e.target );
 660  
 661              if ( target.hasClass('restore-backup') ) {
 662                  self.restorePost( self.restore_post_data );
 663                  target.parent().hide();
 664                  $(this).find('p.undo-restore').show();
 665              } else if ( target.hasClass('undo-restore-backup') ) {
 666                  self.restorePost( self.undo_post_data );
 667                  target.parent().hide();
 668                  $(this).find('p.local-restore').show();
 669              }
 670  
 671              e.preventDefault();
 672          });
 673      },
 674  
 675      // Restore the current title, content and excerpt from post_data.
 676      restorePost: function( post_data ) {
 677          var editor;
 678  
 679          if ( post_data ) {
 680              // Set the last saved data
 681              this.lastSavedData = wp.autosave.getCompareString( post_data );
 682  
 683              if ( $('#title').val() != post_data.post_title )
 684                  $('#title').focus().val( post_data.post_title || '' );
 685  
 686              $('#excerpt').val( post_data.excerpt || '' );
 687              editor = typeof tinymce != 'undefined' && tinymce.get('content');
 688  
 689              if ( editor && ! editor.isHidden() && typeof switchEditors != 'undefined' ) {
 690                  // Make sure there's an undo level in the editor
 691                  editor.undoManager.add();
 692                  editor.setContent( post_data.content ? switchEditors.wpautop( post_data.content ) : '' );
 693              } else {
 694                  // Make sure the Text editor is selected
 695                  $('#content-html').click();
 696                  $('#content').val( post_data.content );
 697              }
 698  
 699              return true;
 700          }
 701  
 702          return false;
 703      }
 704  };
 705  
 706  wp.autosave.local.init();
 707  
 708  }(jQuery));


Generated: Tue Mar 25 01:41:18 2014 WordPress honlapkészítés: online1.hu