<commit_before>import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, find, click } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';

module('Integration | Component | toggle icons', function(hooks) {
  setupRenderingTest(hooks);

  test('it renders', async function(assert) {
    assert.expect(9);

    const firstLabel = 'label:nth-of-type(1)';
    const firstRadio = 'input:nth-of-type(1)';
    const secondLabel = 'label:nth-of-type(2)';
    const secondRadio = 'input:nth-of-type(2)';
    const icon = 'svg:nth-of-type(1)';

    this.set('nothing', parseInt);
    await render(hbs`{{toggle-icons
      action=(action nothing)
      firstOptionSelected=true
      firstLabel='First'
      firstIcon='user'
      secondLabel='Second'
      secondIcon='expand'
    }}`);
    assert.dom(firstLabel).hasText('First', 'first label has correct text');
    assert.ok(this.$(firstRadio).is(':checked'), 'first radio is checked');
    assert.equal(find(firstLabel).getAttribute('for'), find(firstRadio).id, 'first label is linked to radio correctly');

    assert.dom(secondLabel).hasText('Second', 'second label has correct text');
    assert.notOk(this.$(secondRadio).is(':checked'), 'second radio is not checked');
    assert.equal(find(secondLabel).getAttribute('for'), find(secondRadio).id, 'second label is linked to radio correctly');

    assert.dom(icon).exists({ count: 1 });
    assert.dom(icon).hasClass('fa-user', 'correct icon is visible');
    assert.equal(find(icon).getAttribute('title').trim(), 'Second', 'Title text is correct');
  });

  test('clicking radio fires toggle action', async function(assert) {
    assert.expect(2);

    const first = 'input:nth-of-type(1)';
    const second = 'input:nth-of-type(2)';

    this.set('firstOptionSelected', true);
    let called = 0;
    this.set('toggle', (newValue) => {
      if (called === 0) {
        assert.notOk(newValue, 'has not been toggled yet');
      } else {
        assert.ok(newValue, 'has been toggled');
      }
      this.set('firstOptionSelected', newValue);
      called ++;
    });
    await render(hbs`{{toggle-icons
      toggle=(action toggle)
      firstOptionSelected=firstOptionSelected
      firstLabel='Left'
      secondLabel='Right'
    }}`);

    await click(second);
    await click(first);
  });

  test('clicking selected radio does not fire toggle action', async function(assert) {
    assert.expect(1);

    const first = 'input:nth-of-type(1)';

    this.set('firstOptionSelected', true);
    this.set('toggle', () => {
      assert.ok(false, 'this should not be fired');
    });
    await render(hbs`{{toggle-icons
      toggle=(action toggle)
      firstOptionSelected=firstOptionSelected
      firstLabel='Left'
      secondLabel='Right'
    }}`);

    await click(first);
    assert.equal(this.get('firstOptionSelected'), true);
  });

  test('clicking icon fires toggle action', async function(assert) {
    assert.expect(8);

    const icon = 'svg:nth-of-type(1)';

    this.set('firstOptionSelected', true);
    let called = 0;
    this.set('toggle', (newValue) => {
      if (called === 0) {
        assert.notOk(newValue, 'has not been toggled yet');
      } else {
        assert.ok(newValue, 'has been toggled');
      }
      this.set('firstOptionSelected', newValue);
      called ++;
    });
    await render(hbs`{{toggle-icons
      toggle=(action toggle)
      firstOptionSelected=firstOptionSelected
      firstLabel='First'
      firstIcon='user'
      secondLabel='Second'
      secondIcon='expand'
    }}`);

    assert.dom(icon).hasClass('fa-user');
    assert.equal(find(icon).getAttribute('title').trim(), 'Second', 'Title text is correct');
    await click(icon);
    assert.dom(icon).hasClass('fa-expand');
    assert.equal(find(icon).getAttribute('title').trim(), 'First', 'Title text is correct');
    await click(icon);
    assert.dom(icon).hasClass('fa-user');
    assert.equal(find(icon).getAttribute('title').trim(), 'Second', 'Title text is correct');
  });
});
<commit_msg>provide missing component args to tests.<commit_after>import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, find, click } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';

module('Integration | Component | toggle icons', function(hooks) {
  setupRenderingTest(hooks);

  test('it renders', async function(assert) {
    assert.expect(9);

    const firstLabel = 'label:nth-of-type(1)';
    const firstRadio = 'input:nth-of-type(1)';
    const secondLabel = 'label:nth-of-type(2)';
    const secondRadio = 'input:nth-of-type(2)';
    const icon = 'svg:nth-of-type(1)';

    this.set('nothing', parseInt);
    await render(hbs`{{toggle-icons
      action=(action nothing)
      firstOptionSelected=true
      firstLabel='First'
      firstIcon='user'
      secondLabel='Second'
      secondIcon='expand'
    }}`);
    assert.dom(firstLabel).hasText('First', 'first label has correct text');
    assert.ok(this.$(firstRadio).is(':checked'), 'first radio is checked');
    assert.equal(find(firstLabel).getAttribute('for'), find(firstRadio).id, 'first label is linked to radio correctly');

    assert.dom(secondLabel).hasText('Second', 'second label has correct text');
    assert.notOk(this.$(secondRadio).is(':checked'), 'second radio is not checked');
    assert.equal(find(secondLabel).getAttribute('for'), find(secondRadio).id, 'second label is linked to radio correctly');

    assert.dom(icon).exists({ count: 1 });
    assert.dom(icon).hasClass('fa-user', 'correct icon is visible');
    assert.equal(find(icon).getAttribute('title').trim(), 'Second', 'Title text is correct');
  });

  test('clicking radio fires toggle action', async function(assert) {
    assert.expect(2);

    const first = 'input:nth-of-type(1)';
    const second = 'input:nth-of-type(2)';

    this.set('firstOptionSelected', true);
    let called = 0;
    this.set('toggle', (newValue) => {
      if (called === 0) {
        assert.notOk(newValue, 'has not been toggled yet');
      } else {
        assert.ok(newValue, 'has been toggled');
      }
      this.set('firstOptionSelected', newValue);
      called ++;
    });
    await render(hbs`{{toggle-icons
      toggle=(action toggle)
      firstOptionSelected=firstOptionSelected
      firstLabel='First'
      firstIcon='user'
      secondLabel='Second'
      secondIcon='expand'
    }}`);

    await click(second);
    await click(first);
  });

  test('clicking selected radio does not fire toggle action', async function(assert) {
    assert.expect(1);

    const first = 'input:nth-of-type(1)';

    this.set('firstOptionSelected', true);
    this.set('toggle', () => {
      assert.ok(false, 'this should not be fired');
    });
    await render(hbs`{{toggle-icons
      toggle=(action toggle)
      firstOptionSelected=firstOptionSelected
      firstLabel='First'
      firstIcon='user'
      secondLabel='Second'
      secondIcon='expand'
    }}`);

    await click(first);
    assert.equal(this.get('firstOptionSelected'), true);
  });

  test('clicking icon fires toggle action', async function(assert) {
    assert.expect(8);

    const icon = 'svg:nth-of-type(1)';

    this.set('firstOptionSelected', true);
    let called = 0;
    this.set('toggle', (newValue) => {
      if (called === 0) {
        assert.notOk(newValue, 'has not been toggled yet');
      } else {
        assert.ok(newValue, 'has been toggled');
      }
      this.set('firstOptionSelected', newValue);
      called ++;
    });
    await render(hbs`{{toggle-icons
      toggle=(action toggle)
      firstOptionSelected=firstOptionSelected
      firstLabel='First'
      firstIcon='user'
      secondLabel='Second'
      secondIcon='expand'
    }}`);

    assert.dom(icon).hasClass('fa-user');
    assert.equal(find(icon).getAttribute('title').trim(), 'Second', 'Title text is correct');
    await click(icon);
    assert.dom(icon).hasClass('fa-expand');
    assert.equal(find(icon).getAttribute('title').trim(), 'First', 'Title text is correct');
    await click(icon);
    assert.dom(icon).hasClass('fa-user');
    assert.equal(find(icon).getAttribute('title').trim(), 'Second', 'Title text is correct');
  });
});
<|endoftext|>


<commit_before>$(document).ready(function () {

  /*
  $('#sections').sortable({
    placeholder: 'citation-placeholder'
  });
  */

  $('body')
    .on('click', '#add-citation', function (event) {
      event.preventDefault();
      deactivateSections();
      addSection();
    })
    .on('click', '.citation-edit:not(.citation-edit-active)', function (event) {
      event.preventDefault();
      deactivateSections();
      editSection($(this));
    })
    .on('click', '.add-new-document', function (event) {
      event.preventDefault();
      addDocumentModal($(this).siblings('input'));
    })
    .on('submit', '#main form', function () {
      deactivateSections();
      return true;
    })
    .on('click', '.save-document', function (event) {
      event.preventDefault();
      saveDocument($(this).closest('.modal'));
    })

  /**
   * Removes current wysihtml5 instance and updates form values to reflect
   * any changes made to previous active section.
   *
   */
  function deactivateSections() {
    $('.citation-edit-active').each(function () {
      var $this = $(this)
        , wysihtml5Selectors
        , isBlankSection

      // Update text content that will be displayed with edited value in textarea
      $('.citation-notes', $this)
        .html($this.data('editor').getValue())
        .show();

      wysihtml5Selectors = [
        'iframe.wysihtml5-sandbox',
        'input[name="_wysihtml5_mode"]',
        '.btn-toolbar'
      ]

      $this.removeClass('citation-edit-active')
        .find(wysihtml5Selectors.join(', '))
          .remove();

      // Right now, a section is blank if it doesn't cite a document. This will
      // need to be updated when we have different kinds of sections
      isBlankSection = !$('.citation-document', $this).text().trim().length;

      if (isBlankSection) {
        $this.remove();
      }

      // Update the management after everything else has been done
      $('input[name="citation-TOTAL_FORMS"]').val($('.citation-edit').length);

    });
  };

  /**
   * Returns representation of form as an object, for use in, e.g., ajax POST
   *
   */
  function formToObject(form) {
    var formArray = $(':input', form).serializeArray()
      , formObject = {}

    for (var i = 0; i < formArray.length; i++) {
      formObject[formArray[i].name] = formArray[i].value;
    }

    return formObject;
  }

  /**
   * Initialize a wysihtml5 instance for a given section
   *
   */
  function editSection($section) {
    var editor
      , toolbar
      , textarea = $section.find('textarea[name$="notes"]')
      , $content = $section.find('.citation-notes');

    $section.addClass('citation-edit-active');

    textarea
      .attr('id', textarea.attr('name'))
      .css({
        'width': '97%',
        'height': (function (h) {
          return (h < 380 ? h : 380) + 120 + 'px'
        })($content.innerHeight())
      })
      .show();

    $content.hide();

    toolbar = $('#citation-toolbar').clone().show()
      .attr('id', $section.attr('id') + '-toolbar')
      .insertBefore(textarea);

    editor = new wysihtml5.Editor(textarea.attr('id'), {
      toolbar: toolbar.attr('id'),
      parserRules: wysihtml5ParserRules,
      stylesheets: ['/static/function/wysihtml5/stylesheet.css'],
      useLineBreaks: false
    });

    $section.data('editor', editor);

  };

  /**
   * Add a new section with a blank input for the document field that allows
   * either selection of existing documents via autocomplete or the creation
   * of a new document (triggered by an anchor)
   *
   */
  function addSection(after) {
    var $newSection = $('
')
      , $documentInput = $('<input type="text">')
      , lastSectionID
      , $newDocumentButton
      , autocompleteOptions = $('body').data('baseAutocompleteOptions');

    lastSectionID = Math.max.apply(Math, $('.citation-edit').map(function () {
      return (/\d+/).exec(this.id);
    }));

    $newSection
      .addClass('citation-edit')
      .prop('id', 'citation-' + (lastSectionID + 1))
      .append('

')
      .append('

')
      .prependTo('#citation-items');

    $('#citation-' + lastSectionID + ' .citation-fields')
      .clone()
      .appendTo($newSection)
      .children(':input').each(function () {
        this.name = this.name.replace(lastSectionID, lastSectionID + 1)
        switch (/\w+$/.exec(this.name)[0]) {
        case 'id':
          $(this).remove();
          break;
        case 'notes':
          $(this).val('').html('').css('height', '1px');
          break;
        case 'note':
          break;
        case 'DELETE':
          $(this).val('').prop('checked', false);
          break;
        default:
          $(this).val('');
          break;
        }
      });

    $documentInput
      .appendTo($newSection.find('.citation-document'))
      .addClass('document-autocomplete')
      .data('search-target', 'documents')
      .prop('placeholder', 'Type to search for a document, or add a new one by clicking the icon to the right.')
      .css({
        'margin': '0 4px',
        'width': '550px',
        'border-radius': '1px'
      })
      .autocomplete($.extend(autocompleteOptions, {
        'select': function (event, ui) {
          $this = $(event.target);

          $this.closest('.citation-document')
            .append(ui.item.value);

          $this.closest('.citation-edit')
            .find('input[name$="document"]')
              .val(ui.item.id);

          $this.remove();
          $newDocumentButton.remove();

        }
      }));

    $newDocumentButton = $('')
      .html('')
      .insertAfter($documentInput);

    $newSection.trigger('click');
  };

  /**
   * Initialize a modal to add a document asynchronously
   *
   */
  function addDocumentModal(sourceInput) {
    var $w = $(window)
      , $modal = $('.add-document-modal').clone()
      , textarea = $('textarea', $modal).prop('id', 'add-document-modal')
      , setModalHeight

    setModalHeight = function () {
      var bodyHeight = $modal.innerHeight() - 33
        - _.reduce($modal.children(':not(.modal-body)'), function (i, item) {
            return i + $(item).innerHeight()
          }, 0);

      $('.modal-body', $modal).css({
        'height': bodyHeight,
        'max-height': bodyHeight
      })
    }

    $modal
      .data('sourceInput', sourceInput)
      .on('hidden', function () {
        $modal.remove();
      })
      .css({
        'position': 'absolute',
        'width': $('#main').width() - 100,
        'height': (function () {
          var h = $w.height() - 40;
          return h > 500 ? h : 500;
        })()
      })
      .appendTo('body')
      .modal()
      // Don't ask me why, but we need to do this twice for webkit
      .position({
        'my': 'top',
        'at': 'top',
        'of': $w,
        'collision': 'none',
        'offset': '0 20'
      })
      .position({
        'my': 'top',
        'at': 'top',
        'of': $w,
        'collision': 'none',
        'offset': '0 20'
      })

    setModalHeight();

    $.ajax({
      url: '/api/document/template/',
      success: function (data) {
        $('.zotero-information-edit', $modal)
          .html(data)
          .zotero({'description': '#' + textarea.prop('id')})
          .data('editor').on('load', function () {
            setModalHeight()
          });
      }
    });

  };

  function saveDocument($modal) {
    var zoteroContainer = $('.zotero-information-edit', $modal)
      , zoteroData = zoteroContainer.data('asZoteroObject')
      , description = zoteroContainer.data('editor').getValue()
      , data = {
        'csrfmiddlewaretoken': $('input[name="csrfmiddlewaretoken"]').val(),
        'description': description
      }

    if (!$.isEmptyObject(zoteroData)) {
      data.zotero_data = JSON.stringify(zoteroData);
    }

    if (!$('
').html(description).text().length) {
      alert('Enter a textual representation of this document before saving it.');
      return;
    }

    $.ajax({
      type: 'POST',
      url: '/admin/main/document/add/',
      data: data,
      success: function (newDocument) {
        var newDocumentObj = JSON.parse(newDocument)
          , oldInput = $modal.data('sourceInput');

        oldInput
          .siblings('.add-new-document').remove();

        oldInput
          .closest('.citation-edit').find('input[name$="document"]')
            .val(newDocumentObj.id);

        oldInput
          .replaceWith(newDocumentObj.value)

        $modal.modal('hide').remove();
      },
      error: function () {
      }
    });
  }

});
<commit_msg>Refactor some js in citation admin to clarify<commit_after>$(document).ready(function () {

  /*
  $('#sections').sortable({
    placeholder: 'citation-placeholder'
  });
  */

  $('body')
    .on('click', '#add-citation', function (event) {
      event.preventDefault();
      deactivateSections();
      addSection();
    })
    .on('click', '.citation-edit:not(.citation-edit-active)', function (event) {
      event.preventDefault();
      deactivateSections();
      editSection($(this));
    })
    .on('click', '.add-new-document', function (event) {
      event.preventDefault();
      addDocumentModal($(this).siblings('input'));
    })
    .on('submit', '#main form', function () {
      deactivateSections();
      return true;
    })
    .on('click', '.save-document', function (event) {
      event.preventDefault();
      saveDocument($(this).closest('.modal'));
    })

  /**
   * Removes current wysihtml5 instance and updates form values to reflect
   * any changes made to previous active section.
   *
   */
  function deactivateSections() {
    $('.citation-edit-active').each(function () {
      var $activeSection = $(this)
        , toRemove
        , isBlankSection

      toRemove = [
        'iframe.wysihtml5-sandbox',
        'input[name="_wysihtml5_mode"]',
        '.btn-toolbar',
        '.edit-row'
      ]

      // Update text content that will be displayed with edited value in textarea
      $('.citation-notes', $activeSection)
        .html($activeSection.data('editor').getValue())
        .show();

      $activeSection
        .removeClass('citation-edit-active')
        .find(toRemove.join(', '))
          .remove();

      // Right now, a section is blank if it doesn't cite a document. This will
      // need to be updated when we have different kinds of sections
      isBlankSection = !$('.citation-document', $activeSection).text().trim().length;

      if (isBlankSection) {
        $activeSection.remove();
      }

    });

    // Update management form
    $('input[name="citation-TOTAL_FORMS"]').val($('.citation-edit').length);
    reorderSections();
  };

  /**
   * Returns representation of form as an object, for use in, e.g., ajax POST
   *
   */
  function formToObject(form) {
    var formArray = $(':input', form).serializeArray()
      , formObject = {}

    for (var i = 0; i < formArray.length; i++) {
      formObject[formArray[i].name] = formArray[i].value;
    }

    return formObject;
  }

  /**
   * Initialize a wysihtml5 instance for a given section
   *
   */
  function editSection($section) {
    var editor
      , toolbar
      , textarea = $section.find('textarea[name$="notes"]')
      , $content = $section.find('.citation-notes');

    $section.addClass('citation-edit-active');

    textarea
      .attr('id', textarea.attr('name'))
      .css({
        'width': '97%',
        'height': (function (h) {
          return (h < 380 ? h : 380) + 120 + 'px'
        })($content.innerHeight())
      })
      .show();

    $content.hide();

    toolbar = $('#citation-toolbar').clone().show()
      .attr('id', $section.attr('id') + '-toolbar')
      .insertBefore(textarea);

    editor = new wysihtml5.Editor(textarea.attr('id'), {
      toolbar: toolbar.attr('id'),
      parserRules: wysihtml5ParserRules,
      stylesheets: ['/static/function/wysihtml5/stylesheet.css'],
      useLineBreaks: false
    });

    $section.data('editor', editor);

  };

  /**
   * Add a new section with a blank input for the document field that allows
   * either selection of existing documents via autocomplete or the creation
   * of a new document (triggered by an anchor)
   *
   */
  function addSection(after) {
    var $newSection = $('
')
      , $documentInput = $('<input type="text">')
      , lastSectionID
      , $newDocumentButton
      , autocompleteOptions = $('body').data('baseAutocompleteOptions');

    lastSectionID = Math.max.apply(Math, $('.citation-edit').map(function () {
      return (/\d+/).exec(this.id);
    }));

    $newSection
      .addClass('citation-edit')
      .prop('id', 'citation-' + (lastSectionID + 1))
      .append('

')
      .append('

')
      .prependTo('#citation-items');

    $('#citation-' + lastSectionID + ' .citation-fields')
      .clone()
      .appendTo($newSection)
      .children(':input').each(function () {
        this.name = this.name.replace(lastSectionID, lastSectionID + 1)
        switch (/\w+$/.exec(this.name)[0]) {
        case 'id':
          $(this).remove();
          break;
        case 'notes':
          $(this).val('').html('').css('height', '1px');
          break;
        case 'note':
          break;
        case 'DELETE':
          $(this).val('').prop('checked', false);
          break;
        default:
          $(this).val('');
          break;
        }
      });

    $documentInput
      .appendTo($newSection.find('.citation-document'))
      .addClass('document-autocomplete')
      .data('search-target', 'documents')
      .prop('placeholder', 'Type to search for a document, or add a new one by clicking the icon to the right.')
      .css({
        'margin': '0 4px',
        'width': '550px',
        'border-radius': '1px'
      })
      .autocomplete($.extend(autocompleteOptions, {
        'select': function (event, ui) {
          $this = $(event.target);

          $this.closest('.citation-document')
            .append(ui.item.value);

          $this.closest('.citation-edit')
            .find('input[name$="document"]')
              .val(ui.item.id);

          $this.remove();
          $newDocumentButton.remove();

        }
      }));

    $newDocumentButton = $('')
      .html('')
      .insertAfter($documentInput);

    $newSection.trigger('click');
  };

  /**
   * Initialize a modal to add a document asynchronously
   *
   */
  function addDocumentModal(sourceInput) {
    var $w = $(window)
      , $modal = $('.add-document-modal').clone()
      , textarea = $('textarea', $modal).prop('id', 'add-document-modal')
      , setModalHeight

    setModalHeight = function () {
      var bodyHeight = $modal.innerHeight() - 33
        - _.reduce($modal.children(':not(.modal-body)'), function (i, item) {
            return i + $(item).innerHeight()
          }, 0);

      $('.modal-body', $modal).css({
        'height': bodyHeight,
        'max-height': bodyHeight
      })
    }

    $modal
      .data('sourceInput', sourceInput)
      .on('hidden', function () {
        $modal.remove();
      })
      .css({
        'position': 'absolute',
        'width': $('#main').width() - 100,
        'height': (function () {
          var h = $w.height() - 40;
          return h > 500 ? h : 500;
        })()
      })
      .appendTo('body')
      .modal()
      // Don't ask me why, but we need to do this twice for webkit
      .position({
        'my': 'top',
        'at': 'top',
        'of': $w,
        'collision': 'none',
        'offset': '0 20'
      })
      .position({
        'my': 'top',
        'at': 'top',
        'of': $w,
        'collision': 'none',
        'offset': '0 20'
      })

    setModalHeight();

    $.ajax({
      url: '/api/document/template/',
      success: function (data) {
        $('.zotero-information-edit', $modal)
          .html(data)
          .zotero({'description': '#' + textarea.prop('id')})
          .data('editor').on('load', function () {
            setModalHeight()
          });
      }
    });

  };

  function saveDocument($modal) {
    var zoteroContainer = $('.zotero-information-edit', $modal)
      , zoteroData = zoteroContainer.data('asZoteroObject')
      , description = zoteroContainer.data('editor').getValue()
      , data = {
        'csrfmiddlewaretoken': $('input[name="csrfmiddlewaretoken"]').val(),
        'description': description
      }

    if (!$.isEmptyObject(zoteroData)) {
      data.zotero_data = JSON.stringify(zoteroData);
    }

    if (!$('
').html(description).text().length) {
      alert('Enter a textual representation of this document before saving it.');
      return;
    }

    $.ajax({
      type: 'POST',
      url: '/admin/main/document/add/',
      data: data,
      success: function (newDocument) {
        var newDocumentObj = JSON.parse(newDocument)
          , oldInput = $modal.data('sourceInput');

        oldInput
          .siblings('.add-new-document').remove();

        oldInput
          .closest('.citation-edit').find('input[name$="document"]')
            .val(newDocumentObj.id);

        oldInput
          .replaceWith(newDocumentObj.value)

        $modal.modal('hide').remove();
      },
      error: function () {
      }
    });
  }

});
<|endoftext|>


<commit_before>(function () {
var tokenRegex = /\{([^\}]+)\}/g,
    objNotationRegex = /(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g, // matches .xxxxx or ["xxxxx"] to run over object properties
    replacer = function (all, key, obj) {
        var res = obj;
        key.replace(objNotationRegex, function (all, name, quote, quotedName, isFunc) {
            name = name || quotedName;
            if (res) {
                if (name in res) {
                    res = res[name];
                }
                // (typeof(res) == "function") && isFunc && (res = res());
            }
        });
        res = (res === null || res == obj ? all : res) + "";
        return res;
    },
    fill = function (str, obj) {
        return String(str).replace(tokenRegex, function (all, key) {
            return replacer(all, key, obj);
        });
    };
    Raphael.fn.popup = function (X, Y, set, pos, ret) {
        pos = String(pos || "top-middle").split("-");
        pos[1] = pos[1] || "middle";
        var r = 5,
            bb = set.getBBox(),
            w = Math.round(bb.width),
            h = Math.round(bb.height),
            x = Math.round(bb.x) - r,
            y = Math.round(bb.y) - r,
            gap = Math.min(h / 2, w / 2, 10),
            shapes = {
                top: "M{x},{y}h{w4},{w4},{w4},{w4}a{r},{r},0,0,1,{r},{r}v{h4},{h4},{h4},{h4}a{r},{r},0,0,1,-{r},{r}l-{right},0-{gap},{gap}-{gap}-{gap}-{left},0a{r},{r},0,0,1-{r}-{r}v-{h4}-{h4}-{h4}-{h4}a{r},{r},0,0,1,{r}-{r}z",
                bottom: "M{x},{y}l{left},0,{gap}-{gap},{gap},{gap},{right},0a{r},{r},0,0,1,{r},{r}v{h4},{h4},{h4},{h4}a{r},{r},0,0,1,-{r},{r}h-{w4}-{w4}-{w4}-{w4}a{r},{r},0,0,1-{r}-{r}v-{h4}-{h4}-{h4}-{h4}a{r},{r},0,0,1,{r}-{r}z",
                right: "M{x},{y}h{w4},{w4},{w4},{w4}a{r},{r},0,0,1,{r},{r}v{h4},{h4},{h4},{h4}a{r},{r},0,0,1,-{r},{r}h-{w4}-{w4}-{w4}-{w4}a{r},{r},0,0,1-{r}-{r}l0-{bottom}-{gap}-{gap},{gap}-{gap},0-{top}a{r},{r},0,0,1,{r}-{r}z",
                left: "M{x},{y}h{w4},{w4},{w4},{w4}a{r},{r},0,0,1,{r},{r}l0,{top},{gap},{gap}-{gap},{gap},0,{bottom}a{r},{r},0,0,1,-{r},{r}h-{w4}-{w4}-{w4}-{w4}a{r},{r},0,0,1-{r}-{r}v-{h4}-{h4}-{h4}-{h4}a{r},{r},0,0,1,{r}-{r}z"
            },
            offset = {
                hx0: X - (x + r + w - gap * 2),
                hx1: X - (x + r + w / 2 - gap),
                hx2: X - (x + r + gap),
                vhy: Y - (y + r + h + r + gap),
                "^hy": Y - (y - gap)

            },
            mask = [{
                x: x + r,
                y: y,
                w: w,
                w4: w / 4,
                h4: h / 4,
                right: 0,
                left: w - gap * 2,
                bottom: 0,
                top: h - gap * 2,
                r: r,
                h: h,
                gap: gap
            }, {
                x: x + r,
                y: y,
                w: w,
                w4: w / 4,
                h4: h / 4,
                left: w / 2 - gap,
                right: w / 2 - gap,
                top: h / 2 - gap,
                bottom: h / 2 - gap,
                r: r,
                h: h,
                gap: gap
            }, {
                x: x + r,
                y: y,
                w: w,
                w4: w / 4,
                h4: h / 4,
                left: 0,
                right: w - gap * 2,
                top: 0,
                bottom: h - gap * 2,
                r: r,
                h: h,
                gap: gap
            }][pos[1] == "middle" ? 1 : (pos[1] == "top" || pos[1] == "left") * 2];
            var dx = 0,
                dy = 0,
                out = this.path(fill(shapes[pos[0]], mask)).insertBefore(set);
            switch (pos[0]) {
                case "top":
                    dx = X - (x + r + mask.left + gap);
                    dy = Y - (y + r + h + r + gap);
                break;
                case "bottom":
                    dx = X - (x + r + mask.left + gap);
                    dy = Y - (y - gap);
                break;
                case "left":
                    dx = X - (x + r + w + r + gap);
                    dy = Y - (y + r + mask.top + gap);
                break;
                case "right":
                    dx = X - (x - gap);
                    dy = Y - (y + r + mask.top + gap);
                break;
            }
            out.translate(dx, dy);
            if (ret) {
                ret = out.attr("path");
                out.remove();
                return {
                    path: ret,
                    dx: dx,
                    dy: dy
                };
            }
            set.translate(dx, dy);
            return out;
    };
})();
<commit_msg>Adminpanel analytics hell<commit_after>(function(){var e=/\{([^\}]+)\}/g,t=/(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g,n=function(e,n,r){var i=r;n.replace(t,function(e,t,n,r,s){t=t||r;if(i){if(t in i){i=i[t]}typeof i=="function"&&s&&(i=i())}});i=(i===null||i==r?e:i)+"";return i},r=function(t,r){return String(t).replace(e,function(e,t){return n(e,t,r)})};Raphael.fn.popup=function(e,t,n,i,s){i=String(i||"top-middle").split("-");i[1]=i[1]||"middle";var o=5,u=n.getBBox(),a=Math.round(u.width),f=Math.round(u.height),l=Math.round(u.x)-o,c=Math.round(u.y)-o,h=Math.min(f/2,a/2,10),p={top:"M{x},{y}h{w4},{w4},{w4},{w4}a{r},{r},0,0,1,{r},{r}v{h4},{h4},{h4},{h4}a{r},{r},0,0,1,-{r},{r}l-{right},0-{gap},{gap}-{gap}-{gap}-{left},0a{r},{r},0,0,1-{r}-{r}v-{h4}-{h4}-{h4}-{h4}a{r},{r},0,0,1,{r}-{r}z",bottom:"M{x},{y}l{left},0,{gap}-{gap},{gap},{gap},{right},0a{r},{r},0,0,1,{r},{r}v{h4},{h4},{h4},{h4}a{r},{r},0,0,1,-{r},{r}h-{w4}-{w4}-{w4}-{w4}a{r},{r},0,0,1-{r}-{r}v-{h4}-{h4}-{h4}-{h4}a{r},{r},0,0,1,{r}-{r}z",right:"M{x},{y}h{w4},{w4},{w4},{w4}a{r},{r},0,0,1,{r},{r}v{h4},{h4},{h4},{h4}a{r},{r},0,0,1,-{r},{r}h-{w4}-{w4}-{w4}-{w4}a{r},{r},0,0,1-{r}-{r}l0-{bottom}-{gap}-{gap},{gap}-{gap},0-{top}a{r},{r},0,0,1,{r}-{r}z",left:"M{x},{y}h{w4},{w4},{w4},{w4}a{r},{r},0,0,1,{r},{r}l0,{top},{gap},{gap}-{gap},{gap},0,{bottom}a{r},{r},0,0,1,-{r},{r}h-{w4}-{w4}-{w4}-{w4}a{r},{r},0,0,1-{r}-{r}v-{h4}-{h4}-{h4}-{h4}a{r},{r},0,0,1,{r}-{r}z"},d={hx0:e-(l+o+a-h*2),hx1:e-(l+o+a/2-h),hx2:e-(l+o+h),vhy:t-(c+o+f+o+h),"^hy":t-(c-h)},v=[{x:l+o,y:c,w:a,w4:a/4,h4:f/4,right:0,left:a-h*2,bottom:0,top:f-h*2,r:o,h:f,gap:h},{x:l+o,y:c,w:a,w4:a/4,h4:f/4,left:a/2-h,right:a/2-h,top:f/2-h,bottom:f/2-h,r:o,h:f,gap:h},{x:l+o,y:c,w:a,w4:a/4,h4:f/4,left:0,right:a-h*2,top:0,bottom:f-h*2,r:o,h:f,gap:h}][i[1]=="middle"?1:(i[1]=="top"||i[1]=="left")*2];var m=0,g=0,y=this.path(r(p[i[0]],v)).insertBefore(n);switch(i[0]){case"top":m=e-(l+o+v.left+h);g=t-(c+o+f+o+h);break;case"bottom":m=e-(l+o+v.left+h);g=t-(c-h);break;case"left":m=e-(l+o+a+o+h);g=t-(c+o+v.top+h);break;case"right":m=e-(l-h);g=t-(c+o+v.top+h);break}y.translate(m,g);if(s){s=y.attr("path");y.remove();return{path:s,dx:m,dy:g}}n.translate(m,g);return y}})()
<|endoftext|>


<commit_before>(function(w) {
  $(function() {
    $('.js-toggle-button').on('click', function(e) {
      e.preventDefault();
      $(this)
        .find('.fa')
          .toggleClass('fa-chevron-down fa-chevron-up')
        .end()
        .closest('.js-toggle-container')
          .find('.js-toggle-content')
            .toggle()
      ;
    });

    // If we're accessing a permalink, ensure it is not inside a
    // closed js-toggle-container!
    var hash = w.gl.utils.getLocationHash();
    var anchor = hash && document.getElementById(hash);
    var container = anchor && $(anchor).closest('.js-toggle-container');

    if (container && container.find('.js-toggle-content').is(':hidden')) {
      container.find('.js-toggle-button').trigger('click');
      anchor.scrollIntoView();
    }
  });
})(window);
<commit_msg>Update toggler_behavior.js to toggle ajax loaded contents like `diffs` page.<commit_after>(function(w) {
  $(function() {
    $('body').on('click', '.js-toggle-button', function(e) {
      e.preventDefault();
      $(this)
        .find('.fa')
          .toggleClass('fa-chevron-down fa-chevron-up')
        .end()
        .closest('.js-toggle-container')
          .find('.js-toggle-content')
            .toggle()
      ;
    });

    // If we're accessing a permalink, ensure it is not inside a
    // closed js-toggle-container!
    var hash = w.gl.utils.getLocationHash();
    var anchor = hash && document.getElementById(hash);
    var container = anchor && $(anchor).closest('.js-toggle-container');

    if (container && container.find('.js-toggle-content').is(':hidden')) {
      container.find('.js-toggle-button').trigger('click');
      anchor.scrollIntoView();
    }
  });
})(window);
<|endoftext|>


<commit_before>define([
  'Class', 'backbone', 'underscore', 'handlebars', 'mps',
  'helpers/languagesHelper',
  'models/UserModel',
  'text!connect/templates/userForm.handlebars'
], function(Class, Backbone, _, Handlebars, mps, languagesHelper, User, tpl) {

  'use strict';

  var UserFormValidator = Class.extend({
    validations: {
      email: {
        message: 'Please enter your email',
        validator: function(user) {
          return ! _.isEmpty(user.get('email'));
        }
      },
      language: {
        message: 'Please enter your language',
        validator: function(user) {
          return ! _.isEmpty(user.get('language'));
        }
      },
      signUpForTesting: {
        message: 'Please enter your email to sign up as an official tester',
        validator: function(user) {
          return !(user.get('signUpForTesting') && _.isEmpty(user.get('email')));
        }
      }
    },

    messages: {},

    isValid: function(user) {
      this.messages = {};

      _.each(this.validations, function(attribute, attributeName) {
        if (attribute.validator(user) === false) {
          this.messages[attributeName] = attribute.message;
        }
      }.bind(this));

      return _.isEmpty(this.messages);
    }
  });

  var UserFormView = Backbone.View.extend({
    className: 'my-gfw-profile-form content-form',

    events: {
      'change select': '_handleSelectChange',
      'change input': '_handleInputChange',
      'keyup input': '_handleInputChange',
      'click #submit': '_submit'
    },

    template: Handlebars.compile(tpl),

    initialize: function(options) {
      options = options || {};
      this.isModal = options.isModal;
      this.user = options.user;
      this.validator = new UserFormValidator();

      if (this.user) {
        this.listenTo(this.user, 'sync', this.render);
        this._checkProfileStatus();
        this.render();
      }
    },

    render: function() {
      var languagesList = languagesHelper.getList();

      this.$el.html(this.template({
        action: window.gfw.config.GFW_API_HOST+'/user',
        redirect: window.location.href,
        user: this.user.toJSON(),
        errors: this.validator.messages,
        isModal: this.isModal,
        languages: languagesList
      }));

      this._renderSelectedOptions();
    },

    _checkProfileStatus: function() {
      var profileComplete = this.user.attributes.profileComplete;
      if (!profileComplete) {
        mps.publish('Notification/open', ['notification-my-gfw-profile-incomplete']);
      }
    },

    _renderSelectedOptions: function() {
      var selectFields = ['sector', 'primaryResponsibilities', 'country', 'howDoYouUse', 'language'],
          attributes = this.user.toJSON();

      selectFields.forEach(function(field) {
        var $select = this.$('select[name="'+field+'"]');

        if (!_.isEmpty(attributes[field])) {
          $select.val(attributes[field]);

          var selected = $select.val() || [],
              $input = $select.siblings('input');
          if (selected.indexOf('Other') > -1) {
            $input.show();
            $input.val(_.last(attributes[field]));
          } else {
            $input.hide();
            $input.val('');
          }
        }
      }.bind(this));

      this.$('input[name="signUpForTesting"][value="'+(attributes.signUpForTesting===true).toString()+'"]').
        prop('checked', true);
    },

    _enableSubmit: function() {
      this.$('#submit').removeClass('disabled');
    },

    _handleInputChange: function(event) {
      this._enableSubmit();
    },

    _handleSelectChange: function(event) {
      this._enableSubmit();

      var $el = this.$(event.currentTarget),
          $input = $el.siblings('input');

      var selected = $el.val() || [];
      if (selected.indexOf('Other') > -1) {
        $input.show();
      } else {
        $input.hide();
        $input.val('');
      }
    },

    _submit: function() {
      var $submitButton = this.$('#submit');

      if ($submitButton.hasClass('disabled')) { return; }

      $submitButton.hide();
      this.$('.spinner3').show();

      var formValues = this.$('form').
        serializeArray().
        reduce(function(prev, curr) {
          if (prev[curr.name] !== undefined) {
            prev[curr.name] = [].concat(prev[curr.name]);
            prev[curr.name].push(curr.value);
          } else {
            prev[curr.name] = curr.value;
          }
          return prev;
        }, {});

      this.user.set(formValues);
      if (this.validator.isValid(this.user)) {
        formValues.profileComplete = true;
        this.user.set({
          profileComplete: true
        });
        this.user.save(formValues, {patch: true}).then(this._redirect.bind(this));
      } else {
        this.render();
        mps.publish('Notification/open', ['notification-my-gfw-profile-errors']);
        window.location.hash = 'user-profile';
      }
    },

    _redirect: function() {
      var match,
        pl     = /\+/g,
        search = /([^&=]+)=?([^&]*)/g,
        decode = function (s) { return decodeURIComponent(s.replace(pl, ' ')); },
        query  = window.location.search.substring(1),
        urlParams = {};

      while((match = search.exec(query)) !== null) {
        urlParams[decode(match[1])] = decode(match[2]);
      }

      if (urlParams.redirect !== undefined) {
        window.location.href = urlParams.redirect;
      } else {
        mps.publish('Notification/open', ['notification-my-gfw-profile-saved']);
        this.trigger('saved');
        window.scrollTo(0,0);
      }
    }
  });

  return UserFormView;

});
<commit_msg>User form submit with user validation<commit_after>define([
  'Class', 'backbone', 'underscore', 'handlebars', 'mps',
  'helpers/languagesHelper',
  'models/UserModel',
  'text!connect/templates/userForm.handlebars'
], function(Class, Backbone, _, Handlebars, mps, languagesHelper, User, tpl) {

  'use strict';

  var UserFormValidator = Class.extend({
    validations: {
      email: {
        message: 'Please enter your email',
        validator: function(user) {
          return ! _.isEmpty(user.get('email'));
        }
      },
      language: {
        message: 'Please enter your language',
        validator: function(user) {
          return ! _.isEmpty(user.get('language'));
        }
      },
      signUpForTesting: {
        message: 'Please enter your email to sign up as an official tester',
        validator: function(user) {
          return !(user.get('signUpForTesting') && _.isEmpty(user.get('email')));
        }
      }
    },

    messages: {},

    isValid: function(user) {
      this.messages = {};

      _.each(this.validations, function(attribute, attributeName) {
        if (attribute.validator(user) === false) {
          this.messages[attributeName] = attribute.message;
        }
      }.bind(this));

      return _.isEmpty(this.messages);
    }
  });

  var UserFormView = Backbone.View.extend({
    className: 'my-gfw-profile-form content-form',

    events: {
      'change select': '_handleSelectChange',
      'change input': '_handleInputChange',
      'keyup input': '_handleInputChange',
      'click #submit': '_submit'
    },

    template: Handlebars.compile(tpl),

    initialize: function(options) {
      options = options || {};
      this.isModal = options.isModal;
      this.user = options.user;
      this.validator = new UserFormValidator();

      if (this.user) {
        this.listenTo(this.user, 'sync', this.render);
        this._checkProfileStatus();
        this.render();
      }
    },

    render: function() {
      var languagesList = languagesHelper.getList();

      this.$el.html(this.template({
        action: window.gfw.config.GFW_API_HOST+'/user',
        redirect: window.location.href,
        user: this.user.toJSON(),
        errors: this.validator.messages,
        isModal: this.isModal,
        languages: languagesList
      }));

      this._renderSelectedOptions();
    },

    _checkProfileStatus: function() {
      var profileComplete = this.user.attributes.profileComplete;
      if (!profileComplete) {
        mps.publish('Notification/open', ['notification-my-gfw-profile-incomplete']);
      }
    },

    _renderSelectedOptions: function() {
      var selectFields = ['sector', 'primaryResponsibilities', 'country', 'howDoYouUse', 'language'],
          attributes = this.user.toJSON();

      selectFields.forEach(function(field) {
        var $select = this.$('select[name="'+field+'"]');

        if (!_.isEmpty(attributes[field])) {
          $select.val(attributes[field]);

          var selected = $select.val() || [],
              $input = $select.siblings('input');
          if (selected.indexOf('Other') > -1) {
            $input.show();
            $input.val(_.last(attributes[field]));
          } else {
            $input.hide();
            $input.val('');
          }
        }
      }.bind(this));

      this.$('input[name="signUpForTesting"][value="'+(attributes.signUpForTesting===true).toString()+'"]').
        prop('checked', true);
    },

    _enableSubmit: function() {
      this.$('#submit').removeClass('disabled');
    },

    _handleInputChange: function(event) {
      this._enableSubmit();
    },

    _handleSelectChange: function(event) {
      this._enableSubmit();

      var $el = this.$(event.currentTarget),
          $input = $el.siblings('input');

      var selected = $el.val() || [];
      if (selected.indexOf('Other') > -1) {
        $input.show();
      } else {
        $input.hide();
        $input.val('');
      }
    },

    _submit: function() {
      this.user.checkLogged()
        .then(function(response) {
          var $submitButton = this.$('#submit');

          if ($submitButton.hasClass('disabled')) { return; }

          $submitButton.hide();
          this.$('.spinner3').show();

          var formValues = this.$('form').
          serializeArray().
          reduce(function(prev, curr) {
            if (prev[curr.name] !== undefined) {
              prev[curr.name] = [].concat(prev[curr.name]);
              prev[curr.name].push(curr.value);
            } else {
              prev[curr.name] = curr.value;
            }
            return prev;
          }, {});

          this.user.set(formValues);
          if (this.validator.isValid(this.user)) {
            formValues.profileComplete = true;
            this.user.set({
              profileComplete: true
            });
            this.user.save(formValues, {patch: true}).then(this._redirect.bind(this));
          } else {
            this.render();
            mps.publish('Notification/open', ['notification-my-gfw-profile-errors']);
            window.location.hash = 'user-profile';
          }
        }.bind(this))
        .catch(function(e) {
          mps.publish('Notification/open', ['notification-my-gfw-not-logged']);
        }.bind(this));
    },

    _redirect: function() {
      var match,
        pl     = /\+/g,
        search = /([^&=]+)=?([^&]*)/g,
        decode = function (s) { return decodeURIComponent(s.replace(pl, ' ')); },
        query  = window.location.search.substring(1),
        urlParams = {};

      while((match = search.exec(query)) !== null) {
        urlParams[decode(match[1])] = decode(match[2]);
      }

      if (urlParams.redirect !== undefined) {
        window.location.href = urlParams.redirect;
      } else {
        mps.publish('Notification/open', ['notification-my-gfw-profile-saved']);
        this.trigger('saved');
        window.scrollTo(0,0);
      }
    }
  });

  return UserFormView;

});
<|endoftext|>


<commit_before>/*global $,cyberDojo*/

var cyberDojo = (function(cd, $) {
  "use strict";

  var getAllMatches = function(string,regex) {
    var matches = [];
    var match;
    while (match = regex.exec(string)) {
      matches.push(match);
    }
    return matches;
  };

  //- - - - - - - - - - - - - - - - - - - -

  var regexFor = function(s) {
    var filenamePattern = '([a-zA-Z0-9_\.]*)';
    var lineNumberPattern = '([0-9]*)';
    s = s.replace('{F}',filenamePattern);
    s = s.replace('{L}',lineNumberPattern);
    return new RegExp(s, 'g');
  };

  //- - - - - - - - - - - - - - - - - - - -

  var getFilenameLineNumberRegexSpec = function(colour) {
    var spec;
    if (colour === 'amber') {
      spec = cd.amberGotoLineSpec();
    }
    if (colour === 'red') {
      spec = cd.redGotoLineSpec();
    }
    var regex = regexFor(spec[0]);
    var fileIndex = spec[1];
    var lineIndex = spec[2];
    return [regex, fileIndex, lineIndex];
  };

  //- - - - - - - - - - - - - - - - - - - -

  var getFileLocations = function(string,spec) {
    var regex = spec[0];
    return getAllMatches(string,regex).map(function(match) {
      var filename = match[spec[1]];
      var lineNumber = match[spec[2]];
      return [filename,lineNumber];
    });
  };

  //- - - - - - - - - - - - - - - - - - - -

  var getTopMatch = function(colour) {
    var specs = getFilenameLineNumberRegexSpec(colour);

    var output = cd.fileContentFor('output').val();
    var matches = getFileLocations(output, specs);

    //TODO: use first filename that names a visible file
    return matches[0];
  };

  //- - - - - - - - - - - - - - - - - - - -

  var blink = function(line) {
    var twoSeconds = 2000;
    // Colors are hard-wired. Saving them
    // and then resetting can fail with
    // rapid repeated Alt+g keystrokes.
    var color      = '#BFBFBF';
    var background = '#7A7A7A';

    line.css('color', background);
    line.css('background', color);
    setTimeout(function() {
      line.css('color', color);
      line.css('background', background);
    }, twoSeconds);
  };

  //- - - - - - - - - - - - - - - - - - - -

  cd.scrollToError = function() {

    //return; // work in progress

    var colour = cd.currentTrafficLightColour();
    if (colour != 'amber' && colour != 'red') {
        return;
    }
    var topMatch = getTopMatch(colour);

    var filename = topMatch[0];
    var lineNumber = topMatch[1];
    var content = cd.fileContentFor(filename);
    var numbers = cd.lineNumbersFor(filename);
    var line = $('#' + lineNumber, numbers);
    var downFromTop = 150;
    var instantly = 0;

    cd.loadFile(filename);

    // line-numbers is not directly scrollable but animate works!
    numbers.animate({
      // scroll the line-number into view
      scrollTop: '+=' + (line.offset().top - downFromTop) + 'px'
      }, instantly, function() {
        // align the content to the line-number
        content.scrollTop(numbers.scrollTop());
        blink(line);
      });
  };

  return cd;
})(cyberDojo || {}, $);
<commit_msg>auto goto-line number save point<commit_after>/*global $,cyberDojo*/

var cyberDojo = (function(cd, $) {
  "use strict";

  var getAllMatches = function(string,regex) {
    var matches = [];
    var match;
    while (match = regex.exec(string)) {
      matches.push(match);
    }
    return matches;
  };

  //- - - - - - - - - - - - - - - - - - - -

  var regexFor = function(s) {
    var filenamePattern = '([a-zA-Z0-9_\.]*)';
    var lineNumberPattern = '([0-9]*)';
    s = s.replace('{F}',filenamePattern);
    s = s.replace('{L}',lineNumberPattern);
    return new RegExp(s, 'g');
  };

  //- - - - - - - - - - - - - - - - - - - -

  var getFileLocations = function(string,spec) {
    var regex = regexFor(spec[0]);
    var fileIndex = spec[1];
    var lineIndex = spec[2];
    return getAllMatches(string,regex).map(function(match) {
      var filename = match[fileIndex];
      var lineNumber = match[lineIndex];
      return [filename,lineNumber];
    });
  };

  //- - - - - - - - - - - - - - - - - - - -

  var getTopMatch = function(spec) {
    var output = cd.fileContentFor('output').val();
    var matches = getFileLocations(output, spec);
    //TODO: use first filename that names a visible file
    return matches[0];
  };

  //- - - - - - - - - - - - - - - - - - - -

  var blink = function(line) {
    var twoSeconds = 2000;
    // Colors are hard-wired. Saving them
    // and then resetting can fail with
    // rapid repeated Alt+g keystrokes.
    var color      = '#BFBFBF';
    var background = '#7A7A7A';

    line.css('color', background);
    line.css('background', color);
    setTimeout(function() {
      line.css('color', color);
      line.css('background', background);
    }, twoSeconds);
  };

  //- - - - - - - - - - - - - - - - - - - -

  var getFilenameLineNumberRegexSpec = function(colour) {
    var spec;
    if (colour === 'amber') {
      spec = cd.amberGotoLineSpec();
    }
    if (colour === 'red') {
      spec = cd.redGotoLineSpec();
    }
    return spec;
  };

  //- - - - - - - - - - - - - - - - - - - -

  cd.scrollToError = function() {

    //return; // work in progress

    var colour = cd.currentTrafficLightColour();
    if (colour != 'amber' && colour != 'red') {
        return;
    }
    var spec = getFilenameLineNumberRegexSpec(colour);
    var topMatch = getTopMatch(spec);

    var filename = topMatch[0];
    var content = cd.fileContentFor(filename);

    var lineNumber = topMatch[1];
    var numbers = cd.lineNumbersFor(filename);

    var line = $('#' + lineNumber, numbers);
    var downFromTop = 150;
    var instantly = 0;

    cd.loadFile(filename);

    // line-numbers is not directly scrollable but animate works!
    numbers.animate({
      // scroll the line-number into view
      scrollTop: '+=' + (line.offset().top - downFromTop) + 'px'
      }, instantly, function() {
        // align the content to the line-number
        content.scrollTop(numbers.scrollTop());
        blink(line);
      });
  };

  return cd;
})(cyberDojo || {}, $);
<|endoftext|>


<commit_before>(function () {
  "use strict"
  var root = this,
      $ = root.jQuery;
  if(typeof root.GOVUK === 'undefined') { root.GOVUK = {}; }

  var filter = {
    searchTimeout: 300,

    ignoreKeyCodes: [16, 17, 18, 91, 93],
    $form: null,
    submitTimeoutId: null,
    lastQuery: null,

    initialize: function () {
      $.ajaxSetup({ cache: false });
      this.$form = $(".js-filter-form");
      this.$form.on( "submit", $.proxy(this.formSubmitted, this) );
      this.$form.find("#search_name").on( "keyup change search", $.proxy(this.keyUpped, this) );
      this.$form.find("#search_contact_group_id").on( "change", $.proxy(this.submit, this) );
    },

    keyUpped: function (e) {
      if (e.keyCode === 13) {
        return this.submit();  // if user presses enter, submit form
      }
      if (this.ignoreKeyCodes.indexOf(e.keyCode) === -1 && this.queryChanged(e.target.value)) {
        this.scheduleForSubmit();
      }
    },

    queryChanged: function (newQuery) {
      if (this.lastQuery != newQuery) {
        this.lastQuery = newQuery;
        return true;
      }
      return false;
    },

    formSubmitted: function (e) {
      this.pushState(e.target);
      $("#working_on_filter_contacts").removeClass("hidden");
    },

    pushState: function (form) {
      if (history.pushState) {
        var path = location.pathname + "?" + $(form).serialize();
        history.pushState(null, null, path);
      }
    },

    scheduleForSubmit: function () {
      this.clearCurrentTimeout();
      this.submitTimeoutId = setTimeout( $.proxy(this.submit, this), this.searchTimeout );
    },

    clearCurrentTimeout: function () {
      if (this.submitTimeoutId) {
        clearTimeout(this.submitTimeoutId);
        this.submitTimeoutId = null;
      }
    },

    submit: function () {
      this.submitTimeoutId = null;
      this.$form.submit();
    }
  };

  root.GOVUK.filterListItems = filter;
}).call(this);
<commit_msg>persist form data on browsers' pushState<commit_after>(function () {
  "use strict"
  var root = this,
      $ = root.jQuery;
  if(typeof root.GOVUK === 'undefined') { root.GOVUK = {}; }

  var filter = {
    searchTimeout: 300,

    ignoreKeyCodes: [16, 17, 18, 91, 93],
    $form: null,
    submitTimeoutId: null,
    lastQuery: null,

    initialize: function () {
      $.ajaxSetup({ cache: false });
      this.$form = $(".js-filter-form");
      this.$form.on( "submit", $.proxy(this.formSubmitted, this) );
      $(window).on( "popstate", $.proxy(this.popStated, this) );
      this.$form.find("#search_name").on( "keyup change search", $.proxy(this.keyUpped, this) );
      this.$form.find("#search_contact_group_id").on( "change", $.proxy(this.submit, this) );
    },

    popStated: function (e) {
      var state = e.originalEvent.state;
      if (state && state.formData) {
        var data, i;
        for (i = 0; i < state.data.length; i++) {
          data = state.data[i];
          this.$form.find("[name='" + data.name + "']").val(data.value);
        }
        this.preventPushingState = true;
        this.submit();
      }
    },

    keyUpped: function (e) {
      if (e.keyCode === 13) {
        return this.submit();  // if user presses enter, submit form
      }
      if (this.ignoreKeyCodes.indexOf(e.keyCode) === -1 && this.queryChanged(e.target.value)) {
        this.scheduleForSubmit();
      }
    },

    queryChanged: function (newQuery) {
      if (this.lastQuery != newQuery) {
        this.lastQuery = newQuery;
        return true;
      }
      return false;
    },

    formSubmitted: function (e) {
      this.pushState(e.target);
      $("#working_on_filter_contacts").removeClass("hidden");
    },

    pushState: function (form) {
      if (history.pushState && !this.preventPushingState) {
        var $form = $(form),
            path = location.pathname + "?" + $form.serialize(),
            formData = { formData: true, data: $form.serializeArray() } ;
        history.pushState(formData, null, path);
      }
      this.preventPushingState = false;
    },

    scheduleForSubmit: function () {
      this.clearCurrentTimeout();
      this.submitTimeoutId = setTimeout( $.proxy(this.submit, this), this.searchTimeout );
    },

    clearCurrentTimeout: function () {
      if (this.submitTimeoutId) {
        clearTimeout(this.submitTimeoutId);
        this.submitTimeoutId = null;
      }
    },

    submit: function () {
      this.submitTimeoutId = null;
      this.$form.submit();
    }
  };

  root.GOVUK.filterListItems = filter;
}).call(this);
<|endoftext|>


<commit_before>/*
 * NOTE: This should only be used for legacy datatables.
 * All future datatables should be generated using data-table-* HTML attributes.
 * See app/views/manage/application/_questionnaire_datatable.html.haml for an example.
 */

var setupDataTables = function () {
  $('.datatable.checkins').DataTable({
    order: [1, 'asc'],
    columns: [{
        orderable: true,
        data: 'first_name',
        visible: false
      },
      {
        orderable: true,
        data: 'last_name',
        visible: false
      },
      {
        orderable: false,
        data: 'about'
      },
      {
        orderable: true,
        data: 'checked_in'
      },
      {
        orderable: false,
        data: 'actions'
      },
    ],
  });

  $('.datatable.users').DataTable({
    order: [1, 'asc'],
    columns: [{
        orderable: true,
        data: 'id',
        visible: false
      },
      {
        orderable: true,
        data: 'email'
      },
      {
        orderable: true,
        data: 'role'
      },
      {
        orderable: true,
        data: 'active'
      },
      {
        orderable: true,
        data: 'receive_weekly_report'
      },
      {
        orderable: true,
        data: 'created_at'
      },
      {
        orderable: true,
        data: 'current_sign_in_at'
      },
      {
        orderable: true,
        data: 'last_sign_in_at'
      },
      {
        orderable: true,
        data: 'current_sign_in_ip'
      },
      {
        orderable: true,
        data: 'last_sign_in_ip'
      },
      {
        orderable: true,
        data: 'sign_in_count'
      },
    ],
  });

  $('.datatable.bulk-messages').DataTable({
    order: [4, 'desc'],
    columns: [{
        orderable: true,
        data: 'id',
        visible: false
      },
      {
        orderable: true,
        data: 'name'
      },
      {
        orderable: true,
        data: 'subject'
      },
      {
        orderable: false,
        data: 'status'
      },
      {
        orderable: true,
        data: 'created_at'
      },
      {
        orderable: true,
        data: 'updated_at',
        visible: false
      },
      {
        orderable: true,
        data: 'delivered_at'
      },
    ],
  });

  $('.datatable.schools').DataTable({
    order: [
      [5, 'desc'],
      [4, 'desc']
    ],
    columns: [{
        orderable: true,
        data: 'id',
        visible: false
      },
      {
        orderable: true,
        data: 'name'
      },
      {
        orderable: true,
        data: 'city'
      },
      {
        orderable: true,
        data: 'state'
      },
      {
        orderable: true,
        data: 'questionnaire_count'
      },
      {
        orderable: true,
        data: 'home_school'
      },
    ],
  });

  $('.datatable.stats').DataTable({
    processing: false,
    serverSide: false,
  });
};
<commit_msg>remove the format that my editor applied<commit_after>/*
 * NOTE: This should only be used for legacy datatables.
 * All future datatables should be generated using data-table-* HTML attributes.
 * See app/views/manage/application/_questionnaire_datatable.html.haml for an example.
 */

var setupDataTables = function() {
  $('.datatable.checkins').DataTable({
    order: [1, 'asc'],
    columns: [
      { orderable: true, data: 'first_name', visible: false },
      { orderable: true, data: 'last_name', visible: false },
      { orderable: false, data: 'about' },
      { orderable: true, data: 'checked_in' },
      { orderable: false, data: 'actions' },
    ],
  });

  $('.datatable.users').DataTable({
    order: [1, 'asc'],
    columns: [
      { orderable: true, data: 'id', visible: false },
      { orderable: true, data: 'email' },
      { orderable: true, data: 'role' },
      { orderable: true, data: 'active' },
      { orderable: true, data: 'receive_weekly_report' },
      { orderable: true, data: 'created_at' },
      { orderable: true, data: 'current_sign_in_at' },
      { orderable: true, data: 'last_sign_in_at' },
      { orderable: true, data: 'current_sign_in_ip' },
      { orderable: true, data: 'last_sign_in_ip' },
      { orderable: true, data: 'sign_in_count' },
    ],
  });

  $('.datatable.bulk-messages').DataTable({
    order: [4, 'desc'],
    columns: [
      { orderable: true, data: 'id', visible: false },
      { orderable: true, data: 'name' },
      { orderable: true, data: 'subject' },
      { orderable: false, data: 'status' },
      { orderable: true, data: 'created_at' },
      { orderable: true, data: 'updated_at', visible: false },
      { orderable: true, data: 'delivered_at' },
    ],
  });

  $('.datatable.schools').DataTable({
    order: [[5, 'desc'], [4, 'desc']],
    columns: [
      { orderable: true, data: 'id', visible: false },
      { orderable: true, data: 'name' },
      { orderable: true, data: 'city' },
      { orderable: true, data: 'state' },
      { orderable: true, data: 'questionnaire_count' },
      { orderable: true, data: 'home_school' },
    ],
  });

  $('.datatable.stats').DataTable({
    processing: false,
    serverSide: false,
  });
};
<|endoftext|>


<commit_before>import { result } from 'underscore';
import Component from './Component';

const svgAttrs =
  'xmlns="http://www.w3.org/2000/svg" width="100" viewBox="0 0 24 24" style="fill: rgba(0,0,0,0.15); transform: scale(0.75)"';

export default Component.extend(
  {
    defaults: {
      ...Component.prototype.defaults,
      type: 'image',
      tagName: 'img',
      void: true,
      droppable: 0,
      editable: 1,
      highlightable: 0,
      resizable: { ratioDefault: 1 },
      traits: ['alt'],

      src: `<svg ${svgAttrs}>
        <path d="M8.5 13.5l2.5 3 3.5-4.5 4.5 6H5m16 1V5a2 2 0 0 0-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2z"></path>
      </svg>`,

      // Fallback image in case the src can't be loaded
      // If you use SVG, xmlns="http://www.w3.org/2000/svg" is required
      fallback: `<svg ${svgAttrs}>
        <path d="M2.28 3L1 4.27l2 2V19c0 1.1.9 2 2 2h12.73l2 2L21 21.72 2.28 3m2.55 0L21 19.17V5a2 2 0 0 0-2-2H4.83M8.5 13.5l2.5 3 1-1.25L14.73 18H5l3.5-4.5z"></path>
      </svg>`,

      // File to load asynchronously once the model is rendered
      file: ''
    },

    initialize(o, opt) {
      Component.prototype.initialize.apply(this, arguments);
      const { src } = this.get('attributes');
      if (src) this.set('src', src, { silent: 1 });
    },

    initToolbar(...args) {
      Component.prototype.initToolbar.apply(this, args);
      const em = this.em;

      if (em) {
        var cmd = em.get('Commands');
        var cmdName = 'image-editor';

        // Add Image Editor button only if the default command exists
        if (cmd.has(cmdName)) {
          let hasButtonBool = false;
          var tb = this.get('toolbar');

          for (let i = 0; i < tb.length; i++) {
            if (tb[i].command === 'image-editor') {
              hasButtonBool = true;
              break;
            }
          }

          if (!hasButtonBool) {
            tb.push({
              attributes: { class: 'fa fa-pencil' },
              command: cmdName
            });
            this.set('toolbar', tb);
          }
        }
      }
    },

    /**
     * Returns object of attributes for HTML
     * @return {Object}
     * @private
     */
    getAttrToHTML(...args) {
      const attr = Component.prototype.getAttrToHTML.apply(this, args);
      const src = this.getSrcResult();
      if (src) attr.src = src;
      return attr;
    },

    getSrcResult(opt = {}) {
      const src = this.get(opt.fallback ? 'fallback' : 'src') || '';
      let result = src;

      if (src && src.substr(0, 4) === '<svg') {
        result = `data:image/svg+xml;base64,${window.btoa(src)}`;
      }

      return result;
    },

    isDefaultSrc() {
      return this.get('src') === result(this, 'defaults').src;
    },

    /**
     * Return a shallow copy of the model's attributes for JSON
     * stringification.
     * @return {Object}
     * @private
     */
    toJSON(...args) {
      const obj = Component.prototype.toJSON.apply(this, args);
      if (obj.src === obj.attributes.src) {
        delete obj.src;
      }

      return obj;
    },

    /**
     * Parse uri
     * @param  {string} uri
     * @return {object}
     * @private
     */
    parseUri(uri) {
      var el = document.createElement('a');
      el.href = uri;
      var query = {};
      var qrs = el.search.substring(1).split('&');
      for (var i = 0; i < qrs.length; i++) {
        var pair = qrs[i].split('=');
        var name = decodeURIComponent(pair[0]);
        if (name) query[name] = decodeURIComponent(pair[1]);
      }
      return {
        hostname: el.hostname,
        pathname: el.pathname,
        protocol: el.protocol,
        search: el.search,
        hash: el.hash,
        port: el.port,
        query
      };
    }
  },
  {
    /**
     * Detect if the passed element is a valid component.
     * In case the element is valid an object abstracted
     * from the element will be returned
     * @param {HTMLElement}
     * @return {Object}
     * @private
     */
    isComponent(el) {
      var result = '';
      if (el.tagName == 'IMG') {
        result = { type: 'image' };
      }
      return result;
    }
  }
);
<commit_msg>Fix toJSON of image component<commit_after>import { result } from 'underscore';
import Component from './Component';

const svgAttrs =
  'xmlns="http://www.w3.org/2000/svg" width="100" viewBox="0 0 24 24" style="fill: rgba(0,0,0,0.15); transform: scale(0.75)"';

export default Component.extend(
  {
    defaults: {
      ...Component.prototype.defaults,
      type: 'image',
      tagName: 'img',
      void: true,
      droppable: 0,
      editable: 1,
      highlightable: 0,
      resizable: { ratioDefault: 1 },
      traits: ['alt'],

      src: `<svg ${svgAttrs}>
        <path d="M8.5 13.5l2.5 3 3.5-4.5 4.5 6H5m16 1V5a2 2 0 0 0-2-2H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2z"></path>
      </svg>`,

      // Fallback image in case the src can't be loaded
      // If you use SVG, xmlns="http://www.w3.org/2000/svg" is required
      fallback: `<svg ${svgAttrs}>
        <path d="M2.28 3L1 4.27l2 2V19c0 1.1.9 2 2 2h12.73l2 2L21 21.72 2.28 3m2.55 0L21 19.17V5a2 2 0 0 0-2-2H4.83M8.5 13.5l2.5 3 1-1.25L14.73 18H5l3.5-4.5z"></path>
      </svg>`,

      // File to load asynchronously once the model is rendered
      file: ''
    },

    initialize(o, opt) {
      Component.prototype.initialize.apply(this, arguments);
      const { src } = this.get('attributes');
      if (src) this.set('src', src, { silent: 1 });
    },

    initToolbar(...args) {
      Component.prototype.initToolbar.apply(this, args);
      const em = this.em;

      if (em) {
        var cmd = em.get('Commands');
        var cmdName = 'image-editor';

        // Add Image Editor button only if the default command exists
        if (cmd.has(cmdName)) {
          let hasButtonBool = false;
          var tb = this.get('toolbar');

          for (let i = 0; i < tb.length; i++) {
            if (tb[i].command === 'image-editor') {
              hasButtonBool = true;
              break;
            }
          }

          if (!hasButtonBool) {
            tb.push({
              attributes: { class: 'fa fa-pencil' },
              command: cmdName
            });
            this.set('toolbar', tb);
          }
        }
      }
    },

    /**
     * Returns object of attributes for HTML
     * @return {Object}
     * @private
     */
    getAttrToHTML(...args) {
      const attr = Component.prototype.getAttrToHTML.apply(this, args);
      const src = this.getSrcResult();
      if (src) attr.src = src;
      return attr;
    },

    getSrcResult(opt = {}) {
      const src = this.get(opt.fallback ? 'fallback' : 'src') || '';
      let result = src;

      if (src && src.substr(0, 4) === '<svg') {
        result = `data:image/svg+xml;base64,${window.btoa(src)}`;
      }

      return result;
    },

    isDefaultSrc() {
      return this.get('src') === result(this, 'defaults').src;
    },

    /**
     * Return a shallow copy of the model's attributes for JSON
     * stringification.
     * @return {Object}
     * @private
     */
    toJSON(...args) {
      const obj = Component.prototype.toJSON.apply(this, args);
      if (obj.attributes && obj.src === obj.attributes.src) {
        delete obj.src;
      }

      return obj;
    },

    /**
     * Parse uri
     * @param  {string} uri
     * @return {object}
     * @private
     */
    parseUri(uri) {
      var el = document.createElement('a');
      el.href = uri;
      var query = {};
      var qrs = el.search.substring(1).split('&');
      for (var i = 0; i < qrs.length; i++) {
        var pair = qrs[i].split('=');
        var name = decodeURIComponent(pair[0]);
        if (name) query[name] = decodeURIComponent(pair[1]);
      }
      return {
        hostname: el.hostname,
        pathname: el.pathname,
        protocol: el.protocol,
        search: el.search,
        hash: el.hash,
        port: el.port,
        query
      };
    }
  },
  {
    /**
     * Detect if the passed element is a valid component.
     * In case the element is valid an object abstracted
     * from the element will be returned
     * @param {HTMLElement}
     * @return {Object}
     * @private
     */
    isComponent(el) {
      var result = '';
      if (el.tagName == 'IMG') {
        result = { type: 'image' };
      }
      return result;
    }
  }
);
<|endoftext|>


<commit_before>/*tiles:
	1.  clear
	2.  opaque and impassible
	3.  impassible but not opaque
*/
function Field ()
{
	//transferweights in blender
	this.grid = [[]]; //just a parallel array

	this.mobNames = {};
	this.mode = null;  //either peaceful, story, or combat
	this.factions = {};
	this.activeFaction = 0;//only meaningful in combat
	this.activeMob = 0; //only meaningful in combat
	this.storyScript = [];
	this.activePlayerCharacter = null;

	//a callback to notify PlayfieldGraphic that the active character has changed, so that the camera can be recentered
	this.newActive = function ()
	{

	}
	//add characters
	//character sight
	this.addMob = function (mob)
	{
		mob.noise = this.noise.bind(this);
		if (this.factions[mob.faction]==null)
		{
			this.factions[mob.faction] = [];
		}
		this.factions[mob.faction].push(mob);
		if (this.activePlayerCharacter==null && mob.faction=='player')
			this.activePlayerCharacter = mob;

		mob.field = this;
	};
	this.getMobs = function ()
	{
		var fac = Object.keys(this.factions);
		var output = [];
		for (var c1=0;c1<fac.length;c1++)
		{
			var facname = fac[c1];
			for (var c2=0;c2<this.factions[facname].length;c2++)
			{
				output.push(this.factions[facname][c2]);
			}
		}
		return output;
	}
	this.tileOpaque = function (x,y) //visual
	{
		return x<0||x>=this.grid.length ||
			   y<0||y>=this.grid[0].length ||
			   this.grid[x][y] == 2;
	};
	this.tileImpassable = function (x,y,mob=false) //if mob is set, will only return false if mob knows obstruction
	{
		return this.tileOpaque(x, y) ||
		       this.grid[x][y] == 3 ||
			   this.mobAt(x,y);
	}
	this.mobAt = function (x,y,mob = false) //if mob is set, will only return true if mob knows target mob
	{
		var moblist = this.getMobs();
		for (var c1=0;c1<moblist.length;c1++)
		{
			var thismob = moblist[c1];
			if (thismob.x==x&&thismob.y==y)return thismob;
		}
		return false;
	};
	this.cycleActiveFaction = function ()
	{
		var fact = Object.keys(this.factions);
		this.activeFaction = (this.activeFaction +1) % fact.length;
		var active = this.factions[fact[this.activeFaction]];
		for (var c1=0;c1<active.length;c1++)
		{
			active[c1].refresh();
		}
		active[0].getMove();
		if (this.activeFaction=='player')this.activePlayerCharacter = this.factions['player'][0];
		else this.activePlayerCharacter = null;
	};
	this.mobAnimationComplete = function (mob) //called when a mob finishes animating
	{
		if (this.mode == 'peaceful')
		{
			mob.refresh();
			if (mob.wantsCombat())
			{
				this.modeCombat(mob);
			}
			//call mob.getmove
			mob.getMove();
		}
		else if (this.mode=='story')
		{
			if (this.storyScript.length == 0)
			{
				this.modePeaceful();
			}
			else
			{
				//go to next part of the script
				var event = this.storyScript.shift();
				event.mob.forceMove(event.move);
			}
		}
		else if (this.mode =='combat')
		{
			if (!mob.attackedThisTurn)mob.getMove();
			else
			{
				var faction = this.factions[mob.faction];
				var idleFound = false;
				for (var c1=0;c1<faction.length;c1++)
				{
					if (!faction[c1].attackedThisTurn)  //attackedThisTurn should be true if the mob's turn is over
					{
						if(mob.faction=='player')this.activePlayerCharacter=mob;
						faction[c1].getMove();
						idleFound = true;
						break;
					}
				}
				if (!idleFound)this.cycleActiveFaction();
			}
		}
	};
	this.tileVisible = function (mob,targetX,targetY)
	{
		var xDiff = targetX-mob.x;
		var yDiff = targetY-mob.y;

		var circle = Math.PI * 2;
		var angle = (Math.atan2(yDiff,xDiff) + circle) % circle;
		var distance = Math.sqrt(Math.pow(xDiff,2) + Math.pow(yDiff,2));
		if (distance > mob.visionRange)return false; //target too far away to be seen

		if (Math.abs(mob.facingAngle() - angle) > mob.fieldOfView/2 && Math.abs(mob.facingAngle() + circle - angle) >mob.fieldOfView/2)return false; //target outside field of view

		//check obstructions
		if (xDiff == 0)
		{
			var ystart = Math.min(mob.y,targetY);
			for (var ycount=0;ycount<yDiff;ycount++)
			{
				if (this.tileOpaque(targetX,ystart+ycount))
					return false; //obstruction in the way of target
			}
		}
		else
		{
			var slope = yDiff/xDiff;
			var yIntercept = targetY - (targetX * slope);

			//y = (slope * x) + yIntercept
			var xstart = Math.min(mob.x,targetX);
			for (var xcount=0;xcount<Math.abs(xDiff);xcount++)
			{
				var matchingY = (slope * (xstart+xcount)) + yIntercept;
				if (this.tileOpaque(xstart+xcount,Math.floor(matchingY)))return false;
				if (this.tileOpaque(xstart+xcount,Math.round(matchingY)))return false; //todo, if .0 check both
				//if (matchingY == Math.floor(matchingY) && this.tileOpaque(xstart+xcount,matchingY-1))return false; //if the line hits a corner, check both?
			}
			//x = (y-yIntercept)/slope
			var ystart = Math.min(mob.y,targetY);
			for (var ycount=0;ycount<Math.abs(yDiff);ycount++)
			{
				var matchingX = (ystart+ycount - yIntercept) / slope;
				if (this.tileOpaque(Math.floor(matchingX),ystart+ycount))return false;
				if (this.tileOpaque(Math.round(matchingX),ystart+ycount))return false;

			}
		}
		return true;
	};
	this.getVisibleTiles = function (mob) //used for player vision
	{
		var output = [];
		//ray cast every tile within range
		for (var xcount = mob.x - mob.visionRange/2;xcount<mob.x+mob.visionRange;xcount++)
		{
			for (var ycount=mob.y - mob.visionRange/2;ycount<mob.y+mob.visionRange;ycount++)
			{
				if (this.tileVisible(mob,xcount,ycount))output.push([xcount,ycount]);
			}
		}
		return output;
	};
	this.getVisibleMobs = function (character)  //used for ai vision (and player vision)
	{
		var output = [];
		var mobs = this.getMobs();
		for (var c1=0;c1<mobs.length;c1++)
		{
			if (this.tileVisible(character,mobs[c1].x,mobs[c1].y))output.push(mobs[c1]);
		}
		return output;
	};
	this.noise = function (maker,volume) //alerts other mobs to the presence of "maker"
	{
		var mobs = this.getMobs();
		for (var c1=0,len=mobs.length;c1<len;c1++)
		{
			var mob = mobs[c1]
			if (volume >= Math.round(Math.sqrt(Math.pow(mob.x-maker.x,2)+Math.pow(mob.y-maker.y))))
				this.mobInform(mob,maker);
		}
		//todo: add a record so it can be recorded for playfieldgraphic
	};
	this.mobLook = function (mob)
	{
		var visibleMobs = this.getVisibleMobs(mob);
		for (var c1=0,len=visibleMobs.length;c1<len;c1++)
		{
			this.mobInform(mob,visibleMobs[c1]);
		}
	};

	this.refreshVision = function ()
	{
		var mobs = this.getMobs();
		for (var c1=0,len=mobs.length;c1<len;c1++)
		{
			this.mobLook(mobs[c1]);
		}
	};
	this.mobInform = function (mob, target)
	{
		//if in peaceful mode, check if target is hostile
		if (this.mode=='peaceful' && mob.perceiveMob(mob))
		{
			this.modeCombat(mob);//this will clear all mob perception
			this.mobLook(mob);
			this.mobInform(mob,target); //make sure the mob remembers what started the fight
			mob.getMove();
		}
	};
	this.clearMobPerception = function ()
	{
		var mobs = this.getMobs();
		for (var c1=0,len=mobs.length;c1<len;c1++)
		{
			mobs[c1].knownMobs={};
			this.mobLook(mobs[c1]);
		}
	};
	this.modePeaceful = function ()
	{
		if (this.mode=='peaceful')return; //prevent recursion
		this.activePlayerCharacter = this.factions['player'][0];
		this.mode='peaceful';
		var mobs = this.getMobs();
		for (var c1=0;c1<mobs.length;c1++)
		{
			mobs[c1].getMove();
		}
	};
	this.modeCombat = function (activeMob)
	{
		if (this.mode=='combat')
			return; //prevent recursion

		this.mode = 'combat';
		this.clearMobPerception();

		if (activeMob == null)this.cycleActiveFaction();
		else
		{
			var fact = Object.keys(this.factions);
			for (var c1=0;c1<fact.length;c1++)
			{
				if (activeMob.faction == fact[c1])
				{
					this.activeFaction = c1;
					break;
				}
			}
			var active = this.factions[activeMob.faction];
			for (var c1=0;c1<active.length;c1++)
			{
				active[c1].refresh();
			}
		}
	};
	this.modeStory = function (script)
	{
		if (this.mode=='story')
			return; //prevent recursion

		this.mode = 'story';
		this.activePlayerCharacter = null;
		this.storyScript = script;
		var event = this.storyScript.shift();
		event.mob.forceMove(event.move,event.text);
	};
	this.astar = function (start,end) //returns an array representing a path between start and end, or false on failure
	{
		if (this.tileImpassable(end[0],end[1]))
			return false;

		//record room costs as {'cost':,'prev':,'coords'}
		var rooms = [[]];
		var toSearch = [];

		var addToSearch = function (thisRoom)
		{
			for (var c1=0;c1<toSearch.length;c1++)
			{
				if (toSearch[c1].dist > thisRoom.dist)
				{
					//insert it
					toSearch.splice(c1,0,thisRoom);
					return;
				}
			}
			toSearch.push(thisRoom);
		};
		var checkRoom = function (thisRoom)//returns true if path found
		{
			//get all exits and record them in rooms
			var potentialExits = [[-1,-1],[-1,0],[-1,1],[0,-1],[0,1],[1,-1],[1,0],[1,1]];
			for (var c1=0;c1<potentialExits.length;c1++)
			{
				var exit = [potentialExits[c1][0]+thisRoom.coords[0],potentialExits[c1][1]+thisRoom.coords[1]];
				//if exit is impassible, continue
				if (this.tileImpassable(exit[0],exit[1]))continue;
				//if the room is already known
				if (rooms[exit[0]] && rooms[exit[0]][exit[1]])
				{
					var nextRoom = rooms[exit[0]][exit[1]];
					if (nextRoom.cost > thisRoom.cost+1)
					{
						nextRoom.cost = thisRoom.cost+1;
						nextRoom.prev = thisRoom.coords;
					}
					else continue; //have already found a shorter path
				}
				else //if it's a new room
				{
					//if it's the end, record the path
					if (!rooms[exit[0]])rooms[exit[0]] = [];
					var newRoom =
					{
						'cost:' : thisRoom.cost+1,
						'prev' :thisRoom.coords,
						'coords' :[exit[0],exit[1]],
						'dist' : Math.sqrt(Math.pow(end[0]-exit[0],2) + Math.pow(end[1]-exit[1],2))
					};
					if (!rooms[exit[0]])rooms[exit[0]] =[];
					rooms[exit[0]][exit[1]] = newRoom;
					addToSearch(newRoom);
					//if it's the end, record the path
					if (exit[0] == end[0] && exit[1]==end[1])return true;
				}
			}
			return false;
		}.bind(this);

		var firstRoom =
		{
			'cost:' : 0,
			'prev' : [0,0],
			'coords' :[start[0],start[1]],
			'dist' : Math.sqrt(Math.pow(end[0]-start[0],2) + Math.pow(end[1]-start[1],2))
		};
		rooms[start[0]] = [];
		rooms[start[0]][start[1]] = firstRoom;
		addToSearch(firstRoom);

		while (toSearch.length>0)
		{
			if (checkRoom(toSearch.shift()))
			{
				//trace the path and return it
				var finalPath = [];
				var troom = end;
				do
				{
					finalPath.unshift(troom);
					troom = rooms[troom[0]][troom[1]].prev;
				}
				while (troom[0] != start[0] || troom[1]!=start[1]);
				return finalPath;
			}
		}
		return false;//path not found
	};
	this.draw = function ()
	{
		return this.tileset.drawField(this);
	};
}
//receive move- callback function passed from mobs

//begin turn- prompts all mobs to move
<commit_msg>added pythagoean method, various visibility refactors<commit_after>/*tiles:
	1.  clear
	2.  opaque and impassible
	3.  impassible but not opaque
*/
function Field ()
{
	//transferweights in blender
	this.grid = [[]]; //just a parallel array

	this.mobNames = {};
	this.mode = null;  //either peaceful, story, or combat
	this.factions = {};
	this.activeFaction = 0;//only meaningful in combat
	this.activeMob = 0; //only meaningful in combat
	this.storyScript = [];
	this.activePlayerCharacter = null;

	//a callback to notify PlayfieldGraphic that the active character has changed, so that the camera can be recentered
	this.newActive = function ()
	{

	}
	//add characters
	//character sight
	this.pythag = function(a, b)
	{
		return Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
	};

	this.addMob = function (mob)
	{
		mob.noise = this.noise.bind(this);
		if (this.factions[mob.faction]==null)
		{
			this.factions[mob.faction] = [];
		}
		this.factions[mob.faction].push(mob);
		if (this.activePlayerCharacter==null && mob.faction=='player')
			this.activePlayerCharacter = mob;

		mob.field = this;
	};
	this.getMobs = function ()
	{
		var fac = Object.keys(this.factions);
		var output = [];
		for (var c1=0;c1<fac.length;c1++)
		{
			var facname = fac[c1];
			for (var c2=0;c2<this.factions[facname].length;c2++)
			{
				output.push(this.factions[facname][c2]);
			}
		}
		return output;
	}
	this.tileOpaque = function (x,y) //visual
	{
		return x<0||x>=this.grid.length ||
			   y<0||y>=this.grid[0].length ||
			   this.grid[x][y] == 2;
	};
	this.tileImpassable = function (x,y,mob=false) //if mob is set, will only return false if mob knows obstruction
	{
		return this.tileOpaque(x, y) ||
		       this.grid[x][y] == 3 ||
			   this.mobAt(x,y);
	}
	this.mobAt = function (x,y,mob = false) //if mob is set, will only return true if mob knows target mob
	{
		var moblist = this.getMobs();
		for (var c1=0;c1<moblist.length;c1++)
		{
			var thismob = moblist[c1];
			if (thismob.x==x&&thismob.y==y)return thismob;
		}
		return false;
	};
	this.cycleActiveFaction = function ()
	{
		var fact = Object.keys(this.factions);
		this.activeFaction = (this.activeFaction +1) % fact.length;
		var active = this.factions[fact[this.activeFaction]];
		for (var c1=0;c1<active.length;c1++)
		{
			active[c1].refresh();
		}
		active[0].getMove();
		if (this.activeFaction=='player')this.activePlayerCharacter = this.factions['player'][0];
		else this.activePlayerCharacter = null;
	};
	this.mobAnimationComplete = function (mob) //called when a mob finishes animating
	{
		if (this.mode == 'peaceful')
		{
			mob.refresh();
			if (mob.wantsCombat())
			{
				this.modeCombat(mob);
			}
			//call mob.getmove
			mob.getMove();
		}
		else if (this.mode=='story')
		{
			if (this.storyScript.length == 0)
			{
				this.modePeaceful();
			}
			else
			{
				//go to next part of the script
				var event = this.storyScript.shift();
				event.mob.forceMove(event.move);
			}
		}
		else if (this.mode =='combat')
		{
			if (!mob.attackedThisTurn)mob.getMove();
			else
			{
				var faction = this.factions[mob.faction];
				var idleFound = false;
				for (var c1=0;c1<faction.length;c1++)
				{
					if (!faction[c1].attackedThisTurn)  //attackedThisTurn should be true if the mob's turn is over
					{
						if(mob.faction=='player')this.activePlayerCharacter=mob;
						faction[c1].getMove();
						idleFound = true;
						break;
					}
				}
				if (!idleFound)this.cycleActiveFaction();
			}
		}
	};
	this.tileVisible = function (mob,targetX,targetY)
	{
		// What to do here when targets are not set?
		var xDiff = targetX-mob.x;
		var yDiff = targetY-mob.y;

		var distance = this.pythag(xDiff, yDiff);
		if (distance > mob.visionRange)
			return false; //target too far away to be seen

		var circle = Math.PI * 2;
		var angle = (Math.atan2(yDiff,xDiff) + circle) % circle;
		var fov = mob.fieldOfView / 2;
		var cw = Math.abs(mob.facingAngle() - angle);
		var ccw = circle + cw;

		if (cw > fov && ccw > fov)
			return false; //target outside field of view

		//check obstructions
		if (xDiff == 0)
		{
			var ystart = Math.min(mob.y,targetY);
			for (var ycount=0;ycount<yDiff;ycount++)
			{
				if (this.tileOpaque(targetX,ystart+ycount))
					return false; //obstruction in the way of target
			}
		}
		else
		{
			var slope = yDiff/xDiff;
			var yIntercept = targetY - (targetX * slope);

			//y = (slope * x) + yIntercept
			var xstart = Math.min(mob.x,targetX);
			for (var xcount=0;xcount<Math.abs(xDiff);xcount++)
			{
				var matchingY = (slope * (xstart+xcount)) + yIntercept;
				if (this.tileOpaque(xstart+xcount,Math.floor(matchingY)))return false;
				if (this.tileOpaque(xstart+xcount,Math.round(matchingY)))return false; //todo, if .0 check both
				//if (matchingY == Math.floor(matchingY) && this.tileOpaque(xstart+xcount,matchingY-1))return false; //if the line hits a corner, check both?
			}
			//x = (y-yIntercept)/slope
			var ystart = Math.min(mob.y,targetY);
			for (var ycount=0;ycount<Math.abs(yDiff);ycount++)
			{
				var matchingX = (ystart+ycount - yIntercept) / slope;
				if (this.tileOpaque(Math.floor(matchingX),ystart+ycount))return false;
				if (this.tileOpaque(Math.round(matchingX),ystart+ycount))return false;
			}
		}
		return true;
	};
	this.getVisibleTiles = function (mob) //used for player vision
	{
		var output = [];
		for (var xcount = mob.x - mob.visionRange/2;xcount<mob.x+mob.visionRange;xcount++)
		{
			for (var ycount=mob.y - mob.visionRange/2;ycount<mob.y+mob.visionRange;ycount++)
			{
				//ray cast every tile within range
				if (this.tileVisible(mob, xcount, ycount))
					output.push([xcount,ycount]);
			}
		}
		return output;
	};
	this.getVisibleMobs = function (character)  //used for ai vision (and player vision)
	{
		var output = [];
		var mobs = this.getMobs();
		for (var c1=0,len=mobs.length;c1<len;c1++)
		{
			var mob = mobs[c1];
			if (this.tileVisible(character, mob.x, mob.y))
				output.push(mob);
		}
		return output;
	};
	this.noise = function (maker,volume) //alerts other mobs to the presence of "maker"
	{
		var mobs = this.getMobs();
		for (var c1=0,len=mobs.length;c1<len;c1++)
		{
			var mob = mobs[c1]
			var distance = this.pythag(mob.x-maker.x, mob.y-maker.y)
			if (volume >= Math.round(distance))
				this.mobInform(mob, maker);
		}
		//todo: add a record so it can be recorded for playfieldgraphic
	};
	this.mobLook = function (mob)
	{
		var visibleMobs = this.getVisibleMobs(mob);
		for (var c1=0,len=visibleMobs.length;c1<len;c1++)
		{
			this.mobInform(mob, visibleMobs[c1]);
		}
	};

	this.refreshVision = function ()
	{
		var mobs = this.getMobs();
		for (var c1=0,len=mobs.length;c1<len;c1++)
		{
			this.mobLook(mobs[c1]);
		}
	};
	this.mobInform = function (mob, target)
	{
		//if in peaceful mode, check if target is hostile
		if (this.mode=='peaceful' && mob.perceiveMob(mob))
		{
			this.modeCombat(mob);//this will clear all mob perception
			this.mobLook(mob);
			this.mobInform(mob, target); //make sure the mob remembers what started the fight
			mob.getMove();
		}
	};
	this.clearMobPerception = function ()
	{
		var mobs = this.getMobs();
		for (var c1=0,len=mobs.length;c1<len;c1++)
		{
			mobs[c1].knownMobs={};
			this.mobLook(mobs[c1]);
		}
	};
	this.modePeaceful = function ()
	{
		if (this.mode=='peaceful')return; //prevent recursion
		this.activePlayerCharacter = this.factions['player'][0];
		this.mode='peaceful';
		var mobs = this.getMobs();
		for (var c1=0;c1<mobs.length;c1++)
		{
			mobs[c1].getMove();
		}
	};
	this.modeCombat = function (activeMob)
	{
		if (this.mode=='combat')
			return; //prevent recursion

		this.mode = 'combat';
		this.clearMobPerception();

		if (activeMob == null)this.cycleActiveFaction();
		else
		{
			var fact = Object.keys(this.factions);
			for (var c1=0;c1<fact.length;c1++)
			{
				if (activeMob.faction == fact[c1])
				{
					this.activeFaction = c1;
					break;
				}
			}
			var active = this.factions[activeMob.faction];
			for (var c1=0;c1<active.length;c1++)
			{
				active[c1].refresh();
			}
		}
	};
	this.modeStory = function (script)
	{
		if (this.mode=='story')
			return; //prevent recursion

		this.mode = 'story';
		this.activePlayerCharacter = null;
		this.storyScript = script;
		var event = this.storyScript.shift();
		event.mob.forceMove(event.move,event.text);
	};
	this.astar = function (start,end) //returns an array representing a path between start and end, or false on failure
	{
		if (this.tileImpassable(end[0],end[1]))
			return false;

		//record room costs as {'cost':,'prev':,'coords'}
		var rooms = [[]];
		var toSearch = [];

		var addToSearch = function (thisRoom)
		{
			for (var c1=0;c1<toSearch.length;c1++)
			{
				if (toSearch[c1].dist > thisRoom.dist)
				{
					//insert it
					toSearch.splice(c1, 0, thisRoom);
					return;
				}
			}
			toSearch.push(thisRoom);
		};
		var checkRoom = function (thisRoom)//returns true if path found
		{
			//get all exits and record them in rooms
			var potentialExits = [[-1,-1],[-1,0],[-1,1],[0,-1],[0,1],[1,-1],[1,0],[1,1]];
			for (var c1=0;c1<potentialExits.length;c1++)
			{
				var exit = [potentialExits[c1][0]+thisRoom.coords[0],potentialExits[c1][1]+thisRoom.coords[1]];
				//if exit is impassible, continue
				if (this.tileImpassable(exit[0],exit[1]))continue;
				//if the room is already known
				if (rooms[exit[0]] && rooms[exit[0]][exit[1]])
				{
					var nextRoom = rooms[exit[0]][exit[1]];
					if (nextRoom.cost > thisRoom.cost+1)
					{
						nextRoom.cost = thisRoom.cost+1;
						nextRoom.prev = thisRoom.coords;
					}
					else continue; //have already found a shorter path
				}
				else //if it's a new room
				{
					//if it's the end, record the path
					if (!rooms[exit[0]])rooms[exit[0]] = [];
					var newRoom =
					{
						'cost:' : thisRoom.cost+1,
						'prev' :thisRoom.coords,
						'coords' :[exit[0],exit[1]],
						'dist' : Math.sqrt(Math.pow(end[0]-exit[0],2) + Math.pow(end[1]-exit[1],2))
					};
					if (!rooms[exit[0]])rooms[exit[0]] =[];
					rooms[exit[0]][exit[1]] = newRoom;
					addToSearch(newRoom);
					//if it's the end, record the path
					if (exit[0] == end[0] && exit[1]==end[1])return true;
				}
			}
			return false;
		}.bind(this);

		var firstRoom =
		{
			'cost:' : 0,
			'prev' : [0,0],
			'coords' :[start[0],start[1]],
			'dist' : this.pythag(end[0] - start[0], end[1] - start[1])
		};
		rooms[start[0]] = [];
		rooms[start[0]][start[1]] = firstRoom;
		addToSearch(firstRoom);

		while (toSearch.length>0)
		{
			if (checkRoom(toSearch.shift()))
			{
				//trace the path and return it
				var finalPath = [];
				var troom = end;
				do
				{
					finalPath.unshift(troom);
					troom = rooms[troom[0]][troom[1]].prev;
				}
				while (troom[0] != start[0] || troom[1]!=start[1]);
				return finalPath;
			}
		}
		return false;//path not found
	};
	this.draw = function ()
	{
		return this.tileset.drawField(this);
	};
}
//receive move- callback function passed from mobs

//begin turn- prompts all mobs to move
<|endoftext|>