<commit_before>'use strict';
((rb, document, $) => {
  const activeClass = 'uploader-dragged-on';
  const maxFilesClass = 'uploader-max-files';

  // Prevent default drag behaviors
  ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
    document.body.addEventListener(eventName, preventDefaults);
  });

  rb.uploaders = rb.uploaders || {};

  rb.uploaders.addUploader = (uploaderConfig) => {
    let uploader;

    if (uploaderConfig &&
        uploaderConfig.selector) {
      uploader = document.querySelector(uploaderConfig.selector);
      uploader.id = uploader.id || `rb_${rb.getShortId()}`;
      uploaderConfig.id = uploader.id;

      uploaderConfig.isMobile =
        uploaderConfig.isMobile ||
        (uploader && uploader.classList && uploader.classList.contains('uploader-mobile')) ||
        document.querySelectorAll('.mobile .uploader, .tablet .uploader').length > 0;

      if (uploader && uploader.classList) {
        if (uploaderConfig.isMobile) {
          uploader.classList.add('uploader-mobile');
          uploader.addEventListener('click', mobileOpenPhotos);
        }

        if (!uploader.classList.contains('uploader-expanded')) {
          uploader.setAttribute(rb.aria.role, 'button');
          uploader.setAttribute('tabindex', 0);
        }
      }

      if (!uploaderConfig.isMobile) {
        uploader.addEventListener('click', expandClickHandler);

        ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
          uploader.addEventListener(eventName, preventDefaults);
        });

        ['dragenter', 'dragover'].forEach(eventName => {
          uploader.addEventListener(eventName, draggedOn);
        });

        ['dragleave', 'drop'].forEach(eventName => {
          uploader.addEventListener(eventName, draggedOff);
        });

        uploader.addEventListener('drop', handleDrop);

        // Add scrim elements
        const scrim = document.createElement('div');
        scrim.classList.add('scrim');
        uploader.insertAdjacentElement('afterbegin', scrim);

        // Register drag-related listeners
        const scrimIcon = document.createElement('div');
        scrimIcon.classList.add('scrim_icon');
        scrim.insertAdjacentElement('afterend', scrimIcon);
      }

      rb.uploaders.instances = rb.uploaders.instances || [];
      rb.uploaders.instances.push({
        config: uploaderConfig,
        files: []
      });

      // Register listener for keyboard input
      $(uploader).find('.uploader_file-label').keydown((e) => {
        // Since the browser's file input UI is hidden, we need to
        // allow the label it's replaced with to be triggered via
        // keyboard.
        if ((e.which === 13 || e.which === 32)) {
          $(e.target).click();
        }
      });

      const $input = $(uploader).find('input[type="file"]');
      const inputId = $input[0].id || `rb_${rb.getShortId()}`;

      $input.attr('id', inputId);
      $(uploader).find('.uploader_file-label')
        .attr('for', inputId)
        .attr('aria-role', 'button')
        .attr('tabindex', 0);

      $input.change(fileInputOnchange);
    }

    return uploader.id;
  };

  function getClosestUploader(el) {
    let actualUploader;

    if (el.classList) {
      actualUploader =
      el.classList.contains('uploader') ? el : el.closest('.uploader');
    }

    return actualUploader;
  }

  function mobileOpenPhotos(e) {
    if(getClosestUploader(e.target)) {
      $(`#${getClosestUploader(e.target).id} input[type="file"]`).click();
    }
  }

  function expandClickHandler(e) {
    expandUploader(getClosestUploader(e.target));
  }

  function expandUploader(uploader) {
    if (uploader && uploader.classList) {
      if (uploader.classList) {
        uploader.classList.add('uploader-expanded');
      }

      uploader.removeAttribute(rb.aria.role);
      uploader.removeAttribute('tabindex');

      uploader.removeEventListener('click', mobileOpenPhotos);
      uploader.removeEventListener('click', expandClickHandler);
    }
  }

  function getInstanceForId(id) {
    return rb.uploaders.instances.filter((i) => {
      return i.config.id === id;
    })[0];
  }

  rb.uploaders.getInstanceForId = getInstanceForId;

  function fileInputOnchange(e) {
    const id = getClosestUploader(e.target).id;
    const instance = getInstanceForId(id);
    const config = instance.config;

    if (config.isMobile) {
      expandClickHandler(e);
    }

    rb.uploaders.handleFiles(this.files, config.id);
  }

  function preventDefaults(e) {
    e.preventDefault();
    e.stopPropagation();
  }

  function draggedOn(e) {
    const $target = $(e.target);

    const $dropArea =
      $target.hasClass(activeClass) ? $target : $target.closest('.uploader');

    if (!$dropArea.hasClass(activeClass)) {
      $dropArea.find('.scrim, .scrim_icon').css('z-index', '1');
      $dropArea.addClass(activeClass);
    }
  }

  function draggedOff(e) {
    const $target = $(e.target);

    const $dropArea =
      $target.hasClass(activeClass) ? $target : $target.closest('.uploader');

    if ($dropArea.hasClass(activeClass)) {
      $dropArea.removeClass(activeClass);
      $dropArea.find('.scrim, .scrim_icon').css('z-index', '-1');
    }
  }

  rb.uploaders.numCanAdd = (id) => {
    const uploaderInstance = getInstanceForId(id);
    const numFiles = uploaderInstance.files ? uploaderInstance.files.length : 0;
    const maxFiles = uploaderInstance.config.maxFiles || Number.POSITIVE_INFINITY;

    return maxFiles - numFiles;
  };

  function handleDrop(e) {
    const uploader = getClosestUploader(e.target);

    if (rb.uploaders.numCanAdd(uploader.id) > 0) {
      const dt = e.dataTransfer;
      const files = dt.files;

      rb.uploaders.handleFiles(files, uploader.id);
    }
  }

  rb.uploaders.handleFiles = function handleFiles(files, uploaderId) {
    const numCanAdd = rb.uploaders.numCanAdd(uploaderId);

    const filesToAdd = [...files].slice(0, numCanAdd);
    filesToAdd.forEach(file => { processFile(file, uploaderId); });
  };

  function getNewDimensions(oldDimensions) {
    const width = oldDimensions.width;
    const height = oldDimensions.height;
    let newWidth;
    let newHeight;
    if (width <= 1500 && height <= 1500) {
      return { 'width': width, 'height': height };
    }

    if (height > width) {
      if (height > 1500) {
        newHeight = 1500;
        newWidth = Math.floor((width / height) * newHeight);
      } else {
        return { 'width': width, 'height': height };
      }
    } else {
      if (width > 1500) {
        newWidth = 1500;
        newHeight = Math.floor((height / width) * newWidth);
      } else {
        return { 'width': width, 'height': height };
      }
    }

    return { 'width': newWidth, 'height': newHeight };
  }

  function downscaleImage(data) {
    return new Promise((resolve, reject) => {
      // Provide default values
      const imageType = 'image/png';
      const encoderOptions = 0.9;

      // Create a temporary image so that we can compute the height of the downscaled image.
      let image = new Image();
      image.src = data;
      image.onload = () => {
        let srcOrientation = 1;
        // exif.getData(this, function () {
        //   srcOrientation = exif.getTag(this, 'Orientation');
        // });
        jQuery.ajax({
          url: 'https://cdn.jsdelivr.net/npm/exif-js',
          dataType: 'script',
          cache: true
        }).done(function () {
          EXIF.getData(image, function () {
            srcOrientation = EXIF.getTag(this, 'Orientation');
            const newDimensions = getNewDimensions({ 'width': image.width, 'height': image.height });
            const newWidth = newDimensions.width;
            const newHeight = newDimensions.height;

            // Create a temporary canvas to draw the downscaled image on.
            let canvas = document.createElement('canvas');
            if (srcOrientation > 4 && srcOrientation < 9) {
              canvas.width = newHeight;
              canvas.height = newWidth;
            } else {
              canvas.width = newWidth;
              canvas.height = newHeight;
            }

            // Draw the downscaled image on the canvas and return the new data URL.
            // Have to use 'let' instead of 'const' so it can be mutable
            let ctx = canvas.getContext('2d');
            switch (srcOrientation) {
            case 2: ctx.transform(-1, 0, 0, 1, newWidth, 0); break;
            case 3: ctx.transform(-1, 0, 0, -1, newWidth, newHeight); break;
            case 4: ctx.transform(1, 0, 0, -1, 0, newHeight); break;
            case 5: ctx.transform(0, 1, 1, 0, 0, 0); break;
            case 6: ctx.transform(0, 1, -1, 0, newHeight, 0); break;
            case 7: ctx.transform(0, -1, -1, 0, newHeight, newWidth); break;
            case 8: ctx.transform(0, -1, 1, 0, 0, newWidth); break;
            default: break;
            }
            ctx.drawImage(image, 0, 0, newWidth, newHeight);
            const newDataUrl = canvas.toDataURL(imageType, encoderOptions);
            const newFileSize = Math.ceil(newDataUrl.split(',')[1].length * 0.75);
            resolve({ 'file': newDataUrl, 'size': newFileSize });
          });
        });
      };
      image.onerror = reject;
    });
  }

  function createPreview(filesAsBinary, instance, instanceEl, reader, scaledImageObj) {
    const img = document.createElement('img');
    img.src = filesAsBinary ? reader.result : scaledImageObj.file;
    img.id = `rb_${rb.getShortId()}`;
    img.classList.add('uploader_thumb');

    const f = {
      id: img.id,
      file: filesAsBinary ? rb.dataURItoBlob(reader.result) : scaledImageObj.file,
      size: filesAsBinary ? reader.size : scaledImageObj.size
    };
    instance.files.push(f);

    instanceEl
      .dispatchEvent(new CustomEvent('rb.uploaders.fileAdded', { detail: f }));

    const button = document.createElement('button');
    button.dataset.targetId = img.id;
    button.classList.add('uploader_thumbs_remove-button');
    button.innerHTML = '×';
    button.setAttribute(rb.aria.label, 'Remove this image');

    rb.once(button, 'click', (e) => {
      const id = e.target.dataset.targetId;
      const imgToRemove = document.querySelector(`#${id}`);
      const container = e.target.closest('.uploader');
      const uploader = document.querySelector(instance.config.selector);

      imgToRemove.parentNode.removeChild(imgToRemove);
      e.target.parentNode.removeChild(e.target);
      $(uploader).find('input[type="file"]').val('');

      if (container && container.querySelectorAll('.uploader_thumb').length === 0) {
        container.classList.remove('uploader-has-thumbs');

        if (instance.config.isMobile) {
          container.classList.remove('uploader-expanded');
          uploader.addEventListener('click', mobileOpenPhotos);
        }
      }

      instance.files = instance.files.filter((el) => {
        return el.id !== id;
      });

      if (rb.uploaders.numCanAdd(instanceEl.id) > 0) {
        const $dropArea = $(`#${instanceEl.id}`);

        $(`#${instanceEl.id} .uploader_file-input, #${instanceEl.id} .uploader_file-label`)
          .removeAttr('disabled');

        $dropArea.removeClass(maxFilesClass);
      }

      const i = { removedId: id };
      document.getElementById(instanceEl.id)
        .dispatchEvent(new CustomEvent('rb.uploaders.fileRemoved', { detail: i }));
    });

    document.querySelector(`#${instanceEl.id} .uploader_thumbs`).appendChild(img);
    document.querySelector(`#${img.id}`).insertAdjacentElement('afterend', button);

    const u = document.querySelector(`#${instanceEl.id}`);

    if (u) {
      u.classList.add('uploader-has-thumbs');
    }

    if (!rb.uploaders.numCanAdd(instanceEl.id)) {
      const $dropArea = $(`#${instanceEl.id}`);

      $(`#${instanceEl.id} .uploader_file-input, #${instanceEl.id} .uploader_file-label`)
        .attr('disabled', '');

      $dropArea.addClass(maxFilesClass);
    }
  }

  function processFile(file, uploaderId) {
    const instance = getInstanceForId(uploaderId);
    const instanceEl = document.getElementById(uploaderId);
    const reader = new FileReader();
    reader.size = file.size;

    instanceEl
      .dispatchEvent(new CustomEvent('rb.uploaders.addingFile'));

    reader.readAsDataURL(file);

    reader.onloadend = () => {
      const fileContents =
        instance.config.filesAsBinary ?
          rb.dataURItoBlob(reader.result) :
          reader.result
      ;

      if (instance.config.filesAsBinary) {
        createPreview(true, instance, instanceEl, reader);
      } else {
        downscaleImage(fileContents).then(scaledImageObj => {
          createPreview(false, instance, instanceEl, reader, scaledImageObj);
        });
      }
    };
  }
})(window.rb, document, jQuery);

<commit_msg>Make maxWidth and maxHeight a variable. Remove unused code<commit_after>'use strict';
((rb, document, $) => {
  const activeClass = 'uploader-dragged-on';
  const maxFilesClass = 'uploader-max-files';

  // Prevent default drag behaviors
  ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
    document.body.addEventListener(eventName, preventDefaults);
  });

  rb.uploaders = rb.uploaders || {};

  rb.uploaders.addUploader = (uploaderConfig) => {
    let uploader;

    if (uploaderConfig &&
        uploaderConfig.selector) {
      uploader = document.querySelector(uploaderConfig.selector);
      uploader.id = uploader.id || `rb_${rb.getShortId()}`;
      uploaderConfig.id = uploader.id;

      uploaderConfig.isMobile =
        uploaderConfig.isMobile ||
        (uploader && uploader.classList && uploader.classList.contains('uploader-mobile')) ||
        document.querySelectorAll('.mobile .uploader, .tablet .uploader').length > 0;

      if (uploader && uploader.classList) {
        if (uploaderConfig.isMobile) {
          uploader.classList.add('uploader-mobile');
          uploader.addEventListener('click', mobileOpenPhotos);
        }

        if (!uploader.classList.contains('uploader-expanded')) {
          uploader.setAttribute(rb.aria.role, 'button');
          uploader.setAttribute('tabindex', 0);
        }
      }

      if (!uploaderConfig.isMobile) {
        uploader.addEventListener('click', expandClickHandler);

        ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
          uploader.addEventListener(eventName, preventDefaults);
        });

        ['dragenter', 'dragover'].forEach(eventName => {
          uploader.addEventListener(eventName, draggedOn);
        });

        ['dragleave', 'drop'].forEach(eventName => {
          uploader.addEventListener(eventName, draggedOff);
        });

        uploader.addEventListener('drop', handleDrop);

        // Add scrim elements
        const scrim = document.createElement('div');
        scrim.classList.add('scrim');
        uploader.insertAdjacentElement('afterbegin', scrim);

        // Register drag-related listeners
        const scrimIcon = document.createElement('div');
        scrimIcon.classList.add('scrim_icon');
        scrim.insertAdjacentElement('afterend', scrimIcon);
      }

      rb.uploaders.instances = rb.uploaders.instances || [];
      rb.uploaders.instances.push({
        config: uploaderConfig,
        files: []
      });

      // Register listener for keyboard input
      $(uploader).find('.uploader_file-label').keydown((e) => {
        // Since the browser's file input UI is hidden, we need to
        // allow the label it's replaced with to be triggered via
        // keyboard.
        if ((e.which === 13 || e.which === 32)) {
          $(e.target).click();
        }
      });

      const $input = $(uploader).find('input[type="file"]');
      const inputId = $input[0].id || `rb_${rb.getShortId()}`;

      $input.attr('id', inputId);
      $(uploader).find('.uploader_file-label')
        .attr('for', inputId)
        .attr('aria-role', 'button')
        .attr('tabindex', 0);

      $input.change(fileInputOnchange);
    }

    return uploader.id;
  };

  function getClosestUploader(el) {
    let actualUploader;

    if (el.classList) {
      actualUploader =
      el.classList.contains('uploader') ? el : el.closest('.uploader');
    }

    return actualUploader;
  }

  function mobileOpenPhotos(e) {
    if(getClosestUploader(e.target)) {
      $(`#${getClosestUploader(e.target).id} input[type="file"]`).click();
    }
  }

  function expandClickHandler(e) {
    expandUploader(getClosestUploader(e.target));
  }

  function expandUploader(uploader) {
    if (uploader && uploader.classList) {
      if (uploader.classList) {
        uploader.classList.add('uploader-expanded');
      }

      uploader.removeAttribute(rb.aria.role);
      uploader.removeAttribute('tabindex');

      uploader.removeEventListener('click', mobileOpenPhotos);
      uploader.removeEventListener('click', expandClickHandler);
    }
  }

  function getInstanceForId(id) {
    return rb.uploaders.instances.filter((i) => {
      return i.config.id === id;
    })[0];
  }

  rb.uploaders.getInstanceForId = getInstanceForId;

  function fileInputOnchange(e) {
    const id = getClosestUploader(e.target).id;
    const instance = getInstanceForId(id);
    const config = instance.config;

    if (config.isMobile) {
      expandClickHandler(e);
    }

    rb.uploaders.handleFiles(this.files, config.id);
  }

  function preventDefaults(e) {
    e.preventDefault();
    e.stopPropagation();
  }

  function draggedOn(e) {
    const $target = $(e.target);

    const $dropArea =
      $target.hasClass(activeClass) ? $target : $target.closest('.uploader');

    if (!$dropArea.hasClass(activeClass)) {
      $dropArea.find('.scrim, .scrim_icon').css('z-index', '1');
      $dropArea.addClass(activeClass);
    }
  }

  function draggedOff(e) {
    const $target = $(e.target);

    const $dropArea =
      $target.hasClass(activeClass) ? $target : $target.closest('.uploader');

    if ($dropArea.hasClass(activeClass)) {
      $dropArea.removeClass(activeClass);
      $dropArea.find('.scrim, .scrim_icon').css('z-index', '-1');
    }
  }

  rb.uploaders.numCanAdd = (id) => {
    const uploaderInstance = getInstanceForId(id);
    const numFiles = uploaderInstance.files ? uploaderInstance.files.length : 0;
    const maxFiles = uploaderInstance.config.maxFiles || Number.POSITIVE_INFINITY;

    return maxFiles - numFiles;
  };

  function handleDrop(e) {
    const uploader = getClosestUploader(e.target);

    if (rb.uploaders.numCanAdd(uploader.id) > 0) {
      const dt = e.dataTransfer;
      const files = dt.files;

      rb.uploaders.handleFiles(files, uploader.id);
    }
  }

  rb.uploaders.handleFiles = function handleFiles(files, uploaderId) {
    const numCanAdd = rb.uploaders.numCanAdd(uploaderId);

    const filesToAdd = [...files].slice(0, numCanAdd);
    filesToAdd.forEach(file => { processFile(file, uploaderId); });
  };

  function getNewDimensions(oldDimensions) {
    const width = oldDimensions.width;
    const height = oldDimensions.height;
    const maxWidth = 1500;
    const maxHeight = 1500;
    let newWidth;
    let newHeight;
    if (width <= maxWidth && height <= maxHeight) {
      return { 'width': width, 'height': height };
    }

    if (height > width) {
      if (height > maxHeight) {
        newHeight = maxHeight;
        newWidth = Math.floor((width / height) * newHeight);
      } else {
        return { 'width': width, 'height': height };
      }
    } else {
      if (width > maxWidth) {
        newWidth = maxWidth;
        newHeight = Math.floor((height / width) * newWidth);
      } else {
        return { 'width': width, 'height': height };
      }
    }

    return { 'width': newWidth, 'height': newHeight };
  }

  function downscaleImage(data) {
    return new Promise((resolve, reject) => {
      // Provide default values
      const imageType = 'image/png';
      const encoderOptions = 0.9;

      // Create a temporary image so that we can compute the height of the downscaled image.
      let image = new Image();
      image.src = data;
      image.onload = () => {
        let srcOrientation = 1;
        jQuery.ajax({
          url: 'https://cdn.jsdelivr.net/npm/exif-js',
          dataType: 'script',
          cache: true
        }).done(function () {
          EXIF.getData(image, function () {
            srcOrientation = EXIF.getTag(this, 'Orientation');
            const newDimensions = getNewDimensions({ 'width': image.width, 'height': image.height });
            const newWidth = newDimensions.width;
            const newHeight = newDimensions.height;

            // Create a temporary canvas to draw the downscaled image on.
            let canvas = document.createElement('canvas');
            if (srcOrientation > 4 && srcOrientation < 9) {
              canvas.width = newHeight;
              canvas.height = newWidth;
            } else {
              canvas.width = newWidth;
              canvas.height = newHeight;
            }

            // Draw the downscaled image on the canvas and return the new data URL.
            // Have to use 'let' instead of 'const' so it can be mutable
            let ctx = canvas.getContext('2d');
            switch (srcOrientation) {
            case 2: ctx.transform(-1, 0, 0, 1, newWidth, 0); break;
            case 3: ctx.transform(-1, 0, 0, -1, newWidth, newHeight); break;
            case 4: ctx.transform(1, 0, 0, -1, 0, newHeight); break;
            case 5: ctx.transform(0, 1, 1, 0, 0, 0); break;
            case 6: ctx.transform(0, 1, -1, 0, newHeight, 0); break;
            case 7: ctx.transform(0, -1, -1, 0, newHeight, newWidth); break;
            case 8: ctx.transform(0, -1, 1, 0, 0, newWidth); break;
            default: break;
            }
            ctx.drawImage(image, 0, 0, newWidth, newHeight);
            const newDataUrl = canvas.toDataURL(imageType, encoderOptions);
            const newFileSize = Math.ceil(newDataUrl.split(',')[1].length * 0.75);
            resolve({ 'file': newDataUrl, 'size': newFileSize });
          });
        });
      };
      image.onerror = reject;
    });
  }

  function createPreview(filesAsBinary, instance, instanceEl, reader, scaledImageObj) {
    const img = document.createElement('img');
    img.src = filesAsBinary ? reader.result : scaledImageObj.file;
    img.id = `rb_${rb.getShortId()}`;
    img.classList.add('uploader_thumb');

    const f = {
      id: img.id,
      file: filesAsBinary ? rb.dataURItoBlob(reader.result) : scaledImageObj.file,
      size: filesAsBinary ? reader.size : scaledImageObj.size
    };
    instance.files.push(f);

    instanceEl
      .dispatchEvent(new CustomEvent('rb.uploaders.fileAdded', { detail: f }));

    const button = document.createElement('button');
    button.dataset.targetId = img.id;
    button.classList.add('uploader_thumbs_remove-button');
    button.innerHTML = '×';
    button.setAttribute(rb.aria.label, 'Remove this image');

    rb.once(button, 'click', (e) => {
      const id = e.target.dataset.targetId;
      const imgToRemove = document.querySelector(`#${id}`);
      const container = e.target.closest('.uploader');
      const uploader = document.querySelector(instance.config.selector);

      imgToRemove.parentNode.removeChild(imgToRemove);
      e.target.parentNode.removeChild(e.target);
      $(uploader).find('input[type="file"]').val('');

      if (container && container.querySelectorAll('.uploader_thumb').length === 0) {
        container.classList.remove('uploader-has-thumbs');

        if (instance.config.isMobile) {
          container.classList.remove('uploader-expanded');
          uploader.addEventListener('click', mobileOpenPhotos);
        }
      }

      instance.files = instance.files.filter((el) => {
        return el.id !== id;
      });

      if (rb.uploaders.numCanAdd(instanceEl.id) > 0) {
        const $dropArea = $(`#${instanceEl.id}`);

        $(`#${instanceEl.id} .uploader_file-input, #${instanceEl.id} .uploader_file-label`)
          .removeAttr('disabled');

        $dropArea.removeClass(maxFilesClass);
      }

      const i = { removedId: id };
      document.getElementById(instanceEl.id)
        .dispatchEvent(new CustomEvent('rb.uploaders.fileRemoved', { detail: i }));
    });

    document.querySelector(`#${instanceEl.id} .uploader_thumbs`).appendChild(img);
    document.querySelector(`#${img.id}`).insertAdjacentElement('afterend', button);

    const u = document.querySelector(`#${instanceEl.id}`);

    if (u) {
      u.classList.add('uploader-has-thumbs');
    }

    if (!rb.uploaders.numCanAdd(instanceEl.id)) {
      const $dropArea = $(`#${instanceEl.id}`);

      $(`#${instanceEl.id} .uploader_file-input, #${instanceEl.id} .uploader_file-label`)
        .attr('disabled', '');

      $dropArea.addClass(maxFilesClass);
    }
  }

  function processFile(file, uploaderId) {
    const instance = getInstanceForId(uploaderId);
    const instanceEl = document.getElementById(uploaderId);
    const reader = new FileReader();
    reader.size = file.size;

    instanceEl
      .dispatchEvent(new CustomEvent('rb.uploaders.addingFile'));

    reader.readAsDataURL(file);

    reader.onloadend = () => {
      const fileContents =
        instance.config.filesAsBinary ?
          rb.dataURItoBlob(reader.result) :
          reader.result
      ;

      if (instance.config.filesAsBinary) {
        createPreview(true, instance, instanceEl, reader);
      } else {
        downscaleImage(fileContents).then(scaledImageObj => {
          createPreview(false, instance, instanceEl, reader, scaledImageObj);
        });
      }
    };
  }
})(window.rb, document, jQuery);

<|endoftext|>


<commit_before>var Sequelize = require('sequelize');

module.exports = function(sequelize, DataTypes) {
	var Incremental = sequelize.define('Incremental', {
			timestamp: {
				type: Sequelize.DATE,
				notNull: true,
			},
			filename: {
				type: Sequelize.STRING(255),
				notEmpty: true,
			},
			md5sum: {
				type: Sequelize.STRING(32),
				notEmpty: true,
			},
			subdirectory: {
				type: Sequelize.STRING(255),
				notEmpty: false,
			},
	}, {
		validate: {
			romIdsValid : function() {
				if (!this.sourceRomId || this.sourceRomId < 0) {
					throw new Error('sourceRomId is a mandatory field!');
				}

				if (!this.targetRomId || this.targetRomId < 0) {
					throw new Error('targetRomId is a mandatory field!');
				}

				if (this.getSourceRom().getDevice().id != this.getTargetRom().getDevice().id) {
					throw new Error('source and target rom have to be for the same device!');
				}
			},
		}
	});

	return Incremental;
}
<commit_msg>Removed non-working validation from Incremental.<commit_after>var Sequelize = require('sequelize');

module.exports = function(sequelize, DataTypes) {
	var Incremental = sequelize.define('Incremental', {
			timestamp: {
				type: Sequelize.DATE,
				notNull: true,
			},
			filename: {
				type: Sequelize.STRING(255),
				notEmpty: true,
			},
			md5sum: {
				type: Sequelize.STRING(32),
				notEmpty: true,
			},
			subdirectory: {
				type: Sequelize.STRING(255),
				notEmpty: false,
			},
	}, {
		validate: {
			romIdsValid : function() {
				if (!this.sourceRomId || this.sourceRomId < 0) {
					throw new Error('sourceRomId is a mandatory field!');
				}

				if (!this.targetRomId || this.targetRomId < 0) {
					throw new Error('targetRomId is a mandatory field!');
				}
			},
		}
	});

	return Incremental;
}
<|endoftext|>


<commit_before>var View = require('ampersand-view');
var State = require('ampersand-state');
var Collection = require('ampersand-collection');

var template = ['
',
	'<input type=\'file\' style=\'visibility:hidden;width:0;height:0;\'/>',
	'',
	'

',
'
'].join('');

var fileTemplate = ['
',
	'',
	'',
	'',
	'',
	'<button data-hook=\'remove\'>Remove</button>',
'
'].join('');

var FileState = State.extend({
	initialize: function(file){
		var self = this;
		this.file = file;

		if (/image/.test(file.type)) {
			var reader = new FileReader();
			reader.onloadend = function() {
				self.preview = reader.result;
			};
			reader.readAsDataURL(file);
		}
	},
	props: {
		size: 'number',
		name: 'text',
		type: 'text',
		file: 'any',
		preview: 'string'
	}
});

var FilesCollection = Collection.extend({
	model: FileState,
	toFiles: function() {
		return this.map(function(fileModel) {
			return fileModel.file;
		});
	}
});

var FileView = View.extend({
	template: fileTemplate,
	events: {
		'click [data-hook=remove]': 'removeFile',
	},
	bindings: {
		'model.size': {
			type: 'text',
			hook: 'size'
		},
		'model.name': {
			type: 'text',
			hook: 'name'
		},
		'model.type': {
			type: 'text',
			hook: 'type'
		},
		'model.preview': [
			{
				type: 'toggle',
				selector: 'img'
			},
			{
				type: 'attribute',
				selector: 'img',
				name: 'src'
			}
		]
	},
	removeFile: function(event) {
		event.preventDefault();
		event.stopImmediatePropagation();
		this.collection.remove(this.model);
	}
});

function eventNoOp(event) {
	event.stopPropagation();
	event.preventDefault();
}

module.exports = View.extend({
	template: template,
	initialize: function() {
		var self = this;

		this.files.on('add remove', function() {
			self.getValue();
			self.checkValid();
			if (self.parent) {
				self.parent.update(self);
			}
		});

		window.files = this.files;
	},
	events: {
		'click [data-hook=drop-zone]': 'simulateInputClick',
		'change input[type=file]': 'handleFileInput',
		'dragenter [data-hook=drop-zone]': 'dragEnter',
		'dragover [data-hook=drop-zone]': 'dragOver',
		'drop [data-hook=drop-zone]': 'drop'
	},
	bindings: {
		name: {
			type: 'attribute',
			selector: 'input[type=file]',
			name: 'name'
		},
		label: [
			{
				type: 'toggle',
				hook: 'label'
			},
			{
				type: 'text',
				hook: 'label'
			}
		],
		holderClass: {
			type: 'class',
			hook: 'drop-zone'
		},
		multiple: {
			type: 'booleanAttribute',
			selector: 'input[type=file]',
			name: 'multiple'
		},
		accept: {
			type: 'attribute',
			selector: 'input[type=file]',
			name: 'accept'
		}
	},
	props: {
		holderClass: {
			type: 'string'
		},
		label: {
			type: 'string',
			default: 'Drag and drop a file'
		},
		value: {
			type: 'any'
		},
		required: {
			type: 'boolean',
			default: false
		},
		valid: {
			type: 'boolean',
			default: true
		},
		multiple: {
			type: 'boolean',
			default: false
		},
		name: {
			type: 'string',
			required: true
		},
		accept: {
			type: 'string',
			default: '*/*'
		}
	},
	derived: {
		acceptArray: {
			deps: ['accept'],
			fn: function() {
				var accept;
				if (typeof this.accept === 'string') {
					accept = this.accept;
				} else if (this.accept === true) {
					accept = '*/*';
				} else {
					accept = '';
				}
				return accept.split(',');
			}
		}
	},
	collections: {
		files: FilesCollection
	},
	getValue: function() {
		var value;

		value = this.files.toFiles();

		this.value = value;
		return this.value;
	},
	checkValid: function() {

		if (this.required && !this.files.length) {
			this.valid = false;
			return;
		}

		if (this.multiple && this.files.length > 1) {
			this.valid = false;
			return;
		}

		this.valid = true;
	},
	render: function() {
		var self = this;

		this.renderWithTemplate(this);
		this.input = this.query('input[type=file]');
		this.renderCollection(self.files, FileView, self.queryByHook('files'));
	},
	reset: function() {
		var self = this;
		setTimeout(function() {
			self.files.reset();
		}, 0);
	},
	clear: function() {
		this.reset();
	},
	simulateInputClick: function() {
		this.input.click();
	},
	handleFiles: function(files) {

		if (this.acceptArray.length) {
			var MIMEtypes = this.acceptArray.map(function(accept) {
				return new RegExp(accept.replace('*', '[^\\/,]+'));
			});

			files = files.filter(function(file) {
				return MIMEtypes.some(function(mime) {
					return mime.test(file.type || 'application/octet-stream');
				});
			});
		}

		if (!this.multiple) {

			if (this.files.length && files.length) {
				throw new Error('Multiple files are not allowed');
			}

			files = files.splice(0, 1);
		}

		this.files.add(files);
	},
	handleFileInput: function() {
		this.handleFiles(Array.prototype.slice.apply(this.input.files));
	},
	dragEnter: eventNoOp,
	dragOver: eventNoOp,
	drop: function(event) {
		event.stopPropagation();
		event.preventDefault();

		this.handleFiles(Array.prototype.slice.apply(event.dataTransfer.files));
	}
});<commit_msg>add proper valid property with tests<commit_after>var View = require('ampersand-view');
var State = require('ampersand-state');
var Collection = require('ampersand-collection');

var template = ['
',
	'<input type=\'file\' style=\'visibility:hidden;width:0;height:0;\'/>',
	'',
	'

',
'
'].join('');

var fileTemplate = ['
',
	'',
	'',
	'',
	'',
	'<button data-hook=\'remove\'>Remove</button>',
'
'].join('');

var FileState = State.extend({
	initialize: function(file){
		var self = this;
		this.file = file;

		if (/image/.test(file.type)) {
			var reader = new FileReader();
			reader.onloadend = function() {
				self.preview = reader.result;
			};
			reader.readAsDataURL(file);
		}
	},
	props: {
		size: 'number',
		name: 'text',
		type: 'text',
		file: 'any',
		preview: 'string'
	}
});

var FilesCollection = Collection.extend({
	model: FileState,
	toFiles: function() {
		return this.map(function(fileModel) {
			return fileModel.file;
		});
	}
});

var FileView = View.extend({
	template: fileTemplate,
	events: {
		'click [data-hook=remove]': 'removeFile',
	},
	bindings: {
		'model.size': {
			type: 'text',
			hook: 'size'
		},
		'model.name': {
			type: 'text',
			hook: 'name'
		},
		'model.type': {
			type: 'text',
			hook: 'type'
		},
		'model.preview': [
			{
				type: 'toggle',
				selector: 'img'
			},
			{
				type: 'attribute',
				selector: 'img',
				name: 'src'
			}
		]
	},
	removeFile: function(event) {
		event.preventDefault();
		event.stopImmediatePropagation();
		this.collection.remove(this.model);
	}
});

function eventNoOp(event) {
	event.stopPropagation();
	event.preventDefault();
}

module.exports = View.extend({
	template: template,
	initialize: function() {
		var self = this;

		this.files.on('add remove', function() {
			self.getValue();
			//self.valid;
			if (self.parent) {
				self.parent.update(self);
			}
		});

		window.files = this.files;
	},
	events: {
		'click [data-hook=drop-zone]': 'simulateInputClick',
		'change input[type=file]': 'handleFileInput',
		'dragenter [data-hook=drop-zone]': 'dragEnter',
		'dragover [data-hook=drop-zone]': 'dragOver',
		'drop [data-hook=drop-zone]': 'drop'
	},
	bindings: {
		name: {
			type: 'attribute',
			selector: 'input[type=file]',
			name: 'name'
		},
		label: [
			{
				type: 'toggle',
				hook: 'label'
			},
			{
				type: 'text',
				hook: 'label'
			}
		],
		holderClass: {
			type: 'class',
			hook: 'drop-zone'
		},
		multiple: {
			type: 'booleanAttribute',
			selector: 'input[type=file]',
			name: 'multiple'
		},
		accept: {
			type: 'attribute',
			selector: 'input[type=file]',
			name: 'accept'
		}
	},
	props: {
		holderClass: {
			type: 'string'
		},
		label: {
			type: 'string',
			default: 'Drag and drop a file'
		},
		value: {
			type: 'any'
		},
		required: {
			type: 'boolean',
			default: true
		},
		multiple: {
			type: 'boolean',
			default: false
		},
		name: {
			type: 'string',
			required: true
		},
		accept: {
			type: 'string',
			default: '*/*'
		},
		tests: {
			type: 'array',
			default: function() {
				return [];
			}
		}
	},
	derived: {
		acceptArray: {
			deps: ['accept'],
			fn: function() {
				var accept;
				if (typeof this.accept === 'string') {
					accept = this.accept;
				} else if (this.accept === true) {
					accept = '*/*';
				} else {
					accept = '';
				}
				return accept.split(',');
			}
		},
		valid: {
			deps: ['value', 'required'],
			fn: function() {

				var filesLength = this.files.length;

				if (this.required && !filesLength) {
					return false;
				}

				if (!this.multiple && filesLength > 1) {
					return false;
				}

				if (!this.tests.every(function(test) {
					return test(this.value);
				})) {
					return false;
				}

				return true;
			}
		}
	},
	collections: {
		files: FilesCollection
	},
	getValue: function() {
		var value;

		value = this.files.toFiles();

		this.value = value;
		return this.value;
	},
	render: function() {
		var self = this;

		this.renderWithTemplate(this);
		this.input = this.query('input[type=file]');
		this.renderCollection(self.files, FileView, self.queryByHook('files'));
	},
	reset: function() {
		var self = this;
		setTimeout(function() {
			self.files.reset();
		}, 0);
	},
	clear: function() {
		this.reset();
	},
	simulateInputClick: function() {
		this.input.click();
	},
	handleFiles: function(files) {

		if (this.acceptArray.length) {
			var MIMEtypes = this.acceptArray.map(function(accept) {
				return new RegExp(accept.replace('*', '[^\\/,]+'));
			});

			files = files.filter(function(file) {
				return MIMEtypes.some(function(mime) {
					return mime.test(file.type || 'application/octet-stream');
				});
			});
		}

		if (!this.multiple) {

			if (this.files.length && files.length) {
				throw new Error('Multiple files are not allowed');
			}

			files = files.splice(0, 1);
		}

		this.files.add(files);
	},
	handleFileInput: function() {
		this.handleFiles(Array.prototype.slice.apply(this.input.files));
	},
	dragEnter: eventNoOp,
	dragOver: eventNoOp,
	drop: function(event) {
		event.stopPropagation();
		event.preventDefault();

		this.handleFiles(Array.prototype.slice.apply(event.dataTransfer.files));
	}
});<|endoftext|>


<commit_before>var Promise = require('bluebird'),
    fs = require('fs'),
    semver = require('semver'),
    packageInfo = require('../../package.json'),
    errors = require('./errors'),
    config = require('./config');

function GhostServer(rootApp) {
    this.rootApp = rootApp;
    this.httpServer = null;
    this.connections = {};
    this.connectionId = 0;
    this.upgradeWarning = setTimeout(this.logUpgradeWarning.bind(this), 5000);

    // Expose config module for use externally.
    this.config = config;
}

GhostServer.prototype.connection = function (socket) {
    var self = this;

    self.connectionId += 1;
    socket._ghostId = self.connectionId;

    socket.on('close', function () {
        delete self.connections[this._ghostId];
    });

    self.connections[socket._ghostId] = socket;
};

// Most browsers keep a persistent connection open to the server
// which prevents the close callback of httpServer from returning
// We need to destroy all connections manually
GhostServer.prototype.closeConnections = function () {
    var self = this;

    Object.keys(self.connections).forEach(function (socketId) {
        var socket = self.connections[socketId];

        if (socket) {
            socket.destroy();
        }
    });
};

GhostServer.prototype.logStartMessages = function () {
    // Tell users if their node version is not supported, and exit
    if (!semver.satisfies(process.versions.node, packageInfo.engines.node) &&
        !semver.satisfies(process.versions.node, packageInfo.engines.iojs)) {
        console.log(
            '\nERROR: Unsupported version of Node'.red,
            '\nGhost needs Node version'.red,
            packageInfo.engines.node.yellow,
            'you are using version'.red,
            process.versions.node.yellow,
            '\nPlease go to http://nodejs.org to get a supported version'.green
        );

        process.exit(0);
    }

    // Startup & Shutdown messages
    if (process.env.NODE_ENV === 'production') {
        console.log(
            'Ghost is running...'.green,
            '\nYour blog is now available on',
            config.url,
            '\nCtrl+C to shut down'.grey
        );
    } else {
        console.log(
            ('Ghost is running in ' + process.env.NODE_ENV + '...').green,
            '\nListening on',
                config.getSocket() || config.server.host + ':' + config.server.port,
            '\nUrl configured as:',
            config.url,
            '\nCtrl+C to shut down'.grey
        );
    }

    function shutdown() {
        console.log('\nGhost has shut down'.red);
        if (process.env.NODE_ENV === 'production') {
            console.log(
                '\nYour blog is now offline'
            );
        } else {
            console.log(
                '\nGhost was running for',
                Math.round(process.uptime()),
                'seconds'
            );
        }
        process.exit(0);
    }
    // ensure that Ghost exits correctly on Ctrl+C and SIGTERM
//    process.
//        removeAllListeners('SIGINT').
//		    on('SIGINT', shutdown).
//        removeAllListeners('SIGTERM').
//		    on('SIGTERM', shutdown);
};

GhostServer.prototype.logShutdownMessages = function () {
    console.log('Ghost is closing connections'.red);
};

GhostServer.prototype.logUpgradeWarning = function () {
    errors.logWarn(
        'Ghost no longer starts automatically when using it as an npm module.',
        'If you\'re seeing this message, you may need to update your custom code.',
        'Please see the docs at http://tinyurl.com/npm-upgrade for more information.'
    );
};

/**
 * Starts the ghost server listening on the configured port.
 * Alternatively you can pass in your own express instance and let Ghost
 * start listening for you.
 * @param  {Object=} externalApp Optional express app instance.
 * @return {Promise}
 */
GhostServer.prototype.start = function (externalApp) {
    var self = this,
        rootApp = externalApp ? externalApp : self.rootApp;

    // ## Start Ghost App
    return new Promise(function (resolve) {
        var socketConfig = config.getSocket();

        if (socketConfig) {
            // Make sure the socket is gone before trying to create another
            try {
                fs.unlinkSync(socketConfig.path);
            } catch (e) {
                // We can ignore this.
            }

            self.httpServer = rootApp.listen(socketConfig.path);

            fs.chmod(socketConfig.path, socketConfig.permissions);
        } else {
            self.httpServer = rootApp.listen(
                config.server.port,
                config.server.host
            );
        }

        self.httpServer.on('error', function (error) {
            if (error.errno === 'EADDRINUSE') {
                errors.logError(
                    '(EADDRINUSE) Cannot start Ghost.',
                    'Port ' + config.server.port + ' is already in use by another program.',
                    'Is another Ghost instance already running?'
                );
            } else {
                errors.logError(
                    '(Code: ' + error.errno + ')',
                    'There was an error starting your server.',
                    'Please use the error code above to search for a solution.'
                );
            }
//            process.exit(-1);
        });
        self.httpServer.on('connection', self.connection.bind(self));
        self.httpServer.on('listening', function () {
            self.logStartMessages();
            clearTimeout(self.upgradeWarning);
            resolve(self);
        });
    });
};

// Returns a promise that will be fulfilled when the server stops.
// If the server has not been started, the promise will be fulfilled
// immediately
GhostServer.prototype.stop = function () {
    var self = this;

    return new Promise(function (resolve) {
        if (self.httpServer === null) {
            resolve(self);
        } else {
            self.httpServer.close(function () {
                self.httpServer = null;
                self.logShutdownMessages();
                resolve(self);
            });

            self.closeConnections();
        }
    });
};

// Restarts the ghost application
GhostServer.prototype.restart = function () {
    return this.stop().then(this.start.bind(this));
};

// To be called after `stop`
GhostServer.prototype.hammertime = function () {
    console.log('Can\'t touch this'.green);

    return Promise.resolve(this);
};

module.exports = GhostServer;
<commit_msg>revert no exit<commit_after>var Promise = require('bluebird'),
    fs = require('fs'),
    semver = require('semver'),
    packageInfo = require('../../package.json'),
    errors = require('./errors'),
    config = require('./config');

function GhostServer(rootApp) {
    this.rootApp = rootApp;
    this.httpServer = null;
    this.connections = {};
    this.connectionId = 0;
    this.upgradeWarning = setTimeout(this.logUpgradeWarning.bind(this), 5000);

    // Expose config module for use externally.
    this.config = config;
}

GhostServer.prototype.connection = function (socket) {
    var self = this;

    self.connectionId += 1;
    socket._ghostId = self.connectionId;

    socket.on('close', function () {
        delete self.connections[this._ghostId];
    });

    self.connections[socket._ghostId] = socket;
};

// Most browsers keep a persistent connection open to the server
// which prevents the close callback of httpServer from returning
// We need to destroy all connections manually
GhostServer.prototype.closeConnections = function () {
    var self = this;

    Object.keys(self.connections).forEach(function (socketId) {
        var socket = self.connections[socketId];

        if (socket) {
            socket.destroy();
        }
    });
};

GhostServer.prototype.logStartMessages = function () {
    // Tell users if their node version is not supported, and exit
    if (!semver.satisfies(process.versions.node, packageInfo.engines.node) &&
        !semver.satisfies(process.versions.node, packageInfo.engines.iojs)) {
        console.log(
            '\nERROR: Unsupported version of Node'.red,
            '\nGhost needs Node version'.red,
            packageInfo.engines.node.yellow,
            'you are using version'.red,
            process.versions.node.yellow,
            '\nPlease go to http://nodejs.org to get a supported version'.green
        );

        process.exit(0);
    }

    // Startup & Shutdown messages
    if (process.env.NODE_ENV === 'production') {
        console.log(
            'Ghost is running...'.green,
            '\nYour blog is now available on',
            config.url,
            '\nCtrl+C to shut down'.grey
        );
    } else {
        console.log(
            ('Ghost is running in ' + process.env.NODE_ENV + '...').green,
            '\nListening on',
                config.getSocket() || config.server.host + ':' + config.server.port,
            '\nUrl configured as:',
            config.url,
            '\nCtrl+C to shut down'.grey
        );
    }

    function shutdown() {
        console.log('\nGhost has shut down'.red);
        if (process.env.NODE_ENV === 'production') {
            console.log(
                '\nYour blog is now offline'
            );
        } else {
            console.log(
                '\nGhost was running for',
                Math.round(process.uptime()),
                'seconds'
            );
        }
        process.exit(0);
    }
    // ensure that Ghost exits correctly on Ctrl+C and SIGTERM
//    process.
//        removeAllListeners('SIGINT').
//		    on('SIGINT', shutdown).
//        removeAllListeners('SIGTERM').
//		    on('SIGTERM', shutdown);
};

GhostServer.prototype.logShutdownMessages = function () {
    console.log('Ghost is closing connections'.red);
};

GhostServer.prototype.logUpgradeWarning = function () {
    errors.logWarn(
        'Ghost no longer starts automatically when using it as an npm module.',
        'If you\'re seeing this message, you may need to update your custom code.',
        'Please see the docs at http://tinyurl.com/npm-upgrade for more information.'
    );
};

/**
 * Starts the ghost server listening on the configured port.
 * Alternatively you can pass in your own express instance and let Ghost
 * start listening for you.
 * @param  {Object=} externalApp Optional express app instance.
 * @return {Promise}
 */
GhostServer.prototype.start = function (externalApp) {
    var self = this,
        rootApp = externalApp ? externalApp : self.rootApp;

    // ## Start Ghost App
    return new Promise(function (resolve) {
        var socketConfig = config.getSocket();

        if (socketConfig) {
            // Make sure the socket is gone before trying to create another
            try {
                fs.unlinkSync(socketConfig.path);
            } catch (e) {
                // We can ignore this.
            }

            self.httpServer = rootApp.listen(socketConfig.path);

            fs.chmod(socketConfig.path, socketConfig.permissions);
        } else {
            self.httpServer = rootApp.listen(
                config.server.port,
                config.server.host
            );
        }

        self.httpServer.on('error', function (error) {
            if (error.errno === 'EADDRINUSE') {
                errors.logError(
                    '(EADDRINUSE) Cannot start Ghost.',
                    'Port ' + config.server.port + ' is already in use by another program.',
                    'Is another Ghost instance already running?'
                );
            } else {
                errors.logError(
                    '(Code: ' + error.errno + ')',
                    'There was an error starting your server.',
                    'Please use the error code above to search for a solution.'
                );
            }
            process.exit(-1);
        });
        self.httpServer.on('connection', self.connection.bind(self));
        self.httpServer.on('listening', function () {
            self.logStartMessages();
            clearTimeout(self.upgradeWarning);
            resolve(self);
        });
    });
};

// Returns a promise that will be fulfilled when the server stops.
// If the server has not been started, the promise will be fulfilled
// immediately
GhostServer.prototype.stop = function () {
    var self = this;

    return new Promise(function (resolve) {
        if (self.httpServer === null) {
            resolve(self);
        } else {
            self.httpServer.close(function () {
                self.httpServer = null;
                self.logShutdownMessages();
                resolve(self);
            });

            self.closeConnections();
        }
    });
};

// Restarts the ghost application
GhostServer.prototype.restart = function () {
    return this.stop().then(this.start.bind(this));
};

// To be called after `stop`
GhostServer.prototype.hammertime = function () {
    console.log('Can\'t touch this'.green);

    return Promise.resolve(this);
};

module.exports = GhostServer;
<|endoftext|>


<commit_before>/**
 * @license
 * Visual Blocks Editor
 *
 * Copyright 2016 Google Inc.
 * https://developers.google.com/blockly/
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @fileoverview Events fired as a result of actions in Blockly's editor.
 * @author fraser@google.com (Neil Fraser)
 */
'use strict';

/**
 * Events fired as a result of actions in Blockly's editor.
 * @namespace Blockly.Events
 */
goog.provide('Blockly.Events');

goog.require('goog.array');
goog.require('goog.math.Coordinate');

/**
 * Group ID for new events.  Grouped events are indivisible.
 * @type {string}
 * @private
 */
Blockly.Events.group_ = '';

/**
 * Sets whether events should be added to the undo stack.
 * @type {boolean}
 */
Blockly.Events.recordUndo = true;

/**
 * Allow change events to be created and fired.
 * @type {number}
 * @private
 */
Blockly.Events.disabled_ = 0;

/**
 * Name of event that creates a block. Will be deprecated for BLOCK_CREATE.
 * @const
 */
Blockly.Events.CREATE = 'create';

/**
 * Name of event that creates a block.
 * @const
 */
Blockly.Events.BLOCK_CREATE = Blockly.Events.CREATE;

/**
 * Name of event that deletes a block. Will be deprecated for BLOCK_DELETE.
 * @const
 */
Blockly.Events.DELETE = 'delete';

/**
 * Name of event that deletes a block.
 * @const
 */
Blockly.Events.BLOCK_DELETE = Blockly.Events.DELETE;

/**
 * Name of event that changes a block. Will be deprecated for BLOCK_CHANGE.
 * @const
 */
Blockly.Events.CHANGE = 'change';

/**
 * Name of event that changes a block.
 * @const
 */
Blockly.Events.BLOCK_CHANGE = Blockly.Events.CHANGE;

/**
 * Name of event that moves a block. Will be deprecated for BLOCK_MOVE.
 * @const
 */
Blockly.Events.MOVE = 'move';

/**
 * Name of event that drags a block outside of or into the blocks workspace
 * @const
 */
Blockly.Events.DRAG_OUTSIDE = 'dragOutside';

/**
 * Name of event that ends a block drag
 * @const
 */
Blockly.Events.END_DRAG = 'endDrag';

/**
 * Name of event that moves a block.
 * @const
 */
Blockly.Events.BLOCK_MOVE = Blockly.Events.MOVE;

/**
 * Name of event that creates a variable.
 * @const
 */
Blockly.Events.VAR_CREATE = 'var_create';

/**
 * Name of event that deletes a variable.
 * @const
 */
Blockly.Events.VAR_DELETE = 'var_delete';

/**
 * Name of event that renames a variable.
 * @const
 */
Blockly.Events.VAR_RENAME = 'var_rename';

/**
 * Name of event that creates a variable.
 * @const
 */
Blockly.Events.COMMENT_CREATE = 'comment_create';

/**
 * Name of event that creates a variable.
 * @const
 */
Blockly.Events.COMMENT_MOVE = 'comment_move';

/**
 * Name of event that creates a variable.
 * @const
 */
Blockly.Events.COMMENT_CHANGE = 'comment_change';

/**
 * Name of event that creates a variable.
 * @const
 */
Blockly.Events.COMMENT_DELETE = 'comment_delete';

/**
 * Name of event that records a UI change.
 * @const
 */
Blockly.Events.UI = 'ui';

/**
 * List of events queued for firing.
 * @private
 */
Blockly.Events.FIRE_QUEUE_ = [];

/**
 * Create a custom event and fire it.
 * @param {!Blockly.Events.Abstract} event Custom data for event.
 */
Blockly.Events.fire = function(event) {
  if (!Blockly.Events.isEnabled()) {
    return;
  }
  if (!Blockly.Events.FIRE_QUEUE_.length) {
    // First event added; schedule a firing of the event queue.
    setTimeout(Blockly.Events.fireNow_, 0);
  }
  Blockly.Events.FIRE_QUEUE_.push(event);
};

/**
 * Fire all queued events.
 * @private
 */
Blockly.Events.fireNow_ = function() {
  var queue = Blockly.Events.filter(Blockly.Events.FIRE_QUEUE_, true);
  Blockly.Events.FIRE_QUEUE_.length = 0;
  for (var i = 0, event; event = queue[i]; i++) {
    var workspace = Blockly.Workspace.getById(event.workspaceId);
    if (workspace) {
      workspace.fireChangeListener(event);
    }
  }
};

/**
 * Filter the queued events and merge duplicates.
 * @param {!Array.<!Blockly.Events.Abstract>} queueIn Array of events.
 * @param {boolean} forward True if forward (redo), false if backward (undo).
 * @return {!Array.<!Blockly.Events.Abstract>} Array of filtered events.
 */
Blockly.Events.filter = function(queueIn, forward) {
  var queue = goog.array.clone(queueIn);
  if (!forward) {
    // Undo is merged in reverse order.
    queue.reverse();
  }
  var mergedQueue = [];
  var hash = Object.create(null);
  // Merge duplicates.
  for (var i = 0, event; event = queue[i]; i++) {
    if (!event.isNull()) {
      var key = [event.type, event.blockId, event.workspaceId].join(' ');
      var lastEvent = hash[key];
      if (!lastEvent) {
        hash[key] = event;
        mergedQueue.push(event);
      } else if (event.type == Blockly.Events.MOVE) {
        // Merge move events.
        lastEvent.newParentId = event.newParentId;
        lastEvent.newInputName = event.newInputName;
        lastEvent.newCoordinate = event.newCoordinate;
      } else if (event.type == Blockly.Events.CHANGE &&
          event.element == lastEvent.element &&
          event.name == lastEvent.name) {
        // Merge change events.
        lastEvent.newValue = event.newValue;
      } else if (event.type == Blockly.Events.UI &&
          event.element == 'click' &&
          (lastEvent.element == 'commentOpen' ||
           lastEvent.element == 'mutatorOpen' ||
           lastEvent.element == 'warningOpen')) {
        // Merge click events.
        lastEvent.newValue = event.newValue;
      } else {
        // Collision: newer events should merge into this event to maintain order
        hash[key] = event;
        mergedQueue.push(event);
      }
    }
  }
  // Filter out any events that have become null due to merging.
  queue = mergedQueue.filter(function(e) { return !e.isNull(); });
  if (!forward) {
    // Restore undo order.
    queue.reverse();
  }
  // Move mutation events to the top of the queue.
  // Intentionally skip first event.
  for (var i = 1, event; event = queue[i]; i++) {
    if (event.type == Blockly.Events.CHANGE &&
        event.element == 'mutation') {
      queue.unshift(queue.splice(i, 1)[0]);
    }
  }
  return queue;
};

/**
 * Modify pending undo events so that when they are fired they don't land
 * in the undo stack.  Called by Blockly.Workspace.clearUndo.
 */
Blockly.Events.clearPendingUndo = function() {
  for (var i = 0, event; event = Blockly.Events.FIRE_QUEUE_[i]; i++) {
    event.recordUndo = false;
  }
};

/**
 * Stop sending events.  Every call to this function MUST also call enable.
 */
Blockly.Events.disable = function() {
  Blockly.Events.disabled_++;
};

/**
 * Start sending events.  Unless events were already disabled when the
 * corresponding call to disable was made.
 */
Blockly.Events.enable = function() {
  Blockly.Events.disabled_--;
};

/**
 * Returns whether events may be fired or not.
 * @return {boolean} True if enabled.
 */
Blockly.Events.isEnabled = function() {
  return Blockly.Events.disabled_ == 0;
};

/**
 * Current group.
 * @return {string} ID string.
 */
Blockly.Events.getGroup = function() {
  return Blockly.Events.group_;
};

/**
 * Start or stop a group.
 * @param {boolean|string} state True to start new group, false to end group.
 *   String to set group explicitly.
 */
Blockly.Events.setGroup = function(state) {
  if (typeof state == 'boolean') {
    Blockly.Events.group_ = state ? Blockly.utils.genUid() : '';
  } else {
    Blockly.Events.group_ = state;
  }
};

/**
 * Compute a list of the IDs of the specified block and all its descendants.
 * @param {!Blockly.Block} block The root block.
 * @return {!Array.<string>} List of block IDs.
 * @private
 */
Blockly.Events.getDescendantIds_ = function(block) {
  var ids = [];
  var descendants = block.getDescendants(false);
  for (var i = 0, descendant; descendant = descendants[i]; i++) {
    ids[i] = descendant.id;
  }
  return ids;
};

/**
 * Decode the JSON into an event.
 * @param {!Object} json JSON representation.
 * @param {!Blockly.Workspace} workspace Target workspace for event.
 * @return {!Blockly.Events.Abstract} The event represented by the JSON.
 */
Blockly.Events.fromJson = function(json, workspace) {
  var event;
  switch (json.type) {
    case Blockly.Events.CREATE:
      event = new Blockly.Events.Create(null);
      break;
    case Blockly.Events.DELETE:
      event = new Blockly.Events.Delete(null);
      break;
    case Blockly.Events.CHANGE:
      event = new Blockly.Events.Change(null);
      break;
    case Blockly.Events.MOVE:
      event = new Blockly.Events.Move(null);
      break;
    case Blockly.Events.VAR_CREATE:
      event = new Blockly.Events.VarCreate(null);
      break;
    case Blockly.Events.VAR_DELETE:
      event = new Blockly.Events.VarDelete(null);
      break;
    case Blockly.Events.VAR_RENAME:
      event = new Blockly.Events.VarRename(null);
      break;
    case Blockly.Events.UI:
      event = new Blockly.Events.Ui(null);
      break;
    case Blockly.Events.DRAG_OUTSIDE:
      event = new Blockly.Events.DragBlockOutside(null);
      break;
    case Blockly.Events.END_DRAG:
      event = new Blockly.Events.EndBlockDrag(null, false);
      break;
    default:
      throw 'Unknown event type.';
  }
  event.fromJson(json);
  event.workspaceId = workspace.id;
  return event;
};

/**
 * Enable/disable a block depending on whether it is properly connected.
 * Use this on applications where all blocks should be connected to a top block.
 * Recommend setting the 'disable' option to 'false' in the config so that
 * users don't try to reenable disabled orphan blocks.
 * @param {!Blockly.Events.Abstract} event Custom data for event.
 */
Blockly.Events.disableOrphans = function(event) {
  if (event.type == Blockly.Events.MOVE ||
      event.type == Blockly.Events.CREATE) {
    Blockly.Events.disable();
    var workspace = Blockly.Workspace.getById(event.workspaceId);
    var block = workspace.getBlockById(event.blockId);
    if (block) {
      if (block.getParent() && !block.getParent().disabled) {
        var children = block.getDescendants(false);
        for (var i = 0, child; child = children[i]; i++) {
          child.setDisabled(false);
        }
      } else if ((block.outputConnection || block.previousConnection) &&
                 !workspace.isDragging()) {
        do {
          block.setDisabled(true);
          block = block.getNextBlock();
        } while (block);
      }
    }
    Blockly.Events.enable();
  }
};
<commit_msg>Fix comments for names of comment events.<commit_after>/**
 * @license
 * Visual Blocks Editor
 *
 * Copyright 2016 Google Inc.
 * https://developers.google.com/blockly/
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @fileoverview Events fired as a result of actions in Blockly's editor.
 * @author fraser@google.com (Neil Fraser)
 */
'use strict';

/**
 * Events fired as a result of actions in Blockly's editor.
 * @namespace Blockly.Events
 */
goog.provide('Blockly.Events');

goog.require('goog.array');
goog.require('goog.math.Coordinate');

/**
 * Group ID for new events.  Grouped events are indivisible.
 * @type {string}
 * @private
 */
Blockly.Events.group_ = '';

/**
 * Sets whether events should be added to the undo stack.
 * @type {boolean}
 */
Blockly.Events.recordUndo = true;

/**
 * Allow change events to be created and fired.
 * @type {number}
 * @private
 */
Blockly.Events.disabled_ = 0;

/**
 * Name of event that creates a block. Will be deprecated for BLOCK_CREATE.
 * @const
 */
Blockly.Events.CREATE = 'create';

/**
 * Name of event that creates a block.
 * @const
 */
Blockly.Events.BLOCK_CREATE = Blockly.Events.CREATE;

/**
 * Name of event that deletes a block. Will be deprecated for BLOCK_DELETE.
 * @const
 */
Blockly.Events.DELETE = 'delete';

/**
 * Name of event that deletes a block.
 * @const
 */
Blockly.Events.BLOCK_DELETE = Blockly.Events.DELETE;

/**
 * Name of event that changes a block. Will be deprecated for BLOCK_CHANGE.
 * @const
 */
Blockly.Events.CHANGE = 'change';

/**
 * Name of event that changes a block.
 * @const
 */
Blockly.Events.BLOCK_CHANGE = Blockly.Events.CHANGE;

/**
 * Name of event that moves a block. Will be deprecated for BLOCK_MOVE.
 * @const
 */
Blockly.Events.MOVE = 'move';

/**
 * Name of event that drags a block outside of or into the blocks workspace
 * @const
 */
Blockly.Events.DRAG_OUTSIDE = 'dragOutside';

/**
 * Name of event that ends a block drag
 * @const
 */
Blockly.Events.END_DRAG = 'endDrag';

/**
 * Name of event that moves a block.
 * @const
 */
Blockly.Events.BLOCK_MOVE = Blockly.Events.MOVE;

/**
 * Name of event that creates a variable.
 * @const
 */
Blockly.Events.VAR_CREATE = 'var_create';

/**
 * Name of event that deletes a variable.
 * @const
 */
Blockly.Events.VAR_DELETE = 'var_delete';

/**
 * Name of event that renames a variable.
 * @const
 */
Blockly.Events.VAR_RENAME = 'var_rename';

/**
 * Name of event that creates a comment.
 * @const
 */
Blockly.Events.COMMENT_CREATE = 'comment_create';

/**
 * Name of event that moves a comment.
 * @const
 */
Blockly.Events.COMMENT_MOVE = 'comment_move';

/**
 * Name of event that changes a comment's property
 * (text content, size, or minimized state).
 * @const
 */
Blockly.Events.COMMENT_CHANGE = 'comment_change';

/**
 * Name of event that deletes a comment.
 * @const
 */
Blockly.Events.COMMENT_DELETE = 'comment_delete';

/**
 * Name of event that records a UI change.
 * @const
 */
Blockly.Events.UI = 'ui';

/**
 * List of events queued for firing.
 * @private
 */
Blockly.Events.FIRE_QUEUE_ = [];

/**
 * Create a custom event and fire it.
 * @param {!Blockly.Events.Abstract} event Custom data for event.
 */
Blockly.Events.fire = function(event) {
  if (!Blockly.Events.isEnabled()) {
    return;
  }
  if (!Blockly.Events.FIRE_QUEUE_.length) {
    // First event added; schedule a firing of the event queue.
    setTimeout(Blockly.Events.fireNow_, 0);
  }
  Blockly.Events.FIRE_QUEUE_.push(event);
};

/**
 * Fire all queued events.
 * @private
 */
Blockly.Events.fireNow_ = function() {
  var queue = Blockly.Events.filter(Blockly.Events.FIRE_QUEUE_, true);
  Blockly.Events.FIRE_QUEUE_.length = 0;
  for (var i = 0, event; event = queue[i]; i++) {
    var workspace = Blockly.Workspace.getById(event.workspaceId);
    if (workspace) {
      workspace.fireChangeListener(event);
    }
  }
};

/**
 * Filter the queued events and merge duplicates.
 * @param {!Array.<!Blockly.Events.Abstract>} queueIn Array of events.
 * @param {boolean} forward True if forward (redo), false if backward (undo).
 * @return {!Array.<!Blockly.Events.Abstract>} Array of filtered events.
 */
Blockly.Events.filter = function(queueIn, forward) {
  var queue = goog.array.clone(queueIn);
  if (!forward) {
    // Undo is merged in reverse order.
    queue.reverse();
  }
  var mergedQueue = [];
  var hash = Object.create(null);
  // Merge duplicates.
  for (var i = 0, event; event = queue[i]; i++) {
    if (!event.isNull()) {
      var key = [event.type, event.blockId, event.workspaceId].join(' ');
      var lastEvent = hash[key];
      if (!lastEvent) {
        hash[key] = event;
        mergedQueue.push(event);
      } else if (event.type == Blockly.Events.MOVE) {
        // Merge move events.
        lastEvent.newParentId = event.newParentId;
        lastEvent.newInputName = event.newInputName;
        lastEvent.newCoordinate = event.newCoordinate;
      } else if (event.type == Blockly.Events.CHANGE &&
          event.element == lastEvent.element &&
          event.name == lastEvent.name) {
        // Merge change events.
        lastEvent.newValue = event.newValue;
      } else if (event.type == Blockly.Events.UI &&
          event.element == 'click' &&
          (lastEvent.element == 'commentOpen' ||
           lastEvent.element == 'mutatorOpen' ||
           lastEvent.element == 'warningOpen')) {
        // Merge click events.
        lastEvent.newValue = event.newValue;
      } else {
        // Collision: newer events should merge into this event to maintain order
        hash[key] = event;
        mergedQueue.push(event);
      }
    }
  }
  // Filter out any events that have become null due to merging.
  queue = mergedQueue.filter(function(e) { return !e.isNull(); });
  if (!forward) {
    // Restore undo order.
    queue.reverse();
  }
  // Move mutation events to the top of the queue.
  // Intentionally skip first event.
  for (var i = 1, event; event = queue[i]; i++) {
    if (event.type == Blockly.Events.CHANGE &&
        event.element == 'mutation') {
      queue.unshift(queue.splice(i, 1)[0]);
    }
  }
  return queue;
};

/**
 * Modify pending undo events so that when they are fired they don't land
 * in the undo stack.  Called by Blockly.Workspace.clearUndo.
 */
Blockly.Events.clearPendingUndo = function() {
  for (var i = 0, event; event = Blockly.Events.FIRE_QUEUE_[i]; i++) {
    event.recordUndo = false;
  }
};

/**
 * Stop sending events.  Every call to this function MUST also call enable.
 */
Blockly.Events.disable = function() {
  Blockly.Events.disabled_++;
};

/**
 * Start sending events.  Unless events were already disabled when the
 * corresponding call to disable was made.
 */
Blockly.Events.enable = function() {
  Blockly.Events.disabled_--;
};

/**
 * Returns whether events may be fired or not.
 * @return {boolean} True if enabled.
 */
Blockly.Events.isEnabled = function() {
  return Blockly.Events.disabled_ == 0;
};

/**
 * Current group.
 * @return {string} ID string.
 */
Blockly.Events.getGroup = function() {
  return Blockly.Events.group_;
};

/**
 * Start or stop a group.
 * @param {boolean|string} state True to start new group, false to end group.
 *   String to set group explicitly.
 */
Blockly.Events.setGroup = function(state) {
  if (typeof state == 'boolean') {
    Blockly.Events.group_ = state ? Blockly.utils.genUid() : '';
  } else {
    Blockly.Events.group_ = state;
  }
};

/**
 * Compute a list of the IDs of the specified block and all its descendants.
 * @param {!Blockly.Block} block The root block.
 * @return {!Array.<string>} List of block IDs.
 * @private
 */
Blockly.Events.getDescendantIds_ = function(block) {
  var ids = [];
  var descendants = block.getDescendants(false);
  for (var i = 0, descendant; descendant = descendants[i]; i++) {
    ids[i] = descendant.id;
  }
  return ids;
};

/**
 * Decode the JSON into an event.
 * @param {!Object} json JSON representation.
 * @param {!Blockly.Workspace} workspace Target workspace for event.
 * @return {!Blockly.Events.Abstract} The event represented by the JSON.
 */
Blockly.Events.fromJson = function(json, workspace) {
  var event;
  switch (json.type) {
    case Blockly.Events.CREATE:
      event = new Blockly.Events.Create(null);
      break;
    case Blockly.Events.DELETE:
      event = new Blockly.Events.Delete(null);
      break;
    case Blockly.Events.CHANGE:
      event = new Blockly.Events.Change(null);
      break;
    case Blockly.Events.MOVE:
      event = new Blockly.Events.Move(null);
      break;
    case Blockly.Events.VAR_CREATE:
      event = new Blockly.Events.VarCreate(null);
      break;
    case Blockly.Events.VAR_DELETE:
      event = new Blockly.Events.VarDelete(null);
      break;
    case Blockly.Events.VAR_RENAME:
      event = new Blockly.Events.VarRename(null);
      break;
    case Blockly.Events.UI:
      event = new Blockly.Events.Ui(null);
      break;
    case Blockly.Events.DRAG_OUTSIDE:
      event = new Blockly.Events.DragBlockOutside(null);
      break;
    case Blockly.Events.END_DRAG:
      event = new Blockly.Events.EndBlockDrag(null, false);
      break;
    default:
      throw 'Unknown event type.';
  }
  event.fromJson(json);
  event.workspaceId = workspace.id;
  return event;
};

/**
 * Enable/disable a block depending on whether it is properly connected.
 * Use this on applications where all blocks should be connected to a top block.
 * Recommend setting the 'disable' option to 'false' in the config so that
 * users don't try to reenable disabled orphan blocks.
 * @param {!Blockly.Events.Abstract} event Custom data for event.
 */
Blockly.Events.disableOrphans = function(event) {
  if (event.type == Blockly.Events.MOVE ||
      event.type == Blockly.Events.CREATE) {
    Blockly.Events.disable();
    var workspace = Blockly.Workspace.getById(event.workspaceId);
    var block = workspace.getBlockById(event.blockId);
    if (block) {
      if (block.getParent() && !block.getParent().disabled) {
        var children = block.getDescendants(false);
        for (var i = 0, child; child = children[i]; i++) {
          child.setDisabled(false);
        }
      } else if ((block.outputConnection || block.previousConnection) &&
                 !workspace.isDragging()) {
        do {
          block.setDisabled(true);
          block = block.getNextBlock();
        } while (block);
      }
    }
    Blockly.Events.enable();
  }
};
<|endoftext|>


<commit_before>angular.module('charViewer.character.service', [])

    .service('CharacterService', ['$http', CharacterService])

function CharacterService($http) {
    var service = this;

    service.getAll = function() {
        return $http.get('/characters')
    };

    service.getById = function(id) {
        return $http.get('/characters/' + id)
    };
}
<commit_msg>add to TODO for some kind of caching.<commit_after>angular.module('charViewer.character.service', [])

    .service('CharacterService', ['$http', CharacterService]);

function CharacterService($http) {
    /*
     * TODO implement caching
     * The service should probably implement some kind of caching. We should avoid loading the character with each method
     * call instead we should have a cache, that stores the characters. This could be as simple as some kind of Map
     * storing ids and already loaded character.
     */

    var service = this;

    service.getAll = function() {
        return $http.get('/characters')
    };

    service.getById = function(id) {
        return $http.get('/characters/' + id)
    };
}
<|endoftext|>


<commit_before><commit_msg>add desc<commit_after>// Each new term in the Fibonacci sequence is generated by adding the previous two terms.
// By starting with 1 and 2, the first 10 terms will be:

// 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, ...

// By considering the terms in the Fibonacci sequence whose values do not exceed four million,
// find the sum of the even-valued terms.

<|endoftext|>


<commit_before>/* ex: set tabstop=4 expandtab: */

// global imports
var fs = require('fs');
var md = require('markdown').markdown;
var mustache = require('mustache');
var exec  = require('child_process').exec;
var kompressor = require('htmlKompressor');
// app imports
var blocks = require('./blocks/block');
var appUtil   = require('./appUtil');
var appConfig   = require('./appConfig');

// Define a couple of templates that are used multiple times or
// are text strings that should be maintainable and not spread all over the code.
var templates = {
    ERROR_MARKDOWN_SYNTAX:{
        TITLE: 'ERROR: Syntax error in "{{markdownFileName}}", stopped parsing block "{{blockTitle}}".',
        HINT: 'In the following find explaination about the syntax of this markdown block.'
    },
    WARNING_USING_DEFAULT_RENDERER:{
        TITLE: 'WARNING: No render method given for block "{{blockTitle}}" in "{{tplFileName}}.html" using the default method "simpleBox".',
        HINT: 'If you want to set a render method use mustache comments for it, like so: { {!simpleBox} } or { {!listBox} }.'
    }
};

var errorMessages = []; // Store all the error messages and concat them later.

// Simple mixin, note: it does not check for hasOwnProperty!
function mixin(target, obj){
    for (var prop in obj){
        target[prop] = obj[prop];
    }
}

function readDir(dir){
    // Summary:
    //      Reads dir and passes found HTML files to renderView

    fs.readdir(dir, function(err, data){
        appUtil.statusLog('Reading HTML files from\t' + dir);
        data && data.forEach(function(item){
            if (item.indexOf('.html') > -1){
                if (appConfig.isVerbose) appUtil.statusLog("\t...." + item);
                renderView(item.split('.html')[0]);
            }
        });
    });
}

function clean(callb){
    // Summary:
    //      1. Cleaning up release dir
    //      2. Copying media and static files to release dir

    appUtil.statusLog('Deleting\t' + appConfig.releaseDir + '/*');
    exec('rm -rf ' + appConfig.releaseDir + '/*', function(err, stdout, stderr){
        appUtil.statusLog('Copying\t' + appConfig.srcDir + '/static to' + appConfig.releaseDir + '/');
        appUtil.statusLog('Copying\t' + appConfig.contentDir + '/media to ' + appConfig.releaseDir + '/');
        var cmd = 'cp -r ' + appConfig.srcDir + '/static ' + appConfig.releaseDir + '/;';
        cmd += 'cp -r ' + appConfig.contentDir + '/media  ' + appConfig.releaseDir + '/';
        exec(cmd,
            function (err, stdout, stderr) {
                if (err !== null) {
                    console.log('Error copying (' + cmd + ') static files: ' + err);
                }else{
                    console.log('Copied static files');
                    callb && callb();
                }
            }
        );
    })
}

// Summary:
//      1. Reads HTML file
//      2. Reads Markdown file
//      3. Deconstructs Markdown tree into blocks
//          3.1 Determines proper function which returns Mustache view
//      4. Renders HTML
//          4.1 Writes output to filesystem

function renderView(name){
    // Summary:
    //      1.

    fs.readFile(appConfig.srcDir + '/' + name + '.html', 'utf-8', function(err, template){
        if (appConfig.mapping[name]){
            var map = appConfig.mapping[name];
            if (Object.prototype.toString.apply(map) === '[object Array]'){
                map.forEach(function(item){
                    if (item.indexOf('.md') > -1){
                        var fileName = item.split('.md')[0];
                        readMarkdown(template, appConfig.contentDir + '/' + fileName + '.md', appConfig.releaseDir + '/' + fileName + '.html', name);
                    }else{
                        readContentDir(item, template);
                    }
                });
            }else if (Object.prototype.toString.apply(map) === '[object String]'){
                if (map.indexOf('.md') > -1){
                    var fileName = item.split('.md')[0];
                    readMarkdown(template, appConfig.contentDir + '/' + fileName + '.md', appConfig.releaseDir + '/' + fileName + '.html', name);
                }else{
                    readContentDir(map, template);
                }
            }
        }else{
            readMarkdown(template, appConfig.contentDir + '/' + name + '.md', appConfig.releaseDir + '/' + name + '.html', name);
        }

    });
}

function readContentDir(name, template){
    // Summary:
    //      2.

    exec('mkdir ' + appConfig.releaseDir + name, function(){
        fs.readdir(appConfig.contentDir + name, function(err, data){
            data && data.forEach(function(item){
                if (item.indexOf('.md') > -1){
                    var fileName = item.split('.md')[0];
                    readMarkdown(template, appConfig.contentDir + name + '/' + fileName+ '.md', appConfig.releaseDir + name + '/' + fileName + '.html', fileName);
                }
            });
        });
    });
}

function readMarkdown(template, input, output, name){
    // Summary:
    //      3.

    appUtil.statusLog('Reading and parsing \t' + appConfig.srcDir + '/' + name + '.md');
    fs.readFile(input, 'utf-8', function(arr, markdown){
        var tree = appUtil.parseMarkdown(markdown);
        var tplContent = { errorMessage:"" };
        tree.forEach(function(block, index){
            // Based on the tree array we create mustache blocks with custom callbacks
            // handling the mapping of Markdown elements to Mustache tags.
            tplContent['block' + index] = function(){
                return function(text, parse){
                    // 3.1
                    // Looking for mustache comment '{{!boxName}}' which specifies the box's render method
                    // we want to use, if none found we default to "simpleBox".
                    var boxMatch = text.match(/[\s\S]*{{!(\w+)}}/);
                    var func;
                    if (boxMatch){
                        func = boxMatch[1];
                    } else {
                        func = "simpleBox";
                        appUtil.consoleLog([
                            {tpl: templates.WARNING_USING_DEFAULT_RENDERER.TITLE, vars:{tplFileName:input, blockTitle:block[0][1]}},
                            {tpl: templates.WARNING_USING_DEFAULT_RENDERER.HINT, verbose:true}
                        ]);
                    }
                    try {
                        var parsedBlock = blocks[func]([].concat(block)); // Make a copy of the block, since the method modifies it inside.
                        return mustache.to_html(text, parsedBlock);
                    }catch(e){
                        var errorMsg = {
                            title: mustache.to_html(templates.ERROR_MARKDOWN_SYNTAX.TITLE, {markdownFileName:input, blockTitle:block[0][1]}),
                            text: blocks[func].__docs__
                        };
                        errorMessages.push(errorMsg);
                        appUtil.consoleLog([
                            {msg: errorMsg.title},
                            {msg: '\n' + mustache.to_html(templates.ERROR_MARKDOWN_SYNTAX.HINT, {}), verbose:true},
                            {msg: '\n' + errorMsg.text, verbose:true}
                        ]);
                        return "";
                    }
                }
            }
        });
        writeView(output, template, tplContent);
    });
}

function writeView(output, template, tplContent){
    // Summary:
    //      4.

    if (errorMessages.length){
        var titles = [];
        var texts = [];
        errorMessages.forEach(function(m){ titles.push(m.title); texts.push(m.text); });
        tplContent.errorMessage = titles.join("\n") + "\n\n" + texts.join("\n************************\n");
    }

    var out = mustache.to_html(template, tplContent);

    // The markdown parser returns elements which don't require a closing tag with a closing tag
    // Converting

 to

    var voidTags = /^area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr$/i;
    out = out.replace(/<(\w+)([^<>]*)><\/\1>/g, function(_, tagName, attribs){
        var markup = "<" + tagName + attribs;
        markup += voidTags.test(tagName) ? " />" : "></" + tagName + ">";
        return markup;
    });

    // 4.1
    // If debug is true we compress the HTML and write it to the filesystem
    appUtil.statusLog('Writing\t' + output);
    fs.writeFile(output, appConfig.isDebug ? out : kompressor(out, true), encoding='utf8');
}

// Description:
//      This build tool renders HTML pages based on markdown pages and HTML templates
//      The default build required to have for instance a home.html template and a home.md
//      content file. Optionally you can define a mapping of one HTML to several markdown files.
//
//      The mapping must map to a directory e.g.
//          { 'team': 'team/' }
//      would map all Markdown files in the /team directory to the team.html template.
//
//      Application Flow:
//
//      1. Cleanup previous build
//      2. Read the contents of the source directory
//      3. Read the Markdown content (optionally iterative if mapping is present)
//      4. Write out result

console.log('Welcome to the epic');
appConfig.printValues();
clean(function(){
    // Reading src directory and passing found HTML file names to renderView()
    readDir(appConfig.srcDir);
});
<commit_msg>Added file prop to rendered templated so we can identify pages<commit_after>/* ex: set tabstop=4 expandtab: */

// global imports
var fs = require('fs');
var md = require('markdown').markdown;
var mustache = require('mustache');
var exec  = require('child_process').exec;
var kompressor = require('htmlKompressor');
// app imports
var blocks = require('./blocks/block');
var appUtil   = require('./appUtil');
var appConfig   = require('./appConfig');

// Define a couple of templates that are used multiple times or
// are text strings that should be maintainable and not spread all over the code.
var templates = {
    ERROR_MARKDOWN_SYNTAX:{
        TITLE: 'ERROR: Syntax error in "{{markdownFileName}}", stopped parsing block "{{blockTitle}}".',
        HINT: 'In the following find explaination about the syntax of this markdown block.'
    },
    WARNING_USING_DEFAULT_RENDERER:{
        TITLE: 'WARNING: No render method given for block "{{blockTitle}}" in "{{tplFileName}}.html" using the default method "simpleBox".',
        HINT: 'If you want to set a render method use mustache comments for it, like so: { {!simpleBox} } or { {!listBox} }.'
    }
};

var errorMessages = []; // Store all the error messages and concat them later.

// Simple mixin, note: it does not check for hasOwnProperty!
function mixin(target, obj){
    for (var prop in obj){
        target[prop] = obj[prop];
    }
}

function readDir(dir){
    // Summary:
    //      Reads dir and passes found HTML files to renderView

    fs.readdir(dir, function(err, data){
        appUtil.statusLog('Reading HTML files from\t' + dir);
        data && data.forEach(function(item){
            if (item.indexOf('.html') > -1){
                if (appConfig.isVerbose) appUtil.statusLog("\t...." + item);
                renderView(item.split('.html')[0]);
            }
        });
    });
}

function clean(callb){
    // Summary:
    //      1. Cleaning up release dir
    //      2. Copying media and static files to release dir

    appUtil.statusLog('Deleting\t' + appConfig.releaseDir + '/*');
    exec('rm -rf ' + appConfig.releaseDir + '/*', function(err, stdout, stderr){
        appUtil.statusLog('Copying\t' + appConfig.srcDir + '/static to' + appConfig.releaseDir + '/');
        appUtil.statusLog('Copying\t' + appConfig.contentDir + '/media to ' + appConfig.releaseDir + '/');
        var cmd = 'cp -r ' + appConfig.srcDir + '/static ' + appConfig.releaseDir + '/;';
        cmd += 'cp -r ' + appConfig.contentDir + '/media  ' + appConfig.releaseDir + '/';
        exec(cmd,
            function (err, stdout, stderr) {
                if (err !== null) {
                    console.log('Error copying (' + cmd + ') static files: ' + err);
                }else{
                    console.log('Copied static files');
                    callb && callb();
                }
            }
        );
    })
}

// Summary:
//      1. Reads HTML file
//      2. Reads Markdown file
//      3. Deconstructs Markdown tree into blocks
//          3.1 Determines proper function which returns Mustache view
//      4. Renders HTML
//          4.1 Writes output to filesystem

function renderView(name){
    // Summary:
    //      1.

    fs.readFile(appConfig.srcDir + '/' + name + '.html', 'utf-8', function(err, template){
        if (appConfig.mapping[name]){
            var map = appConfig.mapping[name];
            if (Object.prototype.toString.apply(map) === '[object Array]'){
                map.forEach(function(item){
                    if (item.indexOf('.md') > -1){
                        var fileName = item.split('.md')[0];
                        readMarkdown(template, appConfig.contentDir + '/' + fileName + '.md', appConfig.releaseDir + '/' + fileName + '.html', name);
                    }else{
                        readContentDir(item, template);
                    }
                });
            }else if (Object.prototype.toString.apply(map) === '[object String]'){
                if (map.indexOf('.md') > -1){
                    var fileName = item.split('.md')[0];
                    readMarkdown(template, appConfig.contentDir + '/' + fileName + '.md', appConfig.releaseDir + '/' + fileName + '.html', name);
                }else{
                    readContentDir(map, template);
                }
            }
        }else{
            readMarkdown(template, appConfig.contentDir + '/' + name + '.md', appConfig.releaseDir + '/' + name + '.html', name);
        }

    });
}

function readContentDir(name, template){
    // Summary:
    //      2.

    exec('mkdir ' + appConfig.releaseDir + name, function(){
        fs.readdir(appConfig.contentDir + name, function(err, data){
            data && data.forEach(function(item){
                if (item.indexOf('.md') > -1){
                    var fileName = item.split('.md')[0];
                    readMarkdown(template, appConfig.contentDir + name + '/' + fileName+ '.md', appConfig.releaseDir + name + '/' + fileName + '.html', fileName);
                }
            });
        });
    });
}

function readMarkdown(template, input, output, name){
    // Summary:
    //      3.

    appUtil.statusLog('Reading and parsing \t' + appConfig.srcDir + '/' + name + '.md');
    fs.readFile(input, 'utf-8', function(arr, markdown){
        var tree = appUtil.parseMarkdown(markdown);
        var tplContent = { errorMessage:"" };
        tplContent[name] = true;
        tree.forEach(function(block, index){
            // Based on the tree array we create mustache blocks with custom callbacks
            // handling the mapping of Markdown elements to Mustache tags.
            tplContent['block' + index] = function(){
                return function(text, parse){
                    // 3.1
                    // Looking for mustache comment '{{!boxName}}' which specifies the box's render method
                    // we want to use, if none found we default to "simpleBox".
                    var boxMatch = text.match(/[\s\S]*{{!(\w+)}}/);
                    var func;
                    if (boxMatch){
                        func = boxMatch[1];
                    } else {
                        func = "simpleBox";
                        appUtil.consoleLog([
                            {tpl: templates.WARNING_USING_DEFAULT_RENDERER.TITLE, vars:{tplFileName:input, blockTitle:block[0][1]}},
                            {tpl: templates.WARNING_USING_DEFAULT_RENDERER.HINT, verbose:true}
                        ]);
                    }
                    try {
                        var parsedBlock = blocks[func]([].concat(block)); // Make a copy of the block, since the method modifies it inside.
                        return mustache.to_html(text, parsedBlock);
                    }catch(e){
                        var errorMsg = {
                            title: mustache.to_html(templates.ERROR_MARKDOWN_SYNTAX.TITLE, {markdownFileName:input, blockTitle:block[0][1]}),
                            text: blocks[func].__docs__
                        };
                        errorMessages.push(errorMsg);
                        appUtil.consoleLog([
                            {msg: errorMsg.title},
                            {msg: '\n' + mustache.to_html(templates.ERROR_MARKDOWN_SYNTAX.HINT, {}), verbose:true},
                            {msg: '\n' + errorMsg.text, verbose:true}
                        ]);
                        return "";
                    }
                }
            }
        });
        writeView(output, template, tplContent);
    });
}

function writeView(output, template, tplContent){
    // Summary:
    //      4.

    if (errorMessages.length){
        var titles = [];
        var texts = [];
        errorMessages.forEach(function(m){ titles.push(m.title); texts.push(m.text); });
        tplContent.errorMessage = titles.join("\n") + "\n\n" + texts.join("\n************************\n");
    }

    var out = mustache.to_html(template, tplContent);

    // The markdown parser returns elements which don't require a closing tag with a closing tag
    // Converting

 to

    var voidTags = /^area|base|br|col|command|embed|hr|img|input|keygen|link|meta|param|source|track|wbr$/i;
    out = out.replace(/<(\w+)([^<>]*)><\/\1>/g, function(_, tagName, attribs){
        var markup = "<" + tagName + attribs;
        markup += voidTags.test(tagName) ? " />" : "></" + tagName + ">";
        return markup;
    });

    // 4.1
    // If debug is true we compress the HTML and write it to the filesystem
    appUtil.statusLog('Writing\t' + output);
    fs.writeFile(output, appConfig.isDebug ? out : kompressor(out, true), encoding='utf8');
}

// Description:
//      This build tool renders HTML pages based on markdown pages and HTML templates
//      The default build required to have for instance a home.html template and a home.md
//      content file. Optionally you can define a mapping of one HTML to several markdown files.
//
//      The mapping must map to a directory e.g.
//          { 'team': 'team/' }
//      would map all Markdown files in the /team directory to the team.html template.
//
//      Application Flow:
//
//      1. Cleanup previous build
//      2. Read the contents of the source directory
//      3. Read the Markdown content (optionally iterative if mapping is present)
//      4. Write out result

console.log('Welcome to the epic');
appConfig.printValues();
clean(function(){
    // Reading src directory and passing found HTML file names to renderView()
    readDir(appConfig.srcDir);
});
<|endoftext|>


<commit_before>/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * @fileoverview
 * Support for basic logging capability for gadgets, meant to replace
 * alert(msg) and window.console.log(msg).
 *
 * Currently only works on browsers with a console (WebKit based browsers
 * or Firefox with Firebug extension).
 *
 * API is designed to be equivalent to existing console.log | warn | error
 * logging APIs supported by Firebug and WebKit based browsers. The only
 * addition is the ability to call gadgets.setLogLevel().
 */

var gadgets = gadgets || {};

/**
 * Log an informational message
 */
gadgets.log = function(message) {
  gadgets.log.logAtLevel(gadgets.log.INFO, message);
};

/**
 * Log a warning
 */
gadgets.warn = function(message) {
  gadgets.log.logAtLevel(gadgets.log.WARNING, message);
};

/**
 * Log an error
 */
gadgets.error = function(message) {
  gadgets.log.logAtLevel(gadgets.log.ERROR, message);
};

/**
 * Sets the log level threshold.
 * @param {Number} logLevel - New log level threshold.
 * @static
 */
gadgets.setLogLevel = function(logLevel) {
  gadgets.log.logLevelThreshold_ = logLevel;
};

/**
 * Logs a log message if output console is available, and log threshold is met.
 * @param {Number} level - the level to log with. Optional, defaults to
 * @param {Object} message - The message to log
 * gadgets.log.INFO.
 * @static
 */
gadgets.log.logAtLevel = function(level, message) {
  if (level < gadgets.log.logLevelThreshold_ || !gadgets.log._console) {
    return;
  }

  var logger;
  var console = gadgets.log.console;

  if (level == gadgets.log.WARNING && console.warn) {
    logger = console.warn;
  } else if (level == gadgets.log.ERROR && console.error) {
    logger = console.error;
  } else {
    logger = console.log;
  }

  logger(message);
};

/**
 * Log level for informational logging.
 * @static
 */
gadgets.log.INFO = 1;

/**
 * Log level for warning logging.
 * @static
 */
gadgets.log.WARNING = 2;

/**
 * Log level for error logging.
 * @static
 */

/**
 * Log level for no logging
 * @static
 */
gadgets.log.NONE = 4;

/**
 * Current log level threshold.
 * @type Number
 * @private
 * @static
 */
gadgets.log.logLevelThreshold_ = gadgets.log.INFO;

/**
 * Console to log to
 * @private
 * @static
 */
gadgets.log.console = window.console ? window.console :
                       window.opera   ? window.opera.postError : undefined;
<commit_msg>Re-patching SHINDIG-1202 (gadgets logging for webkit, avoid using local variable console)<commit_after>/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/**
 * @fileoverview
 * Support for basic logging capability for gadgets, meant to replace
 * alert(msg) and window.console.log(msg).
 *
 * Currently only works on browsers with a console (WebKit based browsers
 * or Firefox with Firebug extension).
 *
 * API is designed to be equivalent to existing console.log | warn | error
 * logging APIs supported by Firebug and WebKit based browsers. The only
 * addition is the ability to call gadgets.setLogLevel().
 */

var gadgets = gadgets || {};

/**
 * Log an informational message
 */
gadgets.log = function(message) {
  gadgets.log.logAtLevel(gadgets.log.INFO, message);
};

/**
 * Log a warning
 */
gadgets.warn = function(message) {
  gadgets.log.logAtLevel(gadgets.log.WARNING, message);
};

/**
 * Log an error
 */
gadgets.error = function(message) {
  gadgets.log.logAtLevel(gadgets.log.ERROR, message);
};

/**
 * Sets the log level threshold.
 * @param {Number} logLevel - New log level threshold.
 * @static
 */
gadgets.setLogLevel = function(logLevel) {
  gadgets.log.logLevelThreshold_ = logLevel;
};

/**
 * Logs a log message if output console is available, and log threshold is met.
 * @param {Number} level - the level to log with. Optional, defaults to
 * @param {Object} message - The message to log
 * gadgets.log.INFO.
 * @static
 */
gadgets.log.logAtLevel = function(level, message) {
  if (level < gadgets.log.logLevelThreshold_ || !gadgets.log._console) {
    return;
  }

  var logger;
  var gadgetconsole = gadgets.log._console;

  if (level == gadgets.log.WARNING && gadgetconsole.warn) {
    gadgetconsole.warn(message)
  } else if (level == gadgets.log.ERROR && gadgetconsole.error) {
    gadgetconsole.error(message);
  } else if (gadgetconsole.log) {
    gadgetconsole.log(message);
  }
};

/**
 * Log level for informational logging.
 * @static
 */
gadgets.log.INFO = 1;

/**
 * Log level for warning logging.
 * @static
 */
gadgets.log.WARNING = 2;

/**
 * Log level for error logging.
 * @static
 */

/**
 * Log level for no logging
 * @static
 */
gadgets.log.NONE = 4;

/**
 * Current log level threshold.
 * @type Number
 * @private
 * @static
 */
gadgets.log.logLevelThreshold_ = gadgets.log.INFO;

/**
 * Console to log to
 * @private
 * @static
 */
gadgets.log._console = window.console ? window.console :
                       window.opera   ? window.opera.postError : undefined;
<|endoftext|>