<commit_before>'use strict';

const fs = require('fs');
const path = require('path');
const hapi = require('@hapi/hapi');
const h2o2 = require('@hapi/h2o2');
const hapiPluginWebsocket = require('hapi-plugin-websocket');
const debugLog = require('./debugLog');
const createLambdaContext = require('./createLambdaContext');
const functionHelper = require('./functionHelper');
const { getUniqueId } = require('./utils');
const authFunctionNameExtractor = require('./authFunctionNameExtractor');
const wsHelpers = require('./websocketHelpers');

module.exports = class ApiGatewayWebSocket {
  constructor(serverless, options) {
    this.serverless = serverless;
    this.service = serverless.service;
    this.serverlessLog = serverless.cli.log.bind(serverless.cli);
    this.options = options;
    this.exitCode = 0;
    this.clients = new Map();
    this.actions = {};
    this.websocketsApiRouteSelectionExpression = serverless.service.provider.websocketsApiRouteSelectionExpression || '$request.body.action';
  }

  _createWebSocket() {
    // start COPY PASTE FROM HTTP SERVER CODE
    const serverOptions = {
      host: this.options.host,
      port: this.options.websocketPort,
      router: {
        stripTrailingSlash: !this.options.preserveTrailingSlash, // removes trailing slashes on incoming paths.
      },
    };

    const httpsDir = this.options.httpsProtocol;

    // HTTPS support
    if (typeof httpsDir === 'string' && httpsDir.length > 0) {
      serverOptions.tls = {
        key: fs.readFileSync(path.resolve(httpsDir, 'key.pem'), 'ascii'),
        cert: fs.readFileSync(path.resolve(httpsDir, 'cert.pem'), 'ascii'),
      };
    }

    serverOptions.state = this.options.enforceSecureCookies ? {
      isHttpOnly: true,
      isSameSite: false,
      isSecure: true,
    } : {
      isHttpOnly: false,
      isSameSite: false,
      isSecure: false,
    };

    // Hapijs server creation
    this.wsServer = hapi.server(serverOptions);

    this.wsServer.register(h2o2).catch(err => err && this.serverlessLog(err));

    // Enable CORS preflight response
    this.wsServer.ext('onPreResponse', (request, h) => {
      if (request.headers.origin) {
        const response = request.response.isBoom ? request.response.output : request.response;

        response.headers['access-control-allow-origin'] = request.headers.origin;
        response.headers['access-control-allow-credentials'] = 'true';

        if (request.method === 'options') {
          response.statusCode = 200;
          response.headers['access-control-expose-headers'] = 'content-type, content-length, etag';
          response.headers['access-control-max-age'] = 60 * 10;

          if (request.headers['access-control-request-headers']) {
            response.headers['access-control-allow-headers'] = request.headers['access-control-request-headers'];
          }

          if (request.headers['access-control-request-method']) {
            response.headers['access-control-allow-methods'] = request.headers['access-control-request-method'];
          }
        }
      }

      return h.continue;
    });
    // end COPY PASTE FROM HTTP SERVER CODE

    this.wsServer.register(hapiPluginWebsocket).catch(err => err && this.serverlessLog(err));

    const doAction = (ws, connectionId, name, event, doDefaultAction) => {
      const sendError = err => {
        if (ws.readyState === /* OPEN */1) {
          ws.send(JSON.stringify({ message:'Internal server error', connectionId, requestId:'1234567890' }));
        }

        debugLog(`Error in handler of action ${action}`, err);
      };

      let action = this.actions[name];

      if (!action && doDefaultAction) action = this.actions.$default;
      if (!action) return;

      function cb(err) {
        if (!err) return;
        sendError(err);
      }

      // TEMP
      const func = {
        ...action.fun,
        name,
      };
      const context = createLambdaContext(func, this.service.provider, cb);

      let p = null;

      try {
        p = action.handler(event, context, cb);
      }
      catch (err) {
        sendError(err);
      }

      if (p) {
        p.catch(err => {
          sendError(err);
        });
      }
    };

    this.wsServer.route({
      method: 'POST',
      path: '/',
      config: {
        payload: {
          output: 'data',
          parse: true,
          allow: 'application/json',
        },
        plugins: {
          websocket: {
            only: true,
            initially: false,
            connect: ({ ws, req }) => {
              const queryStringParameters = parseQuery(req.url);
              const connection = { connectionId:getUniqueId(), connectionTime:Date.now() };

              debugLog(`connect:${connection.connectionId}`);

              this.clients.set(ws, connection);

              let event = wsHelpers.createConnectEvent('$connect', 'CONNECT', connection, this.options);

              if (Object.keys(queryStringParameters).length > 0) {
                event = { queryStringParameters, ...event };
              }

              doAction(ws, connection.connectionId, '$connect', event, false);
            },
            disconnect: ({ ws }) => {
              const connection = this.clients.get(ws);

              debugLog(`disconnect:${connection.connectionId}`);

              this.clients.delete(ws);

              const event = wsHelpers.createDisconnectEvent('$disconnect', 'DISCONNECT', connection, this.options);

              doAction(ws, connection.connectionId, '$disconnect', event, false);
            },
          },
        },
      },
      handler: (request, h) => {
        const { initially, ws } = request.websocket();

        if (!request.payload || initially) {
          return h.response().code(204);
        }

        const connection = this.clients.get(ws);
        let actionName = null;

        if (this.websocketsApiRouteSelectionExpression.startsWith('$request.body.')) {
          actionName = request.payload;

          if (typeof actionName === 'object') {
            this.websocketsApiRouteSelectionExpression
            .replace('$request.body.', '')
            .split('.')
            .forEach(key => {
              if (actionName) {
                actionName = actionName[key];
              }
            });
          }
          else actionName = null;
        }

        if (typeof actionName !== 'string') {
          actionName = null;
        }

        const action = actionName || '$default';

        debugLog(`action:${action} on connection=${connection.connectionId}`);

        const event = wsHelpers.createEvent(action, 'MESSAGE', connection, request.payload, this.options);

        doAction(ws, connection.connectionId, action, event, true);

        return h.response().code(204);
      },
    });

    this.wsServer.route({
      method: 'GET',
      path: '/{path*}',
      handler: (request, h) => h.response().code(426),
    });

    this.wsServer.route({
      method: 'POST',
      path: '/@connections/{connectionId}',
      config: { payload: { parse: false } },
      handler: (request, h) => {
        debugLog(`got POST to ${request.url}`);

        const getByConnectionId = (map, searchValue) => {
          for (const [key, connection] of map.entries()) {
            if (connection.connectionId === searchValue) return key;
          }

          return undefined;
        };

        const ws = getByConnectionId(this.clients, request.params.connectionId);

        if (!ws) return h.response().code(410);
        if (!request.payload) return '';

        ws.send(request.payload.toString());

        debugLog(`sent data to connection:${request.params.connectionId}`);

        return '';
      },
    });

    this.wsServer.route({
      method: 'DELETE',
      path: '/@connections/{connectionId}',
      config: { payload: { parse: false } },
      handler: (request, h) => {
        debugLog(`got DELETE to ${request.url}`);

        const getByConnectionId = (map, searchValue) => {
          for (const [key, connection] of map.entries()) {
            if (connection.connectionId === searchValue) return key;
          }

          return undefined;
        };

        const ws = getByConnectionId(this.clients, request.params.connectionId);

        if (!ws) return h.response().code(410);

        ws.close();

        debugLog(`closed connection:${request.params.connectionId}`);

        return '';
      },
    });
  }

  _createWsAction(fun, funName, servicePath, funOptions, event) {
    let handler; // The lambda function
    Object.assign(process.env, this.originalEnvironment);

    try {
      if (this.options.noEnvironment) {
        // This evict errors in server when we use aws services like ssm
        const baseEnvironment = {
          AWS_REGION: 'dev',
        };

        if (!process.env.AWS_PROFILE) {
          baseEnvironment.AWS_ACCESS_KEY_ID = 'dev';
          baseEnvironment.AWS_SECRET_ACCESS_KEY = 'dev';
        }

        process.env = Object.assign(baseEnvironment, process.env);
      }
      else {
        Object.assign(
          process.env,
          { AWS_REGION: this.service.provider.region },
          this.service.provider.environment,
          this.service.functions[funName].environment
        );
      }

      process.env._HANDLER = fun.handler;
      handler = functionHelper.createHandler(funOptions, this.options);
    }
    catch (error) {
      return this.serverlessLog(`Error while loading ${funName}`, error);
    }

    const actionName = event.websocket.route;
    const action = { funName, fun, funOptions, servicePath, handler };

    this.actions[actionName] = action;
    this.serverlessLog(`Action '${event.websocket.route}'`);
  }

  _extractAuthFunctionName(endpoint) {
    const result = authFunctionNameExtractor(endpoint, this.serverlessLog);

    return result.unsupportedAuth ? null : result.authorizerName;
  }

  // All done, we can listen to incomming requests
  async _listen() {
    try {
      await this.wsServer.start();
    }
    catch (error) {
      console.error(`Unexpected error while starting serverless-offline websocket server on port ${this.options.websocketPort}:`, error);
      process.exit(1);
    }

    this.printBlankLine();
    this.serverlessLog(`Offline [websocket] listening on ws${this.options.httpsProtocol ? 's' : ''}://${this.options.host}:${this.options.websocketPort}`);
  }
};

function parseQuery(queryString) {
  const query = {};
  const parts = queryString.split('?');

  if (parts.length < 2) return {};

  const pairs = parts[1].split('&');

  pairs.forEach(pair => {
    const kv = pair.split('=');
    query[decodeURIComponent(kv[0])] = decodeURIComponent(kv[1] || '');
  });

  return query;
}
<commit_msg>fix: add missing printBlankLine method<commit_after>'use strict';

const fs = require('fs');
const path = require('path');
const hapi = require('@hapi/hapi');
const h2o2 = require('@hapi/h2o2');
const hapiPluginWebsocket = require('hapi-plugin-websocket');
const debugLog = require('./debugLog');
const createLambdaContext = require('./createLambdaContext');
const functionHelper = require('./functionHelper');
const { getUniqueId } = require('./utils');
const authFunctionNameExtractor = require('./authFunctionNameExtractor');
const wsHelpers = require('./websocketHelpers');

module.exports = class ApiGatewayWebSocket {
  constructor(serverless, options) {
    this.serverless = serverless;
    this.service = serverless.service;
    this.serverlessLog = serverless.cli.log.bind(serverless.cli);
    this.options = options;
    this.exitCode = 0;
    this.clients = new Map();
    this.actions = {};
    this.websocketsApiRouteSelectionExpression = serverless.service.provider.websocketsApiRouteSelectionExpression || '$request.body.action';
  }

  printBlankLine() {
    console.log();
  }

  _createWebSocket() {
    // start COPY PASTE FROM HTTP SERVER CODE
    const serverOptions = {
      host: this.options.host,
      port: this.options.websocketPort,
      router: {
        stripTrailingSlash: !this.options.preserveTrailingSlash, // removes trailing slashes on incoming paths.
      },
    };

    const httpsDir = this.options.httpsProtocol;

    // HTTPS support
    if (typeof httpsDir === 'string' && httpsDir.length > 0) {
      serverOptions.tls = {
        key: fs.readFileSync(path.resolve(httpsDir, 'key.pem'), 'ascii'),
        cert: fs.readFileSync(path.resolve(httpsDir, 'cert.pem'), 'ascii'),
      };
    }

    serverOptions.state = this.options.enforceSecureCookies ? {
      isHttpOnly: true,
      isSameSite: false,
      isSecure: true,
    } : {
      isHttpOnly: false,
      isSameSite: false,
      isSecure: false,
    };

    // Hapijs server creation
    this.wsServer = hapi.server(serverOptions);

    this.wsServer.register(h2o2).catch(err => err && this.serverlessLog(err));

    // Enable CORS preflight response
    this.wsServer.ext('onPreResponse', (request, h) => {
      if (request.headers.origin) {
        const response = request.response.isBoom ? request.response.output : request.response;

        response.headers['access-control-allow-origin'] = request.headers.origin;
        response.headers['access-control-allow-credentials'] = 'true';

        if (request.method === 'options') {
          response.statusCode = 200;
          response.headers['access-control-expose-headers'] = 'content-type, content-length, etag';
          response.headers['access-control-max-age'] = 60 * 10;

          if (request.headers['access-control-request-headers']) {
            response.headers['access-control-allow-headers'] = request.headers['access-control-request-headers'];
          }

          if (request.headers['access-control-request-method']) {
            response.headers['access-control-allow-methods'] = request.headers['access-control-request-method'];
          }
        }
      }

      return h.continue;
    });
    // end COPY PASTE FROM HTTP SERVER CODE

    this.wsServer.register(hapiPluginWebsocket).catch(err => err && this.serverlessLog(err));

    const doAction = (ws, connectionId, name, event, doDefaultAction) => {
      const sendError = err => {
        if (ws.readyState === /* OPEN */1) {
          ws.send(JSON.stringify({ message:'Internal server error', connectionId, requestId:'1234567890' }));
        }

        debugLog(`Error in handler of action ${action}`, err);
      };

      let action = this.actions[name];

      if (!action && doDefaultAction) action = this.actions.$default;
      if (!action) return;

      function cb(err) {
        if (!err) return;
        sendError(err);
      }

      // TEMP
      const func = {
        ...action.fun,
        name,
      };
      const context = createLambdaContext(func, this.service.provider, cb);

      let p = null;

      try {
        p = action.handler(event, context, cb);
      }
      catch (err) {
        sendError(err);
      }

      if (p) {
        p.catch(err => {
          sendError(err);
        });
      }
    };

    this.wsServer.route({
      method: 'POST',
      path: '/',
      config: {
        payload: {
          output: 'data',
          parse: true,
          allow: 'application/json',
        },
        plugins: {
          websocket: {
            only: true,
            initially: false,
            connect: ({ ws, req }) => {
              const queryStringParameters = parseQuery(req.url);
              const connection = { connectionId:getUniqueId(), connectionTime:Date.now() };

              debugLog(`connect:${connection.connectionId}`);

              this.clients.set(ws, connection);

              let event = wsHelpers.createConnectEvent('$connect', 'CONNECT', connection, this.options);

              if (Object.keys(queryStringParameters).length > 0) {
                event = { queryStringParameters, ...event };
              }

              doAction(ws, connection.connectionId, '$connect', event, false);
            },
            disconnect: ({ ws }) => {
              const connection = this.clients.get(ws);

              debugLog(`disconnect:${connection.connectionId}`);

              this.clients.delete(ws);

              const event = wsHelpers.createDisconnectEvent('$disconnect', 'DISCONNECT', connection, this.options);

              doAction(ws, connection.connectionId, '$disconnect', event, false);
            },
          },
        },
      },
      handler: (request, h) => {
        const { initially, ws } = request.websocket();

        if (!request.payload || initially) {
          return h.response().code(204);
        }

        const connection = this.clients.get(ws);
        let actionName = null;

        if (this.websocketsApiRouteSelectionExpression.startsWith('$request.body.')) {
          actionName = request.payload;

          if (typeof actionName === 'object') {
            this.websocketsApiRouteSelectionExpression
            .replace('$request.body.', '')
            .split('.')
            .forEach(key => {
              if (actionName) {
                actionName = actionName[key];
              }
            });
          }
          else actionName = null;
        }

        if (typeof actionName !== 'string') {
          actionName = null;
        }

        const action = actionName || '$default';

        debugLog(`action:${action} on connection=${connection.connectionId}`);

        const event = wsHelpers.createEvent(action, 'MESSAGE', connection, request.payload, this.options);

        doAction(ws, connection.connectionId, action, event, true);

        return h.response().code(204);
      },
    });

    this.wsServer.route({
      method: 'GET',
      path: '/{path*}',
      handler: (request, h) => h.response().code(426),
    });

    this.wsServer.route({
      method: 'POST',
      path: '/@connections/{connectionId}',
      config: { payload: { parse: false } },
      handler: (request, h) => {
        debugLog(`got POST to ${request.url}`);

        const getByConnectionId = (map, searchValue) => {
          for (const [key, connection] of map.entries()) {
            if (connection.connectionId === searchValue) return key;
          }

          return undefined;
        };

        const ws = getByConnectionId(this.clients, request.params.connectionId);

        if (!ws) return h.response().code(410);
        if (!request.payload) return '';

        ws.send(request.payload.toString());

        debugLog(`sent data to connection:${request.params.connectionId}`);

        return '';
      },
    });

    this.wsServer.route({
      method: 'DELETE',
      path: '/@connections/{connectionId}',
      config: { payload: { parse: false } },
      handler: (request, h) => {
        debugLog(`got DELETE to ${request.url}`);

        const getByConnectionId = (map, searchValue) => {
          for (const [key, connection] of map.entries()) {
            if (connection.connectionId === searchValue) return key;
          }

          return undefined;
        };

        const ws = getByConnectionId(this.clients, request.params.connectionId);

        if (!ws) return h.response().code(410);

        ws.close();

        debugLog(`closed connection:${request.params.connectionId}`);

        return '';
      },
    });
  }

  _createWsAction(fun, funName, servicePath, funOptions, event) {
    let handler; // The lambda function
    Object.assign(process.env, this.originalEnvironment);

    try {
      if (this.options.noEnvironment) {
        // This evict errors in server when we use aws services like ssm
        const baseEnvironment = {
          AWS_REGION: 'dev',
        };

        if (!process.env.AWS_PROFILE) {
          baseEnvironment.AWS_ACCESS_KEY_ID = 'dev';
          baseEnvironment.AWS_SECRET_ACCESS_KEY = 'dev';
        }

        process.env = Object.assign(baseEnvironment, process.env);
      }
      else {
        Object.assign(
          process.env,
          { AWS_REGION: this.service.provider.region },
          this.service.provider.environment,
          this.service.functions[funName].environment
        );
      }

      process.env._HANDLER = fun.handler;
      handler = functionHelper.createHandler(funOptions, this.options);
    }
    catch (error) {
      return this.serverlessLog(`Error while loading ${funName}`, error);
    }

    const actionName = event.websocket.route;
    const action = { funName, fun, funOptions, servicePath, handler };

    this.actions[actionName] = action;
    this.serverlessLog(`Action '${event.websocket.route}'`);
  }

  _extractAuthFunctionName(endpoint) {
    const result = authFunctionNameExtractor(endpoint, this.serverlessLog);

    return result.unsupportedAuth ? null : result.authorizerName;
  }

  // All done, we can listen to incomming requests
  async _listen() {
    try {
      await this.wsServer.start();
    }
    catch (error) {
      console.error(`Unexpected error while starting serverless-offline websocket server on port ${this.options.websocketPort}:`, error);
      process.exit(1);
    }

    this.printBlankLine();
    this.serverlessLog(`Offline [websocket] listening on ws${this.options.httpsProtocol ? 's' : ''}://${this.options.host}:${this.options.websocketPort}`);
  }
};

function parseQuery(queryString) {
  const query = {};
  const parts = queryString.split('?');

  if (parts.length < 2) return {};

  const pairs = parts[1].split('&');

  pairs.forEach(pair => {
    const kv = pair.split('=');
    query[decodeURIComponent(kv[0])] = decodeURIComponent(kv[1] || '');
  });

  return query;
}
<|endoftext|>


<commit_before>// Expanding Textareas v0.2.0
// MIT License
// https://github.com/bgrins/ExpandingTextareas

(function(factory) {
  // Add jQuery via AMD registration or browser globals
  if (typeof define === 'function' && define.amd) {
    define([ 'jquery' ], factory);
  }
  else {
    factory(jQuery);
  }
}(function ($) {

  // Class Definition
  // ================

  var Expanding = function($textarea, opts) {
    this.$textarea = $textarea;
    this.$textCopy = $("");
    this.$clone = $("

").prepend(this.$textCopy);

    $textarea
      .wrap($("
"))
      .after(this.$clone);

    this.attach();
    this.setStyles();
    this.update();
    if (opts.update) $textarea.bind("update.expanding", opts.update);
  };

  Expanding.DEFAULTS = {
    autoInitialize: true,
    initialSelector: "textarea.expanding",
    opts: {
      update: function() { }
    }
  };

  // Returns the version of Internet Explorer or -1
  // (indicating the use of another browser).
  // From: http://msdn.microsoft.com/en-us/library/ms537509(v=vs.85).aspx#ParsingUA
  var ieVersion = (function() {
    var v = -1;
    if (navigator.appName === "Microsoft Internet Explorer") {
      var ua = navigator.userAgent;
      var re = new RegExp("MSIE ([0-9]{1,}[\\.0-9]{0,})");
      if (re.exec(ua) !== null) v = parseFloat(RegExp.$1);
    }
    return v;
  })();

  // Check for oninput support
  // IE9 supports oninput, but not when deleting text, so keyup is used.
  // onpropertychange _is_ supported by IE8/9, but may not be fired unless
  // attached with `attachEvent`
  // (see: http://stackoverflow.com/questions/18436424/ie-onpropertychange-event-doesnt-fire),
  // and so is avoided altogether.
  var inputSupported = "oninput" in document.createElement("input") && ieVersion !== 9;

  Expanding.prototype = {

    // Attaches input events
    // Only attaches `keyup` events if `input` is not fully suported
    attach: function() {
      var events = 'input.expanding change.expanding',
        _this = this;
      if(!inputSupported) events += ' keyup.expanding';
      this.$textarea.bind(events, function() { _this.update(); });
    },

    // Updates the clone with the textarea value
    update: function() {
      this.$textCopy.text(this.$textarea.val().replace(/\r\n/g, "\n"));

      // Use `triggerHandler` to prevent conflicts with `update` in Prototype.js
      this.$textarea.triggerHandler("update.expanding");
    },

    // Tears down the plugin: removes generated elements, applies styles
    // that were prevously present, removes instance from data, unbinds events
    destroy: function() {
      this.$clone.remove();
      this.$textarea
        .unwrap()
        .attr('style', this._oldTextareaStyles || '')
        .removeData('expanding')
        .unbind('input.expanding change.expanding keyup.expanding update.expanding');

      delete this._oldTextareaStyles;
    },

    setStyles: function() {
      this._resetStyles();
      this._setCloneStyles();
      this._setTextareaStyles();
    },

    // Applies reset styles to the textarea and clone
    // Stores the original textarea styles in case of destroying
    _resetStyles: function() {
      this._oldTextareaStyles = this.$textarea.attr('style');

      this.$textarea.add(this.$clone).css({
        margin: 0,
        webkitBoxSizing: "border-box",
        mozBoxSizing: "border-box",
        boxSizing: "border-box",
        width: "100%"
      });
    },

    // Sets the basic clone styles and copies styles over from the textarea
    _setCloneStyles: function() {
      var css = {
        display: 'block',
        border: '0 solid',
        visibility: 'hidden',
        minHeight: this.$textarea.outerHeight()
      };

      if(this.$textarea.attr("wrap") === "off") css.overflowX = "scroll";
      else css.whiteSpace = "pre-wrap";

      this.$clone.css(css);
      this._copyTextareaStylesToClone();
    },

    _copyTextareaStylesToClone: function() {
      var _this = this,
        properties = [
          'lineHeight', 'textDecoration', 'letterSpacing',
          'fontSize', 'fontFamily', 'fontStyle',
          'fontWeight', 'textTransform', 'textAlign',
          'direction', 'wordSpacing', 'fontSizeAdjust',
          'wordWrap', 'word-break',
          'borderLeftWidth', 'borderRightWidth',
          'borderTopWidth','borderBottomWidth',
          'paddingLeft', 'paddingRight',
          'paddingTop','paddingBottom', 'maxHeight'];

      $.each(properties, function(i, property) {
        var val = _this.$textarea.css(property);

        // Prevent overriding percentage css values.
        if(_this.$clone.css(property) !== val) {
          _this.$clone.css(property, val);
          if(property === 'maxHeight' && val !== 'none') {
            _this.$clone.css('overflow', 'hidden');
          }
        }
      });
    },

    _setTextareaStyles: function() {
      this.$textarea.css({
        position: "absolute",
        top: 0,
        left: 0,
        height: "100%",
        resize: "none",
        overflow: "auto"
      });
    }
  };

  // Plugin Definition
  // =================

  function Plugin(option) {
    if (option === 'active') { return !!this.data('expanding'); }

    this.filter('textarea').each(function () {
      var $this = $(this);

      var instance = $this.data('expanding');

      if (instance && option === 'destroy') {
        return instance.destroy();
      }

      if (instance && option === 'refresh') {
        return instance.setStyles();
      }

      var visible = this.offsetWidth > 0 || this.offsetHeight > 0;

      if(!visible) _warn('ExpandingTextareas: attempt to initialize an invisible textarea. ' +
                         'Call expanding() again once it has been inserted into the page and/or is visible.');

      if (!instance && visible) {
        var options = $.extend({}, Expanding.DEFAULTS, $.expanding, typeof option === 'object' && option);
        $this.data('expanding', new Expanding($this, options));
      }
    });
    return this;
  }

  $.expanding = $.expanding || {};
  $.fn.expanding = Plugin;
  $.fn.expanding.Constructor = Expanding;

  function _warn(text) {
    if(window.console && console.warn) console.warn(text);
  }

  $(function () {
    if ($.expanding.autoInitialize) {
      $($.expanding.initialSelector).expanding();
    }
  });

}));<commit_msg>Remove update callback from initial options<commit_after>// Expanding Textareas v0.2.0
// MIT License
// https://github.com/bgrins/ExpandingTextareas

(function(factory) {
  // Add jQuery via AMD registration or browser globals
  if (typeof define === 'function' && define.amd) {
    define([ 'jquery' ], factory);
  }
  else {
    factory(jQuery);
  }
}(function ($) {

  // Class Definition
  // ================

  var Expanding = function($textarea, options) {
    this.$textarea = $textarea;
    this.$textCopy = $("");
    this.$clone = $("

").prepend(this.$textCopy);

    $textarea
      .wrap($("
"))
      .after(this.$clone);

    this.attach();
    this.setStyles();
    this.update();
    if (options.update) $textarea.bind("update.expanding", options.update);
  };

  Expanding.DEFAULTS = {
    autoInitialize: true,
    initialSelector: "textarea.expanding"
  };

  // Returns the version of Internet Explorer or -1
  // (indicating the use of another browser).
  // From: http://msdn.microsoft.com/en-us/library/ms537509(v=vs.85).aspx#ParsingUA
  var ieVersion = (function() {
    var v = -1;
    if (navigator.appName === "Microsoft Internet Explorer") {
      var ua = navigator.userAgent;
      var re = new RegExp("MSIE ([0-9]{1,}[\\.0-9]{0,})");
      if (re.exec(ua) !== null) v = parseFloat(RegExp.$1);
    }
    return v;
  })();

  // Check for oninput support
  // IE9 supports oninput, but not when deleting text, so keyup is used.
  // onpropertychange _is_ supported by IE8/9, but may not be fired unless
  // attached with `attachEvent`
  // (see: http://stackoverflow.com/questions/18436424/ie-onpropertychange-event-doesnt-fire),
  // and so is avoided altogether.
  var inputSupported = "oninput" in document.createElement("input") && ieVersion !== 9;

  Expanding.prototype = {

    // Attaches input events
    // Only attaches `keyup` events if `input` is not fully suported
    attach: function() {
      var events = 'input.expanding change.expanding',
        _this = this;
      if(!inputSupported) events += ' keyup.expanding';
      this.$textarea.bind(events, function() { _this.update(); });
    },

    // Updates the clone with the textarea value
    update: function() {
      this.$textCopy.text(this.$textarea.val().replace(/\r\n/g, "\n"));

      // Use `triggerHandler` to prevent conflicts with `update` in Prototype.js
      this.$textarea.triggerHandler("update.expanding");
    },

    // Tears down the plugin: removes generated elements, applies styles
    // that were prevously present, removes instance from data, unbinds events
    destroy: function() {
      this.$clone.remove();
      this.$textarea
        .unwrap()
        .attr('style', this._oldTextareaStyles || '')
        .removeData('expanding')
        .unbind('input.expanding change.expanding keyup.expanding update.expanding');

      delete this._oldTextareaStyles;
    },

    setStyles: function() {
      this._resetStyles();
      this._setCloneStyles();
      this._setTextareaStyles();
    },

    // Applies reset styles to the textarea and clone
    // Stores the original textarea styles in case of destroying
    _resetStyles: function() {
      this._oldTextareaStyles = this.$textarea.attr('style');

      this.$textarea.add(this.$clone).css({
        margin: 0,
        webkitBoxSizing: "border-box",
        mozBoxSizing: "border-box",
        boxSizing: "border-box",
        width: "100%"
      });
    },

    // Sets the basic clone styles and copies styles over from the textarea
    _setCloneStyles: function() {
      var css = {
        display: 'block',
        border: '0 solid',
        visibility: 'hidden',
        minHeight: this.$textarea.outerHeight()
      };

      if(this.$textarea.attr("wrap") === "off") css.overflowX = "scroll";
      else css.whiteSpace = "pre-wrap";

      this.$clone.css(css);
      this._copyTextareaStylesToClone();
    },

    _copyTextareaStylesToClone: function() {
      var _this = this,
        properties = [
          'lineHeight', 'textDecoration', 'letterSpacing',
          'fontSize', 'fontFamily', 'fontStyle',
          'fontWeight', 'textTransform', 'textAlign',
          'direction', 'wordSpacing', 'fontSizeAdjust',
          'wordWrap', 'word-break',
          'borderLeftWidth', 'borderRightWidth',
          'borderTopWidth','borderBottomWidth',
          'paddingLeft', 'paddingRight',
          'paddingTop','paddingBottom', 'maxHeight'];

      $.each(properties, function(i, property) {
        var val = _this.$textarea.css(property);

        // Prevent overriding percentage css values.
        if(_this.$clone.css(property) !== val) {
          _this.$clone.css(property, val);
          if(property === 'maxHeight' && val !== 'none') {
            _this.$clone.css('overflow', 'hidden');
          }
        }
      });
    },

    _setTextareaStyles: function() {
      this.$textarea.css({
        position: "absolute",
        top: 0,
        left: 0,
        height: "100%",
        resize: "none",
        overflow: "auto"
      });
    }
  };

  // Plugin Definition
  // =================

  function Plugin(option) {
    if (option === 'active') { return !!this.data('expanding'); }

    this.filter('textarea').each(function () {
      var $this = $(this);

      var instance = $this.data('expanding');

      if (instance && option === 'destroy') {
        return instance.destroy();
      }

      if (instance && option === 'refresh') {
        return instance.setStyles();
      }

      var visible = this.offsetWidth > 0 || this.offsetHeight > 0;

      if(!visible) _warn('ExpandingTextareas: attempt to initialize an invisible textarea. ' +
                         'Call expanding() again once it has been inserted into the page and/or is visible.');

      if (!instance && visible) {
        var options = $.extend({}, Expanding.DEFAULTS, $.expanding, typeof option === 'object' && option);
        $this.data('expanding', new Expanding($this, options));
      }
    });
    return this;
  }

  $.expanding = $.expanding || {};
  $.fn.expanding = Plugin;
  $.fn.expanding.Constructor = Expanding;

  function _warn(text) {
    if(window.console && console.warn) console.warn(text);
  }

  $(function () {
    if ($.expanding.autoInitialize) {
      $($.expanding.initialSelector).expanding();
    }
  });

}));<|endoftext|>


<commit_before>M.wrap('github/jillix/navigation/v0.0.1/navigation.js', function (require, module, exports) {

var View = require('github/jillix/view/v0.0.1/view');

// emit dom states
function domStateHandler (self, view, state) {

    // compute the new state
    state = self.base + state.replace(/^\//, '');

    return function () {
        // save current active dom elm
        self.active = this;

        // emit the new state
        view.state.emit(state);
    };
}

function activate (state) {
    var self = this;

    // set active when state is emitted from popstate
    if (self.pattern && state.popstate) {
        var active = state.url === self.base ? '/' : getActiveNameFromUrl(self.pattern);
        if (self.items[active]) {
            self.active = self.items[active];
        }
    }

    // remove the active class
    $('.active', self.layout.template.dom).removeClass('active');

    // add the active class to the active navigation tab
    if (self.active) {
        $(self.active).addClass('active');
    }
}

function loadModel (config, item, callback) {

    switch (typeof config.model) {

        // model by name
        case 'string':
            item.model(config.model, function (err, model) {
                model.read({q: config.query || {}}, function (err, data) {
                    callback(err, data);
                });
            });
            return;

        // model data
        case 'object':
            callback(null, config.model);
            return;

        default:
            callback('Invalid navigation model');
            return;
    }
}

function getActiveNameFromUrl (pattern) {
    var active;
    var path = location.pathname;
    if (path === self.base) {
        active = '/';
    } else {
        active = path.match(new RegExp(pattern));
        active = active ? active[1] : null;
    }

    return active;
}

function init () {
    var self = this;
    var config = self.mono.config.data;
    var V = View(self);

    if (!config) {
        return console.error('[nav: no config available]');
    }

    // state handler
    self.activate = activate;

    // set base
    config.baseLength = config.baseLength || 0;
    if (config.baseLength < 0) {
        config.baseLength = 0;
    }
    self.base = location.pathname.split('/').slice(0, config.baseLength + 1).join('/') + '/';

    // get current active
    var active = null;
    if (config.pattern) {
        self.pattern = config.pattern;
        active = getActiveNameFromUrl(config.pattern);
    }

    // parse query
    if (config.query) {
        config.query = JSON.parse(config.query);
    }

    // init layout view
    V.load(config.layout, function (err, layout) {

        if (err) {
            return console.error('[nav: ' + err.toString() + ']');
        }

        // return when no dom is available
        if (!layout.template && layout.template.dom) {
            return console.error('[nav: no dom available]');
        }

        self.layout = layout;

        // render html
        layout.template.render();

        // init item view
        V.load(config.item, function (err, item) {

            if (err) {
                return console.error('[nav: ' + err.toString() + ']');
            }

            // return when no dom is available
            if (!item.template && item.template.dom) {
                return console.error('[nav item: no dom available]');
            }

            loadModel(config, item, function(err, data) {

                if (err) {
                    return console.error('[nav: ' + err.toString() + ']');
                }

                self.item = item;

                // check if stateKey exists
                if (!data[0] || typeof data[0][config.stateKey] === 'undefined') {
                    return;
                }

                // render items
                item.template.render(data);

                // add change state handler to nav items
                var i = 0;
                var items = $('li', item.template.dom);
                self.items = {};

                for (i = 0; i < items.length; ++i) {
                    var $item = $(items[i]);

                    // save state item
                    self.items[data[i][config.stateKey]] = $item;

                    // save active item if found in the url
                    if (data[i][config.stateKey] === active) {
                        self.active = $item;
                    }

                    $item.on('click', domStateHandler(self, item, data[i][config.stateKey]));
                }

                // emit state events on dom elms
                if (config.domStates) {
                    for (i = 0; i < config.domStates.length; ++i) {
                        // get element
                        var elm = $(config.domStates[i].selector, layout.template.dom);
                        if (elm) {

                            // save state items
                            self.items[config.domStates[i].state] = elm;

                            // save active item if found in the url
                            if (config.domStates[i].state === active){
                                self.active = elm;
                            }

                            elm.on(config.domStates[i].event, domStateHandler(self, item, config.domStates[i].state));
                        }
                    }
                }

                item.state.emit();
            });
        });
    });
}

module.exports = init;

return module; });
<commit_msg>emit events or states on dom events<commit_after>M.wrap('github/jillix/navigation/v0.0.1/navigation.js', function (require, module, exports) {

var View = require('github/jillix/view/v0.0.1/view');

// emit dom states
function domStateHandler (self, view, config) {

    var state;

    // set default setActive value to true
    if (config.setActive === undefined) {
        config.setActive = true;
    }

    // compute the new state
    if (config[self.config.stateKey]) {
        state = self.base + config[self.config.stateKey].replace(/^\//, '');
    }

    return function () {

        // save current active dom elm
        if (config.setActive) {
            self.active = this;
        }

        // emit the new state
        if (state) {
            view.state.emit(state);
        }

        // emit events
        if (config.emit) {
            for (var i = 0; i < config.emit.length; ++i) {
                self.push(config.emit[i].inst || self.mono.name, config.emit[i].name, config.emit[i].args || []);
            }
        }
    };
}

function activate (state) {
    var self = this;

    // set active when state is emitted from popstate
    if (self.pattern && state.popstate) {
        var active = state.url === self.base ? '/' : getActiveNameFromUrl(self.pattern);
        if (self.items[active]) {
            self.active = self.items[active];
        }
    }

    // remove the active class
    $('.active', self.layout.template.dom).removeClass('active');

    // add the active class to the active navigation tab
    if (self.active) {
        $(self.active).addClass('active');
    }
}

function loadModel (config, item, callback) {

    switch (typeof config.model) {

        // model by name
        case 'string':
            item.model(config.model, function (err, model) {
                model.read({q: config.query || {}}, function (err, data) {
                    callback(err, data);
                });
            });
            return;

        // model data
        case 'object':
            callback(null, config.model);
            return;

        default:
            callback('Invalid navigation model');
            return;
    }
}

function getActiveNameFromUrl (pattern) {
    var active;
    var path = location.pathname;
    if (path === self.base) {
        active = '/';
    } else {
        active = path.match(new RegExp(pattern));
        active = active ? active[1] : null;
    }

    return active;
}

function init () {
    var self = this;
    var config = self.mono.config.data;
    var V = View(self);

    if (!config) {
        return console.error('[nav: no config available]');
    }

    self.config = config;

    // state handler
    self.activate = activate;

    // set base
    config.baseLength = config.baseLength || 0;
    if (config.baseLength < 0) {
        config.baseLength = 0;
    }
    self.base = location.pathname.split('/').slice(0, config.baseLength + 1).join('/') + '/';

    // get current active
    var active = null;
    if (config.pattern) {
        self.pattern = config.pattern;
        active = getActiveNameFromUrl(config.pattern);
    }

    // parse query
    if (config.query) {
        config.query = JSON.parse(config.query);
    }

    // init layout view
    V.load(config.layout, function (err, layout) {

        if (err) {
            return console.error('[nav: ' + err.toString() + ']');
        }

        // return when no dom is available
        if (!layout.template && layout.template.dom) {
            return console.error('[nav: no dom available]');
        }

        self.layout = layout;

        // render html
        layout.template.render();

        // init item view
        V.load(config.item, function (err, item) {

            if (err) {
                return console.error('[nav: ' + err.toString() + ']');
            }

            // return when no dom is available
            if (!item.template && item.template.dom) {
                return console.error('[nav item: no dom available]');
            }

            loadModel(config, item, function(err, data) {

                if (err) {
                    return console.error('[nav: ' + err.toString() + ']');
                }

                self.item = item;

                // check if stateKey exists
                if (!data[0] || typeof data[0][config.stateKey] === 'undefined') {
                    return;
                }

                // render items
                item.template.render(data);

                // add change state handler to nav items
                var i = 0;
                var items = $('li', item.template.dom);
                self.items = {};

                for (i = 0; i < items.length; ++i) {
                    var $item = $(items[i]);

                    // save state item
                    self.items[data[i][config.stateKey]] = $item;

                    // save active item if found in the url
                    if (data[i][config.stateKey] === active) {
                        self.active = $item;
                    }

                    $item.on('click', domStateHandler(self, item, data[i]));
                }

                // emit state events on dom elms
                if (config.domStates) {
                    for (i = 0; i < config.domStates.length; ++i) {
                        // get element
                        var elm = $(config.domStates[i].selector, layout.template.dom);
                        if (elm) {

                            // save state items
                            self.items[config.domStates[i].state] = elm;

                            // save active item if found in the url
                            if (config.domStates[i].state === active){
                                self.active = elm;
                            }

                            elm.on(config.domStates[i].event, domStateHandler(self, item, config.domStates[i]));
                        }
                    }
                }

                item.state.emit();
            });
        });
    });
}

module.exports = init;

return module; });
<|endoftext|>


<commit_before>import React, { Component } from 'react'
import { SidebarSwitcher } from './icons/icon-sidebar-switch';
import { Route, Link } from 'react-router-dom';
import { Controlled as CodeMirror } from 'react-codemirror2'
import { dateToDa, stringToSymbol } from '/lib/util';

import 'codemirror/mode/markdown/markdown';

export class NewPost extends Component {
  constructor(props) {
    super(props);
    this.state = {
      body: '',
      title: '',
      submit: false,
      awaiting: null,
    }

    this.postSubmit = this.postSubmit.bind(this);
    this.titleChange = this.titleChange.bind(this);
    this.bodyChange = this.bodyChange.bind(this);
  }

  postSubmit() {
    let newNote = {
      "new-note": {
        who: this.props.ship.slice(1),
        book: this.props.book,
        note: stringToSymbol(this.state.title),
        title: this.state.title,
        body: this.state.body,
      }
    }

    this.setState({
      awaiting: newNote["new-note"].note
    }, () => {
      window.api.setSpinner(true);
      window.api.action("publish", "publish-action", newNote);
    });
  }

  componentWillMount() {
    window.api.fetchNotebook(this.props.ship, this.props.book);
  }

  componentDidUpdate(prevProps, prevState) {
    let notebook = this.props.notebooks[this.props.ship][this.props.book];
    if (notebook.notes[this.state.awaiting]) {
      window.api.setSpinner(false);
      let popout = (this.props.popout) ? "popout/" : "";
      let redirect =
     `/~publish/${popout}note/${this.props.ship}/${this.props.book}/${this.state.awaiting}`;
      this.props.history.push(redirect);
    }
  }

  titleChange(evt) {
    let submit = !(evt.target.value === '' || this.state.body === '');
    this.setState({title: evt.target.value, submit: submit});
  }

  bodyChange(editor, data, value) {
    let submit = !(value === '' || this.state.title === '');
    this.setState({body: value, submit: submit});
  }

  render() {
    const { props, state } = this;

    let notebook = props.notebooks[props.ship][props.book];

    const options = {
      mode: 'markdown',
      theme: 'tlon',
      lineNumbers: false,
      lineWrapping: true,
      scrollbarStyle: null,
      cursorHeight: 0.85
    };

    let date = dateToDa(new Date()).slice(1, -10);

    let submitStyle = (state.submit)
      ? { color: '#2AA779', cursor: "pointer" }
      : { color: '#B1B2B3', cursor: "auto" };

    let hrefIndex = props.location.pathname.indexOf("/notebook/");
    let publishsubStr = props.location.pathname.substr(hrefIndex)
    let popoutHref = `/~publish/popout${publishsubStr}`;

    let hiddenOnPopout = (props.popout)
      ? "" : "dib-m dib-l dib-xl";

    return (

          <SidebarSwitcher
            sidebarShown={props.sidebarShown}
            popout={props.popout}
          />
          <button
            className={"v-mid w-100 mw6 tl h1 pl4"}
            disabled={!state.submit}
            style={submitStyle}
            onClick={this.postSubmit}>
            Publish To {notebook.title}
          </button>
          <Link
          className={"dn absolute right-1 top-1 " + hiddenOnPopout}
          to={popoutHref}
          target="_blank">

          </Link>

            <input
              autoFocus
              type="text"
              style={{ paddingBottom: 8 }}
              className="w-100"
              onChange={this.titleChange}
              placeholder="New Post"
            />

{date}

            <CodeMirror
              value={state.body}
              options={options}
              onBeforeChange={(e, d, v) => this.bodyChange(e, d, v)}
              onChange={(editor, data, value) => {}}
            />

    );
  }
}

export default NewPost;
<commit_msg>publish: inline style -> class changes<commit_after>import React, { Component } from 'react'
import { SidebarSwitcher } from './icons/icon-sidebar-switch';
import { Route, Link } from 'react-router-dom';
import { Controlled as CodeMirror } from 'react-codemirror2'
import { dateToDa, stringToSymbol } from '/lib/util';

import 'codemirror/mode/markdown/markdown';

export class NewPost extends Component {
  constructor(props) {
    super(props);
    this.state = {
      body: '',
      title: '',
      submit: false,
      awaiting: null,
    }

    this.postSubmit = this.postSubmit.bind(this);
    this.titleChange = this.titleChange.bind(this);
    this.bodyChange = this.bodyChange.bind(this);
  }

  postSubmit() {
    let newNote = {
      "new-note": {
        who: this.props.ship.slice(1),
        book: this.props.book,
        note: stringToSymbol(this.state.title),
        title: this.state.title,
        body: this.state.body,
      }
    }

    this.setState({
      awaiting: newNote["new-note"].note
    }, () => {
      window.api.setSpinner(true);
      window.api.action("publish", "publish-action", newNote);
    });
  }

  componentWillMount() {
    window.api.fetchNotebook(this.props.ship, this.props.book);
  }

  componentDidUpdate(prevProps, prevState) {
    let notebook = this.props.notebooks[this.props.ship][this.props.book];
    if (notebook.notes[this.state.awaiting]) {
      window.api.setSpinner(false);
      let popout = (this.props.popout) ? "popout/" : "";
      let redirect =
     `/~publish/${popout}note/${this.props.ship}/${this.props.book}/${this.state.awaiting}`;
      this.props.history.push(redirect);
    }
  }

  titleChange(evt) {
    let submit = !(evt.target.value === '' || this.state.body === '');
    this.setState({title: evt.target.value, submit: submit});
  }

  bodyChange(editor, data, value) {
    let submit = !(value === '' || this.state.title === '');
    this.setState({body: value, submit: submit});
  }

  render() {
    const { props, state } = this;

    let notebook = props.notebooks[props.ship][props.book];

    const options = {
      mode: 'markdown',
      theme: 'tlon',
      lineNumbers: false,
      lineWrapping: true,
      scrollbarStyle: null,
      cursorHeight: 0.85
    };

    let date = dateToDa(new Date()).slice(1, -10);

    let submitStyle = (state.submit)
      ? { color: '#2AA779', cursor: "pointer" }
      : { color: '#B1B2B3', cursor: "auto" };

    let hrefIndex = props.location.pathname.indexOf("/notebook/");
    let publishsubStr = props.location.pathname.substr(hrefIndex)
    let popoutHref = `/~publish/popout${publishsubStr}`;

    let hiddenOnPopout = (props.popout)
      ? "" : "dib-m dib-l dib-xl";

    return (

          <SidebarSwitcher
            sidebarShown={props.sidebarShown}
            popout={props.popout}
          />
          <button
            className={"v-mid w-100 mw6 tl h1 pl4"}
            disabled={!state.submit}
            style={submitStyle}
            onClick={this.postSubmit}>
            Publish To {notebook.title}
          </button>
          <Link
          className={"dn absolute right-1 top-1 " + hiddenOnPopout}
          to={popoutHref}
          target="_blank">

          </Link>

            <input
              autoFocus
              type="text"
              className="w-100 pb2"
              onChange={this.titleChange}
              placeholder="New Post"
            />

{date}

            <CodeMirror
              value={state.body}
              options={options}
              onBeforeChange={(e, d, v) => this.bodyChange(e, d, v)}
              onChange={(editor, data, value) => {}}
            />

    );
  }
}

export default NewPost;
<|endoftext|>


<commit_before>module.exports = function (grunt) {
  'use strict';

  /**
   * read optional JSON from filepath
   * @param {String} filepath
   * @return {Object}
   */
  var readOptionalJSON = function (filepath) {
    var data = {};
    try {
      data = grunt.file.readJSON(filepath);
      // The concatenated file won't pass onevar
      // But our modules can
      delete data.onever;
    } catch (e) { }
    return data;
  };

  var customLaunchers = {
    'SL_IE8': {
      base: 'SauceLabs',
      browserName: 'internet explorer',
      version: '8.0',
      platform: 'windows XP'
    },
    'SL_IE9': {
      base: 'SauceLabs',
      browserName: 'internet explorer',
      version: '9.0',
      platform: 'windows 7'
    },
    'SL_IE10': {
      base: 'SauceLabs',
      browserName: 'internet explorer',
      version: '10.0',
      platform: 'windows 8'
    },
    'SL_IE11': {
      base: 'SauceLabs',
      browserName: 'internet explorer',
      version: '11.0',
      platform: 'windows 8.1'
    },
    'SL_CHROME': {
      base: 'SauceLabs',
      browserName: 'chrome',
      version: '43',
      platform: 'windows 8'
    },
    'SL_FIREFOX': {
      base: 'SauceLabs',
      browserName: 'firefox',
      version: '38',
      platform: 'windows 8'
    },
    'SL_SAFARI': {
      base: 'SauceLabs',
      browserName: 'safari',
      version: '8.0',
      platform: 'OS X 10.10'
    }
  };

  grunt.initConfig({
    // package File
    pkg: grunt.file.readJSON('package.json'),

    // bulid source(grunt-build.js).
    build: {
      all: {
        baseUrl: 'src/js',        // base url
        startFile: 'intro.js',    // intro part
        endFile: 'outro.js',      // outro part
        outFile: 'dist/summernote.js' // out file
      }
    },

    // for javascript convention.
    jshint: {
      all: {
        src: [
          'src/**/*.js',
          'plugin/**/*.js',
          'lang/**/*.js',
          'Gruntfile.js',
          'test/**/*.js',
          '!test/coverage/**/*.js',
          'build/*.js'
        ],
        options: {
          jshintrc: true
        }
      },
      dist: {
        src: 'dist/summernote.js',
        options: readOptionalJSON('.jshintrc')
      }
    },

    // uglify: minify javascript
    uglify: {
      options: {
        banner: '/*! Summernote v<%=pkg.version%> | (c) 2013-2015 Alan Hong and other contributors | MIT license */\n'
      },
      all: {
        files: [
          { 'dist/summernote.min.js': ['dist/summernote.js'] },
          {
            expand: true,
            cwd: 'dist/lang',
            src: '**/*.js',
            dest: 'dist/lang',
            ext: '.min.js'
          }
        ]
      }
    },

    // recess: minify stylesheets
    recess: {
      dist: {
        options: { compile: true, compress: true },
        files: {
          'dist/summernote.css': ['src/less/summernote.less']
        }
      }
    },

    // compress: summernote-{{version}}-dist.zip
    compress: {
      main: {
        options: {
          archive: function () {
            return 'dist/summernote-{{version}}-dist.zip'.replace(
              '{{version}}',
              grunt.config('pkg.version')
            );
          }
        },
        files: [{
          expand: true,
          src: [
            'dist/*.js',
            'dist/*.css'
          ]
        }, {
          src: ['plugin/**/*.js', 'lang/**/*.js'],
          dest: 'dist/'
        }]
      }
    },

    // connect configuration.
    connect: {
      all: {
        options: {
          port: 3000
        }
      }
    },

    // watch source code change
    watch: {
      all: {
        files: ['src/less/*.less', 'src/js/**/*.js'],
        tasks: ['recess', 'jshint'],
        options: {
          livereload: true
        }
      }
    },

    // Meteor commands to test and publish package
    exec: {
      'meteor-test': {
        command: 'meteor/runtests.sh'
      },
      'meteor-publish': {
        command: 'meteor/publish.sh'
      }
    },

    karma: {
      options: {
        configFile: './test/karma.conf.js'
      },
      all: {
        // Chrome, ChromeCanary, Firefox, Opera, Safari, PhantomJS, IE
        browsers: ['PhantomJS'],
        reporters: ['progress']
      },
      dist: {
        singleRun: true,
        browsers: ['PhantomJS']
      },
      travis: {
        singleRun: true,
        browsers: ['PhantomJS'],
        reporters: ['progress', 'coverage']
      },
      saucelabs: {
        reporters: ['saucelabs'],
        sauceLabs: {
          testName: '[Travis] unit tests for summernote',
          startConnect: false,
          tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER,
          build: process.env.TRAVIS_BUILD_NUMBER,
          tags: [process.env.TRAVIS_BRANCH, process.env.TRAVIS_PULL_REQUEST]
        },
        captureTimeout: 120000,
        customLaunchers: customLaunchers,
        browsers: Object.keys(customLaunchers),
        singleRun: true
      }
    },

    coveralls: {
      options: {
        force: false
      },
      travis: {
        src: 'test/coverage/**/lcov.info'
      }
    },
    clean: {
      dist: ['dist']
    },
    copy: {
      dist: {
        files: [
          {src: 'lang/*', dest: 'dist/'}
        ]
      }
    }
  });

  // load all tasks from the grunt plugins used in this file
  require('load-grunt-tasks')(grunt);

  // load all grunts/*.js
  grunt.loadTasks('grunts');

  // server: runt server for development
  grunt.registerTask('server', ['connect', 'watch']);

  // test: unit test on test folder
  grunt.registerTask('test', ['jshint', 'karma:all']);

  // test: unit test on travis
  grunt.registerTask('test-travis', ['jshint', 'karma:travis']);

  // test: saucelabs test
  grunt.registerTask('saucelabs-test', ['karma:saucelabs']);

  // dist: make dist files
  grunt.registerTask('dist', [
    'clean:dist',
    'build', 'jshint', 'karma:dist',
    'copy:dist', 'uglify', 'recess', 'compress'
  ]);

  // default: server
  grunt.registerTask('default', ['server']);

  // Meteor tasks
  grunt.registerTask('meteor-test', 'exec:meteor-test');
  grunt.registerTask('meteor-publish', 'exec:meteor-publish');
  grunt.registerTask('meteor', ['meteor-test', 'meteor-publish']);

};
<commit_msg>Comment IE8 launcher.<commit_after>module.exports = function (grunt) {
  'use strict';

  /**
   * read optional JSON from filepath
   * @param {String} filepath
   * @return {Object}
   */
  var readOptionalJSON = function (filepath) {
    var data = {};
    try {
      data = grunt.file.readJSON(filepath);
      // The concatenated file won't pass onevar
      // But our modules can
      delete data.onever;
    } catch (e) { }
    return data;
  };

  var customLaunchers = {
    /*
    'SL_IE8': {
      base: 'SauceLabs',
      browserName: 'internet explorer',
      version: '8.0',
      platform: 'windows XP'
    },
    */
    'SL_IE9': {
      base: 'SauceLabs',
      browserName: 'internet explorer',
      version: '9.0',
      platform: 'windows 7'
    },
    'SL_IE10': {
      base: 'SauceLabs',
      browserName: 'internet explorer',
      version: '10.0',
      platform: 'windows 8'
    },
    'SL_IE11': {
      base: 'SauceLabs',
      browserName: 'internet explorer',
      version: '11.0',
      platform: 'windows 8.1'
    },
    'SL_CHROME': {
      base: 'SauceLabs',
      browserName: 'chrome',
      version: '43',
      platform: 'windows 8'
    },
    'SL_FIREFOX': {
      base: 'SauceLabs',
      browserName: 'firefox',
      version: '38',
      platform: 'windows 8'
    },
    'SL_SAFARI': {
      base: 'SauceLabs',
      browserName: 'safari',
      version: '8.0',
      platform: 'OS X 10.10'
    }
  };

  grunt.initConfig({
    // package File
    pkg: grunt.file.readJSON('package.json'),

    // bulid source(grunt-build.js).
    build: {
      all: {
        baseUrl: 'src/js',        // base url
        startFile: 'intro.js',    // intro part
        endFile: 'outro.js',      // outro part
        outFile: 'dist/summernote.js' // out file
      }
    },

    // for javascript convention.
    jshint: {
      all: {
        src: [
          'src/**/*.js',
          'plugin/**/*.js',
          'lang/**/*.js',
          'Gruntfile.js',
          'test/**/*.js',
          '!test/coverage/**/*.js',
          'build/*.js'
        ],
        options: {
          jshintrc: true
        }
      },
      dist: {
        src: 'dist/summernote.js',
        options: readOptionalJSON('.jshintrc')
      }
    },

    // uglify: minify javascript
    uglify: {
      options: {
        banner: '/*! Summernote v<%=pkg.version%> | (c) 2013-2015 Alan Hong and other contributors | MIT license */\n'
      },
      all: {
        files: [
          { 'dist/summernote.min.js': ['dist/summernote.js'] },
          {
            expand: true,
            cwd: 'dist/lang',
            src: '**/*.js',
            dest: 'dist/lang',
            ext: '.min.js'
          }
        ]
      }
    },

    // recess: minify stylesheets
    recess: {
      dist: {
        options: { compile: true, compress: true },
        files: {
          'dist/summernote.css': ['src/less/summernote.less']
        }
      }
    },

    // compress: summernote-{{version}}-dist.zip
    compress: {
      main: {
        options: {
          archive: function () {
            return 'dist/summernote-{{version}}-dist.zip'.replace(
              '{{version}}',
              grunt.config('pkg.version')
            );
          }
        },
        files: [{
          expand: true,
          src: [
            'dist/*.js',
            'dist/*.css'
          ]
        }, {
          src: ['plugin/**/*.js', 'lang/**/*.js'],
          dest: 'dist/'
        }]
      }
    },

    // connect configuration.
    connect: {
      all: {
        options: {
          port: 3000
        }
      }
    },

    // watch source code change
    watch: {
      all: {
        files: ['src/less/*.less', 'src/js/**/*.js'],
        tasks: ['recess', 'jshint'],
        options: {
          livereload: true
        }
      }
    },

    // Meteor commands to test and publish package
    exec: {
      'meteor-test': {
        command: 'meteor/runtests.sh'
      },
      'meteor-publish': {
        command: 'meteor/publish.sh'
      }
    },

    karma: {
      options: {
        configFile: './test/karma.conf.js'
      },
      all: {
        // Chrome, ChromeCanary, Firefox, Opera, Safari, PhantomJS, IE
        browsers: ['PhantomJS'],
        reporters: ['progress']
      },
      dist: {
        singleRun: true,
        browsers: ['PhantomJS']
      },
      travis: {
        singleRun: true,
        browsers: ['PhantomJS'],
        reporters: ['progress', 'coverage']
      },
      saucelabs: {
        reporters: ['saucelabs'],
        sauceLabs: {
          testName: '[Travis] unit tests for summernote',
          startConnect: false,
          tunnelIdentifier: process.env.TRAVIS_JOB_NUMBER,
          build: process.env.TRAVIS_BUILD_NUMBER,
          tags: [process.env.TRAVIS_BRANCH, process.env.TRAVIS_PULL_REQUEST]
        },
        captureTimeout: 120000,
        customLaunchers: customLaunchers,
        browsers: Object.keys(customLaunchers),
        singleRun: true
      }
    },

    coveralls: {
      options: {
        force: false
      },
      travis: {
        src: 'test/coverage/**/lcov.info'
      }
    },
    clean: {
      dist: ['dist']
    },
    copy: {
      dist: {
        files: [
          {src: 'lang/*', dest: 'dist/'}
        ]
      }
    }
  });

  // load all tasks from the grunt plugins used in this file
  require('load-grunt-tasks')(grunt);

  // load all grunts/*.js
  grunt.loadTasks('grunts');

  // server: runt server for development
  grunt.registerTask('server', ['connect', 'watch']);

  // test: unit test on test folder
  grunt.registerTask('test', ['jshint', 'karma:all']);

  // test: unit test on travis
  grunt.registerTask('test-travis', ['jshint', 'karma:travis']);

  // test: saucelabs test
  grunt.registerTask('saucelabs-test', ['karma:saucelabs']);

  // dist: make dist files
  grunt.registerTask('dist', [
    'clean:dist',
    'build', 'jshint', 'karma:dist',
    'copy:dist', 'uglify', 'recess', 'compress'
  ]);

  // default: server
  grunt.registerTask('default', ['server']);

  // Meteor tasks
  grunt.registerTask('meteor-test', 'exec:meteor-test');
  grunt.registerTask('meteor-publish', 'exec:meteor-publish');
  grunt.registerTask('meteor', ['meteor-test', 'meteor-publish']);

};
<|endoftext|>


<commit_before>'use strict';

module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
	simplemocha: {
		app: {
			src: ['test/test.js']
		}
	},
	jshint: {
		options: {
			jshintrc: '.jshintrc'
		},
		lib: ['lib/**/*.js', 'tasks/**/*.js', 'Gruntfile.js']
	},
	release: {},
	"steal-build": {
		"test-webworker": {
			options: {
				system: {
					configMain: "@empty",
					main: "worker",
					baseUrl: __dirname + "/test/browser/webworker"
				},
				buildOptions: {
					bundleSteal: true,
					quiet: true
				}
			}
		}
	},
	testee: {
		tests: {
			options: {
				browsers: [ "firefox" ]
			},
			src: [ "test/browser/test.html" ]
		}
	}
  });

  grunt.registerTask('default', 'test');
  grunt.registerTask('test', [ 'jshint', 'simplemocha' ]);
  grunt.registerTask("test:browser", [ "steal-build", "testee" ]);

  grunt.loadNpmTasks('grunt-simple-mocha');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-release');
  grunt.loadNpmTasks('testee');
  grunt.loadTasks("tasks");
};
<commit_msg>Set a very large timeout for AppVeyor slowness<commit_after>'use strict';

module.exports = function(grunt) {

  // Project configuration.
  grunt.initConfig({
	simplemocha: {
    options: {
      timeout: 30000
    },
		app: {
			src: ['test/test.js']
		}
	},
	jshint: {
		options: {
			jshintrc: '.jshintrc'
		},
		lib: ['lib/**/*.js', 'tasks/**/*.js', 'Gruntfile.js']
	},
	release: {},
	"steal-build": {
		"test-webworker": {
			options: {
				system: {
					configMain: "@empty",
					main: "worker",
					baseUrl: __dirname + "/test/browser/webworker"
				},
				buildOptions: {
					bundleSteal: true,
					quiet: true
				}
			}
		}
	},
	testee: {
		tests: {
			options: {
				browsers: [ "firefox" ]
			},
			src: [ "test/browser/test.html" ]
		}
	}
  });

  grunt.registerTask('default', 'test');
  grunt.registerTask('test', [ 'jshint', 'simplemocha' ]);
  grunt.registerTask("test:browser", [ "steal-build", "testee" ]);

  grunt.loadNpmTasks('grunt-simple-mocha');
  grunt.loadNpmTasks('grunt-contrib-jshint');
  grunt.loadNpmTasks('grunt-release');
  grunt.loadNpmTasks('testee');
  grunt.loadTasks("tasks");
};
<|endoftext|>


<commit_before>module.exports = {
	Client: require('./client'),
	CommandoClient: require('./client'),
	CommandoRegistry: require('./registry'),
	CommandoGuild: require('./extensions/guild'),
	CommandoMessage: require('./extensions/message'),
	Command: require('./commands/base'),
	CommandGroup: require('./commands/group'),
	ArgumentCollector: require('./commands/collector'),
	Argument: require('./commands/argument'),
	ArgumentType: require('./types/base'),
	FriendlyError: require('./errors/friendly'),
	CommandFormatError: require('./errors/command-format'),

	util: require('./util'),
	version: require('../package').version,

	SettingProvider: require('./providers/base'),
	get SQLiteProvider() {
		return require('./providers/sqlite');
	},
	get SyncSQLiteProvider() {
		return require('./providers/sqlite-sync');
	}
};

/**
 * @external Channel
 * @see {@link https://discord.js.org/#/docs/main/master/class/Channel}
 */
/**
 * @external Client
 * @see {@link https://discord.js.org/#/docs/main/master/class/Client}
 */
/**
 * @external ClientOptions
 * @see {@link https://discord.js.org/#/docs/main/master/typedef/ClientOptions}
 */
/**
 * @external Collection
 * @see {@link https://discord.js.org/#/docs/main/master/class/Collection}
 */
/**
 * @external DMChannel
 * @see {@link https://discord.js.org/#/docs/main/master/class/DMChannel}
 */
/**
 * @external GroupDMChannel
 * @see {@link https://discord.js.org/#/docs/main/master/class/GroupDMChannel}
 */
/**
 * @external Guild
 * @see {@link https://discord.js.org/#/docs/main/master/class/Guild}
 */
/**
 * @external GuildMember
 * @see {@link https://discord.js.org/#/docs/main/master/class/GuildMember}
 */
/**
 * @external GuildResolvable
 * @see {@link https://discord.js.org/#/docs/main/master/typedef/GuildResolvable}
 */
/**
 * @external Message
 * @see {@link https://discord.js.org/#/docs/main/master/class/Message}
 */
/**
 * @external MessageAttachment
 * @see {@link https://discord.js.org/#/docs/main/master/class/MessageAttachment}
 */
/**
 * @external MessageEmbed
 * @see {@link https://discord.js.org/#/docs/main/master/class/MessageEmbed}
 */
/**
 * @external MessageReaction
 * @see {@link https://discord.js.org/#/docs/main/master/class/MessageReaction}
 */
/**
 * @external MessageOptions
 * @see {@link https://discord.js.org/#/docs/main/master/typedef/MessageOptions}
 */
/**
 * @external Role
 * @see {@link https://discord.js.org/#/docs/main/master/class/Role}
 */
/**
 * @external StringResolvable
 * @see {@link https://discord.js.org/#/docs/main/master/typedef/StringResolvable}
 */
/**
 * @external TextChannel
 * @see {@link https://discord.js.org/#/docs/main/master/class/TextChannel}
 */
/**
 * @external User
 * @see {@link https://discord.js.org/#/docs/main/master/class/User}
 */
/**
 * @external UserResolvable
 * @see {@link https://discord.js.org/#/docs/main/master/class/UserResolvable}
 */
/**
 * @external Emoji
 * @see {@link https://discord.js.org/#/docs/main/master/class/Emoji}
 */
/**
 * @external ReactionEmoji
 * @see {@link https://discord.js.org/#/docs/main/master/class/ReactionEmoji}
 */
/**
 * @external Webhook
 * @see {@link https://discord.js.org/#/docs/main/master/class/Webhook}
 */
/**
 * @external MessageEmbed
 * @see {@link https://discord.js.org/#/docs/main/master/class/MessageEmbed}
 */
/**
 * @external ShardingManager
 * @see {@link https://discord.js.org/#/docs/main/master/class/ShardingManager}
 */
/**
 * @external RequireAllOptions
 * @see {@link https://www.npmjs.com/package/require-all}
 */
<commit_msg>Add missing external<commit_after>module.exports = {
	Client: require('./client'),
	CommandoClient: require('./client'),
	CommandoRegistry: require('./registry'),
	CommandoGuild: require('./extensions/guild'),
	CommandoMessage: require('./extensions/message'),
	Command: require('./commands/base'),
	CommandGroup: require('./commands/group'),
	ArgumentCollector: require('./commands/collector'),
	Argument: require('./commands/argument'),
	ArgumentType: require('./types/base'),
	FriendlyError: require('./errors/friendly'),
	CommandFormatError: require('./errors/command-format'),

	util: require('./util'),
	version: require('../package').version,

	SettingProvider: require('./providers/base'),
	get SQLiteProvider() {
		return require('./providers/sqlite');
	},
	get SyncSQLiteProvider() {
		return require('./providers/sqlite-sync');
	}
};

/**
 * @external Channel
 * @see {@link https://discord.js.org/#/docs/main/master/class/Channel}
 */
/**
 * @external Client
 * @see {@link https://discord.js.org/#/docs/main/master/class/Client}
 */
/**
 * @external ClientOptions
 * @see {@link https://discord.js.org/#/docs/main/master/typedef/ClientOptions}
 */
/**
 * @external Collection
 * @see {@link https://discord.js.org/#/docs/main/master/class/Collection}
 */
/**
 * @external DMChannel
 * @see {@link https://discord.js.org/#/docs/main/master/class/DMChannel}
 */
/**
 * @external GroupDMChannel
 * @see {@link https://discord.js.org/#/docs/main/master/class/GroupDMChannel}
 */
/**
 * @external Guild
 * @see {@link https://discord.js.org/#/docs/main/master/class/Guild}
 */
/**
 * @external GuildMember
 * @see {@link https://discord.js.org/#/docs/main/master/class/GuildMember}
 */
/**
 * @external GuildResolvable
 * @see {@link https://discord.js.org/#/docs/main/master/typedef/GuildResolvable}
 */
/**
 * @external Message
 * @see {@link https://discord.js.org/#/docs/main/master/class/Message}
 */
/**
 * @external MessageAttachment
 * @see {@link https://discord.js.org/#/docs/main/master/class/MessageAttachment}
 */
/**
 * @external MessageEmbed
 * @see {@link https://discord.js.org/#/docs/main/master/class/MessageEmbed}
 */
/**
 * @external MessageReaction
 * @see {@link https://discord.js.org/#/docs/main/master/class/MessageReaction}
 */
/**
 * @external MessageOptions
 * @see {@link https://discord.js.org/#/docs/main/master/typedef/MessageOptions}
 */
/**
 * @external PermissionResolvable
 * @see {@link https://discord.js.org/#/docs/main/master/typedef/PermissionResolvable}
 */
/**
 * @external Role
 * @see {@link https://discord.js.org/#/docs/main/master/class/Role}
 */
/**
 * @external StringResolvable
 * @see {@link https://discord.js.org/#/docs/main/master/typedef/StringResolvable}
 */
/**
 * @external TextChannel
 * @see {@link https://discord.js.org/#/docs/main/master/class/TextChannel}
 */
/**
 * @external User
 * @see {@link https://discord.js.org/#/docs/main/master/class/User}
 */
/**
 * @external UserResolvable
 * @see {@link https://discord.js.org/#/docs/main/master/class/UserResolvable}
 */
/**
 * @external Emoji
 * @see {@link https://discord.js.org/#/docs/main/master/class/Emoji}
 */
/**
 * @external ReactionEmoji
 * @see {@link https://discord.js.org/#/docs/main/master/class/ReactionEmoji}
 */
/**
 * @external Webhook
 * @see {@link https://discord.js.org/#/docs/main/master/class/Webhook}
 */
/**
 * @external MessageEmbed
 * @see {@link https://discord.js.org/#/docs/main/master/class/MessageEmbed}
 */
/**
 * @external ShardingManager
 * @see {@link https://discord.js.org/#/docs/main/master/class/ShardingManager}
 */
/**
 * @external RequireAllOptions
 * @see {@link https://www.npmjs.com/package/require-all}
 */
<|endoftext|>


<commit_before>var cordova = require('cordova');

/**
 * Clipboard plugin for Cordova
 *
 * @constructor
 */
function Clipboard () {}

/**
 * Sets the clipboard content
 *
 * @param {String} text The content to copy to the clipboard
 */
Clipboard.prototype.copy = function (text, onSuccess, onFail) {
	cordova.exec(onSuccess, onFail, "Clipboard", "copy", [text]);
}

/**
 * Gets the clipboard content
 *
 * @param {Function} callback The function to call with the result
 */
Clipboard.prototype.paste = function (onSuccess, onFail) {
	cordova.exec(onSuccess, onFail, "Clipboard", "paste", []);
}

// Register the plugin
var clipboard = new Clipboard();
module.exports = clipboard;<commit_msg>Update clipboard.js<commit_after>var cordova = require('cordova');

/**
 * Clipboard plugin for Cordova
 *
 * @constructor
 */
function Clipboard () {}

/**
 * Sets the clipboard content
 *
 * @param {String}   text      The content to copy to the clipboard
 * @param {Function} onSuccess The function to call in case of success (takes the copied text as argument)
 * @param {Function} onFail    The function to call in case of error
 */
Clipboard.prototype.copy = function (text, onSuccess, onFail) {
	cordova.exec(onSuccess, onFail, "Clipboard", "copy", [text]);
}

/**
 * Gets the clipboard content
 *
 * @param {Function} onSuccess The function to call in case of success
 * @param {Function} onFail    The function to call in case of error
 */
Clipboard.prototype.paste = function (onSuccess, onFail) {
	cordova.exec(onSuccess, onFail, "Clipboard", "paste", []);
}

// Register the plugin
var clipboard = new Clipboard();
module.exports = clipboard;
<|endoftext|>


<commit_before>define([
    '/api/config?cb=' + Math.random().toString(16).substring(2),
    '/code/rt_codemirror.js',
    '/common/messages.js',
    '/common/crypto.js',
    '/common/realtime-input.js',
    '/common/TextPatcher.js',
    '/bower_components/jquery/dist/jquery.min.js'
], function (Config, RTCode, Messages, Crypto, Realtime, TextPatcher) {
    var $ = window.jQuery;
    var ifrw = $('#pad-iframe')[0].contentWindow;
    var module = {};

    $(function () {
        var userName = Crypto.rand64(8);

        var key;
        var channel = '';
        if (!/#/.test(window.location.href)) {
            key = Crypto.genKey();
        } else {
            var hash = window.location.hash.slice(1);
            channel = hash.slice(0, 32);
            key = hash.slice(32);
        }

        var config = {
            userName: userName,
            websocketURL: Config.websocketURL,
            channel: channel,
            cryptKey: key,
            crypto: Crypto,
        };

        var andThen = function (CMeditor) {
            var $pad = $('#pad-iframe');
            var $textarea = $pad.contents().find('#editor1');

            var editor = CMeditor.fromTextArea($textarea[0], {
                lineNumbers: true,
                lineWrapping: true,
                autoCloseBrackets: true,
                matchBrackets : true,
                showTrailingSpace : true,
                styleActiveLine : true,
                search: true,
                highlightSelectionMatches: {showToken: /\w+/},
                extraKeys: {"Ctrl-Q": function(cm){ cm.foldCode(cm.getCursor()); }},
                foldGutter: true,
                gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
                mode: "javascript"
            });
            editor.setValue(Messages.codeInitialState);

            // TODO lock editor until chain is synced
            // then unlock
            var setEditable = function () { };

            var initializing = true;

            var onInit = config.onInit = function (info) {
                window.location.hash = info.channel + key;
                var realtime = info.realtime;
                module.patchText = TextPatcher.create({
                    realtime: realtime,
                    logging: true,
                });
                $(window).on('hashchange', function() {
                    window.location.reload();
                });
            };

            var onReady = config.onReady = function (info) {
                console.log("READY!");

                initializing = false;
            };

            var onRemote = config.onRemote = function (info) {
                if (initializing) { return; }
                // check cursor
                // apply changes to textarea
                // replace cursor
            };

            var onLocal = config.onLocal = function () {
                editor.save();
                //rtw.onEvent();
            };

            var onAbort = config.onAbort = function (info) {
                // TODO  alert the user
                // inform of network disconnect
            };

            var realtime = module.realtime = Realtime.start(config);

            editor.on('change', onLocal);
        };

        var interval = 100;

        var first = function () {
            if (ifrw.CodeMirror) {
                // it exists, call your continuation
                andThen(ifrw.CodeMirror);
            } else {
                console.log("CodeMirror was not defined. Trying again in %sms", interval);
                // try again in 'interval' ms
                setTimeout(first, interval);
            }
        };

        first();
    });
});
<commit_msg>start rewriting codepad to use realtime-input<commit_after>define([
    '/api/config?cb=' + Math.random().toString(16).substring(2),
//    '/code/rt_codemirror.js',
    '/common/messages.js',
    '/common/crypto.js',
    '/common/realtime-input.js',
    '/common/TextPatcher.js',
    '/bower_components/jquery/dist/jquery.min.js'
], function (Config, /*RTCode,*/ Messages, Crypto, Realtime, TextPatcher) {
    var $ = window.jQuery;
    var module = window.APP = {};
    var ifrw = module.ifrw = $('#pad-iframe')[0].contentWindow;

    $(function () {
        var userName = Crypto.rand64(8);

        var key;
        var channel = '';
        var hash = false;
        if (!/#/.test(window.location.href)) {
            key = Crypto.genKey();
        } else {
            hash = window.location.hash.slice(1);
            channel = hash.slice(0, 32);
            key = hash.slice(32);
        }

        var config = {
            //initialState: Messages.codeInitialState,
            userName: userName,
            websocketURL: Config.websocketURL,
            channel: channel,
            cryptKey: key,
            crypto: Crypto,
        };

        var andThen = function (CMeditor) {
            var $pad = $('#pad-iframe');
            var $textarea = $pad.contents().find('#editor1');

            var editor = module.editor = CMeditor.fromTextArea($textarea[0], {
                lineNumbers: true,
                lineWrapping: true,
                autoCloseBrackets: true,
                matchBrackets : true,
                showTrailingSpace : true,
                styleActiveLine : true,
                search: true,
                highlightSelectionMatches: {showToken: /\w+/},
                extraKeys: {"Ctrl-Q": function(cm){ cm.foldCode(cm.getCursor()); }},
                foldGutter: true,
                gutters: ["CodeMirror-linenumbers", "CodeMirror-foldgutter"],
                mode: "javascript"
            });

            // TODO lock editor until chain is synced
            // then unlock
            var setEditable = function () { };
            var canonicalize = function (t) { return t.replace(/\r\n/g, '\n'); };

            var initializing = true;

            var onInit = config.onInit = function (info) {
                window.location.hash = info.channel + key;
                var realtime = module.realtime = info.realtime;
                module.patchText = TextPatcher.create({
                    realtime: realtime,
                    logging: true,
                    //initialState: Messages.codeInitialState
                });

                if (!hash) {
                    editor.setValue(Messages.codeInitialState);
                    module.patchText(Messages.codeInitialState);
                    module.patchText(Messages.codeInitialState);
                    editor.setValue(Messages.codeInitialState);
                }

                //$(window).on('hashchange', function() { window.location.reload(); });
            };

            var onReady = config.onReady = function (info) {
                console.log("READY!");

                var userDoc = module.realtime.getUserDoc();

                editor.setValue(userDoc);

                initializing = false;
            };

            var onRemote = config.onRemote = function (info) {
                if (initializing) { return; }
                console.log("REMOTE");

                var oldDoc = $textarea.val();
                var userDoc = module.realtime.getUserDoc();

                $textarea.val(userDoc);
                editor.setValue(userDoc);

                editor.save();

                // check cursor
                // apply changes to textarea
                // replace cursor
            };

            var onLocal = config.onLocal = function () {
                if (initializing) { return; }
                console.log("LOCAL");
                module.patchText(canonicalize($textarea.val()));
                editor.save();
            };

            var onAbort = config.onAbort = function (info) {
                // TODO  alert the user
                // inform of network disconnect
                window.alert("Network Connection Lost!");
            };

            var realtime = module.realtime = Realtime.start(config);

            editor.on('change', onLocal);

            ['keydown', /*'keyup',*/ 'select', 'mousedown', 'mouseup', 'click'].forEach(function (evt) {
                $textarea.on(evt, function () {
                    onRemote();
                    onLocal();
                });
                // onLocal?
            });

        };

        var interval = 100;

        var first = function () {
            if (ifrw.CodeMirror) {
                // it exists, call your continuation
                andThen(ifrw.CodeMirror);
            } else {
                console.log("CodeMirror was not defined. Trying again in %sms", interval);
                // try again in 'interval' ms
                setTimeout(first, interval);
            }
        };

        first();
    });
});
<|endoftext|>


<commit_before>const { FileModel } = require('../model');
const { userModel } = require('../../user/model');

const getFile = id => FileModel
	.findOne({ _id: id })
	.populate('owner')
	.exec();

const checkPermissions = (permission) => {
	return async (user, file) => {
		const fileObject = await getFile(file);
		const {
			permissions,
			refOwnerModel,
			owner: { _id: owner },
		} = fileObject;

		// return always true for owner of file
		if (user.toString() === owner.toString()) {
			return Promise.resolve(true);
		}

		// or legacy course model
		if (refOwnerModel === 'course') {
			const userObject = await userModel.findOne({ _id: user }).populate('roles').exec();
			const isStudent = userObject.roles.find(role => role.name === 'student');

			if (isStudent) {
				const rolePermissions = permissions.find(
					perm => perm.refId && perm.refId.toString() === isStudent._id.toString(),
				);
				return rolePermissions[permission] ? Promise.resolve(true) : Promise.reject();
			}
			return Promise.resolve(true);
		}

		const teamMember = fileObject.owner.userIds && fileObject.owner.userIds
			.find(_ => _.userId.toString() === user.toString());
		const userPermissions = permissions
			.find(perm => perm.refId && perm.refId.toString() === user.toString());

		// User is either not member of Team
		// or file has no explicit user permissions (sharednetz files)
		if (!teamMember && !userPermissions) {
			return Promise.reject();
		}

		return new Promise((resolve, reject) => {
			if (userPermissions) {
				return userPermissions[permission] ? resolve(true) : reject();
			}

			const { role } = teamMember;
			const rolePermissions = permissions.find(perm => perm.refId.toString() === role.toString());

			return rolePermissions[permission] ? resolve(true) : reject();
		});
	};
};

module.exports = {
	checkPermissions,
	canWrite: checkPermissions('write'),
	canRead: checkPermissions('read'),
	canCreate: checkPermissions('create'),
	canDelete: checkPermissions('delete'),
};
<commit_msg>Consider member status of course in permission check<commit_after>const { FileModel } = require('../model');
const { userModel } = require('../../user/model');

const getFile = id => FileModel
	.findOne({ _id: id })
	.populate('owner')
	.exec();

const checkMemberStatus = ({ file, user }) => {
	const { owner: { userIds, teacherIds } } = file;
	const finder = obj => user.equals(obj.userId || obj);

	if (!userIds && !teacherIds) {
		return false;
	}

	return userIds.find(finder) || teacherIds.find(finder);
};

const checkPermissions = permission => async (user, file) => {
	const fileObject = await getFile(file);
	const {
		permissions,
		refOwnerModel,
		owner: { _id: owner },
	} = fileObject;

	// return always true for owner of file
	if (user.toString() === owner.toString()) {
		return Promise.resolve(true);
	}

	const isMember = checkMemberStatus({ file: fileObject, user });

	// User is no member of team or course
	if (!isMember) {
		return Promise.reject();
	}

	// or legacy course model
	if (refOwnerModel === 'course') {
		const userObject = await userModel.findOne({ _id: user }).populate('roles').exec();
		const isStudent = userObject.roles.find(role => role.name === 'student');

		if (isStudent) {
			const rolePermissions = permissions.find(
				perm => perm.refId && perm.refId.toString() === isStudent._id.toString(),
			);
			return rolePermissions[permission] ? Promise.resolve(true) : Promise.reject();
		}
		return Promise.resolve(true);
	}

	const userPermissions = permissions
		.find(perm => perm.refId && perm.refId.toString() === user.toString());

	// file has no explicit user permissions (sharednetz files)
	if (!userPermissions) {
		return Promise.reject();
	}

	return new Promise((resolve, reject) => {
		if (userPermissions) {
			return userPermissions[permission] ? resolve(true) : reject();
		}

		const { role } = isMember;
		const rolePermissions = permissions.find(perm => perm.refId.toString() === role.toString());

		return rolePermissions[permission] ? resolve(true) : reject();
	});
};

module.exports = {
	checkPermissions,
	canWrite: checkPermissions('write'),
	canRead: checkPermissions('read'),
	canCreate: checkPermissions('create'),
	canDelete: checkPermissions('delete'),
};
<|endoftext|>


<commit_before>/* global module:false */
module.exports = function(grunt) {
	var port = grunt.option('port') || 8000;
	var base = grunt.option('base') || '.';

	// Project configuration
	grunt.initConfig({
		pkg: grunt.file.readJSON('package.json'),
		meta: {
			banner:
				'/*!\n' +
				' * reveal.js <%= pkg.version %> (<%= grunt.template.today("yyyy-mm-dd, HH:MM") %>)\n' +
				' * http://lab.hakim.se/reveal-js\n' +
				' * MIT licensed\n' +
				' *\n' +
				' * Copyright (C) 2016 Hakim El Hattab, http://hakim.se\n' +
				' */'
		},

		qunit: {
			files: [ 'test/*.html' ]
		},

		uglify: {
			options: {
				banner: '<%= meta.banner %>\n'
			},
			build: {
				src: 'js/reveal.js',
				dest: 'js/reveal.min.js'
			}
		},

		sass: {
			core: {
				files: {
					'css/reveal.css': 'css/reveal.scss',
				}
			},
			themes: {
				files: [
					{
						expand: true,
						cwd: 'css/theme/source',
						src: ['*.scss'],
						dest: 'css/theme',
						ext: '.css'
					}
				]
			}
		},

		autoprefixer: {
			dist: {
				src: 'css/reveal.css'
			}
		},

		cssmin: {
			compress: {
				files: {
					'css/reveal.min.css': [ 'css/reveal.css' ]
				}
			}
		},

		jshint: {
			options: {
				curly: false,
				eqeqeq: true,
				immed: true,
				latedef: true,
				newcap: true,
				noarg: true,
				sub: true,
				undef: true,
				eqnull: true,
				browser: true,
				expr: true,
				globals: {
					head: false,
					module: false,
					console: false,
					unescape: false,
					define: false,
					exports: false
				}
			},
			files: [ 'Gruntfile.js', 'js/reveal.js' ]
		},

		connect: {
			server: {
				options: {
          			port: 9026,
					base: base,
					livereload: true,
 		          	keepalive: true,
					open: true
				}
			}
		},

		zip: {
			'reveal-js-presentation.zip': [
				'index.html',
				'css/**',
				'js/**',
				'lib/**',
				'images/**',
				'plugin/**',
				'**.md'
			]
		},

		watch: {
			js: {
				files: [ 'Gruntfile.js', 'js/reveal.js' ],
				tasks: 'js'
			},
			theme: {
				files: [ 'css/theme/source/*.scss', 'css/theme/template/*.scss' ],
				tasks: 'css-themes'
			},
			css: {
				files: [ 'css/reveal.scss' ],
				tasks: 'css-core'
			},
			html: {
				files: [ '*.html']
			},
			markdown: {
				files: [ '*.md' ]
			},
			options: {
				livereload: true
			}
		}

	});

	// Dependencies
	grunt.loadNpmTasks( 'grunt-contrib-qunit' );
	grunt.loadNpmTasks( 'grunt-contrib-jshint' );
	grunt.loadNpmTasks( 'grunt-contrib-cssmin' );
	grunt.loadNpmTasks( 'grunt-contrib-uglify' );
	grunt.loadNpmTasks( 'grunt-contrib-watch' );
	grunt.loadNpmTasks( 'grunt-sass' );
	grunt.loadNpmTasks( 'grunt-contrib-connect' );
	grunt.loadNpmTasks( 'grunt-autoprefixer' );
	grunt.loadNpmTasks( 'grunt-zip' );

	// Default task
	grunt.registerTask( 'default', [ 'css', 'js' ] );

	// JS task
	grunt.registerTask( 'js', [ 'jshint', 'uglify', 'qunit' ] );

	// Theme CSS
	grunt.registerTask( 'css-themes', [ 'sass:themes' ] );

	// Core framework CSS
	grunt.registerTask( 'css-core', [ 'sass:core', 'autoprefixer', 'cssmin' ] );

	// All CSS
	grunt.registerTask( 'css', [ 'sass', 'autoprefixer', 'cssmin' ] );

	// Package presentation to archive
	grunt.registerTask( 'package', [ 'default', 'zip' ] );

	// Serve presentation locally
	grunt.registerTask( 'serve', [ 'connect', 'watch' ] );

	// Run tests
	grunt.registerTask( 'test', [ 'jshint', 'qunit' ] );

};
<commit_msg>restored process.env.VCAP_APP_PORT to resolve bluemix startup<commit_after>/* global module:false */
module.exports = function(grunt) {
	var port = grunt.option('port') || 8000;
	var base = grunt.option('base') || '.';

	// Project configuration
	grunt.initConfig({
		pkg: grunt.file.readJSON('package.json'),
		meta: {
			banner:
				'/*!\n' +
				' * reveal.js <%= pkg.version %> (<%= grunt.template.today("yyyy-mm-dd, HH:MM") %>)\n' +
				' * http://lab.hakim.se/reveal-js\n' +
				' * MIT licensed\n' +
				' *\n' +
				' * Copyright (C) 2016 Hakim El Hattab, http://hakim.se\n' +
				' */'
		},

		qunit: {
			files: [ 'test/*.html' ]
		},

		uglify: {
			options: {
				banner: '<%= meta.banner %>\n'
			},
			build: {
				src: 'js/reveal.js',
				dest: 'js/reveal.min.js'
			}
		},

		sass: {
			core: {
				files: {
					'css/reveal.css': 'css/reveal.scss',
				}
			},
			themes: {
				files: [
					{
						expand: true,
						cwd: 'css/theme/source',
						src: ['*.scss'],
						dest: 'css/theme',
						ext: '.css'
					}
				]
			}
		},

		autoprefixer: {
			dist: {
				src: 'css/reveal.css'
			}
		},

		cssmin: {
			compress: {
				files: {
					'css/reveal.min.css': [ 'css/reveal.css' ]
				}
			}
		},

		jshint: {
			options: {
				curly: false,
				eqeqeq: true,
				immed: true,
				latedef: true,
				newcap: true,
				noarg: true,
				sub: true,
				undef: true,
				eqnull: true,
				browser: true,
				expr: true,
				globals: {
					head: false,
					module: false,
					console: false,
					unescape: false,
					define: false,
					exports: false
				}
			},
			files: [ 'Gruntfile.js', 'js/reveal.js' ]
		},

		connect: {
			server: {
				options: {
          			port: process.env.VCAP_APP_PORT || 9026,
					base: base,
					livereload: true,
 		          	keepalive: true,
					open: true
				}
			}
		},

		zip: {
			'reveal-js-presentation.zip': [
				'index.html',
				'css/**',
				'js/**',
				'lib/**',
				'images/**',
				'plugin/**',
				'**.md'
			]
		},

		watch: {
			js: {
				files: [ 'Gruntfile.js', 'js/reveal.js' ],
				tasks: 'js'
			},
			theme: {
				files: [ 'css/theme/source/*.scss', 'css/theme/template/*.scss' ],
				tasks: 'css-themes'
			},
			css: {
				files: [ 'css/reveal.scss' ],
				tasks: 'css-core'
			},
			html: {
				files: [ '*.html']
			},
			markdown: {
				files: [ '*.md' ]
			},
			options: {
				livereload: true
			}
		}

	});

	// Dependencies
	grunt.loadNpmTasks( 'grunt-contrib-qunit' );
	grunt.loadNpmTasks( 'grunt-contrib-jshint' );
	grunt.loadNpmTasks( 'grunt-contrib-cssmin' );
	grunt.loadNpmTasks( 'grunt-contrib-uglify' );
	grunt.loadNpmTasks( 'grunt-contrib-watch' );
	grunt.loadNpmTasks( 'grunt-sass' );
	grunt.loadNpmTasks( 'grunt-contrib-connect' );
	grunt.loadNpmTasks( 'grunt-autoprefixer' );
	grunt.loadNpmTasks( 'grunt-zip' );

	// Default task
	grunt.registerTask( 'default', [ 'css', 'js' ] );

	// JS task
	grunt.registerTask( 'js', [ 'jshint', 'uglify', 'qunit' ] );

	// Theme CSS
	grunt.registerTask( 'css-themes', [ 'sass:themes' ] );

	// Core framework CSS
	grunt.registerTask( 'css-core', [ 'sass:core', 'autoprefixer', 'cssmin' ] );

	// All CSS
	grunt.registerTask( 'css', [ 'sass', 'autoprefixer', 'cssmin' ] );

	// Package presentation to archive
	grunt.registerTask( 'package', [ 'default', 'zip' ] );

	// Serve presentation locally
	grunt.registerTask( 'serve', [ 'connect', 'watch' ] );

	// Run tests
	grunt.registerTask( 'test', [ 'jshint', 'qunit' ] );

};
<|endoftext|>