<commit_before>import React from 'react';

export default function() {
    return(

Hello, students!

Welcome to your new single-page application, built with:

ASP.NET Core and C# for cross-platform server-side code

React for client-side code

Bootstrap for layout and styling

Source Code on Github

To help you get started, we've also set up:

Client-side navigation. For example, click Counter then Back to return here.

Development server integration. In development mode, the development server from create-react-app runs in the background automatically, so your client-side resources are dynamically built on demand and the page refreshes when you modify any file.

Efficient production builds. In production mode, development-time features are disabled, and your dotnet publish configuration produces minified, efficiently bundled JavaScript files.

The ClientApp subdirectory is a standard React application based on the create-react-app template. If you open a command prompt in that directory, you can run npm commands such as npm test or npm install.

    );
}<commit_msg>update HomePage to mirror web-app content<commit_after>import React from 'react';
import { Jumbotron, Grid, Row, Col } from 'react-bootstrap';

export default function () {
    return (

            <Jumbotron>

Contoso University

Welcome to your new single-page application, built with:

ASP.NET Core and C# for cross-platform server-side code

React for client-side code

Bootstrap for layout and styling

Source Code on Github

            </Jumbotron>
            <Grid>
                <Row>
                    <Col md={4}>

                            Contoso University is a place for learning AspNetCore and related technologies.  This demo application is an amalgamation of smaller demo applications found in tutorials at AspNetCore docs.  The tutorials are great at demonstrating isolated concepts, but issues surfaces when applying these concepts/techniques in a larger context.  The purpose of this demo application is to apply concepts/techniques learned from those tutorial into a single domain (i.e. university).

                    </Col>
                    <Col md={4}>

Build it from scratch

                            You can build the initial application by following the steps in a series of tutorials.

                            Seee the tutorial »

                    </Col>
                    <Col md={4}>

Download it

You can download the completed project from Github.

                            See project source code »

                    </Col>
                </Row>
            </Grid>

    );
}<|endoftext|>


<commit_before>var util = require("../util");

module.exports = addTextLabel;

/*
 * Attaches a text label to the specified root. Handles escape sequences.
 */
function addTextLabel(root, node) {
  var domNode = root.append("text");

  var lines = processEscapeSequences(node.label).split("\n");
  for (var i = 0; i < lines.length; i++) {
    domNode
      .append("tspan")
        .attr("dy", "1em")
        .attr("x", "1")
        .text(lines[i]);
  }

  util.applyStyles(domNode, node, "labelStyle");

  return domNode;
}

function processEscapeSequences(text) {
  var newText = "",
      escaped = false,
      ch;
  for (var i = 0; i < text.length; ++i) {
    ch = text[i];
    if (escaped) {
      switch(ch) {
        case "n": newText += "\n"; break;
        default: newText += ch;
      }
      escaped = false;
    } else if (ch === "\\") {
      escaped = true;
    } else {
      newText += ch;
    }
  }
  return newText;
}
<commit_msg>Preserve whitespace in labels<commit_after>var util = require("../util");

module.exports = addTextLabel;

/*
 * Attaches a text label to the specified root. Handles escape sequences.
 */
function addTextLabel(root, node) {
  var domNode = root.append("text");

  var lines = processEscapeSequences(node.label).split("\n");
  for (var i = 0; i < lines.length; i++) {
    domNode
      .append("tspan")
        .attr("xml:space", "preserve")
        .attr("dy", "1em")
        .attr("x", "1")
        .text(lines[i]);
  }

  util.applyStyles(domNode, node, "labelStyle");

  return domNode;
}

function processEscapeSequences(text) {
  var newText = "",
      escaped = false,
      ch;
  for (var i = 0; i < text.length; ++i) {
    ch = text[i];
    if (escaped) {
      switch(ch) {
        case "n": newText += "\n"; break;
        default: newText += ch;
      }
      escaped = false;
    } else if (ch === "\\") {
      escaped = true;
    } else {
      newText += ch;
    }
  }
  return newText;
}
<|endoftext|>


<commit_before>/**
 * ---
 * title: mermaidAPI
 * order: 5
 * ---
 * # mermaidAPI
 * This is the api to be used when handling the integration with the web page instead of using the default integration
 * (mermaid.js).
 *
 * The core of this api is the **render** function that given a graph definitionas text renders the graph/diagram and
 * returns a svg element for the graph. It is is then up to the user of the API to make use of the svg, either insert it
 * somewhere in the page or something completely different.
*/
import * as d3 from 'd3'
import scope from 'scope-css'

import { logger, setLogLevel } from './logger'
import utils from './utils'
import flowRenderer from './diagrams/flowchart/flowRenderer'
import flowParser from './diagrams/flowchart/parser/flow'
import flowDb from './diagrams/flowchart/flowDb'
import sequenceRenderer from './diagrams/sequence/sequenceRenderer'
import sequenceParser from './diagrams/sequence/parser/sequenceDiagram'
import sequenceDb from './diagrams/sequence/sequenceDb'
import ganttRenderer from './diagrams/gantt/ganttRenderer'
import ganttParser from './diagrams/gantt/parser/gantt'
import ganttDb from './diagrams/gantt/ganttDb'
import classRenderer from './diagrams/class/classRenderer'
import classParser from './diagrams/class/parser/classDiagram'
import classDb from './diagrams/class/classDb'
import gitGraphRenderer from './diagrams/git/gitGraphRenderer'
import gitGraphParser from './diagrams/git/parser/gitGraph'
import gitGraphAst from './diagrams/git/gitGraphAst'

const themes = {}
for (const themeName of ['default', 'forest', 'dark', 'neutral']) {
  themes[themeName] = require(`./themes/${themeName}/index.scss`)
}

/**
 * ## Configuration
 * These are the default options which can be overridden with the initialization call as in the example below:
 * ```
 * mermaid.initialize({
 *   flowchart:{
 *      htmlLabels: false
 *   }
 * });
 * ```
 */
const config = {

/** theme , the CSS style sheet
  *
  * **theme** - Choose one of the built-in themes: default, forest, dark or neutral. To disable any pre-defined mermaid theme, use "null".
  * **themeCSS** - Use your own CSS. This overrides **theme**.
  *```
  * "theme": "forest",
  * "themeCSS": ".node rect { fill: red; }"
  *```
  */

  theme: 'default',
  themeCSS: undefined,

  /**
   * logLevel , decides the amount of logging to be used.
   *    * debug: 1
   *    * info: 2
   *    * warn: 3
   *    * error: 4
   *    * fatal: 5
   */
  logLevel: 5,

  /**
   * **startOnLoad** - This options controls whether or mermaid starts when the page loads
   */
  startOnLoad: true,

  /**
   * **arrowMarkerAbsolute** - This options controls whether or arrow markers in html code will be absolute paths or
   * an anchor, #. This matters if you are using base tag settings.
   */
  arrowMarkerAbsolute: false,

  /**
   * ### flowchart
   * *The object containing configurations specific for flowcharts*
   */
  flowchart: {
    /**
     * **htmlLabels** - Flag for setting whether or not a html tag should be used for rendering labels
     * on the edges
     */
    htmlLabels: true,

    curve: 'linear'
  },

  /**
   * ###  sequenceDiagram
   * The object containing configurations specific for sequence diagrams
   */
  sequence: {

    /**
     * **diagramMarginX** - margin to the right and left of the sequence diagram
     */
    diagramMarginX: 50,

    /**
     * **diagramMarginY** - margin to the over and under the sequence diagram
     */
    diagramMarginY: 10,

    /**
     * **actorMargin** - Margin between actors
     */
    actorMargin: 50,

    /**
     * **width** - Width of actor boxes
     */
    width: 150,

    /**
     * **height** - Height of actor boxes
     */
    height: 65,

    /**
     * **boxMargin** - Margin around loop boxes
     */
    boxMargin: 10,

    /**
     * **boxTextMargin** - margin around the text in loop/alt/opt boxes
     */
    boxTextMargin: 5,

    /**
     * **noteMargin** - margin around notes
     */
    noteMargin: 10,

    /**
     * **messageMargin** - Space between messages
     */
    messageMargin: 35,

    /**
     * **mirrorActors** - mirror actors under diagram
     */
    mirrorActors: true,

    /**
     * **bottomMarginAdj** - Depending on css styling this might need adjustment.
     * Prolongs the edge of the diagram downwards
     */
    bottomMarginAdj: 1,

    /**
     * **useMaxWidth** - when this flag is set the height and width is set to 100% and is then scaling with the
     * available space if not the absolute space required is used
     */
    useMaxWidth: true
  },

  /** ### gantt
   * The object containing configurations specific for gantt diagrams*
   */
  gantt: {
    /**
     * **titleTopMargin** - margin top for the text over the gantt diagram
     */
    titleTopMargin: 25,

    /**
     * **barHeight** - the height of the bars in the graph
     */
    barHeight: 20,

    /**
     * **barGap** - the margin between the different activities in the gantt diagram
     */
    barGap: 4,

    /**
     *  **topPadding** - margin between title and gantt diagram and between axis and gantt diagram.
     */
    topPadding: 50,

    /**
     *  **leftPadding** - the space allocated for the section name to the left of the activities.
     */
    leftPadding: 75,

    /**
     *  **gridLineStartPadding** - Vertical starting position of the grid lines
     */
    gridLineStartPadding: 35,

    /**
     *  **fontSize** - font size ...
     */
    fontSize: 11,

    /**
     * **fontFamily** - font family ...
     */
    fontFamily: '"Open-Sans", "sans-serif"',

    /**
     * **numberSectionStyles** - the number of alternating section styles
     */
    numberSectionStyles: 4,

    /**
     * **axisFormat** - datetime format of the axis, this might need adjustment to match your locale and preferences
     */
    axisFormat: '%Y-%m-%d'
  },
  class: {},
  git: {}
}

setLogLevel(config.logLevel)

function parse (text) {
  const graphType = utils.detectType(text)
  let parser

  switch (graphType) {
    case 'git':
      parser = gitGraphParser
      parser.parser.yy = gitGraphAst
      break
    case 'flowchart':
      parser = flowParser
      parser.parser.yy = flowDb
      break
    case 'sequence':
      parser = sequenceParser
      parser.parser.yy = sequenceDb
      break
    case 'gantt':
      parser = ganttParser
      parser.parser.yy = ganttDb
      break
    case 'class':
      parser = classParser
      parser.parser.yy = classDb
      break
  }

  parser.parser.yy.parseError = (str, hash) => {
    const error = { str, hash }
    throw error
  }

  parser.parse(text)
}

export const encodeEntities = function (text) {
  let txt = text

  txt = txt.replace(/style.*:\S*#.*;/g, function (s) {
    const innerTxt = s.substring(0, s.length - 1)
    return innerTxt
  })
  txt = txt.replace(/classDef.*:\S*#.*;/g, function (s) {
    const innerTxt = s.substring(0, s.length - 1)
    return innerTxt
  })

  txt = txt.replace(/#\w+;/g, function (s) {
    const innerTxt = s.substring(1, s.length - 1)

    const isInt = /^\+?\d+$/.test(innerTxt)
    if (isInt) {
      return 'ﬂ°°' + innerTxt + '¶ß'
    } else {
      return 'ﬂ°' + innerTxt + '¶ß'
    }
  })

  return txt
}

export const decodeEntities = function (text) {
  let txt = text

  txt = txt.replace(/ﬂ°°/g, function () {
    return '&#'
  })
  txt = txt.replace(/ﬂ°/g, function () {
    return '&'
  })
  txt = txt.replace(/¶ß/g, function () {
    return ';'
  })

  return txt
}
/**
 * ##render
 * Function that renders an svg with a graph from a chart definition. Usage example below.
 *
 * ```
 * mermaidAPI.initialize({
 *      startOnLoad:true
 *  });
 *  $(function(){
 *      const graphDefinition = 'graph TB\na-->b';
 *      const cb = function(svgGraph){
 *          console.log(svgGraph);
 *      };
 *      mermaidAPI.render('id1',graphDefinition,cb);
 *  });
 *```
 * @param id the id of the element to be rendered
 * @param txt the graph definition
 * @param cb callback which is called after rendering is finished with the svg code as inparam.
 * @param container selector to element in which a div with the graph temporarily will be inserted. In one is
 * provided a hidden div will be inserted in the body of the page instead. The element will be removed when rendering is
 * completed.
 */
const render = function (id, txt, cb, container) {
  if (typeof container !== 'undefined') {
    container.innerHTML = ''

    d3.select(container).append('div')
      .attr('id', 'd' + id)
      .append('svg')
      .attr('id', id)
      .attr('width', '100%')
      .attr('xmlns', 'http://www.w3.org/2000/svg')
      .append('g')
  } else {
    const element = document.querySelector('#' + 'd' + id)
    if (element) {
      element.innerHTML = ''
    }

    d3.select('body').append('div')
      .attr('id', 'd' + id)
      .append('svg')
      .attr('id', id)
      .attr('width', '100%')
      .attr('xmlns', 'http://www.w3.org/2000/svg')
      .append('g')
  }

  window.txt = txt
  txt = encodeEntities(txt)

  const element = d3.select('#d' + id).node()
  const graphType = utils.detectType(txt)

  // insert inline style into svg
  const svg = element.firstChild
  const firstChild = svg.firstChild

  // pre-defined theme
  let style = themes[config.theme]
  if (style === undefined) {
    style = ''
  }

  // user provided theme CSS
  if (config.themeCSS !== undefined) {
    style += `\n${config.themeCSS}`
  }

  // classDef
  if (graphType === 'flowchart') {
    const classes = flowRenderer.getClasses(txt)
    for (const className in classes) {
      style += `\n.${className} > * { ${classes[className].styles.join(' !important; ')} !important; }`
    }
  }

  const style1 = document.createElement('style')
  style1.innerHTML = scope(style, `#${id}`)
  svg.insertBefore(style1, firstChild)

  const style2 = document.createElement('style')
  const cs = window.getComputedStyle(svg)
  style2.innerHTML = `#${id} {
    color: ${cs.color};
    font: ${cs.font};
  }`
  svg.insertBefore(style2, firstChild)

  switch (graphType) {
    case 'git':
      config.flowchart.arrowMarkerAbsolute = config.arrowMarkerAbsolute
      gitGraphRenderer.setConf(config.git)
      gitGraphRenderer.draw(txt, id, false)
      break
    case 'flowchart':
      config.flowchart.arrowMarkerAbsolute = config.arrowMarkerAbsolute
      flowRenderer.setConf(config.flowchart)
      flowRenderer.draw(txt, id, false)
      break
    case 'sequence':
      config.sequence.arrowMarkerAbsolute = config.arrowMarkerAbsolute
      if (config.sequenceDiagram) { // backwards compatibility
        sequenceRenderer.setConf(Object.assign(config.sequence, config.sequenceDiagram))
        console.error('`mermaid config.sequenceDiagram` has been renamed to `config.sequence`. Please update your mermaid config.')
      } else {
        sequenceRenderer.setConf(config.sequence)
      }
      sequenceRenderer.draw(txt, id)
      break
    case 'gantt':
      config.gantt.arrowMarkerAbsolute = config.arrowMarkerAbsolute
      ganttRenderer.setConf(config.gantt)
      ganttRenderer.draw(txt, id)
      break
    case 'class':
      config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute
      classRenderer.setConf(config.class)
      classRenderer.draw(txt, id)
      break
  }

  d3.select(`[id="${id}"]`).selectAll('foreignobject > *').attr('xmlns', 'http://www.w3.org/1999/xhtml')

  let url = ''
  if (config.arrowMarkerAbsolute) {
    url = window.location.protocol + '//' + window.location.host + window.location.pathname + window.location.search
    url = url.replace(/\(/g, '\\(')
    url = url.replace(/\)/g, '\\)')
  }

  // Fix for when the base tag is used
  let svgCode = d3.select('#d' + id).node().innerHTML.replace(/url\(#arrowhead/g, 'url(' + url + '#arrowhead', 'g')

  svgCode = decodeEntities(svgCode)

  if (typeof cb !== 'undefined') {
    cb(svgCode, flowDb.bindFunctions)
  } else {
    logger.warn('CB = undefined!')
  }

  const node = d3.select('#d' + id).node()
  if (node !== null && typeof node.remove === 'function') {
    d3.select('#d' + id).node().remove()
  }

  return svgCode
}

const setConf = function (cnf) {
  // Top level initially mermaid, gflow, sequenceDiagram and gantt
  const lvl1Keys = Object.keys(cnf)
  for (let i = 0; i < lvl1Keys.length; i++) {
    if (typeof cnf[lvl1Keys[i]] === 'object' && cnf[lvl1Keys[i]] != null) {
      const lvl2Keys = Object.keys(cnf[lvl1Keys[i]])

      for (let j = 0; j < lvl2Keys.length; j++) {
        logger.debug('Setting conf ', lvl1Keys[i], '-', lvl2Keys[j])
        if (typeof config[lvl1Keys[i]] === 'undefined') {
          config[lvl1Keys[i]] = {}
        }
        logger.debug('Setting config: ' + lvl1Keys[i] + ' ' + lvl2Keys[j] + ' to ' + cnf[lvl1Keys[i]][lvl2Keys[j]])
        config[lvl1Keys[i]][lvl2Keys[j]] = cnf[lvl1Keys[i]][lvl2Keys[j]]
      }
    } else {
      config[lvl1Keys[i]] = cnf[lvl1Keys[i]]
    }
  }
}

function initialize (options) {
  logger.debug('Initializing mermaidAPI')
  // Update default config with options supplied at initialization
  if (typeof options === 'object') {
    setConf(options)
  }
  setLogLevel(config.logLevel)
}

function getConfig () {
  return config
}

const mermaidAPI = {
  render,
  parse,
  initialize,
  getConfig
}

export default mermaidAPI
<commit_msg>Fix code style issue<commit_after>/**
 * ---
 * title: mermaidAPI
 * order: 5
 * ---
 * # mermaidAPI
 * This is the api to be used when handling the integration with the web page instead of using the default integration
 * (mermaid.js).
 *
 * The core of this api is the **render** function that given a graph definitionas text renders the graph/diagram and
 * returns a svg element for the graph. It is is then up to the user of the API to make use of the svg, either insert it
 * somewhere in the page or something completely different.
*/
import * as d3 from 'd3'
import scope from 'scope-css'

import { logger, setLogLevel } from './logger'
import utils from './utils'
import flowRenderer from './diagrams/flowchart/flowRenderer'
import flowParser from './diagrams/flowchart/parser/flow'
import flowDb from './diagrams/flowchart/flowDb'
import sequenceRenderer from './diagrams/sequence/sequenceRenderer'
import sequenceParser from './diagrams/sequence/parser/sequenceDiagram'
import sequenceDb from './diagrams/sequence/sequenceDb'
import ganttRenderer from './diagrams/gantt/ganttRenderer'
import ganttParser from './diagrams/gantt/parser/gantt'
import ganttDb from './diagrams/gantt/ganttDb'
import classRenderer from './diagrams/class/classRenderer'
import classParser from './diagrams/class/parser/classDiagram'
import classDb from './diagrams/class/classDb'
import gitGraphRenderer from './diagrams/git/gitGraphRenderer'
import gitGraphParser from './diagrams/git/parser/gitGraph'
import gitGraphAst from './diagrams/git/gitGraphAst'

const themes = {}
for (const themeName of ['default', 'forest', 'dark', 'neutral']) {
  themes[themeName] = require(`./themes/${themeName}/index.scss`)
}

/**
 * ## Configuration
 * These are the default options which can be overridden with the initialization call as in the example below:
 * ```
 * mermaid.initialize({
 *   flowchart:{
 *      htmlLabels: false
 *   }
 * });
 * ```
 */
const config = {

  /** theme , the CSS style sheet
  *
  * **theme** - Choose one of the built-in themes: default, forest, dark or neutral. To disable any pre-defined mermaid theme, use "null".
  * **themeCSS** - Use your own CSS. This overrides **theme**.
  *```
  * "theme": "forest",
  * "themeCSS": ".node rect { fill: red; }"
  *```
  */

  theme: 'default',
  themeCSS: undefined,

  /**
   * logLevel , decides the amount of logging to be used.
   *    * debug: 1
   *    * info: 2
   *    * warn: 3
   *    * error: 4
   *    * fatal: 5
   */
  logLevel: 5,

  /**
   * **startOnLoad** - This options controls whether or mermaid starts when the page loads
   */
  startOnLoad: true,

  /**
   * **arrowMarkerAbsolute** - This options controls whether or arrow markers in html code will be absolute paths or
   * an anchor, #. This matters if you are using base tag settings.
   */
  arrowMarkerAbsolute: false,

  /**
   * ### flowchart
   * *The object containing configurations specific for flowcharts*
   */
  flowchart: {
    /**
     * **htmlLabels** - Flag for setting whether or not a html tag should be used for rendering labels
     * on the edges
     */
    htmlLabels: true,

    curve: 'linear'
  },

  /**
   * ###  sequenceDiagram
   * The object containing configurations specific for sequence diagrams
   */
  sequence: {

    /**
     * **diagramMarginX** - margin to the right and left of the sequence diagram
     */
    diagramMarginX: 50,

    /**
     * **diagramMarginY** - margin to the over and under the sequence diagram
     */
    diagramMarginY: 10,

    /**
     * **actorMargin** - Margin between actors
     */
    actorMargin: 50,

    /**
     * **width** - Width of actor boxes
     */
    width: 150,

    /**
     * **height** - Height of actor boxes
     */
    height: 65,

    /**
     * **boxMargin** - Margin around loop boxes
     */
    boxMargin: 10,

    /**
     * **boxTextMargin** - margin around the text in loop/alt/opt boxes
     */
    boxTextMargin: 5,

    /**
     * **noteMargin** - margin around notes
     */
    noteMargin: 10,

    /**
     * **messageMargin** - Space between messages
     */
    messageMargin: 35,

    /**
     * **mirrorActors** - mirror actors under diagram
     */
    mirrorActors: true,

    /**
     * **bottomMarginAdj** - Depending on css styling this might need adjustment.
     * Prolongs the edge of the diagram downwards
     */
    bottomMarginAdj: 1,

    /**
     * **useMaxWidth** - when this flag is set the height and width is set to 100% and is then scaling with the
     * available space if not the absolute space required is used
     */
    useMaxWidth: true
  },

  /** ### gantt
   * The object containing configurations specific for gantt diagrams*
   */
  gantt: {
    /**
     * **titleTopMargin** - margin top for the text over the gantt diagram
     */
    titleTopMargin: 25,

    /**
     * **barHeight** - the height of the bars in the graph
     */
    barHeight: 20,

    /**
     * **barGap** - the margin between the different activities in the gantt diagram
     */
    barGap: 4,

    /**
     *  **topPadding** - margin between title and gantt diagram and between axis and gantt diagram.
     */
    topPadding: 50,

    /**
     *  **leftPadding** - the space allocated for the section name to the left of the activities.
     */
    leftPadding: 75,

    /**
     *  **gridLineStartPadding** - Vertical starting position of the grid lines
     */
    gridLineStartPadding: 35,

    /**
     *  **fontSize** - font size ...
     */
    fontSize: 11,

    /**
     * **fontFamily** - font family ...
     */
    fontFamily: '"Open-Sans", "sans-serif"',

    /**
     * **numberSectionStyles** - the number of alternating section styles
     */
    numberSectionStyles: 4,

    /**
     * **axisFormat** - datetime format of the axis, this might need adjustment to match your locale and preferences
     */
    axisFormat: '%Y-%m-%d'
  },
  class: {},
  git: {}
}

setLogLevel(config.logLevel)

function parse (text) {
  const graphType = utils.detectType(text)
  let parser

  switch (graphType) {
    case 'git':
      parser = gitGraphParser
      parser.parser.yy = gitGraphAst
      break
    case 'flowchart':
      parser = flowParser
      parser.parser.yy = flowDb
      break
    case 'sequence':
      parser = sequenceParser
      parser.parser.yy = sequenceDb
      break
    case 'gantt':
      parser = ganttParser
      parser.parser.yy = ganttDb
      break
    case 'class':
      parser = classParser
      parser.parser.yy = classDb
      break
  }

  parser.parser.yy.parseError = (str, hash) => {
    const error = { str, hash }
    throw error
  }

  parser.parse(text)
}

export const encodeEntities = function (text) {
  let txt = text

  txt = txt.replace(/style.*:\S*#.*;/g, function (s) {
    const innerTxt = s.substring(0, s.length - 1)
    return innerTxt
  })
  txt = txt.replace(/classDef.*:\S*#.*;/g, function (s) {
    const innerTxt = s.substring(0, s.length - 1)
    return innerTxt
  })

  txt = txt.replace(/#\w+;/g, function (s) {
    const innerTxt = s.substring(1, s.length - 1)

    const isInt = /^\+?\d+$/.test(innerTxt)
    if (isInt) {
      return 'ﬂ°°' + innerTxt + '¶ß'
    } else {
      return 'ﬂ°' + innerTxt + '¶ß'
    }
  })

  return txt
}

export const decodeEntities = function (text) {
  let txt = text

  txt = txt.replace(/ﬂ°°/g, function () {
    return '&#'
  })
  txt = txt.replace(/ﬂ°/g, function () {
    return '&'
  })
  txt = txt.replace(/¶ß/g, function () {
    return ';'
  })

  return txt
}
/**
 * ##render
 * Function that renders an svg with a graph from a chart definition. Usage example below.
 *
 * ```
 * mermaidAPI.initialize({
 *      startOnLoad:true
 *  });
 *  $(function(){
 *      const graphDefinition = 'graph TB\na-->b';
 *      const cb = function(svgGraph){
 *          console.log(svgGraph);
 *      };
 *      mermaidAPI.render('id1',graphDefinition,cb);
 *  });
 *```
 * @param id the id of the element to be rendered
 * @param txt the graph definition
 * @param cb callback which is called after rendering is finished with the svg code as inparam.
 * @param container selector to element in which a div with the graph temporarily will be inserted. In one is
 * provided a hidden div will be inserted in the body of the page instead. The element will be removed when rendering is
 * completed.
 */
const render = function (id, txt, cb, container) {
  if (typeof container !== 'undefined') {
    container.innerHTML = ''

    d3.select(container).append('div')
      .attr('id', 'd' + id)
      .append('svg')
      .attr('id', id)
      .attr('width', '100%')
      .attr('xmlns', 'http://www.w3.org/2000/svg')
      .append('g')
  } else {
    const element = document.querySelector('#' + 'd' + id)
    if (element) {
      element.innerHTML = ''
    }

    d3.select('body').append('div')
      .attr('id', 'd' + id)
      .append('svg')
      .attr('id', id)
      .attr('width', '100%')
      .attr('xmlns', 'http://www.w3.org/2000/svg')
      .append('g')
  }

  window.txt = txt
  txt = encodeEntities(txt)

  const element = d3.select('#d' + id).node()
  const graphType = utils.detectType(txt)

  // insert inline style into svg
  const svg = element.firstChild
  const firstChild = svg.firstChild

  // pre-defined theme
  let style = themes[config.theme]
  if (style === undefined) {
    style = ''
  }

  // user provided theme CSS
  if (config.themeCSS !== undefined) {
    style += `\n${config.themeCSS}`
  }

  // classDef
  if (graphType === 'flowchart') {
    const classes = flowRenderer.getClasses(txt)
    for (const className in classes) {
      style += `\n.${className} > * { ${classes[className].styles.join(' !important; ')} !important; }`
    }
  }

  const style1 = document.createElement('style')
  style1.innerHTML = scope(style, `#${id}`)
  svg.insertBefore(style1, firstChild)

  const style2 = document.createElement('style')
  const cs = window.getComputedStyle(svg)
  style2.innerHTML = `#${id} {
    color: ${cs.color};
    font: ${cs.font};
  }`
  svg.insertBefore(style2, firstChild)

  switch (graphType) {
    case 'git':
      config.flowchart.arrowMarkerAbsolute = config.arrowMarkerAbsolute
      gitGraphRenderer.setConf(config.git)
      gitGraphRenderer.draw(txt, id, false)
      break
    case 'flowchart':
      config.flowchart.arrowMarkerAbsolute = config.arrowMarkerAbsolute
      flowRenderer.setConf(config.flowchart)
      flowRenderer.draw(txt, id, false)
      break
    case 'sequence':
      config.sequence.arrowMarkerAbsolute = config.arrowMarkerAbsolute
      if (config.sequenceDiagram) { // backwards compatibility
        sequenceRenderer.setConf(Object.assign(config.sequence, config.sequenceDiagram))
        console.error('`mermaid config.sequenceDiagram` has been renamed to `config.sequence`. Please update your mermaid config.')
      } else {
        sequenceRenderer.setConf(config.sequence)
      }
      sequenceRenderer.draw(txt, id)
      break
    case 'gantt':
      config.gantt.arrowMarkerAbsolute = config.arrowMarkerAbsolute
      ganttRenderer.setConf(config.gantt)
      ganttRenderer.draw(txt, id)
      break
    case 'class':
      config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute
      classRenderer.setConf(config.class)
      classRenderer.draw(txt, id)
      break
  }

  d3.select(`[id="${id}"]`).selectAll('foreignobject > *').attr('xmlns', 'http://www.w3.org/1999/xhtml')

  let url = ''
  if (config.arrowMarkerAbsolute) {
    url = window.location.protocol + '//' + window.location.host + window.location.pathname + window.location.search
    url = url.replace(/\(/g, '\\(')
    url = url.replace(/\)/g, '\\)')
  }

  // Fix for when the base tag is used
  let svgCode = d3.select('#d' + id).node().innerHTML.replace(/url\(#arrowhead/g, 'url(' + url + '#arrowhead', 'g')

  svgCode = decodeEntities(svgCode)

  if (typeof cb !== 'undefined') {
    cb(svgCode, flowDb.bindFunctions)
  } else {
    logger.warn('CB = undefined!')
  }

  const node = d3.select('#d' + id).node()
  if (node !== null && typeof node.remove === 'function') {
    d3.select('#d' + id).node().remove()
  }

  return svgCode
}

const setConf = function (cnf) {
  // Top level initially mermaid, gflow, sequenceDiagram and gantt
  const lvl1Keys = Object.keys(cnf)
  for (let i = 0; i < lvl1Keys.length; i++) {
    if (typeof cnf[lvl1Keys[i]] === 'object' && cnf[lvl1Keys[i]] != null) {
      const lvl2Keys = Object.keys(cnf[lvl1Keys[i]])

      for (let j = 0; j < lvl2Keys.length; j++) {
        logger.debug('Setting conf ', lvl1Keys[i], '-', lvl2Keys[j])
        if (typeof config[lvl1Keys[i]] === 'undefined') {
          config[lvl1Keys[i]] = {}
        }
        logger.debug('Setting config: ' + lvl1Keys[i] + ' ' + lvl2Keys[j] + ' to ' + cnf[lvl1Keys[i]][lvl2Keys[j]])
        config[lvl1Keys[i]][lvl2Keys[j]] = cnf[lvl1Keys[i]][lvl2Keys[j]]
      }
    } else {
      config[lvl1Keys[i]] = cnf[lvl1Keys[i]]
    }
  }
}

function initialize (options) {
  logger.debug('Initializing mermaidAPI')
  // Update default config with options supplied at initialization
  if (typeof options === 'object') {
    setConf(options)
  }
  setLogLevel(config.logLevel)
}

function getConfig () {
  return config
}

const mermaidAPI = {
  render,
  parse,
  initialize,
  getConfig
}

export default mermaidAPI
<|endoftext|>


<commit_before>"use strict";

const { escapeRegExp } = require("lodash");
const hostedGitInfo = require("hosted-git-info");

class GitVersionParser {
  constructor(versionPrefix = "v") {
    this._gitUrlPattern = new RegExp(`(.+?#${escapeRegExp(versionPrefix)})(.+)$`);
  }

  parseVersion(version) {
    const gitInfo = hostedGitInfo.fromUrl(version);
    let targetMatches;

    if (gitInfo && gitInfo.committish) {
      targetMatches = this._gitUrlPattern.exec(version);
    }

    return {
      prefix: targetMatches ? targetMatches[1] : null,
      version: targetMatches ? targetMatches[2] : version,
    };
  }
}

module.exports = GitVersionParser;
<commit_msg>chore: don't destructure lodash require<commit_after>"use strict";

const _ = require("lodash");
const hostedGitInfo = require("hosted-git-info");

class GitVersionParser {
  constructor(versionPrefix = "v") {
    this._gitUrlPattern = new RegExp(`(.+?#${_.escapeRegExp(versionPrefix)})(.+)$`);
  }

  parseVersion(version) {
    const gitInfo = hostedGitInfo.fromUrl(version);
    let targetMatches;

    if (gitInfo && gitInfo.committish) {
      targetMatches = this._gitUrlPattern.exec(version);
    }

    return {
      prefix: targetMatches ? targetMatches[1] : null,
      version: targetMatches ? targetMatches[2] : version,
    };
  }
}

module.exports = GitVersionParser;
<|endoftext|>


<commit_before>const Should = require('should');
const Hapi = require('@hapi/hapi');
const Joi = require('@hapi/joi');
const Path = require('path');
const Boom = require('boom');
const Locale = require('../');
const Vision = require('vision');
const Handlebars = require('handlebars');

const translateString_en = 'All\'s well that ends well.';
const translateString_en_GB = 'All\'s well that ends well (british version).';
const translateString_de = 'Ende gut, alles gut.';
const translateString_fr = 'Tout est bien qui finit bien.';

Handlebars.registerHelper('i18n',function(context){
  return this.__(context);
});

async function setupServer() {
  const serverOptions = {
    port: 8047
  };

  const server = new Hapi.Server(serverOptions);

  var doSomething = () => {
    return new Promise((resolve) => {
      var data = {
        rows: []
      };
      for (i = 1; i < 100; ++i) {
        data.rows.push(i);
      }
      var fs = require('fs');
      var fileName = Path.join(__dirname, 'database.json');
      fs.writeFile(fileName, JSON.stringify(data), function (err) {
        Should.not.exist(err);
        resolve();
      });
    });
  };

  server.route({
    method: 'GET',
    path: '/no/language-code/path/parameter',
    options: {
      handler: async function (request, h) {
        await doSomething();
        return {
          locale: request.i18n.getLocale(),
          message: request.i18n.__(translateString_en)
        };
      }
    }
  });

  server.route({
    method: 'GET',
    path: '/{languageCode}/localized/resource',
    options: {
      handler: async function (request, h) {
        await doSomething();
        return ({
          locale: request.i18n.getLocale(),
          requestedLocale: request.params.languageCode,
          message: request.i18n.__(translateString_en)
        });
      }
    }
  });

  server.route({
    method: 'POST',
    path: '/{languageCode}/localized/validation',
    options: {
      handler: function () {
      },
      validate: {
        payload: Joi.object({
          param: Joi.string().required()
        }),
        failAction: (request, h, error) => {
          throw Boom.badRequest(request.i18n.__('Validation failed'));
        }
      }
    }
  });

  server.route({
    method: 'GET',
    path: '/localized/with/headers',
    options: {
      handler: async function (request, h) {
        await doSomething();
        return ({
          locale: request.i18n.getLocale(),
          requestedLocale: request.headers['accept-language'],
          message: request.i18n.__(translateString_en)
        });
      }
    }
  });

  server.route({
    method: 'GET',
    path: '/localized/with/query',
    options: {
      handler: async function (request, h) {
        await doSomething();
        return ({
          locale: request.i18n.getLocale(),
          requestedLocale: request.query['lang'],
          message: request.i18n.__(translateString_en)
        });
      }
    }
  });

  server.route({
    method: 'GET',
    path: '/localized/with/empty',
    options: {
      handler: async function (request, h) {
        await doSomething();
        return ({
          locale: request.i18n.getLocale(),
          requestedLocale: request.query['lang'],
          message: request.i18n.__('')
        });
      }
    }
  });

  server.route({
    method: 'GET',
    path: '/{languageCode}/localized/view',
    options: {
      handler: async function (request, h) {
        await doSomething();
        return h.view('test',{
          title: 'Hapi i18n handlebars test',
          message: 'All\'s well that ends well.',
          song: request.i18n.__n('%s bottles of beer on the wall.', 99),
          languageCode: request.params.languageCode
        });
      }
    }
  });

  return server;
}

async function startServer(server) {
  try {
    await server.register(Vision);
    server.views({
      engines: {
        html: require('handlebars'),
      },
      relativeTo: __dirname,
      path: __dirname + '/views',

    });
    await server.start();
  }
  catch (err) {
    console.log(err);
    process.exit(1);
  }
}

describe('Localization', function () {
  describe('Usage of locale in hapi', function () {
    let server;

    before(async () => {
      server = await setupServer();
      await startServer(server);
    });

    after(async () => {
      const err = await server.stop({ timeout: 5000 });
    });

    it('can be added as plugin', async () => {
      const i18n_options = {
        locales: ['de', 'en-GB', 'en', 'fr'],
        directory: __dirname + '/locales',
        languageHeaderField: 'Accept-language',
        queryParameter: 'lang'
      };
      await server.register({plugin: Locale, options:i18n_options});
    });

    it('extracts the default locale from the configured locales', () => {
      Should.throws(() => {
        Locale.extractDefaultLocale()
      }, Error);
      Should.throws(() => {
        Locale.extractDefaultLocale([])
      }, Error);
      Locale.extractDefaultLocale(['fr', 'de']).should.equal('fr');
    });

    it('uses the default locale if no language code path parameter is available', async () => {
      const response = await server.inject(
        {
          method: 'GET',
          url: '/no/language-code/path/parameter'
        })
      response.result.locale.should.equal('de');
      response.result.message.should.equal(translateString_de);
    });

    it('uses the requested locale if language code is provided', async () => {
      const response = await server.inject(
        {
          method: 'GET',
          url: '/fr/localized/resource'
        })
      response.result.locale.should.equal('fr');
      response.result.requestedLocale.should.equal('fr');
      response.result.message.should.equal(translateString_fr);
    });

    it('uses the requested locale if language code is provided in headers', async () => {
      let response = await server.inject(
        {
          method: 'GET',
          url: '/localized/with/headers',
          headers: {
            'accept-language': 'fr-CA,en-GB,en-US;q=0.9;q=0.7,en;q=0.8'
          }
        }
      );
      response.result.locale.should.equal('fr');
      response.result.requestedLocale.should.equal('fr-CA,en-GB,en-US;q=0.9;q=0.7,en;q=0.8');
      response.result.message.should.equal(translateString_fr);
      response = await server.inject(
          {
            method: 'GET',
            url: '/localized/with/headers',
            headers: {
              'Accept-Language': 'es,en-GB,en-US;q=0.9;q=0.7,en;q=0.8'
            }
          }
      );
      response.result.locale.should.equal('en-GB');
      response.result.message.should.equal(translateString_en_GB);
      response = await server.inject(
        {
          method: 'GET',
          url: '/localized/with/headers',
          headers: {}
        }
      );
      response.result.locale.should.equal('de');
      response.result.message.should.equal(translateString_de);
    });

    it('uses the default locale if language codes in header don\'t match', async () => {
      let response = await server.inject(
        {
          method: 'GET',
          url: '/localized/with/headers',
          headers: {
            'accept-language': 'es,it'
          }
        }
      );
      response.result.locale.should.equal('de');
      response.result.message.should.equal(translateString_de);
    });

    it('uses the language query parameter over the header parameter because this is more explicit', async () => {
      const response = await server.inject(
        {
          method: 'GET',
          url: '/localized/with/query?lang=fr'
        }
      );
      response.result.locale.should.equal('fr');
      response.result.requestedLocale.should.equal('fr');
      response.result.message.should.equal(translateString_fr);
    });

    it('should return an empty string with empty parameter', function () {
      return server.inject(
        {
          method: 'GET',
          url: '/localized/with/empty',
          headers: {
            'accept-language': 'fr'
          }
        })
        .then ( (response) => {
          response.result.locale.should.equal('fr');
          response.result.message.should.equal('');
        });
    });

    it('uses the language path parameter over the header parameter because this is more explicit', async () => {
      const response = await server.inject(
        {
          method: 'GET',
          url: '/fr/localized/resource',
          headers: {
            'accept-language': 'en'
          }
        }
      );
      response.result.locale.should.equal('fr');
      response.result.requestedLocale.should.equal('fr');
      response.result.message.should.equal(translateString_fr);
    });

    it('translates localized strings in handlebars templates', async () => {
      const response = await server.inject(
        {
          method: 'GET',
          url: '/fr/localized/view'
        }
      );
      response.statusCode.should.equal(200);
      response.result.should.equal('<!DOCTYPE html>
Tout est bien qui finit bien.

99 bouteilles de bière sur le mur.

Langue courante: fr
\n');
    });

    it('returns status code NOT-FOUND if the requested locale is not available', async () => {
      const response = await server.inject(
        {
          method: 'GET',
          url: '/en-US/localized/resource'
        }
       );
        const payload = JSON.parse(response.payload);
        payload.statusCode.should.equal(404);
        payload.message.should.equal('No localization available for en-US');
    });

    it('is available in the validation failAction handler ', async () => {
      const response = await server.inject(
        {
          method: 'POST',
          url: '/de/localized/validation'
        }
      );
      var payload = JSON.parse(response.payload);
      payload.statusCode.should.equal(400);
      payload.message.should.equal('Prüfung fehlgeschlagen');
    });

    it('must assure correct localization when processing requests concurrently', function (done) {
      var numIterations = 200;
      var numRequestsPerIteration = 3;
      var numProcessedRequests = 0;
      var numTotalRequests = numIterations * numRequestsPerIteration;
      var numErrorsWrongDefaultLocale = 0;
      var numErrorsWrongTranslation = 0;
      var numErrorsWrongRequestedLocale = 0;
      this.timeout(numTotalRequests * 10);
      for (iteration = 0; iteration < numIterations; ++iteration) {

        var onLastResponse = function () {
          numProcessedRequests.should.equal(numTotalRequests);
          numErrorsWrongDefaultLocale.should.equal(0);
          numErrorsWrongRequestedLocale.should.equal(0);
          done();
        };

        server.inject(
          {
            method: 'GET',
            url: '/no/language-code/path/parameter'
          })
          .then ( (response) => {
            if (response.result.locale !== 'de') {
              ++numErrorsWrongDefaultLocale;
            }
            if (response.result.message !== translateString_de) {
              ++numErrorsWrongTranslation;
            }
            ++numProcessedRequests;
            if (numProcessedRequests == numTotalRequests) {
              onLastResponse();
            }
          }
        );

        server.inject(
          {
            method: 'GET',
            url: '/en/localized/resource'
          })
          .then ( (response) => {
            if (response.result.locale !== 'en') {
              ++numErrorsWrongRequestedLocale;
            }
            if (response.result.message !== translateString_en) {
              ++numErrorsWrongTranslation;
            }
            response.result.requestedLocale.should.equal('en');
            ++numProcessedRequests;
            if (numProcessedRequests == numTotalRequests) {
              onLastResponse();
            }
          }
        );

        server.inject(
          {
            method: 'GET',
            url: '/fr/localized/resource'
          })
          .then ( (response) => {
            if (response.result.locale !== 'fr') {
              ++numErrorsWrongRequestedLocale;
            }
            response.result.requestedLocale.should.equal('fr');
            ++numProcessedRequests;
            if (numProcessedRequests == numTotalRequests) {
              onLastResponse();
            }
          }
        );
      }
    })

  });

  describe('with a custom 404 handler', () => {
    let server;

    before(async () => {
      server = await setupServer();

      const i18n_options = {
        locales: ['de', 'en-GB', 'en', 'fr'],
        directory: __dirname + '/locales',
        languageHeaderField: 'accept-language',
        queryParameter: 'lang'
      };
      await server.register({plugin: Locale, options:i18n_options});

      server.ext('onPreResponse', (request, h) => {
        const response = request.response;
        if (response.isBoom) {
          if (response.output.statusCode === 404) {
            return h.view('test',{
              title: 'Hapi i18n handlebars test',
              message: 'All\'s well that ends well.',
              song: request.i18n.__n('%s bottles of beer on the wall.', 99),
              languageCode: request.params.languageCode
            }).code(404);
          }
        }

        return h.continue;
      }, {before: 'hapi-i18n'});

      await startServer(server);
    });

    after(async () => {
      const err = await server.stop({ timeout: 5000 });
    });

    it('is still available when handling missing routes', async () => {
      const response = await server.inject({
        method: 'GET',
        url: '/does-not-exist'
      });
      response.statusCode.should.equal(404);
      should(response.result).startWith('<!DOCTYPE html><html lang=');
    });
  });

})
<commit_msg>Added test to verfify plugin returns with continue when language code is not found<commit_after>const Should = require('should');
const Hapi = require('@hapi/hapi');
const Joi = require('@hapi/joi');
const Path = require('path');
const Boom = require('boom');
const Locale = require('../');
const Vision = require('vision');
const Handlebars = require('handlebars');

const translateString_en = 'All\'s well that ends well.';
const translateString_en_GB = 'All\'s well that ends well (british version).';
const translateString_de = 'Ende gut, alles gut.';
const translateString_fr = 'Tout est bien qui finit bien.';

Handlebars.registerHelper('i18n',function(context){
  return this.__(context);
});

async function setupServer() {
  const serverOptions = {
    port: 8047
  };

  const server = new Hapi.Server(serverOptions);

  var doSomething = () => {
    return new Promise((resolve) => {
      var data = {
        rows: []
      };
      for (i = 1; i < 100; ++i) {
        data.rows.push(i);
      }
      var fs = require('fs');
      var fileName = Path.join(__dirname, 'database.json');
      fs.writeFile(fileName, JSON.stringify(data), function (err) {
        Should.not.exist(err);
        resolve();
      });
    });
  };

  server.route({
    method: 'GET',
    path: '/no/language-code/path/parameter',
    options: {
      handler: async function (request, h) {
        await doSomething();
        return {
          locale: request.i18n.getLocale(),
          message: request.i18n.__(translateString_en)
        };
      }
    }
  });

  server.route({
    method: 'GET',
    path: '/{languageCode}/localized/resource',
    options: {
      handler: async function (request, h) {
        await doSomething();
        return ({
          locale: request.i18n.getLocale(),
          requestedLocale: request.params.languageCode,
          message: request.i18n.__(translateString_en)
        });
      }
    }
  });

  server.route({
    method: 'POST',
    path: '/{languageCode}/localized/validation',
    options: {
      handler: function () {
      },
      validate: {
        payload: Joi.object({
          param: Joi.string().required()
        }),
        failAction: (request, h, error) => {
          throw Boom.badRequest(request.i18n.__('Validation failed'));
        }
      }
    }
  });

  server.route({
    method: 'GET',
    path: '/localized/with/headers',
    options: {
      handler: async function (request, h) {
        await doSomething();
        return ({
          locale: request.i18n.getLocale(),
          requestedLocale: request.headers['accept-language'],
          message: request.i18n.__(translateString_en)
        });
      }
    }
  });

  server.route({
    method: 'GET',
    path: '/localized/with/query',
    options: {
      handler: async function (request, h) {
        await doSomething();
        return ({
          locale: request.i18n.getLocale(),
          requestedLocale: request.query['lang'],
          message: request.i18n.__(translateString_en)
        });
      }
    }
  });

  server.route({
    method: 'GET',
    path: '/localized/with/empty',
    options: {
      handler: async function (request, h) {
        await doSomething();
        return ({
          locale: request.i18n.getLocale(),
          requestedLocale: request.query['lang'],
          message: request.i18n.__('')
        });
      }
    }
  });

  server.route({
    method: 'GET',
    path: '/{languageCode}/localized/view',
    options: {
      handler: async function (request, h) {
        await doSomething();
        return h.view('test',{
          title: 'Hapi i18n handlebars test',
          message: 'All\'s well that ends well.',
          song: request.i18n.__n('%s bottles of beer on the wall.', 99),
          languageCode: request.params.languageCode
        });
      }
    }
  });

  return server;
}

async function startServer(server) {
  try {
    await server.register(Vision);
    server.views({
      engines: {
        html: require('handlebars'),
      },
      relativeTo: __dirname,
      path: __dirname + '/views',

    });
    await server.start();
  }
  catch (err) {
    console.log(err);
    process.exit(1);
  }
}

describe('Localization', function () {
  describe('Usage of locale in hapi', function () {
    let server;

    before(async () => {
      server = await setupServer();
      await startServer(server);
    });

    after(async () => {
      const err = await server.stop({ timeout: 5000 });
    });

    it('can be added as plugin', async () => {
      const i18n_options = {
        locales: ['de', 'en-GB', 'en', 'fr'],
        directory: __dirname + '/locales',
        languageHeaderField: 'Accept-language',
        queryParameter: 'lang'
      };
      await server.register({plugin: Locale, options:i18n_options});
    });

    it('extracts the default locale from the configured locales', () => {
      Should.throws(() => {
        Locale.extractDefaultLocale()
      }, Error);
      Should.throws(() => {
        Locale.extractDefaultLocale([])
      }, Error);
      Locale.extractDefaultLocale(['fr', 'de']).should.equal('fr');
    });

    it('uses the default locale if no language code path parameter is available', async () => {
      const response = await server.inject(
        {
          method: 'GET',
          url: '/no/language-code/path/parameter'
        })
      response.result.locale.should.equal('de');
      response.result.message.should.equal(translateString_de);
    });

    it('uses the requested locale if language code is provided', async () => {
      const response = await server.inject(
        {
          method: 'GET',
          url: '/fr/localized/resource'
        })
      response.result.locale.should.equal('fr');
      response.result.requestedLocale.should.equal('fr');
      response.result.message.should.equal(translateString_fr);
    });

    it('uses the requested locale if language code is provided in headers', async () => {
      let response = await server.inject(
        {
          method: 'GET',
          url: '/localized/with/headers',
          headers: {
            'accept-language': 'fr-CA,en-GB,en-US;q=0.9;q=0.7,en;q=0.8'
          }
        }
      );
      response.result.locale.should.equal('fr');
      response.result.requestedLocale.should.equal('fr-CA,en-GB,en-US;q=0.9;q=0.7,en;q=0.8');
      response.result.message.should.equal(translateString_fr);
      response = await server.inject(
          {
            method: 'GET',
            url: '/localized/with/headers',
            headers: {
              'Accept-Language': 'es,en-GB,en-US;q=0.9;q=0.7,en;q=0.8'
            }
          }
      );
      response.result.locale.should.equal('en-GB');
      response.result.message.should.equal(translateString_en_GB);
      response = await server.inject(
        {
          method: 'GET',
          url: '/localized/with/headers',
          headers: {}
        }
      );
      response.result.locale.should.equal('de');
      response.result.message.should.equal(translateString_de);
    });

    it('uses the default locale if language codes in header don\'t match', async () => {
      let response = await server.inject(
        {
          method: 'GET',
          url: '/localized/with/headers',
          headers: {
            'accept-language': 'es,it'
          }
        }
      );
      response.result.locale.should.equal('de');
      response.result.message.should.equal(translateString_de);
    });

    it('uses the language query parameter over the header parameter because this is more explicit', async () => {
      const response = await server.inject(
        {
          method: 'GET',
          url: '/localized/with/query?lang=fr'
        }
      );
      response.result.locale.should.equal('fr');
      response.result.requestedLocale.should.equal('fr');
      response.result.message.should.equal(translateString_fr);
    });

    it('should return an empty string with empty parameter', function () {
      return server.inject(
        {
          method: 'GET',
          url: '/localized/with/empty',
          headers: {
            'accept-language': 'fr'
          }
        })
        .then ( (response) => {
          response.result.locale.should.equal('fr');
          response.result.message.should.equal('');
        });
    });

    it('uses the language path parameter over the header parameter because this is more explicit', async () => {
      const response = await server.inject(
        {
          method: 'GET',
          url: '/fr/localized/resource',
          headers: {
            'accept-language': 'en'
          }
        }
      );
      response.result.locale.should.equal('fr');
      response.result.requestedLocale.should.equal('fr');
      response.result.message.should.equal(translateString_fr);
    });

    it('translates localized strings in handlebars templates', async () => {
      const response = await server.inject(
        {
          method: 'GET',
          url: '/fr/localized/view'
        }
      );
      response.statusCode.should.equal(200);
      response.result.should.equal('<!DOCTYPE html>
Tout est bien qui finit bien.

99 bouteilles de bière sur le mur.

Langue courante: fr
\n');
    });

    it('returns status code NOT-FOUND if the requested locale is not available', async () => {
      const response = await server.inject(
        {
          method: 'GET',
          url: '/en-US/localized/resource'
        }
       );
        const payload = JSON.parse(response.payload);
        payload.statusCode.should.equal(404);
        payload.message.should.equal('No localization available for en-US');
    });

    it('allows other plugins to handle pre response', async () => {
      let otherPluginCalled = false;
      const otherPluginWithPreResponseHandling = {
        name: 'onPreResponseTest',
        register: function (server, options) {
          server.ext('onPreResponse', function (request, h) {
            otherPluginCalled = true;
            return h.continue;
          });
        }
      };
      await server.register({plugin: otherPluginWithPreResponseHandling});
      const response = await server.inject(
        {
          method: 'GET',
          url: '/en-US/localized/resource'
        }
      );
      const payload = JSON.parse(response.payload);
      payload.statusCode.should.equal(404);
      otherPluginCalled.should.be.true();
    });

    it('is available in the validation failAction handler ', async () => {
      const response = await server.inject(
        {
          method: 'POST',
          url: '/de/localized/validation'
        }
      );
      var payload = JSON.parse(response.payload);
      payload.statusCode.should.equal(400);
      payload.message.should.equal('Prüfung fehlgeschlagen');
    });

    it('must assure correct localization when processing requests concurrently', function (done) {
      var numIterations = 200;
      var numRequestsPerIteration = 3;
      var numProcessedRequests = 0;
      var numTotalRequests = numIterations * numRequestsPerIteration;
      var numErrorsWrongDefaultLocale = 0;
      var numErrorsWrongTranslation = 0;
      var numErrorsWrongRequestedLocale = 0;
      this.timeout(numTotalRequests * 10);
      for (iteration = 0; iteration < numIterations; ++iteration) {

        var onLastResponse = function () {
          numProcessedRequests.should.equal(numTotalRequests);
          numErrorsWrongDefaultLocale.should.equal(0);
          numErrorsWrongRequestedLocale.should.equal(0);
          done();
        };

        server.inject(
          {
            method: 'GET',
            url: '/no/language-code/path/parameter'
          })
          .then ( (response) => {
            if (response.result.locale !== 'de') {
              ++numErrorsWrongDefaultLocale;
            }
            if (response.result.message !== translateString_de) {
              ++numErrorsWrongTranslation;
            }
            ++numProcessedRequests;
            if (numProcessedRequests == numTotalRequests) {
              onLastResponse();
            }
          }
        );

        server.inject(
          {
            method: 'GET',
            url: '/en/localized/resource'
          })
          .then ( (response) => {
            if (response.result.locale !== 'en') {
              ++numErrorsWrongRequestedLocale;
            }
            if (response.result.message !== translateString_en) {
              ++numErrorsWrongTranslation;
            }
            response.result.requestedLocale.should.equal('en');
            ++numProcessedRequests;
            if (numProcessedRequests == numTotalRequests) {
              onLastResponse();
            }
          }
        );

        server.inject(
          {
            method: 'GET',
            url: '/fr/localized/resource'
          })
          .then ( (response) => {
            if (response.result.locale !== 'fr') {
              ++numErrorsWrongRequestedLocale;
            }
            response.result.requestedLocale.should.equal('fr');
            ++numProcessedRequests;
            if (numProcessedRequests == numTotalRequests) {
              onLastResponse();
            }
          }
        );
      }
    })

  });

  describe('with a custom 404 handler', () => {
    let server;

    before(async () => {
      server = await setupServer();

      const i18n_options = {
        locales: ['de', 'en-GB', 'en', 'fr'],
        directory: __dirname + '/locales',
        languageHeaderField: 'accept-language',
        queryParameter: 'lang'
      };
      await server.register({plugin: Locale, options:i18n_options});

      server.ext('onPreResponse', (request, h) => {
        const response = request.response;
        if (response.isBoom) {
          if (response.output.statusCode === 404) {
            return h.view('test',{
              title: 'Hapi i18n handlebars test',
              message: 'All\'s well that ends well.',
              song: request.i18n.__n('%s bottles of beer on the wall.', 99),
              languageCode: request.params.languageCode
            }).code(404);
          }
        }

        return h.continue;
      }, {before: 'hapi-i18n'});

      await startServer(server);
    });

    after(async () => {
      const err = await server.stop({ timeout: 5000 });
    });

    it('is still available when handling missing routes', async () => {
      const response = await server.inject({
        method: 'GET',
        url: '/does-not-exist'
      });
      response.statusCode.should.equal(404);
      should(response.result).startWith('<!DOCTYPE html><html lang=');
    });

  });

})
<|endoftext|>


<commit_before>(function($) {

  $.declare('weekly', {
    defaults: {
      startTime: 8,
      endTime: 6,
      weekOffset: 0,
      currentDate: new Date(),
      autoRender: true,
      template: '##template##'
    },

    init: function() {
      this.events = [];

      if (this.autoRender) {
        this.update();
      }
    },

    update: function() {
      this.render({
        timef: this.timef,
        getWeekSpan: this.proxy(this.getWeekSpan),
        currentDate: this.currentDate,
        dates: this.getDates(),
        times: this.getTimes()
      });

      for(var i = 0, c = this.events.length; i < c; i++) {
        this.renderEvent(this.events[i]);
      }

      this.timeDifference = (this.endTime + 13) - this.startTime;

      this.registerClickToCreate();

      this.highlightToday();

      if(!this.weekOffset) {
        this.el.find(".weekly-change-today-button").css('display', 'none');
      } else {
        this.el.find(".weekly-change-today-button").css('display', 'block');
      }

      this.el.find('.weekly-time-navigation .weekly-previous-week .week').html(this.getWeekSpan(this.currentDate, this.weekOffset - 1));
      this.el.find('.weekly-time-navigation .weekly-next-week .week').html(this.getWeekSpan(this.currentDate, this.weekOffset + 1));

      this.el.find('.weekly-time-navigation .weekly-header').html(this.getWeekSpan(this.currentDate, this.weekOffset));

      this.el.find(".weekly-days .weekly-day, .weekly-times .weekly-time").fitText(1, {
        'minFontSize': 11,
        'maxFontSize': 15
      });
    },

    highlightToday: function() {
      var today = new Date();

      this.el.find('[data-date="' + this.timef('%Y-%n-%j', today) + '"]').addClass('weekly-today');
    },

    registerClickToCreate: function() {
      this.mouseDown = false;
      this.pendingEvent = null;
      this.pendingEventStart = null;

      var gridDays = this.el.find('.weekly-grid .weekly-day');

      // Make sure anything previously bound is bound no more.
      gridDays.unbind('mousedown mousemove mouseup mouseout click');

      gridDays.on('mousedown', this.proxy(function(event){
        if(event.which !== 1 || $(event.target).is('.weekly-dragger')) return;
        this.mouseDown = true;

        if(this.pendingEvent) {
          this.pendingEvent.remove();
          this.pendingEvent = null;
        }
      }));

      gridDays.on('mouseup', this.proxy(function(){
        this.mouseDown = false;

        if(this.pendingEvent) {
          var eventData = this.pendingEvent.data();

          var parsedDate = eventData.date.split('-');

          this.addEvent({
            start: new Date(parsedDate[0], parsedDate[1], parsedDate[2], eventData.starttime),
            end: new Date(parsedDate[0], parsedDate[1], parsedDate[2], eventData.endtime)
          });

          this.pendingEvent.remove();
          this.pendingEvent = null;
          this.pendingEventStart = null;
        }
      }));

      gridDays.on('mousemove', this.proxy(function(event){
        if(this.mouseDown) {
          this.createEvent(event);
        }
      }));

      gridDays.on('click', this.proxy(function(event){
        if($(event.target).is('.weekly-time,.weekly-day')) {
          this.createEvent(event);
          gridDays.trigger('mouseup');
        }
      }));

      gridDays.on('mouseleave', this.proxy(function(event){
        if(this.mouseDown) {
          gridDays.trigger('mouseup');
        }
      }));
    },

    registerModifyEvent: function() {
      this.mouseDown = false;
      this.pendingEvent = null;
      this.pendingEventStart = null;

      var eventDraggers = this.el.find('.weekly-grid .weekly-dragger');

      // Make sure anything previously bound is bound no more.
      eventDraggers.unbind('mousedown mousemove mouseup mouseout click');

      eventDraggers.on('mousedown', this.proxy(function(event){
        if(event.which !== 1) return;
        this.mouseDown = true;

        this.eventOffset = 0;
      }));

      gridDays.on('mouseup', this.proxy(function(){
        this.mouseDown = false;

        if(this.eventOffset) {
          var eventData = this.pendingEvent.data();

          this.eventOffset = 0;
        }
      }));

      gridDays.on('mousemove', this.proxy(function(event){
        if(this.mouseDown) {
          this.createEvent(event);
        }
      }));

      gridDays.on('click', this.proxy(function(event){
        if($(event.target).is('.weekly-time,.weekly-day')) {
          this.createEvent(event);
          gridDays.trigger('mouseup');
        }
      }));

      gridDays.on('mouseleave', this.proxy(function(event){
        if(this.mouseDown) {
          gridDays.trigger('mouseup');
        }
      }));
    },

    createEvent: function(event) {
      var target = $(event.currentTarget);
      var targetOffset = target.parent().offset();
      var mouseOffsetTop = event.pageY - targetOffset.top;
      var dayHeight = $(event.currentTarget).height();
      var hourHeight = Math.round(dayHeight / this.timeDifference);

      var tempStart = Math.floor(mouseOffsetTop / hourHeight) * hourHeight;
      var tempEnd = Math.ceil(mouseOffsetTop / hourHeight) * hourHeight;

      if(this.pendingEventStart === null) {
        this.pendingEventStart = tempStart;
      }

      if(this.pendingEventEnd === null || tempEnd > this.pendingEventStart) {
        this.pendingEventEnd = tempEnd;
      }

      if(!this.pendingEvent) {
        target.append('

');
        this.pendingEvent = target.find('.weekly-event-pending');
        this.pendingEvent.data('date', target.data('date'));
      }

      this.pendingEvent.css({
        top: this.pendingEventStart,
        bottom: dayHeight - this.pendingEventEnd
      });

      this.pendingEvent.data('starttime', ((this.pendingEventStart / hourHeight) || 0) + this.startTime);
      this.pendingEvent.data('endtime', ((this.pendingEventEnd / hourHeight) || 1) + this.startTime);
    },

    modifyEvent: function(event) {
      var target = $(event.currentTarget);
      var targetOffset = target.parent().offset();
      var mouseOffsetTop = event.pageY - targetOffset.top;
      var dayHeight = $(event.currentTarget).height();
      var hourHeight = Math.round(dayHeight / this.timeDifference);

      var tempStart = Math.floor(mouseOffsetTop / hourHeight) * hourHeight;
      var tempEnd = Math.ceil(mouseOffsetTop / hourHeight) * hourHeight;

      console.log(tempStart, tempEnd);
    },

    getWeekSpan: function(date, offset) {
      var first = this.getFirstDayOfWeek(date, offset);
      var last = this.getLastDayOfWeek(date, offset);

      var span = this.timef('%M %d', first) + ' - ';
      if (first.getMonth() == last.getMonth()) {
        span += this.timef('%d', last);
      } else {
        span += this.timef('%M %d', last);
      }
      return span;
    },

    getFirstDayOfWeek: function(date, offset) {
      offset = offset || 0;
      var first = date.getDate() - date.getDay();
      var newDate = new Date(date.getTime());
      newDate.setDate(first + (offset * 7));
      return newDate;
    },

    getLastDayOfWeek: function(date, weekOffset) {
      weekOffset = weekOffset || 0;
      var first = date.getDate() - date.getDay();
      var newDate = new Date(date.getTime());
      newDate.setDate(first + 6 + (weekOffset * 7));
      return newDate;
    },

    getDates: function() {
      var curr = this.currentDate;

      var daysInWeek = 7;

      var days = [];
      var sunday = this.getFirstDayOfWeek(curr, this.weekOffset);

      for (var i = 0, c = daysInWeek; i < c; i++) {
        var d = new Date(sunday.getTime());
        d.setDate(d.getDate() - d.getDay() + i);
        days.push(d);
      }
      return days;
    },

    getTimes: function() {
      var end = this.endTime + 12;

      var times = [];
      for (var i = this.startTime; i <= end; i++) {
        var hour = (i > 12) ? i - 12 : i;
        var timeString = hour+':00 ';

        timeString += (i > 11) ? 'PM' : 'AM';

        times.push(timeString);
      }

      return times;
    },

    nextWeek: function() {
      this.changeDate(1);
    },

    prevWeek: function() {
      this.changeDate(-1);
    },

    jumpToday: function() {
      this.changeDate(0);
    },

    changeDate: function(offsetWeek) {
      if(!offsetWeek) {
        this.weekOffset = 0;
      } else {
        this.weekOffset += offsetWeek;
      }

      this.update();
    },

    renderEvent: function(event) {
      var startDate = event.start.getFullYear() + "-" + event.start.getMonth() + "-" + event.start.getDate();
      var startTime = event.start.toTimeString().slice(0,5);
      var endDate = event.end.getFullYear() + "-" + event.end.getMonth() + "-" + event.end.getDate();
      var endTime = event.end.toTimeString().slice(0,5);

      var topOffset = 100 - this.getTimeOffsetPercent(startTime);
      var bottomOffset = this.getTimeOffsetPercent(endTime);

      var eventTemplate = $('

');

      eventTemplate.data(event);

      eventTemplate.css({
        top: topOffset + '%',
        bottom: bottomOffset + '%'
      }).append([
        '<button data-action="removeEvent" class="weekly-delete">×</button>',
        '
' + this.timef('%g:%i %a', event.start) + ' -
' + this.timef('%g:%i %a', event.end) + '
',
        '
' + event.name + '
',
        '
' + event.description + '
',
        '

'
      ].join(''));

      var selectedDay = this.el.find('.weekly-grid .weekly-day[data-date="' + startDate + '"]');

      selectedDay.append(eventTemplate);

      selectedDay.find(".weekly-event").fitText();
    },

    toFraction: function(time) {
      if(time.toString().indexOf(':') === -1) {
        return time;
      }

      var parts = time.toString().split(':');

      return parseFloat(parts[0] + '.' + 100/(60/parts[1]));
    },

    getTimeOffsetPercent: function(time) {
      time = this.toFraction(time) - this.startTime;

      var diff = this.timeDifference - time;

      if(diff < 0) {
        diff = time - this.timeDifference;
      }

      var percent = (diff / this.timeDifference) * 100;

      return percent;
    },

    timef: function(format, time) {
      if(!time instanceof Date) return;

      var months = 'January|February|March|April|May|June|July|August|September|October|November|December'.split('|');
      var days = 'Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday'.split('|');

      // Implements PHP's date format syntax.
      return format.replace(/%d|%D|%j|%l|%S|%w|%F|%m|%M|%n|%Y|%y|%a|%A|%g|%G|%h|%H|%i|%s|%u|%e/g, function(match) {
        switch(match) {
          case '%d':
            return ("0" + time.getDate()).substr(-2,2);
          case '%D':
            return days[time.getDay()].substr(0,3);
          case '%j':
            return time.getDate();
          case '%l':
            return days[time.getDay()];
          case '%S':
            if(time.getDate() === 1) {
              return 'st';
            } else if(time.getDate() === 2) {
              return 'nd';
            } else if(time.getDate() === 3) {
              return 'rd';
            } else {
              return 'th';
            }
            break;
          case '%w':
            return time.getDay();
          case '%F':
            return months[time.getMonth()];
          case '%m':
            return ("0" + time.getMonth()).substr(-2,2);
          case '%M':
            return months[time.getMonth()].substr(0,3);
          case '%n':
            return time.getMonth();
          case '%Y':
            return time.getFullYear();
          case '%y':
            return time.getFullYear().toString().substr(-2,2);
          case '%a':
            return time.getHours() > 11 ? 'pm' : 'am';
          case '%A':
            return time.getHours() > 11 ? 'PM' : 'AM';
          case '%g':
            return time.getHours() > 12 ? time.getHours() -12 : time.getHours();
          case '%G':
          return time.getHours();
          case '%h':
            return ("0" + (time.getHours() > 12 ? time.getHours() -12 : time.getHours())).substr(-2,2);
          case '%H':
            return ("0" + time.getHours()).substr(-2,2);
          case '%i':
            return ("0" + time.getMinutes()).substr(-2,2);
          case '%s':
            return ("0" + time.getSeconds()).substr(-2,2);
          case '%u':
            return time.getMilliseconds();
          case '%e':
            return time.getTimezoneOffset();
        }
      });
    },

    addEvent: function(event) {
      if(event instanceof Array) {
        for(var i = 0, c = event.length; i < c; i++) {
          this.addEvent(event[i]);
        }
        return;
      }

      event = $.extend({
        name: '',
        description: '',
        start: null,
        end: null
      }, event);

      event.id = this.events.length;

      this.renderEvent(event);
      this.events.push(event);

      this.el.trigger('addEvent', [event]);
    },

    removeEvent: function(e) {
      var el = $(e.target).parents('.weekly-event');
      var event = el.data();

      this.events.splice(event.id, 1);
      el.remove();

      this.el.trigger('removeEvent', event);

      return false;
    },

    clearEvents: function() {
      this.el.find('.weekly-event').remove();

      this.events = [];

      this.el.trigger('clearedEvents');
    }
  });

})(jQuery);
<commit_msg>option for min/max for fitText<commit_after>(function($) {

  $.declare('weekly', {
    defaults: {
      startTime: 8,
      endTime: 6,
      weekOffset: 0,
      currentDate: new Date(),
      autoRender: true,
      fitText: true,
      fitTextMin: 11,
      fitTextMax: 15,
      template: '##template##'
    },

    init: function() {
      this.events = [];

      if (this.autoRender) {
        this.update();
      }
    },

    update: function() {
      this.render({
        timef: this.timef,
        getWeekSpan: this.proxy(this.getWeekSpan),
        currentDate: this.currentDate,
        dates: this.getDates(),
        times: this.getTimes()
      });

      for(var i = 0, c = this.events.length; i < c; i++) {
        this.renderEvent(this.events[i]);
      }

      this.timeDifference = (this.endTime + 13) - this.startTime;

      this.registerClickToCreate();

      this.highlightToday();

      if(!this.weekOffset) {
        this.el.find(".weekly-change-today-button").css('display', 'none');
      } else {
        this.el.find(".weekly-change-today-button").css('display', 'block');
      }

      this.el.find('.weekly-time-navigation .weekly-previous-week .week').html(this.getWeekSpan(this.currentDate, this.weekOffset - 1));
      this.el.find('.weekly-time-navigation .weekly-next-week .week').html(this.getWeekSpan(this.currentDate, this.weekOffset + 1));

      this.el.find('.weekly-time-navigation .weekly-header').html(this.getWeekSpan(this.currentDate, this.weekOffset));

      if (this.fitText) {
        this.el.find(".weekly-days .weekly-day, .weekly-times .weekly-time").fitText(1, {
          'minFontSize': this.fitTextMin,
          'maxFontSize': this.fitTextMax
        });
      }
    },

    highlightToday: function() {
      var today = new Date();

      this.el.find('[data-date="' + this.timef('%Y-%n-%j', today) + '"]').addClass('weekly-today');
    },

    registerClickToCreate: function() {
      this.mouseDown = false;
      this.pendingEvent = null;
      this.pendingEventStart = null;

      var gridDays = this.el.find('.weekly-grid .weekly-day');

      // Make sure anything previously bound is bound no more.
      gridDays.unbind('mousedown mousemove mouseup mouseout click');

      gridDays.on('mousedown', this.proxy(function(event){
        if(event.which !== 1 || $(event.target).is('.weekly-dragger')) return;
        this.mouseDown = true;

        if(this.pendingEvent) {
          this.pendingEvent.remove();
          this.pendingEvent = null;
        }
      }));

      gridDays.on('mouseup', this.proxy(function(){
        this.mouseDown = false;

        if(this.pendingEvent) {
          var eventData = this.pendingEvent.data();

          var parsedDate = eventData.date.split('-');

          this.addEvent({
            start: new Date(parsedDate[0], parsedDate[1], parsedDate[2], eventData.starttime),
            end: new Date(parsedDate[0], parsedDate[1], parsedDate[2], eventData.endtime)
          });

          this.pendingEvent.remove();
          this.pendingEvent = null;
          this.pendingEventStart = null;
        }
      }));

      gridDays.on('mousemove', this.proxy(function(event){
        if(this.mouseDown) {
          this.createEvent(event);
        }
      }));

      gridDays.on('click', this.proxy(function(event){
        if($(event.target).is('.weekly-time,.weekly-day')) {
          this.createEvent(event);
          gridDays.trigger('mouseup');
        }
      }));

      gridDays.on('mouseleave', this.proxy(function(event){
        if(this.mouseDown) {
          gridDays.trigger('mouseup');
        }
      }));
    },

    registerModifyEvent: function() {
      this.mouseDown = false;
      this.pendingEvent = null;
      this.pendingEventStart = null;

      var eventDraggers = this.el.find('.weekly-grid .weekly-dragger');

      // Make sure anything previously bound is bound no more.
      eventDraggers.unbind('mousedown mousemove mouseup mouseout click');

      eventDraggers.on('mousedown', this.proxy(function(event){
        if(event.which !== 1) return;
        this.mouseDown = true;

        this.eventOffset = 0;
      }));

      gridDays.on('mouseup', this.proxy(function(){
        this.mouseDown = false;

        if(this.eventOffset) {
          var eventData = this.pendingEvent.data();

          this.eventOffset = 0;
        }
      }));

      gridDays.on('mousemove', this.proxy(function(event){
        if(this.mouseDown) {
          this.createEvent(event);
        }
      }));

      gridDays.on('click', this.proxy(function(event){
        if($(event.target).is('.weekly-time,.weekly-day')) {
          this.createEvent(event);
          gridDays.trigger('mouseup');
        }
      }));

      gridDays.on('mouseleave', this.proxy(function(event){
        if(this.mouseDown) {
          gridDays.trigger('mouseup');
        }
      }));
    },

    createEvent: function(event) {
      var target = $(event.currentTarget);
      var targetOffset = target.parent().offset();
      var mouseOffsetTop = event.pageY - targetOffset.top;
      var dayHeight = $(event.currentTarget).height();
      var hourHeight = Math.round(dayHeight / this.timeDifference);

      var tempStart = Math.floor(mouseOffsetTop / hourHeight) * hourHeight;
      var tempEnd = Math.ceil(mouseOffsetTop / hourHeight) * hourHeight;

      if(this.pendingEventStart === null) {
        this.pendingEventStart = tempStart;
      }

      if(this.pendingEventEnd === null || tempEnd > this.pendingEventStart) {
        this.pendingEventEnd = tempEnd;
      }

      if(!this.pendingEvent) {
        target.append('

');
        this.pendingEvent = target.find('.weekly-event-pending');
        this.pendingEvent.data('date', target.data('date'));
      }

      this.pendingEvent.css({
        top: this.pendingEventStart,
        bottom: dayHeight - this.pendingEventEnd
      });

      this.pendingEvent.data('starttime', ((this.pendingEventStart / hourHeight) || 0) + this.startTime);
      this.pendingEvent.data('endtime', ((this.pendingEventEnd / hourHeight) || 1) + this.startTime);
    },

    modifyEvent: function(event) {
      var target = $(event.currentTarget);
      var targetOffset = target.parent().offset();
      var mouseOffsetTop = event.pageY - targetOffset.top;
      var dayHeight = $(event.currentTarget).height();
      var hourHeight = Math.round(dayHeight / this.timeDifference);

      var tempStart = Math.floor(mouseOffsetTop / hourHeight) * hourHeight;
      var tempEnd = Math.ceil(mouseOffsetTop / hourHeight) * hourHeight;

      console.log(tempStart, tempEnd);
    },

    getWeekSpan: function(date, offset) {
      var first = this.getFirstDayOfWeek(date, offset);
      var last = this.getLastDayOfWeek(date, offset);

      var span = this.timef('%M %d', first) + ' - ';
      if (first.getMonth() == last.getMonth()) {
        span += this.timef('%d', last);
      } else {
        span += this.timef('%M %d', last);
      }
      return span;
    },

    getFirstDayOfWeek: function(date, offset) {
      offset = offset || 0;
      var first = date.getDate() - date.getDay();
      var newDate = new Date(date.getTime());
      newDate.setDate(first + (offset * 7));
      return newDate;
    },

    getLastDayOfWeek: function(date, weekOffset) {
      weekOffset = weekOffset || 0;
      var first = date.getDate() - date.getDay();
      var newDate = new Date(date.getTime());
      newDate.setDate(first + 6 + (weekOffset * 7));
      return newDate;
    },

    getDates: function() {
      var curr = this.currentDate;

      var daysInWeek = 7;

      var days = [];
      var sunday = this.getFirstDayOfWeek(curr, this.weekOffset);

      for (var i = 0, c = daysInWeek; i < c; i++) {
        var d = new Date(sunday.getTime());
        d.setDate(d.getDate() - d.getDay() + i);
        days.push(d);
      }
      return days;
    },

    getTimes: function() {
      var end = this.endTime + 12;

      var times = [];
      for (var i = this.startTime; i <= end; i++) {
        var hour = (i > 12) ? i - 12 : i;
        var timeString = hour+':00 ';

        timeString += (i > 11) ? 'PM' : 'AM';

        times.push(timeString);
      }

      return times;
    },

    nextWeek: function() {
      this.changeDate(1);
    },

    prevWeek: function() {
      this.changeDate(-1);
    },

    jumpToday: function() {
      this.changeDate(0);
    },

    changeDate: function(offsetWeek) {
      if(!offsetWeek) {
        this.weekOffset = 0;
      } else {
        this.weekOffset += offsetWeek;
      }

      this.update();
    },

    renderEvent: function(event) {
      var startDate = event.start.getFullYear() + "-" + event.start.getMonth() + "-" + event.start.getDate();
      var startTime = event.start.toTimeString().slice(0,5);
      var endDate = event.end.getFullYear() + "-" + event.end.getMonth() + "-" + event.end.getDate();
      var endTime = event.end.toTimeString().slice(0,5);

      var topOffset = 100 - this.getTimeOffsetPercent(startTime);
      var bottomOffset = this.getTimeOffsetPercent(endTime);

      var eventTemplate = $('

');

      eventTemplate.data(event);

      eventTemplate.css({
        top: topOffset + '%',
        bottom: bottomOffset + '%'
      }).append([
        '<button data-action="removeEvent" class="weekly-delete">×</button>',
        '
' + this.timef('%g:%i %a', event.start) + ' -
' + this.timef('%g:%i %a', event.end) + '
',
        '
' + event.name + '
',
        '
' + event.description + '
',
        '

'
      ].join(''));

      var selectedDay = this.el.find('.weekly-grid .weekly-day[data-date="' + startDate + '"]');

      selectedDay.append(eventTemplate);

      if (this.fitText) {
        selectedDay.find(".weekly-event").fitText(1, {
          'minFontSize': this.fitTextMin,
          'maxFontSize': this.fitTextMax
        });
      }
    },

    toFraction: function(time) {
      if(time.toString().indexOf(':') === -1) {
        return time;
      }

      var parts = time.toString().split(':');

      return parseFloat(parts[0] + '.' + 100/(60/parts[1]));
    },

    getTimeOffsetPercent: function(time) {
      time = this.toFraction(time) - this.startTime;

      var diff = this.timeDifference - time;

      if(diff < 0) {
        diff = time - this.timeDifference;
      }

      var percent = (diff / this.timeDifference) * 100;

      return percent;
    },

    timef: function(format, time) {
      if(!time instanceof Date) return;

      var months = 'January|February|March|April|May|June|July|August|September|October|November|December'.split('|');
      var days = 'Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday'.split('|');

      // Implements PHP's date format syntax.
      return format.replace(/%d|%D|%j|%l|%S|%w|%F|%m|%M|%n|%Y|%y|%a|%A|%g|%G|%h|%H|%i|%s|%u|%e/g, function(match) {
        switch(match) {
          case '%d':
            return ("0" + time.getDate()).substr(-2,2);
          case '%D':
            return days[time.getDay()].substr(0,3);
          case '%j':
            return time.getDate();
          case '%l':
            return days[time.getDay()];
          case '%S':
            if(time.getDate() === 1) {
              return 'st';
            } else if(time.getDate() === 2) {
              return 'nd';
            } else if(time.getDate() === 3) {
              return 'rd';
            } else {
              return 'th';
            }
            break;
          case '%w':
            return time.getDay();
          case '%F':
            return months[time.getMonth()];
          case '%m':
            return ("0" + time.getMonth()).substr(-2,2);
          case '%M':
            return months[time.getMonth()].substr(0,3);
          case '%n':
            return time.getMonth();
          case '%Y':
            return time.getFullYear();
          case '%y':
            return time.getFullYear().toString().substr(-2,2);
          case '%a':
            return time.getHours() > 11 ? 'pm' : 'am';
          case '%A':
            return time.getHours() > 11 ? 'PM' : 'AM';
          case '%g':
            return time.getHours() > 12 ? time.getHours() -12 : time.getHours();
          case '%G':
          return time.getHours();
          case '%h':
            return ("0" + (time.getHours() > 12 ? time.getHours() -12 : time.getHours())).substr(-2,2);
          case '%H':
            return ("0" + time.getHours()).substr(-2,2);
          case '%i':
            return ("0" + time.getMinutes()).substr(-2,2);
          case '%s':
            return ("0" + time.getSeconds()).substr(-2,2);
          case '%u':
            return time.getMilliseconds();
          case '%e':
            return time.getTimezoneOffset();
        }
      });
    },

    addEvent: function(event) {
      if(event instanceof Array) {
        for(var i = 0, c = event.length; i < c; i++) {
          this.addEvent(event[i]);
        }
        return;
      }

      event = $.extend({
        name: '',
        description: '',
        start: null,
        end: null
      }, event);

      event.id = this.events.length;

      this.renderEvent(event);
      this.events.push(event);

      this.el.trigger('addEvent', [event]);
    },

    removeEvent: function(e) {
      var el = $(e.target).parents('.weekly-event');
      var event = el.data();

      this.events.splice(event.id, 1);
      el.remove();

      this.el.trigger('removeEvent', event);

      return false;
    },

    clearEvents: function() {
      this.el.find('.weekly-event').remove();

      this.events = [];

      this.el.trigger('clearedEvents');
    }
  });

})(jQuery);
<|endoftext|>


<commit_before>module.exports = function user(state) {
  return {
    namespace: 'user',
    state: {} || state,
    reducers: {}
  };
};
<commit_msg>Fix user not reading initial state<commit_after>module.exports = function user(state) {
  return {
    namespace: 'user',
    state: state || {},
    reducers: {}
  };
};
<|endoftext|>


<commit_before>/* eslint max-nested-callbacks: 0*/
/* eslint no-unused-vars: 0*/
  // mocha's 'should' is not used
/* eslint max-len: 0 */
  // the html is partially longer than 80 characters
'use strict';

const chai = require('chai');
const should = chai.should();
const expect = chai.expect;
const keva = require('keva');
const noWhitespace = require('no-whitespace');
const express = require('express');
const frix = require('../lib/frix');
let opt = require('../lib/frix.conf.js');
opt.root = 'test/files/';

chai.use(require('chai-http'));

describe('frix', function() {
  describe('express handler', function() {
    it('should create valid function and html', function(done) {
      let expectedHtml = noWhitespace(`
        <!DOCTYPE html>

            <meta charset="utf-8"/>
            <title>Woody</title>

Tree

Baum

written by Wikipedia

A tree is a tall plant with a trunk and branches made of wood.

      `);
      let app = express();
      frix.render().then(requestHandler => {
        app.use(requestHandler);
        chai.request(app).get('/page1').end((err, res) => {
          if (err) throw err;
          expect(noWhitespace(res.text)).to.equal(expectedHtml);
          done();
        });
      }, err => console.log(err));
    });

    it('should not return anything when url is invalid', function(done) {
      let app = express();
      frix.render().then(requestHandler => {
        app.use(requestHandler);
        chai.request(app).get('/invalid').end((_, res) => {
          expect(res).to.have.status(404);
          done();
        });
      });
    });
  });

  describe('loops', function() {
    it('should be executed', function(done) {
      let expectedHtml = noWhitespace(`

Tree

Baum

written by Wikipedia

A tree is a tall plant with a trunk and branches made of wood.

        Insert smart-ass quote here.

      `);
      let app = express();
      opt.key = 'loop-test.json';
      frix.render().then(requestHandler => {
        app.use(requestHandler);
        chai.request(app).get('/loop-test').end((_, res) => {
          expect(noWhitespace(res.text)).to.equal(expectedHtml);
          done();
        });
      });
    });

    after(function() {
      opt.key = 'key.json';
    });
  });

  describe('modifiers', function() {
    let someFunction = function(html) {
      return html;
    };

    afterEach(function() {
      frix.modifiers.content = [];
    });

    describe('should be added', function() {
      it('using attributes', function() {
        frix.addModifier('content', someFunction);
        expect(frix.modifiers.content.includes(someFunction))
          .to.equal(true);
      });

      it('using object', function() {
        frix.addModifier({
          target: 'content',
          modifier: someFunction
        });
        expect(frix.modifiers.content.includes(someFunction))
          .to.equal(true);
      });
    });

    it('should be rejected when not a function', function() {
      expect(frix.addModifier.bind(frix, 'content', null))
        .to.throw('Not a function.');
    });

    it('should be rejected when event does not exist', function() {
      expect(frix.addModifier.bind(frix, 'invalid-string', someFunction))
        .to.throw('Event does not exist.');
    });

    it('should all be called', function(done) {
      let promises = [];
      for (let [key] of keva(frix.modifiers)) {
        promises.push(new Promise(resolve => {
          frix.addModifier(key, html => {
            resolve(key);
            return html;
          });
        }));
      }
      Promise.all(promises).then(() => {
        done();
      });
      frix.render();
    });
  });
});
<commit_msg>fixed page url<commit_after>/* eslint max-nested-callbacks: 0*/
/* eslint no-unused-vars: 0*/
  // mocha's 'should' is not used
/* eslint max-len: 0 */
  // the html is partially longer than 80 characters
'use strict';

const chai = require('chai');
const should = chai.should();
const expect = chai.expect;
const keva = require('keva');
const noWhitespace = require('no-whitespace');
const express = require('express');
const frix = require('../lib/frix');
let opt = require('../lib/frix.conf.js');
opt.root = 'test/files/';

chai.use(require('chai-http'));

describe('frix', function() {
  describe('express handler', function() {
    it('should create valid function and html', function(done) {
      let expectedHtml = noWhitespace(`
        <!DOCTYPE html>

            <meta charset="utf-8"/>
            <title>Woody</title>

Tree

Baum

written by Wikipedia

A tree is a tall plant with a trunk and branches made of wood.

      `);
      let app = express();
      frix.render().then(requestHandler => {
        app.use(requestHandler);
        chai.request(app).get('/page2').end((err, res) => {
          if (err) throw err;
          expect(noWhitespace(res.text)).to.equal(expectedHtml);
          done();
        });
      }, err => console.log(err));
    });

    it('should not return anything when url is invalid', function(done) {
      let app = express();
      frix.render().then(requestHandler => {
        app.use(requestHandler);
        chai.request(app).get('/invalid').end((_, res) => {
          expect(res).to.have.status(404);
          done();
        });
      });
    });
  });

  describe('loops', function() {
    it('should be executed', function(done) {
      let expectedHtml = noWhitespace(`

Tree

Baum

written by Wikipedia

A tree is a tall plant with a trunk and branches made of wood.

        Insert smart-ass quote here.

      `);
      let app = express();
      opt.key = 'loop-test.json';
      frix.render().then(requestHandler => {
        app.use(requestHandler);
        chai.request(app).get('/loop-test').end((_, res) => {
          expect(noWhitespace(res.text)).to.equal(expectedHtml);
          done();
        });
      });
    });

    after(function() {
      opt.key = 'key.json';
    });
  });

  describe('modifiers', function() {
    let someFunction = function(html) {
      return html;
    };

    afterEach(function() {
      frix.modifiers.content = [];
    });

    describe('should be added', function() {
      it('using attributes', function() {
        frix.addModifier('content', someFunction);
        expect(frix.modifiers.content.includes(someFunction))
          .to.equal(true);
      });

      it('using object', function() {
        frix.addModifier({
          target: 'content',
          modifier: someFunction
        });
        expect(frix.modifiers.content.includes(someFunction))
          .to.equal(true);
      });
    });

    it('should be rejected when not a function', function() {
      expect(frix.addModifier.bind(frix, 'content', null))
        .to.throw('Not a function.');
    });

    it('should be rejected when event does not exist', function() {
      expect(frix.addModifier.bind(frix, 'invalid-string', someFunction))
        .to.throw('Event does not exist.');
    });

    it('should all be called', function(done) {
      let promises = [];
      for (let [key] of keva(frix.modifiers)) {
        promises.push(new Promise(resolve => {
          frix.addModifier(key, html => {
            resolve(key);
            return html;
          });
        }));
      }
      Promise.all(promises).then(() => {
        done();
      });
      frix.render();
    });
  });
});
<|endoftext|>