<commit_before>const mdxToJsx = require('@mdx-js/mdx/mdx-hast-to-jsx');
const parser = require('@babel/parser');
const generate = require('@babel/generator').default;
const camelCase = require('lodash/camelCase');
const jsStringEscape = require('js-string-escape');
const { toId, storyNameFromExport } = require('@storybook/router/utils');

// Generate the MDX as is, but append named exports for every
// story in the contents

const STORY_REGEX = /^<Story[\s>]/;
const PREVIEW_REGEX = /^<Preview[\s>]/;
const META_REGEX = /^<Meta[\s>]/;
const RESERVED = /^(?:do|if|in|for|let|new|try|var|case|else|enum|eval|false|null|this|true|void|with|await|break|catch|class|const|super|throw|while|yield|delete|export|import|public|return|static|switch|typeof|default|extends|finally|package|private|continue|debugger|function|arguments|interface|protected|implements|instanceof)$/;

function getAttr(elt, what) {
  const attr = elt.attributes.find(n => n.name.name === what);
  return attr && attr.value;
}

const isReserved = name => RESERVED.exec(name);
const startsWithNumber = name => /^\d/.exec(name);

const sanitizeName = name => {
  let key = camelCase(name);
  if (startsWithNumber(key)) {
    key = `_${key}`;
  } else if (isReserved(key)) {
    key = `${key}Story`;
  }
  return key;
};

const getStoryKey = (name, counter) => (name ? sanitizeName(name) : `story${counter}`);

function genStoryExport(ast, context) {
  let storyName = getAttr(ast.openingElement, 'name');
  let storyId = getAttr(ast.openingElement, 'id');
  storyName = storyName && storyName.value;
  storyId = storyId && storyId.value;

  if (!storyId && !storyName) {
    throw new Error('Expected a story name or ID attribute');
  }

  // We don't generate exports for story references or the smart "current story"
  if (storyId || !storyName) {
    return null;
  }

  // console.log('genStoryExport', JSON.stringify(ast, null, 2));

  const statements = [];
  const storyKey = getStoryKey(storyName, context.counter);

  let body = ast.children.find(n => n.type !== 'JSXText');
  let storyCode = null;
  let isJsx = false;
  if (!body) {
    // plain text node
    const { code } = generate(ast.children[0], {});
    storyCode = `'${code}'`;
  } else {
    if (body.type === 'JSXExpressionContainer') {
      // FIXME: handle fragments
      body = body.expression;
    } else {
      isJsx = true;
    }
    const { code } = generate(body, {});
    storyCode = code;
  }
  if (isJsx) {
    statements.push(
      `export const ${storyKey} = () => (
        ${storyCode}
      );`
    );
  } else {
    statements.push(`export const ${storyKey} = makeStoryFn(${storyCode});`);
  }
  statements.push(`${storyKey}.story = {};`);

  // always preserve the name, since CSF exports can get modified by displayName
  statements.push(`${storyKey}.story.name = '${storyName}';`);

  let parameters = getAttr(ast.openingElement, 'parameters');
  parameters = parameters && parameters.expression;
  const source = jsStringEscape(storyCode);
  if (parameters) {
    const { code: params } = generate(parameters, {});
    // FIXME: hack in the story's source as a parameter
    statements.push(`${storyKey}.story.parameters = { mdxSource: '${source}', ...${params} };`);
  } else {
    statements.push(`${storyKey}.story.parameters = { mdxSource: '${source}' };`);
  }

  let decorators = getAttr(ast.openingElement, 'decorators');
  decorators = decorators && decorators.expression;
  if (decorators) {
    const { code: decos } = generate(decorators, {});
    statements.push(`${storyKey}.story.decorators = ${decos};`);
  }

  // eslint-disable-next-line no-param-reassign
  context.storyNameToKey[storyName] = storyKey;

  return {
    [storyKey]: statements.join('\n'),
  };
}

function genPreviewExports(ast, context) {
  // console.log('genPreviewExports', JSON.stringify(ast, null, 2));

  const previewExports = {};
  for (let i = 0; i < ast.children.length; i += 1) {
    const child = ast.children[i];
    if (child.type === 'JSXElement' && child.openingElement.name.name === 'Story') {
      const storyExport = genStoryExport(child, context);
      if (storyExport) {
        Object.assign(previewExports, storyExport);
        // eslint-disable-next-line no-param-reassign
        context.counter += 1;
      }
    }
  }
  return previewExports;
}

function genMeta(ast) {
  let title = getAttr(ast.openingElement, 'title');
  let id = getAttr(ast.openingElement, 'id');
  let parameters = getAttr(ast.openingElement, 'parameters');
  let decorators = getAttr(ast.openingElement, 'decorators');
  if (title) {
    if (title.type === 'StringLiteral') {
      title = "'".concat(jsStringEscape(title.value), "'");
    } else {
      try {
        // generate code, so the expression is evaluated by the CSF compiler
        const { code } = generate(title, {});
        // remove the curly brackes at start and end of code
        title = code.replace(/^\{(.+)\}$/, '$1');
      } catch (e) {
        // eat exception if title parsing didnt go well
        // eslint-disable-next-line no-console
        console.warn('Invalid title found');
        title = undefined;
      }
    }
  }
  id = id && `'${id.value}'`;
  if (parameters && parameters.expression) {
    const { code: params } = generate(parameters.expression, {});
    parameters = params;
  }
  if (decorators && decorators.expression) {
    const { code: decos } = generate(decorators.expression, {});
    decorators = decos;
  }
  return {
    title,
    id,
    parameters,
    decorators,
  };
}

function getExports(node, counter) {
  const { value, type } = node;
  if (type === 'jsx') {
    if (STORY_REGEX.exec(value)) {
      // Single story
      const ast = parser.parseExpression(value, { plugins: ['jsx'] });
      const storyExport = genStoryExport(ast, counter);
      return storyExport && { stories: storyExport };
    }
    if (PREVIEW_REGEX.exec(value)) {
      // Preview, possibly containing multiple stories
      const ast = parser.parseExpression(value, { plugins: ['jsx'] });
      return { stories: genPreviewExports(ast, counter) };
    }
    if (META_REGEX.exec(value)) {
      // Preview, possibly containing multiple stories
      const ast = parser.parseExpression(value, { plugins: ['jsx'] });
      return { meta: genMeta(ast) };
    }
  }
  return null;
}

// insert `mdxKind` into the context so that we can know what "kind" we're rendering into
// when we render <Story name="xxx">...</Story>, since this MDX can be attached to any `selectedKind`!
const wrapperJs = `
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = {
  ...(componentMeta.parameters.docs || {}),
  page: () => <AddContext mdxStoryNameToId={mdxStoryNameToId}><MDXContent /></AddContext>,
};
`.trim();

// Use this rather than JSON.stringify because `Meta`'s attributes
// are already valid code strings, so we want to insert them raw
// rather than add an extra set of quotes
function stringifyMeta(meta) {
  let result = '{ ';
  Object.entries(meta).forEach(([key, val]) => {
    if (val) {
      result += `${key}: ${val}, `;
    }
  });
  result += ' }';
  return result;
}

const hasStoryChild = node => {
  if (node.openingElement && node.openingElement.name.name === 'Story') {
    return node;
  }
  if (node.children && node.children.length > 0) {
    return node.children.find(child => hasStoryChild(child));
  }
  return null;
};

function extractExports(node, options) {
  node.children.forEach(child => {
    if (child.type === 'jsx') {
      try {
        const ast = parser.parseExpression(child.value, { plugins: ['jsx'] });
        if (
          ast.openingElement &&
          ast.openingElement.type === 'JSXOpeningElement' &&
          ast.openingElement.name.name === 'Preview' &&
          !hasStoryChild(ast)
        ) {
          const previewAst = ast.openingElement;
          previewAst.attributes.push({
            type: 'JSXAttribute',
            name: {
              type: 'JSXIdentifier',
              name: 'mdxSource',
            },
            value: {
              type: 'StringLiteral',
              value: encodeURI(
                ast.children
                  .map(
                    el =>
                      generate(el, {
                        quotes: 'double',
                      }).code
                  )
                  .join('\n')
              ),
            },
          });
        }
        const { code } = generate(ast, {});
        // eslint-disable-next-line no-param-reassign
        child.value = code;
      } catch {
        /** catch erroneous child.value string where the babel parseExpression makes exception
         * https://github.com/mdx-js/mdx/issues/767
         * eg <button>
         *
hello world

         *
         *    </button>
         * generates error
         * 1. child.value =`<button>\n  <div>hello world</div`
         * 2. child.value =`\n`
         * 3. child.value =`</button>`
         *
         */
      }
    }
  });
  // we're overriding default export
  const defaultJsx = mdxToJsx.toJSX(node, {}, { ...options, skipExport: true });
  const storyExports = [];
  const includeStories = [];
  let metaExport = null;
  const context = {
    counter: 0,
    storyNameToKey: {},
  };
  node.children.forEach(n => {
    const exports = getExports(n, context);
    if (exports) {
      const { stories, meta } = exports;
      if (stories) {
        Object.entries(stories).forEach(([key, story]) => {
          includeStories.push(key);
          storyExports.push(story);
        });
      }
      if (meta) {
        if (metaExport) {
          throw new Error('Meta can only be declared once');
        }
        metaExport = meta;
      }
    }
  });
  if (metaExport) {
    if (!storyExports.length) {
      storyExports.push('export const __page = () => { throw new Error("Docs-only story"); };');
      storyExports.push('__page.story = { parameters: { docsOnly: true } };');
      includeStories.push('__page');
    }
  } else {
    metaExport = {};
  }
  metaExport.includeStories = JSON.stringify(includeStories);

  const { title, id: componentId } = metaExport;
  const mdxStoryNameToId = Object.entries(context.storyNameToKey).reduce(
    (acc, [storyName, storyKey]) => {
      if (title) {
        acc[storyName] = toId(componentId || title, storyNameFromExport(storyKey));
      }
      return acc;
    },
    {}
  );

  const fullJsx = [
    'import { makeStoryFn, AddContext } from "@storybook/addon-docs/blocks";',
    defaultJsx,
    ...storyExports,
    `const componentMeta = ${stringifyMeta(metaExport)};`,
    `const mdxStoryNameToId = ${JSON.stringify(mdxStoryNameToId)};`,
    wrapperJs,
    'export default componentMeta;',
  ].join('\n\n');

  return fullJsx;
}

function createCompiler(mdxOptions) {
  return function compiler(options = {}) {
    this.Compiler = tree => extractExports(tree, options, mdxOptions);
  };
}

module.exports = createCompiler;
<commit_msg>console.warm full pathname<commit_after>const mdxToJsx = require('@mdx-js/mdx/mdx-hast-to-jsx');
const parser = require('@babel/parser');
const generate = require('@babel/generator').default;
const camelCase = require('lodash/camelCase');
const jsStringEscape = require('js-string-escape');
const { toId, storyNameFromExport } = require('@storybook/router/utils');

// Generate the MDX as is, but append named exports for every
// story in the contents

const STORY_REGEX = /^<Story[\s>]/;
const PREVIEW_REGEX = /^<Preview[\s>]/;
const META_REGEX = /^<Meta[\s>]/;
const RESERVED = /^(?:do|if|in|for|let|new|try|var|case|else|enum|eval|false|null|this|true|void|with|await|break|catch|class|const|super|throw|while|yield|delete|export|import|public|return|static|switch|typeof|default|extends|finally|package|private|continue|debugger|function|arguments|interface|protected|implements|instanceof)$/;

function getAttr(elt, what) {
  const attr = elt.attributes.find(n => n.name.name === what);
  return attr && attr.value;
}

const isReserved = name => RESERVED.exec(name);
const startsWithNumber = name => /^\d/.exec(name);

const sanitizeName = name => {
  let key = camelCase(name);
  if (startsWithNumber(key)) {
    key = `_${key}`;
  } else if (isReserved(key)) {
    key = `${key}Story`;
  }
  return key;
};

const getStoryKey = (name, counter) => (name ? sanitizeName(name) : `story${counter}`);

function genStoryExport(ast, context) {
  let storyName = getAttr(ast.openingElement, 'name');
  let storyId = getAttr(ast.openingElement, 'id');
  storyName = storyName && storyName.value;
  storyId = storyId && storyId.value;

  if (!storyId && !storyName) {
    throw new Error('Expected a story name or ID attribute');
  }

  // We don't generate exports for story references or the smart "current story"
  if (storyId || !storyName) {
    return null;
  }

  // console.log('genStoryExport', JSON.stringify(ast, null, 2));

  const statements = [];
  const storyKey = getStoryKey(storyName, context.counter);

  let body = ast.children.find(n => n.type !== 'JSXText');
  let storyCode = null;
  let isJsx = false;
  if (!body) {
    // plain text node
    const { code } = generate(ast.children[0], {});
    storyCode = `'${code}'`;
  } else {
    if (body.type === 'JSXExpressionContainer') {
      // FIXME: handle fragments
      body = body.expression;
    } else {
      isJsx = true;
    }
    const { code } = generate(body, {});
    storyCode = code;
  }
  if (isJsx) {
    statements.push(
      `export const ${storyKey} = () => (
        ${storyCode}
      );`
    );
  } else {
    statements.push(`export const ${storyKey} = makeStoryFn(${storyCode});`);
  }
  statements.push(`${storyKey}.story = {};`);

  // always preserve the name, since CSF exports can get modified by displayName
  statements.push(`${storyKey}.story.name = '${storyName}';`);

  let parameters = getAttr(ast.openingElement, 'parameters');
  parameters = parameters && parameters.expression;
  const source = jsStringEscape(storyCode);
  if (parameters) {
    const { code: params } = generate(parameters, {});
    // FIXME: hack in the story's source as a parameter
    statements.push(`${storyKey}.story.parameters = { mdxSource: '${source}', ...${params} };`);
  } else {
    statements.push(`${storyKey}.story.parameters = { mdxSource: '${source}' };`);
  }

  let decorators = getAttr(ast.openingElement, 'decorators');
  decorators = decorators && decorators.expression;
  if (decorators) {
    const { code: decos } = generate(decorators, {});
    statements.push(`${storyKey}.story.decorators = ${decos};`);
  }

  // eslint-disable-next-line no-param-reassign
  context.storyNameToKey[storyName] = storyKey;

  return {
    [storyKey]: statements.join('\n'),
  };
}

function genPreviewExports(ast, context) {
  // console.log('genPreviewExports', JSON.stringify(ast, null, 2));

  const previewExports = {};
  for (let i = 0; i < ast.children.length; i += 1) {
    const child = ast.children[i];
    if (child.type === 'JSXElement' && child.openingElement.name.name === 'Story') {
      const storyExport = genStoryExport(child, context);
      if (storyExport) {
        Object.assign(previewExports, storyExport);
        // eslint-disable-next-line no-param-reassign
        context.counter += 1;
      }
    }
  }
  return previewExports;
}

function genMeta(ast, options) {
  let title = getAttr(ast.openingElement, 'title');
  let id = getAttr(ast.openingElement, 'id');
  let parameters = getAttr(ast.openingElement, 'parameters');
  let decorators = getAttr(ast.openingElement, 'decorators');
  if (title) {
    if (title.type === 'StringLiteral') {
      title = "'".concat(jsStringEscape(title.value), "'");
    } else {
      try {
        // generate code, so the expression is evaluated by the CSF compiler
        const { code } = generate(title, {});
        // remove the curly brackes at start and end of code
        title = code.replace(/^\{(.+)\}$/, '$1');
      } catch (e) {
        // eat exception if title parsing didnt go well
        // eslint-disable-next-line no-console
        console.warn(`Invalid title: ${options.filepath}`);
        title = undefined;
      }
    }
  }
  id = id && `'${id.value}'`;
  if (parameters && parameters.expression) {
    const { code: params } = generate(parameters.expression, {});
    parameters = params;
  }
  if (decorators && decorators.expression) {
    const { code: decos } = generate(decorators.expression, {});
    decorators = decos;
  }
  return {
    title,
    id,
    parameters,
    decorators,
  };
}

function getExports(node, counter, options) {
  const { value, type } = node;
  if (type === 'jsx') {
    if (STORY_REGEX.exec(value)) {
      // Single story
      const ast = parser.parseExpression(value, { plugins: ['jsx'] });
      const storyExport = genStoryExport(ast, counter);
      return storyExport && { stories: storyExport };
    }
    if (PREVIEW_REGEX.exec(value)) {
      // Preview, possibly containing multiple stories
      const ast = parser.parseExpression(value, { plugins: ['jsx'] });
      return { stories: genPreviewExports(ast, counter) };
    }
    if (META_REGEX.exec(value)) {
      // Preview, possibly containing multiple stories
      const ast = parser.parseExpression(value, { plugins: ['jsx'] });
      return { meta: genMeta(ast, options) };
    }
  }
  return null;
}

// insert `mdxKind` into the context so that we can know what "kind" we're rendering into
// when we render <Story name="xxx">...</Story>, since this MDX can be attached to any `selectedKind`!
const wrapperJs = `
componentMeta.parameters = componentMeta.parameters || {};
componentMeta.parameters.docs = {
  ...(componentMeta.parameters.docs || {}),
  page: () => <AddContext mdxStoryNameToId={mdxStoryNameToId}><MDXContent /></AddContext>,
};
`.trim();

// Use this rather than JSON.stringify because `Meta`'s attributes
// are already valid code strings, so we want to insert them raw
// rather than add an extra set of quotes
function stringifyMeta(meta) {
  let result = '{ ';
  Object.entries(meta).forEach(([key, val]) => {
    if (val) {
      result += `${key}: ${val}, `;
    }
  });
  result += ' }';
  return result;
}

const hasStoryChild = node => {
  if (node.openingElement && node.openingElement.name.name === 'Story') {
    return node;
  }
  if (node.children && node.children.length > 0) {
    return node.children.find(child => hasStoryChild(child));
  }
  return null;
};

function extractExports(node, options) {
  node.children.forEach(child => {
    if (child.type === 'jsx') {
      try {
        const ast = parser.parseExpression(child.value, { plugins: ['jsx'] });
        if (
          ast.openingElement &&
          ast.openingElement.type === 'JSXOpeningElement' &&
          ast.openingElement.name.name === 'Preview' &&
          !hasStoryChild(ast)
        ) {
          const previewAst = ast.openingElement;
          previewAst.attributes.push({
            type: 'JSXAttribute',
            name: {
              type: 'JSXIdentifier',
              name: 'mdxSource',
            },
            value: {
              type: 'StringLiteral',
              value: encodeURI(
                ast.children
                  .map(
                    el =>
                      generate(el, {
                        quotes: 'double',
                      }).code
                  )
                  .join('\n')
              ),
            },
          });
        }
        const { code } = generate(ast, {});
        // eslint-disable-next-line no-param-reassign
        child.value = code;
      } catch {
        /** catch erroneous child.value string where the babel parseExpression makes exception
         * https://github.com/mdx-js/mdx/issues/767
         * eg <button>
         *
hello world

         *
         *    </button>
         * generates error
         * 1. child.value =`<button>\n  <div>hello world</div`
         * 2. child.value =`\n`
         * 3. child.value =`</button>`
         *
         */
      }
    }
  });
  // we're overriding default export
  const defaultJsx = mdxToJsx.toJSX(node, {}, { ...options, skipExport: true });
  const storyExports = [];
  const includeStories = [];
  let metaExport = null;
  const context = {
    counter: 0,
    storyNameToKey: {},
  };
  node.children.forEach(n => {
    const exports = getExports(n, context, options);
    if (exports) {
      const { stories, meta } = exports;
      if (stories) {
        Object.entries(stories).forEach(([key, story]) => {
          includeStories.push(key);
          storyExports.push(story);
        });
      }
      if (meta) {
        if (metaExport) {
          throw new Error('Meta can only be declared once');
        }
        metaExport = meta;
      }
    }
  });
  if (metaExport) {
    if (!storyExports.length) {
      storyExports.push('export const __page = () => { throw new Error("Docs-only story"); };');
      storyExports.push('__page.story = { parameters: { docsOnly: true } };');
      includeStories.push('__page');
    }
  } else {
    metaExport = {};
  }
  metaExport.includeStories = JSON.stringify(includeStories);

  const { title, id: componentId } = metaExport;
  const mdxStoryNameToId = Object.entries(context.storyNameToKey).reduce(
    (acc, [storyName, storyKey]) => {
      if (title) {
        acc[storyName] = toId(componentId || title, storyNameFromExport(storyKey));
      }
      return acc;
    },
    {}
  );

  const fullJsx = [
    'import { makeStoryFn, AddContext } from "@storybook/addon-docs/blocks";',
    defaultJsx,
    ...storyExports,
    `const componentMeta = ${stringifyMeta(metaExport)};`,
    `const mdxStoryNameToId = ${JSON.stringify(mdxStoryNameToId)};`,
    wrapperJs,
    'export default componentMeta;',
  ].join('\n\n');

  return fullJsx;
}

function createCompiler(mdxOptions) {
  return function compiler(options = {}) {
    this.Compiler = tree => extractExports(tree, options, mdxOptions);
  };
}

module.exports = createCompiler;
<|endoftext|>


<commit_before>odoo.define('web.core', function (require) {
"use strict";

var Class = require('web.Class');
var mixins = require('web.mixins');
var Registry = require('web.Registry');
var translation = require('web.translation');

var qweb = require('qweb');
var _ = require('_');
var $ = require('$');

var debug = $.deparam($.param.querystring()).debug !== undefined;

var _t = translation._t;
var _lt = translation._lt;

/**
 * Event Bus used to bind events scoped in the current instance
 */
var Bus = Class.extend(mixins.EventDispatcherMixin, {
    init: function() {
        mixins.EventDispatcherMixin.init.call(this, parent);
        var self = this;
        // TODO fme: allow user to bind keys for some global actions.
        //           check gtk bindings
        // http://unixpapa.com/js/key.html
        _.each('click,dblclick,keydown,keypress,keyup'.split(','), function(evtype) {
            $('html').on(evtype, function(ev) {
                self.trigger(evtype, ev);
            });
        });
        _.each('resize,scroll'.split(','), function(evtype) {
            $(window).on(evtype, function(ev) {
                self.trigger(evtype, ev);
            });
        });
    },
});

// Underscore customization
//-------------------------------------------------------------------------
_.str.toBoolElse = function (str, elseValues, trueValues, falseValues) {
    var ret = _.str.toBool(str, trueValues, falseValues);
    if (_.isUndefined(ret)) {
        return elseValues;
    }
    return ret;
};

// IE patch
//-------------------------------------------------------------------------
if (typeof(console) === "undefined") {
    // Even IE9 only exposes console object if debug window opened
    window.console = {};
    ('log error debug info warn assert clear dir dirxml trace group'
        + ' groupCollapsed groupEnd time timeEnd profile profileEnd count'
        + ' exception').split(/\s+/).forEach(function(property) {
            console[property] = _.identity;
    });
}

/**
    Some hack to make placeholders work in ie9.
*/
if (!('placeholder' in document.createElement('input'))) {
    document.addEventListener("DOMNodeInserted",function(event){
        var nodename =  event.target.nodeName.toLowerCase();
        if ( nodename === "input" || nodename == "textarea" ) {
            $(event.target).placeholder();
        }
    });
}

/**
 * Lazy translation function, only performs the translation when actually
 * printed (e.g. inserted into a template)
 *
 * Useful when defining translatable strings in code evaluated before the
 * translation database is loaded, as class attributes or at the top-level of
 * an OpenERP Web module
 *
 * @param {String} s string to translate
 * @returns {Object} lazy translation object
 */
qweb.debug = debug;
_.extend(qweb.default_dict, {
    '__debug__': debug,
    'moment': function(date) { return new moment(date); },
});

qweb.preprocess_node = function() {
    // Note that 'this' is the Qweb Node
    switch (this.node.nodeType) {
        case Node.TEXT_NODE:
        case Node.CDATA_SECTION_NODE:
            // Text and CDATAs
            var translation = this.node.parentNode.attributes['t-translation'];
            if (translation && translation.value === 'off') {
                return;
            }
            var match = /^(\s*)([\s\S]+?)(\s*)$/.exec(this.node.data);
            if (match) {
                this.node.data = match[1] + _t(match[2]) + match[3];
            }
            break;
        case Node.ELEMENT_NODE:
            // Element
            var attr, attrs = ['label', 'title', 'alt', 'placeholder'];
            while ((attr = attrs.pop())) {
                if (this.attributes[attr]) {
                    this.attributes[attr] = _t(this.attributes[attr]);
                }
            }
    }
};

/** Setup jQuery timeago */
/*
 * Strings in timeago are "composed" with prefixes, words and suffixes. This
 * makes their detection by our translating system impossible. Use all literal
 * strings we're using with a translation mark here so the extractor can do its
 * job.
 */
{
    _t('less than a minute ago');
    _t('about a minute ago');
    _t('%d minutes ago');
    _t('about an hour ago');
    _t('%d hours ago');
    _t('a day ago');
    _t('%d days ago');
    _t('about a month ago');
    _t('%d months ago');
    _t('about a year ago');
    _t('%d years ago');
}

$.async_when = function() {
    var async = false;
    var def = $.Deferred();
    $.when.apply($, arguments).done(function() {
        var args = arguments;
        var action = function() {
            def.resolve.apply(def, args);
        };
        if (async)
            action();
        else
            setTimeout(action, 0);
    }).fail(function() {
        var args = arguments;
        var action = function() {
            def.reject.apply(def, args);
        };
        if (async)
            action();
        else
            setTimeout(action, 0);
    });
    async = true;
    return def;
};

return {
    debug: debug,
    qweb: qweb,

    // core classes and functions
    Class: Class,
    mixins: mixins,
    bus: new Bus (),
    _t: _t,
    _lt: _lt,

    // registries
    view_registry: new Registry(),
    crash_registry: new Registry(),
    action_registry : new Registry(),
    form_widget_registry: new Registry(),
    form_tag_registry: new Registry(),
    form_custom_registry: new Registry(),
    list_widget_registry: new Registry(),
    search_widgets_registry: new Registry(),
    search_filters_registry: new Registry(),
};

});
<commit_msg>[ADD] Web: core.js: export Bus<commit_after>odoo.define('web.core', function (require) {
"use strict";

var Class = require('web.Class');
var mixins = require('web.mixins');
var Registry = require('web.Registry');
var translation = require('web.translation');

var qweb = require('qweb');
var _ = require('_');
var $ = require('$');

var debug = $.deparam($.param.querystring()).debug !== undefined;

var _t = translation._t;
var _lt = translation._lt;

/**
 * Event Bus used to bind events scoped in the current instance
 */
var Bus = Class.extend(mixins.EventDispatcherMixin, {
    init: function() {
        mixins.EventDispatcherMixin.init.call(this, parent);
        var self = this;
        // TODO fme: allow user to bind keys for some global actions.
        //           check gtk bindings
        // http://unixpapa.com/js/key.html
        _.each('click,dblclick,keydown,keypress,keyup'.split(','), function(evtype) {
            $('html').on(evtype, function(ev) {
                self.trigger(evtype, ev);
            });
        });
        _.each('resize,scroll'.split(','), function(evtype) {
            $(window).on(evtype, function(ev) {
                self.trigger(evtype, ev);
            });
        });
    },
});

// Underscore customization
//-------------------------------------------------------------------------
_.str.toBoolElse = function (str, elseValues, trueValues, falseValues) {
    var ret = _.str.toBool(str, trueValues, falseValues);
    if (_.isUndefined(ret)) {
        return elseValues;
    }
    return ret;
};

// IE patch
//-------------------------------------------------------------------------
if (typeof(console) === "undefined") {
    // Even IE9 only exposes console object if debug window opened
    window.console = {};
    ('log error debug info warn assert clear dir dirxml trace group'
        + ' groupCollapsed groupEnd time timeEnd profile profileEnd count'
        + ' exception').split(/\s+/).forEach(function(property) {
            console[property] = _.identity;
    });
}

/**
    Some hack to make placeholders work in ie9.
*/
if (!('placeholder' in document.createElement('input'))) {
    document.addEventListener("DOMNodeInserted",function(event){
        var nodename =  event.target.nodeName.toLowerCase();
        if ( nodename === "input" || nodename == "textarea" ) {
            $(event.target).placeholder();
        }
    });
}

/**
 * Lazy translation function, only performs the translation when actually
 * printed (e.g. inserted into a template)
 *
 * Useful when defining translatable strings in code evaluated before the
 * translation database is loaded, as class attributes or at the top-level of
 * an OpenERP Web module
 *
 * @param {String} s string to translate
 * @returns {Object} lazy translation object
 */
qweb.debug = debug;
_.extend(qweb.default_dict, {
    '__debug__': debug,
    'moment': function(date) { return new moment(date); },
});

qweb.preprocess_node = function() {
    // Note that 'this' is the Qweb Node
    switch (this.node.nodeType) {
        case Node.TEXT_NODE:
        case Node.CDATA_SECTION_NODE:
            // Text and CDATAs
            var translation = this.node.parentNode.attributes['t-translation'];
            if (translation && translation.value === 'off') {
                return;
            }
            var match = /^(\s*)([\s\S]+?)(\s*)$/.exec(this.node.data);
            if (match) {
                this.node.data = match[1] + _t(match[2]) + match[3];
            }
            break;
        case Node.ELEMENT_NODE:
            // Element
            var attr, attrs = ['label', 'title', 'alt', 'placeholder'];
            while ((attr = attrs.pop())) {
                if (this.attributes[attr]) {
                    this.attributes[attr] = _t(this.attributes[attr]);
                }
            }
    }
};

/** Setup jQuery timeago */
/*
 * Strings in timeago are "composed" with prefixes, words and suffixes. This
 * makes their detection by our translating system impossible. Use all literal
 * strings we're using with a translation mark here so the extractor can do its
 * job.
 */
{
    _t('less than a minute ago');
    _t('about a minute ago');
    _t('%d minutes ago');
    _t('about an hour ago');
    _t('%d hours ago');
    _t('a day ago');
    _t('%d days ago');
    _t('about a month ago');
    _t('%d months ago');
    _t('about a year ago');
    _t('%d years ago');
}

$.async_when = function() {
    var async = false;
    var def = $.Deferred();
    $.when.apply($, arguments).done(function() {
        var args = arguments;
        var action = function() {
            def.resolve.apply(def, args);
        };
        if (async)
            action();
        else
            setTimeout(action, 0);
    }).fail(function() {
        var args = arguments;
        var action = function() {
            def.reject.apply(def, args);
        };
        if (async)
            action();
        else
            setTimeout(action, 0);
    });
    async = true;
    return def;
};

return {
    debug: debug,
    qweb: qweb,

    // core classes and functions
    Class: Class,
    Bus: Bus,
    mixins: mixins,
    bus: new Bus (),
    _t: _t,
    _lt: _lt,

    // registries
    view_registry: new Registry(),
    crash_registry: new Registry(),
    action_registry : new Registry(),
    form_widget_registry: new Registry(),
    form_tag_registry: new Registry(),
    form_custom_registry: new Registry(),
    list_widget_registry: new Registry(),
    search_widgets_registry: new Registry(),
    search_filters_registry: new Registry(),
};

});
<|endoftext|>


<commit_before>'use strict';
'require fs';
'require rpc';

var callSystemBoard = rpc.declare({
	object: 'system',
	method: 'board'
});

var callSystemInfo = rpc.declare({
	object: 'system',
	method: 'info'
});

return L.Class.extend({
	title: _('System'),

	load: function() {
		return Promise.all([
			L.resolveDefault(callSystemBoard(), {}),
			L.resolveDefault(callSystemInfo(), {}),
			fs.lines('/usr/lib/lua/luci/version.lua')
		]);
	},

	render: function(data) {
		var boardinfo   = data[0],
		    systeminfo  = data[1],
		    luciversion = data[2];

		luciversion = luciversion.filter(function(l) {
			return l.match(/^\s*(luciname|luciversion)\s*=/);
		}).map(function(l) {
			return l.replace(/^\s*\w+\s*=\s*['"]([^'"]+)['"].*$/, '$1');
		}).join(' ');

		var fields = [
			_('Hostname'),         boardinfo.hostname,
			_('Model'),            boardinfo.model,
			_('Architecture'),     boardinfo.system,
			_('Firmware Version'), (L.isObject(boardinfo.release) ? boardinfo.release.description + ' / ' : '') + (luciversion || ''),
			_('Kernel Version'),   boardinfo.kernel,
			_('Local Time'),       systeminfo.localtime ? (new Date(systeminfo.localtime * 1000)).toLocaleString() : null,
			_('Uptime'),           systeminfo.uptime ? '%t'.format(systeminfo.uptime) : null,
			_('Load Average'),     Array.isArray(systeminfo.load) ? '%.2f, %.2f, %.2f'.format(
				systeminfo.load[0] / 65535.0,
				systeminfo.load[1] / 65535.0,
				systeminfo.load[2] / 65535.0
			) : null
		];

		var table = E('div', { 'class': 'table' });

		for (var i = 0; i < fields.length; i += 2) {
			table.appendChild(E('div', { 'class': 'tr' }, [
				E('div', { 'class': 'td left', 'width': '33%' }, [ fields[i] ]),
				E('div', { 'class': 'td left' }, [ (fields[i + 1] != null) ? fields[i + 1] : '?' ])
			]));
		}

		return table;
	}
});
<commit_msg>luci-mod-status: 10_system.js: fix local time display<commit_after>'use strict';
'require fs';
'require rpc';

var callSystemBoard = rpc.declare({
	object: 'system',
	method: 'board'
});

var callSystemInfo = rpc.declare({
	object: 'system',
	method: 'info'
});

return L.Class.extend({
	title: _('System'),

	load: function() {
		return Promise.all([
			L.resolveDefault(callSystemBoard(), {}),
			L.resolveDefault(callSystemInfo(), {}),
			fs.lines('/usr/lib/lua/luci/version.lua')
		]);
	},

	render: function(data) {
		var boardinfo   = data[0],
		    systeminfo  = data[1],
		    luciversion = data[2];

		luciversion = luciversion.filter(function(l) {
			return l.match(/^\s*(luciname|luciversion)\s*=/);
		}).map(function(l) {
			return l.replace(/^\s*\w+\s*=\s*['"]([^'"]+)['"].*$/, '$1');
		}).join(' ');

		var datestr = null;

		if (systeminfo.localtime) {
			var date = new Date(systeminfo.localtime * 1000);

			datestr = '%04d-%02d-%02d %02d:%02d:%02d'.format(
				date.getUTCFullYear(),
				date.getUTCMonth() + 1,
				date.getUTCDate(),
				date.getUTCHours(),
				date.getUTCMinutes(),
				date.getUTCSeconds()
			);
		}

		var fields = [
			_('Hostname'),         boardinfo.hostname,
			_('Model'),            boardinfo.model,
			_('Architecture'),     boardinfo.system,
			_('Firmware Version'), (L.isObject(boardinfo.release) ? boardinfo.release.description + ' / ' : '') + (luciversion || ''),
			_('Kernel Version'),   boardinfo.kernel,
			_('Local Time'),       datestr,
			_('Uptime'),           systeminfo.uptime ? '%t'.format(systeminfo.uptime) : null,
			_('Load Average'),     Array.isArray(systeminfo.load) ? '%.2f, %.2f, %.2f'.format(
				systeminfo.load[0] / 65535.0,
				systeminfo.load[1] / 65535.0,
				systeminfo.load[2] / 65535.0
			) : null
		];

		var table = E('div', { 'class': 'table' });

		for (var i = 0; i < fields.length; i += 2) {
			table.appendChild(E('div', { 'class': 'tr' }, [
				E('div', { 'class': 'td left', 'width': '33%' }, [ fields[i] ]),
				E('div', { 'class': 'td left' }, [ (fields[i + 1] != null) ? fields[i + 1] : '?' ])
			]));
		}

		return table;
	}
});
<|endoftext|>


<commit_before>import axios from "@agir/lib/utils/axios";

const bulkUpdateActivityStatusEndpoint = "/api/activity/update-status/";
const activityEndpoint = "/api/activity/:id/";

export const activityStatus = {
  STATUS_UNDISPLAYED: "U",
  STATUS_DISPLAYED: "S",
  STATUS_INTERACTED: "I",
};

export const requiredActivityTypes = [
  "waiting-payment",
  "group-invitation",
  "new-member",
  "waiting-location-group",
  "group-coorganization-invite",
  "waiting-location-event",
];

export const parseActivities = (data, dismissed = []) => {
  const parsedActivities = {
    required: [],
    unrequired: [],
  };
  if (Array.isArray(data) && data.length > 0) {
    data.forEach((activity) => {
      if (dismissed.includes(activity.id)) {
        return;
      }
      if (requiredActivityTypes.includes(activity.type)) {
        parsedActivities.required.push(activity);
      } else {
        parsedActivities.unrequired.push(activity);
      }
    });
  }
  return parsedActivities;
};

export const getUnread = (data) => {
  return Array.isArray(data)
    ? data.filter(
        (activity) => activity.status === activityStatus.STATUS_UNDISPLAYED
      )
    : [];
};

export const getUnreadCount = (data) => {
  return getUnread(data).length;
};

export const dismissActivity = async (
  id,
  status = activityStatus.STATUS_INTERACTED
) => {
  let result = false;
  if (!id) {
    return result;
  }
  const url = activityEndpoint.replace(":id", id);
  const data = {
    status,
  };
  let res = null;
  try {
    res = await axios.put(url, data);
    result = !!res && res.status === 200;
  } catch (e) {
    console.log(e);
    result = false;
  }

  return result;
};

export const setActivitiesAsRead = async (ids = []) => {
  let result = false;
  if (!Array.isArray(ids) || ids.length === 0) {
    return result;
  }
  const url = bulkUpdateActivityStatusEndpoint;
  const data = {
    status: activityStatus.STATUS_DISPLAYED,
    ids,
  };
  let res = null;
  try {
    res = await axios.post(url, data);
    result = !!res && res.status === 200;
  } catch (e) {
    console.log(e);
    result = false;
  }

  return result;
};
<commit_msg>feat: update activity bulk update endpoint URL<commit_after>import axios from "@agir/lib/utils/axios";

const bulkUpdateActivityStatusEndpoint = "/api/activity/bulk/update-status/";
const activityEndpoint = "/api/activity/:id/";

export const activityStatus = {
  STATUS_UNDISPLAYED: "U",
  STATUS_DISPLAYED: "S",
  STATUS_INTERACTED: "I",
};

export const requiredActivityTypes = [
  "waiting-payment",
  "group-invitation",
  "new-member",
  "waiting-location-group",
  "group-coorganization-invite",
  "waiting-location-event",
];

export const parseActivities = (data, dismissed = []) => {
  const parsedActivities = {
    required: [],
    unrequired: [],
  };
  if (Array.isArray(data) && data.length > 0) {
    data.forEach((activity) => {
      if (dismissed.includes(activity.id)) {
        return;
      }
      if (requiredActivityTypes.includes(activity.type)) {
        parsedActivities.required.push(activity);
      } else {
        parsedActivities.unrequired.push(activity);
      }
    });
  }
  return parsedActivities;
};

export const getUnread = (data) => {
  return Array.isArray(data)
    ? data.filter(
        (activity) => activity.status === activityStatus.STATUS_UNDISPLAYED
      )
    : [];
};

export const getUnreadCount = (data) => {
  return getUnread(data).length;
};

export const dismissActivity = async (
  id,
  status = activityStatus.STATUS_INTERACTED
) => {
  let result = false;
  if (!id) {
    return result;
  }
  const url = activityEndpoint.replace(":id", id);
  const data = {
    status,
  };
  let res = null;
  try {
    res = await axios.put(url, data);
    result = !!res && res.status === 200;
  } catch (e) {
    console.log(e);
    result = false;
  }

  return result;
};

export const setActivitiesAsRead = async (ids = []) => {
  let result = false;
  if (!Array.isArray(ids) || ids.length === 0) {
    return result;
  }
  const url = bulkUpdateActivityStatusEndpoint;
  const data = {
    status: activityStatus.STATUS_DISPLAYED,
    ids,
  };
  let res = null;
  try {
    res = await axios.post(url, data);
    result = !!res && res.status === 200;
  } catch (e) {
    console.log(e);
    result = false;
  }

  return result;
};
<|endoftext|>


<commit_before>(function () {

	'use strict';

	angular.module('tweet-pa-more.controllers')
           .controller('TweetController', TweetController);

    function TweetController($scope) {

        $scope.posts = [];
        $scope.newPost = { created_by: '', message: '', created_at: '' };

        // Function Definitions
        // ---------------------

        /**
         * Submits the tweet.
         */
        $scope.PostTweet = function () {

            $scope.newPost.created_at = Date.now();
            $scope.posts.push($scope.newPost);

            // Reset the scope data, so that we can re-use it again
            $scope.newPost = {
                created_by: '',
                message: '',
                created_at: ''
            };

        };

    }
})();<commit_msg>Added temporary bypass checking<commit_after>(function () {

	'use strict';

	angular.module('tweet-pa-more.controllers')
           .controller('TweetController', TweetController);

    function TweetController($scope) {

        $scope.posts = [];
        $scope.newPost = { created_by: '', message: '', created_at: '' };

        // Function Definitions
        // ---------------------

        /**
         * Submits the tweet.
         */
        $scope.PostTweet = function () {

            if ( ! $scope.newPost.message.length)
                return;

            $scope.newPost.created_at = Date.now();
            $scope.posts.push($scope.newPost);

            // Reset the scope data, so that we can re-use it again
            $scope.newPost = {
                created_by: '',
                message: '',
                created_at: ''
            };

        };

    }
})();<|endoftext|>


<commit_before>/**
 * Creates an instance of a PusherChatWidget, binds to a chat channel on the pusher instance and
 * and creates the UI for the chat widget.
 *
 * @param {Pusher} pusher The Pusher object used for the chat widget.
 * @param {Map} options A hash of key value options for the widget.
 */
// $(function(){
//   if($('span').is('.hive-chat')){

function PusherChatWidget(pusher, options) {
  PusherChatWidget.instances.push(this);
  var self = this;

  this._pusher = pusher;
  this._autoScroll = true;

  options = options || {};
  this.settings = $.extend({
    maxItems: 500, // max items to show in the UI. Items beyond this limit will be removed as new ones come in.

    // EndPoint changes based on if we are looking at Heroku or LocalHost due to the difference in length of the base URL
    chatEndPoint: document.location.href.slice(28) + '/chat', // the end point where chat messages should be sanitized and then triggered
    channelName: document.location.href, // the name of the channel the chat will take place on
    appendTo: $('#chat-window'), // A jQuery selector or object. Defines where the element should be appended to
    debug: true
  }, options);

  if(this.settings.debug && !Pusher.log) {
    Pusher.log = function(msg) {
      if(console && console.log) {
        console.log(msg);
      }
    }
  }

  // remove any unsupported characters from the chat channel name
  // see: http://pusher.com/docs/client_api_guide/client_channels#naming-channels
  this.settings.channelName = PusherChatWidget.getValidChannelName(this.settings.channelName);

  this._chatChannel = this._pusher.subscribe(this.settings.channelName);

  this._chatChannel.bind('chat_message', function(data) {
    self._chatMessageReceived(data);
  })

  this._itemCount = 0;

  this._widget = PusherChatWidget._createHTML(this.settings.appendTo);
  this._nicknameEl = this._widget.find('input[name=nickname]');
  this._emailEl = this._widget.find('input[name=email]');
  this._messageInputEl = this._widget.find('textarea');
  this._messagesEl = this._widget.find('ul');

  this._widget.find('button').click(function(event) {
    event.preventDefault();
    event.stopPropagation();
    self._sendChatButtonClicked();
  })

  var messageEl = this._messagesEl;
  messageEl.scroll(function() {
    var el = messageEl.get(0);
    var scrollableHeight = (el.scrollHeight - messageEl.height());
    self._autoScroll = ( scrollableHeight === messageEl.scrollTop() );
  });

  this._startTimeMonitor();
  if (typeof gon !== "undefined"){
    this._populateChat(gon.mapped_messages);
  }
};
PusherChatWidget.instances = [];

/* @private */
PusherChatWidget.prototype._chatMessageReceived = function(data) {
  var self = this;

  if(this._itemCount === 0) {
    this._messagesEl.html('');
  }

  var messageEl = PusherChatWidget._buildListItem(data);
  messageEl.hide();
  this._messagesEl.append(messageEl);
  messageEl.slideDown(function() {
    if(self._autoScroll) {
      var messageEl = self._messagesEl.get(0);
      var scrollableHeight = (messageEl.scrollHeight - self._messagesEl.height());
      self._messagesEl.scrollTop(messageEl.scrollHeight);
    }
  });

  ++this._itemCount;

  if(this._itemCount > this.settings.maxItems) {
    /* get first li of list */
    this._messagesEl.children(':first').slideUp(function() {
      $(this).remove();
    });
  }
};

/* @private */
PusherChatWidget.prototype._sendChatButtonClicked = function(event) {
  var message = $.trim(this._messageInputEl.val());
  if(!message) {
    alert('please supply a chat message');
    return;
  }

  var chatInfo = {
    text: message
  };
  this._sendChatMessage(chatInfo);
};

/* @private */
PusherChatWidget.prototype._sendChatMessage = function(data) {
  var self = this;
  this._messageInputEl.attr('readonly', 'readonly');
  $.ajax({
    url: this.settings.chatEndPoint,
    type: 'post',
    dataType: 'json',
    data: {
      'chat_info': data
    },
    complete: function(xhr, status) {
      Pusher.log('Chat message sent. Result: ' + status + ' : ' + xhr.responseText);
      if(xhr.status === 200) {
        self._messageInputEl.val('');
      }
      self._messageInputEl.removeAttr('readonly');
    },
    success: function(result) {
      var activity = result.activity;
      var imageInfo = activity.actor.image;
      var image = $('
' +
                      '' +
                    '
');
      var name = $('
' + activity.actor.displayName.replace(/\\'/g, "'") + '
');
      var header = self._widget.find('.pusher-chat-widget-header');
      console.log(header)
      header.html(image).append(name);
    }
  })
};

/* @private */
PusherChatWidget.prototype._startTimeMonitor = function() {
  var self = this;

  setInterval(function() {
    self._messagesEl.children('.activity').each(function(i, el) {
      var timeEl = $(el).find('a.timestamp span[data-activity-published]');
      var time = timeEl.attr('data-activity-published');
      var newDesc = PusherChatWidget.timeToDescription(time);
      timeEl.text(newDesc);
    });
  }, 10 * 1000)
};
PusherChatWidget.prototype._populateChat = function(messages) {
  var self = this;
  console.log(self)
  console.log(messages)
  for (var i = 0; i < messages.length; i += 1){
    var imageInfo = messages[i].actor.image;
    console.log(imageInfo)
    var image = $('
' +
                    '' +
                  '
');
    var name = $('
' + messages[i].actor.displayName.replace(/\\'/g, "'") + '
');
    var header = self._widget.find('.pusher-chat-widget-header');
    console.log(header)
    header.html(image).append(name);

    var messageEl = PusherChatWidget._buildListItem(messages[i]);
  messageEl.hide();
  this._messagesEl.append(messageEl);
  messageEl.slideDown(function() {
    if(self._autoScroll) {
      var messageEl = self._messagesEl.get(0);
      var scrollableHeight = (messageEl.scrollHeight - self._messagesEl.height());
      self._messagesEl.scrollTop(messageEl.scrollHeight);
    }
  });

  ++this._itemCount;

  if(this._itemCount > this.settings.maxItems) {
    /* get first li of list */
    this._messagesEl.children(':first').slideUp(function() {
      $(this).remove();
    });
  }
  }
}

/* @private */
PusherChatWidget._createHTML = function(appendTo) {
  var html = '' +
  '
' +
    '
Generate Buzz Below
' +
    '
' +
      '
' +
        '
No chat messages available
' +
      '
' +
    '
' +
    '
' +
    '
' +
      'Message' +
      '<textarea name="message" class="comment-box"></textarea>' +
      '<button class="button-sub" id="sub-message">Send</button>' +
      '' +
      '' +
    '
' +
    '
' +
  '
';
  var widget = $(html);
  $(appendTo).append(widget);
  return widget;
};

/* @private */
PusherChatWidget._buildListItem = function(activity) {
  var li = $('

');
  li.attr('data-activity-id', activity.id);
  var item = $('

');
  li.append(item);

  var imageInfo = activity.actor.image;
  var image = $('
' +
                  '' +
                '
');
  item.append(image);

  var content = $('

');
  item.append(content);

  var user = $('
' +
                '' +
                  '' + activity.actor.displayName.replace(/\\'/g, "'") + '' +
                  //'' + activity.actor.displayName + '' +
                '' +
              '
');
  content.append(user);

  var message = $('
' +
                    '
' + activity.body.replace(/\\('|")/g, '$1') + '
' +
                  '
');
  content.append(message);

  var time = $('
' +
                '' +
                  '' + PusherChatWidget.timeToDescription(activity.published) + '' +
                '' +
                '' +
                  /*'' +
                    'Like' +
                  '' +*/
                '' +
              '
');
  content.append(time);

  return li;
};

/**
 * converts a string into something which can be used as a valid channel name in Pusher.
 * @param {String} from The string to be converted.
 *
 * @see http://pusher.com/docs/client_api_guide/client_channels#naming-channels
 */
PusherChatWidget.getValidChannelName = function(from) {
  var pattern = /(\W)+/g;
  return from.replace(pattern, '-');
}

/**
 * converts a string or date parameter into a 'social media style'
 * time description.
 */
PusherChatWidget.timeToDescription = function(time) {
  if(time instanceof Date === false) {
    time = new Date(Date.parse(time));
  }
  var desc = "dunno";
  var now = new Date();
  var howLongAgo = (now - time);
  var seconds = Math.round(howLongAgo/1000);
  var minutes = Math.round(seconds/60);
  var hours = Math.round(minutes/60);
  if(seconds === 0) {
    desc = "just now";
  }
  else if(minutes < 1) {
    desc = seconds + " second" + (seconds !== 1?"s":"") + " ago";
  }
  else if(minutes < 60) {
    desc = "about " + minutes + " minute" + (minutes !== 1?"s":"") + " ago";
  }
  else if(hours < 24) {
    desc = "about " + hours + " hour"  + (hours !== 1?"s":"") + " ago";
  }
  else {
    desc = time.getDay() + " " + ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"][time.getMonth()]
  }
  return desc;
};

// var populateChat = function(messages) {
//   console.log(messages)
//   for (var i = 0; i < messages.length; i += 1){
//     var imageInfo = messages[i].actor.image;
//     console.log(imageInfo)
//     var image = $('
' +
//                     '' +
//                   '
');
//     var name = $('
' + messages[i].actor.displayName.replace(/\\'/g, "'") + '
');
//     var header = PusherChatWidget.instances.find('.pusher-chat-widget-header');
//     header.html(image).append(name);
//   }
// }

//   }
// });<commit_msg>change channelName's to no longer include user ID's - I believe this was causing no cross user updates<commit_after>/**
 * Creates an instance of a PusherChatWidget, binds to a chat channel on the pusher instance and
 * and creates the UI for the chat widget.
 *
 * @param {Pusher} pusher The Pusher object used for the chat widget.
 * @param {Map} options A hash of key value options for the widget.
 */
// $(function(){
//   if($('span').is('.hive-chat')){

function PusherChatWidget(pusher, options) {
  PusherChatWidget.instances.push(this);
  var self = this;

  this._pusher = pusher;
  this._autoScroll = true;

  options = options || {};
  this.settings = $.extend({
    maxItems: 500, // max items to show in the UI. Items beyond this limit will be removed as new ones come in.

    // EndPoint changes based on if we are looking at Heroku or LocalHost due to the difference in length of the base URL
    chatEndPoint: document.location.href.slice(28) + '/chat', // the end point where chat messages should be sanitized and then triggered
    channelName: document.location.href.slice(36), // the name of the channel the chat will take place on
    appendTo: $('#chat-window'), // A jQuery selector or object. Defines where the element should be appended to
    debug: true
  }, options);

  if(this.settings.debug && !Pusher.log) {
    Pusher.log = function(msg) {
      if(console && console.log) {
        console.log(msg);
      }
    }
  }

  // remove any unsupported characters from the chat channel name
  // see: http://pusher.com/docs/client_api_guide/client_channels#naming-channels
  this.settings.channelName = PusherChatWidget.getValidChannelName(this.settings.channelName);

  this._chatChannel = this._pusher.subscribe(this.settings.channelName);

  this._chatChannel.bind('chat_message', function(data) {
    self._chatMessageReceived(data);
  })

  this._itemCount = 0;

  this._widget = PusherChatWidget._createHTML(this.settings.appendTo);
  this._nicknameEl = this._widget.find('input[name=nickname]');
  this._emailEl = this._widget.find('input[name=email]');
  this._messageInputEl = this._widget.find('textarea');
  this._messagesEl = this._widget.find('ul');

  this._widget.find('button').click(function(event) {
    event.preventDefault();
    event.stopPropagation();
    self._sendChatButtonClicked();
  })

  var messageEl = this._messagesEl;
  messageEl.scroll(function() {
    var el = messageEl.get(0);
    var scrollableHeight = (el.scrollHeight - messageEl.height());
    self._autoScroll = ( scrollableHeight === messageEl.scrollTop() );
  });

  this._startTimeMonitor();
  if (typeof gon !== "undefined"){
    this._populateChat(gon.mapped_messages);
  }
};
PusherChatWidget.instances = [];

/* @private */
PusherChatWidget.prototype._chatMessageReceived = function(data) {
  var self = this;

  if(this._itemCount === 0) {
    this._messagesEl.html('');
  }

  var messageEl = PusherChatWidget._buildListItem(data);
  messageEl.hide();
  this._messagesEl.append(messageEl);
  messageEl.slideDown(function() {
    if(self._autoScroll) {
      var messageEl = self._messagesEl.get(0);
      var scrollableHeight = (messageEl.scrollHeight - self._messagesEl.height());
      self._messagesEl.scrollTop(messageEl.scrollHeight);
    }
  });

  ++this._itemCount;

  if(this._itemCount > this.settings.maxItems) {
    /* get first li of list */
    this._messagesEl.children(':first').slideUp(function() {
      $(this).remove();
    });
  }
};

/* @private */
PusherChatWidget.prototype._sendChatButtonClicked = function(event) {
  var message = $.trim(this._messageInputEl.val());
  if(!message) {
    alert('please supply a chat message');
    return;
  }

  var chatInfo = {
    text: message
  };
  this._sendChatMessage(chatInfo);
};

/* @private */
PusherChatWidget.prototype._sendChatMessage = function(data) {
  var self = this;
  this._messageInputEl.attr('readonly', 'readonly');
  $.ajax({
    url: this.settings.chatEndPoint,
    type: 'post',
    dataType: 'json',
    data: {
      'chat_info': data
    },
    complete: function(xhr, status) {
      Pusher.log('Chat message sent. Result: ' + status + ' : ' + xhr.responseText);
      if(xhr.status === 200) {
        self._messageInputEl.val('');
      }
      self._messageInputEl.removeAttr('readonly');
    },
    success: function(result) {
      var activity = result.activity;
      var imageInfo = activity.actor.image;
      var image = $('
' +
                      '' +
                    '
');
      var name = $('
' + activity.actor.displayName.replace(/\\'/g, "'") + '
');
      var header = self._widget.find('.pusher-chat-widget-header');
      console.log(header)
      header.html(image).append(name);
    }
  })
};

/* @private */
PusherChatWidget.prototype._startTimeMonitor = function() {
  var self = this;

  setInterval(function() {
    self._messagesEl.children('.activity').each(function(i, el) {
      var timeEl = $(el).find('a.timestamp span[data-activity-published]');
      var time = timeEl.attr('data-activity-published');
      var newDesc = PusherChatWidget.timeToDescription(time);
      timeEl.text(newDesc);
    });
  }, 10 * 1000)
};
PusherChatWidget.prototype._populateChat = function(messages) {
  var self = this;
  console.log(self)
  console.log(messages)
  for (var i = 0; i < messages.length; i += 1){
    var imageInfo = messages[i].actor.image;
    console.log(imageInfo)
    var image = $('
' +
                    '' +
                  '
');
    var name = $('
' + messages[i].actor.displayName.replace(/\\'/g, "'") + '
');
    var header = self._widget.find('.pusher-chat-widget-header');
    console.log(header)
    header.html(image).append(name);

    var messageEl = PusherChatWidget._buildListItem(messages[i]);
  messageEl.hide();
  this._messagesEl.append(messageEl);
  messageEl.slideDown(function() {
    if(self._autoScroll) {
      var messageEl = self._messagesEl.get(0);
      var scrollableHeight = (messageEl.scrollHeight - self._messagesEl.height());
      self._messagesEl.scrollTop(messageEl.scrollHeight);
    }
  });

  ++this._itemCount;

  if(this._itemCount > this.settings.maxItems) {
    /* get first li of list */
    this._messagesEl.children(':first').slideUp(function() {
      $(this).remove();
    });
  }
  }
}

/* @private */
PusherChatWidget._createHTML = function(appendTo) {
  var html = '' +
  '
' +
    '
Generate Buzz Below
' +
    '
' +
      '
' +
        '
No chat messages available
' +
      '
' +
    '
' +
    '
' +
    '
' +
      'Message' +
      '<textarea name="message" class="comment-box"></textarea>' +
      '<button class="button-sub" id="sub-message">Send</button>' +
      '' +
      '' +
    '
' +
    '
' +
  '
';
  var widget = $(html);
  $(appendTo).append(widget);
  return widget;
};

/* @private */
PusherChatWidget._buildListItem = function(activity) {
  var li = $('

');
  li.attr('data-activity-id', activity.id);
  var item = $('

');
  li.append(item);

  var imageInfo = activity.actor.image;
  var image = $('
' +
                  '' +
                '
');
  item.append(image);

  var content = $('

');
  item.append(content);

  var user = $('
' +
                '' +
                  '' + activity.actor.displayName.replace(/\\'/g, "'") + '' +
                  //'' + activity.actor.displayName + '' +
                '' +
              '
');
  content.append(user);

  var message = $('
' +
                    '
' + activity.body.replace(/\\('|")/g, '$1') + '
' +
                  '
');
  content.append(message);

  var time = $('
' +
                '' +
                  '' + PusherChatWidget.timeToDescription(activity.published) + '' +
                '' +
                '' +
                  /*'' +
                    'Like' +
                  '' +*/
                '' +
              '
');
  content.append(time);

  return li;
};

/**
 * converts a string into something which can be used as a valid channel name in Pusher.
 * @param {String} from The string to be converted.
 *
 * @see http://pusher.com/docs/client_api_guide/client_channels#naming-channels
 */
PusherChatWidget.getValidChannelName = function(from) {
  var pattern = /(\W)+/g;
  return from.replace(pattern, '-');
}

/**
 * converts a string or date parameter into a 'social media style'
 * time description.
 */
PusherChatWidget.timeToDescription = function(time) {
  if(time instanceof Date === false) {
    time = new Date(Date.parse(time));
  }
  var desc = "dunno";
  var now = new Date();
  var howLongAgo = (now - time);
  var seconds = Math.round(howLongAgo/1000);
  var minutes = Math.round(seconds/60);
  var hours = Math.round(minutes/60);
  if(seconds === 0) {
    desc = "just now";
  }
  else if(minutes < 1) {
    desc = seconds + " second" + (seconds !== 1?"s":"") + " ago";
  }
  else if(minutes < 60) {
    desc = "about " + minutes + " minute" + (minutes !== 1?"s":"") + " ago";
  }
  else if(hours < 24) {
    desc = "about " + hours + " hour"  + (hours !== 1?"s":"") + " ago";
  }
  else {
    desc = time.getDay() + " " + ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sept", "Oct", "Nov", "Dec"][time.getMonth()]
  }
  return desc;
};

// var populateChat = function(messages) {
//   console.log(messages)
//   for (var i = 0; i < messages.length; i += 1){
//     var imageInfo = messages[i].actor.image;
//     console.log(imageInfo)
//     var image = $('
' +
//                     '' +
//                   '
');
//     var name = $('
' + messages[i].actor.displayName.replace(/\\'/g, "'") + '
');
//     var header = PusherChatWidget.instances.find('.pusher-chat-widget-header');
//     header.html(image).append(name);
//   }
// }

//   }
// });<|endoftext|>


<commit_before>/*global jQuery,cyberDojo*/

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

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // Filenames
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  let theCurrentFilename = '';
  let theLastNonOutputFilename = '';

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

  cd.currentFilename = () => {
    return theCurrentFilename;
  };

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

  cd.eachFilename = (f) => {
    cd.filenames().forEach(f);
  };

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

  cd.filenames = () => {
    // Gets the kata/edit page filenames. The review/show
    // page/dialog collects filenames in its own way.
    const filenames = [];
    const prefix = 'file_content_for_';
    $('textarea[id^=' + prefix + ']').each(function(_) {
      const id = $(this).attr('id');
      const filename = id.substr(prefix.length, id.length - prefix.length);
      filenames.push(filename);
    });
    return filenames;
  };

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

  cd.sortedFilenames = (filenames) => {
    // Controls the order of files in the filename-list
    // Used in two places
    // 1. kata/edit page to help show filename-list
    // 2. review/show page/dialog to help show filename-list
    return [].concat(hiFilenames(filenames), ['output'], loFilenames(filenames));
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // Load a named file
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.loadFile = (filename) => {
    fileDiv(cd.currentFilename()).hide();
    fileDiv(filename).show();

    selectFileInFileList(filename);
    cd.focusSyntaxHighlightEditor(filename);

    theCurrentFilename = filename;
    if (filename !== 'output') {
      theLastNonOutputFilename = filename;
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // Filename hot-key navigation
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // See app/assets/javascripts/cyber-dojo_codemirror.js
  // See app/views/shared/_hotkeys.html.erb
  // Alt-J ==> loadNextFile()
  // Alt-K ==> loadPreviousFile()
  // Alt-O ==> toggleOutputFile()

  cd.loadNextFile = () => {
    const hi = hiFilenames(cd.filenames());
    const index = $.inArray(cd.currentFilename(), hi);
    if (index == -1) {
      const next = 0;
      cd.loadFile(hi[next]);
    } else {
      const next = (index + 1) % hi.length;
      cd.loadFile(hi[next]);
    }
  };

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

  cd.loadPreviousFile = () => {
    const hi = hiFilenames(cd.filenames());
    const index = $.inArray(cd.currentFilename(), hi);
    if (index === 0 || index === -1) {
      const previous = hi.length - 1;
      cd.loadFile(hi[previous]);
    } else {
      const previous = index - 1;
      cd.loadFile(hi[previous]);
    }
  };

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

  cd.toggleOutputFile = () => {
    if (cd.currentFilename() !== 'output') {
      cd.loadFile('output');
    } else {
      cd.loadFile(theLastNonOutputFilename);
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // new-file, rename-file, delete-file
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // See app/views/kata/_file_new_rename_delete.html.erb
  // See app/views/kata/_files.html.erb
  // See app/views/kata/_run_tests.js.erb

  cd.newFile = (filename, content) => {
    const newFile = makeNewFile(filename, content);
    $('#visible-files-container').append(newFile);
    rebuildFilenameList();
    cd.switchEditorToCodeMirror(filename);
  };

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

  cd.deleteFile = (filename) => {
    fileDiv(filename).remove();
    rebuildFilenameList();
    theLastNonOutputFilename = testFilename();
  };

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

  cd.renameFile = (oldFilename, newFilename) => {
    // This should restore the caret/cursor/selection
    // but it currently does not. See
    // https://github.com/cyber-dojo/web/issues/51
    const content = fileContent(oldFilename);
    cd.deleteFile(oldFilename);
    cd.newFile(newFilename, content);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // Helpers
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.editorRefocus = () => {
    cd.loadFile(cd.currentFilename());
  };

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

  cd.loadTestFile = () => {
    cd.loadFile(testFilename());
  };

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

  cd.radioEntrySwitch = (previous, current) => {
    // Used in test-page, setup-pages, and history/diff-dialog
    // See app/assets/stylesheets/wide-list-item.scss
    if (previous != undefined) {
      previous.removeClass('selected');
    }
    current.addClass('selected');
  };

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

  cd.hashOfFile = (filename) => {
    const content = fileContent(filename);
    let hash = 0;
    for (let i = 0; i < content.length; ++i) {
      hash = (hash << 5) - hash + content.charCodeAt(i);
      hash &= hash;
    }
    return hash;
  };

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

  const fileContent = (filename) => {
    cd.saveCodeFromIndividualSyntaxHighlightEditor(filename);
    return jqElement('file_content_for_' + filename).val();
  };

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

  const jqElement = (name) => {
    return $('[id="' + name + '"]');
  };

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

  const fileDiv = (filename) => {
    return jqElement(filename + '_div');
  };

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

  const makeNewFile = (filename, content) => {
    const div = $('<div>', {
      'class': 'filename_div',
      id: filename + '_div'
    });
    const text = $('<textarea>', {
      'class': 'file_content',
      'spellcheck': 'false',
      'data-filename': filename,
      name: 'file_content[' + filename + ']',
      id: 'file_content_for_' + filename
      //wrap: 'off'
    });
    // For some reason, setting wrap cannot be done as per the
    // commented out line above... when you create a new file in
    // FireFox 17.0.1 it still wraps at the textarea width.
    // So instead I do it like this, which works in FireFox?!
    text.attr('wrap', 'off');

    text.val(content);
    div.append(text);

    return div;
  };

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

  const testFilename = () => {
    const filenames = cd.filenames();
    return filenames[testFilenameIndex(filenames)];
  };

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

  const testFilenameIndex = (filenames) => {
    // When starting and in file-knave navigation
    // the current file is sometimes not present.
    // (eg the file has been renamed/deleted).
    // When this happens, try to select a test file.
    for (let i = 0; i < filenames.length; i++) {
      const parts = filenames[i].toLowerCase().split('/');
      const filename = parts[parts.length - 1];
      if (filename.search('test') !== -1) {
        return i;
      }
    }
    return 0;
  };

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

  const setRenameAndDeleteButtons = (filename) => {
    const fileOps = $('#file-operations');
    const   newFile  = fileOps.find('#new');
    const renameFile = fileOps.find('#rename');
    const deleteFile = fileOps.find('#delete');
    const disable = (node) => { node.prop('disabled', true ); };
    const enable  = (node) => { node.prop('disabled', false); };

    if (cantBeRenamedOrDeleted(filename)) {
      disable(renameFile);
      disable(deleteFile);
    } else {
      enable(renameFile);
      enable(deleteFile);
    }
  };

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

  const cantBeRenamedOrDeleted = (filename) => {
    return cd.inArray(filename, [ 'cyber-dojo.sh', 'output' ]);
  };

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

  const rebuildFilenameList = () => {
    const filenames = cd.filenames();
    const filenameList = $('#filename-list');
    filenameList.empty();
    $.each(cd.sortedFilenames(filenames), (_, filename) => {
      filenameList.append(makeFileListEntry(filename));
    });
    return filenames;
  };

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

  const selectFileInFileList = (filename) => {
    // Can't do $('radio_' + filename) because filename
    // could contain characters that aren't strictly legal
    // characters in a dom node id so I do this instead...
    const node = $('[id="radio_' + filename + '"]');
    const previousFilename = cd.currentFilename();
    const previous = $('[id="radio_' + previousFilename + '"]');
    cd.radioEntrySwitch(previous, node);
    setRenameAndDeleteButtons(filename);
  };

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

  const hiFilenames = (filenames) => {
    // Controls which filenames appear at the
    // top of the filename-list, above 'output'
    // Used in three places.
    // 1. kata/edit page to help show filename list
    // 2. kata/edit page in alt-j alt-k hotkeys
    // 3. review/show page/dialog to help show filename list
    let hi = [];
    $.each(filenames, function(_, filename) {
      if (isSourceFile(filename) || filename == 'instructions') {
        hi.push(filename);
      }
    });
    hi.sort();
    hi = hi.filter(item => item !== 'output')
    hi = hi.filter(item => item != 'cyber-dojo.sh')
    return hi;
  };

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

  const loFilenames = (filenames) => {
    // Controls which filenames appear at the
    // bottom of the filename list, below 'output'
    // Used in three places.
    // 1. kata/edit page to help show filename-list
    // 2. kata/edit page in Alt-j Alt-k hotkeys
    // 3. review/show page/dialog to help show filename-list
    let lo = [];
    $.each(filenames, (_, filename) => {
      if (!isSourceFile(filename) && filename != 'instructions') {
        lo.push(filename);
      }
    });
    lo.sort();
    lo = lo.filter(item => item !== 'output')
    return lo;
  };

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

  const isSourceFile = (filename) => {
    let match = false;
    $.each(cd.extensionFilenames(), (_, extension) => {
      // Shell test frameworks (eg shunit2) use .sh as their
      // filename extension but we don't want cyber-dojo.sh
      // in the hiFilenames() above output in the filename-list.
      if (filename.endsWith(extension) && filename != 'cyber-dojo.sh') {
        match = true;
      }
    });
    return match;
  };

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

  const makeFileListEntry = (filename) => {
    const div = $('<div>', {
      'class': 'filename',
           id: 'radio_' + filename,
         text: filename
    });
    if (cd.inArray(filename, cd.highlightFilenames())) {
      div.addClass('highlight');
    }
    div.click(() => { cd.loadFile(filename); });
    return div;
  };

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

  return cd;

})(cyberDojo || {}, jQuery);
<commit_msg>refactoring; delete dead js statement<commit_after>/*global jQuery,cyberDojo*/

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

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // Filenames
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  let theCurrentFilename = '';
  let theLastNonOutputFilename = '';

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

  cd.currentFilename = () => {
    return theCurrentFilename;
  };

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

  cd.eachFilename = (f) => {
    cd.filenames().forEach(f);
  };

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

  cd.filenames = () => {
    // Gets the kata/edit page filenames. The review/show
    // page/dialog collects filenames in its own way.
    const filenames = [];
    const prefix = 'file_content_for_';
    $('textarea[id^=' + prefix + ']').each(function(_) {
      const id = $(this).attr('id');
      const filename = id.substr(prefix.length, id.length - prefix.length);
      filenames.push(filename);
    });
    return filenames;
  };

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

  cd.sortedFilenames = (filenames) => {
    // Controls the order of files in the filename-list
    // Used in two places
    // 1. kata/edit page to help show filename-list
    // 2. review/show page/dialog to help show filename-list
    return [].concat(hiFilenames(filenames), ['output'], loFilenames(filenames));
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // Load a named file
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.loadFile = (filename) => {
    fileDiv(cd.currentFilename()).hide();
    fileDiv(filename).show();

    selectFileInFileList(filename);
    cd.focusSyntaxHighlightEditor(filename);

    theCurrentFilename = filename;
    if (filename !== 'output') {
      theLastNonOutputFilename = filename;
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // Filename hot-key navigation
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // See app/assets/javascripts/cyber-dojo_codemirror.js
  // See app/views/shared/_hotkeys.html.erb
  // Alt-J ==> loadNextFile()
  // Alt-K ==> loadPreviousFile()
  // Alt-O ==> toggleOutputFile()

  cd.loadNextFile = () => {
    const hi = hiFilenames(cd.filenames());
    const index = $.inArray(cd.currentFilename(), hi);
    if (index == -1) {
      const next = 0;
      cd.loadFile(hi[next]);
    } else {
      const next = (index + 1) % hi.length;
      cd.loadFile(hi[next]);
    }
  };

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

  cd.loadPreviousFile = () => {
    const hi = hiFilenames(cd.filenames());
    const index = $.inArray(cd.currentFilename(), hi);
    if (index === 0 || index === -1) {
      const previous = hi.length - 1;
      cd.loadFile(hi[previous]);
    } else {
      const previous = index - 1;
      cd.loadFile(hi[previous]);
    }
  };

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

  cd.toggleOutputFile = () => {
    if (cd.currentFilename() !== 'output') {
      cd.loadFile('output');
    } else {
      cd.loadFile(theLastNonOutputFilename);
    }
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // new-file, rename-file, delete-file
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // See app/views/kata/_file_new_rename_delete.html.erb
  // See app/views/kata/_files.html.erb
  // See app/views/kata/_run_tests.js.erb

  cd.newFile = (filename, content) => {
    const newFile = makeNewFile(filename, content);
    $('#visible-files-container').append(newFile);
    rebuildFilenameList();
    cd.switchEditorToCodeMirror(filename);
  };

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

  cd.deleteFile = (filename) => {
    fileDiv(filename).remove();
    rebuildFilenameList();
    theLastNonOutputFilename = testFilename();
  };

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

  cd.renameFile = (oldFilename, newFilename) => {
    // This should restore the caret/cursor/selection
    // but it currently does not. See
    // https://github.com/cyber-dojo/web/issues/51
    const content = fileContent(oldFilename);
    cd.deleteFile(oldFilename);
    cd.newFile(newFilename, content);
  };

  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  // Helpers
  //- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

  cd.editorRefocus = () => {
    cd.loadFile(cd.currentFilename());
  };

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

  cd.loadTestFile = () => {
    cd.loadFile(testFilename());
  };

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

  cd.radioEntrySwitch = (previous, current) => {
    // Used in test-page, setup-pages, and history/diff-dialog
    // See app/assets/stylesheets/wide-list-item.scss
    if (previous != undefined) {
      previous.removeClass('selected');
    }
    current.addClass('selected');
  };

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

  cd.hashOfFile = (filename) => {
    const content = fileContent(filename);
    let hash = 0;
    for (let i = 0; i < content.length; ++i) {
      hash = (hash << 5) - hash + content.charCodeAt(i);
      hash &= hash;
    }
    return hash;
  };

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

  const fileContent = (filename) => {
    cd.saveCodeFromIndividualSyntaxHighlightEditor(filename);
    return jqElement('file_content_for_' + filename).val();
  };

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

  const jqElement = (name) => {
    return $('[id="' + name + '"]');
  };

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

  const fileDiv = (filename) => {
    return jqElement(filename + '_div');
  };

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

  const makeNewFile = (filename, content) => {
    const div = $('<div>', {
      'class': 'filename_div',
      id: filename + '_div'
    });
    const text = $('<textarea>', {
      'class': 'file_content',
      'spellcheck': 'false',
      'data-filename': filename,
      name: 'file_content[' + filename + ']',
      id: 'file_content_for_' + filename
      //wrap: 'off'
    });
    // For some reason, setting wrap cannot be done as per the
    // commented out line above... when you create a new file in
    // FireFox 17.0.1 it still wraps at the textarea width.
    // So instead I do it like this, which works in FireFox?!
    text.attr('wrap', 'off');

    text.val(content);
    div.append(text);

    return div;
  };

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

  const testFilename = () => {
    const filenames = cd.filenames();
    return filenames[testFilenameIndex(filenames)];
  };

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

  const testFilenameIndex = (filenames) => {
    // When starting and in file-knave navigation
    // the current file is sometimes not present.
    // (eg the file has been renamed/deleted).
    // When this happens, try to select a test file.
    for (let i = 0; i < filenames.length; i++) {
      const parts = filenames[i].toLowerCase().split('/');
      const filename = parts[parts.length - 1];
      if (filename.search('test') !== -1) {
        return i;
      }
    }
    return 0;
  };

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

  const setRenameAndDeleteButtons = (filename) => {
    const fileOps = $('#file-operations');
    const renameFile = fileOps.find('#rename');
    const deleteFile = fileOps.find('#delete');
    const disable = (node) => { node.prop('disabled', true ); };
    const enable  = (node) => { node.prop('disabled', false); };

    if (cantBeRenamedOrDeleted(filename)) {
      disable(renameFile);
      disable(deleteFile);
    } else {
      enable(renameFile);
      enable(deleteFile);
    }
  };

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

  const cantBeRenamedOrDeleted = (filename) => {
    return cd.inArray(filename, [ 'cyber-dojo.sh', 'output' ]);
  };

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

  const rebuildFilenameList = () => {
    const filenames = cd.filenames();
    const filenameList = $('#filename-list');
    filenameList.empty();
    $.each(cd.sortedFilenames(filenames), (_, filename) => {
      filenameList.append(makeFileListEntry(filename));
    });
    return filenames;
  };

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

  const selectFileInFileList = (filename) => {
    // Can't do $('radio_' + filename) because filename
    // could contain characters that aren't strictly legal
    // characters in a dom node id so I do this instead...
    const node = $('[id="radio_' + filename + '"]');
    const previousFilename = cd.currentFilename();
    const previous = $('[id="radio_' + previousFilename + '"]');
    cd.radioEntrySwitch(previous, node);
    setRenameAndDeleteButtons(filename);
  };

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

  const hiFilenames = (filenames) => {
    // Controls which filenames appear at the
    // top of the filename-list, above 'output'
    // Used in three places.
    // 1. kata/edit page to help show filename list
    // 2. kata/edit page in alt-j alt-k hotkeys
    // 3. review/show page/dialog to help show filename list
    let hi = [];
    $.each(filenames, function(_, filename) {
      if (isSourceFile(filename) || filename == 'instructions') {
        hi.push(filename);
      }
    });
    hi.sort();
    hi = hi.filter(item => item !== 'output')
    hi = hi.filter(item => item != 'cyber-dojo.sh')
    return hi;
  };

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

  const loFilenames = (filenames) => {
    // Controls which filenames appear at the
    // bottom of the filename list, below 'output'
    // Used in three places.
    // 1. kata/edit page to help show filename-list
    // 2. kata/edit page in Alt-j Alt-k hotkeys
    // 3. review/show page/dialog to help show filename-list
    let lo = [];
    $.each(filenames, (_, filename) => {
      if (!isSourceFile(filename) && filename != 'instructions') {
        lo.push(filename);
      }
    });
    lo.sort();
    lo = lo.filter(item => item !== 'output')
    return lo;
  };

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

  const isSourceFile = (filename) => {
    let match = false;
    $.each(cd.extensionFilenames(), (_, extension) => {
      // Shell test frameworks (eg shunit2) use .sh as their
      // filename extension but we don't want cyber-dojo.sh
      // in the hiFilenames() above output in the filename-list.
      if (filename.endsWith(extension) && filename != 'cyber-dojo.sh') {
        match = true;
      }
    });
    return match;
  };

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

  const makeFileListEntry = (filename) => {
    const div = $('<div>', {
      'class': 'filename',
           id: 'radio_' + filename,
         text: filename
    });
    if (cd.inArray(filename, cd.highlightFilenames())) {
      div.addClass('highlight');
    }
    div.click(() => { cd.loadFile(filename); });
    return div;
  };

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

  return cd;

})(cyberDojo || {}, jQuery);
<|endoftext|>


<commit_before>phaserLifeCycleFunctions.create = function(){
  var sky = game.add.sprite(0, 0, 'sky') // set background
  sky.scale.setTo(2,1)
  game.physics.startSystem(Phaser.Physics.ARCADE)
  gameUI.textInputLine = new Phaser.Rectangle(0, 550, 800, 50)

  //rain

  var emitter = game.add.emitter(game.world.width, 475, 500)  //(x, y , max particles)

  emitter.height = 400

  emitter.makeParticles('rain')
  // emitter.gravity.x = 0

  emitter.minParticleScale = 1
  emitter.maxParticleScale = 1

  // emitter.setYSpeed(500,-500);
  emitter.setXSpeed(-800, -1500)
  emitter.setYSpeed(0,0)

  emitter.minRotation = 1
  emitter.maxRotation = 1

  emitter.start(false, 3000,  0.5) //(explode, lifespan, frequency, quantity, forceQuantity)
  emitter.gravity.x = 1000

  // bullets

  bullets = game.add.group()
  bullets.enableBody = true
  bullets.physicsBodyType = Phaser.Physics.ARCADE

  //  All 40 of them
  bullets.createMultiple(400, 'bullet')
  bullets.setAll('anchor.x', 0.5)
  bullets.setAll('anchor.y', 0.5)

  // create platforms (stuff the character can stand on)

  var platforms = gameState.groups.platforms = game.add.group()
  platforms.enableBody = true

  var ground = platforms.create(0, game.world.height -30, 'ground')
  ground.scale.setTo(4,1)
  ground.body.immovable = true

  // var rightLedge = platforms.create(400, 400, 'ground')
  // rightLedge.body.immovable = true
  var screenSplit = platforms.create(0, 200, 'ground')
  screenSplit.scale.setTo(4,1)
  screenSplit.body.immovable = true

  // create player object

  var player = gameState.player = game.add.sprite(32, game.world.height - 150, 'dude')
  game.physics.arcade.enable(player)
  player.body.bounce.y = 0.2
  player.body.collideWorldBounds = true

  // create keyboard listeners
  gameState.cursors = game.input.keyboard.createCursorKeys()
  game.input.keyboard.addCallbacks(this, flashCardUI.wordKeysHandler)

  // answer input
  gameState.userGuess = game.add.text(400,16, '', {fontSize: '32px', fill: '#000'})
  // flascard question
  gameState.currentQuestion = game.add.text(16,16, gameState.currentDeck.currentCard.term , {fontSize: '32px', fill: '#000'})
}
<commit_msg>delete old commented out js code<commit_after>phaserLifeCycleFunctions.create = function(){
  var sky = game.add.sprite(0, 0, 'sky') // set background
  sky.scale.setTo(2,1)
  game.physics.startSystem(Phaser.Physics.ARCADE)

  // vector shapes
  gameUI.textInputLine = new Phaser.Rectangle(0, 550, 800, 50)

  // rain
  var emitter = game.add.emitter(game.world.width, 475, 500)  //(x, y , max particles)
  emitter.height = 400
  emitter.makeParticles('rain')
  emitter.minParticleScale = 1
  emitter.maxParticleScale = 1
  emitter.setXSpeed(-800, -1500)
  emitter.setYSpeed(0,0)
  emitter.minRotation = 1
  emitter.maxRotation = 1
  emitter.start(false, 3000,  0.5) //(explode, lifespan, frequency, quantity, forceQuantity)
  emitter.gravity.x = 1000

  // bullets
  bullets = game.add.group()
  bullets.enableBody = true
  bullets.physicsBodyType = Phaser.Physics.ARCADE
  //  All 40 of them
  bullets.createMultiple(400, 'bullet')
  bullets.setAll('anchor.x', 0.5)
  bullets.setAll('anchor.y', 0.5)

  // create platforms (stuff the character can stand on)
  var platforms = gameState.groups.platforms = game.add.group()
  platforms.enableBody = true

  var ground = platforms.create(0, game.world.height -30, 'ground')
  ground.scale.setTo(4,1)
  ground.body.immovable = true

  // var rightLedge = platforms.create(400, 400, 'ground')
  // rightLedge.body.immovable = true
  var screenSplit = platforms.create(0, 200, 'ground')
  screenSplit.scale.setTo(4,1)
  screenSplit.body.immovable = true

  // create player object

  var player = gameState.player = game.add.sprite(32, game.world.height - 150, 'dude')
  game.physics.arcade.enable(player)
  player.body.bounce.y = 0.2
  player.body.collideWorldBounds = true

  // create keyboard listeners
  gameState.cursors = game.input.keyboard.createCursorKeys()
  game.input.keyboard.addCallbacks(this, flashCardUI.wordKeysHandler)

  // answer input
  gameState.userGuess = game.add.text(400,16, '', {fontSize: '32px', fill: '#000'})
  // flascard question
  gameState.currentQuestion = game.add.text(16,16, gameState.currentDeck.currentCard.term , {fontSize: '32px', fill: '#000'})
}
<|endoftext|>