<commit_before>/*globals $, jsbin, editors, RSVP, loopProtect, documentTitle, CodeMirror, hintingDone*/

var renderCodeWorking = false;

var getRenderedCode = function () {
  'use strict';

  var formatErrors = function(res) {
    var errors = [];
    var line = 0;
    var ch = 0;
    for (var i = 0; i < res.length; i++) {
      line = res[i].line || 0;
      ch = res[i].ch || 0;
      errors.push({
        from: CodeMirror.Pos(line, ch),
        to: CodeMirror.Pos(line, ch),
        message: res[i].msg,
        severity : 'error'
      });
    }
    return errors;
  };

  if (renderCodeWorking) {
    // cancel existing jobs, and replace with this job
  }

  renderCodeWorking = true;

  function render(language) {
    return new RSVP.Promise(function (resolve, reject) {
      editors[language].render().then(resolve, function (error) {
        console.warn(editors[language].processor.id + ' processor compilation failed');
        if (!error) {
          error = {};
        }

        if ($.isArray(error)) { // then this is for our hinter
          // console.log(data.errors);
          var cm = jsbin.panels.panels[language].editor;

          // if we have the error reporting function (called updateLinting)
          if (typeof cm.updateLinting !== 'undefined') {
            hintingDone(cm);
            var err = formatErrors(error);
            cm.updateLinting(err);
          } else {
            // otherwise dump to the console
            console.warn(error);
          }
        } else if (error.message) {
          console.warn(error.message, error.stack);
        } else {
          console.warn(error);
        }

        reject(error);
      });
    });
  }

  var promises = {
    html: render('html'),
    javascript: render('javascript'),
    jasmine: render('jasmine'),
    css: render('css')
  };

  return RSVP.hash(promises);
};

var getPreparedCodeCreator = function (is_test) { // jshint ignore:line
  'use strict';

  var escapeMap = {
    '<': '<',
    '>': '>',
    '&': '&'
  }, re = {
      docReady: /\$\(document\)\.ready/,
      shortDocReady: /\$\(function/,
      console: /(^.|\b)console\.(\S+)/g,

      script: /<\/script/ig,
      code: /%code%/,
      csscode: /%css%/,
      title: /<title>(.*)<\/title>/i,
      description: /<meta.*name=["']description['"].*?>/i,
      winLoad: /window\.onload\s*=/,
      scriptopen: /\
\
\
<link href="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.0.0/jasmine.css" rel="stylesheet">\
      '

  return function (nojs) {
    // reset all the regexp positions for reuse
    re.docReady.lastIndex = 0;
    re.shortDocReady.lastIndex = 0;
    re.console.lastIndex = 0;
    re.script.lastIndex = 0;
    re.code.lastIndex = 0;
    re.csscode.lastIndex = 0;
    re.title.lastIndex = 0;
    re.winLoad.lastIndex = 0;
    re.scriptopen.lastIndex = 0;

    return getRenderedCode().then(function (code) {
      var parts = [],
          html = code.html,
          js = !nojs ? code.javascript : '',
          jasmine = code.jasmine,
          css = code.css,
          close = '',
          hasHTML = !!html.trim().length,
          hasCSS = !!css.trim().length,
          hasJS = !!js.trim().length,
          replaceWith = 'window.runnerWindow.proxyConsole.';

      if (is_test) {
        html = html.replace('', jasmine_assets + '');
      }

      // this is used to capture errors with processors, sometimes their errors
      // aren't useful (Script error. (line 0) #1354) so we try/catch and then
      // throw the real error. This also works exactly as expected with non-
      // processed JavaScript
      if (hasHTML && is_test) {
        js = 'try {' + js + '\n\n' + jasmine + '\n } catch (error) { throw error; }';
      } else if(hasHTML && !is_test){
        js = 'try {' + js + '\n } catch (error) { throw error; }';
      }

      // Rewrite loops to detect infiniteness.
      // This is done by rewriting the for/while/do loops to perform a check at
      // the start of each iteration.
      js = loopProtect.rewriteLoops(js);

      // escape any script tags in the JS code, because that'll break the mushing together
      js = js.replace(re.script, '<\\/script');

      // redirect console logged to our custom log while debugging
      if (re.console.test(js)) {
        // yes, this code looks stupid, but in fact what it does is look for
        // 'console.' and then checks the position of the code. If it's inside
        // an openning script tag, it'll change it to window.top._console,
        // otherwise it'll leave it.
        js = js.replace(re.console, function (all, str, arg) {
          return replaceWith + arg;
        });
      }

      // note that I'm using split and reconcat instead of replace, because if the js var
      // contains '$$' it's replaced to '$' - thus breaking Prototype code. This method
      // gets around the problem.
      if (!hasHTML && hasJS) {
        html = '
\n' + js.replace(/[<>&]/g, function (m) {
          return escapeMap[m];
        }) + '
';
      } else if (re.code.test(html)) {
        html = html.split('%code%').join(js);
      } else if (hasJS) {
        close = '';
        if (html.indexOf('') !== -1) {
          parts.push(html.substring(0, html.lastIndexOf('')));
          parts.push(html.substring(html.lastIndexOf('')));

          html = parts[0];
          close = parts.length === 2 && parts[1] ? parts[1] : '';
        }

        // RS: not sure why I ran this in closure, but it means the expected globals are no longer so
        // js = "window.onload = function(){" + js + "\n}\n";
        var type = jsbin.panels.panels.javascript.type ? ' type="text/' + jsbin.panels.panels.javascript.type + '"' : '';

        js += '\n\n//# sourceURL=' + jsbin.state.code + '.js';

        html += '\n' + close;
      }

      // reapply the same proxyConsole - but to all the html code, since
      if (re.console.test(html)) {
        // yes, this code looks stupid, but in fact what it does is look for
        // 'console.' and then checks the position of the code. If it's inside
        // an openning script tag, it'll change it to window.top._console,
        // otherwise it'll leave it.
        var first = ' /* double call explained https://github.com/jsbin/jsbin/issues/1833 */';
        html = html.replace(re.console, function (all, str, arg, pos) {
          var open = html.lastIndexOf(' close) {
            return replaceWith + arg;
          } else {
            return all;
          }
        });
      }

      if (!hasHTML && !hasJS && hasCSS) {
        html = '
\n' + css.replace(/[<>&]/g, function (m) {
          return escapeMap[m];
        }) + '
';
      } else if (re.csscode.test(html)) {
        html = html.split('%css%').join(css);
      } else if (css && hasHTML) {
        parts = [];
        close = '';
        if (html.indexOf('') !== -1) {
          parts.push(html.substring(0, html.lastIndexOf('')));
          parts.push(html.substring(html.lastIndexOf('')));

          html = parts[0];
          close = parts.length === 2 && parts[1] ? parts[1] : '';
        }
        html += '\n' + close;
      }

      // Add defer to all inline script tags in IE.
      // This is because IE runs scripts as it loads them, so variables that
      // scripts like jQuery add to the global scope are undefined.
      // See http://jsbin.com/ijapom/5
      if (jsbin.ie && re.scriptopen.test(html)) {
        html = html.replace(/\
\
\
<link href="https://cdnjs.cloudflare.com/ajax/libs/jasmine/2.0.0/jasmine.css" rel="stylesheet">\
      '

  return function (nojs) {
    // reset all the regexp positions for reuse
    re.docReady.lastIndex = 0;
    re.shortDocReady.lastIndex = 0;
    re.console.lastIndex = 0;
    re.script.lastIndex = 0;
    re.code.lastIndex = 0;
    re.csscode.lastIndex = 0;
    re.title.lastIndex = 0;
    re.winLoad.lastIndex = 0;
    re.scriptopen.lastIndex = 0;

    return getRenderedCode().then(function (code) {
      var parts = [],
          html = code.html,
          js = !nojs ? code.javascript : '',
          jasmine = code.jasmine,
          dataframe = code.dataframe,
          css = code.css,
          close = '',
          hasHTML = !!html.trim().length,
          hasCSS = !!css.trim().length,
          hasJS = !!js.trim().length,
          replaceWith = 'window.runnerWindow.proxyConsole.';

      try{
        dataframe = JSON.stringify(JSON.parse(dataframe))
      }catch(error){
        dataframe = ""
      }

      if (is_test) {
        html = html.replace('', jasmine_assets + '');
      }

      // this is used to capture errors with processors, sometimes their errors
      // aren't useful (Script error. (line 0) #1354) so we try/catch and then
      // throw the real error. This also works exactly as expected with non-
      // processed JavaScript
      if (hasHTML && is_test) {
        js = 'try {' + js + '\n\n' + jasmine + '\n } catch (error) { throw error; }';
      } else if(hasHTML && !is_test){
        js = 'try { \nwindow.data = JSON.parse(\'' + dataframe +  '\');\n' + js + '\n } catch (error) { throw error; }';
      }

      // Rewrite loops to detect infiniteness.
      // This is done by rewriting the for/while/do loops to perform a check at
      // the start of each iteration.
      js = loopProtect.rewriteLoops(js);

      // escape any script tags in the JS code, because that'll break the mushing together
      js = js.replace(re.script, '<\\/script');

      // redirect console logged to our custom log while debugging
      if (re.console.test(js)) {
        // yes, this code looks stupid, but in fact what it does is look for
        // 'console.' and then checks the position of the code. If it's inside
        // an openning script tag, it'll change it to window.top._console,
        // otherwise it'll leave it.
        js = js.replace(re.console, function (all, str, arg) {
          return replaceWith + arg;
        });
      }

      // note that I'm using split and reconcat instead of replace, because if the js var
      // contains '$$' it's replaced to '$' - thus breaking Prototype code. This method
      // gets around the problem.
      if (!hasHTML && hasJS) {
        html = '
\n' + js.replace(/[<>&]/g, function (m) {
          return escapeMap[m];
        }) + '
';
      } else if (re.code.test(html)) {
        html = html.split('%code%').join(js);
      } else if (hasJS) {
        close = '';
        if (html.indexOf('') !== -1) {
          parts.push(html.substring(0, html.lastIndexOf('')));
          parts.push(html.substring(html.lastIndexOf('')));

          html = parts[0];
          close = parts.length === 2 && parts[1] ? parts[1] : '';
        }

        // RS: not sure why I ran this in closure, but it means the expected globals are no longer so
        // js = "window.onload = function(){" + js + "\n}\n";
        var type = jsbin.panels.panels.javascript.type ? ' type="text/' + jsbin.panels.panels.javascript.type + '"' : '';

        js += '\n\n//# sourceURL=' + jsbin.state.code + '.js';

        html += '\n' + close;
      }

      // reapply the same proxyConsole - but to all the html code, since
      if (re.console.test(html)) {
        // yes, this code looks stupid, but in fact what it does is look for
        // 'console.' and then checks the position of the code. If it's inside
        // an openning script tag, it'll change it to window.top._console,
        // otherwise it'll leave it.
        var first = ' /* double call explained https://github.com/jsbin/jsbin/issues/1833 */';
        html = html.replace(re.console, function (all, str, arg, pos) {
          var open = html.lastIndexOf(' close) {
            return replaceWith + arg;
          } else {
            return all;
          }
        });
      }

      if (!hasHTML && !hasJS && hasCSS) {
        html = '
\n' + css.replace(/[<>&]/g, function (m) {
          return escapeMap[m];
        }) + '
';
      } else if (re.csscode.test(html)) {
        html = html.split('%css%').join(css);
      } else if (css && hasHTML) {
        parts = [];
        close = '';
        if (html.indexOf('') !== -1) {
          parts.push(html.substring(0, html.lastIndexOf('')));
          parts.push(html.substring(html.lastIndexOf('')));

          html = parts[0];
          close = parts.length === 2 && parts[1] ? parts[1] : '';
        }
        html += '\n' + close;
      }

      // Add defer to all inline script tags in IE.
      // This is because IE runs scripts as it loads them, so variables that
      // scripts like jQuery add to the global scope are undefined.
      // See http://jsbin.com/ijapom/5
      if (jsbin.ie && re.scriptopen.test(html)) {
        html = html.replace(/


<commit_before>// Copyright 2002-2015, University of Colorado Boulder

/**
 * Javascript for translate-sim.html
 * @author Michael Kauzmann
 * @author Aaron Davis
 */
$( document ).ready( function() {

  var testButtonTop = document.getElementById( 'testButtonTop' );
  var saveButtonTop = document.getElementById( 'saveButtonTop' );
  var testButtonBottom = document.getElementById( 'testButtonBottom' );
  var saveButtonBottom = document.getElementById( 'saveButtonBottom' );
  var simData = document.getElementById( 'simData' );
  var rtl = simData.getAttribute( 'data-direction' ) === 'rtl';
  var savedSpans = $( '.savedSpan' );

  // Var to help only restore save button once from inputs
  var once = false;

  function testButtonEventListener() {
    var simUrl = simData.getAttribute( 'data-sim-url' );
    var stringsToReplace = {};
    var inputs = $( '.rosetta-table tr' );
    inputs.each( function( i, row ) {
      var input = $( row ).find( 'input' ).get( 0 );
      if ( input ) {
        var translation = input.value;
        var repo = row.getAttribute( 'data-string-repo' );
        if ( repo && translation && translation.length > 0 ) {
          // add rtl embedding markers for rtl strings
          if ( rtl ) {
            translation = '%E2%80%AB' + translation + '%E2%80%AC';
          }
          var stringKey = repo.replace( /-/g, '_' ).toUpperCase() + '/' + row.getAttribute( 'data-string-key' );
          stringsToReplace[ stringKey ] = translation;
        }
      }
    } );
    var encodedStrings = encodeURIComponent( JSON.stringify( stringsToReplace ) );
    encodedStrings = encodedStrings.replace( /%5C%5Cn/g, '%5Cn' ); // put back newlines

    var win = window.open( simUrl + '?' + 'strings=' + encodedStrings, '_blank' );
    win.focus();
  }

  testButtonTop.addEventListener( 'click', testButtonEventListener );
  testButtonBottom.addEventListener( 'click', testButtonEventListener );

  function saveButtonEventListener() {
    var strings = {};
    inputs.each( function( index, item ) {
      strings[ item.name ] = item.value;
    } );

    $.post( '/translate/sim/save/' + simData.getAttribute( 'data-sim-name' ) + '/' + simData.getAttribute( 'data-locale-name' ), strings, function( data ) {
      $( '.saving-gif' ).hide();

      if ( data.success ) {
        // grey out both save buttons, add saved below
        saveButtonTop.style.backgroundColor = 'grey';
        saveButtonTop.style.borderColor = 'black';
        saveButtonTop.disabled = true;
        saveButtonBottom.style.backgroundColor = 'grey';
        saveButtonBottom.style.borderColor = 'black';
        saveButtonBottom.disabled = true;
        savedSpans.attr( 'style', 'visibility: visible; color: green;' );
        savedSpans.html( '

Saved' );
        once = false;
      }
      else {
        savedSpans.attr( 'style', 'visibility: visible; color: red;' );
        savedSpans.html( '

Error Saving' );
      }
    } );
    $( '.saving-gif' ).show();
  }

  saveButtonTop.addEventListener( 'click', saveButtonEventListener );
  saveButtonBottom.addEventListener( 'click', saveButtonEventListener );

  /**
   * Restore a button to its original (non-greyed out) state
   * @param button
   */
  function restoreButton( button ) {
    button.style.backgroundColor = '#2a326a';
    button.style.borderColor = '#1A87B9';
    button.disabled = false;
    $( '#' + button.id ).hover( function() {
        $( this ).css( {
          background: '#6D77BD'
        } );
      },
      function() {
        $( this ).css( {
          background: '#2a326a'
        } );
      } );
  }

  // add highlight to '\n' and '{\d}' so users are aware that this is treated specially
  $( 'td:nth-child(2)' ).each( function( index, element ) {
    $( element ).html( $( element ).html().replace( /(\{\d\}|\\n)/g, '$1' ) );
  } );

  /**
   * Make sure a string that should have pattern fields has the required fields in the translated string
   * @param row - the row of the table, including the english string and the input string
   * @returns {boolean}
   */
  function validateRow( row ) {
    var validated = true;
    var tds = $( row ).find( 'td' );
    var matches = $( tds[ 1 ] ).text().match( /\{\d\}/g );
    if ( matches ) {
      var td = $( tds[ 2 ] );
      var input = $( td.find( 'div[contenteditable]' ).get( 0 ) );
      var value = input.text();
      var redOutline = false;
      var missingPlaceholders = [];
      var extraPlaceholders = [];
      var duplicatePlaceholders = [];

      // make sure every MessageFormat placeholder that exists in the English exists in the translation
      for ( var i = 0; i < matches.length; i++ ) {
        var escapedMatch = matches[ i ].replace( '{', '\\{' ).replace( '}', '\\}' );
        var numberOfOccurrences = (value.match( new RegExp(  escapedMatch, 'g' ) ) || []).length;
        if ( value.length > 0 && numberOfOccurrences !== 1 ) {
          validated = false;
          redOutline = true;
          if ( numberOfOccurrences < 1 ) {
            missingPlaceholders.push( matches[ i ] );
          }
          else {
            duplicatePlaceholders.push( matches[ i ] );
          }
        }
      }

      // make sure the are no MessageFormat placeholders that exist in the translation that don't exist in the English
      var regExp = new RegExp( '\{[' + matches.length + '-9]\}', 'g' );
      var extraMatches = value.match( regExp );
      if ( extraMatches !== null ) {
        redOutline = true;
        validated = false;
        extraPlaceholders = extraPlaceholders.concat( extraMatches );
      }

      td.find( 'img:last-child' ).remove(); // remove the old error message either way
      if ( redOutline ) {
        input.css( { outline: '1px solid red' } );
        var img = $( '', { src: '/translate/img/warning.png', class: 'warning' } );
        td.append( img );

        img.click( function() {
          var errorMessage = [ 'Your translation has the following errors:\n' ];
          if ( missingPlaceholders.length ) {
            errorMessage.push( 'missing MessageFormat placeholders: ' + missingPlaceholders.join( ', ' ) );
          }
          if ( extraPlaceholders.length ) {
            errorMessage.push( 'extra MessageFormat placeholders: ' + extraPlaceholders.join( ', ' ) );
          }
          if ( duplicatePlaceholders.length ) {
            errorMessage.push( 'duplicate MessageFormat placeholders: ' + duplicatePlaceholders.join( ', ' ) );
          }
          alert( errorMessage.join( '\n' ) );
        } );
      }
      else {
        input.css( { outline: 'initial' } );
      }
    }
    return validated;
  }

  /**
   * Make sure every row has the required patterns
   * @returns {boolean}
   */
  function validatePatterns() {
    var validated = true;
    $( 'tr' ).each( function( index, item ) {
      if ( !validateRow( item ) ) {
        validated = false;
      }
    } );
    return validated;
  }

  // validate the inputs before submitting the form
  $( '#strings' ).submit( function( event ) {
    if ( !validatePatterns() ) {
      $( '.validation-message' ).text( 'Your translation has MessageFormat errors. Please correct these before submitting' );
      event.preventDefault();
    }
    else {
      $( '.validation-message' ).text( '' );
    }
  } );

  var inputSelector = '.rosetta-table div[contenteditable]';
  var inputs = $( inputSelector );

  // fix dir and text-align for rtl languages
  if ( rtl ) {
    inputs.attr( 'dir', 'rtl' );
    inputs.css( 'text-align', 'right' );
  }

  // disable pressing enter in inputs because it complicates things by adding
 and possibly other html
  $( document ).on( 'keypress', inputSelector, function( e ) {
    return e.which !== 13;
  } );

  // on every change, copy the content from the editable div to a hidden input so it gets submitted with the form
  $( document ).on( 'keyup paste', inputSelector, function( e ) {
    var contentEditable = $( this );
    var input = contentEditable.next().get( 0 );
    input.value = contentEditable.text();

    // need to set a timeout after pasting in so that the paste has time to take effect
    if ( e.type === 'paste' ) {
      setTimeout( function() {
        input.value = contentEditable.text();
        contentEditable.text( contentEditable.text() );
      }, 50 );
    }

    // undo-gray saved button on any input change
    if ( !once ) {
      $( '.savedSpan' ).attr( 'style', 'visibility: hidden' );
      restoreButton( saveButtonTop );
      restoreButton( saveButtonBottom );
      once = true;
    }
  } );

  // on blur, validate the row to make sure it has the correct patterns
  $( document ).on( 'blur', inputSelector, function( e ) {
    var row = this.parentNode.parentNode;
    validateRow( row );
  } );
} );
<commit_msg>improve validation for MessageFormat placeholders, see #59<commit_after>// Copyright 2002-2015, University of Colorado Boulder

/**
 * Javascript for translate-sim.html
 * @author Michael Kauzmann
 * @author Aaron Davis
 */
$( document ).ready( function() {

  var testButtonTop = document.getElementById( 'testButtonTop' );
  var saveButtonTop = document.getElementById( 'saveButtonTop' );
  var testButtonBottom = document.getElementById( 'testButtonBottom' );
  var saveButtonBottom = document.getElementById( 'saveButtonBottom' );
  var simData = document.getElementById( 'simData' );
  var rtl = simData.getAttribute( 'data-direction' ) === 'rtl';
  var savedSpans = $( '.savedSpan' );

  // Var to help only restore save button once from inputs
  var once = false;

  function testButtonEventListener() {
    var simUrl = simData.getAttribute( 'data-sim-url' );
    var stringsToReplace = {};
    var inputs = $( '.rosetta-table tr' );
    inputs.each( function( i, row ) {
      var input = $( row ).find( 'input' ).get( 0 );
      if ( input ) {
        var translation = input.value;
        var repo = row.getAttribute( 'data-string-repo' );
        if ( repo && translation && translation.length > 0 ) {
          // add rtl embedding markers for rtl strings
          if ( rtl ) {
            translation = '%E2%80%AB' + translation + '%E2%80%AC';
          }
          var stringKey = repo.replace( /-/g, '_' ).toUpperCase() + '/' + row.getAttribute( 'data-string-key' );
          stringsToReplace[ stringKey ] = translation;
        }
      }
    } );
    var encodedStrings = encodeURIComponent( JSON.stringify( stringsToReplace ) );
    encodedStrings = encodedStrings.replace( /%5C%5Cn/g, '%5Cn' ); // put back newlines

    var win = window.open( simUrl + '?' + 'strings=' + encodedStrings, '_blank' );
    win.focus();
  }

  testButtonTop.addEventListener( 'click', testButtonEventListener );
  testButtonBottom.addEventListener( 'click', testButtonEventListener );

  function saveButtonEventListener() {
    var strings = {};
    inputs.each( function( index, item ) {
      strings[ item.name ] = item.value;
    } );

    $.post( '/translate/sim/save/' + simData.getAttribute( 'data-sim-name' ) + '/' + simData.getAttribute( 'data-locale-name' ), strings, function( data ) {
      $( '.saving-gif' ).hide();

      if ( data.success ) {
        // grey out both save buttons, add saved below
        saveButtonTop.style.backgroundColor = 'grey';
        saveButtonTop.style.borderColor = 'black';
        saveButtonTop.disabled = true;
        saveButtonBottom.style.backgroundColor = 'grey';
        saveButtonBottom.style.borderColor = 'black';
        saveButtonBottom.disabled = true;
        savedSpans.attr( 'style', 'visibility: visible; color: green;' );
        savedSpans.html( '

Saved' );
        once = false;
      }
      else {
        savedSpans.attr( 'style', 'visibility: visible; color: red;' );
        savedSpans.html( '

Error Saving' );
      }
    } );
    $( '.saving-gif' ).show();
  }

  saveButtonTop.addEventListener( 'click', saveButtonEventListener );
  saveButtonBottom.addEventListener( 'click', saveButtonEventListener );

  /**
   * Restore a button to its original (non-greyed out) state
   * @param button
   */
  function restoreButton( button ) {
    button.style.backgroundColor = '#2a326a';
    button.style.borderColor = '#1A87B9';
    button.disabled = false;
    $( '#' + button.id ).hover( function() {
        $( this ).css( {
          background: '#6D77BD'
        } );
      },
      function() {
        $( this ).css( {
          background: '#2a326a'
        } );
      } );
  }

  // add highlight to '\n' and '{\d}' so users are aware that this is treated specially
  $( 'td:nth-child(2)' ).each( function( index, element ) {
    $( element ).html( $( element ).html().replace( /(\{\d\}|\\n)/g, '$1' ) );
  } );

  /**
   * Make sure a string that should have pattern fields has the required fields in the translated string
   * @param row - the row of the table, including the english string and the input string
   * @returns {boolean}
   */
  function validateRow( row ) {
    var validated = true;
    var messageFormatRegexp = /\{\d+\}/g;
    var tds = $( row ).find( 'td' );
    var englishMatches = $( tds[ 1 ] ).text().match( messageFormatRegexp );
    if ( englishMatches ) {
      var td = $( tds[ 2 ] );
      var input = $( td.find( 'div[contenteditable]' ).get( 0 ) );
      var value = input.text();
      var missingPlaceholders = [];
      var extraPlaceholders = [];

      var translatedMatches = value.match( messageFormatRegexp );

      // make sure every MessageFormat placeholder that exists in the English exists in the translation
      for ( var i = 0; i < englishMatches.length; i++ ) {
        if ( value.length > 0 && value.indexOf( englishMatches[ i ] ) === -1 ) {
          validated = false;
          missingPlaceholders.push( englishMatches[ i ] );
        }
        if ( translatedMatches ) {
          var index = translatedMatches.indexOf( englishMatches[ i ] );
          if ( index !== -1 ) {
            translatedMatches.splice( index, 1 );
          }
        }
      }
      if ( translatedMatches && translatedMatches.length ) {
        validated = false;
        extraPlaceholders = translatedMatches;
      }

      td.find( 'img:last-child' ).remove(); // remove the old error message either way
      if ( missingPlaceholders.length || extraPlaceholders.length ) {
        input.css( { outline: '1px solid red' } );
        var img = $( '', { src: '/translate/img/warning.png', class: 'warning' } );
        td.append( img );

        img.click( function() {
          var errorMessage = [ 'Your translation has the following errors:\n' ];
          if ( missingPlaceholders.length ) {
            errorMessage.push( 'missing MessageFormat placeholders: ' + missingPlaceholders.join( ', ' ) );
          }
          if ( extraPlaceholders.length ) {
            errorMessage.push( 'extra MessageFormat placeholders: ' + extraPlaceholders.join( ', ' ) );
          }
          alert( errorMessage.join( '\n' ) );
        } );
      }
      else {
        input.css( { outline: 'initial' } );
      }
    }
    return validated;
  }

  /**
   * Make sure every row has the required patterns
   * @returns {boolean}
   */
  function validatePatterns() {
    var validated = true;
    $( 'tr' ).each( function( index, item ) {
      if ( !validateRow( item ) ) {
        validated = false;
      }
    } );
    return validated;
  }

  // validate the inputs before submitting the form
  $( '#strings' ).submit( function( event ) {
    if ( !validatePatterns() ) {
      $( '.validation-message' ).text( 'Your translation has MessageFormat errors. Please correct these before submitting' );
      event.preventDefault();
    }
    else {
      $( '.validation-message' ).text( '' );
    }
  } );

  var inputSelector = '.rosetta-table div[contenteditable]';
  var inputs = $( inputSelector );

  // fix dir and text-align for rtl languages
  if ( rtl ) {
    inputs.attr( 'dir', 'rtl' );
    inputs.css( 'text-align', 'right' );
  }

  // disable pressing enter in inputs because it complicates things by adding
 and possibly other html
  $( document ).on( 'keypress', inputSelector, function( e ) {
    return e.which !== 13;
  } );

  // on every change, copy the content from the editable div to a hidden input so it gets submitted with the form
  $( document ).on( 'keyup paste', inputSelector, function( e ) {
    var contentEditable = $( this );
    var input = contentEditable.next().get( 0 );
    input.value = contentEditable.text();

    // need to set a timeout after pasting in so that the paste has time to take effect
    if ( e.type === 'paste' ) {
      setTimeout( function() {
        input.value = contentEditable.text();
        contentEditable.text( contentEditable.text() );
      }, 50 );
    }

    // undo-gray saved button on any input change
    if ( !once ) {
      $( '.savedSpan' ).attr( 'style', 'visibility: hidden' );
      restoreButton( saveButtonTop );
      restoreButton( saveButtonBottom );
      once = true;
    }
  } );

  // on blur, validate the row to make sure it has the correct patterns
  $( document ).on( 'blur', inputSelector, function( e ) {
    var row = this.parentNode.parentNode;
    validateRow( row );
  } );
} );
<|endoftext|>


<commit_before>function initialize() {
  //Setup Google Map
  var myLatlng = new google.maps.LatLng(50,10);
  var light_grey_style = [{"featureType":"landscape","stylers":[{"saturation":-100},{"lightness":65},{"visibility":"on"}]},{"featureType":"poi","stylers":[{"saturation":-100},{"lightness":51},{"visibility":"simplified"}]},{"featureType":"road.highway","stylers":[{"saturation":-100},{"visibility":"simplified"}]},{"featureType":"road.arterial","stylers":[{"saturation":-100},{"lightness":30},{"visibility":"on"}]},{"featureType":"road.local","stylers":[{"saturation":-100},{"lightness":40},{"visibility":"on"}]},{"featureType":"transit","stylers":[{"saturation":-100},{"visibility":"simplified"}]},{"featureType":"administrative.province","stylers":[{"visibility":"off"}]},{"featureType":"water","elementType":"labels","stylers":[{"visibility":"on"},{"lightness":-25},{"saturation":-100}]},{"featureType":"water","elementType":"geometry","stylers":[{"hue":"#ffff00"},{"lightness":-25},{"saturation":-97}]}];
  var myOptions = {
    zoom: 5,
    center: myLatlng,
    mapTypeId: google.maps.MapTypeId.ROADMAP,
    mapTypeControl: true,
    mapTypeControlOptions: {
      style: google.maps.MapTypeControlStyle.HORIZONTAL_BAR,
      position: google.maps.ControlPosition.LEFT_BOTTOM
    },
    styles: light_grey_style
  };
  var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);

  //Setup heat map and link to Twitter array we will append data to
  var heatmap;
  var liveTweets = new google.maps.MVCArray();
  heatmap = new google.maps.visualization.HeatmapLayer({
    data: liveTweets,
    radius: 25
  });
  heatmap.setMap(map);

  if(io !== undefined) {
    // Storage for WebSocket connections
    var socket = io.connect('/');
    // This listens on the "twitter-steam" channel and data is
    // received everytime a new tweet is receieved.
    socket.on('twitter-stream', function (data) {
      console.log(JSON.stringify(data));
      //Add tweet to the heat map array.
      var tweetLocation = new google.maps.LatLng(data.lng,data.lat);
      liveTweets.push(tweetLocation);
      //Flash a dot onto the map quickly
      var image = "css/small-dot-icon.png";
      console.log(data.text);
      var marker = new google.maps.Marker({
        position: tweetLocation,
        map: map,
        title:data.text
        // ,icon: image
      });
      setTimeout(function(){
        marker.setMap(null);
      },5000);
      var infowindow = new google.maps.InfoWindow({
        content: data.text
      });
      google.maps.event.addListener(marker, 'click', function() {
        infowindow.open(map,marker);
      });
    });

    // Listens for a success response from the server to
    // say the connection was successful.
    socket.on("connected", function(r) {

      //Now that we are connected to the server let's tell
      //the server we are ready to start receiving tweets.
      socket.emit("start tweets");
    });
  }
}
<commit_msg>marker pop in working<commit_after>function initialize() {
  //Setup Google Map
  var myLatlng = new google.maps.LatLng(50,10);
  var light_grey_style = [{"featureType":"landscape","stylers":[{"saturation":-100},{"lightness":65},{"visibility":"on"}]},{"featureType":"poi","stylers":[{"saturation":-100},{"lightness":51},{"visibility":"simplified"}]},{"featureType":"road.highway","stylers":[{"saturation":-100},{"visibility":"simplified"}]},{"featureType":"road.arterial","stylers":[{"saturation":-100},{"lightness":30},{"visibility":"on"}]},{"featureType":"road.local","stylers":[{"saturation":-100},{"lightness":40},{"visibility":"on"}]},{"featureType":"transit","stylers":[{"saturation":-100},{"visibility":"simplified"}]},{"featureType":"administrative.province","stylers":[{"visibility":"off"}]},{"featureType":"water","elementType":"labels","stylers":[{"visibility":"on"},{"lightness":-25},{"saturation":-100}]},{"featureType":"water","elementType":"geometry","stylers":[{"hue":"#ffff00"},{"lightness":-25},{"saturation":-97}]}];
  var myOptions = {
    zoom: 5,
    center: myLatlng,
    mapTypeId: google.maps.MapTypeId.ROADMAP,
    mapTypeControl: true,
    mapTypeControlOptions: {
      style: google.maps.MapTypeControlStyle.HORIZONTAL_BAR,
      position: google.maps.ControlPosition.LEFT_BOTTOM
    },
    styles: light_grey_style
  };
  var map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);

  //Setup heat map and link to Twitter array we will append data to
  var heatmap;
  var liveTweets = new google.maps.MVCArray();
  heatmap = new google.maps.visualization.HeatmapLayer({
    data: liveTweets,
    radius: 25
  });
  heatmap.setMap(map);

  if(io !== undefined) {
    // Storage for WebSocket connections
    var socket = io.connect('/');
    // This listens on the "twitter-steam" channel and data is
    // received everytime a new tweet is receieved.
    socket.on('twitter-stream', function (data) {
      console.log(JSON.stringify(data));
      //Add tweet to the heat map array.
      var tweetLocation = new google.maps.LatLng(data.lng,data.lat);
      liveTweets.push(tweetLocation);
      //Flash a dot onto the map quickly
      var image = "css/small-dot-icon.png";
      var marker = new google.maps.Marker({
        position: tweetLocation,
        map: map,
        title:data.title
        // ,icon: image
      });
      setTimeout(function(){
        marker.setMap(null);
      },5000);
      var infowindow = new google.maps.InfoWindow({
        content: data.title
      });
      google.maps.event.addListener(marker, 'click', function() {
        infowindow.open(map,marker);
      });
    });

    // Listens for a success response from the server to
    // say the connection was successful.
    socket.on("connected", function(r) {

      //Now that we are connected to the server let's tell
      //the server we are ready to start receiving tweets.
      socket.emit("start tweets");
    });
  }
}
<|endoftext|>


<commit_before>(function (window) {
  var requestMethod = {};
  var isProcessing = false;

  var HttpUtil = {};

  /**
   * HttpUtil.get
   *
   * @param url
   * @param params: data
   * @param callback: function(err, result)
   */
  HttpUtil.get = function (url, params, callback) {
    if (arguments.length < 3) {
      callback = params;
      params = null;
    }
    if (requestMethod.requestUrl === url) {
      requestMethod.abort();
    }
    requestMethod = $.ajax({
      method: 'GET',
      url: url,
      data: params,
      error: function errorHandler(jqXHR, textStatus, errorThrown) {
        callback("status : " + jqXHR.status + " msg : " + textStatus);
      },
      success: function successHandler(data, textStatus, jqXHR) {
        callback(null, data);
        requestMethod = {};
      },
      xhrFields: {
        withCredentials: true
      }

    });
    requestMethod.url = url;
  };

  /**
   * HttpUtil.post
   *
   * @param url
   * @param params: data
   * @param callback: function(err, result)
   */
  HttpUtil.post = function (url, params, callback) {
    if (arguments.length < 3) {
      callback = params;
      params = {};
    }
    if (isProcessing) return;
    isProcessing = true;
    $.ajax({
      method: 'POST',
      url: url,
      data: JSON.stringify(params),
      contentType: 'application/json;charset=UTF-8',
      dataType: 'json',
      error: function errorHandler(jqXHR, textStatus, errorThrown) {
        callback("status : " + jqXHR.status + " msg : " + textStatus);
      },
      success: function successHandler(data, status, xhr) {
        callback(null, data);
      },
      complete: function () {
        isProcessing = false;
      },
      xhrFields: {
        withCredentials: true
      }
    })
  };

  /**
   * HttpUtil.put
   *
   * @param url
   * @param params: data
   * @param callback: function(err, result)
   */
  HttpUtil.put = function (url, params, callback) {
    if (arguments.length < 3) {
      callback = params;
      params = {};
    }
    if (isProcessing) return;
    isProcessing = true;
    $.ajax({
      method: 'PUT',
      url: url,
      data: JSON.stringify(params),
      contentType: 'application/json;charset=UTF-8',
      dataType: 'json',
      error: function errorHandler(jqXHR, textStatus, errorThrown) {
        callback("status : " + jqXHR.status + " msg : " + textStatus);
      },
      success: function successHandler(data, status, xhr) {
        callback(null, data);
      },
      complete: function () {
        isProcessing = false;
      },
      xhrFields: {
        withCredentials: true
      }
    })
  };

  /**
   * HttpUtil.delete
   *
   * @param url
   * @param params: data
   * @param callback: function(err, result)
   */
  HttpUtil["delete"] = function (url, params, callback) {
    if (arguments.length < 3) {
      callback = params;
      params = {};
    }
    if (isProcessing) return;
    isProcessing = true;
    $.ajax({
      method: 'DELETE',
      url: url,
      data: JSON.stringify(params),
      contentType: 'application/json;charset=UTF-8',
      dataType: 'json',
      error: function errorHandler(jqXHR, textStatus, errorThrown) {
        callback("status : " + jqXHR.status + " msg : " + textStatus);
      },
      success: function successHandler(data, status, xhr) {
        callback(null, data);
      },
      complete: function () {
        isProcessing = false;
      }
    })
  };

  window.HOST_URL = 'http://localhost:5001';
  window.HttpUtil = HttpUtil;

})(window);<commit_msg>httpUtil changed<commit_after>(function (window) {
  var requestMethod = {};
  var isProcessing = false;

  var HttpUtil = {};

  /**
   * HttpUtil.get
   *
   * @param url
   * @param params: data
   * @param callback: function(err, result)
   */
  HttpUtil.get = function (url, params, callback) {
    if (arguments.length < 3) {
      callback = params;
      params = null;
    }
    if (requestMethod.requestUrl === url) {
      requestMethod.abort();
    }
    requestMethod = $.ajax({
      method: 'GET',
      url: url,
      data: params,
      error: function errorHandler(jqXHR, textStatus, errorThrown) {
        callback("status : " + jqXHR.status + " msg : " + textStatus);
      },
      success: function successHandler(data, textStatus, jqXHR) {
        callback(null, data);
        requestMethod = {};
      },
      xhrFields: {
        withCredentials: true
      }

    });
    requestMethod.url = url;
  };

  /**
   * HttpUtil.post
   *
   * @param url
   * @param params: data
   * @param callback: function(err, result)
   */
  HttpUtil.post = function (url, params, callback) {
    if (arguments.length < 3) {
      callback = params;
      params = {};
    }
    if (isProcessing) return;
    isProcessing = true;
    $.ajax({
      method: 'POST',
      url: url,
      data: JSON.stringify(params),
      contentType: 'application/json;charset=UTF-8',
      dataType: 'json',
      error: function errorHandler(jqXHR, textStatus, errorThrown) {
        callback("status : " + jqXHR.status + " msg : " + textStatus);
      },
      success: function successHandler(data, status, xhr) {
        callback(null, data);
      },
      complete: function () {
        isProcessing = false;
      },
      xhrFields: {
        withCredentials: true
      }
    })
  };

  /**
   * HttpUtil.put
   *
   * @param url
   * @param params: data
   * @param callback: function(err, result)
   */
  HttpUtil.put = function (url, params, callback) {
    if (arguments.length < 3) {
      callback = params;
      params = {};
    }
    if (isProcessing) return;
    isProcessing = true;
    $.ajax({
      method: 'PUT',
      url: url,
      data: JSON.stringify(params),
      contentType: 'application/json;charset=UTF-8',
      dataType: 'json',
      error: function errorHandler(jqXHR, textStatus, errorThrown) {
        callback("status : " + jqXHR.status + " msg : " + textStatus);
      },
      success: function successHandler(data, status, xhr) {
        callback(null, data);
      },
      complete: function () {
        isProcessing = false;
      },
      xhrFields: {
        withCredentials: true
      }
    })
  };

  /**
   * HttpUtil.delete
   *
   * @param url
   * @param params: data
   * @param callback: function(err, result)
   */
  HttpUtil["delete"] = function (url, params, callback) {
    if (arguments.length < 3) {
      callback = params;
      params = {};
    }
    if (isProcessing) return;
    isProcessing = true;
    $.ajax({
      method: 'DELETE',
      url: url,
      data: JSON.stringify(params),
      contentType: 'application/json;charset=UTF-8',
      dataType: 'json',
      error: function errorHandler(jqXHR, textStatus, errorThrown) {
        callback("status : " + jqXHR.status + " msg : " + textStatus);
      },
      success: function successHandler(data, status, xhr) {
        callback(null, data);
      },
      complete: function () {
        isProcessing = false;
      }
    })
  };

  window.HOST_URL = 'http://127.0.0.1:5001';
  window.HttpUtil = HttpUtil;

})(window);
<|endoftext|>


<commit_before>/*------------------------- Load Page In Ajax ----------------------*/
function PageZipperAjax() {

	/*----- Add New Page -----*/

	//takes a url, returns a div containing the body content of the page at url
	this.loadPage = function(url) {
		//load page
		pgzp().jq.ajax({url: url,
						dataType: 'html',
						success: function(data){
							//try to get only the body, but use the entire doc if we cant locate it
							var results = data.match(/<body.*?>([\w\W]*?)<\/body>/i);
							data = (results && results.length >= 2) ? results[1] : data;
							//remove all js to prevent pgzp.doc.write() from messing us up!
							//data = data.replace(/<script[\w\W]*?>[\w\W]*?<\/script>/ig, '').replace(/<script[\w\W]*?\/>/ig, '').replace(/<noscript>([\w\W]*?)<\/noscript>/ig, "$1");
							//pgzp().log("body: " + data);
							pgzp().ajax.processPageAdd(url, data);
						}
					});
	}

	this.processPageAdd = function(url, nextPageData) {
		var nextPage = pgzp().ajax.buildPageFromData(url, nextPageData);
		pgzp().pages.push(nextPage);
		pgzp().url_list.push( nextPage.url );  //track urls we've already loaded, so they don't get loaded again
		pgzp().ajax.copyPage(nextPage.elemContent);
		pgzp().ajax.removeAbsolutePositioning(nextPage.elemContent);
		pgzp().menuIncrementPagesLoaded();
		nextPage.nextLinkObj = pgzp().getNextLink(nextPage.elemContent); //very slow- do this after the page has been loaded
		pgzp().is_loading_page = false;

		//make sure that the page load is working - if the doc height hasn't increased, switch to compat mode
		if (pgzp().pages.length >= 3) {
			var second_page = pgzp().jq( pgzp().pages[1].elemContent );
			var last_page = pgzp().jq( pgzp().pages[pgzp().pages.length-1].elemContent );
			if (last_page.offset().top <= second_page.offset().top) {
				pgzp().toggleCompatMode();
			}
		}

		pgzp().mainBlock(); //continue the loop
	}

	this.buildPageFromData = function(url, data) {
		var page = pgzp().doc.createElement("div");
		page.id = "pgzp_page" + pgzp().pages.length;
		page.style.clear = 'both';
		page.innerHTML = data;
		return pgzp().buildPage(url, page);
	}

	/*----- Utils -----*/
	this.copyPage = function(body) {
		pgzp().doc.body.appendChild(body);
	}

	//switch position: absolute with position: relative
	this.removeAbsolutePositioning = function(body) {
		pgzp().jq( pgzp().doc.body ).children().each(function() {
															if (pgzp().jq(this).css("position") == "absolute") pgzp().jq(this).css("position", "static");
														});
		}
}<commit_msg>sanitize data for ff extension<commit_after>/*------------------------- Load Page In Ajax ----------------------*/
function PageZipperAjax() {

	/*----- Add New Page -----*/

	//takes a url, returns a div containing the body content of the page at url
	this.loadPage = function(url) {
		//load page
		pgzp().jq.ajax({url: url,
						dataType: 'html',
						success: function(data){
							//try to get only the body, but use the entire doc if we cant locate it
							var results = data.match(/<body.*?>([\w\W]*?)<\/body>/i);
							data = (results && results.length >= 2) ? results[1] : data;
							//remove all js to prevent pgzp.doc.write() from messing us up!
							//data = data.replace(/<script[\w\W]*?>[\w\W]*?<\/script>/ig, '').replace(/<script[\w\W]*?\/>/ig, '').replace(/<noscript>([\w\W]*?)<\/noscript>/ig, "$1");
							//pgzp().log("body: " + data);
							pgzp().ajax.processPageAdd(url, data);
						}
					});
	}

	this.processPageAdd = function(url, nextPageData) {
		var nextPage = pgzp().ajax.buildPageFromData(url, nextPageData);
		pgzp().pages.push(nextPage);
		pgzp().url_list.push( nextPage.url );  //track urls we've already loaded, so they don't get loaded again
		pgzp().ajax.copyPage(nextPage.elemContent);
		pgzp().ajax.removeAbsolutePositioning(nextPage.elemContent);
		pgzp().menuIncrementPagesLoaded();
		nextPage.nextLinkObj = pgzp().getNextLink(nextPage.elemContent); //very slow- do this after the page has been loaded
		pgzp().is_loading_page = false;

		//make sure that the page load is working - if the doc height hasn't increased, switch to compat mode
		if (pgzp().pages.length >= 3) {
			var second_page = pgzp().jq( pgzp().pages[1].elemContent );
			var last_page = pgzp().jq( pgzp().pages[pgzp().pages.length-1].elemContent );
			if (last_page.offset().top <= second_page.offset().top) {
				pgzp().toggleCompatMode();
			}
		}

		pgzp().mainBlock(); //continue the loop
	}

	this.buildPageFromData = function(url, data) {
		var page = pgzp().doc.createElement("div");
		page.id = "pgzp_page" + pgzp().pages.length;
		page.style.clear = 'both';

		//Sanitize data for FF extension
		if (pgzp().loader_type == "ffextension") {
			var parserUtils = Components.classes["@mozilla.org/parserutils;1"].getService(Components.interfaces.nsIParserUtils);
			data = parserUtils.sanitize(data, 2);
		}

		page.innerHTML = data;
		return pgzp().buildPage(url, page);
	}

	/*----- Utils -----*/
	this.copyPage = function(body) {
		pgzp().doc.body.appendChild(body);
	}

	//switch position: absolute with position: relative
	this.removeAbsolutePositioning = function(body) {
		pgzp().jq( pgzp().doc.body ).children().each(function() {
															if (pgzp().jq(this).css("position") == "absolute") pgzp().jq(this).css("position", "static");
														});
		}
}<|endoftext|>


<commit_before>require([
    "Underscore",
    "yapp/yapp",
    "yapp/args",

    "utils/navigation",
    "utils/tv",
    "utils/updates",

    "views/imports",
    "ressources/imports",
], function(_, yapp, args, Navigation, TV, Updates) {
    // Configure yapp
    yapp.configure(args, {
         "logLevels": {
            "requests": "error",
            "ressources": "error",
        },
    });

    // Define base application
    var Application = yapp.Application.extend({
        name: "tv.js",
        template: "main.html",
        metas: {
            "viewport": "width=device-width, initial-scale=1, maximum-scale=1"
        },
        links: {
            "icon": yapp.Urls.static("images/logo_48.png")
        },
        routes: {
            "*actions": "routeHome",
            "search/:q": "routeSearch",
            "play/:id": "routePlay",
        },
        events: {
            "keydown .header .search": "searchKeydown",
            "keyup .header .search": "searchKeyup",
        },

        initialize: function() {
            Application.__super__.initialize.apply(this, arguments);
            this.page = null;
            this.doSearch = _.debounce(function() {
                q = this.$(".header .search").val();
                this.router.navigate("search/:q", {
                    "q": q
                });
            }, 1000);
            return this;
        },

        finish: function() {
            var self = this;
            var only = function(mode, callback, context) {
                callback = _.bind(callback, context);
                return function() {
                    if (self.page == mode
                    && !self.components.keyboard.isOpen()) {
                        callback();
                    }
                }
            };
            var except = function(mode, callback, context) {
                callback = _.bind(callback, context);
                return function() {
                    if (self.page != mode
                    && !self.components.keyboard.isOpen()) {
                        callback();
                    }
                }
            };

            // esc : return to homepage
            Navigation.bind('esc', _.bind(function() {
                if (this.components.keyboard.isOpen()) {
                    this.components.movies.focus();
                } else {
                    this.goHome();
                }
            }, this));

            // movies : navigation
            Navigation.bind('right', except("player", this.components.movies.selectionRight, this.components.movies));
            Navigation.bind('left', except("player", this.components.movies.selectionLeft, this.components.movies));
            Navigation.bind('up', except("player", this.components.movies.selectionUp, this.components.movies));
            Navigation.bind('down', except("player", this.components.movies.selectionDown, this.components.movies));
            Navigation.bind(['enter', 'space'], except("player", this.components.movies.actionSelection, this.components.movies));

            // player
            Navigation.bind(['enter', 'space'], only("player", this.components.player.togglePlay,  this.components.player));

            // Bind keyboard to search
            this.components.keyboard.bindTo(this.$(".header .search"));

            // Get tv ip
            yapp.Requests.getJSON("/api/ip").done(_.bind(function(data) {
                this.$(".intro .ip").text(data.ip);
            }, this));

            // Bind remote
            Updates.on("remote:start", function() {
                this.$(".intro").hide();
            }, this);
            Updates.on("remote:end", function() {
                this.$(".intro").show();
            }, this);

            return Application.__super__.finish.apply(this, arguments);
        },

        templateContext: function() {
            return {}
        },

        /* Route 'home' */
        routeHome: function(id) {
            this.page = "home";
            this.components.player.hide();
            this.components.movies.recents();
            this.$(".header .search").val("");
            if (!TV.check()) this.focusSearch();
            return this;
        },

        /* Route 'search' */
        routeSearch: function(q) {
            this.page = "search";
            this.components.player.hide();
            this.components.movies.search(q);
            return this;
        },

        /* Route 'search' */
        routePlay: function(id) {
            this.page = "player";
            this.components.player.show(id);
            return this;
        },

        /* Return to home page */
        goHome: function() {
            this.router.navigate("home");
            return this;
        },

        /* Focus search bar */
        focusSearch: function() {
            this.$(".header .search").focus();
            $('html, body').animate({
                "scrollTop": 0
            }, 600);
        },

        /* Blur search bar */
        blurSearch: function() {
            this.$(".header .search").blur();
        },

        /*
         *  Search input : keydown on search bar
         *      -> down|enter : go to list
         */
        searchKeyup: function(e) {
            var listCodes = [
                13 /* enter */,
                40 /* down */
            ];
            var code = (e.keyCode ? e.keyCode : e.which);
            if (!TV.check()) {
                if (code == 27 /* esc */) {
                    this.goHome();
                } else if (_.contains(listCodes, code)) {
                    this.components.movies.focus();
                }
            }
            this.doSearch();
        },
        searchKeydown: function(e) {
            if (TV.check()) {
                e.preventDefault();
            }
        },
    });

    var app = new Application();
    app.run();
});<commit_msg>Remove useless method from application<commit_after>require([
    "Underscore",
    "yapp/yapp",
    "yapp/args",

    "utils/navigation",
    "utils/tv",
    "utils/updates",

    "views/imports",
    "ressources/imports",
], function(_, yapp, args, Navigation, TV, Updates) {
    // Configure yapp
    yapp.configure(args, {
         "logLevels": {
            "requests": "error",
            "ressources": "error",
        },
    });

    // Define base application
    var Application = yapp.Application.extend({
        name: "tv.js",
        template: "main.html",
        metas: {
            "viewport": "width=device-width, initial-scale=1, maximum-scale=1"
        },
        links: {
            "icon": yapp.Urls.static("images/logo_48.png")
        },
        routes: {
            "*actions": "routeHome",
            "search/:q": "routeSearch",
            "play/:id": "routePlay",
        },
        events: {
            "keydown .header .search": "searchKeydown",
            "keyup .header .search": "searchKeyup",
        },

        initialize: function() {
            Application.__super__.initialize.apply(this, arguments);
            this.page = null;
            this.doSearch = _.debounce(function() {
                q = this.$(".header .search").val();
                this.router.navigate("search/:q", {
                    "q": q
                });
            }, 1000);
            return this;
        },

        finish: function() {
            var self = this;
            var only = function(mode, callback, context) {
                callback = _.bind(callback, context);
                return function() {
                    if (self.page == mode
                    && !self.components.keyboard.isOpen()) {
                        callback();
                    }
                }
            };
            var except = function(mode, callback, context) {
                callback = _.bind(callback, context);
                return function() {
                    if (self.page != mode
                    && !self.components.keyboard.isOpen()) {
                        callback();
                    }
                }
            };

            // esc : return to homepage
            Navigation.bind('esc', _.bind(function() {
                if (this.components.keyboard.isOpen()) {
                    this.components.movies.focus();
                } else {
                    this.router.navigate("home");
                }
            }, this));

            // movies : navigation
            Navigation.bind('right', except("player", this.components.movies.selectionRight, this.components.movies));
            Navigation.bind('left', except("player", this.components.movies.selectionLeft, this.components.movies));
            Navigation.bind('up', except("player", this.components.movies.selectionUp, this.components.movies));
            Navigation.bind('down', except("player", this.components.movies.selectionDown, this.components.movies));
            Navigation.bind(['enter', 'space'], except("player", this.components.movies.actionSelection, this.components.movies));

            // player
            Navigation.bind(['enter', 'space'], only("player", this.components.player.togglePlay,  this.components.player));

            // Bind keyboard to search
            this.components.keyboard.bindTo(this.$(".header .search"));

            // Get tv ip
            yapp.Requests.getJSON("/api/ip").done(_.bind(function(data) {
                this.$(".intro .ip").text(data.ip);
            }, this));

            // Bind remote
            Updates.on("remote:start", function() {
                this.$(".intro").hide();
            }, this);
            Updates.on("remote:end", function() {
                this.$(".intro").show();
            }, this);

            return Application.__super__.finish.apply(this, arguments);
        },

        templateContext: function() {
            return {}
        },

        /* Route 'home' */
        routeHome: function(id) {
            this.page = "home";
            this.components.player.hide();
            this.components.movies.recents();
            this.$(".header .search").val("");
            if (!TV.check()) this.focusSearch();
            return this;
        },

        /* Route 'search' */
        routeSearch: function(q) {
            this.page = "search";
            this.components.player.hide();
            this.components.movies.search(q);
            return this;
        },

        /* Route 'search' */
        routePlay: function(id) {
            this.page = "player";
            this.components.player.show(id);
            return this;
        },

        /* Focus search bar */
        focusSearch: function() {
            this.$(".header .search").focus();
            $('html, body').animate({
                "scrollTop": 0
            }, 600);
        },

        /* Blur search bar */
        blurSearch: function() {
            this.$(".header .search").blur();
        },

        /*
         *  Search input : keydown on search bar
         *      -> down|enter : go to list
         */
        searchKeyup: function(e) {
            var listCodes = [
                13 /* enter */,
                40 /* down */
            ];
            var code = (e.keyCode ? e.keyCode : e.which);
            if (!TV.check()) {
                if (code == 27 /* esc */) {
                    this.router.navigate("home");
                } else if (_.contains(listCodes, code)) {
                    this.components.movies.focus();
                }
            }
            this.doSearch();
        },
        searchKeydown: function(e) {
            if (TV.check()) {
                e.preventDefault();
            }
        },
    });

    var app = new Application();
    app.run();
});<|endoftext|>


<commit_before>$.paidParkingAPI = {
	init: function(settings) {
		$.QuadTree.init("paid", settings.bounds, this.cluster, this.extractCoordinates, settings.capacity);
	},

	getInformation: function(bounds, callback) {
		$.get("http://acc-api.ville.quebec.qc.ca/stationnement/rest/vdqpark/availabilityservice?response=json", function( data ) {
			if (data.STATUS != "SUCCESS") {
				return;
			}

			var currentID = 0;
			var previousID = 0;
			var uniqueParkings = [];
			$(data.AVL).each(function() {

				if(typeof(this.BFID) !== undefined)
					currentID = this.BFID;
				else
					currentID = this.OSPID;

				if(currentID != previousID)
					uniqueParkings.push(this);
				else {

					var index = uniqueParkings.length - 1;
					uniqueParkings[index].OCC += this.OCC;
					uniqueParkings[index].OPER += this.OPER;
				}
				previousID = currentID;
			});

			$.QuadTree.clear("paid");
			uniqueParkings.forEach(function(value) {

				$.QuadTree.insert("paid", value);
			});
			$.QuadTree.compute("paid");
		});
	},

	addObject: function(callback, avl, zoom) {
		if (avl.PTS != "1") {
			return;
		}
		var mapObject = {tag: ["paying_parking"], zoom: zoom};
		var points = avl.LOC.split(",");
		mapObject.position = {
			latitude: points[1],
			longitude: points[0]
		};
		var description = "";
		if (avl.TYPE == "ON") {
			mapObject.id = "p_on_"+avl.BFID;
			description = avl.NAME;
			if (+avl.OCC < +avl.OPER) {
				mapObject.type = "available_parking";
			}
			else {
				mapObject.type = "unavailable_parking";
			}
		}
		else {
			mapObject.id = "p_off_"+avl.OSPID;
			description = avl.DESC;
			var occupancy = avl.OCC / avl.OPER;
			mapObject.type = getVehiculeParcType(occupancy);
		}
		mapObject.available = (avl.OPER - avl.OCC);
		if(!avl.CLUSTER) {

		mapObject.description = "

Ce point de stationnement rapporte que " + (avl.OPER - avl.OCC) + " stationnement(s) sont libres.

Adresse: " + description + "

<button class=\"expand\" onclick=\"$.parkingMap.getDirectionsTo({latitude: "+mapObject.position.latitude+", longitude: "+mapObject.position.longitude+"});\">Obtenir l'itinéraire</button>

";

		}
		mapObject.label = "
"+mapObject.available+"
";
		callback(mapObject);
	},

	cluster: function(nodeArray) {
		var id = "c";
		if (nodeArray[0].value.TYPE == "ON") id += nodeArray[0].value.BFID;
		else id += nodeArray[0].value.OSPID;
		var clusterNode = {
			PTS: "1",
			TYPE: "ON",
			NAME: "",
			BFID: id,
			OCC: 0,
			OPER: 0,
			CLUSTER: true
		};
		var clusterLatitude = 0;
		var clusterLongitude = 0;
		for (var i = 0; i < nodeArray.length; i++) {
			clusterNode.OPER += parseFloat(nodeArray[i].value.OPER);
			clusterNode.OCC += parseFloat(nodeArray[i].value.OCC);
			clusterLatitude += parseFloat(nodeArray[i].coordinates.latitude);
			clusterLongitude += parseFloat(nodeArray[i].coordinates.longitude);
		}
		clusterLatitude /= nodeArray.length;
		clusterLongitude /= nodeArray.length;
		clusterNode.LOC = clusterLongitude+","+clusterLatitude;
		return {
			coordinates: {
				latitude: clusterLatitude,
				longitude: clusterLongitude
			},
			value: clusterNode
		};
	},

	extractCoordinates: function(avl) {
		var points = avl.LOC.split(",");
		return {
			latitude: parseFloat(points[1]),
			longitude: parseFloat(points[0])
		};
	}
}

function getVehiculeParcType(occupancy) {

	var type = "vehicule_park_high_occupancy";

	if(occupancy < 0.5){
		type = "vehicule_park_low_occupancy";
	}
	else if (occupancy >= 1){
		type = "vehicule_park_full";
	}

	return type;
}
<commit_msg>fixed a bug with the off-street parking clustering<commit_after>$.paidParkingAPI = {
	init: function(settings) {
		$.QuadTree.init("paid", settings.bounds, this.cluster, this.extractCoordinates, settings.capacity);
	},

	getInformation: function(bounds, callback) {
		$.get("http://acc-api.ville.quebec.qc.ca/stationnement/rest/vdqpark/availabilityservice?response=json", function( data ) {
			if (data.STATUS != "SUCCESS") {
				return;
			}

			var currentID = 0;
			var previousID = 0;
			var uniqueParkings = [];
			$(data.AVL).each(function() {

				if(this.BFID !== undefined)
					currentID = this.BFID;
				else
					currentID = this.OSPID;

				if(currentID != previousID)
					uniqueParkings.push(this);
				else {

					var index = uniqueParkings.length - 1;
					uniqueParkings[index].OCC += this.OCC;
					uniqueParkings[index].OPER += this.OPER;
				}
				previousID = currentID;
			});

			$.QuadTree.clear("paid");
			uniqueParkings.forEach(function(value) {

				$.QuadTree.insert("paid", value);
			});
			$.QuadTree.compute("paid");
		});
	},

	addObject: function(callback, avl, zoom) {
		if (avl.PTS != "1") {
			return;
		}
		var mapObject = {tag: ["paying_parking"], zoom: zoom};
		var points = avl.LOC.split(",");
		mapObject.position = {
			latitude: points[1],
			longitude: points[0]
		};
		var description = "";
		if (avl.TYPE == "ON") {
			mapObject.id = "p_on_"+avl.BFID;
			description = avl.NAME;
			if (+avl.OCC < +avl.OPER) {
				mapObject.type = "available_parking";
			}
			else {
				mapObject.type = "unavailable_parking";
			}
		}
		else {
			mapObject.id = "p_off_"+avl.OSPID;
			description = avl.DESC;
			var occupancy = avl.OCC / avl.OPER;
			mapObject.type = getVehiculeParcType(occupancy);
		}
		mapObject.available = (avl.OPER - avl.OCC);
		if(!avl.CLUSTER) {

		mapObject.description = "

Ce point de stationnement rapporte que " + (avl.OPER - avl.OCC) + " stationnement(s) sont libres.

Adresse: " + description + "

<button class=\"expand\" onclick=\"$.parkingMap.getDirectionsTo({latitude: "+mapObject.position.latitude+", longitude: "+mapObject.position.longitude+"});\">Obtenir l'itinéraire</button>

";

		}
		mapObject.label = "
"+mapObject.available+"
";
		callback(mapObject);
	},

	cluster: function(nodeArray) {
		var id = "c";
		if (nodeArray[0].value.TYPE == "ON") id += nodeArray[0].value.BFID;
		else id += nodeArray[0].value.OSPID;
		var clusterNode = {
			PTS: "1",
			TYPE: "ON",
			NAME: "",
			BFID: id,
			OCC: 0,
			OPER: 0,
			CLUSTER: true
		};
		var clusterLatitude = 0;
		var clusterLongitude = 0;
		for (var i = 0; i < nodeArray.length; i++) {
			clusterNode.OPER += parseFloat(nodeArray[i].value.OPER);
			clusterNode.OCC += parseFloat(nodeArray[i].value.OCC);
			clusterLatitude += parseFloat(nodeArray[i].coordinates.latitude);
			clusterLongitude += parseFloat(nodeArray[i].coordinates.longitude);
		}
		clusterLatitude /= nodeArray.length;
		clusterLongitude /= nodeArray.length;
		clusterNode.LOC = clusterLongitude+","+clusterLatitude;
		return {
			coordinates: {
				latitude: clusterLatitude,
				longitude: clusterLongitude
			},
			value: clusterNode
		};
	},

	extractCoordinates: function(avl) {
		var points = avl.LOC.split(",");
		return {
			latitude: parseFloat(points[1]),
			longitude: parseFloat(points[0])
		};
	}
}

function getVehiculeParcType(occupancy) {

	var type = "vehicule_park_high_occupancy";

	if(occupancy < 0.5){
		type = "vehicule_park_low_occupancy";
	}
	else if (occupancy >= 1){
		type = "vehicule_park_full";
	}

	return type;
}
<|endoftext|>


<commit_before>'use strict';
console.log('running');
function initMap() {
  var uluru = {lat: -25.363, lng: 131.044};
  var map = new google.maps.Map(document.getElementById('map'), {
    zoom: 4,
    center: uluru
  });
  var marker = new google.maps.Marker({
    position: uluru,
    map: map
  });
}
<commit_msg>added old map js<commit_after>'use strict';

//array to hold all of our toilets
var toilets = [];
var toilets = JSON.parse(localStorage.data);

//initializing google map
function initMap() {
  var coords = {lat: 47.6164918, lng: -122.3511866};
  var map = new google.maps.Map(document.getElementById('map'), {
    zoom: 15,
    center: coords
  });

  //following lines of code creates a marker then gives that marker an info window and a event listener
  function addMarker(obj){
    var marker = new google.maps.Marker({
      position: obj.coords,
      map: map,
      icon: obj.icon
    });

    var infoWindow = new google.maps.InfoWindow({
      content: obj.info
    });
    marker.addListener('mouseover', function() {
      infoWindow.open(map, marker);
    });
    marker.addListener('mouseout', function() {
      infoWindow.close(map,marker);
    });
  }

  //loops through our array with all the toilets and adds a marker and info window and event listener
  for (var i = 0; i < toilets.length; i++) {
    addMarker(toilets[i]);
  }
};
<|endoftext|>


<commit_before>document.elem = function (elemname, attributes, text) {
    if (typeof attributes === 'string') {
        text = attributes;
        attributes = null;
    }
    return document.createElement(elemname).attrib(attributes).setText(text);
};
document.getElem = function (selector) {
    return document.querySelector(selector);
};
document.getElemAll = function (selector) {
    return document.querySelectorAll(selector);
};
document.delElem = function (elemname) {
    if (element && element.parentNode) {
        element.parentNode.removeChild(element);
    }
};
Element.prototype.getElem = function (selector) {
    return this.querySelector(selector);
};
Element.prototype.getElemAll = function (selector) {
    return this.querySelectorAll(selector);
};
Element.prototype.elem = function (elemname, attr, text, returnparent) {
    var elem = (typeof elemname === 'string') ? document.elem(elemname, attr, text) : elemname;
    this.appendChild(elem);
    return (returnparent !== null && returnparent) ? this : elem;
};
Element.prototype.attrib = function (attribute, value) {
    if (attribute) {
        if (typeof attribute === "object") {
            for (var key in attribute) {
                if (attribute.hasOwnProperty(key)) {
                    this.setAttribute(key, attribute[key]);
                }
            }
        } else if (typeof attribute === 'string') {
            this.setAttribute(attribute, value);
        }
    }
    return this;
};
Element.prototype.setText = function (text) {
    if (text) {
        this.textContent = text;
    }
    return this;
};
Element.prototype.clearElem = function () {
    if (this.textContent !== null) {
        this.textContent = '';
    }
    while (this.firstChild) {
        this.removeChild(this.firstChild);
    }
    return this;
};
['span', 'div', 'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4', 'i', 'em', 'b', 'img', 'table', 'tbody', 'thead', 'tr', 'th', 'td', 'a', 'label', 'input', 'br', 'hr'].map(function (elemname) {
    Element.prototype[elemname] = function (attr, text, returnparent) {
        return this.elem(elemname, attr, text, ['br', 'hr'].indexOf(elemname) !== -1 ? true : returnparent);
    };
    document[elemname] = function (attr, text) {
        return this.elem(elemname, attr, text);
    };
});
NodeList.prototype.each = function (callback) {
    for (var i = 0; i < this.length; i++) {
        callback.call(this[i], i);
    }
};
NodeList.prototype.attrib = function (attribute, value) {
    this.each(function () {
        this.attrib(attribute, value)
    });
};<commit_msg>typo + delElem by selector<commit_after>document.elem = function (elemname, attributes, text) {
    if (typeof attributes === 'string') {
        text = attributes;
        attributes = null;
    }
    return document.createElement(elemname).attrib(attributes).setText(text);
};
document.getElem = function (selector) {
    return document.querySelector(selector);
};
document.getElemAll = function (selector) {
    return document.querySelectorAll(selector);
};
document.delElem = function (element) {
    if (typeof element === 'string') {
        document.getElemAll(element).each( function() {
            if (this.parentNode) this.parentNode.removeChild(this);
        });
    } else if (element && element.parentNode) {
        element.parentNode.removeChild(element);
    }
};
Element.prototype.getElem = function (selector) {
    return this.querySelector(selector);
};
Element.prototype.getElemAll = function (selector) {
    return this.querySelectorAll(selector);
};
Element.prototype.elem = function (elemname, attr, text, returnparent) {
    var elem = (typeof elemname === 'string') ? document.elem(elemname, attr, text) : elemname;
    this.appendChild(elem);
    return (returnparent !== null && returnparent) ? this : elem;
};
Element.prototype.attrib = function (attribute, value) {
    if (attribute) {
        if (typeof attribute === "object") {
            for (var key in attribute) {
                if (attribute.hasOwnProperty(key)) {
                    this.setAttribute(key, attribute[key]);
                }
            }
        } else if (typeof attribute === 'string') {
            this.setAttribute(attribute, value);
        }
    }
    return this;
};
Element.prototype.setText = function (text) {
    if (text) {
        this.textContent = text;
    }
    return this;
};
Element.prototype.clearElem = function () {
    if (this.textContent !== null) {
        this.textContent = '';
    }
    while (this.firstChild) {
        this.removeChild(this.firstChild);
    }
    return this;
};
['span', 'div', 'ul', 'ol', 'li', 'h1', 'h2', 'h3', 'h4', 'i', 'em', 'b', 'img', 'table', 'tbody', 'thead', 'tr', 'th', 'td', 'a', 'label', 'input', 'br', 'hr'].map(function (elemname) {
    Element.prototype[elemname] = function (attr, text, returnparent) {
        return this.elem(elemname, attr, text, ['br', 'hr'].indexOf(elemname) !== -1 ? true : returnparent);
    };
    document[elemname] = function (attr, text) {
        return this.elem(elemname, attr, text);
    };
});
NodeList.prototype.each = function (callback) {
    for (var i = 0; i < this.length; i++) {
        callback.call(this[i], i);
    }
};
NodeList.prototype.attrib = function (attribute, value) {
    this.each(function () {
        this.attrib(attribute, value)
    });
};<|endoftext|>


<commit_before>define([
    "jquery",
    "../lib/ajax",
    "../core/parser",
    "../core/logger",
    "../registry",
    "../utils",
    "jquery-textchange",
    "tiny_mce"
], function($, ajax, Parser, logger, registry, utils) {
    var log = logger.getLogger("pat.editTinyMCE"),
        parser = new Parser("edit-tinymce");

    parser.add_argument("tinymce-baseurl");

    var _ = {
        name: "editTinyMCE",
        trigger: "form textarea.pat-edit-tinymce",
        init: function($el, opts) {
            var $form = $el.parents("form"),
                id = $el.attr("id");

            // make sure the textarea has an id
            if (!id) {
                var formid = $form.attr("id"),
                    name = $el.attr("name");
                if (!formid) {
                    log.error("Textarea or parent form needs an id", $el, $form);
                    return false;
                }
                if (!name) {
                    log.error("Textarea needs a name", $el);
                    return false;
                }
                id = formid + "_" + name;
                if ($("#"+id).length > 0) {
                    log.error("Textarea needs an id", $el);
                    return false;
                }
                $el.attr({id: id});
            }

            // read configuration
            var cfg = $el.data("tinymce-json");
            if (!cfg) {
                log.info("data-tinymce-json empty, using default config", $el);
                cfg = {};
            }
            cfg.elements = id;
            cfg.mode = "exact";
            cfg.readonly = Boolean($el.attr("readonly"));

            // get arguments
            var args = parser.parse($el, opts);

            if (!args.tinymceBaseurl) {
                log.error("tinymce-baseurl has to point to TinyMCE resources");
                return false;
            }

            var base_url = window.location.toString(), idx;
            if ((idx=base_url.indexOf("?"))!==-1)
                base_url=base_url.slice(0, idx);

            // handle rebasing of own urls if we were injected
            var parents = $el.parents().filter(function() {
                return $(this).data("pat-injected");
            });
            if (parents.length)
                base_url = utils.rebaseURL(base_url, parents.first().data("pat-injected").origin);
            if (cfg.content_css)
                cfg.content_css = utils.rebaseURL(base_url, cfg.content_css);
            tinyMCE.baseURL = utils.rebaseURL(base_url, args.tinymceBaseurl);
            tinyMCE.baseURI = new tinyMCE.util.URI(tinyMCE.baseURL);

            var $tinymce, $tinyifr,
                propagate = function(ev) {
                    $tinyifr.trigger(ev);
                };
            cfg.oninit = function() {
                // find tiny's iframe and edit field
                $tinyifr = $("#" + id + "_ifr");
                $tinymce = $tinyifr.contents().find("#tinymce");

                // propagate first textchange event outside the iframe
                $tinymce.one("textchange.pat-tinymce", propagate)
                    .one("change.pat-tinymce", propagate);
            };

            // reactivate textchange propagation on reset and successfull submit
            $form.on("reset.pat-tinymce pat-ajax-success.pat-tinymce", function() {
                $tinymce
                    .off(".pat-tinymce")
                    .one("textchange.pat-tinymce", propagate)
                    .one("change.pat-tinymce", propagate);
            });

            // initialize editor
            tinyMCE.init(cfg);

            // ajaxify form
            var ajaxopts = {
                beforeSerialize: (function(id) {
                    return function() {
                        tinyMCE.editors[id].save();
                    };
                })(id)
            };

            $form.on("submit.pat-tinymce", function(ev) {
                ev.preventDefault();
                ajax($form, ajaxopts);
            });

            return $el;
        },

        destroy: function() {
            // XXX
        }
    };

    registry.register(_);
    return _;
});

// jshint indent: 4, browser: true, jquery: true, quotmark: double
// vim: sw=4 expandtab
<commit_msg>Remove ajax dependency from tiny and simplify code.<commit_after>define([
    "jquery",
    "../core/parser",
    "../core/logger",
    "../registry",
    "../utils",
    "jquery-textchange",
    "tiny_mce"
], function($, Parser, logger, registry, utils) {
    var log = logger.getLogger("pat.editTinyMCE"),
        parser = new Parser("edit-tinymce");

    parser.add_argument("tinymce-baseurl");

    var _ = {
        name: "editTinyMCE",
        trigger: "form textarea.pat-edit-tinymce",
        init: function($el, opts) {
            var $form = $el.parents("form"),
                id = $el.attr("id");

            // make sure the textarea has an id
            if (!id) {
                var formid = $form.attr("id"),
                    name = $el.attr("name");
                if (!formid) {
                    log.error("Textarea or parent form needs an id", $el, $form);
                    return false;
                }
                if (!name) {
                    log.error("Textarea needs a name", $el);
                    return false;
                }
                id = formid + "_" + name;
                if ($("#"+id).length > 0) {
                    log.error("Textarea needs an id", $el);
                    return false;
                }
                $el.attr({id: id});
            }

            // read configuration
            var cfg = $el.data("tinymce-json");
            if (!cfg) {
                log.info("data-tinymce-json empty, using default config", $el);
                cfg = {};
            }
            cfg.elements = id;
            cfg.mode = "exact";
            cfg.readonly = Boolean($el.attr("readonly"));

            // get arguments
            var args = parser.parse($el, opts);

            if (!args.tinymceBaseurl) {
                log.error("tinymce-baseurl has to point to TinyMCE resources");
                return false;
            }

            var base_url = window.location.toString(), idx;
            if ((idx=base_url.indexOf("?"))!==-1)
                base_url=base_url.slice(0, idx);

            // handle rebasing of own urls if we were injected
            var parents = $el.parents().filter(function() {
                return $(this).data("pat-injected");
            });
            if (parents.length)
                base_url = utils.rebaseURL(base_url, parents.first().data("pat-injected").origin);
            if (cfg.content_css)
                cfg.content_css = utils.rebaseURL(base_url, cfg.content_css);
            tinyMCE.baseURL = utils.rebaseURL(base_url, args.tinymceBaseurl);
            tinyMCE.baseURI = new tinyMCE.util.URI(tinyMCE.baseURL);

            cfg.oninit = function() {
                var $tinymce, $tinyifr;

                // find tiny's iframe and edit field
                $tinyifr = $("#" + id + "_ifr");
                $tinymce = $tinyifr.contents().find("#tinymce");

                // XXX: add events for undo, redo, ...
                if ("oninput" in window) {
                    $tinymce.on("input.pat-tinymce", function() {
                        log.debug('translating tiny input');
                        tinyMCE.editors[id].save();
                        $el.trigger("input-change");
                    });
                } else {
                    // this is the legacy code path for IE8
                    $tinymce.on("change.pat-tinymce textchange.pat-tinymce", function() {
                        log.debug('translating tiny change and textchange');
                        tinyMCE.editors[id].save();
                        $el.trigger("input-change");
                    });
                }
            };

            // initialize editor
            tinyMCE.init(cfg);

            return $el;
        },

        destroy: function() {
            // XXX
        }
    };

    registry.register(_);
    return _;
});

// jshint indent: 4, browser: true, jquery: true, quotmark: double
// vim: sw=4 expandtab
<|endoftext|>