<commit_before>// acorn.shells.MultiShell -- one shell to contain them all.
// ---------------------------------------------------------

var MultiShell = acorn.shells.MultiShell = Shell.extend({
  // leelo dallas multi shell

  shellid: 'acorn.MultiShell',

  // The cannonical type of this media. One of `acorn.types`.
  type: 'multimedia',

  // The shell-specific control components to use.
  controls: [
    'LeftControl',
    'ListControl',
    'RightControl',
  ],

  initialize: function() {
    Shell.prototype.initialize.call(this);

    this.data.shells = this.data.shells || [];
  },

  // **title** returns a simple title of the shell
  title: function() {
    var items = _.keys(this.data.shells).length;
    var s = (items == 1 ? '' : 's');
    return 'playlist with ' + items + ' item' + s;
  },

  // **thumbnailLink** use the first shell's thumbnail
  thumbnailLink: function() {
    var first = this.data.shells[0];
    if (!first)
      return '';

    var shell = acorn.shellWithData(first);
    return shell.thumbnailLink();
  },

  // **addShellData** adds another shell (via data)
  addShellData: function(shellData) {
    this.data.shells.push(shellData);
  },

  // **addShell** adds another shell
  addShell: function(shell) {
    this.addShellData(shell.data)
  },

});

// **ContentView** - Render each subshell in sequence.
// ---------------------------------------------------

MultiShell.ContentView = Shell.ContentView.extend({

  // **shellViews** is a container for sub shellViews.
  shellViews: [],

  // Supported trigger events
  //
  // * change:subview - fired when subview currently show changes.

  initialize: function() {
    Shell.ContentView.prototype.initialize.call(this);

    // controls
    this.parent.on('controls:left', this.onShowPrevious);
    this.parent.on('controls:list', this.onShowPlaylist);
    this.parent.on('controls:right', this.onShowNext);

    // multishell events
    this.on('change:subview', this.onChangedSubview);

    // initialize shells
    this.shells = _.map(this.shell.data.shells, acorn.shellWithData);
    _.map(this.shells, function (shell) { shell.retrieveExtraInfo(); });

  },

  render: function() {
    // remove all previously rendered shellViews.
    this.map(function(shellView) { shellView.remove(); });

    // construct all the views
    this.shellViews = _.map(this.shells, function (shell) {
      var contentView = new shell.shellClass.ContentView({
        shell: shell,
        parent: this,
        autoplay: true,
      });

      // subshell events
      contentView.on('playback:ended', this.onSubShellPlaybackEnded);

      return contentView;
    }, this);

    // clean up our elem
    this.$el.empty();

    this.showView(0)
  },

  currentViewIndex: function() {
    return _.indexOf(this.shellViews, this.currentView);
  },

  showView: function(index) {
    var shellView = this.shellViews[index];

    // bail if no view at that index.
    if (!shellView)
      return;

    // tear down previous ``currentView``
    if (this.currentView) {
      this.currentView.remove();
      // removing may be a bit drastic. perhaps:
      // this.currentView.stop();
      // this.currentView.$el.hide();
    }

    // set up shellView as ``currentView``
    this.currentView = shellView;
    if (!this.currentView.el.parentNode) {
      this.currentView.render();
      this.$el.append(this.currentView.el);
    }

    this.currentView.$el.show();

    // announce changes
    this.trigger('change:subview');
  },

  // **showPlaylist** bring up a container with subview summaries
  showPlaylist: function() {
    // only open playlist once.
    var playlistId = this.shell.shellClass.PlaylistView.prototype.id;
    if (this.$el.find('#' + playlistId).length > 0)
      return;

    var playlistView = new this.shell.shellClass.PlaylistView({
      shell: this.shell,
      parent: this,
    });

    playlistView.render();
    this.$el.append(playlistView.el);

    // stop playback on the currently-playing view
    this.currentView.stop();
  },

  // -- MultiShell Events

  onChangedSubview: function() {
    var contentView = this.parent;
    var controlsView = contentView.player.controlsView;

    var left = controlsView.controlWithId('left');
    var list = controlsView.controlWithId('list');
    var right = controlsView.controlWithId('right');

    left.$el.removeAttr('disabled');
    right.$el.removeAttr('disabled');

    if (this.currentView == _.first(this.shellViews))
      left.$el.attr('disabled', 'disabled');

    if (this.currentView == _.last(this.shellViews))
      right.$el.attr('disabled', 'disabled');
  },

  // **onShowPrevious** move back in the playlist.
  onShowPrevious: function() {
    this.showView(this.currentViewIndex() - 1);
  },

  // **onShowNext** move forward in the playlist.
  onShowNext: function() {
    this.showView(this.currentViewIndex() + 1);
  },

  // **onShowPlaylist** show the playlist to the user.
  onShowPlaylist: function() {
    this.showPlaylist();
  },

  // -- SubShell Events

  // **onSubShellPlaybackEnded** advance subshells
  onSubShellPlaybackEnded: function() {
    // if there are more subshells to play, play them.
    if (this.hasNext())
      this.onShowNext();

    // otherwise, signal multishell playback ended.
    else
      this.trigger('playback:ended');
  },

  // helper to know whether there are more subshells forward.
  hasNext: function() {
    return this.currentViewIndex() < this.shellViews.length - 1;
  },

  // helper to know whether there are more subshells backward.
  hasPrevious: function() {
    return this.currentViewIndex() > 0;
  },

  // helper to map `func` through `shellViews` with `this` as context
  map: function(func) {
    _.map(this.shellViews, func, this);
  },

});

// **PlaylistView** - A view to summarize the shells within a MultiShell
// ---------------------------------------------------------------------

MultiShell.PlaylistView = ShellView.extend({

  id: 'acorn-multishell-playlist',

  template: _.template('\

\
    <button id="close" class="btn">\
       Close\
    </button>\

\
  '),

  events: {
    'click button#close': 'onClickClose',
    'click button#view': 'onClickView',
  },

  render: function() {
    this.$el.empty();
    this.$el.html(this.template());

    var title = this.shell.title();
    this.$el.find('#title').text(title);

    var currentIndex = this.parent.currentViewIndex();

    var summaries = this.$el.find('#summaries');
    _.map(this.parent.shells, function(shell, idx) {

      var summary = new shell.shellClass.SummaryView({
        shell: shell,
        parent: this,
        autplay: this.options.autoplay,
      });

      summary.render();
      summaries.append(summary.el);

      // if this is the currently-viewed shell, mark it selected
      if (idx == currentIndex)
        summary.$el.addClass('selected');

      // an action to view shells from the playlist
      var view_btn =
        $('<button>')
          .text('View')
          .addClass('btn')
          .attr('id', 'view')
          .attr('data-index', idx);

      summary.$el.find('#buttons').append(view_btn);

    }, this);

  },

  onClickClose: function() {
    this.remove();
  },

  onClickView: function(event) {
    var index = $(event.target).attr('data-index');
    this.parent.showView(index);
    this.remove();
  },

});

// EditView -- displays the subshell EditViews.
// --------------------------------------------

MultiShell.EditView = Shell.EditView.extend({

  // **shellViews** is a container for sub shellViews.
  shellViews: [],

  events: {
    'click button#add': 'onClickAdd',
  },

  template: _.template('\

\
    <button class="btn btn-large" id="add">Add Link</button>\
  '),

  render: function() {

    // remove all previously rendered shellViews.
    this.map(function(shellView) { shellView.remove(); });

    // construct all the views
    this.shellViews = _.map(this.shell.data.shells, function(data, index) {
      return this.constructView(data, index);
    }, this);

    // clean up our elem
    this.$el.empty();
    this.$el.append(this.template());

    var subshells_el = this.$el.find('#subshells');

    // render and append all shellViews.
    this.map(function (shellView) {
      shellView.render();
      subshells_el.append(shellView.el);
    });

  },

  constructView: function(shellData, index) {
    // retrieve shell class from data info
    var shell = acorn.shellWithData(shellData);

    // construct this shell's EditView.
    var shellView = new shell.shellClass.EditView({
      shell: shell,
      parent: this,
    });

    // listen to events of shell EditView
    shellView.on('swap:shell', function(data) {
      this.onSwapSubShell(data, index);
    }, this);

    shellView.on('change:shell', function(data) {
      this.onChangeSubShell(data, index);
    }, this);

    shellView.on('change:editState', this.onChangeEditState);

    return shellView;
  },

  // -- Shell Overrides

  // **isEditing** returns whether any shellView isEditing
  isEditing: function(value) {
    return _.any(this.shellViews, function (shellView) {
      return shellView.isEditing();
    });
  },

  // **onChangeShell** overriden to prevent re-render
  onChangeShell: function() {},

  // **finalizeEdit** propagate to subshells
  finalizeEdit: function() {
    this.map(function (shellView) {
      return shellView.finalizeEdit();
    });
  },

  // -- MultiShell Events

  onChangeEditState: function() {
    this.trigger('change:editState');
  },

  // **onClickAdd** add another link + shell
  onClickAdd: function() {
    // // additional, placeholder adding shells
    var nextIndex = this.shellViews.length;
    var addData = acorn.shellForLink('').data;
    var addView = this.constructView(addData, nextIndex);

    addView.render();
    this.shellViews.push(addView);

    var subshells_el = this.$el.find('#subshells');
    subshells_el.append(addView.el);
  },

  // -- Sub Shell Events

  onSwapSubShell: function(data, index) {
    var oldShellView = this.shellViews[index];
    var newShellView = this.constructView(data, index);

    // render and add the new shellView.
    newShellView.render();
    oldShellView.$el.after(newShellView.el);

    // remove old shellView
    oldShellView.remove();
    this.shellViews[index] = newShellView;

    // update the data itself
    var shells = this.shells();
    shells[index] = data;
    this.shells(shells);
  },

  onChangeSubShell: function(shell, index) {
    // update the data itself
    var shells = this.shells();
    shells[index] = shell.data;
    this.shells(shells);
  },

  // get/setter for shells value
  shells: function(shells) {
    if (shells !== undefined) {
      this.shell.data.shells = shells;

      // if there is at most 1 shell, swap.
      if (shells.length <= 1)
        this.trigger('swap:shell', shells[0] || {});

      // else, announce that the shell has changed.
      else
        this.trigger('change:shell', this.shell);
    }
    return this.shell.data.shells;
  },

  // helper to map `func` through `shellViews` with `this` as context
  map: function(func) {
    return _.map(this.shellViews, func, this);
  },

});

// Register the shell with the acorn object.
acorn.registerShell(MultiShell);
<commit_msg>f/multishells: add `indexOfView` method, refactor `onSwapSubShell` and `onChangeSubShell`. this robusticizes these methods against `shells` array splicing when shells are deleted. - #65<commit_after>// acorn.shells.MultiShell -- one shell to contain them all.
// ---------------------------------------------------------

var MultiShell = acorn.shells.MultiShell = Shell.extend({
  // leelo dallas multi shell

  shellid: 'acorn.MultiShell',

  // The cannonical type of this media. One of `acorn.types`.
  type: 'multimedia',

  // The shell-specific control components to use.
  controls: [
    'LeftControl',
    'ListControl',
    'RightControl',
  ],

  initialize: function() {
    Shell.prototype.initialize.call(this);

    this.data.shells = this.data.shells || [];
  },

  // **title** returns a simple title of the shell
  title: function() {
    var items = _.keys(this.data.shells).length;
    var s = (items == 1 ? '' : 's');
    return 'playlist with ' + items + ' item' + s;
  },

  // **thumbnailLink** use the first shell's thumbnail
  thumbnailLink: function() {
    var first = this.data.shells[0];
    if (!first)
      return '';

    var shell = acorn.shellWithData(first);
    return shell.thumbnailLink();
  },

  // **addShellData** adds another shell (via data)
  addShellData: function(shellData) {
    this.data.shells.push(shellData);
  },

  // **addShell** adds another shell
  addShell: function(shell) {
    this.addShellData(shell.data)
  },

});

// **ContentView** - Render each subshell in sequence.
// ---------------------------------------------------

MultiShell.ContentView = Shell.ContentView.extend({

  // **shellViews** is a container for sub shellViews.
  shellViews: [],

  // Supported trigger events
  //
  // * change:subview - fired when subview currently show changes.

  initialize: function() {
    Shell.ContentView.prototype.initialize.call(this);

    // controls
    this.parent.on('controls:left', this.onShowPrevious);
    this.parent.on('controls:list', this.onShowPlaylist);
    this.parent.on('controls:right', this.onShowNext);

    // multishell events
    this.on('change:subview', this.onChangedSubview);

    // initialize shells
    this.shells = _.map(this.shell.data.shells, acorn.shellWithData);
    _.map(this.shells, function (shell) { shell.retrieveExtraInfo(); });

  },

  render: function() {
    // remove all previously rendered shellViews.
    this.map(function(shellView) { shellView.remove(); });

    // construct all the views
    this.shellViews = _.map(this.shells, function (shell) {
      var contentView = new shell.shellClass.ContentView({
        shell: shell,
        parent: this,
        autoplay: true,
      });

      // subshell events
      contentView.on('playback:ended', this.onSubShellPlaybackEnded);

      return contentView;
    }, this);

    // clean up our elem
    this.$el.empty();

    this.showView(0)
  },

  indexOfView: function(view) {
    return _.indexOf(this.shellViews, view);
  },

  currentViewIndex: function() {
    return this.indexOfView(this.currentView);
  },

  showView: function(index) {
    var shellView = this.shellViews[index];

    // bail if no view at that index.
    if (!shellView)
      return;

    // tear down previous ``currentView``
    if (this.currentView) {
      this.currentView.remove();
      // removing may be a bit drastic. perhaps:
      // this.currentView.stop();
      // this.currentView.$el.hide();
    }

    // set up shellView as ``currentView``
    this.currentView = shellView;
    if (!this.currentView.el.parentNode) {
      this.currentView.render();
      this.$el.append(this.currentView.el);
    }

    this.currentView.$el.show();

    // announce changes
    this.trigger('change:subview');
  },

  // **showPlaylist** bring up a container with subview summaries
  showPlaylist: function() {
    // only open playlist once.
    var playlistId = this.shell.shellClass.PlaylistView.prototype.id;
    if (this.$el.find('#' + playlistId).length > 0)
      return;

    var playlistView = new this.shell.shellClass.PlaylistView({
      shell: this.shell,
      parent: this,
    });

    playlistView.render();
    this.$el.append(playlistView.el);

    // stop playback on the currently-playing view
    this.currentView.stop();
  },

  // -- MultiShell Events

  onChangedSubview: function() {
    var contentView = this.parent;
    var controlsView = contentView.player.controlsView;

    var left = controlsView.controlWithId('left');
    var list = controlsView.controlWithId('list');
    var right = controlsView.controlWithId('right');

    left.$el.removeAttr('disabled');
    right.$el.removeAttr('disabled');

    if (this.currentView == _.first(this.shellViews))
      left.$el.attr('disabled', 'disabled');

    if (this.currentView == _.last(this.shellViews))
      right.$el.attr('disabled', 'disabled');
  },

  // **onShowPrevious** move back in the playlist.
  onShowPrevious: function() {
    this.showView(this.currentViewIndex() - 1);
  },

  // **onShowNext** move forward in the playlist.
  onShowNext: function() {
    this.showView(this.currentViewIndex() + 1);
  },

  // **onShowPlaylist** show the playlist to the user.
  onShowPlaylist: function() {
    this.showPlaylist();
  },

  // -- SubShell Events

  // **onSubShellPlaybackEnded** advance subshells
  onSubShellPlaybackEnded: function() {
    // if there are more subshells to play, play them.
    if (this.hasNext())
      this.onShowNext();

    // otherwise, signal multishell playback ended.
    else
      this.trigger('playback:ended');
  },

  // helper to know whether there are more subshells forward.
  hasNext: function() {
    return this.currentViewIndex() < this.shellViews.length - 1;
  },

  // helper to know whether there are more subshells backward.
  hasPrevious: function() {
    return this.currentViewIndex() > 0;
  },

  // helper to map `func` through `shellViews` with `this` as context
  map: function(func) {
    _.map(this.shellViews, func, this);
  },

});

// **PlaylistView** - A view to summarize the shells within a MultiShell
// ---------------------------------------------------------------------

MultiShell.PlaylistView = ShellView.extend({

  id: 'acorn-multishell-playlist',

  template: _.template('\

\
    <button id="close" class="btn">\
       Close\
    </button>\

\
  '),

  events: {
    'click button#close': 'onClickClose',
    'click button#view': 'onClickView',
  },

  render: function() {
    this.$el.empty();
    this.$el.html(this.template());

    var title = this.shell.title();
    this.$el.find('#title').text(title);

    var currentIndex = this.parent.currentViewIndex();

    var summaries = this.$el.find('#summaries');
    _.map(this.parent.shells, function(shell, idx) {

      var summary = new shell.shellClass.SummaryView({
        shell: shell,
        parent: this,
        autplay: this.options.autoplay,
      });

      summary.render();
      summaries.append(summary.el);

      // if this is the currently-viewed shell, mark it selected
      if (idx == currentIndex)
        summary.$el.addClass('selected');

      // an action to view shells from the playlist
      var view_btn =
        $('<button>')
          .text('View')
          .addClass('btn')
          .attr('id', 'view')
          .attr('data-index', idx);

      summary.$el.find('#buttons').append(view_btn);

    }, this);

  },

  onClickClose: function() {
    this.remove();
  },

  onClickView: function(event) {
    var index = $(event.target).attr('data-index');
    this.parent.showView(index);
    this.remove();
  },

});

// EditView -- displays the subshell EditViews.
// --------------------------------------------

MultiShell.EditView = Shell.EditView.extend({

  // **shellViews** is a container for sub shellViews.
  shellViews: [],

  events: {
    'click button#add': 'onClickAdd',
  },

  template: _.template('\

\
    <button class="btn btn-large" id="add">Add Link</button>\
  '),

  render: function() {

    // remove all previously rendered shellViews.
    this.map(function(shellView) { shellView.remove(); });

    // construct all the views
    this.shellViews = _.map(this.shell.data.shells, function(data, index) {
      return this.constructView(data, index);
    }, this);

    // clean up our elem
    this.$el.empty();
    this.$el.append(this.template());

    var subshells_el = this.$el.find('#subshells');

    // render and append all shellViews.
    this.map(function (shellView) {
      shellView.render();
      subshells_el.append(shellView.el);
    });

  },

  constructView: function(shellData, index) {
    // retrieve shell class from data info
    var shell = acorn.shellWithData(shellData);

    // construct this shell's EditView.
    var shellView = new shell.shellClass.EditView({
      shell: shell,
      parent: this,
    });

    // listen to events of shell EditView
    shellView.on('swap:shell', function(data) {
      this.onSwapSubShell(data, shellView);
    }, this);

    shellView.on('change:shell', function(data) {
      this.onChangeSubShell(data, shellView);
    }, this);

    shellView.on('change:editState', this.onChangeEditState);

    return shellView;
  },

  // -- Shell Overrides

  // **isEditing** returns whether any shellView isEditing
  isEditing: function(value) {
    return _.any(this.shellViews, function (shellView) {
      return shellView.isEditing();
    });
  },

  // **onChangeShell** overriden to prevent re-render
  onChangeShell: function() {},

  // **finalizeEdit** propagate to subshells
  finalizeEdit: function() {
    this.map(function (shellView) {
      return shellView.finalizeEdit();
    });
  },

  // -- MultiShell Events

  onChangeEditState: function() {
    this.trigger('change:editState');
  },

  // **onClickAdd** add another link + shell
  onClickAdd: function() {
    // // additional, placeholder adding shells
    var nextIndex = this.shellViews.length;
    var addData = acorn.shellForLink('').data;
    var addView = this.constructView(addData, nextIndex);

    addView.render();
    this.shellViews.push(addView);

    var subshells_el = this.$el.find('#subshells');
    subshells_el.append(addView.el);
  },

  // -- Sub Shell Events

  onSwapSubShell: function(data, shellView) {
    var index = this.indexOfView(shellView);
    var oldShellView = this.shellViews[index];
    var newShellView = this.constructView(data, index);

    // render and add the new shellView.
    newShellView.render();
    oldShellView.$el.after(newShellView.el);

    // remove old shellView
    oldShellView.remove();
    this.shellViews[index] = newShellView;

    // update the data itself
    var shells = this.shells();
    shells[index] = data;
    this.shells(shells);
  },

  onChangeSubShell: function(shell, shellView) {
    // update the data itself
    var index = this.indexOfView(shellView);
    var shells = this.shells();
    shells[index] = shell.data;
    this.shells(shells);
  },

  // get/setter for shells value
  shells: function(shells) {
    if (shells !== undefined) {
      this.shell.data.shells = shells;

      // if there is at most 1 shell, swap.
      if (shells.length <= 1)
        this.trigger('swap:shell', shells[0] || {});

      // else, announce that the shell has changed.
      else
        this.trigger('change:shell', this.shell);
    }
    return this.shell.data.shells;
  },

  // helper to return a view's index in `shellViews`
  indexOfView: function(view) {
    return (
      _.bind(this.shell.shellClass.ContentView.prototype.indexOfView, this)
    )(view);
  },

  // helper to map `func` through `shellViews` with `this` as context
  map: function(func) {
    return _.map(this.shellViews, func, this);
  },

});

// Register the shell with the acorn object.
acorn.registerShell(MultiShell);
<|endoftext|>


<commit_before>"use strict";
const ws_1 = require("ws");
const models_1 = require("./models");
let findLatestMessage = (num) => {
    return models_1.Message.findAll({
        limit: num,
        order: "id DESC",
        raw: true
    }).then((messages) => {
        return messages.reverse();
    });
};
var ReceiveEventType;
(function (ReceiveEventType) {
    ReceiveEventType[ReceiveEventType["CREATE_MESSAGE"] = 0] = "CREATE_MESSAGE";
    ReceiveEventType[ReceiveEventType["PONG"] = 1] = "PONG";
})(ReceiveEventType || (ReceiveEventType = {}));
var SendEventType;
(function (SendEventType) {
    SendEventType[SendEventType["NEW_MESSAGE"] = 0] = "NEW_MESSAGE";
    SendEventType[SendEventType["PING"] = 1] = "PING";
})(SendEventType || (SendEventType = {}));
class BaseReceiveEvent {
    constructor(ev, value) {
        this.ev = ev;
        this.value = value;
    }
    response() {
    }
}
class CreateMessageEvent extends BaseReceiveEvent {
    response() {
        models_1.Message.create({ text: this.value });
        return newMessage(SendEventType.NEW_MESSAGE, this.value);
    }
}
let newMessage = (ev, value) => {
    return JSON.stringify({
        ev: SendEventType[ev],
        value: value,
    });
};
exports.bootup = () => {
    let wss = new ws_1.Server({ port: 8080 });
    let broadcast = (message) => {
        wss.clients.forEach((client) => {
            client.send(message);
        });
    };
    wss.on('connection', (ws) => {
        // send latest 10 messages
        findLatestMessage(10).then((messages) => {
            messages.forEach((obj) => {
                ws.send(newMessage(SendEventType.NEW_MESSAGE, obj["text"]));
            });
        });
        // wip
        // setInterval(() => {
        // ws.send(newMessage(SendEventType.PING, ""))
        // }, 2000)
        /*
         * expected json shceme:
         * {
         *   "ev": "CREATE_MESSAGE",
         *   "value": value,
         * }
         *
         * result json scheme:
         * {
         *   "ev":"NEW_MESSAGE",
         *   "value":""
         * }
         */
        ws.on('message', (undecoded_json) => {
            try {
                let json = JSON.parse(undecoded_json);
                // switching
                let { ev, value } = json;
                if (ev === ReceiveEventType[ReceiveEventType.CREATE_MESSAGE]) {
                    let response = new CreateMessageEvent(ev, value).response();
                    broadcast(response);
                }
            }
            catch (e) {
            }
        });
    });
};
<commit_msg>fixed<commit_after><|endoftext|>


<commit_before>/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 Michael Gira
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

var socket  = io();
var $frames = $('.frame');

/* Menu */
var $menuFrame      = $('.menu-frame');
var $titleHeader    = $('.menu-frame .menu-title h1');
var $menuSelections = $('.menu-frame .menu-selections');
var $createGame     = $('.menu-frame .menu-selections div:first-child');
var $joinGame       = $('.menu-frame .menu-selections div:last-child');
var $usernameForm   = $('.menu-frame .inputUsername');
var $usernameInput  = $('.menu-frame .inputUsername .username');
var $usernameButton = $('.menu-frame .inputUsername button');

/* Create Game */
var $createGameFrame = $('.createGame-frame');
var $startGameButton = $('.createGame-frame .createGame-start');
var $roundTime       = $('.createGame-frame .createGame-options .roundTime');

/* Game Code */
var $gameCodeFrame  = $('.gameCode-frame');
var $gameCode       = $('.gameCode-frame .gameCode-code');
var $gameCodeReady  = $('.gameCode-frame .gameCode-ready');
var $gameCodeCancel = $('.gameCode-frame .gameCode-cancel');

/* Join Game */
var $joinGameFrame  = $('.joinGame-frame');
var $joinGameBack   = $('.joinGame-frame .back-header .back-to-menu');
var $joinGameError  = $('.joinGame-frame .back-header .wrong-gameCode');
var $joinGameForm   = $('.joinGame-frame .joinGame-submitCode');
var $joinGameCode   = $('.joinGame-frame .joinGame-submitCode input')
var $joinGameButton = $('.joinGame-frame .joinGame-submitCode button');

/* Actual Game */
var $gameFrame = $('.game-frame'); // Your game-frame frame game is lame
var $leaveGame = $('.game-frame .leave-game'); // Lame

function showMenu() {
    $titleHeader.css('top', '-100vh');
    // Hide frames in case we have to skip multiple
    $frames.not('.active').not($menuFrame).css('visibility', 'hidden');
    $frames.removeClass('active');

    // Transition frames to the right
    $frames.not($menuFrame).animate({
        right: '-100%'
    }, 600, 'easeOutCubic', function() {
        $frames.css('visibility', 'visible');
    });

    // Show Menu
    $menuFrame.animate({
        right: 0
    }, 600, 'easeOutCubic', function() {
        $menuFrame.addClass('active');
    });

    // Trigger the bounce animation for the menu title
    $titleHeader.animate({
        top: 0
    }, 1500, 'easeOutBounce');
}

function transitionLeft($fromFrame, $toFrame) {
    $frames.removeClass('active');

    // Transition original frame to the right
    $fromFrame.animate({
        right: '-100%'
    }, 600, 'easeOutCubic');

    // Bring next frame from the left
    $toFrame.animate({
        right: 0
    }, 600, 'easeOutCubic', function() {
        $toFrame.addClass('active');
    });
}

function transitionRight($fromFrame, $toFrame) {
    $frames.removeClass('active');

    // Transition original frame to the left
    $fromFrame.animate({
        right: '100%'
    }, 600, 'easeOutCubic');

    // Bring next frame from the right
    $toFrame.animate({
        right: 0
    }, 600, 'easeOutCubic', function() {
        $toFrame.addClass('active');
    });
}

/* Make the buttons work */

$('.back-to-menu').click(showMenu);

// Menu

// Disable button when input is empty
$usernameInput.bind('keydown keyup', function(event) {
    $usernameButton.prop('disabled', this.value === '');
});

// Emit username when the form is submitted
$usernameForm.submit(function(event) {
    event.preventDefault();
    socket.emit('username', $usernameInput.val());
    $usernameForm.fadeOut();
    $menuSelections.delay(400).fadeIn();
});

$createGame.click(function() {
    transitionRight($menuFrame, $createGameFrame);
});

$joinGame.click(function() {
    transitionRight($menuFrame, $joinGameFrame);
});

// Create Game

$startGameButton.click(function() {
    var roundTime = parseInt($roundTime.val(), 10);
    socket.emit('create game', {
        roundTime: roundTime
    });

    socket.on('game id', function(id) {
        $gameCode.text(id);
        transitionRight($createGameFrame, $gameCodeFrame);
    });
});

// Game Code

$gameCodeReady.click(function() {
    transitionRight($gameCodeFrame, $gameFrame);
});

$gameCodeCancel.click(function() {
    socket.emit('leave game');
    transitionLeft($gameCodeFrame, $createGameFrame);
});

// Join Game

// Disable button when input is empty
$joinGameCode.bind('keydown keyup', function(event) {
    $joinGameButton.prop('disabled', this.value === '');
});

// Emit 'join game' when the form is submitted
$joinGameForm.submit(function(event) {
    event.preventDefault();
    socket.emit('join game', $joinGameCode.val());
    socket.on('join game response', function(success) {
        if(success) {
            $joinGameError.fadeOut();
            transitionRight($joinGameFrame, $gameFrame);
        } else {
            $joinGameError.fadeIn();
        }
    });
});

$joinGameBack.click(function() {
    $joinGameError.fadeOut();
});

// Actual Game

$leaveGame.click(function() {
    socket.emit('leave game');
    showMenu();
});

/* Actually do stuff */

$menuFrame.css('right', 0);
$menuSelections.hide();
$usernameButton.prop('disabled', $usernameInput.val() === '');
$joinGameButton.prop('disabled', $joinGameCode.val() === '');
$joinGameError.hide();
showMenu();

socket.on('debug', function(data) {
    console.log('debug');
    console.log(data);
});<commit_msg>Bounce title delay<commit_after>/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 Michael Gira
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

var socket  = io();
var $frames = $('.frame');

/* Menu */
var $menuFrame      = $('.menu-frame');
var $titleHeader    = $('.menu-frame .menu-title h1');
var $menuSelections = $('.menu-frame .menu-selections');
var $createGame     = $('.menu-frame .menu-selections div:first-child');
var $joinGame       = $('.menu-frame .menu-selections div:last-child');
var $usernameForm   = $('.menu-frame .inputUsername');
var $usernameInput  = $('.menu-frame .inputUsername .username');
var $usernameButton = $('.menu-frame .inputUsername button');

/* Create Game */
var $createGameFrame = $('.createGame-frame');
var $startGameButton = $('.createGame-frame .createGame-start');
var $roundTime       = $('.createGame-frame .createGame-options .roundTime');

/* Game Code */
var $gameCodeFrame  = $('.gameCode-frame');
var $gameCode       = $('.gameCode-frame .gameCode-code');
var $gameCodeReady  = $('.gameCode-frame .gameCode-ready');
var $gameCodeCancel = $('.gameCode-frame .gameCode-cancel');

/* Join Game */
var $joinGameFrame  = $('.joinGame-frame');
var $joinGameBack   = $('.joinGame-frame .back-header .back-to-menu');
var $joinGameError  = $('.joinGame-frame .back-header .wrong-gameCode');
var $joinGameForm   = $('.joinGame-frame .joinGame-submitCode');
var $joinGameCode   = $('.joinGame-frame .joinGame-submitCode input')
var $joinGameButton = $('.joinGame-frame .joinGame-submitCode button');

/* Actual Game */
var $gameFrame = $('.game-frame'); // Your game-frame frame game is lame
var $leaveGame = $('.game-frame .leave-game'); // Lame

function showMenu() {
    $titleHeader.css('top', '-100vh');
    // Hide frames in case we have to skip multiple
    $frames.not('.active').not($menuFrame).css('visibility', 'hidden');
    $frames.removeClass('active');

    // Transition frames to the right
    $frames.not($menuFrame).animate({
        right: '-100%'
    }, 600, 'easeOutCubic', function() {
        $frames.css('visibility', 'visible');
    });

    // Show Menu
    $menuFrame.animate({
        right: 0
    }, 600, 'easeOutCubic', function() {
        $menuFrame.addClass('active');
    });

    // Trigger the bounce animation for the menu title
    $titleHeader.delay(100).animate({
        top: 0
    }, 1500, 'easeOutBounce');
}

function transitionLeft($fromFrame, $toFrame) {
    $frames.removeClass('active');

    // Transition original frame to the right
    $fromFrame.animate({
        right: '-100%'
    }, 600, 'easeOutCubic');

    // Bring next frame from the left
    $toFrame.animate({
        right: 0
    }, 600, 'easeOutCubic', function() {
        $toFrame.addClass('active');
    });
}

function transitionRight($fromFrame, $toFrame) {
    $frames.removeClass('active');

    // Transition original frame to the left
    $fromFrame.animate({
        right: '100%'
    }, 600, 'easeOutCubic');

    // Bring next frame from the right
    $toFrame.animate({
        right: 0
    }, 600, 'easeOutCubic', function() {
        $toFrame.addClass('active');
    });
}

/* Make the buttons work */

$('.back-to-menu').click(showMenu);

// Menu

// Disable button when input is empty
$usernameInput.bind('keydown keyup', function(event) {
    $usernameButton.prop('disabled', this.value === '');
});

// Emit username when the form is submitted
$usernameForm.submit(function(event) {
    event.preventDefault();
    socket.emit('username', $usernameInput.val());
    $usernameForm.fadeOut();
    $menuSelections.delay(400).fadeIn();
});

$createGame.click(function() {
    transitionRight($menuFrame, $createGameFrame);
});

$joinGame.click(function() {
    transitionRight($menuFrame, $joinGameFrame);
});

// Create Game

$startGameButton.click(function() {
    var roundTime = parseInt($roundTime.val(), 10);
    socket.emit('create game', {
        roundTime: roundTime
    });

    socket.on('game id', function(id) {
        $gameCode.text(id);
        transitionRight($createGameFrame, $gameCodeFrame);
    });
});

// Game Code

$gameCodeReady.click(function() {
    transitionRight($gameCodeFrame, $gameFrame);
});

$gameCodeCancel.click(function() {
    socket.emit('leave game');
    transitionLeft($gameCodeFrame, $createGameFrame);
});

// Join Game

// Disable button when input is empty
$joinGameCode.bind('keydown keyup', function(event) {
    $joinGameButton.prop('disabled', this.value === '');
});

// Emit 'join game' when the form is submitted
$joinGameForm.submit(function(event) {
    event.preventDefault();
    socket.emit('join game', $joinGameCode.val());
    socket.on('join game response', function(success) {
        if(success) {
            $joinGameError.fadeOut();
            transitionRight($joinGameFrame, $gameFrame);
        } else {
            $joinGameError.fadeIn();
        }
    });
});

$joinGameBack.click(function() {
    $joinGameError.fadeOut();
});

// Actual Game

$leaveGame.click(function() {
    socket.emit('leave game');
    showMenu();
});

/* Actually do stuff */

$menuFrame.css('right', 0);
$menuSelections.hide();
$usernameButton.prop('disabled', $usernameInput.val() === '');
$joinGameButton.prop('disabled', $joinGameCode.val() === '');
$joinGameError.hide();
showMenu();

socket.on('debug', function(data) {
    console.log('debug');
    console.log(data);
});<|endoftext|>


<commit_before>const cards = {};

/******************************************************************************/
cards.albums = {};

cards.albums.drag_start =
function drag_start(event)
{
    const album_card = event.target.closest(".album_card");
    event.dataTransfer.setData("text/plain", album_card.id);
}

cards.albums.drag_end =
function drag_end(event)
{
}

cards.albums.drag_over =
function drag_over(event)
{
    event.preventDefault();
}

cards.albums.drag_drop =
function drag_drop(event)
{
    const child = document.getElementById(event.dataTransfer.getData("text"));
    const child_id = child.dataset.id;
    const parent = event.currentTarget;
    const parent_id = parent.dataset.id;
    event.dataTransfer.clearData();

    if (child_id == parent_id)
    {
        return;
    }

    let prompt;
    if (parent_id === "root")
    {
        const child_title = child.querySelector('.album_card_title').textContent.trim();
        prompt = `Remove child\n${child_title}?`;
    }
    else
    {
        const child_title = child.querySelector('.album_card_title').textContent.trim();
        const parent_title = parent.querySelector('.album_card_title').textContent.trim();
        prompt = `Move\n${child_title}\ninto\n${parent_title}?`;
    }

    if (! confirm(prompt))
    {
        return;
    }

    if (parent_id === "root")
    {
        api.albums.remove_child(ALBUM_ID, child_id, common.refresh_or_alert);
    }
    else if (ALBUM_ID)
    {
        api.albums.add_child(parent_id, child_id, null);
        api.albums.remove_child(ALBUM_ID, child_id, common.refresh_or_alert);
    }
    else
    {
        api.albums.add_child(parent_id, child_id, common.refresh_or_alert);
    }
}

/******************************************************************************/
cards.bookmarks = {};

cards.bookmarks.create =
function create(bookmark, add_author, add_delete_button, add_url_element)
{
    const bookmark_card = document.createElement("div");
    bookmark_card.className = "bookmark_card"
    bookmark_card.dataset.id = bookmark.id;

    const bookmark_title = document.createElement("a");
    bookmark_title.className = "bookmark_title";
    bookmark_title.href = bookmark.url;
    bookmark_title.innerText = bookmark.display_name;
    bookmark_card.appendChild(bookmark_title);

    // The URL element is always display:none, but its presence is useful in
    // facilitating the Editor object. If this bookmark will not be editable,
    // there is no need for it.
    if (add_url_element)
    {
        const bookmark_url = document.createElement("a");
        bookmark_url.className = "bookmark_url";
        bookmark_url.href = bookmark.url;
        bookmark_url.innerText = bookmark.url;
        bookmark_card.appendChild(bookmark_url);
    }

    // If more tools are added, this will become an `or`.
    // I just want to have the structure in place now.
    if (add_delete_button)
    {
        const bookmark_toolbox = document.createElement("div");
        bookmark_toolbox.className = "bookmark_toolbox"
        bookmark_card.appendChild(bookmark_toolbox);

        if (add_delete_button)
        {
            const delete_button = document.createElement("button");
            delete_button.className = "red_button button_with_confirm";
            delete_button.dataset.onclick = "return delete_bookmark_form(event);";
            delete_button.dataset.prompt = "Delete Bookmark?";
            delete_button.dataset.cancelClass = "gray_button";
            delete_button.innerText = "Delete";
            bookmark_toolbox.appendChild(delete_button);
            common.init_button_with_confirm(delete_button);
        }
    }

    return bookmark_card;
}

/******************************************************************************/
cards.photos = {};

cards.photos.MIMETYPE_THUMBNAILS = {
    "svg": "svg",

    "application/zip": "archive",
    "application/x-tar": "archive",

    "archive": "archive",
    "audio": "audio",
    "image": "image",
    "video": "video",
    "text": "txt",
};

cards.photos.file_link =
function file_link(photo, short)
{
    if (short)
    {
        return `/file/${photo.id}${photo.dot_extension}`;
    }
    const basename = escape(photo.filename);
    return `/file/${photo.id}/${basename}`;
}

cards.photos.create =
function create(photo, view)
{
    if (view !== "list" && view !== "grid")
    {
        view = "grid";
    }

    const photo_card = document.createElement("div");
    photo_card.id = `photo_card_${photo.id}`;
    photo_card.dataset.id = photo.id;
    photo_card.className = `photo_card photo_card_${view} photo_card_unselected`
    if (photo.searchhidden)
    {
        photo_card.classList.add("photo_card_searchhidden");
    }
    photo_card.ondragstart = "return cards.photos.drag_start(event);";
    photo_card.ondragend = "return cards.photos.drag_end(event);";
    photo_card.ondragover = "return cards.photos.drag_over(event);";
    photo_card.ondrop = "return cards.photos.drag_drop(event);";
    photo_card.draggable = true;

    const photo_card_filename = document.createElement("div");
    photo_card_filename.className = "photo_card_filename";
    const filename_link = document.createElement("a");
    filename_link.target = "_blank";
    filename_link.href = `/photo/${photo.id}`;
    filename_link.draggable = false;
    filename_link.innerText = photo.filename;
    photo_card_filename.appendChild(filename_link);
    photo_card.appendChild(photo_card_filename);

    const photo_card_metadata = document.createElement("span");
    photo_card_metadata.className = "photo_card_metadata";
    const metadatas = [];
    if (photo.width)
    {
        metadatas.push(`${photo.width}×${photo.height}`);
    }
    if (photo.duration)
    {
        metadatas.push(`${photo.duration_string}`);
    }
    photo_card_metadata.innerHTML = common.join_and_trail(metadatas, ", ");
    const filesize_file_link = document.createElement("a");
    filesize_file_link.href = cards.photos.file_link(photo);
    filesize_file_link.target = "_blank";
    filesize_file_link.draggable = false;
    filesize_file_link.innerText = photo.bytes_string;
    photo_card_metadata.append(filesize_file_link);
    photo_card.appendChild(photo_card_metadata);

    if (view == "grid")
    {
        let thumbnail_src;
        if (photo.has_thumbnail)
        {
            thumbnail_src = `/thumbnail/${photo.id}.jpg`;
        }
        else
        {
            thumbnail_src =
            cards.photos.MIMETYPE_THUMBNAILS[photo.extension] ||
            cards.photos.MIMETYPE_THUMBNAILS[photo.mimetype] ||
            cards.photos.MIMETYPE_THUMBNAILS[photo.simple_mimetype] ||
            "other";
            thumbnail_src = `/static/basic_thumbnails/${thumbnail_src}.png`;
        }

        const photo_card_thumbnail = document.createElement("a");
        photo_card_thumbnail.className = "photo_card_thumbnail";
        photo_card_thumbnail.target = "_blank";
        photo_card_thumbnail.href = `/photo/${photo.id}`;
        photo_card_thumbnail.draggable = false;
        const thumbnail_img = document.createElement("img");
        thumbnail_img.loading = "lazy";
        thumbnail_img.src = thumbnail_src;
        thumbnail_img.draggable = false;
        photo_card_thumbnail.appendChild(thumbnail_img);
        photo_card.appendChild(photo_card_thumbnail);
    }

    let tag_names_title = [];
    let tag_names_inner = "";
    for (const tag of photo.tags)
    {
        tag_names_title.push(tag.name);
        tag_names_inner = "T";
    }
    const photo_card_tags = document.createElement("span");
    photo_card_tags.className = "photo_card_tags";
    photo_card_tags.title = tag_names_title.join(",");
    photo_card_tags.innerText = tag_names_inner;
    photo_card.appendChild(photo_card_tags);

    const toolbutton = document.createElement("button");
    toolbutton.className = "photo_card_toolbutton hidden";
    toolbutton.onclick = "return cards.photos.show_tools(event);";
    photo_card.appendChild(toolbutton);

    const photo_card_tools = document.createElement("div");
    photo_card_tools.className = "photo_card_tools";
    photo_card_tools.onclick = "event.stopPropagation(); return;";
    photo_card.appendChild(photo_card_tools);

    if (photo_clipboard)
    {
        const clipboard_checkbox = photo_clipboard.give_checkbox(photo_card);
        photo_clipboard.apply_check(clipboard_checkbox);
    }

    return photo_card;
}

cards.photos.drag_start =
function drag_start(event)
{
}

cards.photos.drag_end =
function drag_end(event)
{
}

cards.photos.drag_over =
function drag_over(event)
{
}

cards.photos.drag_drop =
function drag_drop(event)
{
}

cards.photos.show_tools =
function show_tools(event)
{
    event.stopPropagation();
    event.preventDefault();
    const photo_card = event.target.closest(".photo_card");
    const toolbox = photo_card.getElementsByClassName("photo_card_tools")[0];
    if (toolbox.childElementCount === 0)
    {
        return;
    }
    contextmenus.show_menu(event, toolbox);
    return false;
}

/******************************************************************************/
cards.tags = {};
<commit_msg>Fix bookmark card being created without the h2.<commit_after>const cards = {};

/******************************************************************************/
cards.albums = {};

cards.albums.drag_start =
function drag_start(event)
{
    const album_card = event.target.closest(".album_card");
    event.dataTransfer.setData("text/plain", album_card.id);
}

cards.albums.drag_end =
function drag_end(event)
{
}

cards.albums.drag_over =
function drag_over(event)
{
    event.preventDefault();
}

cards.albums.drag_drop =
function drag_drop(event)
{
    const child = document.getElementById(event.dataTransfer.getData("text"));
    const child_id = child.dataset.id;
    const parent = event.currentTarget;
    const parent_id = parent.dataset.id;
    event.dataTransfer.clearData();

    if (child_id == parent_id)
    {
        return;
    }

    let prompt;
    if (parent_id === "root")
    {
        const child_title = child.querySelector('.album_card_title').textContent.trim();
        prompt = `Remove child\n${child_title}?`;
    }
    else
    {
        const child_title = child.querySelector('.album_card_title').textContent.trim();
        const parent_title = parent.querySelector('.album_card_title').textContent.trim();
        prompt = `Move\n${child_title}\ninto\n${parent_title}?`;
    }

    if (! confirm(prompt))
    {
        return;
    }

    if (parent_id === "root")
    {
        api.albums.remove_child(ALBUM_ID, child_id, common.refresh_or_alert);
    }
    else if (ALBUM_ID)
    {
        api.albums.add_child(parent_id, child_id, null);
        api.albums.remove_child(ALBUM_ID, child_id, common.refresh_or_alert);
    }
    else
    {
        api.albums.add_child(parent_id, child_id, common.refresh_or_alert);
    }
}

/******************************************************************************/
cards.bookmarks = {};

cards.bookmarks.create =
function create(bookmark, add_author, add_delete_button, add_url_element)
{
    const bookmark_card = document.createElement("div");
    bookmark_card.className = "bookmark_card"
    bookmark_card.dataset.id = bookmark.id;

    const h2 = document.createElement("h2");
    const bookmark_title = document.createElement("a");
    bookmark_title.className = "bookmark_title";
    bookmark_title.href = bookmark.url;
    bookmark_title.innerText = bookmark.display_name;
    h2.appendChild(bookmark_title);
    bookmark_card.appendChild(h2);

    // The URL element is always display:none, but its presence is useful in
    // facilitating the Editor object. If this bookmark will not be editable,
    // there is no need for it.
    if (add_url_element)
    {
        const bookmark_url = document.createElement("a");
        bookmark_url.className = "bookmark_url";
        bookmark_url.href = bookmark.url;
        bookmark_url.innerText = bookmark.url;
        bookmark_card.appendChild(bookmark_url);
    }

    // If more tools are added, this will become an `or`.
    // I just want to have the structure in place now.
    if (add_delete_button)
    {
        const bookmark_toolbox = document.createElement("div");
        bookmark_toolbox.className = "bookmark_toolbox"
        bookmark_card.appendChild(bookmark_toolbox);

        if (add_delete_button)
        {
            const delete_button = document.createElement("button");
            delete_button.className = "red_button button_with_confirm";
            delete_button.dataset.onclick = "return delete_bookmark_form(event);";
            delete_button.dataset.prompt = "Delete Bookmark?";
            delete_button.dataset.cancelClass = "gray_button";
            delete_button.innerText = "Delete";
            bookmark_toolbox.appendChild(delete_button);
            common.init_button_with_confirm(delete_button);
        }
    }

    return bookmark_card;
}

/******************************************************************************/
cards.photos = {};

cards.photos.MIMETYPE_THUMBNAILS = {
    "svg": "svg",

    "application/zip": "archive",
    "application/x-tar": "archive",

    "archive": "archive",
    "audio": "audio",
    "image": "image",
    "video": "video",
    "text": "txt",
};

cards.photos.file_link =
function file_link(photo, short)
{
    if (short)
    {
        return `/file/${photo.id}${photo.dot_extension}`;
    }
    const basename = escape(photo.filename);
    return `/file/${photo.id}/${basename}`;
}

cards.photos.create =
function create(photo, view)
{
    if (view !== "list" && view !== "grid")
    {
        view = "grid";
    }

    const photo_card = document.createElement("div");
    photo_card.id = `photo_card_${photo.id}`;
    photo_card.dataset.id = photo.id;
    photo_card.className = `photo_card photo_card_${view} photo_card_unselected`
    if (photo.searchhidden)
    {
        photo_card.classList.add("photo_card_searchhidden");
    }
    photo_card.ondragstart = "return cards.photos.drag_start(event);";
    photo_card.ondragend = "return cards.photos.drag_end(event);";
    photo_card.ondragover = "return cards.photos.drag_over(event);";
    photo_card.ondrop = "return cards.photos.drag_drop(event);";
    photo_card.draggable = true;

    const photo_card_filename = document.createElement("div");
    photo_card_filename.className = "photo_card_filename";
    const filename_link = document.createElement("a");
    filename_link.target = "_blank";
    filename_link.href = `/photo/${photo.id}`;
    filename_link.draggable = false;
    filename_link.innerText = photo.filename;
    photo_card_filename.appendChild(filename_link);
    photo_card.appendChild(photo_card_filename);

    const photo_card_metadata = document.createElement("span");
    photo_card_metadata.className = "photo_card_metadata";
    const metadatas = [];
    if (photo.width)
    {
        metadatas.push(`${photo.width}×${photo.height}`);
    }
    if (photo.duration)
    {
        metadatas.push(`${photo.duration_string}`);
    }
    photo_card_metadata.innerHTML = common.join_and_trail(metadatas, ", ");
    const filesize_file_link = document.createElement("a");
    filesize_file_link.href = cards.photos.file_link(photo);
    filesize_file_link.target = "_blank";
    filesize_file_link.draggable = false;
    filesize_file_link.innerText = photo.bytes_string;
    photo_card_metadata.append(filesize_file_link);
    photo_card.appendChild(photo_card_metadata);

    if (view == "grid")
    {
        let thumbnail_src;
        if (photo.has_thumbnail)
        {
            thumbnail_src = `/thumbnail/${photo.id}.jpg`;
        }
        else
        {
            thumbnail_src =
            cards.photos.MIMETYPE_THUMBNAILS[photo.extension] ||
            cards.photos.MIMETYPE_THUMBNAILS[photo.mimetype] ||
            cards.photos.MIMETYPE_THUMBNAILS[photo.simple_mimetype] ||
            "other";
            thumbnail_src = `/static/basic_thumbnails/${thumbnail_src}.png`;
        }

        const photo_card_thumbnail = document.createElement("a");
        photo_card_thumbnail.className = "photo_card_thumbnail";
        photo_card_thumbnail.target = "_blank";
        photo_card_thumbnail.href = `/photo/${photo.id}`;
        photo_card_thumbnail.draggable = false;
        const thumbnail_img = document.createElement("img");
        thumbnail_img.loading = "lazy";
        thumbnail_img.src = thumbnail_src;
        thumbnail_img.draggable = false;
        photo_card_thumbnail.appendChild(thumbnail_img);
        photo_card.appendChild(photo_card_thumbnail);
    }

    let tag_names_title = [];
    let tag_names_inner = "";
    for (const tag of photo.tags)
    {
        tag_names_title.push(tag.name);
        tag_names_inner = "T";
    }
    const photo_card_tags = document.createElement("span");
    photo_card_tags.className = "photo_card_tags";
    photo_card_tags.title = tag_names_title.join(",");
    photo_card_tags.innerText = tag_names_inner;
    photo_card.appendChild(photo_card_tags);

    const toolbutton = document.createElement("button");
    toolbutton.className = "photo_card_toolbutton hidden";
    toolbutton.onclick = "return cards.photos.show_tools(event);";
    photo_card.appendChild(toolbutton);

    const photo_card_tools = document.createElement("div");
    photo_card_tools.className = "photo_card_tools";
    photo_card_tools.onclick = "event.stopPropagation(); return;";
    photo_card.appendChild(photo_card_tools);

    if (photo_clipboard)
    {
        const clipboard_checkbox = photo_clipboard.give_checkbox(photo_card);
        photo_clipboard.apply_check(clipboard_checkbox);
    }

    return photo_card;
}

cards.photos.drag_start =
function drag_start(event)
{
}

cards.photos.drag_end =
function drag_end(event)
{
}

cards.photos.drag_over =
function drag_over(event)
{
}

cards.photos.drag_drop =
function drag_drop(event)
{
}

cards.photos.show_tools =
function show_tools(event)
{
    event.stopPropagation();
    event.preventDefault();
    const photo_card = event.target.closest(".photo_card");
    const toolbox = photo_card.getElementsByClassName("photo_card_tools")[0];
    if (toolbox.childElementCount === 0)
    {
        return;
    }
    contextmenus.show_menu(event, toolbox);
    return false;
}

/******************************************************************************/
cards.tags = {};
<|endoftext|>


<commit_before>// Open Source Initiative OSI - The MIT License (MIT):Licensing
//
// The MIT License (MIT)
// Copyright (c) 2012 DotCloud Inc (opensource@dotcloud.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

var socket = require("./socket"),
    _ = require("underscore"),
    nodeUtil = require("util"),
    events = require("events"),
    util = require("./util"),
    middleware = require("./middleware");

//Gets the arguments associated with a function as an array
//fun : Function
//      The function to get the arguments from.
//return : Array of Strings
//      The function's arguments
function getArguments(fun) {
    var m1 = /^[\s\(]*function[^(]*\(([^)]*)\)/,
        m2 = /\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g,
        m3 = /\s+/g;

    var names = fun.toString().match(m1)[1].replace(m2, '').replace(m3, '').split(',');
    var args = names.length == 1 && !names[0] ? [] : names;

    //Remove the last argument from the args becase it is the callback
    args.pop();

    return args;
}

//Extracts the public methods (and their arguments) of a context.
//context : Object
//      The object to be exposed
//return : Object of String => Array
//      A map where they key is the method name and the value are the method
//      arguments, not including the callback argument.
function publicMethods(context) {
    var methods = {};

    //Ignore members that start with an underscore or are not functions
    for(var name in context) {
        if(!/^_/.test(name) && typeof(context[name]) == 'function') {
            methods[name] = getArguments(context[name]);
        }
    }

    return methods;
}

// Creates a new server
// context : Object
//      The object to expose
function Server(context) {
    var self = this;

    self._socket = socket.server();
    util.eventProxy(self._socket, self, "error");

    self._context = context || {};
    self._methods = publicMethods(self._context);

    var newInspector = self._createIntrospector(context.constructor.name || "Object");

    context._zerorpc_inspect = newInspector;
    self._methods._zerorpc_inspect = getArguments(newInspector);

    self._socket.on("multiplexing-socket/receive", function(event) {
        if(event.name in self._methods) self._recv(event, self._context);
    });
}

nodeUtil.inherits(Server, events.EventEmitter);

Server.prototype.expose = function(name, method) {
    // use 'publicMethods' to use the same naming constraint
    var context = {};
    context[name] = method;
    var methods = publicMethods(context);
    // only extend this._methods when the method is 'public'
    if(name in methods) {
        this._methods[name] = methods[name];
    }
    // add to this._context anyway
    this._context[name] = method;
    return this;
};

Server.prototype.unexpose = function(name) {
    if(name in this._methods && name in this._context) {
        delete this._methods[name];
        delete this._context[name];
    }
    return this;
};

//Creates the new-school introspector
Server.prototype._createIntrospector = function(contextName) {
    var methods = {};
    var results = { name: contextName, methods: methods };

    for(var name in this._methods) {
        methods[name] = {
            doc: "",

            args: _.map(this._methods[name], function(arg) {
                return { name: arg };
            })
        };
    }

    return function(reply) {
        reply(null, results);
    };
}

//Called when a method call event is received
//event : Object
//      The ZeroRPC event
//context : Object
//      The object to expose.
Server.prototype._recv = function(event, context) {
    var self = this;
    var ch = self._socket.openChannel(event);
    var isFirst = true;

    //Sends an error
    var sendError = function(error) {
        var args = [error.type || "Error", error.message, error.stack];
        ch.send("ERR", args);
        ch.close();
    };

    //Listen for protocol errors - this should never happen
    ch.on("protocol-error", function(error) {
        self.emit("error", error);
    });

    //Listen for heartbeat errors
    ch.on("heartbeat-error", function(error) {
        self.emit("error", error);
    });

    //This is passed to RPC methods to call when they finish, or have a stream
    //update
    var result = function(error, item, more) {
        more = more || false;

        if(error) {
            //Create an error object if we were passed a string
            sendError(typeof(error) == 'string' ? new Error(error) : error);
        } else {
            if(isFirst && !more) {
                //Wrap item in an array for backwards compatibility issues
                //with ZeroRPC
                ch.send("OK", [item]);
            } else if(item !== undefined) {
                //Stream is a newer method that does not require item to be
                //wrapped in an array
                ch.send("STREAM", item);
            }

            if(!more) {
                if(!isFirst) ch.send("STREAM_DONE", []);
                ch.close();
            }
        }

        isFirst = false;
    };

    //The arguments should be an array
    if(!(event.args instanceof Array)) {
        self.emit("error", "Invalid event: Bad args");
        return ch.close();
    }

    //Call the method
    event.args.push(result);

    //Catch any errors and send them back to the client
    try {
        context[event.name].apply(context, event.args);
    } catch(e) {
        sendError(e);
    }
}

//Binds to a ZeroMQ endpoint
//endpoint : String
//      The ZeroMQ endpoint
Server.prototype.bind = function(endpoint) {
    this._socket.bind(endpoint);
};

//Connects to a ZeroMQ endpoint
//endpoint : String
//      The ZeroMQ endpoint
Server.prototype.connect = function(endpoint) {
    this._socket.connect(endpoint);
};

//Closes the server
Server.prototype.close = function() {
    this._socket.close();
};

exports.Server = Server;<commit_msg>Fix bug that occured when context was empty.<commit_after>// Open Source Initiative OSI - The MIT License (MIT):Licensing
//
// The MIT License (MIT)
// Copyright (c) 2012 DotCloud Inc (opensource@dotcloud.com)
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the "Software"),
// to deal in the Software without restriction, including without limitation
// the rights to use, copy, modify, merge, publish, distribute, sublicense,
// and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

var socket = require("./socket"),
    _ = require("underscore"),
    nodeUtil = require("util"),
    events = require("events"),
    util = require("./util"),
    middleware = require("./middleware");

//Gets the arguments associated with a function as an array
//fun : Function
//      The function to get the arguments from.
//return : Array of Strings
//      The function's arguments
function getArguments(fun) {
    var m1 = /^[\s\(]*function[^(]*\(([^)]*)\)/,
        m2 = /\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g,
        m3 = /\s+/g;

    var names = fun.toString().match(m1)[1].replace(m2, '').replace(m3, '').split(',');
    var args = names.length == 1 && !names[0] ? [] : names;

    //Remove the last argument from the args becase it is the callback
    args.pop();

    return args;
}

//Extracts the public methods (and their arguments) of a context.
//context : Object
//      The object to be exposed
//return : Object of String => Array
//      A map where they key is the method name and the value are the method
//      arguments, not including the callback argument.
function publicMethods(context) {
    var methods = {};

    //Ignore members that start with an underscore or are not functions
    for(var name in context) {
        if(!/^_/.test(name) && typeof(context[name]) == 'function') {
            methods[name] = getArguments(context[name]);
        }
    }

    return methods;
}

// Creates a new server
// context : Object
//      The object to expose
function Server(context) {
    var self = this;

    self._socket = socket.server();
    util.eventProxy(self._socket, self, "error");

    self._context = context || {};
    self._methods = publicMethods(self._context);

    var newInspector = self._createIntrospector(self._context.constructor.name || "Object");

    self._context._zerorpc_inspect = newInspector;
    self._methods._zerorpc_inspect = getArguments(newInspector);

    self._socket.on("multiplexing-socket/receive", function(event) {
        if(event.name in self._methods) self._recv(event, self._context);
    });
}

nodeUtil.inherits(Server, events.EventEmitter);

Server.prototype.expose = function(name, method) {
    // use 'publicMethods' to use the same naming constraint
    var context = {};
    context[name] = method;
    var methods = publicMethods(context);
    // only extend this._methods when the method is 'public'
    if(name in methods) {
        this._methods[name] = methods[name];
    }
    // add to this._context anyway
    this._context[name] = method;
    return this;
};

Server.prototype.unexpose = function(name) {
    if(name in this._methods && name in this._context) {
        delete this._methods[name];
        delete this._context[name];
    }
    return this;
};

//Creates the new-school introspector
Server.prototype._createIntrospector = function(contextName) {
    var methods = {};
    var results = { name: contextName, methods: methods };

    for(var name in this._methods) {
        methods[name] = {
            doc: "",

            args: _.map(this._methods[name], function(arg) {
                return { name: arg };
            })
        };
    }

    return function(reply) {
        reply(null, results);
    };
}

//Called when a method call event is received
//event : Object
//      The ZeroRPC event
//context : Object
//      The object to expose.
Server.prototype._recv = function(event, context) {
    var self = this;
    var ch = self._socket.openChannel(event);
    var isFirst = true;

    //Sends an error
    var sendError = function(error) {
        var args = [error.type || "Error", error.message, error.stack];
        ch.send("ERR", args);
        ch.close();
    };

    //Listen for protocol errors - this should never happen
    ch.on("protocol-error", function(error) {
        self.emit("error", error);
    });

    //Listen for heartbeat errors
    ch.on("heartbeat-error", function(error) {
        self.emit("error", error);
    });

    //This is passed to RPC methods to call when they finish, or have a stream
    //update
    var result = function(error, item, more) {
        more = more || false;

        if(error) {
            //Create an error object if we were passed a string
            sendError(typeof(error) == 'string' ? new Error(error) : error);
        } else {
            if(isFirst && !more) {
                //Wrap item in an array for backwards compatibility issues
                //with ZeroRPC
                ch.send("OK", [item]);
            } else if(item !== undefined) {
                //Stream is a newer method that does not require item to be
                //wrapped in an array
                ch.send("STREAM", item);
            }

            if(!more) {
                if(!isFirst) ch.send("STREAM_DONE", []);
                ch.close();
            }
        }

        isFirst = false;
    };

    //The arguments should be an array
    if(!(event.args instanceof Array)) {
        self.emit("error", "Invalid event: Bad args");
        return ch.close();
    }

    //Call the method
    event.args.push(result);

    //Catch any errors and send them back to the client
    try {
        context[event.name].apply(context, event.args);
    } catch(e) {
        sendError(e);
    }
}

//Binds to a ZeroMQ endpoint
//endpoint : String
//      The ZeroMQ endpoint
Server.prototype.bind = function(endpoint) {
    this._socket.bind(endpoint);
};

//Connects to a ZeroMQ endpoint
//endpoint : String
//      The ZeroMQ endpoint
Server.prototype.connect = function(endpoint) {
    this._socket.connect(endpoint);
};

//Closes the server
Server.prototype.close = function() {
    this._socket.close();
};

exports.Server = Server;<|endoftext|>


<commit_before>(function () {
    var http = require('http');
    var serverClass = function (sys, api, utils) {
        this.sys = sys;
        this.api = api;
        this.utils = utils;
    };

    serverClass.prototype.runServer = function () {
        var api = this.api;
        var sys = this.sys;
        var utils = this.utils;
        var oops = this.utils.apiError;
        http.createServer(function (request, response) {
            if(request.method == "OPTIONS"){
                var nowdate = new Date();
                response.writeHead(200, {
                    'Date': nowdate.toUTCString(),
                    'Allow': 'GET,POST,OPTIONS',
                    'Content-Length': 0,
                    'Content-Type': 'text/plain',
                });
                response.end('');
                return;
            }

            var fs = require('fs');
            var qs = require('querystring');
            var url = require('url');
            var uriObj = url.parse(request.url);
            var params = qs.parse(uriObj.query);
            params.pathname = uriObj.pathname;

            if (sys.admin[params.admin]
                && params.cmd === 'upload') {

                var form = new sys.multiparty.Form();
                form.parse(request, function(err, fields, files) {
                    // Put it where you will.
                    var adminID = sys.admin[params.admin];

                    console.log("fields.pageDate="+fields.pageDate);
                    var pageDate = parseInt(fields.pageDate,10);

                    var attachmentTitle = fields.attachmentTitle[0];
                    console.log("attachmentTitle: "+attachmentTitle);
                    var docTxt = fs.readFileSync(files.attachmentDocument[0].path);
                    var mimeType = fields.mimeType[0];
                    var filenameExtension = fields.filenameExtension[0];
                    var dateFlag = !fields.searchableTitle;
                    console.log("dateFlag="+dateFlag+", searchableTitle="+fields.searchableTitle);

                    if (dateFlag) {
                        checkUniqueDocument();
                    } else {
                        checkSearchableDocument();
                    }

                    function checkSearchableDocument () {
                        console.log("checkSearchableDocument()");
                        var sql = 'SELECT documentID '
                            + 'FROM documents '
                            + 'JOIN titles USING(titleID) '
                            + 'WHERE title=? '
                            + 'AND uploadDate IS NULL;';
                        sys.db.get(sql,[attachmentTitle],function(err,row){
                            if (err) {return oops(response,err,'upload(1)')};
                            if (row && row.documentID) {
                                updateDocument(row.documentID);
                            } else {
                                checkTitle(attachmentTitle);
                            }
                        });
                    };

                    function checkUniqueDocument () {
                        console.log("checkUniqueDocument() with "+pageDate);
                        var sql = 'SELECT documentID '
                            + 'FROM documents '
                            + 'JOIN titles USING(titleID) '
                            + 'WHERE title=? '
                            + 'AND uploadDate=?;';
                        sys.db.get(sql,[attachmentTitle,pageDate],function(err,row){
                            if (err) {return oops(response,err,'upload(2)')};
                            if (row && row.documentID) {
                                updateDocument(row.documentID);
                            } else {
                                checkTitle(attachmentTitle);
                            }
                        });
                    };

                    function updateDocument(documentID) {
                        console.log("updateDocument();");
                        // Replace existing file with upload (filesystem operation)
                        fs.writeFileSync(sys.document_path + '/' + documentID,docTxt);
                        var sql = 'UPDATE documents SET mimeType=?,filenameExtension=? WHERE documentID=?;';
                        sys.db.run(sql,[mimeType,filenameExtension,documentID],function(err){
                            if (err) {return oops(response,err,'upload(3)')};
                            finishUpload(documentID);
                        });
                    };

                    function checkTitle (attachmentTitle) {
                        console.log("checkTitle();");
                        var sql = 'SELECT titleID FROM titles WHERE title=?;';
                        sys.db.get(sql,[attachmentTitle],function(err,row){
                            if (err) {return oops(response,err,'upload(4)')};
                            if (row && row.titleID) {
                                addDocument(row.titleID);
                            } else {
                                addTitle(attachmentTitle);
                            }
                        });
                    };

                    function addTitle(attachmentTitle) {
                        console.log("addTitle();");
                        var sql = 'INSERT INTO titles VALUES (NULL,?);';
                        sys.db.run(sql,[attachmentTitle],function(err){
                            if (err) {return oops(response,err,'upload(5)')};
                            addDocument(this.lastID);
                        });
                    };

                    function addDocument (titleID) {
                        console.log("addDocument();");
                        // Insert database row and get ID
                        var sql = 'INSERT INTO documents VALUES (NULL,?,?,?,?);';
                        sys.db.run(sql,[titleID,mimeType,filenameExtension,pageDate],function(err){
                            if (err) {return oops(response,err,'upload(6)')};
                            saveDocument(this.lastID)
                        });
                    };

                    function saveDocument (documentID) {
                        console.log("saveDocument();");
                        // Add a document under database ID
                        fs.writeFileSync(sys.document_path + '/' + documentID, docTxt);
                        finishUpload(documentID);
                    };

                    function finishUpload(documentID) {
                        console.log("finishUpload();");
                        // Send back documentID, so browser can update page
                        response.writeHead(200, {'Content-Type': 'application/json'});
                        response.end(JSON.stringify({documentID:documentID,documentTitle:attachmentTitle}));
                    }
                })
            } else {
                serveMe();
            }
            function serveMe() {
                request.on('data', function(data){
                    if(typeof this.POSTDATA === "undefined"){
                        this.POSTDATA = "";
                    }
                    this.POSTDATA += data;
                    if(this.POSTDATA.length > 1e6) {
                        this.POSTDATA = "";
                        response.writeHead(413, {'Content-Type': 'text/plain'}).end();
                        request.connection.destroy();
                    }
                });
                request.on('end', function(){
                    console.log("this.url="+this.url);

                    if (this.POSTDATA) {
                        var payload;
                        if (!request.headers['content-type'].match(/multipart\/form-data/)) {
                            payload = JSON.parse(this.POSTDATA);
                            for (var key in payload) {
                                params[key] = payload[key];
                            }
                        }
                    }

                    if (uriObj.href.match(/\/attachments\/[0-9]+$/)) {
                        var m = uriObj.href.match(/attachments\/([0-9]+)$/);
                        var documentID = m[1];
                        var docTxt = fs.readFileSync('./attachments/' + documentID);
                        // XXX Response is governed by a database lookup
                        var sql = 'SELECT title,filenameExtension FROM documents JOIN titles USING(titleID) WHERE documentID=?;';
                        sys.db.get(sql,[documentID],function(err,row){
                            if (err) {return oops(response,err,'download(1)')};
                            if (row && row.title) {
                                // Get mime type, title and file extension from the DB
                                response.writeHead(200, {'Content-Type': row.mimeType,
                                                         "Content-Disposition": "attachment; filename='" + row.title + row.filenameExtension + "'"
                                                        });
                                response.end(docTxt);
                            } else {
                                response.writeHead(404, {'Content-Type': 'text/plain'});
                                response.end('File not found in database');
                            }
                        });
                        return;
                    } else if ('.js' === uriObj.href.slice(-3)) {
                        var myxml = fs.readFileSync(__dirname + '/../' + uriObj.href.replace(/^.*?\/(js|node_modules)\/(.*)/, '$1/$2'));
                        response.writeHead(200, {'Content-Type': 'text/javascript'});
                        response.end(myxml);
                        return;
                    } else if ('.css' === uriObj.href.slice(-4)) {
                        console.log(__dirname + '/' + uriObj.href.replace(/^.*?\/(css\/.*)/, '$1'));
                        var myxml = fs.readFileSync(__dirname + '/../' + uriObj.href.replace(/^.*?\/(css\/.*)/, '$1'));
                        response.writeHead(200, {'Content-Type': 'text/css'});
                        response.end(myxml);
                        return;
                    } else {
                        api(params,request,response);
                    }
                });
            }
        }).listen(this.sys.real_port);
    }
    exports.serverClass = serverClass;
})();
<commit_msg>Set mimeType in server, add filename extension delimiter if necessary<commit_after>(function () {
    var http = require('http');
    var serverClass = function (sys, api, utils) {
        this.sys = sys;
        this.api = api;
        this.utils = utils;
    };

    serverClass.prototype.runServer = function () {
        var api = this.api;
        var sys = this.sys;
        var utils = this.utils;
        var oops = this.utils.apiError;
        http.createServer(function (request, response) {
            if(request.method == "OPTIONS"){
                var nowdate = new Date();
                response.writeHead(200, {
                    'Date': nowdate.toUTCString(),
                    'Allow': 'GET,POST,OPTIONS',
                    'Content-Length': 0,
                    'Content-Type': 'text/plain',
                });
                response.end('');
                return;
            }

            var fs = require('fs');
            var qs = require('querystring');
            var url = require('url');
            var uriObj = url.parse(request.url);
            var params = qs.parse(uriObj.query);
            params.pathname = uriObj.pathname;

            if (sys.admin[params.admin]
                && params.cmd === 'upload') {

                var form = new sys.multiparty.Form();
                form.parse(request, function(err, fields, files) {
                    // Put it where you will.
                    var adminID = sys.admin[params.admin];

                    console.log("fields.pageDate="+fields.pageDate);
                    var pageDate = null;
                    if (!fields.searchableTitle) {
                        pageDate = parseInt(fields.pageDate,10);
                    }
                    var attachmentTitle = fields.attachmentTitle[0];
                    console.log("attachmentTitle: "+attachmentTitle);
                    var docTxt = fs.readFileSync(files.attachmentDocument[0].path);
                    var mimeType = fields.mimeType[0];
                    var filenameExtension = fields.filenameExtension[0];
                    var dateFlag = !fields.searchableTitle;
                    console.log("dateFlag="+dateFlag+", searchableTitle="+fields.searchableTitle);

                    if (dateFlag) {
                        checkUniqueDocument();
                    } else {
                        checkSearchableDocument();
                    }

                    function checkSearchableDocument () {
                        console.log("checkSearchableDocument()");
                        var sql = 'SELECT documentID '
                            + 'FROM documents '
                            + 'JOIN titles USING(titleID) '
                            + 'WHERE title=? '
                            + 'AND uploadDate IS NULL;';
                        sys.db.get(sql,[attachmentTitle],function(err,row){
                            if (err) {return oops(response,err,'upload(1)')};
                            if (row && row.documentID) {
                                updateDocument(row.documentID);
                            } else {
                                checkTitle(attachmentTitle);
                            }
                        });
                    };

                    function checkUniqueDocument () {
                        console.log("checkUniqueDocument() with "+pageDate);
                        var sql = 'SELECT documentID '
                            + 'FROM documents '
                            + 'JOIN titles USING(titleID) '
                            + 'WHERE title=? '
                            + 'AND uploadDate=?;';
                        sys.db.get(sql,[attachmentTitle,pageDate],function(err,row){
                            if (err) {return oops(response,err,'upload(2)')};
                            if (row && row.documentID) {
                                updateDocument(row.documentID);
                            } else {
                                checkTitle(attachmentTitle);
                            }
                        });
                    };

                    function updateDocument(documentID) {
                        console.log("updateDocument();");
                        // Replace existing file with upload (filesystem operation)
                        fs.writeFileSync(sys.document_path + '/' + documentID,docTxt);
                        var sql = 'UPDATE documents SET mimeType=?,filenameExtension=? WHERE documentID=?;';
                        sys.db.run(sql,[mimeType,filenameExtension,documentID],function(err){
                            if (err) {return oops(response,err,'upload(3)')};
                            finishUpload(documentID);
                        });
                    };

                    function checkTitle (attachmentTitle) {
                        console.log("checkTitle();");
                        var sql = 'SELECT titleID FROM titles WHERE title=?;';
                        sys.db.get(sql,[attachmentTitle],function(err,row){
                            if (err) {return oops(response,err,'upload(4)')};
                            if (row && row.titleID) {
                                addDocument(row.titleID);
                            } else {
                                addTitle(attachmentTitle);
                            }
                        });
                    };

                    function addTitle(attachmentTitle) {
                        console.log("addTitle();");
                        var sql = 'INSERT INTO titles VALUES (NULL,?);';
                        sys.db.run(sql,[attachmentTitle],function(err){
                            if (err) {return oops(response,err,'upload(5)')};
                            addDocument(this.lastID);
                        });
                    };

                    function addDocument (titleID) {
                        console.log("addDocument();");
                        // Insert database row and get ID
                        var sql = 'INSERT INTO documents VALUES (NULL,?,?,?,?);';
                        sys.db.run(sql,[titleID,mimeType,filenameExtension,pageDate],function(err){
                            if (err) {return oops(response,err,'upload(6)')};
                            saveDocument(this.lastID)
                        });
                    };

                    function saveDocument (documentID) {
                        console.log("saveDocument();");
                        // Add a document under database ID
                        fs.writeFileSync(sys.document_path + '/' + documentID, docTxt);
                        finishUpload(documentID);
                    };

                    function finishUpload(documentID) {
                        console.log("finishUpload();");
                        // Send back documentID, so browser can update page
                        response.writeHead(200, {'Content-Type': 'application/json'});
                        response.end(JSON.stringify({documentID:documentID,documentTitle:attachmentTitle}));
                    }
                })
            } else {
                serveMe();
            }
            function serveMe() {
                request.on('data', function(data){
                    if(typeof this.POSTDATA === "undefined"){
                        this.POSTDATA = "";
                    }
                    this.POSTDATA += data;
                    if(this.POSTDATA.length > 1e6) {
                        this.POSTDATA = "";
                        response.writeHead(413, {'Content-Type': 'text/plain'}).end();
                        request.connection.destroy();
                    }
                });
                request.on('end', function(){
                    console.log("this.url="+this.url);

                    if (this.POSTDATA) {
                        var payload;
                        if (!request.headers['content-type'].match(/multipart\/form-data/)) {
                            payload = JSON.parse(this.POSTDATA);
                            for (var key in payload) {
                                params[key] = payload[key];
                            }
                        }
                    }

                    if (uriObj.href.match(/\/attachments\/[0-9]+$/)) {
                        var m = uriObj.href.match(/attachments\/([0-9]+)$/);
                        var documentID = m[1];
                        var docTxt = fs.readFileSync('./attachments/' + documentID);
                        // XXX Response is governed by a database lookup
                        var sql = 'SELECT title,filenameExtension,mimeType FROM documents JOIN titles USING(titleID) WHERE documentID=?;';
                        sys.db.get(sql,[documentID],function(err,row){
                            if (err) {return oops(response,err,'download(1)')};
                            if (row && row.title) {
                                // Get mime type, title and file extension from the DB
                                console.log("MIME TYPE: "+row.mimeType+" "+row.title);
                                var filenameExtension = row.filenameExtension;
                                if (filenameExtension && filenameExtension.slice(0,1) !== '.') {
                                    filenameExtension = '.' + filenameExtension;
                                }
                                response.writeHead(200, {'Content-Type': row.mimeType,
                                                         "Content-Disposition": "attachment; filename='" + row.title + filenameExtension + "'"
                                                        });
                                response.end(docTxt);
                            } else {
                                response.writeHead(404, {'Content-Type': 'text/plain'});
                                response.end('File not found in database');
                            }
                        });
                        return;
                    } else if ('.js' === uriObj.href.slice(-3)) {
                        var myxml = fs.readFileSync(__dirname + '/../' + uriObj.href.replace(/^.*?\/(js|node_modules)\/(.*)/, '$1/$2'));
                        response.writeHead(200, {'Content-Type': 'text/javascript'});
                        response.end(myxml);
                        return;
                    } else if ('.css' === uriObj.href.slice(-4)) {
                        console.log(__dirname + '/' + uriObj.href.replace(/^.*?\/(css\/.*)/, '$1'));
                        var myxml = fs.readFileSync(__dirname + '/../' + uriObj.href.replace(/^.*?\/(css\/.*)/, '$1'));
                        response.writeHead(200, {'Content-Type': 'text/css'});
                        response.end(myxml);
                        return;
                    } else {
                        api(params,request,response);
                    }
                });
            }
        }).listen(this.sys.real_port);
    }
    exports.serverClass = serverClass;
})();
<|endoftext|>