<commit_before>/* This Source Code Form is subject to the terms of the Mozilla Public
 License, v. 2.0. If a copy of the MPL was not distributed with this
 file, You can obtain one at http://mozilla.org/MPL/2.0/. */

var MusicList = (function() {
  function getListContainer() {
    return $id('music-list-container');
  }

  function retriveName(str) {
    var index = str.lastIndexOf('/');
    if (index < 0) {
      return 'Unkown';
    }
    str = str.substr(index + 1, str.length);

    index = str.lastIndexOf('.');
    if (index < 0) {
      return str;
    }
    return str.substr(0, index);
  }

  //function retriveTimeSpan() {}

  function retriveType(type) {
    var index = type.lastIndexOf('/');
    if (index < 0) {
      return type;
    }
    return type.substr(index + 1, type.length);
  }

  function retriveExtension(str) {
    var index = str.lastIndexOf('.');
    if (index < 0) {
      return '';
    }
    return str.substr(index + 1, str.length);
  }

  function retriveSize(size) {
    return Math.round(100 * size / 1024 / 1024) / 100;
  }

  function createMusicListItem(music, index) {
    var html = '';
    html += '';
    html += '
' + retriveName(music.name) + '
';
    html += '
' + music.metadate.artist + '
';
    html += '
' + music.metadate.album + '
';
    html += '
' + '5:05' + '
';
    html += '
' + retriveExtension(music.name) + '
';
    html += '
' + retriveSize(music.size) + ' MB' + '
';

    var elem = document.createElement('div');
    elem.classList.add('music-header');
    elem.classList.add('music-list-item');
    if (index % 2) {
      elem.classList.add('odd');
    } else {
      elem.classList.add('even');
    }
    elem.innerHTML = html;

    elem.dataset.music = JSON.stringify(music);
    elem.dataset.name = retriveName(music.name);
    elem.dataset.type = retriveExtension(music.name);
    elem.dataset.id = music.name;
    elem.dataset.checked = false;
    elem.onclick = function onclick_messages_list(event) {
      var target = event.target;
      if (target instanceof HTMLLabelElement) {
        toggleMusicItem(elem);
      } else {
        musicItemClicked(elem);
      }
    };
    elem.setAttribute('id', music.name);
    return elem;
  }

  function toggleMusicItem(elem) {
    var item = $expr('label.unchecked', elem)[0];
    if (item) {
      item.classList.toggle('checked');
    }
    if (item.classList.contains('checked')) {
      elem.dataset.checked = true;
    } else {
      elem.dataset.checked = false;
    }
    opStateChanged();
  }

  function opStateChanged() {
    if ($expr('#music-list-container .music-list-item').length == 0) {
      $id('selectAll-musics').dataset.checked = false;
      $id('selectAll-musics').dataset.disabled = true;
    } else {
      $id('selectAll-musics').dataset.checked =
        $expr('#music-list-container .music-list-item').length ===
          $expr('#music-list-container .music-list-item[data-checked="true"]').length;
      $id('selectAll-musics').dataset.disabled = false;
    }
    $id('remove-musics').dataset.disabled =
      $expr('#music-list-container .music-list-item[data-checked="true"]').length === 0;
    $id('export-musics').dataset.disabled =
      $expr('#music-list-container .music-list-item[data-checked="true"]').length === 0;
  }

  function musicItemClicked(elem) {
    $expr('#music-list-container .music-list-item[data-checked="true"]').forEach(function(e) {
      if (e != elem) {
        e.dataset.checked = false;
        var item = $expr('label.unchecked', e)[0];
        if (item) {
          item.classList.remove('checked');
        }
      }
    });

    item = $expr('label.unchecked', elem)[0];
    if (item) {
      item.classList.add('checked');
    }
    elem.dataset.checked = true;
    if ($expr('#music-list-container .music-list-item').length === 1) {
      $id('selectAll-musics').dataset.checked = true;
    } else {
      $id('selectAll-musics').dataset.checked = false;
    }
    $id('remove-musics').dataset.disabled = false;
    $id('export-musics').dataset.disabled = false;
  }

  function checkIfMusicListEmpty() {
    var isEmpty = $expr('#music-list-container .music-list-item').length === 0 ;
    if (isEmpty) {
      $id('selectAll-musics').dataset.disabled = true;
      showEmptyMusics(true);
    } else {
      $id('selectAll-musics').dataset.disabled = false;
      showEmptyMusics(false);
    }
  }

  function showEmptyMusics(bFlag) {
    if (bFlag) {
      $id('empty-music-container').style.display = 'block';
    } else {
      $id('empty-music-container').style.display = 'none';
    }
  }

  var groupedList = null;

  function initList(musics) {
    var container = getListContainer();
    container.innerHTML = '';
    var index = 0;
    musics.forEach(function(music) {
      var listItem = createMusicListItem(music, index);
      container.appendChild(listItem);
      index++;
    });
    checkIfMusicListEmpty();
  }

  function selectAllMusics(select) {
    $expr('#music-list-container .music-list-item').forEach(function(item) {
      selectMusicItem(item, select);
    });

    opStateChanged();
  }

  function selectMusicItem(elem, selected) {
    var item = $expr('label.unchecked', elem)[0];
    if (item) {
      if (selected) {
        item.classList.add('checked');
        elem.dataset.checked = true;
      } else {
        item.classList.remove('checked');
        elem.dataset.checked = false;
      }
    }
  }

  /**
   * Get music object by give music id
   */

  function getMusic(id) {
    var musicItem = $id('music-' + id);
    if (!musicItem) {
      throw 'No music item is found!';
    }
    return JSON.parse(musicItem.dataset.music);
  }

  /**
   * Remove musics
   */

  function removeMusic(files) {
    var items = files || [];
    var toBeRemovedFiles = [];
    var index = 0;
    items.forEach(function(item) {
      var req = navigator.mozFFOSAssistant.runCmd('adb shell rm ' + item);
      req.onsuccess = function (result) {
        toBeRemovedFiles.push(item);
        index++;
        if (index == items.length) {
          updateMusicsList(toBeRemovedFiles);
        }
      }
      req.onerror = function(e) {
        index++;
        if (index == items.length) {
          updateMusicsList(toBeRemovedFiles);
        }
        alert('error occured when removing ' + item);
      }
    });
  }

  function updateMusicsList(toBeRemovedFiles) {
    var container = $id('music-list-container');
    toBeRemovedFiles.forEach(function(item) {
      var music = $id(item);
      container.removeChild(music);
    });
    var musics = $expr('#music-list-container .music-list-item');
    for (var index = 0; index < musics.length; index++) {
      if (index % 2) {
        if (musics[index].classList.contains('even')) {
          musics[index].classList.remove('even');
        }
        musics[index].classList.add('odd');
      } else {
        if (musics[index].classList.contains('odd')) {
          musics[index].classList.remove('odd');
        }
        musics[index].classList.add('even');
      }
    }
  }

  function importMusics() {
    navigator.mozFFOSAssistant.selectMultiFilesFromDisk(function (state, data) {
      data = data.substr(0,data.length-1);
      var musics = data.split(';');
      var importedNum = 0;
      var oldFileIndex = 0;
      var steps = 0;
      var dialog = new ImportFilesDialog({});
      var filesIndicator = $id('files-indicator');
      var pb = $id('processbar');
      var ratio = 0;
      filesIndicator.innerHTML = '0/' + musics.length;

      if (musics.length > 0) {
        var range = Math.round(100 / musics.length);
        var step = range / 50;
        var bTimer = false;
         for (var index = 0; index < musics.length; index++) {
          var cmd = 'adb push ' + musics[index] + ' /sdcard/Music/';
          var req = navigator.mozFFOSAssistant.runCmd(cmd);
          if (!bTimer) {
            bTimer = true;
            var timer = setInterval(function() {
              if (oldFileIndex == importedNum) {
                if (steps < 50) {
                  steps++;
                  ratio+= step;
                  pb.style.width = ratio + '%';
                }
              } else {
                oldFileIndex = importedNum;
                steps = 0;
              }
            },100);
          }
          req.onsuccess = req.onerror= function(e) {
            importedNum++;
            ratio = Math.round(importedNum * 100 / musics.length);
            pb.style.width = ratio + '%';
            filesIndicator.innerHTML = importedNum + '/' + musics.length;
            if (importedNum == musics.length) {
              clearInterval(timer);
              pb.style.width.innerHTML = '100%';
              dialog.closeAll();
              FFOSAssistant.getAndShowAllMusics();
            }
          }
        }
      }
    });
  }

  window.addEventListener('load', function wnd_onload(event) {
    $id('selectAll-musics').addEventListener('click', function sall_onclick(event) {
      if (this.dataset.disabled == "true") {
        return;
      }
      if (this.dataset.checked == "false") {
        selectAllMusics(true);
      } else {
        selectAllMusics(false);
      }
    });

    $id('remove-musics').addEventListener('click', function onclick_removeMusic(event) {
      // Do nothing if the button is disabled.
      if (this.dataset.disabled == 'true') {
        return;
      }

      var files = [];
      $expr('#music-list-container .music-list-item[data-checked="true"]').forEach(function(item) {
        files.push(JSON.parse(item.dataset.music).name);
      });

      if (window.confirm(_('delete-musics-confirm', {n: files.length}))) {
        MusicList.removeMusic(files);
      }
    });

    $id('refresh-musics').addEventListener('click', function onclick_refreshMusics(event) {
      FFOSAssistant.getAndShowAllMusics();
    });

    $id('import-musics-btn').addEventListener('click', importMusics);

    $id('import-musics').addEventListener('click', importMusics);

    $id('export-musics').addEventListener('click', function onclick_exportMusics(event) {
      if (this.dataset.disabled == 'true') {
        return;
      }
      var musics = $expr('#music-list-container .music-list-item[data-checked="true"]');
      navigator.mozFFOSAssistant.selectDirectory(function(status, dir) {
        if (status) {
          setTimeout(function() {
            var length = musics.length;
            var index = 0;

            function traverseList() {
              if (index == length) {
                return;
              }
              var cmd = 'adb pull ' + musics[index].dataset.id + ' ' + decodeURI(dir) + '/'
                        + musics[index].dataset.name + '.' + musics[index].dataset.type;
              var req = navigator.mozFFOSAssistant.runCmd(cmd);
              req.onsuccess = req.onerror= function(e) {
                index++;
                setTimeout(traverseList, 0);
              }
            }
            traverseList();
          }, 0);
        }
      }, {
        title: 'Choose where to save'
      });
    selectAllMusics(false);
    });
  });

  return {
    init: initList,
    getMusic: getMusic,
    selectAllMusics: selectAllMusics,
    removeMusic: removeMusic
  };
})();
<commit_msg>fix music export bug in linux<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 License, v. 2.0. If a copy of the MPL was not distributed with this
 file, You can obtain one at http://mozilla.org/MPL/2.0/. */

var MusicList = (function() {
  function getListContainer() {
    return $id('music-list-container');
  }

  function retriveName(str) {
    var index = str.lastIndexOf('/');
    if (index < 0) {
      return 'Unkown';
    }
    str = str.substr(index + 1, str.length);

    index = str.lastIndexOf('.');
    if (index < 0) {
      return str;
    }
    return str.substr(0, index);
  }

  //function retriveTimeSpan() {}

  function retriveType(type) {
    var index = type.lastIndexOf('/');
    if (index < 0) {
      return type;
    }
    return type.substr(index + 1, type.length);
  }

  function retriveExtension(str) {
    var index = str.lastIndexOf('.');
    if (index < 0) {
      return '';
    }
    return str.substr(index + 1, str.length);
  }

  function retriveSize(size) {
    return Math.round(100 * size / 1024 / 1024) / 100;
  }

  function createMusicListItem(music, index) {
    var html = '';
    html += '';
    html += '
' + retriveName(music.name) + '
';
    html += '
' + music.metadate.artist + '
';
    html += '
' + music.metadate.album + '
';
    html += '
' + '5:05' + '
';
    html += '
' + retriveExtension(music.name) + '
';
    html += '
' + retriveSize(music.size) + ' MB' + '
';

    var elem = document.createElement('div');
    elem.classList.add('music-header');
    elem.classList.add('music-list-item');
    if (index % 2) {
      elem.classList.add('odd');
    } else {
      elem.classList.add('even');
    }
    elem.innerHTML = html;

    elem.dataset.music = JSON.stringify(music);
    elem.dataset.name = retriveName(music.name);
    elem.dataset.type = retriveExtension(music.name);
    elem.dataset.id = music.name;
    elem.dataset.checked = false;
    elem.onclick = function onclick_messages_list(event) {
      var target = event.target;
      if (target instanceof HTMLLabelElement) {
        toggleMusicItem(elem);
      } else {
        musicItemClicked(elem);
      }
    };
    elem.setAttribute('id', music.name);
    return elem;
  }

  function toggleMusicItem(elem) {
    var item = $expr('label.unchecked', elem)[0];
    if (item) {
      item.classList.toggle('checked');
    }
    if (item.classList.contains('checked')) {
      elem.dataset.checked = true;
    } else {
      elem.dataset.checked = false;
    }
    opStateChanged();
  }

  function opStateChanged() {
    if ($expr('#music-list-container .music-list-item').length == 0) {
      $id('selectAll-musics').dataset.checked = false;
      $id('selectAll-musics').dataset.disabled = true;
    } else {
      $id('selectAll-musics').dataset.checked =
        $expr('#music-list-container .music-list-item').length ===
          $expr('#music-list-container .music-list-item[data-checked="true"]').length;
      $id('selectAll-musics').dataset.disabled = false;
    }
    $id('remove-musics').dataset.disabled =
      $expr('#music-list-container .music-list-item[data-checked="true"]').length === 0;
    $id('export-musics').dataset.disabled =
      $expr('#music-list-container .music-list-item[data-checked="true"]').length === 0;
  }

  function musicItemClicked(elem) {
    $expr('#music-list-container .music-list-item[data-checked="true"]').forEach(function(e) {
      if (e != elem) {
        e.dataset.checked = false;
        var item = $expr('label.unchecked', e)[0];
        if (item) {
          item.classList.remove('checked');
        }
      }
    });

    item = $expr('label.unchecked', elem)[0];
    if (item) {
      item.classList.add('checked');
    }
    elem.dataset.checked = true;
    if ($expr('#music-list-container .music-list-item').length === 1) {
      $id('selectAll-musics').dataset.checked = true;
    } else {
      $id('selectAll-musics').dataset.checked = false;
    }
    $id('remove-musics').dataset.disabled = false;
    $id('export-musics').dataset.disabled = false;
  }

  function checkIfMusicListEmpty() {
    var isEmpty = $expr('#music-list-container .music-list-item').length === 0 ;
    if (isEmpty) {
      $id('selectAll-musics').dataset.disabled = true;
      showEmptyMusics(true);
    } else {
      $id('selectAll-musics').dataset.disabled = false;
      showEmptyMusics(false);
    }
  }

  function showEmptyMusics(bFlag) {
    if (bFlag) {
      $id('empty-music-container').style.display = 'block';
    } else {
      $id('empty-music-container').style.display = 'none';
    }
  }

  var groupedList = null;

  function initList(musics) {
    var container = getListContainer();
    container.innerHTML = '';
    var index = 0;
    musics.forEach(function(music) {
      var listItem = createMusicListItem(music, index);
      container.appendChild(listItem);
      index++;
    });
    checkIfMusicListEmpty();
  }

  function selectAllMusics(select) {
    $expr('#music-list-container .music-list-item').forEach(function(item) {
      selectMusicItem(item, select);
    });

    opStateChanged();
  }

  function selectMusicItem(elem, selected) {
    var item = $expr('label.unchecked', elem)[0];
    if (item) {
      if (selected) {
        item.classList.add('checked');
        elem.dataset.checked = true;
      } else {
        item.classList.remove('checked');
        elem.dataset.checked = false;
      }
    }
  }

  /**
   * Get music object by give music id
   */

  function getMusic(id) {
    var musicItem = $id('music-' + id);
    if (!musicItem) {
      throw 'No music item is found!';
    }
    return JSON.parse(musicItem.dataset.music);
  }

  /**
   * Remove musics
   */

  function removeMusic(files) {
    var items = files || [];
    var toBeRemovedFiles = [];
    var index = 0;
    items.forEach(function(item) {
      var req = navigator.mozFFOSAssistant.runCmd('adb shell rm ' + item);
      req.onsuccess = function (result) {
        toBeRemovedFiles.push(item);
        index++;
        if (index == items.length) {
          updateMusicsList(toBeRemovedFiles);
        }
      }
      req.onerror = function(e) {
        index++;
        if (index == items.length) {
          updateMusicsList(toBeRemovedFiles);
        }
        alert('error occured when removing ' + item);
      }
    });
  }

  function updateMusicsList(toBeRemovedFiles) {
    var container = $id('music-list-container');
    toBeRemovedFiles.forEach(function(item) {
      var music = $id(item);
      container.removeChild(music);
    });
    var musics = $expr('#music-list-container .music-list-item');
    for (var index = 0; index < musics.length; index++) {
      if (index % 2) {
        if (musics[index].classList.contains('even')) {
          musics[index].classList.remove('even');
        }
        musics[index].classList.add('odd');
      } else {
        if (musics[index].classList.contains('odd')) {
          musics[index].classList.remove('odd');
        }
        musics[index].classList.add('even');
      }
    }
  }

  function importMusics() {
    navigator.mozFFOSAssistant.selectMultiFilesFromDisk(function (state, data) {
      data = data.substr(0,data.length-1);
      var musics = data.split(';');
      var importedNum = 0;
      var oldFileIndex = 0;
      var steps = 0;
      var dialog = new ImportFilesDialog({});
      var filesIndicator = $id('files-indicator');
      var pb = $id('processbar');
      var ratio = 0;
      filesIndicator.innerHTML = '0/' + musics.length;

      if (musics.length > 0) {
        var range = Math.round(100 / musics.length);
        var step = range / 50;
        var bTimer = false;
         for (var index = 0; index < musics.length; index++) {
          var cmd = 'adb push "' + musics[index] + '" /sdcard/Music/';
          var req = navigator.mozFFOSAssistant.runCmd(cmd);
          if (!bTimer) {
            bTimer = true;
            var timer = setInterval(function() {
              if (oldFileIndex == importedNum) {
                if (steps < 50) {
                  steps++;
                  ratio+= step;
                  pb.style.width = ratio + '%';
                }
              } else {
                oldFileIndex = importedNum;
                steps = 0;
              }
            },100);
          }
          req.onsuccess = req.onerror= function(e) {
            importedNum++;
            ratio = Math.round(importedNum * 100 / musics.length);
            pb.style.width = ratio + '%';
            filesIndicator.innerHTML = importedNum + '/' + musics.length;
            if (importedNum == musics.length) {
              clearInterval(timer);
              pb.style.width.innerHTML = '100%';
              dialog.closeAll();
              FFOSAssistant.getAndShowAllMusics();
            }
          }
        }
      }
    });
  }

  window.addEventListener('load', function wnd_onload(event) {
    $id('selectAll-musics').addEventListener('click', function sall_onclick(event) {
      if (this.dataset.disabled == "true") {
        return;
      }
      if (this.dataset.checked == "false") {
        selectAllMusics(true);
      } else {
        selectAllMusics(false);
      }
    });

    $id('remove-musics').addEventListener('click', function onclick_removeMusic(event) {
      // Do nothing if the button is disabled.
      if (this.dataset.disabled == 'true') {
        return;
      }

      var files = [];
      $expr('#music-list-container .music-list-item[data-checked="true"]').forEach(function(item) {
        files.push(JSON.parse(item.dataset.music).name);
      });

      if (window.confirm(_('delete-musics-confirm', {n: files.length}))) {
        MusicList.removeMusic(files);
      }
    });

    $id('refresh-musics').addEventListener('click', function onclick_refreshMusics(event) {
      FFOSAssistant.getAndShowAllMusics();
    });

    $id('import-musics-btn').addEventListener('click', importMusics);

    $id('import-musics').addEventListener('click', importMusics);

    $id('export-musics').addEventListener('click', function onclick_exportMusics(event) {
      if (this.dataset.disabled == 'true') {
        return;
      }
      var musics = $expr('#music-list-container .music-list-item[data-checked="true"]');
      navigator.mozFFOSAssistant.selectDirectory(function(status, dir) {
        if (status) {
          setTimeout(function() {
            var length = musics.length;
            var index = 0;

            function traverseList() {
              if (index == length) {
                return;
              }
              var cmd = 'adb pull "' + musics[index].dataset.id + '" "' + decodeURI(dir) + '/'
                        + musics[index].dataset.name + '.' + musics[index].dataset.type + '"';
              var req = navigator.mozFFOSAssistant.runCmd(cmd);
              req.onsuccess = req.onerror= function(e) {
                index++;
                setTimeout(traverseList, 0);
              }
            };
            traverseList();
          }, 0);
        }
      }, {
        title: 'Choose where to save'
      });
    //selectAllMusics(false);
    });
  });

  return {
    init: initList,
    getMusic: getMusic,
    selectAllMusics: selectAllMusics,
    removeMusic: removeMusic
  };
})();
<|endoftext|>


<commit_before>/*!
 * Copyright (c) 2015 Jessica Tallon & Matt Molyneaux
 * Licensed under AGPLv3 (https://github.com/Inboxen/Inboxen/blob/master/LICENSE)
 */

function initForm($form, completeCallback) {
    $form.submit(function(event) {
        event.preventDefault();
        var $this;

        $this = $(this);
        $this.$form = $form;

        if ($this.data("sending") === "yes") {
            return false;
        }

        $this.find("button").prop("disabled", true);
        $this.find("a.btn").addClass("disabled");
        $this.data("sending", "yes");
        setTimeout(function() {
            $this.data("sending", "no");
            $this.find("button").prop("disabled", false);
            $this.find("a.btn").removeClass("disabled");
        }, 3000);

        $.ajax({
            type: "POST",
            url: $this.attr('action'),
            data: $this.serializeArray(),
            complete: completeCallback.bind($this)
        });
    });
}

function homeFormComplete(xhr, statusText) {
    var description, inboxSelector, is_disabled, $row;

    inboxSelector = this.$form.data("inbox-selector");
    $row = $("#" + inboxSelector + " + .row");
    description = this.find("#id_description").val();
    is_disabled = this.find("#id_disable_inbox").prop("checked");

    if (xhr.status === 204) {
        var $inbox_row = $("#" + inboxSelector);
        var $description_cell = $inbox_row.children(".inbox-description");

        $description_cell.text(description);

        if (is_disabled && !$inbox_row.hasClass("inbox-disabled")) {
            $inbox_row.addClass("inbox-disabled");
            $inbox_row.find(".inbox-flags").empty();
            $inbox_row.find(".inbox-flags").append("
Disabled
");
        } else if (!is_disabled && $inbox_row.hasClass("inbox-disabled")) {
            $inbox_row.removeClass("inbox-disabled");
            $inbox_row.find(".inbox-flags").empty();
        }

        $row.remove();
    } else if (xhr.status === 200) {
        this.$form.html(xhr.responseText);
    } else {
        this.$form.html("
Sorry, something went wrong.
");
        console.log("Form for " + inboxSelector + " failed to POST (" + xhr.status + ")");
    }
}

// adds event listeners for inline forms to be popped in
$(document).ready(function() {
    var $optionButtons = $("#inbox-list .inbox-options a");

    $optionButtons.click(function() {
        var $this = $(this);
        var $row = $this.parents("div.row:has(.inbox-name)");
        var formURL = "/forms/inbox/edit/" + $row.attr("id") + "/";

        if (!$row.next().hasClass("inbox-edit-form-row")) {
            if ($this.data("clicked") === "yes") {
                return false;
            } else {
                $this.data("clicked", "yes");
                $this.addClass("disabled");
                setTimeout(function() {
                    $this.data("clicked", "no");
                    $this.removeClass("disabled");
                }, 3000);
            }

            $.get(formURL, function(data) {
                // double check
                if (!$row.next().hasClass("inbox-edit-form-row")) {
                    $row.after("

" + data + "

");
                    initForm($row.next().find("form"), homeFormComplete);
                    $row.next().find("a").click(function() {
                        $row.next().remove();
                    });
                }
            });
        } else if ($row.next().hasClass("inbox-edit-form-row")) {
            $row.next().remove();
        }
        return false;
    });
});
<commit_msg>No need to declare a variable there<commit_after>/*!
 * Copyright (c) 2015 Jessica Tallon & Matt Molyneaux
 * Licensed under AGPLv3 (https://github.com/Inboxen/Inboxen/blob/master/LICENSE)
 */

function initForm($form, completeCallback) {
    $form.submit(function(event) {
        event.preventDefault();
        var $this;

        $this = $(this);
        $this.$form = $form;

        if ($this.data("sending") === "yes") {
            return false;
        }

        $this.find("button").prop("disabled", true);
        $this.find("a.btn").addClass("disabled");
        $this.data("sending", "yes");
        setTimeout(function() {
            $this.data("sending", "no");
            $this.find("button").prop("disabled", false);
            $this.find("a.btn").removeClass("disabled");
        }, 3000);

        $.ajax({
            type: "POST",
            url: $this.attr('action'),
            data: $this.serializeArray(),
            complete: completeCallback.bind($this)
        });
    });
}

function homeFormComplete(xhr, statusText) {
    var description, inboxSelector, is_disabled, $row;

    inboxSelector = this.$form.data("inbox-selector");
    $row = $("#" + inboxSelector + " + .row");
    description = this.find("#id_description").val();
    is_disabled = this.find("#id_disable_inbox").prop("checked");

    if (xhr.status === 204) {
        var $inbox_row = $("#" + inboxSelector);
        var $description_cell = $inbox_row.children(".inbox-description");

        $description_cell.text(description);

        if (is_disabled && !$inbox_row.hasClass("inbox-disabled")) {
            $inbox_row.addClass("inbox-disabled");
            $inbox_row.find(".inbox-flags").empty();
            $inbox_row.find(".inbox-flags").append("
Disabled
");
        } else if (!is_disabled && $inbox_row.hasClass("inbox-disabled")) {
            $inbox_row.removeClass("inbox-disabled");
            $inbox_row.find(".inbox-flags").empty();
        }

        $row.remove();
    } else if (xhr.status === 200) {
        this.$form.html(xhr.responseText);
    } else {
        this.$form.html("
Sorry, something went wrong.
");
        console.log("Form for " + inboxSelector + " failed to POST (" + xhr.status + ")");
    }
}

// adds event listeners for inline forms to be popped in
$(document).ready(function() {
    $("#inbox-list .inbox-options a").click(function() {
        // option buttons on inbox list
        var $this = $(this);
        var $row = $this.parents("div.row:has(.inbox-name)");
        var formURL = "/forms/inbox/edit/" + $row.attr("id") + "/";

        if (!$row.next().hasClass("inbox-edit-form-row")) {
            if ($this.data("clicked") === "yes") {
                return false;
            } else {
                $this.data("clicked", "yes");
                $this.addClass("disabled");
                setTimeout(function() {
                    $this.data("clicked", "no");
                    $this.removeClass("disabled");
                }, 3000);
            }

            $.get(formURL, function(data) {
                // double check
                if (!$row.next().hasClass("inbox-edit-form-row")) {
                    $row.after("

" + data + "

");
                    initForm($row.next().find("form"), homeFormComplete);
                    $row.next().find("a").click(function() {
                        $row.next().remove();
                    });
                }
            });
        } else if ($row.next().hasClass("inbox-edit-form-row")) {
            $row.next().remove();
        }
        return false;
    });
});
<|endoftext|>


<commit_before>YUI.add('facsimile', function (Y) {

    var SVG_NS = "http://www.w3.org/2000/svg",
        XLINK_NS = "http://www.w3.org/1999/xlink",
        NULL_IMAGE_VALUE = { width: Number.MAX_VALUE, height: Number.MAX_VALUE, tileSize: 1, maxZoom: 0 },
        NULL_VIEW_VALUE = { x: 0, y: 0, width: 0, height: 0, zoom: 0 };

    function qscale(degree) {
        return function (val) {
            return val * Math.pow(2, degree);
        };
    }

    function svgElement(name) {
        return Y.config.doc.createElementNS(SVG_NS, name);
    }

    function svgAttrs(element, attrs) {
        Y.Object.each(attrs, function(v, k) {
            element.setAttribute(k, v);
        });
        return element;
    }

    function svgStyles(element, styles) {
        Y.Object.each(styles, function(v, k) {
            element.style.setProperty(k, v);
        });
        return element;
    }

    var TiledViewModel = Y.Base.create("tile-view-model", Y.Base, [], {
        initializer: function () {
            this.imageChangeSub = this.after("imageChange", this.generateTiles, this);
            this.viewChangeSub = this.after("viewChange", this.generateTiles, this);
        },
        destructor: function () {
            this.viewChangeSub.detach();
            this.imageChangeSub.detach();
        },
        generateTiles: function () {
            var view = this.get("view"),
                tileSize = this.get("image").tileSize,
                startX = Math.max(0, Math.floor(Math.min(view.imageWidth, view.x) / tileSize)),
                startY = Math.max(0, Math.floor(Math.min(view.imageHeight, view.y) / tileSize)),
                endX = Math.ceil(Math.min(view.imageWidth, view.x + view.width) / tileSize),
                endY = Math.ceil(Math.min(view.imageHeight, view.y + view.height) / tileSize);

            var tiles = [];
            for (var y = startY; y < endY; y++) {
                for (var x = startX; x < endX; x++) {
                    tiles.push({ x: x, y: y });
                }
            }
            this.set("tiles", tiles);
        },
        center: function () {
            var view = this.get("view");
            this.moveTo(
                Math.max(view.imageWidth - view.width, 0) / 2,
                Math.max(view.imageHeight - view.height, 0) / 2
            );
        },
        centerOn: function (x, y) {
            var view = this.get("view");
            this.moveTo(
                x - Math.min(view.imageWidth, view.width) / 2,
                y - Math.min(view.imageHeight, view.height) / 2
            );
        },
        moveTo: function (x, y) {
            var view = this.get("view");
            this.set("view", {
                x: x || 0,
                y: y || 0
            });
        },
        move: function (x, y) {
            var view = this.get("view");
            this.moveTo(
                view.x + (x || 0),
                view.y + (y || 0)
            );
        },
        zoom: function (zoom) {
            var view = this.get("view"), prev = this.get("zoom"), prevScale = qscale(prev);
            this.set("zoom", prev + (zoom || 0));
            var nextScale = qscale(-this.get("zoom"));
            this.centerOn(
                nextScale(prevScale(view.x + (Math.min(view.imageWidth, view.x + view.width) - view.x) / 2)),
                nextScale(prevScale(view.y + (Math.min(view.imageHeight, view.y + view.height) - view.y) / 2)));
        },
        fitToView: function () {
            var image = this.get("image"), view = this.get("view");
            var zoom = Math.round(Math.log(Math.max((image.width / view.width), (image.height / view.height))) / Math.log(2));
            this.zoom(zoom - this.get("zoom"));
            this.center();
        }
    }, {
        ATTRS: {
            image: {
                value: NULL_IMAGE_VALUE,
                validator: function (val) {
                    return (val.width >= 0) && (val.height >= 0) && (val.maxZoom >= 0) && (val.tileSize > 0);
                }
            },
            view: {
                value: NULL_VIEW_VALUE,
                setter: function (val) {
                    var image = this.get("image"),
                        scale = qscale(-this.get("zoom")),
                        imageWidth = scale(image.width),
                        imageHeight = scale(image.height),
                        view = this.get("view") || NULL_VIEW_VALUE,
                        isNumber = Y.Lang.isNumber;
                    return {
                        x: Math.min(Math.max(isNumber(val.x) ? val.x : view.x, 0), Math.max(0, imageWidth - view.width)),
                        y: Math.min(Math.max(isNumber(val.y) ? val.y : view.y, 0), Math.max(0, imageHeight - view.height)),
                        width: Math.max(isNumber(val.width) ? val.width : view.width, 0),
                        height: Math.max(isNumber(val.height) ? val.height : view.height, 0),
                        imageWidth: imageWidth,
                        imageHeight: imageHeight
                    };
                }
            },
            zoom: {
                value: 0,
                setter: function (val) {
                    return Math.min(Math.max(val || 0, 0), this.get("image").maxZoom);
                }
            },
            tiles: {
                valueFn: function () {
                    return []
                }
            }
        }
    });

    var FacsimileViewer = Y.Base.create("facsimile-viewer", Y.Widget, [], {
        initializer: function (config) {
            var view = config.view || NULL_VIEW_VALUE;

            this.svg = svgStyles(svgAttrs(svgElement("svg"), {
                "version": "1.1",
                "width": view.width,
                "height": view.height
            }), {
                background: "black",
                margin: "1em auto",
                border: 0,
                padding: 0,
                position: "relative",
                overflow: "hidden"
            });
            this.get("contentBox").getDOMNode().appendChild(this.svg);

            this.model = new TiledViewModel({ view: view });
            this.modelChangeSub = this.model.after(["tilesChange", "viewChange", "imageChange"], this.syncUI, this);

            Y.io(this.imageSrc(), {
                data: {metadata: true },
                on: { success: Y.bind(this.metadataReceived, this) }
            });
        },
        destructor: function () {
            this.modelChangeSub.detach();
        },
        renderUI: function () {
        },
        metadataReceived: function (transactionId, response) {
            this.model.set("image", Y.merge(this.get("image"), Y.JSON.parse(response.responseText)));
            this.model.fitToView();
        },
        imageSrc: function () {
            return cp + "/facsimile/gsa/391098/391098_0001";
        },
        tileSrc: function (x, y) {
            return Y.substitute("{imageSrc}?x={x}&y={y}&zoom={zoom}", {
                imageSrc: this.imageSrc(),
                x: x,
                y: y,
                zoom: this.model.get("zoom")
            });
        },
        syncUI: function () {
            var view = this.model.get("view"),
                xOffset = Math.max(0, (view.width - view.imageWidth) / 2),
                yOffset = Math.max(0, (view.height - view.imageHeight) / 2),
                tileSize = this.model.get("image").tileSize,
                tiles = this.model.get("tiles");

            while (this.svg.firstChild) this.svg.removeChild(this.svg.firstChild);
            svgAttrs(this.svg, {
                "width": view.width,
                "height": view.height
            });
            Y.Array.each(tiles, function (tile) {
                var x = tile.x * tileSize, y = tile.y * tileSize;
                this.svg.appendChild(svgAttrs(svgElement("image"), {
                    "x": Math.floor(x - view.x + xOffset),
                    "y": Math.floor(y - view.y + yOffset),
                    "width": Math.min(tileSize, view.imageWidth - x),
                    "height": Math.min(tileSize, view.imageHeight - y)
                })).setAttributeNS(XLINK_NS, "href", this.tileSrc(tile.x, tile.y));
            }, this);
        }
    });

    Y.mix(Y.namespace("Faust"), { FacsimileViewer: FacsimileViewer });
}, '0.0', {
    requires: ["base", "widget", "substitute", "array-extras", "io", "json"]
});<commit_msg>Facsimile SVG display: Firefox compatibility<commit_after>YUI.add('facsimile', function (Y) {

    var SVG_NS = "http://www.w3.org/2000/svg",
        XLINK_NS = "http://www.w3.org/1999/xlink",
        NULL_IMAGE_VALUE = { width: Number.MAX_VALUE, height: Number.MAX_VALUE, tileSize: 1, maxZoom: 0 },
        NULL_VIEW_VALUE = { x: 0, y: 0, width: 0, height: 0, zoom: 0 };

    function qscale(degree) {
        return function (val) {
            return val * Math.pow(2, degree);
        };
    }

    function svgElement(name) {
        return Y.config.doc.createElementNS(SVG_NS, name);
    }

    function svgAttrs(element, attrs) {
        Y.Object.each(attrs, function(v, k) {
            element.setAttribute(k, v);
        });
        return element;
    }

    function svgStyles(element, styles) {
        Y.Object.each(styles, function(v, k) {
            element.style[k] = v;
        });
        return element;
    }

    var TiledViewModel = Y.Base.create("tile-view-model", Y.Base, [], {
        initializer: function () {
            this.imageChangeSub = this.after("imageChange", this.generateTiles, this);
            this.viewChangeSub = this.after("viewChange", this.generateTiles, this);
        },
        destructor: function () {
            this.viewChangeSub.detach();
            this.imageChangeSub.detach();
        },
        generateTiles: function () {
            var view = this.get("view"),
                tileSize = this.get("image").tileSize,
                startX = Math.max(0, Math.floor(Math.min(view.imageWidth, view.x) / tileSize)),
                startY = Math.max(0, Math.floor(Math.min(view.imageHeight, view.y) / tileSize)),
                endX = Math.ceil(Math.min(view.imageWidth, view.x + view.width) / tileSize),
                endY = Math.ceil(Math.min(view.imageHeight, view.y + view.height) / tileSize);

            var tiles = [];
            for (var y = startY; y < endY; y++) {
                for (var x = startX; x < endX; x++) {
                    tiles.push({ x: x, y: y });
                }
            }
            this.set("tiles", tiles);
        },
        center: function () {
            var view = this.get("view");
            this.moveTo(
                Math.max(view.imageWidth - view.width, 0) / 2,
                Math.max(view.imageHeight - view.height, 0) / 2
            );
        },
        centerOn: function (x, y) {
            var view = this.get("view");
            this.moveTo(
                x - Math.min(view.imageWidth, view.width) / 2,
                y - Math.min(view.imageHeight, view.height) / 2
            );
        },
        moveTo: function (x, y) {
            var view = this.get("view");
            this.set("view", {
                x: x || 0,
                y: y || 0
            });
        },
        move: function (x, y) {
            var view = this.get("view");
            this.moveTo(
                view.x + (x || 0),
                view.y + (y || 0)
            );
        },
        zoom: function (zoom) {
            var view = this.get("view"), prev = this.get("zoom"), prevScale = qscale(prev);
            this.set("zoom", prev + (zoom || 0));
            var nextScale = qscale(-this.get("zoom"));
            this.centerOn(
                nextScale(prevScale(view.x + (Math.min(view.imageWidth, view.x + view.width) - view.x) / 2)),
                nextScale(prevScale(view.y + (Math.min(view.imageHeight, view.y + view.height) - view.y) / 2)));
        },
        fitToView: function () {
            var image = this.get("image"), view = this.get("view");
            var zoom = Math.round(Math.log(Math.max((image.width / view.width), (image.height / view.height))) / Math.log(2));
            this.zoom(zoom - this.get("zoom"));
            this.center();
        }
    }, {
        ATTRS: {
            image: {
                value: NULL_IMAGE_VALUE,
                validator: function (val) {
                    return (val.width >= 0) && (val.height >= 0) && (val.maxZoom >= 0) && (val.tileSize > 0);
                }
            },
            view: {
                value: NULL_VIEW_VALUE,
                setter: function (val) {
                    var image = this.get("image"),
                        scale = qscale(-this.get("zoom")),
                        imageWidth = scale(image.width),
                        imageHeight = scale(image.height),
                        view = this.get("view") || NULL_VIEW_VALUE,
                        isNumber = Y.Lang.isNumber;
                    return {
                        x: Math.min(Math.max(isNumber(val.x) ? val.x : view.x, 0), Math.max(0, imageWidth - view.width)),
                        y: Math.min(Math.max(isNumber(val.y) ? val.y : view.y, 0), Math.max(0, imageHeight - view.height)),
                        width: Math.max(isNumber(val.width) ? val.width : view.width, 0),
                        height: Math.max(isNumber(val.height) ? val.height : view.height, 0),
                        imageWidth: imageWidth,
                        imageHeight: imageHeight
                    };
                }
            },
            zoom: {
                value: 0,
                setter: function (val) {
                    return Math.min(Math.max(val || 0, 0), this.get("image").maxZoom);
                }
            },
            tiles: {
                valueFn: function () {
                    return []
                }
            }
        }
    });

    var FacsimileViewer = Y.Base.create("facsimile-viewer", Y.Widget, [], {
        initializer: function (config) {
            var view = config.view || NULL_VIEW_VALUE;

            this.svg = svgStyles(svgAttrs(svgElement("svg"), {
                "version": "1.1",
                "width": view.width,
                "height": view.height
            }), {
                background: "black",
                margin: "1em auto",
                border: 0,
                padding: 0,
                position: "relative",
                overflow: "hidden"
            });
            this.svg = this.get("contentBox").getDOMNode().appendChild(this.svg).appendChild(svgElement("g"));

            this.model = new TiledViewModel({ view: view });
            this.modelChangeSub = this.model.after(["tilesChange", "viewChange", "imageChange"], this.syncUI, this);

            Y.io(this.imageSrc(), {
                data: {metadata: true },
                on: { success: Y.bind(this.metadataReceived, this) }
            });
        },
        destructor: function () {
            this.modelChangeSub.detach();
        },
        renderUI: function () {
        },
        metadataReceived: function (transactionId, response) {
            this.model.set("image", Y.merge(this.get("image"), Y.JSON.parse(response.responseText)));
            this.model.fitToView();
        },
        imageSrc: function () {
            return cp + "/facsimile/gsa/391098/391098_0001";
        },
        tileSrc: function (x, y) {
            return Y.substitute("{imageSrc}?x={x}&y={y}&zoom={zoom}", {
                imageSrc: this.imageSrc(),
                x: x,
                y: y,
                zoom: this.model.get("zoom")
            });
        },
        syncUI: function () {
            var view = this.model.get("view"),
                xOffset = Math.floor(Math.max(0, (view.width - view.imageWidth) / 2)),
                yOffset = Math.floor(Math.max(0, (view.height - view.imageHeight) / 2)),
                tileSize = this.model.get("image").tileSize,
                tiles = this.model.get("tiles");

            while (this.svg.firstChild) this.svg.removeChild(this.svg.firstChild);
            svgAttrs(this.svg.parentNode, {
                "width": view.width,
                "height": view.height
            });
            Y.Array.each(tiles, function (tile) {
                var x = tile.x * tileSize, y = tile.y * tileSize;
                this.svg.appendChild(svgAttrs(svgElement("image"), {
                    "x": Math.floor(x - view.x + xOffset),
                    "y": Math.floor(y - view.y + yOffset),
                    "width": Math.min(tileSize, view.imageWidth - x) + 1,
                    "height": Math.min(tileSize, view.imageHeight - y) + 1
                })).setAttributeNS(XLINK_NS, "href", this.tileSrc(tile.x, tile.y));
            }, this);
        }
    });

    Y.mix(Y.namespace("Faust"), { FacsimileViewer: FacsimileViewer });
}, '0.0', {
    requires: ["base", "widget", "substitute", "array-extras", "io", "json"]
});<|endoftext|>


<commit_before>/*
 * Main application controller.
 */

/*
 * Module dependencies.
 */
var util = require('util');
var fs = require('fs');
var path = require('path');
var Prana = require('prana');
var Waterline = require('waterline');
var pranaAdapter = require('waterline-prana');
var mongoAdapter = require('sails-mongo');
var express = require('express');
var bodyParser = require('body-parser');
var methodOverride = require('method-override');
var cookieParser = require('cookie-parser');
var serveStatic = require('serve-static');
var vhost = require('vhost');
var async = require('async');
var lodash = require('lodash');
var utils = Prana.utils;
var modelPatch = require('./model-patch');
var adapterMethods = require('./adapter-patch');

// Patch Prana.
require('./prana-patch');

/**
 * Main application controller class.
 *
 * @param {Server} mainApplication Main Connect server.
 * @param {String} hostname Application hostname.
 * @param {Object} settings Application settings object.
 * @class Application
 */
var Application = module.exports = function(mainApplication, hostname, settings) {
  Prana.call(this, settings);

  this.mainApplication = mainApplication;

  // Set hostname for easy access to it when needed.
  this.settings.hostname = hostname;

  // Create child application.
  this.application = express();

  // Initialize required middlewares.
  this.application.use(bodyParser.urlencoded({
    extended: false
  }));
  this.application.use(bodyParser.json());
  this.application.use(methodOverride());
  this.application.use(cookieParser());

  // Create Waterline instance.
  this.waterline = new Waterline();

  // Initialize types and collections container.
  this.types = {};
  this.collections = {};

  // Initialize routes container.
  this.routes = {};
};

util.inherits(Application, Prana);

/**
 * Initialize application.
 *
 * @param {Function} callback Function to run when application finishes
 *   initializing.
 */
Application.prototype.start = function(callback) {
  // Setup application public files first to make sure applications can override
  // default application and extensions public files.
  this.application.use(serveStatic(this.settings.applicationDir + '/public'));
  this.application.use(serveStatic(this.settings.baseDir + '/applications/default/public'));

  var self = this;

  async.series([

    // Run installer if there are no database settings.
    function(next) {
      if (!self.settings.database) {
        return self.install(next);
      }
      next();
    },

    // Load all extensions and call init hooks on all of them.
    function(next) {
      self.loadAllExtensions(next);
    },

    // Load all types and create collections for them in Waterline.
    function(next) {
      self.loadAllTypes(next);
    },

    // Configure and initialize Waterline.
    function(next) {
      // Add methods not implemented in waterline-prana that we need for
      // overrides to be persisted.
      utils.extend(pranaAdapter, adapterMethods(self));

      // Pass Choko instance to the Prana Waterline adapter.
      pranaAdapter.setApplication(self);

      var config = {
        adapters: {
          'prana': pranaAdapter,
          'mongo': mongoAdapter
        },
        connections: {
          'prana': {
            adapter: 'prana'
          },
          'mongo': {
            adapter: 'mongo',
            url: self.settings.database
          }
        }
      };

      // Initialize Waterline ORM.
      self.waterline.initialize(config, function(error, models) {
        self.collections = models.collections;

        // Add type and application object to collections.
        for (var collectionName in self.collections) {
          self.collections[collectionName].application = self;
          self.collections[collectionName].type = self.types[collectionName];
        }

        next();
      });
    },

    // Call Prana init that also calls init hook on all extensions.
    function(next) {
      // Can't pass next directly to init() since it returns the application as
      // the first argument and next() expect an error or null.
      self.init(function() {
        next();
      });
    }
  ],
  function(error, results) {
    if (error) {
      return callback(error);
    }

    // Create application vhost.
    self.mainApplication.use(vhost(self.settings.hostname, self.application));
    callback();
  });
};

/**
 * Override Prana's init() method. Create subtypes for polymorphic types just
 * after initializing core types.
 *
 * @param {Function} callback Callback to run after prana was initialized.
 */
Application.prototype.init = function(callback) {
  var self = this;

  // @todo: improve indentation after commiting.

    // Add types for all polymorphic types subtypes.
    async.each(Object.keys(self.types), function(typeName, next) {
      var type = self.types[typeName];

      if (!type.polymorphic) {
        return next();
      }

      // Initialize subtypes container.
      type.subtypes = type.subtypes || {};

      // Get list of items for subtypes collector type.
      self.collect(typeName + 'Type', function(error, subtypes) {
        if (error) {
          return next(error);
        }

        async.each(Object.keys(subtypes), function(subtypeName, next) {
          var subtypeSettings = {
            name: subtypeName,
            // Set main type to use on form submits.
            mainTypeName: typeName,
            // Set short name to be used for classifying subtypes.
            shortName: subtypeName,
            // Initialize fields container.
            fields: {}
          };
          utils.extend(subtypeSettings, subtypes[subtypeName]);

          // Merge in main type fields.
          if (type.fields) {
            utils.extend(subtypeSettings.fields, type.fields);
          }

          // Create and store the subtype on the main type.
          type.subtypes[subtypeName] = subtypeSettings;
          self.type(subtypeName + utils.capitalizeFirstLetter(typeName), subtypeSettings);

          next();
        }, next);
      });
    },
    function() {
      // Run init hook on all extensions.
      self.invoke('init', self, callback);
    });
};

/**
 * Override Prana.collect() to add overrides.
 *
 * @param {String} type Type of data to collect items for.
 * @param {Function} callback Callback to run after all data have been
 *   collected.
 */
Application.prototype.collect = function(type, callback) {
  var self = this;
  Prana.prototype.collect.call(this, type, function(error, result) {
    if (error) {
      return callback(error);
    }

    self.overrides(type, result, function() {
      callback(null, result);
    });
  });
};

/**
 * Set or get a type.
 *
 * @param {String} name A string to be used to identify the type.
 * @param {Object} settings The settings for for the type to be created.
 * @return {Model} A Model subclass created exclusivelly for this type.
 */
Application.prototype.type = function(name, settings) {
  // If there's no settings we want to get a type.
  if (!settings) {
    // Return this as earlier as possible.
    return this.collections[name.toLowerCase()];
  }

  // Default type properties.
  var settings = lodash.merge({
    keyProperty: 'name',
    storage: 'memory'
  }, settings);

  // Add type to types container.
  this.types[name] = settings;

  // Create schema and register it in Waterline.
  var schema = {
    identity: name,
    tableName: name,
    schema: false,

    // @todo: allow extensions to add storages/connections/adapters.
    connection: (settings.storage == 'database' ? 'mongo' : 'prana'),

    // Disable automatic migration.
    migrate: 'safe',

    // Disable auto primary key and created/update.
    autoPK: (!settings.keyProperty),
    autoCreatedAt: false,
    autoUpdatedAt: false,

    // Initialize attibutes.
    attributes: {}
  };

  if (settings.fields) {
    // Create attributes for all type fields.
    for (fieldName in settings.fields) {
      var fieldSettings = settings.fields[fieldName];

      // Process reference fields.
      if (fieldSettings.type == 'reference' && fieldSettings.reference) {
        if (fieldSettings.reference.inline) {
          // Use 'array' or 'json' for inline references.
          schema.attributes[fieldName] = {
            type: fieldSettings.reference.multiple ? 'array' : 'json',
            required: fieldSettings.required
          };
        }
        else {
          if (fieldSettings.reference.multiple) {
            schema.attributes[fieldName] = {
              // Many-to-many relationships will just use array for now, since
              // waterline does not support many to many relatioships with
              // embbeded documents.
              // @todo: eventually we may be able to use something like this.
              // collection: fieldSettings.reference.type
              type: 'array',
              required: fieldSettings.required,
            };
          }
          else {
            schema.attributes[fieldName] = {
              required: fieldSettings.required,
              model: fieldSettings.reference.type
            };
          }
        }
      }
      else {
        var primaryKey = (settings.keyProperty == fieldName);
        // Process scalar fields.
        var attribute = schema.attributes[fieldName] = {
          type: 'string',
          // @todo: we can't just set primaryKey here since if it's set to
          // false it will behave like if it was set to true.
          // See https://github.com/balderdashy/waterline/issues/606
          // primaryKey: primaryKey,

          // Primary keys need to be required and unique.
          required: fieldSettings.required || primaryKey,
          unique: fieldSettings.unique || primaryKey
        };

        // Set primary key verbose like this since it can't be set to false due
        // to a bug in waterline, see above.
        if (primaryKey) {
          attribute.primaryKey = true;
        }
      }
    }
  }

  // Add validation related methods.
  // @todo: rename this something else than patch, since it's not this anymore.
  utils.extend(schema, modelPatch);

  // Add before/after callbacks.
  ['Validate', 'Update', 'Create', 'Destroy'].map(function(operation) {
    ['before', 'after'].map(function(when) {
      var callbackName = when + operation;
      if (callbackName in settings) {
        schema[callbackName] = settings[callbackName];
      }
    });
  });

  // Add type specific collection methods.
  if (settings.statics && schema.attributes) {
    utils.extend(schema, settings.statics);
  }

  // Add type methods to model.
  if (settings.methods && schema.attributes) {
    utils.extend(schema.attributes, settings.methods);
  }

  var collection = Waterline.Collection.extend(schema);

  // Load the collection into Waterline.
  this.waterline.loadCollection(collection);
};

/**
 * Load all enabled extensions.
 *
 * @param {Function} callback Function to run when application finishes
 *   loading extensions.
 */
Application.prototype.loadAllExtensions = function(callback) {
  var extensionsPaths = [
    this.settings.baseDir + '/applications/default/extensions',
    this.settings.applicationDir + '/extensions'
  ];

  var self = this;
  this.loadExtensions(extensionsPaths, function(error, extensions) {
    if (error) {
      return callback(error);
    }
    async.each(Object.keys(extensions), function(extensionName, next) {
      // Add extension static files.
      var filesPath = extensions[extensionName].settings.path + '/public';
      fs.exists(filesPath, function(exists) {
        if (exists) {
          self.application.use(serveStatic(filesPath));
        }
        next();
      });
    }, callback);
  });
};

/**
 * Load all types.
 *
 * @param {Function} callback Function to run when application finishes
 *   loading types.
 */
Application.prototype.loadAllTypes = function(callback) {
  var self = this;

  // Collect types from all extensions.
  this.collect('type', function(error, types) {
    async.each(Object.keys(types), function(typeName, next) {
      self.type(typeName, types[typeName]);
      next();
    }, callback);
  });
};

/**
 * Collect data from JSON files on application overrides folder and merge them
 * to the data container object.
 *
 * @param {Type} type Type object to collect data for.
 * @param {Object} data Data container object.
 * @param {Function} callback Callback to run after all data have been
 *   collected.
 */
Application.prototype.overrides = function(type, data, callback) {
  var overridesDir = path.join(this.settings.applicationDir, 'overrides');

  fs.exists(overridesDir, function(exists) {
    if (!exists) {
      return callback();
    }

    // Scan for JSON files for this type.
    Prana.Extension.scanItems(overridesDir, type, function(error, foundItems) {
      if (error) {
        return callback(error);
      }

      utils.extend(data, foundItems);
      callback();
    });

  });
};

/**
 * List items from the storage.
 *
 * @param {String} typeName A type name string.
 * @param {Object} query Query object with conditions and filters.
 * @param {Function} callback Function to run when items are returned.
 */
Application.prototype.list = function(typeName, query, callback) {
  this.type(typeName).list(query, callback);
};

/**
 * Load a single item from the storage.
 *
 * @param {String} typeName A type name string.
 * @param {Mixed} key The key that represents the object to load.
 * @param {Function} callback Function to run when the item is returned.
 */
Application.prototype.load = function(typeName, key, callback) {
  this.type(typeName).load(key, callback);
};

/**
 * Save a item to the storage.
 *
 * @param {String} typeName A type name string.
 * @param {Object} key Object with values for the object.
 * @param {Function} callback Function to run when the item is saved.
 */
Application.prototype.save = function(typeName, item, callback) {
  this.type(typeName).save(item, callback);
};

/**
 * Delete a single item from the storage.
 *
 * @param {String} typeName A type name string.
 * @param {Mixed} key The key that represents the object to delete.
 * @param {Function} callback Function to run when the item is deleted.
 */
Application.prototype.delete = function(typeName, key, callback) {
  this.type(typeName).delete(key, callback);
};

Application.prototype.install = function(callback) {
  this.settings.extensions = {
    'field': {},
    'route': {},
    'context': {},
    'layout': {},
    'panel': {},
    'navigation': {},
    'page': {},
    'theme': {},
    'rest': {},
    'form': {},
    'installer': {}
  };

  this.access = function(request, permission, callback) {
    // Allow access to eveything.
    callback(null, true);
  };

  callback();
};
<commit_msg>Improving error handling.<commit_after>/*
 * Main application controller.
 */

/*
 * Module dependencies.
 */
var util = require('util');
var fs = require('fs');
var path = require('path');
var Prana = require('prana');
var Waterline = require('waterline');
var pranaAdapter = require('waterline-prana');
var mongoAdapter = require('sails-mongo');
var express = require('express');
var bodyParser = require('body-parser');
var methodOverride = require('method-override');
var cookieParser = require('cookie-parser');
var serveStatic = require('serve-static');
var vhost = require('vhost');
var async = require('async');
var lodash = require('lodash');
var utils = Prana.utils;
var modelPatch = require('./model-patch');
var adapterMethods = require('./adapter-patch');

// Patch Prana.
require('./prana-patch');

/**
 * Main application controller class.
 *
 * @param {Server} mainApplication Main Connect server.
 * @param {String} hostname Application hostname.
 * @param {Object} settings Application settings object.
 * @class Application
 */
var Application = module.exports = function(mainApplication, hostname, settings) {
  Prana.call(this, settings);

  this.mainApplication = mainApplication;

  // Set hostname for easy access to it when needed.
  this.settings.hostname = hostname;

  // Create child application.
  this.application = express();

  // Initialize required middlewares.
  this.application.use(bodyParser.urlencoded({
    extended: false
  }));
  this.application.use(bodyParser.json());
  this.application.use(methodOverride());
  this.application.use(cookieParser());

  // Create Waterline instance.
  this.waterline = new Waterline();

  // Initialize types and collections container.
  this.types = {};
  this.collections = {};

  // Initialize routes container.
  this.routes = {};
};

util.inherits(Application, Prana);

/**
 * Initialize application.
 *
 * @param {Function} callback Function to run when application finishes
 *   initializing.
 */
Application.prototype.start = function(callback) {
  // Setup application public files first to make sure applications can override
  // default application and extensions public files.
  this.application.use(serveStatic(this.settings.applicationDir + '/public'));
  this.application.use(serveStatic(this.settings.baseDir + '/applications/default/public'));

  var self = this;

  async.series([

    // Run installer if there are no database settings.
    function(next) {
      if (!self.settings.database) {
        return self.install(next);
      }
      next();
    },

    // Load all extensions and call init hooks on all of them.
    function(next) {
      self.loadAllExtensions(next);
    },

    // Load all types and create collections for them in Waterline.
    function(next) {
      self.loadAllTypes(next);
    },

    // Configure and initialize Waterline.
    function(next) {
      // Add methods not implemented in waterline-prana that we need for
      // overrides to be persisted.
      utils.extend(pranaAdapter, adapterMethods(self));

      // Pass Choko instance to the Prana Waterline adapter.
      pranaAdapter.setApplication(self);

      var config = {
        adapters: {
          'prana': pranaAdapter,
          'mongo': mongoAdapter
        },
        connections: {
          'prana': {
            adapter: 'prana'
          },
          'mongo': {
            adapter: 'mongo',
            url: self.settings.database
          }
        }
      };

      // Initialize Waterline ORM.
      self.waterline.initialize(config, function(error, models) {
        if (error) {
          return next(error);
        }

        self.collections = models.collections;

        // Add type and application object to collections.
        for (var collectionName in self.collections) {
          self.collections[collectionName].application = self;
          self.collections[collectionName].type = self.types[collectionName];
        }

        next();
      });
    },

    // Call Prana init that also calls init hook on all extensions.
    function(next) {
      // Can't pass next directly to init() since it returns the application as
      // the first argument and next() expect an error or null.
      self.init(function() {
        next();
      });
    }
  ],
  function(error, results) {
    if (error) {
      return callback(error);
    }

    // Create application vhost.
    self.mainApplication.use(vhost(self.settings.hostname, self.application));
    callback();
  });
};

/**
 * Override Prana's init() method. Create subtypes for polymorphic types just
 * after initializing core types.
 *
 * @param {Function} callback Callback to run after prana was initialized.
 */
Application.prototype.init = function(callback) {
  var self = this;

  // @todo: improve indentation after commiting.

    // Add types for all polymorphic types subtypes.
    async.each(Object.keys(self.types), function(typeName, next) {
      var type = self.types[typeName];

      if (!type.polymorphic) {
        return next();
      }

      // Initialize subtypes container.
      type.subtypes = type.subtypes || {};

      // Get list of items for subtypes collector type.
      self.collect(typeName + 'Type', function(error, subtypes) {
        if (error) {
          return next(error);
        }

        async.each(Object.keys(subtypes), function(subtypeName, next) {
          var subtypeSettings = {
            name: subtypeName,
            // Set main type to use on form submits.
            mainTypeName: typeName,
            // Set short name to be used for classifying subtypes.
            shortName: subtypeName,
            // Initialize fields container.
            fields: {}
          };
          utils.extend(subtypeSettings, subtypes[subtypeName]);

          // Merge in main type fields.
          if (type.fields) {
            utils.extend(subtypeSettings.fields, type.fields);
          }

          // Create and store the subtype on the main type.
          type.subtypes[subtypeName] = subtypeSettings;
          self.type(subtypeName + utils.capitalizeFirstLetter(typeName), subtypeSettings);

          next();
        }, next);
      });
    },
    function() {
      // Run init hook on all extensions.
      self.invoke('init', self, callback);
    });
};

/**
 * Override Prana.collect() to add overrides.
 *
 * @param {String} type Type of data to collect items for.
 * @param {Function} callback Callback to run after all data have been
 *   collected.
 */
Application.prototype.collect = function(type, callback) {
  var self = this;
  Prana.prototype.collect.call(this, type, function(error, result) {
    if (error) {
      return callback(error);
    }

    self.overrides(type, result, function() {
      callback(null, result);
    });
  });
};

/**
 * Set or get a type.
 *
 * @param {String} name A string to be used to identify the type.
 * @param {Object} settings The settings for for the type to be created.
 * @return {Model} A Model subclass created exclusivelly for this type.
 */
Application.prototype.type = function(name, settings) {
  // If there's no settings we want to get a type.
  if (!settings) {
    // Return this as earlier as possible.
    return this.collections[name.toLowerCase()];
  }

  // Default type properties.
  var settings = lodash.merge({
    keyProperty: 'name',
    storage: 'memory'
  }, settings);

  // Add type to types container.
  this.types[name] = settings;

  // Create schema and register it in Waterline.
  var schema = {
    identity: name,
    tableName: name,
    schema: false,

    // @todo: allow extensions to add storages/connections/adapters.
    connection: (settings.storage == 'database' ? 'mongo' : 'prana'),

    // Disable automatic migration.
    migrate: 'safe',

    // Disable auto primary key and created/update.
    autoPK: (!settings.keyProperty),
    autoCreatedAt: false,
    autoUpdatedAt: false,

    // Initialize attibutes.
    attributes: {}
  };

  if (settings.fields) {
    // Create attributes for all type fields.
    for (fieldName in settings.fields) {
      var fieldSettings = settings.fields[fieldName];

      // Process reference fields.
      if (fieldSettings.type == 'reference' && fieldSettings.reference) {
        if (fieldSettings.reference.inline) {
          // Use 'array' or 'json' for inline references.
          schema.attributes[fieldName] = {
            type: fieldSettings.reference.multiple ? 'array' : 'json',
            required: fieldSettings.required
          };
        }
        else {
          if (fieldSettings.reference.multiple) {
            schema.attributes[fieldName] = {
              // Many-to-many relationships will just use array for now, since
              // waterline does not support many to many relatioships with
              // embbeded documents.
              // @todo: eventually we may be able to use something like this.
              // collection: fieldSettings.reference.type
              type: 'array',
              required: fieldSettings.required,
            };
          }
          else {
            schema.attributes[fieldName] = {
              required: fieldSettings.required,
              model: fieldSettings.reference.type
            };
          }
        }
      }
      else {
        var primaryKey = (settings.keyProperty == fieldName);
        // Process scalar fields.
        var attribute = schema.attributes[fieldName] = {
          type: 'string',
          // @todo: we can't just set primaryKey here since if it's set to
          // false it will behave like if it was set to true.
          // See https://github.com/balderdashy/waterline/issues/606
          // primaryKey: primaryKey,

          // Primary keys need to be required and unique.
          required: fieldSettings.required || primaryKey,
          unique: fieldSettings.unique || primaryKey
        };

        // Set primary key verbose like this since it can't be set to false due
        // to a bug in waterline, see above.
        if (primaryKey) {
          attribute.primaryKey = true;
        }
      }
    }
  }

  // Add validation related methods.
  // @todo: rename this something else than patch, since it's not this anymore.
  utils.extend(schema, modelPatch);

  // Add before/after callbacks.
  ['Validate', 'Update', 'Create', 'Destroy'].map(function(operation) {
    ['before', 'after'].map(function(when) {
      var callbackName = when + operation;
      if (callbackName in settings) {
        schema[callbackName] = settings[callbackName];
      }
    });
  });

  // Add type specific collection methods.
  if (settings.statics && schema.attributes) {
    utils.extend(schema, settings.statics);
  }

  // Add type methods to model.
  if (settings.methods && schema.attributes) {
    utils.extend(schema.attributes, settings.methods);
  }

  var collection = Waterline.Collection.extend(schema);

  // Load the collection into Waterline.
  this.waterline.loadCollection(collection);
};

/**
 * Load all enabled extensions.
 *
 * @param {Function} callback Function to run when application finishes
 *   loading extensions.
 */
Application.prototype.loadAllExtensions = function(callback) {
  var extensionsPaths = [
    this.settings.baseDir + '/applications/default/extensions',
    this.settings.applicationDir + '/extensions'
  ];

  var self = this;
  this.loadExtensions(extensionsPaths, function(error, extensions) {
    if (error) {
      return callback(error);
    }
    async.each(Object.keys(extensions), function(extensionName, next) {
      // Add extension static files.
      var filesPath = extensions[extensionName].settings.path + '/public';
      fs.exists(filesPath, function(exists) {
        if (exists) {
          self.application.use(serveStatic(filesPath));
        }
        next();
      });
    }, callback);
  });
};

/**
 * Load all types.
 *
 * @param {Function} callback Function to run when application finishes
 *   loading types.
 */
Application.prototype.loadAllTypes = function(callback) {
  var self = this;

  // Collect types from all extensions.
  this.collect('type', function(error, types) {
    if (error) {
      return callback(error);
    }

    async.each(Object.keys(types), function(typeName, next) {
      self.type(typeName, types[typeName]);
      next();
    }, callback);
  });
};

/**
 * Collect data from JSON files on application overrides folder and merge them
 * to the data container object.
 *
 * @param {Type} type Type object to collect data for.
 * @param {Object} data Data container object.
 * @param {Function} callback Callback to run after all data have been
 *   collected.
 */
Application.prototype.overrides = function(type, data, callback) {
  var overridesDir = path.join(this.settings.applicationDir, 'overrides');

  fs.exists(overridesDir, function(exists) {
    if (!exists) {
      return callback();
    }

    // Scan for JSON files for this type.
    Prana.Extension.scanItems(overridesDir, type, function(error, foundItems) {
      if (error) {
        return callback(error);
      }

      utils.extend(data, foundItems);
      callback();
    });

  });
};

/**
 * List items from the storage.
 *
 * @param {String} typeName A type name string.
 * @param {Object} query Query object with conditions and filters.
 * @param {Function} callback Function to run when items are returned.
 */
Application.prototype.list = function(typeName, query, callback) {
  this.type(typeName).list(query, callback);
};

/**
 * Load a single item from the storage.
 *
 * @param {String} typeName A type name string.
 * @param {Mixed} key The key that represents the object to load.
 * @param {Function} callback Function to run when the item is returned.
 */
Application.prototype.load = function(typeName, key, callback) {
  this.type(typeName).load(key, callback);
};

/**
 * Save a item to the storage.
 *
 * @param {String} typeName A type name string.
 * @param {Object} key Object with values for the object.
 * @param {Function} callback Function to run when the item is saved.
 */
Application.prototype.save = function(typeName, item, callback) {
  this.type(typeName).save(item, callback);
};

/**
 * Delete a single item from the storage.
 *
 * @param {String} typeName A type name string.
 * @param {Mixed} key The key that represents the object to delete.
 * @param {Function} callback Function to run when the item is deleted.
 */
Application.prototype.delete = function(typeName, key, callback) {
  this.type(typeName).delete(key, callback);
};

Application.prototype.install = function(callback) {
  this.settings.extensions = {
    'field': {},
    'route': {},
    'context': {},
    'layout': {},
    'panel': {},
    'navigation': {},
    'page': {},
    'theme': {},
    'rest': {},
    'form': {},
    'installer': {}
  };

  this.access = function(request, permission, callback) {
    // Allow access to eveything.
    callback(null, true);
  };

  callback();
};
<|endoftext|>


<commit_before>var CSLValidator = (function() {

    //to access URL parameters
    var uri;

    //required for highlighting in ace editor
    var Range;

    //keep track of source code highlighting, so we can remove prior highlighting
    //when selecting a different error
    var marker;

    var validateButton;

    //keep track of how much time validator.nu is taking
    var responseTimer;
    var responseMaxTime = 6000; //in milliseconds
    var responseStartTime;
    var responseEndTime;

    var init = function() {

        //Initialize URI.js
        uri = new URI();

        //Create range for Ace editor
        Range = ace.require("ace/range").Range;

        //Initialize Ladda button
        validateButton = Ladda.create(document.querySelector('#validate'));

        //set schema-version if specified
        if (uri.hasQuery('version')) {
          var setSchemaVersion = uri.query(true)['version'];

          //http://stackoverflow.com/a/2248991/1712389
          $('#schema-version option').each(function() {
              if (this.value == setSchemaVersion) {
                  $("#schema-version").val(setSchemaVersion);
                  return false;
              }
          });
        }

        //run validation if URL parameters includes URL
        if (uri.hasQuery('url')) {
            var setURL = uri.query(true)['url'];
            $("#source-url").val(setURL);
            validate();
        }

        //update page for selected input method
        $("#source-method").change(function() {
            var sourceMethod = this.value;
            adaptToSourceMethod(sourceMethod);
        });

        //validate on button click
        $("#validate").click(validate);

        //validate when pressing Enter in URL text field
        $('#source-url').keydown(function(event) {
            if (event.keyCode == 13) {
                event.preventDefault();
                validate();
            }
        });
    };

    var adaptToSourceMethod = function(sourceMethod) {
        var inputField = "";

        switch (sourceMethod) {
            case "url":
                inputField = '<input id="source-url" class="form-control source-input">';
                break;
            case "file-upload":
                inputField = '<input id="source-file" class="source-input" type="file">';
                break;
            case "textarea":
                inputField = '<textarea id="source-text" class="form-control source-input" rows="15"></textarea>';
                break;
        }

        $(".source-input").replaceWith(inputField);
    };

    function validate() {

        removeValidationResults();

        validateButton.start();

        responseStartTime = new Date();
        responseTimer = window.setTimeout(reportTimeOut, responseMaxTime);

        var schemaURL = "https://raw.githubusercontent.com/citation-style-language/schema/v" + $('#schema-version').val() + "/csl.rnc";
        schemaURL += " " + "https://raw.githubusercontent.com/citation-style-language/schema/master/csl.sch";

        var sourceMethod = $('#source-method').val();

        switch (sourceMethod) {
            case "url":
                var documentURL = $('#source-url').val();

                uri.setSearch("url", documentURL);
                uri.setSearch("version", $('#schema-version').val());
                history.pushState({}, document.title, uri);

                //don't try validation on empty string
                if ($.trim(documentURL).length > 0) {
                    validateViaGET(schemaURL, documentURL);
                } else {
                    window.clearTimeout(responseTimer);
                    validateButton.stop();
                }

                break;
            case "file-upload":
                uri.search("");
                history.pushState({}, document.title, uri);

                var documentFile = $('#source-file').get(0).files[0];
                validateViaPOST(schemaURL, documentFile, sourceMethod);
                break;
            case "textarea":
                uri.search("");
                history.pushState({}, document.title, uri);

                var documentContent = $('#source-text').val();
                validateViaPOST(schemaURL, documentContent, sourceMethod);
                break;
        }
    }

    function validateViaGET(schemaURL, documentURL) {
        $.get("http://validator.w3.org/nu/", {
                doc: documentURL,
                schema: schemaURL,
                parser: "xml",
                laxtype: "yes",
                level: "error",
                out: "json",
                showsource: "yes"
            })
            .done(function(data) {
                parseResponse(data);
            });
    }

    function validateViaPOST(schemaURL, documentContent, sourceMethod) {
        var formData = new FormData();
        formData.append("schema", schemaURL);
        formData.append("parser", "xml");
        formData.append("laxtype", "yes");
        formData.append("level", "error");
        formData.append("out", "json");
        formData.append("showsource", "yes");

        if (sourceMethod == "textarea") {
            formData.append("content", documentContent);
        } else {
            formData.append("file", documentContent);
        }

        $.ajax({
            type: "POST",
            url: "http://validator.w3.org/nu/",
            data: formData,
            success: function(data) {
                parseResponse(data);
            },
            processData: false,
            contentType: false
        });
    }

    function parseResponse(data) {
        //console.log(JSON.stringify(data));

        window.clearTimeout(responseTimer);
        responseEndTime = new Date();
        console.log("Received response from http://validator.w3.org/nu/ after " + (responseEndTime - responseStartTime) + "ms");

        removeValidationResults();

        var messages = data.messages;
        var errorCount = 0;
        var nonDocumentError = "";
        for (var i = 0; i < messages.length; i++) {
            if (messages[i].type == "non-document-error") {
                nonDocumentError = messages[i].message;
            } else if (messages[i].type == "error") {
                errorCount += 1;

                var results = "";
                results += '
';

                var range = "";
                var firstLine = "";
                var lineText = "";
                var lastLine = messages[i].lastLine;
                var firstColumn = messages[i].firstColumn;
                var lastColumn = messages[i].lastColumn;
                if (messages[i].hasOwnProperty('firstLine')) {
                    firstLine = messages[i].firstLine;
                    range = firstLine + "-" + lastLine;
                    lineText = "Lines " + range;
                } else {
                    firstLine = lastLine;
                    lineText = "Line " + lastLine;
                }
                sourceHighlightRange = firstLine + ',' + firstColumn + ',' + lastLine + ',' + lastColumn;
                results += '' + lineText + ': ';

                results += messages[i].message;
                results += '
';
                results += "
";
                $("#error-list").append(results);
                $("#error-" + errorCount).text(messages[i].extract);

                var errorDiv = ace.edit("error-" + errorCount);
                errorDiv.setReadOnly(true);
                errorDiv.getSession().setUseWrapMode(true);
                errorDiv.renderer.setShowGutter(false);
                errorDiv.setHighlightActiveLine(false);
                errorDiv.renderer.$cursorLayer.element.style.opacity = 0;
                errorDiv.setTheme("ace/theme/kuroir");
                errorDiv.getSession().setMode("ace/mode/xml");

                lineDifference = lastLine - firstLine;
                if (firstLine == 1) {
                    firstLine = firstLine - 1;
                } else {
                    firstLine = 1;
                }
                lastLine = firstLine + lineDifference;
                errorHighlightRange = new Range(firstLine, firstColumn - 1, lastLine, lastColumn);
                errorDiv.session.addMarker(errorHighlightRange, "ace_active-line", "text");

                var newHeight =
                    errorDiv.getSession().getScreenLength() * errorDiv.renderer.lineHeight + errorDiv.renderer.scrollBar.getWidth();

                $("#error-" + errorCount).height(newHeight.toString() + "px");
                errorDiv.resize();
            }
        }

        if (nonDocumentError !== "") {
            $("#alert").append('
Validation failed: ' + nonDocumentError + '
');
        } else if (errorCount === 0) {
            $("#alert").append('
Good job! No errors found.
Interested in contributing your style or locale file? See our instructions.
');
        } else if (errorCount > 0) {
            if (errorCount == 1) {
                $("#alert").append('
Oops, I found 1 error.
');
            } else {
                $("#alert").append('
Oops, I found ' + errorCount + ' errors.
');
            }
            $("#alert > div.alert-danger").append('
If you have trouble understanding the error messages below, start by reading the CSL specification.');

            $("#errors").attr("class", "panel panel-warning");
            $("#errors").prepend('

Errors

');
            $('[data-toggle="tooltip"]').tooltip();
        }

        if (data.source.code.length > 0) {
            $("#source").append('

Source

');
            $("#source").append('

');
            $("#source").attr("class", "panel panel-primary");
            $("#source-code").text(data.source.code);

            window.editor = ace.edit("source-code");
            editor.setReadOnly(true);
            editor.getSession().setUseWrapMode(true);
            editor.setHighlightActiveLine(true);
            editor.renderer.$cursorLayer.element.style.opacity = 0;
            editor.setTheme("ace/theme/eclipse");
            editor.getSession().setMode("ace/mode/xml");
        }

        validateButton.stop();
    }

    function moveToLine(firstLine, firstColumn, lastLine, lastColumn) {
        editor.scrollToLine(firstLine, true, true, function() {});
        editor.gotoLine(firstLine, 0, false);
        //alert(firstLine + "," + firstColumn + "," + lastLine + "," + lastColumn);
        sourceHighlightRange = new Range(firstLine - 1, firstColumn - 1, lastLine - 1, lastColumn);
        editor.session.removeMarker(marker);
        marker = editor.session.addMarker(sourceHighlightRange, "ace_selection", "text");
    }

    function removeValidationResults() {
        $(".inserted").remove();
        $("#errors").removeAttr("class");
        $("#source").removeAttr("class");
    }

    function reportTimeOut() {
        validateButton.stop();
        console.log("Call to http://validator.w3.org/nu/ timed out after " + responseMaxTime + "ms.");
        $("#alert").append('
Validation is taking longer than expected! (more than ' + responseMaxTime/1000 + ' seconds)
');
        $("#alert > div.alert-warning").append('
This typically happens if the Nu HTML Checker website is down, but maybe you get lucky if you wait a little longer.');
    }

    return {
        init: init,
        moveToLine: moveToLine
    };
}());
<commit_msg>Use range selection instead of marker<commit_after>var CSLValidator = (function() {

    //to access URL parameters
    var uri;

    //required for highlighting in ace editor
    var Range;

    //keep track of source code highlighting, so we can remove prior highlighting
    //when selecting a different error
    var marker;

    var validateButton;

    //keep track of how much time validator.nu is taking
    var responseTimer;
    var responseMaxTime = 6000; //in milliseconds
    var responseStartTime;
    var responseEndTime;

    var init = function() {

        //Initialize URI.js
        uri = new URI();

        //Create range for Ace editor
        Range = ace.require("ace/range").Range;

        //Initialize Ladda button
        validateButton = Ladda.create(document.querySelector('#validate'));

        //set schema-version if specified
        if (uri.hasQuery('version')) {
          var setSchemaVersion = uri.query(true)['version'];

          //http://stackoverflow.com/a/2248991/1712389
          $('#schema-version option').each(function() {
              if (this.value == setSchemaVersion) {
                  $("#schema-version").val(setSchemaVersion);
                  return false;
              }
          });
        }

        //run validation if URL parameters includes URL
        if (uri.hasQuery('url')) {
            var setURL = uri.query(true)['url'];
            $("#source-url").val(setURL);
            validate();
        }

        //update page for selected input method
        $("#source-method").change(function() {
            var sourceMethod = this.value;
            adaptToSourceMethod(sourceMethod);
        });

        //validate on button click
        $("#validate").click(validate);

        //validate when pressing Enter in URL text field
        $('#source-url').keydown(function(event) {
            if (event.keyCode == 13) {
                event.preventDefault();
                validate();
            }
        });
    };

    var adaptToSourceMethod = function(sourceMethod) {
        var inputField = "";

        switch (sourceMethod) {
            case "url":
                inputField = '<input id="source-url" class="form-control source-input">';
                break;
            case "file-upload":
                inputField = '<input id="source-file" class="source-input" type="file">';
                break;
            case "textarea":
                inputField = '<textarea id="source-text" class="form-control source-input" rows="15"></textarea>';
                break;
        }

        $(".source-input").replaceWith(inputField);
    };

    function validate() {

        removeValidationResults();

        validateButton.start();

        responseStartTime = new Date();
        responseTimer = window.setTimeout(reportTimeOut, responseMaxTime);

        var schemaURL = "https://raw.githubusercontent.com/citation-style-language/schema/v" + $('#schema-version').val() + "/csl.rnc";
        schemaURL += " " + "https://raw.githubusercontent.com/citation-style-language/schema/master/csl.sch";

        var sourceMethod = $('#source-method').val();

        switch (sourceMethod) {
            case "url":
                var documentURL = $('#source-url').val();

                uri.setSearch("url", documentURL);
                uri.setSearch("version", $('#schema-version').val());
                history.pushState({}, document.title, uri);

                //don't try validation on empty string
                if ($.trim(documentURL).length > 0) {
                    validateViaGET(schemaURL, documentURL);
                } else {
                    window.clearTimeout(responseTimer);
                    validateButton.stop();
                }

                break;
            case "file-upload":
                uri.search("");
                history.pushState({}, document.title, uri);

                var documentFile = $('#source-file').get(0).files[0];
                validateViaPOST(schemaURL, documentFile, sourceMethod);
                break;
            case "textarea":
                uri.search("");
                history.pushState({}, document.title, uri);

                var documentContent = $('#source-text').val();
                validateViaPOST(schemaURL, documentContent, sourceMethod);
                break;
        }
    }

    function validateViaGET(schemaURL, documentURL) {
        $.get("http://validator.w3.org/nu/", {
                doc: documentURL,
                schema: schemaURL,
                parser: "xml",
                laxtype: "yes",
                level: "error",
                out: "json",
                showsource: "yes"
            })
            .done(function(data) {
                parseResponse(data);
            });
    }

    function validateViaPOST(schemaURL, documentContent, sourceMethod) {
        var formData = new FormData();
        formData.append("schema", schemaURL);
        formData.append("parser", "xml");
        formData.append("laxtype", "yes");
        formData.append("level", "error");
        formData.append("out", "json");
        formData.append("showsource", "yes");

        if (sourceMethod == "textarea") {
            formData.append("content", documentContent);
        } else {
            formData.append("file", documentContent);
        }

        $.ajax({
            type: "POST",
            url: "http://validator.w3.org/nu/",
            data: formData,
            success: function(data) {
                parseResponse(data);
            },
            processData: false,
            contentType: false
        });
    }

    function parseResponse(data) {
        //console.log(JSON.stringify(data));

        window.clearTimeout(responseTimer);
        responseEndTime = new Date();
        console.log("Received response from http://validator.w3.org/nu/ after " + (responseEndTime - responseStartTime) + "ms");

        removeValidationResults();

        var messages = data.messages;
        var errorCount = 0;
        var nonDocumentError = "";
        for (var i = 0; i < messages.length; i++) {
            if (messages[i].type == "non-document-error") {
                nonDocumentError = messages[i].message;
            } else if (messages[i].type == "error") {
                errorCount += 1;

                var results = "";
                results += '
';

                var range = "";
                var firstLine = "";
                var lineText = "";
                var lastLine = messages[i].lastLine;
                var firstColumn = messages[i].firstColumn;
                var lastColumn = messages[i].lastColumn;
                if (messages[i].hasOwnProperty('firstLine')) {
                    firstLine = messages[i].firstLine;
                    range = firstLine + "-" + lastLine;
                    lineText = "Lines " + range;
                } else {
                    firstLine = lastLine;
                    lineText = "Line " + lastLine;
                }
                sourceHighlightRange = firstLine + ',' + firstColumn + ',' + lastLine + ',' + lastColumn;
                results += '' + lineText + ': ';

                results += messages[i].message;
                results += '
';
                results += "
";
                $("#error-list").append(results);
                $("#error-" + errorCount).text(messages[i].extract);

                var errorDiv = ace.edit("error-" + errorCount);
                errorDiv.setReadOnly(true);
                errorDiv.getSession().setUseWrapMode(true);
                errorDiv.renderer.setShowGutter(false);
                errorDiv.setHighlightActiveLine(false);
                errorDiv.renderer.$cursorLayer.element.style.opacity = 0;
                errorDiv.setTheme("ace/theme/kuroir");
                errorDiv.getSession().setMode("ace/mode/xml");

                lineDifference = lastLine - firstLine;
                if (firstLine == 1) {
                    firstLine = firstLine - 1;
                } else {
                    firstLine = 1;
                }
                lastLine = firstLine + lineDifference;
                errorHighlightRange = new Range(firstLine, firstColumn - 1, lastLine, lastColumn);
                errorDiv.session.addMarker(errorHighlightRange, "ace_active-line", "text");

                var newHeight =
                    errorDiv.getSession().getScreenLength() * errorDiv.renderer.lineHeight + errorDiv.renderer.scrollBar.getWidth();

                $("#error-" + errorCount).height(newHeight.toString() + "px");
                errorDiv.resize();
            }
        }

        if (nonDocumentError !== "") {
            $("#alert").append('
Validation failed: ' + nonDocumentError + '
');
        } else if (errorCount === 0) {
            $("#alert").append('
Good job! No errors found.
Interested in contributing your style or locale file? See our instructions.
');
        } else if (errorCount > 0) {
            if (errorCount == 1) {
                $("#alert").append('
Oops, I found 1 error.
');
            } else {
                $("#alert").append('
Oops, I found ' + errorCount + ' errors.
');
            }
            $("#alert > div.alert-danger").append('
If you have trouble understanding the error messages below, start by reading the CSL specification.');

            $("#errors").attr("class", "panel panel-warning");
            $("#errors").prepend('

Errors

');
            $('[data-toggle="tooltip"]').tooltip();
        }

        if (data.source.code.length > 0) {
            $("#source").append('

Source

');
            $("#source").append('

');
            $("#source").attr("class", "panel panel-primary");
            $("#source-code").text(data.source.code);

            window.editor = ace.edit("source-code");
            editor.setReadOnly(true);
            editor.getSession().setUseWrapMode(true);
            editor.setHighlightActiveLine(true);
            editor.renderer.$cursorLayer.element.style.opacity = 0;
            editor.setTheme("ace/theme/eclipse");
            editor.getSession().setMode("ace/mode/xml");
        }

        validateButton.stop();
    }

    function moveToLine(firstLine, firstColumn, lastLine, lastColumn) {
        editor.scrollToLine(firstLine, true, true, function() {});
        editor.gotoLine(firstLine, 0, false);
        sourceHighlightRange = new Range(firstLine - 1, firstColumn - 1, lastLine - 1, lastColumn);
        editor.selection.setRange(sourceHighlightRange);
    }

    function removeValidationResults() {
        $(".inserted").remove();
        $("#errors").removeAttr("class");
        $("#source").removeAttr("class");
    }

    function reportTimeOut() {
        validateButton.stop();
        console.log("Call to http://validator.w3.org/nu/ timed out after " + responseMaxTime + "ms.");
        $("#alert").append('
Validation is taking longer than expected! (more than ' + responseMaxTime/1000 + ' seconds)
');
        $("#alert > div.alert-warning").append('
This typically happens if the Nu HTML Checker website is down, but maybe you get lucky if you wait a little longer.');
    }

    return {
        init: init,
        moveToLine: moveToLine
    };
}());
<|endoftext|>