User:Ruud Koot/Listing editor/develop.js

From Wikivoyage
Jump to navigation Jump to search

Note: After publishing, you may have to bypass your browser's cache to see the changes.

  • Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
  • Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
  • Internet Explorer / Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5
  • Opera: Press Ctrl-F5.
/* KNOWN ISSUES
   - edittoken can expire
   - editing issues
     - copy & paste requires some care with contenteditable
     - {{!}}
     - need better heuristics when user adds an ampersand
     - bold, italics and other simple wiki markup
     - html-characters that need escaping, such as '<' and '>'
   - not all field are handled yet
     - cannot edit 'url', 'lat', or 'long'
   - round-tripping is terribly incomplete
     - e.g. Wikilinks will be turned into HTML anchors
     - we should check if we can accurately round-trip ''before'' editing, and abort and log otherwise
   - missing (must have) features
     - add additional fields to listings
     - add new listings
   - does not fail gracefully on errors
   - does not remove "save" button while saving
*/

mw.loader.using( ['mediawiki.util', 'jquery.client'], function () {

  var version = 'develop'

  importStylesheet( 'User:Ruud Koot/Listing editor/' + version + '.css' );

  var fields = [ 'name', 'alt', 'address', 'directions', 'lat', 'long', 'phone', 'tollfree', 'email', 'fax', 'url', 'hours', 'checkin', 'checkout', 'price', 'content' ];
  var relevantFields = {
    'see'    : [             [ 'name', 'alt', 'url', 'email' ], [ 'address', 'lat', 'long', 'directions' ], [ 'phone', 'tollfree', 'fax' ], [ 'hours', 'price' ],                        [ 'content' ] ],
    'do'     : [             [ 'name', 'alt', 'url', 'email' ], [ 'address', 'lat', 'long', 'directions' ], [ 'phone', 'tollfree', 'fax' ], [ 'hours', 'price' ],                        [ 'content' ] ],
    'buy'    : [             [ 'name', 'alt', 'url', 'email' ], [ 'address', 'lat', 'long', 'directions' ], [ 'phone', 'tollfree', 'fax' ], [ 'hours', 'price' ],                        [ 'content' ] ],
    'eat'    : [             [ 'name', 'alt', 'url', 'email' ], [ 'address', 'lat', 'long', 'directions' ], [ 'phone', 'tollfree', 'fax' ], [ 'hours', 'price' ],                        [ 'content' ] ],
    'drink'  : [             [ 'name', 'alt', 'url', 'email' ], [ 'address', 'lat', 'long', 'directions' ], [ 'phone', 'tollfree', 'fax' ], [ 'hours', 'price' ],                        [ 'content' ] ],
    'sleep'  : [             [ 'name', 'alt', 'url', 'email' ], [ 'address', 'lat', 'long', 'directions' ], [ 'phone', 'tollfree', 'fax' ], [ 'hours', 'price', 'checkin', 'checkout' ], [ 'content' ] ],
    'listing': [ [ 'type' ], [ 'name', 'alt', 'url', 'email' ], [ 'address', 'lat', 'long', 'directions' ], [ 'phone', 'tollfree', 'fax' ], [ 'hours', 'price', 'checkin', 'checkout' ], [ 'content' ] ]
  };

  var savedEntry      = undefined; /* global? */
  var wikiTextSection = undefined; /* global? */
  var wikitext        = undefined; /* global? */
  var listingType     = undefined; /* global? */

  function handleException( f, e ) {
    try {
      alert( f + ': ' + e );

      $.ajax({
          url: mw.util.wikiScript( 'api' ),
          data: {
              format: 'json',
              action: 'edit',
              title: 'User:Ruud_Koot/Listing_editor/errorlog',
              summary: '[[User:Ruud Koot/Listing editor|Listing editor]] (' + version + ')',
              prependtext: '* ' + f + ': ' + e,
              token: mw.user.tokens.get( 'csrfToken' )
          },
          dataType: 'json',
          type: 'POST',
          success: function( data ) {
              if ( data && data.edit && data.edit.result == 'Success' ) {
                  window.location.reload(); // reload page if edit was successful
              } else if ( data && data.error ) {
                  alert( 'Error: API returned error code "' + data.error.code + '": ' + data.error.info );
              } else {
                  alert( 'Error: Unknown result from API.' );
              }
          },
          error: function( xhr ) {
              alert( 'Error: Request failed.' );
          }
      });

      switch ( f ) {
        case 'addEditButtons':
          return;
        default:
          window.location.reload();
      }
    } catch ( e ) {
      alert( 'double fault' );
      window.location.reload();
    }
  }

  function addEditButtons() {
    try {
      var editButton = $( '<span class="vcard-edit-button">&nbsp;[<a>edit</a>]</span>' ).click( startEditing ); //.trigger( 'click' );
      $( '.vcard' ).append( editButton );
    } catch ( e ) {
      handleException( 'addEditButtons', e );
    }
  }

  function removeEditButtons() {
    try {
      $( '.vcard-edit-button' ).remove()
    } catch ( e ) {
      handleException( 'removeEditButtons', e );
    }
  }

  function addSaveButton( entry ) {
    try {
      var saveButton = $( '<span class="vcard-save-button">&nbsp;[<a>save</a>]</span>' ).click( entry, saveTheEdits );
      entry.append( saveButton );
      return saveButton;
    } catch ( e ) {
      handleException( 'addSaveButton', e );
    }
  }

  function addCancelButton( entry ) {
    try {
      var button = $( '<span class="vcard-cancel-button">&nbsp;[<a>cancel</a>]</span>' ).click( entry, cancelEditing );
      entry.append( button );
      return button;
    } catch ( e ) {
      handleException( 'addCancelButton', e );
    }
  }

  function startEditing() {
    try {
      var entry = $(this).parent();

      removeEditButtons();

      savedEntry = entry.html();
      wikitext = theWikitextOfUs( entry );
    
      addSaveButton( entry );
      addCancelButton( entry );

      fields.forEach( function ( element, index, array ) {
        if (element === 'url') return;
        entry.find( '.listing-' + element ).attr( 'contenteditable', true );
      } );
    } catch ( e ) {
      handleException( 'startEditing', e );
    }
  }

  function saveTheEdits( event ) {
    try {
      var newEntry = serializeListing( event.data );
      var newWikitext = wikiTextSection.replace( wikitext, newEntry );
    
      $.ajax({
          url: mw.util.wikiScript( 'api' ),
          data: {
              format: 'json',
              action: 'edit',
              title: mw.config.get( 'wgPageName' ),
              section: theSectionWeAreIn( event.data ),
              summary: '[[User:Ruud Koot/Listing editor|Listing editor]] (' + version + '): ' + ourIdentifier( event.data ),
              text: newWikitext,
              token: mw.user.tokens.get( 'csrfToken' )
          },
          dataType: 'json',
          type: 'POST',
          success: function( data ) {
              if ( data && data.edit && data.edit.result == 'Success' ) {
                  window.location.reload(); // reload page if edit was successful
              } else if ( data && data.error ) {
                  alert( 'Error: API returned error code "' + data.error.code + '": ' + data.error.info );
              } else {
                  alert( 'Error: Unknown result from API.' );
              }
          },
          error: function( xhr ) {
              alert( 'Error: Request failed.' );
          }
      });
    } catch ( e ) {
      handleException( 'saveTheEdits', e );
    }
  }

  function cancelEditing( event ) {
    try {
      event.data.html( savedEntry );
      addEditButtons();
    } catch ( e ) {
      handleException( 'cancelEditing', e );
    }
  }

  function theSectionWeAreIn( entry ) {
    try {
      return entry.parent().parent().prevAll( 'h2' ).find( '.mw-editsection a' ).attr( 'href' ).split( '=' ).pop();
    } catch ( e ) {
      handleException( 'theSectionWeAreIn', e );
    }
  }

  function theWikitextOfOurSection( entry ) {
    try {
      var sectionNumber = theSectionWeAreIn( entry );
    
      var wikiText = $.ajax({
        type: "GET",
        url: '/w/index.php?title=' + mw.config.get('wgPageName') + '&action=raw&oldid=' + mw.config.get('wgCurRevisionId') + '&section=' + sectionNumber,
        dataType: 'text',
        async: false
      }).responseText;

      return wikiText;
    } catch ( e ) {
      handleException( 'theWikitextOfOurSection', e );
    }
  }

  function ourIdentifier( entry ) {
    return entry.find( '.org' ).text()
  }

  function theWikitextOfUs( entry ) {
    var identifier = ourIdentifier( entry );
    wikiTextSection = theWikitextOfOurSection( entry );
    var regex = new RegExp( "\x7B\x7B(see|do|buy|eat|drink|sleep|listing).*\\n?.*\\|.*name.*=.*" + regexQuote( identifier ) + "(.|\\n)*?}}" );
    var matched = wikiTextSection.match( regex );
    var us = matched[0];
    listingType = matched[1]

    console.log( us );
    return us;
  }

  function ampersandQuote( str ) {
    return str.replace( /&/g, "&amp;" );
  }

  function regexQuote( str ) {
    try {
      return str.replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
    } catch ( e ) {
      handleException( 'regexQuote', e );
    }
  }

  function convertHtmlToWikitext( str ) {
     try {
       return str.replace( /<[/]?i>/ig, "''" ).replace( /<[/]?b>/ig, "'''");
     } catch ( e ) {
       handleExceptions( 'convertHtmlToWikitext', e );
     }
  }

  function serializeListing( entry ) {
    try {
      var result = '{{' + listingType + "\n";
      relevantFields[listingType].forEach( function( element, index, array ) {
        element.forEach( function( element, index, array ) {
          var value = entry.find( '.listing-' + element ).text();
          if ( element === 'url' ) {
            try {
              value = entry.find( '.listing-url a' ).attr( 'href' );
            } catch ( e ) {
              value = '';
            }
          }
          if ( element === 'lat' ) {
            try {
              value = entry.find( '.listing-coordinates abbr' ).attr( 'title' ).split( ';' )[0];
            } catch ( e ) {
              value = '';
            }
          }
          if ( element === 'long' ) {
            try {
              value = entry.find( '.listing-coordinates abbr' ).attr( 'title' ).split( ';' )[1];
            } catch ( e ) {
              value = '';
            }
          }
          result += ' | ' + element + '=' + convertHtmlToWikitext( value );
        } );
        result += '\n';
      } );
      result += '}}';
      return result;
    } catch ( e ) {
      handleException( 'serializeListing', e );
    }
  }

  addEditButtons();

} );