<commit_before>import React, { PropTypes } from 'react'
import markdown from 'lib/markdown'
import CodeMirror from 'codemirror'
import _ from 'lodash'
import katex from 'katex'

const { shell } = require('electron')

CodeMirror.modeURL = '../../node_modules/codemirror/mode/%N/%N.js'

// TODO: should override whole meta.js
function parseMode (mode) {
  switch (mode) {
    case 'js':
    case 'javascript':
      mode = 'jsx'
  }
  let syntax = CodeMirror.findModeByName(mode)
  if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
  return syntax
}

function buildFontStyle (fontSize, fontFamily, codeBlockFontFamily) {
  return `
   .markdown-body {
      font-size: ${fontSize}px;
      font-family: ${fontFamily}, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
    }
   .markdown-body code,
   .markdown-body pre {
      font-family: ${codeBlockFontFamily};
    }
  `
}

class MarkdownPreview extends React.Component {
  constructor (props) {
    super(props)

    this.state = {
    }
  }

  componentDidMount () {
    this.iframe.contentWindow.document.head.innerHTML = `
    <link href="../../node_modules/github-markdown-css/github-markdown.css" rel="stylesheet">
    <link rel="stylesheet" type="text/css" href="../../node_modules/codemirror/lib/codemirror.css">
    <link rel="stylesheet" type="text/css" id="codeMirrorTheme">
    <link rel="stylesheet" type="text/css" href="../../node_modules/katex/dist/katex.min.css">
    <style>
      body {
        height: 100%;
      }

      // Code Block
      .CodeMirror {
        height: inherit;
      }

      // Katex
      .katex {
        text-align: center;
      }

      .katex .frac-line {
        top: 0.9em;
        position: relative;
      }

      .katex .reset-textstyle.scriptstyle {
        top: 0.4em;
        position: relative;
      }

      // Dark Theme
      body[theme="dark"].markdown-body {
        background-color: #1E1E1E;
      }

      body[theme="dark"].markdown-body {
        color: #EEE;
      }

      body[theme="dark"].markdown-body hr {
        background-color: #444;
      }

      body[theme="dark"].markdown-body blockquote {
        color: #999;
        border-left: 0.25em solid #444;
      }

      body[theme="dark"].markdown-body kbd {
        background-color: #fcfcfc;
        border: solid 1px #444;
        border-bottom-color: #555;
        box-shadow: inset 0 -1px 0 #555;
      }

      body[theme="dark"].markdown-body h1 .octicon-link,
      body[theme="dark"].markdown-body h2 .octicon-link,
      body[theme="dark"].markdown-body h3 .octicon-link,
      body[theme="dark"].markdown-body h4 .octicon-link,
      body[theme="dark"].markdown-body h5 .octicon-link,
      body[theme="dark"].markdown-body h6 .octicon-link {
        color: #E0E0E0;
      }

      body[theme="dark"].markdown-body h1 {
        border-color: #444;
      }

      body[theme="dark"].markdown-body h2 {
        border-color: #444;
      }

      body[theme="dark"].markdown-body table tr {
        background-color: #1e1e1e;
        border-color: #444;
      }
      body[theme="dark"].markdown-body table tr:nth-child(2n) {
        background-color: #2a2a2a;
      }

      body[theme="dark"].markdown-body img {
        background-color: #1e1e1e;
      }

      body[theme="dark"].markdown-body code {
        background-color: rgba(255,255,255,0.1);
      }

      body[theme="dark"].markdown-body pre>code {
        background-color: transparent;
      }

      body[theme="dark"].markdown-body :checked+.radio-label {
        border-color: #444;
      }
    </style>
    <style id='font'>
      ${buildFontStyle(this.props.fontSize, this.props.fontFamily, this.props.codeBlockFontFamily)}
    </style>
    `
    this.iframe.contentWindow.document.body.className = 'markdown-body'

    this.iframe.contentWindow.document.addEventListener('scroll', this.handleContentScroll)

    this.mountContent()
  }

  componentWillUnmount () {
    this.iframe.contentWindow.document.removeEventListener('scroll', this.handleContentScroll)

    this.unmountContent()
  }

  componentDidUpdate (prevProps) {
    // TODO: Rebounce render
    // TODO: Use web worker
    if (prevProps.content !== this.props.content || prevProps.theme !== this.props.theme || prevProps.codeBlockTheme !== this.props.codeBlockTheme) {
      this.unmountContent()
      this.mountContent()
    }

    if (prevProps.fontFamily !== this.props.fontFamily ||
      prevProps.fontSize !== this.props.fontSize ||
      prevProps.codeBlockFontFamily !== this.props.codeBlockFontFamily
    ) {
      this.applyFont()
    }
  }

  handleContentMouseUp = e => {
    this.props.onMouseUp != null && this.props.onMouseUp()
  }

  handleContentMouseDown = e => {
    this.props.onMouseDown != null && this.props.onMouseDown()
  }

  handleContentScroll = e => {
    const { onScroll } = this.props

    if (this.shouldIgnoreScroll) {
      this.shouldIgnoreScroll = false
      return
    }

    if (onScroll != null) {
      _.some(this.iframe.contentWindow.document.body.children, child => {
        if (this.iframe.contentWindow.document.body.scrollTop < child.offsetTop) {
          onScroll(parseInt(child.getAttribute('line'), 10))
          return true
        }
        return false
      })
    }
  }

  handleAnchorClick = e => {
    e.preventDefault()
    e.stopPropagation()

    const href = e.target.getAttribute('href')

    // Check if the link is internal
    if (/^#(.+)/.test(href)) {
      // If it is, scroll the target element of anchor.
      this.iframe.contentWindow.document.body.scrollTop = this.iframe.contentWindow.document.body.querySelector(href).offsetTop - 10
    } else {
      // Or, open in user's default browser.
      shell.openExternal(e.target.href)
    }
  }

  handleAnchorMouseUp = e => {
    // This will prevent focusing.
    e.preventDefault()
    e.stopPropagation()
  }

  /**
   * Mount Content
   *
   * 1. Parse markdown
   * 2. Load theme
   * 3. Queue rewriting tags
   *   - Rewrite math block and inline by katex
   *   - Rewrite codeblock by runmode of codemirror
   * 4. Bind event handlers
   *   - Bind anchor handlers(open the link from user's default browser)
   *     - `click` event : replace default behavior with custom one
   *     - `mouseup` event : block `mouseup` event of content
   *   - Bind content handler
   *     - `mouseup` event : Fire `handlePreviewMouseDown` of MarkdownEditor
   *     - `mouseup` event : Fire `handlePreviewMouseUp` of MarkdownEditor
   *
   * @memberOf MarkdownPreview
   */
  mountContent () {
    const { content, theme } = this.props
    console.time('mount')
    // Render markdown

    console.time('parse md')
    this.iframe.contentWindow.document.body.innerHTML = markdown.quickRender(content)
    console.timeEnd('parse md')

    console.time('load theme')
    this.iframe.contentWindow.document.body.setAttribute('theme', theme)
    if (this.props.codeBlockTheme !== 'default') {
      this.iframe.contentWindow.document.getElementById('codeMirrorTheme').href = '../../node_modules/codemirror/theme/' + this.props.codeBlockTheme + '.css'
    }
    console.timeEnd('load theme')

    console.time('queue rewriting')
    // Re-render codeblokcs by CodeMirror run mode and Katex
    let codeBlocks = this.iframe.contentWindow.document.body.querySelectorAll('pre code')
    _.forEach(codeBlocks, block => {
      if (block.className === 'math') {
        let value = _.unescape(block.innerHTML)
        let rendered = document.createElement('div')
        block.parentNode.parentNode.replaceChild(rendered, block.parentNode)
        rendered.className = 'katex'
        rendered.title = value.trim()
        rendered.innerHTML = katex.renderToString(value)
        return
      }
      let syntax = parseMode(block.className.substring(9))

      CodeMirror.requireMode(syntax.mode, () => {
        let value = _.unescape(block.innerHTML)
        block.innerHTML = ''
        block.parentNode.className = ` cm-s-${this.props.codeBlockTheme} CodeMirror`
        CodeMirror.runMode(value, syntax.mime, block, {
          tabSize: 2
        })
      })
    })

    let codeInlines = this.iframe.contentWindow.document.body.querySelectorAll('code.math')
    _.forEach(codeInlines, inline => {
      let value = _.unescape(inline.innerHTML)
      let rendered = document.createElement('span')
      inline.parentNode.replaceChild(rendered, inline)
      rendered.className = 'katex'
      rendered.title = value.trim()
      rendered.innerHTML = katex.renderToString(value)
    })
    console.timeEnd('queue rewriting')

    console.time('bind event handler')
    // Apply click handler for switching mode
    this.iframe.contentWindow.document.addEventListener('mouseup', this.handleContentMouseUp)
    this.iframe.contentWindow.document.addEventListener('mousedown', this.handleContentMouseDown)
    _.forEach(this.iframe.contentWindow.document.body.querySelectorAll('a'), anchor => {
      anchor.addEventListener('mouseup', this.handleAnchorMouseUp)
      anchor.addEventListener('click', this.handleAnchorClick)
    })
    console.timeEnd('bind event handler')

    console.timeEnd('mount')
  }

  /**
   * Unmount Content
   *
   * Unbind event handlers
   * - Unbind anchor handlers
   * - Unbind content handler
   *
   * @memberOf MarkdownPreview
   */
  unmountContent () {
    // Remove click handler before rewriting.
    this.iframe.contentWindow.document.removeEventListener('mouseup', this.handleContentMouseUp)
    this.iframe.contentWindow.document.removeEventListener('mousedown', this.handleContentMouseDown)
    _.forEach(this.iframe.contentWindow.document.body.querySelectorAll('a'), anchor => {
      anchor.removeEventListener('mouseup', this.handleAnchorMouseUp)
      anchor.removeEventListener('click', this.handleAnchorClick)
    })
  }

  applyFont () {
    this.iframe.contentWindow.document.getElementById('font').innerHTML = buildFontStyle(this.props.fontSize, this.props.fontFamily, this.props.codeBlockFontFamily)
  }

  findAnchor (target) {
    while (target) {
      if (target.nodeName === 'A') {
        return target
      }
      target = target.parentNode
    }
    return null
  }

  scrollTo (line) {
    this.shouldIgnoreScroll = true
    const shouldScrollToBottom = !_.some(this.iframe.contentWindow.document.body.children, child => {
      const currentLine = parseInt(child.getAttribute('line'), 10)
      if (line < currentLine) {
        this.iframe.contentWindow.document.body.scrollTop = (child.offsetTop - 30)
        return true
      }
      return false
    })
    if (shouldScrollToBottom) {
      console.log('nothing matched')
      this.iframe.contentWindow.document.body.scrollTop = this.iframe.contentWindow.document.body.scrollHeight - this.iframe.contentWindow.document.body.offsetHeight
    }
  }

  render () {
    const { className, style } = this.props

    return (
      <iframe ref={c => (this.iframe = c)}
        className={'MarkdownPreview ' + className}
        style={style}
        sandbox='allow-scripts'
      />
    )
  }
}

MarkdownPreview.propTypes = {
  content: PropTypes.string
}

export default MarkdownPreview
<commit_msg>:bug: Fix MarkdownPreview bugs<commit_after>import React, { PropTypes } from 'react'
import markdown from 'lib/markdown'
import CodeMirror from 'codemirror'
import _ from 'lodash'
import katex from 'katex'

const { shell } = require('electron')

CodeMirror.modeURL = '../../node_modules/codemirror/mode/%N/%N.js'

// TODO: should override whole meta.js
function parseMode (mode) {
  switch (mode) {
    case 'js':
    case 'javascript':
      mode = 'jsx'
  }
  let syntax = CodeMirror.findModeByName(mode)
  if (syntax == null) syntax = CodeMirror.findModeByName('Plain Text')
  return syntax
}

function buildFontStyle (fontSize, fontFamily, codeBlockFontFamily) {
  return `
   .markdown-body {
      font-size: ${fontSize}px;
      font-family: ${fontFamily}, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
    }
   .markdown-body code,
   .markdown-body pre {
      font-family: ${codeBlockFontFamily};
    }
  `
}

class MarkdownPreview extends React.Component {
  constructor (props) {
    super(props)

    this.state = {
    }
  }

  componentDidMount () {
    this.iframe.contentWindow.document.head.innerHTML = `
    <link href="../../node_modules/github-markdown-css/github-markdown.css" rel="stylesheet">
    <link rel="stylesheet" type="text/css" href="../../node_modules/codemirror/lib/codemirror.css">
    <link rel="stylesheet" type="text/css" id="codeMirrorTheme">
    <link rel="stylesheet" type="text/css" href="../../node_modules/katex/dist/katex.min.css">
    <style>
      body {
        height: 100%;
      }

      .katex {
        text-align: center;
      }

      .katex .frac-line {
        top: 0.9em;
        position: relative;
      }

      .katex .reset-textstyle.scriptstyle {
        top: 0.4em;
        position: relative;
      }

      .katex-error {
        background-color: #EB1119;
        color: white;
        padding: 10px;
        box-sizing: border-box;
        border-radius: 4px;
      }

      .CodeMirror {
        height: initial;
      }
      body.markdown-body {
        max-width: 600px;
        padding: 15px;
        margin: 0 auto;
      }

      body[theme="dark"].markdown-body {
        background-color: #1E1E1E;
      }

      body[theme="dark"].markdown-body {
        color: #EEE;
      }

      body[theme="dark"].markdown-body hr {
        background-color: #444;
      }

      body[theme="dark"].markdown-body blockquote {
        color: #999;
        border-left: 0.25em solid #444;
      }

      body[theme="dark"].markdown-body kbd {
        background-color: #fcfcfc;
        border: solid 1px #444;
        border-bottom-color: #555;
        box-shadow: inset 0 -1px 0 #555;
      }

      body[theme="dark"].markdown-body h1 .octicon-link,
      body[theme="dark"].markdown-body h2 .octicon-link,
      body[theme="dark"].markdown-body h3 .octicon-link,
      body[theme="dark"].markdown-body h4 .octicon-link,
      body[theme="dark"].markdown-body h5 .octicon-link,
      body[theme="dark"].markdown-body h6 .octicon-link {
        color: #E0E0E0;
      }

      body[theme="dark"].markdown-body h1 {
        border-color: #444;
      }

      body[theme="dark"].markdown-body h2 {
        border-color: #444;
      }

      body[theme="dark"].markdown-body table tr {
        background-color: #1e1e1e;
        border-color: #444;
      }
      body[theme="dark"].markdown-body table tr:nth-child(2n) {
        background-color: #2a2a2a;
      }

      body[theme="dark"].markdown-body img {
        background-color: #1e1e1e;
      }

      body[theme="dark"].markdown-body code {
        background-color: rgba(255,255,255,0.1);
      }

      body[theme="dark"].markdown-body pre>code {
        background-color: transparent;
      }

      body[theme="dark"].markdown-body :checked+.radio-label {
        border-color: #444;
      }
    </style>
    <style id='font'>
      ${buildFontStyle(this.props.fontSize, this.props.fontFamily, this.props.codeBlockFontFamily)}
    </style>
    `
    this.iframe.contentWindow.document.body.className = 'markdown-body'

    this.iframe.contentWindow.document.addEventListener('scroll', this.handleContentScroll)

    this.mountContent()
  }

  componentWillUnmount () {
    this.iframe.contentWindow.document.removeEventListener('scroll', this.handleContentScroll)

    this.unmountContent()
  }

  componentDidUpdate (prevProps) {
    // TODO: Rebounce render
    // TODO: Use web worker
    if (prevProps.content !== this.props.content || prevProps.theme !== this.props.theme || prevProps.codeBlockTheme !== this.props.codeBlockTheme) {
      this.unmountContent()
      this.mountContent()
    }

    if (prevProps.fontFamily !== this.props.fontFamily ||
      prevProps.fontSize !== this.props.fontSize ||
      prevProps.codeBlockFontFamily !== this.props.codeBlockFontFamily
    ) {
      this.applyFont()
    }
  }

  handleContentMouseUp = e => {
    this.props.onMouseUp != null && this.props.onMouseUp()
  }

  handleContentMouseDown = e => {
    this.props.onMouseDown != null && this.props.onMouseDown()
  }

  handleContentScroll = e => {
    const { onScroll } = this.props

    if (this.shouldIgnoreScroll) {
      this.shouldIgnoreScroll = false
      return
    }

    if (onScroll != null) {
      _.some(this.iframe.contentWindow.document.body.children, child => {
        if (this.iframe.contentWindow.document.body.scrollTop < child.offsetTop) {
          onScroll(parseInt(child.getAttribute('line'), 10))
          return true
        }
        return false
      })
    }
  }

  handleAnchorClick = e => {
    e.preventDefault()
    e.stopPropagation()

    const href = e.target.getAttribute('href')

    // Check if the link is internal
    if (/^#(.+)/.test(href)) {
      // If it is, scroll the target element of anchor.
      this.iframe.contentWindow.document.body.scrollTop = this.iframe.contentWindow.document.body.querySelector(href).offsetTop - 10
    } else {
      // Or, open in user's default browser.
      shell.openExternal(e.target.href)
    }
  }

  handleAnchorMouseUp = e => {
    // This will prevent focusing.
    e.preventDefault()
    e.stopPropagation()
  }

  /**
   * Mount Content
   *
   * 1. Parse markdown
   * 2. Load theme
   * 3. Queue rewriting tags
   *   - Rewrite math block and inline by katex
   *   - Rewrite codeblock by runmode of codemirror
   * 4. Bind event handlers
   *   - Bind anchor handlers(open the link from user's default browser)
   *     - `click` event : replace default behavior with custom one
   *     - `mouseup` event : block `mouseup` event of content
   *   - Bind content handler
   *     - `mouseup` event : Fire `handlePreviewMouseDown` of MarkdownEditor
   *     - `mouseup` event : Fire `handlePreviewMouseUp` of MarkdownEditor
   *
   * @memberOf MarkdownPreview
   */
  mountContent () {
    const { content, theme } = this.props

    // Render markdown
    console.time('mount')

    console.time('parse md')
    this.iframe.contentWindow.document.body.innerHTML = markdown.quickRender(content)
    console.timeEnd('parse md')

    console.time('load theme')
    this.iframe.contentWindow.document.body.setAttribute('theme', theme)
    if (this.props.codeBlockTheme !== 'default') {
      this.iframe.contentWindow.document.getElementById('codeMirrorTheme').href = '../../node_modules/codemirror/theme/' + this.props.codeBlockTheme + '.css'
    }
    console.timeEnd('load theme')

    console.time('queue rewriting')
    // Re-render codeblokcs by CodeMirror run mode and Katex
    let codeBlocks = this.iframe.contentWindow.document.body.querySelectorAll('pre code')
    _.forEach(codeBlocks, block => {
      if (block.className === 'math') {
        let value = _.unescape(block.innerHTML)
        let rendered = document.createElement('div')
        block.parentNode.parentNode.replaceChild(rendered, block.parentNode)
        try {
          rendered.innerHTML = katex.renderToString(value)
          rendered.className = 'katex'
        } catch (e) {
          rendered.innerHTML = e.message
          rendered.className = 'katex-error'
        }
        rendered.title = value.trim()
        return
      }
      let syntax = parseMode(block.className.substring(9))

      CodeMirror.requireMode(syntax.mode, () => {
        let value = _.unescape(block.innerHTML)
        block.innerHTML = ''
        block.parentNode.className = `cm-s-${this.props.codeBlockTheme} CodeMirror`
        CodeMirror.runMode(value, syntax.mime, block, {
          tabSize: 2
        })
      })
    })

    let codeInlines = this.iframe.contentWindow.document.body.querySelectorAll('code.math')
    _.forEach(codeInlines, inline => {
      let value = _.unescape(inline.innerHTML)
      let rendered = document.createElement('span')
      inline.parentNode.replaceChild(rendered, inline)
      try {
        rendered.innerHTML = katex.renderToString(value)
        rendered.className = 'katex'
      } catch (e) {
        rendered.innerHTML = e.message
        rendered.className = 'katex-error'
      }
    })
    console.timeEnd('queue rewriting')

    console.time('bind event handler')
    // Apply click handler for switching mode
    this.iframe.contentWindow.document.addEventListener('mouseup', this.handleContentMouseUp)
    this.iframe.contentWindow.document.addEventListener('mousedown', this.handleContentMouseDown)
    _.forEach(this.iframe.contentWindow.document.body.querySelectorAll('a'), anchor => {
      anchor.addEventListener('mouseup', this.handleAnchorMouseUp)
      anchor.addEventListener('click', this.handleAnchorClick)
    })
    console.timeEnd('bind event handler')

    console.timeEnd('mount')
  }

  /**
   * Unmount Content
   *
   * Unbind event handlers
   * - Unbind anchor handlers
   * - Unbind content handler
   *
   * @memberOf MarkdownPreview
   */
  unmountContent () {
    // Remove click handler before rewriting.
    this.iframe.contentWindow.document.removeEventListener('mouseup', this.handleContentMouseUp)
    this.iframe.contentWindow.document.removeEventListener('mousedown', this.handleContentMouseDown)
    _.forEach(this.iframe.contentWindow.document.body.querySelectorAll('a'), anchor => {
      anchor.removeEventListener('mouseup', this.handleAnchorMouseUp)
      anchor.removeEventListener('click', this.handleAnchorClick)
    })
  }

  applyFont () {
    this.iframe.contentWindow.document.getElementById('font').innerHTML = buildFontStyle(this.props.fontSize, this.props.fontFamily, this.props.codeBlockFontFamily)
  }

  findAnchor (target) {
    while (target) {
      if (target.nodeName === 'A') {
        return target
      }
      target = target.parentNode
    }
    return null
  }

  scrollTo (line) {
    this.shouldIgnoreScroll = true
    const shouldScrollToBottom = !_.some(this.iframe.contentWindow.document.body.children, child => {
      const currentLine = parseInt(child.getAttribute('line'), 10)
      if (line < currentLine) {
        this.iframe.contentWindow.document.body.scrollTop = (child.offsetTop - 30)
        return true
      }
      return false
    })
    if (shouldScrollToBottom) {
      console.log('nothing matched')
      this.iframe.contentWindow.document.body.scrollTop = this.iframe.contentWindow.document.body.scrollHeight - this.iframe.contentWindow.document.body.offsetHeight
    }
  }

  render () {
    const { className, style } = this.props

    return (
      <iframe ref={c => (this.iframe = c)}
        className={'MarkdownPreview ' + className}
        style={style}
        sandbox='allow-scripts'
      />
    )
  }
}

MarkdownPreview.propTypes = {
  content: PropTypes.string
}

export default MarkdownPreview
<|endoftext|>


<commit_before>module.exports = function( grunt ) {
	grunt.initConfig({
		meta: {
			banner: "/*! Tetra.js v<%= pkg.version %> | (MIT Licence) (c) Viadeo/APVO Corp */"
		},
		concat: {
			'dist/tetra.js': [
				"lib/tetra.js",
				"lib/deps/require.js",
				"lib/mod/require/client.js",
				"lib/mod/tmpl/client-micro-tmpl.js",
				"lib/tetra-view.js",
				"lib/tetra-controller.js",
				"lib/tetra-model.js",
				"lib/conf/default.js"
			],
			'dist/tetra-viadeo.js': [
				"lib/tetra.js",
				"lib/deps/sizzle.js",
				"lib/deps/mod/libAbstracted/jquery-connector.js",
				"lib/deps/mod/libAbstracted/prototype-connector.js",
				"lib/deps/mod/libAbstracted/builder.js",
				"lib/deps/require.js",
				"lib/mod/require/client.js",
				"lib/mod/tmpl/client-micro-tmpl.js",
				"lib/tetra-view.js",
				"lib/tetra-controller.js",
				"lib/tetra-model.js",
				"lib/conf/viadeo-webapp.js"
			]
		},
		min: {
			'dist/tetra.min.js': ['<banner>', 'dist/tetra.js'],
			'dist/tetra-viadeo.min.js': ['<banner>', 'dist/tetra-viadeo.js']
		},
		watch: {
			files: ["lib/*.js"],
			tasks: 'default'
		}
	});

	// Default task.
	grunt.registerTask('default', 'concat min watch');
};<commit_msg>Add pkg attribute to grunt conf<commit_after>module.exports = function( grunt ) {
	grunt.initConfig({
		pkg: "<json:package.json>",
		meta: {
			banner: "/*! Tetra.js v<%= pkg.version %> | (MIT Licence) (c) Viadeo/APVO Corp */"
		},
		concat: {
			'dist/tetra.js': [
				"lib/tetra.js",
				"lib/deps/require.js",
				"lib/mod/require/client.js",
				"lib/mod/tmpl/client-micro-tmpl.js",
				"lib/tetra-view.js",
				"lib/tetra-controller.js",
				"lib/tetra-model.js",
				"lib/conf/default.js"
			],
			'dist/tetra-viadeo.js': [
				"lib/tetra.js",
				"lib/deps/sizzle.js",
				"lib/deps/mod/libAbstracted/jquery-connector.js",
				"lib/deps/mod/libAbstracted/prototype-connector.js",
				"lib/deps/mod/libAbstracted/builder.js",
				"lib/deps/require.js",
				"lib/mod/require/client.js",
				"lib/mod/tmpl/client-micro-tmpl.js",
				"lib/tetra-view.js",
				"lib/tetra-controller.js",
				"lib/tetra-model.js",
				"lib/conf/viadeo-webapp.js"
			]
		},
		min: {
			'dist/tetra.min.js': ['<banner>', 'dist/tetra.js'],
			'dist/tetra-viadeo.min.js': ['<banner>', 'dist/tetra-viadeo.js']
		},
		watch: {
			files: ["lib/*.js"],
			tasks: 'default'
		}
	});

	// Default task.
	grunt.registerTask('default', 'concat min watch');
};<|endoftext|>


<commit_before>/* jshint esversion:6 */

const MGDL_UNITS = 'mg/dL';
const MMOLL_UNITS = 'mmol/L';

module.exports = {
  MGDL_PER_MMOLL: 18.01559,
  MGDL_UNITS: 'mg/dL',
  MMOLL_UNITS: 'mmol/L',
  DEFAULT_BG_BOUNDS: {
  	[MGDL_UNITS]:{
  		veryLow: 54,
  		targetLower: 70,
  		targetUpper: 180,
  		veryHigh:250,
  	},
  	[MMOLL_UNITS]: {
  		veryLow: 3.0,
     	targetLower: 3.9,
      	targetUpper: 10.0,
      	veryHigh: 13.9,
  	},
  },
  BG_CLAMP_THRESHOLD: {
  	[MGDL_UNITS]: 600,
  	[MMOLL_UNITS]: 600/MGDL_PER_MMOLL,
  }''
};
<commit_msg>Fix separate BG units<commit_after>/* jshint esversion:6 */

const MGDL_UNITS = 'mg/dL';
const MMOLL_UNITS = 'mmol/L';

module.exports = {
  MGDL_PER_MMOLL: 18.01559,
  MGDL_UNITS: 'mg/dL',
  MMOLL_UNITS: 'mmol/L',
  DEFAULT_BG_BOUNDS: {
  	[MGDL_UNITS]:{
  		veryLow: 54,
  		targetLower: 70,
  		targetUpper: 180,
  		veryHigh:250,
  	},
  	[MMOLL_UNITS]: {
  		veryLow: 3.0,
     	targetLower: 3.9,
      	targetUpper: 10.0,
      	veryHigh: 13.9,
  	},
  },
  BG_CLAMP_THRESHOLD: {
  	[MGDL_UNITS]: 600,
  	[MMOLL_UNITS]: 600/MGDL_PER_MMOLL,
  },
};
<|endoftext|>


<commit_before>var _ = require('lodash');
var moment = require('moment');
var Logger = require('logger-facade-nodejs');
var log = Logger.getLogger('micro.metric.service');

// TODO: Add proper test to ensure the format of the metric, mocking logger was giving troubles
function metric(name, val, message) {
  // val is not a NaN should log the metric
  if (isNaN(val)) { return message; }

  var metadata = _.pick(message, ['type', 'rid', 'address', 'status', 'client', 'clientId', 'transaction']);
  metadata['micrometric'] = { name: name, value: val }
  log.info(metadata, 'Publishing metric "%s":%sms', name, val);
  return metadata;
}

function start(message) {
  var now = moment().valueOf();

  // add the micro service receive timestamp
  message.headers = _.defaults(message.headers, {'micro.sr': now});
  metric('micro.sr', now, message);

  var bbes = message.headers['micro.bbes'];
  metric('micro.bs.span', now - bbes, message);

  return message;
}

function end(message) {
  var now = moment().valueOf();

  // add the micro service send timestamp
  message.headers = _.defaults(message.headers, {'micro.ss': now});
  metric('micro.ss', now, message);

  var sr = message.headers['micro.sr'];
  metric('micro.s.span', now - sr, message);

  return message;
}

module.exports = {start: start, end: end};
<commit_msg>remove todo or codeclimate will fail<commit_after>var _ = require('lodash');
var moment = require('moment');
var Logger = require('logger-facade-nodejs');
var log = Logger.getLogger('micro.metric.service');

// Add Test: Add proper test to ensure the format of the metric, mocking logger was giving troubles
function metric(name, val, message) {
  // val is not a NaN should log the metric
  if (isNaN(val)) { return message; }

  var metadata = _.pick(message, ['type', 'rid', 'address', 'status', 'client', 'clientId', 'transaction']);
  metadata['micrometric'] = { name: name, value: val }
  log.info(metadata, 'Publishing metric "%s":%sms', name, val);
  return metadata;
}

function start(message) {
  var now = moment().valueOf();

  // add the micro service receive timestamp
  message.headers = _.defaults(message.headers, {'micro.sr': now});
  metric('micro.sr', now, message);

  var bbes = message.headers['micro.bbes'];
  metric('micro.bs.span', now - bbes, message);

  return message;
}

function end(message) {
  var now = moment().valueOf();

  // add the micro service send timestamp
  message.headers = _.defaults(message.headers, {'micro.ss': now});
  metric('micro.ss', now, message);

  var sr = message.headers['micro.sr'];
  metric('micro.s.span', now - sr, message);

  return message;
}

module.exports = {start: start, end: end};
<|endoftext|>


<commit_before>import api, { getLinks } from '../api'
import Immutable from 'immutable';
import IntlMessageFormat from 'intl-messageformat';
import { unescape } from 'lodash';
import { fetchRelationships } from './accounts';

export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE';

export const NOTIFICATIONS_REFRESH_REQUEST = 'NOTIFICATIONS_REFRESH_REQUEST';
export const NOTIFICATIONS_REFRESH_SUCCESS = 'NOTIFICATIONS_REFRESH_SUCCESS';
export const NOTIFICATIONS_REFRESH_FAIL    = 'NOTIFICATIONS_REFRESH_FAIL';

export const NOTIFICATIONS_EXPAND_REQUEST = 'NOTIFICATIONS_EXPAND_REQUEST';
export const NOTIFICATIONS_EXPAND_SUCCESS = 'NOTIFICATIONS_EXPAND_SUCCESS';
export const NOTIFICATIONS_EXPAND_FAIL    = 'NOTIFICATIONS_EXPAND_FAIL';

export const NOTIFICATIONS_CLEAR      = 'NOTIFICATIONS_CLEAR';
export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP';

const fetchRelatedRelationships = (dispatch, notifications) => {
  const accountIds = notifications.filter(item => item.type === 'follow').map(item => item.account.id);

  if (accountIds > 0) {
    dispatch(fetchRelationships(accountIds));
  }
};

const unescapeHTML = (html) => unescape(html).replace(/<\/?\w+(?:\s[^>]*)?>/g, '');

export function updateNotifications(notification, intlMessages, intlLocale) {
  return (dispatch, getState) => {
    const showAlert = getState().getIn(['settings', 'notifications', 'alerts', notification.type], true);
    const playSound = getState().getIn(['settings', 'notifications', 'sounds', notification.type], true);

    dispatch({
      type: NOTIFICATIONS_UPDATE,
      notification,
      account: notification.account,
      status: notification.status,
      meta: playSound ? { sound: 'boop' } : undefined
    });

    fetchRelatedRelationships(dispatch, [notification]);

    // Desktop notifications
    if (typeof window.Notification !== 'undefined' && showAlert) {
      const title = new IntlMessageFormat(intlMessages[`notification.${notification.type}`], intlLocale).format({ name: notification.account.display_name.length > 0 ? notification.account.display_name : notification.account.username });
      const body  = (notification.status && notification.status.spoiler_text.length > 0) ? unescapeHTML(notification.status.spoiler_text) : unescapeHTML(notification.status ? notification.status.content : '');

      new Notification(title, { body, icon: notification.account.avatar, tag: notification.id });
    }
  };
};

const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS();

export function refreshNotifications() {
  return (dispatch, getState) => {
    const params = {};
    const ids    = getState().getIn(['notifications', 'items']);

    let skipLoading = false;

    if (ids.size > 0) {
      params.since_id = ids.first().get('id');
    }

    if (getState().getIn(['notifications', 'loaded'])) {
      skipLoading = true;
    }

    params.exclude_types = excludeTypesFromSettings(getState());

    dispatch(refreshNotificationsRequest(skipLoading));

    api(getState).get('/api/v1/notifications', { params }).then(response => {
      const next = getLinks(response).refs.find(link => link.rel === 'next');

      dispatch(refreshNotificationsSuccess(response.data, skipLoading, next ? next.uri : null));
      fetchRelatedRelationships(dispatch, response.data);
    }).catch(error => {
      dispatch(refreshNotificationsFail(error, skipLoading));
    });
  };
};

export function refreshNotificationsRequest(skipLoading) {
  return {
    type: NOTIFICATIONS_REFRESH_REQUEST,
    skipLoading
  };
};

export function refreshNotificationsSuccess(notifications, skipLoading, next) {
  return {
    type: NOTIFICATIONS_REFRESH_SUCCESS,
    notifications,
    accounts: notifications.map(item => item.account),
    statuses: notifications.map(item => item.status).filter(status => !!status),
    skipLoading,
    next
  };
};

export function refreshNotificationsFail(error, skipLoading) {
  return {
    type: NOTIFICATIONS_REFRESH_FAIL,
    error,
    skipLoading
  };
};

export function expandNotifications() {
  return (dispatch, getState) => {
    const url    = getState().getIn(['notifications', 'next'], null);
    const lastId = getState().getIn(['notifications', 'items']).last();

    if (url === null || getState().getIn(['notifications', 'isLoading'])) {
      return;
    }

    dispatch(expandNotificationsRequest());

    const params = {
      max_id: lastId,
      limit: 20
    };

    params.exclude_types = excludeTypesFromSettings(getState());

    api(getState).get(url, params).then(response => {
      const next = getLinks(response).refs.find(link => link.rel === 'next');

      dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null));
      fetchRelatedRelationships(dispatch, response.data);
    }).catch(error => {
      dispatch(expandNotificationsFail(error));
    });
  };
};

export function expandNotificationsRequest() {
  return {
    type: NOTIFICATIONS_EXPAND_REQUEST
  };
};

export function expandNotificationsSuccess(notifications, next) {
  return {
    type: NOTIFICATIONS_EXPAND_SUCCESS,
    notifications,
    accounts: notifications.map(item => item.account),
    statuses: notifications.map(item => item.status).filter(status => !!status),
    next
  };
};

export function expandNotificationsFail(error) {
  return {
    type: NOTIFICATIONS_EXPAND_FAIL,
    error
  };
};

export function clearNotifications() {
  return (dispatch, getState) => {
    dispatch({
      type: NOTIFICATIONS_CLEAR
    });

    api(getState).post('/api/v1/notifications/clear');
  };
};

export function scrollTopNotifications(top) {
  return {
    type: NOTIFICATIONS_SCROLL_TOP,
    top
  };
};
<commit_msg>Refine unescapeHTML (#2977)<commit_after>import api, { getLinks } from '../api'
import Immutable from 'immutable';
import IntlMessageFormat from 'intl-messageformat';
import { fetchRelationships } from './accounts';

export const NOTIFICATIONS_UPDATE = 'NOTIFICATIONS_UPDATE';

export const NOTIFICATIONS_REFRESH_REQUEST = 'NOTIFICATIONS_REFRESH_REQUEST';
export const NOTIFICATIONS_REFRESH_SUCCESS = 'NOTIFICATIONS_REFRESH_SUCCESS';
export const NOTIFICATIONS_REFRESH_FAIL    = 'NOTIFICATIONS_REFRESH_FAIL';

export const NOTIFICATIONS_EXPAND_REQUEST = 'NOTIFICATIONS_EXPAND_REQUEST';
export const NOTIFICATIONS_EXPAND_SUCCESS = 'NOTIFICATIONS_EXPAND_SUCCESS';
export const NOTIFICATIONS_EXPAND_FAIL    = 'NOTIFICATIONS_EXPAND_FAIL';

export const NOTIFICATIONS_CLEAR      = 'NOTIFICATIONS_CLEAR';
export const NOTIFICATIONS_SCROLL_TOP = 'NOTIFICATIONS_SCROLL_TOP';

const fetchRelatedRelationships = (dispatch, notifications) => {
  const accountIds = notifications.filter(item => item.type === 'follow').map(item => item.account.id);

  if (accountIds > 0) {
    dispatch(fetchRelationships(accountIds));
  }
};

const unescapeHTML = (html) => {
  const wrapper = document.createElement('div');
  wrapper.innerHTML = html;
  return wrapper.textContent;
}

export function updateNotifications(notification, intlMessages, intlLocale) {
  return (dispatch, getState) => {
    const showAlert = getState().getIn(['settings', 'notifications', 'alerts', notification.type], true);
    const playSound = getState().getIn(['settings', 'notifications', 'sounds', notification.type], true);

    dispatch({
      type: NOTIFICATIONS_UPDATE,
      notification,
      account: notification.account,
      status: notification.status,
      meta: playSound ? { sound: 'boop' } : undefined
    });

    fetchRelatedRelationships(dispatch, [notification]);

    // Desktop notifications
    if (typeof window.Notification !== 'undefined' && showAlert) {
      const title = new IntlMessageFormat(intlMessages[`notification.${notification.type}`], intlLocale).format({ name: notification.account.display_name.length > 0 ? notification.account.display_name : notification.account.username });
      const body  = (notification.status && notification.status.spoiler_text.length > 0) ? unescapeHTML(notification.status.spoiler_text) : unescapeHTML(notification.status ? notification.status.content : '');

      new Notification(title, { body, icon: notification.account.avatar, tag: notification.id });
    }
  };
};

const excludeTypesFromSettings = state => state.getIn(['settings', 'notifications', 'shows']).filter(enabled => !enabled).keySeq().toJS();

export function refreshNotifications() {
  return (dispatch, getState) => {
    const params = {};
    const ids    = getState().getIn(['notifications', 'items']);

    let skipLoading = false;

    if (ids.size > 0) {
      params.since_id = ids.first().get('id');
    }

    if (getState().getIn(['notifications', 'loaded'])) {
      skipLoading = true;
    }

    params.exclude_types = excludeTypesFromSettings(getState());

    dispatch(refreshNotificationsRequest(skipLoading));

    api(getState).get('/api/v1/notifications', { params }).then(response => {
      const next = getLinks(response).refs.find(link => link.rel === 'next');

      dispatch(refreshNotificationsSuccess(response.data, skipLoading, next ? next.uri : null));
      fetchRelatedRelationships(dispatch, response.data);
    }).catch(error => {
      dispatch(refreshNotificationsFail(error, skipLoading));
    });
  };
};

export function refreshNotificationsRequest(skipLoading) {
  return {
    type: NOTIFICATIONS_REFRESH_REQUEST,
    skipLoading
  };
};

export function refreshNotificationsSuccess(notifications, skipLoading, next) {
  return {
    type: NOTIFICATIONS_REFRESH_SUCCESS,
    notifications,
    accounts: notifications.map(item => item.account),
    statuses: notifications.map(item => item.status).filter(status => !!status),
    skipLoading,
    next
  };
};

export function refreshNotificationsFail(error, skipLoading) {
  return {
    type: NOTIFICATIONS_REFRESH_FAIL,
    error,
    skipLoading
  };
};

export function expandNotifications() {
  return (dispatch, getState) => {
    const url    = getState().getIn(['notifications', 'next'], null);
    const lastId = getState().getIn(['notifications', 'items']).last();

    if (url === null || getState().getIn(['notifications', 'isLoading'])) {
      return;
    }

    dispatch(expandNotificationsRequest());

    const params = {
      max_id: lastId,
      limit: 20
    };

    params.exclude_types = excludeTypesFromSettings(getState());

    api(getState).get(url, params).then(response => {
      const next = getLinks(response).refs.find(link => link.rel === 'next');

      dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null));
      fetchRelatedRelationships(dispatch, response.data);
    }).catch(error => {
      dispatch(expandNotificationsFail(error));
    });
  };
};

export function expandNotificationsRequest() {
  return {
    type: NOTIFICATIONS_EXPAND_REQUEST
  };
};

export function expandNotificationsSuccess(notifications, next) {
  return {
    type: NOTIFICATIONS_EXPAND_SUCCESS,
    notifications,
    accounts: notifications.map(item => item.account),
    statuses: notifications.map(item => item.status).filter(status => !!status),
    next
  };
};

export function expandNotificationsFail(error) {
  return {
    type: NOTIFICATIONS_EXPAND_FAIL,
    error
  };
};

export function clearNotifications() {
  return (dispatch, getState) => {
    dispatch({
      type: NOTIFICATIONS_CLEAR
    });

    api(getState).post('/api/v1/notifications/clear');
  };
};

export function scrollTopNotifications(top) {
  return {
    type: NOTIFICATIONS_SCROLL_TOP,
    top
  };
};
<|endoftext|>


<commit_before>import styled from 'styled-components'
import media from 'styled-media-query'
import AniLink from 'gatsby-plugin-transition-link/AniLink'

export const PostItemWrapper = styled.section`
  display: flex;
  align-items: center;
  width: 100%;
  padding: 2rem 3rem;

  &:(not:last-child) {
    border-bottom: 1px solid var(--borders);
  }

  ${media.lessThan("large")`
    align-items: flex-start;
    flex-direction: column;
    padding: 2rem 1rem;
  `}

  body#grid & {
    flex-direction: column;
    justify-content: center;
    padding: 2rem 1rem;
    border: none;
  }
`

export const PostItemLink = styled(AniLink)`
  display: flex;
  color: var(--texts);
  text-decoration: none;

  ${media.lessThan("large")`
    min-width: auto;
    min-height: auto;
    margin-bottom: .7rem;
    padding: .2rem .5rem;
    font-size: 1rem;
    border-radius: 0;
  `}

  body#grid & {
    background-color: var(--background);
  }
`

export const PostItemTag = styled.div`
  display: flex;
  align-items: center;
  justify-content: center;
  background: ${props => props.background ? props.background : 'var(--highlight)'};
  border-radius: 50%;
  color: #fff;
  font-family: 'Inter', sans-serif;
  font-size: 1.3rem;
  font-weight: 700;
  text-transform: uppercase;
  min-width: 90px;
  min-height: 90px;

  ${media.lessThan("large")`
    min-width: initial;
    min-height: 24px;
    margin-bottom: 12px;
    padding: 0 16px;
    border-radius: 7px;
    font-size: 0.8rem;
  `}

  body#grid & {
    margin-bottom: 1.5rem;
  }
`

export const PostItemInfo = styled.div`
  display: flex;
  flex-direction: column;
  margin-left: 1.5rem;
  transition: color 0.5s;

  ${media.lessThan("large")`
    margin: 0;
  `}

  &:hover {
    color: var(--highlight);
  }
`

export const PostItemDate = styled.time`
  font-size: 0.9rem;
`

export const PostItemTitle = styled.h1`
  font-size: 1.6rem;
  font-weight: 700;
  margin: 0.2rem 0 0.5rem;

  body#grid & {
    margin: 0.8rem 0;
    line-height: 1.1;
  }
`

export const PostItemDescription = styled.p`
  font-size: 1.2rem;
  font-weight: 300;
  line-height: 1.2;
`
<commit_msg>style: add a new color on hover<commit_after>import styled from 'styled-components'
import media from 'styled-media-query'
import AniLink from 'gatsby-plugin-transition-link/AniLink'

export const PostItemWrapper = styled.section`
  display: flex;
  align-items: center;
  width: 100%;
  padding: 2rem 3rem;

  &:(not:last-child) {
    border-bottom: 1px solid var(--borders);
  }

  ${media.lessThan("large")`
    align-items: flex-start;
    flex-direction: column;
    padding: 2rem 1rem;
  `}

  body#grid & {
    flex-direction: column;
    justify-content: center;
    padding: 2rem 1rem;
    border: none;
  }
`

export const PostItemLink = styled(AniLink)`
  display: flex;
  color: var(--texts);
  text-decoration: none;

  ${media.lessThan("large")`
    min-width: auto;
    min-height: auto;
    margin-bottom: .7rem;
    padding: .2rem .5rem;
    font-size: 1rem;
    border-radius: 0;
  `}

  body#grid & {
    background-color: var(--background);
  }
`

export const PostItemTag = styled.div`
  display: flex;
  align-items: center;
  justify-content: center;
  background: ${props => props.background ? props.background : 'var(--highlight)'};
  border-radius: 50%;
  color: #fff;
  font-family: 'Inter', sans-serif;
  font-size: 1.3rem;
  font-weight: 700;
  min-width: 90px;
  min-height: 90px;

  ${media.lessThan("large")`
    min-width: initial;
    min-height: 24px;
    margin-bottom: 12px;
    padding: 0 16px;
    border-radius: 7px;
    font-size: 0.8rem;
  `}

  body#grid & {
    margin-bottom: 1.5rem;
  }
`

export const PostItemInfo = styled.div`
  display: flex;
  flex-direction: column;
  margin-left: 1.5rem;
  transition: color 0.5s;

  ${media.lessThan("large")`
    margin: 0;
  `}

  &:hover {
    color: var(--postColor);
  }
`

export const PostItemDate = styled.time`
  font-size: 0.9rem;
`

export const PostItemTitle = styled.h1`
  font-size: 1.6rem;
  font-weight: 700;
  margin: 0.2rem 0 0.5rem;

  body#grid & {
    margin: 0.8rem 0;
    line-height: 1.1;
  }
`

export const PostItemDescription = styled.p`
  font-size: 1.2rem;
  font-weight: 300;
  line-height: 1.2;
`
<|endoftext|>


<commit_before>/*global module:false*/
module.exports = function(grunt) {

  // WrathNet configuration
  grunt.initConfig({
    pkg: '<json:package.json>',
    meta: {

      // Banner prepended to distribution
      banner: '/**\n' +
              ' * WrathNet Foundation v<%= pkg.version %> \n' +
              ' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %> <<%= pkg.homepage %>>\n' +
              ' *\n' +
              ' * World of Warcraft client foundation written in JavaScript, enabling\n' +
              ' * development of expansion-agnostic clients, bots and other useful tools.\n' +
              ' *\n' +
              ' * The contents of this file are subject to the MIT License, under which \n' +
              ' * this library is licensed. See the LICENSE file for the full license.\n' +
              ' */'
    },

    // Compiles CoffeeScript source and specs
    coffee: {
      src: {
        dir: 'src',
        dest: 'build'
      },
      spec: {
        dir: 'spec',
        dest: 'build-spec'
      }
    },

    // Lints compiled JavaScript files
    lint: {
      files: [
        'grunt.js',
        'build/**/*.js',
        'build-spec/**/*.js',
        'vendor/**/*.js'
      ]
    },

    // Concatenate compiled JavaScript files
    // Note: Order is significant due to namespacing
    concat: {
      dist: {
        src: [
          '<banner:meta.banner>',
          'build/wrathnet.js',
          'build/wrathnet/utils/**/*.js',
          'vendor/byte-buffer.js',
          'vendor/jsbn.js',
          'build/wrathnet/crypto/**/*.js',
          'build/wrathnet/net/**/*.js',
          'build/wrathnet/expansions/expansion.js',
          'build/wrathnet/expansions/wotlk/wotlk-expansion.js',
          'build/wrathnet/expansions/wotlk/enums/**/*.js',
          'build/wrathnet/expansions/wotlk/net/**/*.js',
          'build/wrathnet/expansions/wotlk/**/*.js',
          'build/wrathnet/sessions/**/*.js',
          'vendor/**/*.js'
        ],
        dest: 'dist/<%= pkg.name %>.js'
      }
    },

    // Minified distribution
    min: {
      dist: {
        src: ['<banner:meta.banner>', '<config:concat.dist.dest>'],
        dest: 'dist/<%= pkg.name %>.min.js'
      }
    },

    // Watch for changes to CoffeeScript files
    watch: {
      files: [
        'src/**/*.coffee',
        'spec/**/*.coffee',
        'vendor/**/*.js'
      ],
      tasks: 'coffee lint concat'
    },

    // JSHint options
    jshint: {
      options: {
        curly: false,
        eqeqeq: true,
        immed: false,
        latedef: false,
        newcap: false,
        noarg: true,
        sub: true,
        undef: true,
        boss: true,
        eqnull: true,
        shadow: true
      },
      globals: {
        WrathNet: true,
        WebSocket: true,
        ByteBuffer: true,
        DataView: true,
        ArrayBuffer: true,
        Uint8Array: true,
        JSBN: true,
        signals: true,
        console: true
      }
    },
    uglify: {}
  });

  grunt.loadTasks('tasks');

  grunt.registerTask('default', 'watch');
  grunt.registerTask('release', 'coffee lint concat min');

};
<commit_msg>Included entities package in build procedure<commit_after>/*global module:false*/
module.exports = function(grunt) {

  // WrathNet configuration
  grunt.initConfig({
    pkg: '<json:package.json>',
    meta: {

      // Banner prepended to distribution
      banner: '/**\n' +
              ' * WrathNet Foundation v<%= pkg.version %> \n' +
              ' * Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %> <<%= pkg.homepage %>>\n' +
              ' *\n' +
              ' * World of Warcraft client foundation written in JavaScript, enabling\n' +
              ' * development of expansion-agnostic clients, bots and other useful tools.\n' +
              ' *\n' +
              ' * The contents of this file are subject to the MIT License, under which \n' +
              ' * this library is licensed. See the LICENSE file for the full license.\n' +
              ' */'
    },

    // Compiles CoffeeScript source and specs
    coffee: {
      src: {
        dir: 'src',
        dest: 'build'
      },
      spec: {
        dir: 'spec',
        dest: 'build-spec'
      }
    },

    // Lints compiled JavaScript files
    lint: {
      files: [
        'grunt.js',
        'build/**/*.js',
        'build-spec/**/*.js',
        'vendor/**/*.js'
      ]
    },

    // Concatenate compiled JavaScript files
    // Note: Order is significant due to namespacing
    concat: {
      dist: {
        src: [
          '<banner:meta.banner>',
          'build/wrathnet.js',
          'build/wrathnet/utils/**/*.js',
          'vendor/byte-buffer.js',
          'vendor/jsbn.js',
          'build/wrathnet/crypto/**/*.js',
          'build/wrathnet/net/**/*.js',
          'build/wrathnet/expansions/expansion.js',
          'build/wrathnet/expansions/wotlk/wotlk-expansion.js',
          'build/wrathnet/expansions/wotlk/enums/**/*.js',
          'build/wrathnet/expansions/wotlk/net/**/*.js',
          'build/wrathnet/expansions/wotlk/**/*.js',
          'build/wrathnet/entities/**/*.js',
          'build/wrathnet/sessions/**/*.js',
          'vendor/**/*.js'
        ],
        dest: 'dist/<%= pkg.name %>.js'
      }
    },

    // Minified distribution
    min: {
      dist: {
        src: ['<banner:meta.banner>', '<config:concat.dist.dest>'],
        dest: 'dist/<%= pkg.name %>.min.js'
      }
    },

    // Watch for changes to CoffeeScript files
    watch: {
      files: [
        'src/**/*.coffee',
        'spec/**/*.coffee',
        'vendor/**/*.js'
      ],
      tasks: 'coffee lint concat'
    },

    // JSHint options
    jshint: {
      options: {
        curly: false,
        eqeqeq: true,
        immed: false,
        latedef: false,
        newcap: false,
        noarg: true,
        sub: true,
        undef: true,
        boss: true,
        eqnull: true,
        shadow: true
      },
      globals: {
        WrathNet: true,
        WebSocket: true,
        ByteBuffer: true,
        DataView: true,
        ArrayBuffer: true,
        Uint8Array: true,
        JSBN: true,
        signals: true,
        console: true
      }
    },
    uglify: {}
  });

  grunt.loadTasks('tasks');

  grunt.registerTask('default', 'watch');
  grunt.registerTask('release', 'coffee lint concat min');

};
<|endoftext|>


<commit_before>// Copyright 2015-2021, University of Colorado Boulder

/**
 * Controls for experimenting with a ParametricSpring.
 * Sliders with 'black' thumbs are parameters that result in instantiation of Vector2s and Shapes.
 * Sliders with 'green' thumbs are parameters that result in mutation of Vector2s and Shapes.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */

import merge from '../../../phet-core/js/merge.js';
import { HBox, Text, VBox } from '../../../scenery/js/imports.js';
import Panel from '../../../sun/js/Panel.js';
import VSeparator from '../../../sun/js/VSeparator.js';
import NumberControl from '../NumberControl.js';
import PhetFont from '../PhetFont.js';
import sceneryPhet from '../sceneryPhet.js';

// strings - no need for i18n since this is a developer-only demo
const aspectRatioString = 'aspect ratio:';
const deltaPhaseString = 'delta phase:';
const lineWidthString = 'line width:';
const loopsString = 'loops:';
const phaseString = 'phase:';
const pointsPerLoopString = 'points per loop:';
const radiusString = 'radius:';
const xScaleString = 'x scale:';

// constants
const CONTROL_FONT = new PhetFont( 18 );
const TICK_LABEL_FONT = new PhetFont( 14 );

class SpringControls extends Panel {

  /**
   * @param {Object} ranges - a hash of dot.Range
   * @param {ParametricSpringNode} springNode
   * @param {Object} [options]
   */
  constructor( ranges, springNode, options ) {

    options = merge( {
      fill: 'rgb( 243, 243, 243 )',
      stroke: 'rgb( 125, 125, 125 )',
      xMargin: 20,
      yMargin: 10
    }, options );

    // controls, options tweaked empirically to match ranges
    const loopsControl = NumberControl.withMinMaxTicks( loopsString, springNode.loopsProperty, ranges.loopsRange, {
      delta: 1,
      titleNodeOptions: { font: CONTROL_FONT },
      numberDisplayOptions: {
        textOptions: {
          font: CONTROL_FONT
        },
        decimalPlaces: 0
      },
      sliderOptions: {
        thumbFill: 'black',
        minorTickSpacing: 1
      }
    } );

    const pointsPerLoopControl = NumberControl.withMinMaxTicks( pointsPerLoopString, springNode.pointsPerLoopProperty, ranges.pointsPerLoopRange, {
      delta: 1,
      titleNodeOptions: { font: CONTROL_FONT },
      numberDisplayOptions: {
        textOptions: {
          font: CONTROL_FONT
        },
        decimalPlaces: 0
      },
      sliderOptions: {
        minorTickSpacing: 10,
        thumbFill: 'black'
      }
    } );

    const radiusControl = NumberControl.withMinMaxTicks( radiusString, springNode.radiusProperty, ranges.radiusRange, {
      delta: 1,
      titleNodeOptions: { font: CONTROL_FONT },
      numberDisplayOptions: {
        textOptions: {
          font: CONTROL_FONT
        },
        decimalPlaces: 0
      },
      sliderOptions: {
        minorTickSpacing: 5,
        thumbFill: 'green'
      }
    } );

    const aspectRatioControl = NumberControl.withMinMaxTicks( aspectRatioString, springNode.aspectRatioProperty, ranges.aspectRatioRange, {
      delta: 0.1,
      titleNodeOptions: { font: CONTROL_FONT },
      numberDisplayOptions: {
        textOptions: {
          font: CONTROL_FONT
        },
        decimalPlaces: 1
      },
      sliderOptions: {
        minorTickSpacing: 0.5,
        thumbFill: 'black'
      }
    } );

    assert && assert( ranges.phaseRange.min === 0 && ranges.phaseRange.max === 2 * Math.PI );
    const phaseControl = new NumberControl( phaseString, springNode.phaseProperty, ranges.phaseRange, {
      delta: 0.1,
      titleNodeOptions: { font: CONTROL_FONT },
      numberDisplayOptions: {
        textOptions: {
          font: CONTROL_FONT
        },
        decimalPlaces: 1
      },
      sliderOptions: {
        minorTickSpacing: 1,
        thumbFill: 'black',
        majorTicks: [
          { value: ranges.phaseRange.min, label: new Text( '0', { font: TICK_LABEL_FONT } ) },
          { value: ranges.phaseRange.getCenter(), label: new Text( '\u03c0', { font: TICK_LABEL_FONT } ) },
          { value: ranges.phaseRange.max, label: new Text( '2\u03c0', { font: TICK_LABEL_FONT } ) }
        ]
      }
    } );

    assert && assert( ranges.deltaPhaseRange.min === 0 && ranges.deltaPhaseRange.max === 2 * Math.PI );
    const deltaPhaseControl = new NumberControl( deltaPhaseString, springNode.deltaPhaseProperty, ranges.deltaPhaseRange, {
      delta: 0.1,
      titleNodeOptions: { font: CONTROL_FONT },
      numberDisplayOptions: {
        textOptions: {
          font: CONTROL_FONT
        },
        decimalPlaces: 1
      },
      sliderOptions: {
        minorTickSpacing: 1,
        thumbFill: 'black',
        majorTicks: [
          { value: ranges.deltaPhaseRange.min, label: new Text( '0', { font: TICK_LABEL_FONT } ) },
          { value: ranges.deltaPhaseRange.getCenter(), label: new Text( '\u03c0', { font: TICK_LABEL_FONT } ) },
          { value: ranges.deltaPhaseRange.max, label: new Text( '2\u03c0', { font: TICK_LABEL_FONT } ) }
        ]
      }
    } );

    const lineWidthControl = NumberControl.withMinMaxTicks( lineWidthString, springNode.lineWidthProperty, ranges.lineWidthRange, {
      delta: 0.1,
      titleNodeOptions: { font: CONTROL_FONT },
      numberDisplayOptions: {
        textOptions: {
          font: CONTROL_FONT
        },
        decimalPlaces: 1
      },
      sliderOptions: {
        minorTickSpacing: 1,
        thumbFill: 'green'
      }
    } );

    const xScaleControl = NumberControl.withMinMaxTicks( xScaleString, springNode.xScaleProperty, ranges.xScaleRange, {
      delta: 0.1,
      titleNodeOptions: { font: CONTROL_FONT },
      numberDisplayOptions: {
        textOptions: {
          font: CONTROL_FONT
        },
        decimalPlaces: 1
      },
      sliderOptions: {
        minorTickSpacing: 0.5,
        thumbFill: 'green'
      }
    } );

    // layout
    const xSpacing = 25;
    const ySpacing = 30;
    const content = new HBox( {
      children: [
        new VBox( { children: [ loopsControl, pointsPerLoopControl ], spacing: ySpacing } ),
        new VBox( { children: [ radiusControl, aspectRatioControl ], spacing: ySpacing } ),
        new VBox( { children: [ phaseControl, deltaPhaseControl ], spacing: ySpacing } ),
        new VSeparator( 225, { stroke: 'rgb( 125, 125, 125 )' } ),
        new VBox( { children: [ lineWidthControl, xScaleControl ], spacing: ySpacing } )
      ],
      spacing: xSpacing,
      align: 'top'
    } );

    super( content, options );
  }
}

sceneryPhet.register( 'SpringControls', SpringControls );
export default SpringControls;<commit_msg>// @ts-nocheck<commit_after>// Copyright 2015-2021, University of Colorado Boulder

// @ts-nocheck
/**
 * Controls for experimenting with a ParametricSpring.
 * Sliders with 'black' thumbs are parameters that result in instantiation of Vector2s and Shapes.
 * Sliders with 'green' thumbs are parameters that result in mutation of Vector2s and Shapes.
 *
 * @author Chris Malley (PixelZoom, Inc.)
 */

import merge from '../../../phet-core/js/merge.js';
import { HBox, Text, VBox } from '../../../scenery/js/imports.js';
import Panel from '../../../sun/js/Panel.js';
import VSeparator from '../../../sun/js/VSeparator.js';
import NumberControl from '../NumberControl.js';
import PhetFont from '../PhetFont.js';
import sceneryPhet from '../sceneryPhet.js';

// strings - no need for i18n since this is a developer-only demo
const aspectRatioString = 'aspect ratio:';
const deltaPhaseString = 'delta phase:';
const lineWidthString = 'line width:';
const loopsString = 'loops:';
const phaseString = 'phase:';
const pointsPerLoopString = 'points per loop:';
const radiusString = 'radius:';
const xScaleString = 'x scale:';

// constants
const CONTROL_FONT = new PhetFont( 18 );
const TICK_LABEL_FONT = new PhetFont( 14 );

class SpringControls extends Panel {

  /**
   * @param {Object} ranges - a hash of dot.Range
   * @param {ParametricSpringNode} springNode
   * @param {Object} [options]
   */
  constructor( ranges, springNode, options ) {

    options = merge( {
      fill: 'rgb( 243, 243, 243 )',
      stroke: 'rgb( 125, 125, 125 )',
      xMargin: 20,
      yMargin: 10
    }, options );

    // controls, options tweaked empirically to match ranges
    const loopsControl = NumberControl.withMinMaxTicks( loopsString, springNode.loopsProperty, ranges.loopsRange, {
      delta: 1,
      titleNodeOptions: { font: CONTROL_FONT },
      numberDisplayOptions: {
        textOptions: {
          font: CONTROL_FONT
        },
        decimalPlaces: 0
      },
      sliderOptions: {
        thumbFill: 'black',
        minorTickSpacing: 1
      }
    } );

    const pointsPerLoopControl = NumberControl.withMinMaxTicks( pointsPerLoopString, springNode.pointsPerLoopProperty, ranges.pointsPerLoopRange, {
      delta: 1,
      titleNodeOptions: { font: CONTROL_FONT },
      numberDisplayOptions: {
        textOptions: {
          font: CONTROL_FONT
        },
        decimalPlaces: 0
      },
      sliderOptions: {
        minorTickSpacing: 10,
        thumbFill: 'black'
      }
    } );

    const radiusControl = NumberControl.withMinMaxTicks( radiusString, springNode.radiusProperty, ranges.radiusRange, {
      delta: 1,
      titleNodeOptions: { font: CONTROL_FONT },
      numberDisplayOptions: {
        textOptions: {
          font: CONTROL_FONT
        },
        decimalPlaces: 0
      },
      sliderOptions: {
        minorTickSpacing: 5,
        thumbFill: 'green'
      }
    } );

    const aspectRatioControl = NumberControl.withMinMaxTicks( aspectRatioString, springNode.aspectRatioProperty, ranges.aspectRatioRange, {
      delta: 0.1,
      titleNodeOptions: { font: CONTROL_FONT },
      numberDisplayOptions: {
        textOptions: {
          font: CONTROL_FONT
        },
        decimalPlaces: 1
      },
      sliderOptions: {
        minorTickSpacing: 0.5,
        thumbFill: 'black'
      }
    } );

    assert && assert( ranges.phaseRange.min === 0 && ranges.phaseRange.max === 2 * Math.PI );
    const phaseControl = new NumberControl( phaseString, springNode.phaseProperty, ranges.phaseRange, {
      delta: 0.1,
      titleNodeOptions: { font: CONTROL_FONT },
      numberDisplayOptions: {
        textOptions: {
          font: CONTROL_FONT
        },
        decimalPlaces: 1
      },
      sliderOptions: {
        minorTickSpacing: 1,
        thumbFill: 'black',
        majorTicks: [
          { value: ranges.phaseRange.min, label: new Text( '0', { font: TICK_LABEL_FONT } ) },
          { value: ranges.phaseRange.getCenter(), label: new Text( '\u03c0', { font: TICK_LABEL_FONT } ) },
          { value: ranges.phaseRange.max, label: new Text( '2\u03c0', { font: TICK_LABEL_FONT } ) }
        ]
      }
    } );

    assert && assert( ranges.deltaPhaseRange.min === 0 && ranges.deltaPhaseRange.max === 2 * Math.PI );
    const deltaPhaseControl = new NumberControl( deltaPhaseString, springNode.deltaPhaseProperty, ranges.deltaPhaseRange, {
      delta: 0.1,
      titleNodeOptions: { font: CONTROL_FONT },
      numberDisplayOptions: {
        textOptions: {
          font: CONTROL_FONT
        },
        decimalPlaces: 1
      },
      sliderOptions: {
        minorTickSpacing: 1,
        thumbFill: 'black',
        majorTicks: [
          { value: ranges.deltaPhaseRange.min, label: new Text( '0', { font: TICK_LABEL_FONT } ) },
          { value: ranges.deltaPhaseRange.getCenter(), label: new Text( '\u03c0', { font: TICK_LABEL_FONT } ) },
          { value: ranges.deltaPhaseRange.max, label: new Text( '2\u03c0', { font: TICK_LABEL_FONT } ) }
        ]
      }
    } );

    const lineWidthControl = NumberControl.withMinMaxTicks( lineWidthString, springNode.lineWidthProperty, ranges.lineWidthRange, {
      delta: 0.1,
      titleNodeOptions: { font: CONTROL_FONT },
      numberDisplayOptions: {
        textOptions: {
          font: CONTROL_FONT
        },
        decimalPlaces: 1
      },
      sliderOptions: {
        minorTickSpacing: 1,
        thumbFill: 'green'
      }
    } );

    const xScaleControl = NumberControl.withMinMaxTicks( xScaleString, springNode.xScaleProperty, ranges.xScaleRange, {
      delta: 0.1,
      titleNodeOptions: { font: CONTROL_FONT },
      numberDisplayOptions: {
        textOptions: {
          font: CONTROL_FONT
        },
        decimalPlaces: 1
      },
      sliderOptions: {
        minorTickSpacing: 0.5,
        thumbFill: 'green'
      }
    } );

    // layout
    const xSpacing = 25;
    const ySpacing = 30;
    const content = new HBox( {
      children: [
        new VBox( { children: [ loopsControl, pointsPerLoopControl ], spacing: ySpacing } ),
        new VBox( { children: [ radiusControl, aspectRatioControl ], spacing: ySpacing } ),
        new VBox( { children: [ phaseControl, deltaPhaseControl ], spacing: ySpacing } ),
        new VSeparator( 225, { stroke: 'rgb( 125, 125, 125 )' } ),
        new VBox( { children: [ lineWidthControl, xScaleControl ], spacing: ySpacing } )
      ],
      spacing: xSpacing,
      align: 'top'
    } );

    super( content, options );
  }
}

sceneryPhet.register( 'SpringControls', SpringControls );
export default SpringControls;<|endoftext|>


<commit_before>/*
  Copyright Jesús Pérez <jesusprubio@gmail.com>
            Sergio García <s3rgio.gr@gmail.com>

  This code may only be used under the MIT license found at
  https://opensource.org/licenses/MIT.
*/

'use strict';

const utils = require('./utils');

const dbg = utils.dbg(__filename);

module.exports = (rhosts, map, opts = {}) =>
  new Promise((resolve) => {
    dbg('Starting, options', opts);
    const ups = [];
    let actives = 0;
    let nextHost = opts.rhosts.next();
    let nextPort;
    // Some of our protocols doesn't include passwords, ie: TFTP.
    let hasPort = false;
    if (opts.rports && opts.rports.next) {
      nextPort = opts.rports.next();
      hasPort = true;
    }

    dbg('Starting the interval', { nextHost, nextPort });
    const interval = setInterval(() => {
      if (actives > opts.concurrency) {
        dbg('Too much actives, skipping ...', { actives });
        return;
      }

      if (nextHost.done) {
        // Waiting untill all finish.
        dbg('Hosts finished, waiting for all the requests to finish');
        if (actives === 0) {
          dbg('Done, all finished now, dropping the interval');
          resolve(ups);
          clearInterval(interval);
        }
        return;
      }

      if (hasPort && nextPort.done) {
        dbg('Ports finished, restarting the iterator and increasing the rhosts one');
        // "Restarting" the generator.
        opts.rports.reset();
        nextPort = opts.rports.next();
        // Increasing the user iterator.
        nextHost = opts.rhosts.next();
        return;
      }

      const actualHost = nextHost.value;
      let nextPortValue;
      if (hasPort) { nextPortValue = nextPort.value; }
      const hostPair = [actualHost, nextPortValue];

      dbg('Starting for', hostPair);

      const optsParsed = opts;
      optsParsed.rport = hostPair[1];

      if (hasPort) {
        nextPort = optsParsed.rports.next();
      } else {
        nextHost = optsParsed.rhosts.next();
      }

      actives += 1;
      dbg('Sending', opts);
      map(hostPair[0], optsParsed)
      .then((res) => {
        actives -= 1;
        dbg('Response received', { hostPair, status: res.statusCode });

        const info = { valid: false, pair: hostPair };
        const finalRes = { ip: hostPair[0] };

        // To avoid return thinks like ['filename', null] (ie: TFTP brute)
        if (hasPort) { finalRes.port = hostPair[1]; }

        if (res && res.up) {
          dbg('up', hostPair);

          if (finalRes.data) { finalRes.data = res.data; }
          ups.push(finalRes);

          info.valid = true;
        }

        opts.events.emit('info', info);
      })
      // Here we don't want to stop the full chain on errors (ie: ECONNREFUSED)
      .catch(() => { actives -= 1; });
    }, opts.delay);
  });
<commit_msg>Fixing an error in last commits.<commit_after>/*
  Copyright Jesús Pérez <jesusprubio@gmail.com>
            Sergio García <s3rgio.gr@gmail.com>

  This code may only be used under the MIT license found at
  https://opensource.org/licenses/MIT.
*/

'use strict';

const utils = require('./utils');

const dbg = utils.dbg(__filename);

module.exports = (rhosts, map, opts = {}) =>
  new Promise((resolve) => {
    dbg('Starting, options', opts);
    const ups = [];
    let actives = 0;
    let nextHost = opts.rhosts.next();
    let nextPort;
    // Some of our protocols doesn't include passwords, ie: TFTP.
    let hasPort = false;
    if (opts.rports && opts.rports.next) {
      nextPort = opts.rports.next();
      hasPort = true;
    }

    dbg('Starting the interval', { nextHost, nextPort });
    const interval = setInterval(() => {
      if (actives > opts.concurrency) {
        dbg('Too much actives, skipping ...', { actives });
        return;
      }

      if (nextHost.done) {
        // Waiting untill all finish.
        dbg('Hosts finished, waiting for all the requests to finish');
        if (actives === 0) {
          dbg('Done, all finished now, dropping the interval');
          resolve(ups);
          clearInterval(interval);
        }
        return;
      }

      if (hasPort && nextPort.done) {
        dbg('Ports finished, restarting the iterator and increasing the rhosts one');
        // "Restarting" the generator.
        opts.rports.reset();
        nextPort = opts.rports.next();
        // Increasing the user iterator.
        nextHost = opts.rhosts.next();
        return;
      }

      const actualHost = nextHost.value;
      let nextPortValue;
      if (hasPort) { nextPortValue = nextPort.value; }
      const hostPair = [actualHost, nextPortValue];

      dbg('Starting for', hostPair);

      const optsParsed = opts;
      optsParsed.rport = hostPair[1];

      if (hasPort) {
        nextPort = optsParsed.rports.next();
      } else {
        nextHost = optsParsed.rhosts.next();
      }

      const info = { valid: false, pair: hostPair, module: utils.pathToTag(__filename) };
      actives += 1;

      dbg('Sending', opts);
      map(hostPair[0], optsParsed)
      .then((res) => {
        actives -= 1;
        dbg('Response received', { hostPair, status: res.statusCode });

        const finalRes = { ip: hostPair[0] };

        // To avoid return thinks like ['filename', null] (ie: TFTP brute)
        if (hasPort) { finalRes.port = hostPair[1]; }

        if (res && res.up) {
          dbg('up', hostPair);

          if (finalRes.data) { finalRes.data = res.data; }
          ups.push(finalRes);

          info.valid = true;
        }

        opts.events.emit('info', info);
      })
      // Here we don't want to stop the full chain on errors (ie: ECONNREFUSED)
      .catch(() => {
        actives -= 1;
        opts.events.emit('info', info);
      });
    }, opts.delay);
  });
<|endoftext|>


<commit_before>Template.ooNativeScroller.created = function () {
  var self = this;
  self.scrollStartY = new Blaze.ReactiveVar(0);
  self.callbackInProgress = new Blaze.ReactiveVar(false);
  self.topShadow = new Blaze.ReactiveVar(false)
  self.bottomShadow = new Blaze.ReactiveVar(false)
};

Template.ooNativeScroller.rendered = function () {
    var self = this;
  if(self.data.inverted) {
    Meteor.setTimeout(function(){
      var height = self.firstNode.scrollHeight;
      $(self.firstNode).scrollTop(height);
    }, 200);
  }
};

Template.ooNativeScroller.helpers({
  isLoading : function () {
    // console.log('%c -- loading   ',  'background: #5D76DB; color: white; padding: 1px 15px 1px 5px;', Template.instance().callbackInProgress.get());
   return !Template.instance().callbackInProgress.get();
  }
});

Template.ooNativeScroller.events({
  'scroll': function(e, t) {
    var self = this;
    if (self.loadMore) {
      if (!t.callbackInProgress.get()) {
        var topOffset = e.currentTarget.scrollTop;
        var maxContentHeight = e.currentTarget.scrollHeight
        var itemHeight = e.currentTarget.clientHeight
        if ((topOffset + itemHeight) === maxContentHeight) {
          e.preventDefault();
          t.callbackInProgress.set(true)
          // remove shadow class
                var fn = ooForms[self.loadMore];

            if (typeof fn === 'function') {
              fn(function(result) {
                if (result) {
                  Meteor.setTimeout(function() {
                    t.callbackInProgress.set(false)
                  }, 300)
                } else {
                  t.callbackInProgress.set(false)
                }
              })
            } else {
              t.callbackInProgress.set(false)

            }

        } else {
          // remove shadow class
        }
      }
    }

  },
  'touchstart .js-noEdgeDrag': function(e, t) {
    // sets start position
    t.scrollStartY.set(e.originalEvent.touches[0].clientY)
  },
  'touchmove .js-noEdgeDrag': function (e, t) {
    var self = this;
    var scrollMoveY = e.originalEvent.changedTouches[0].clientY;
    var topOffset = e.currentTarget.scrollTop;
    var maxContentHeight = e.currentTarget.scrollHeight
    var itemHeight = e.currentTarget.clientHeight
    // Prevent dragging when cotent is scrolled to bottom
    if (t.scrollStartY.get() > scrollMoveY) {
        if ((topOffset + itemHeight) === maxContentHeight) {
          // console.log('preventing when bottom reached')
          e.preventDefault();
        }
    } else {
      // Prevent dragging when 'top' offset is 0 (content top reached)
      if (!topOffset) {
        // console.log('preveinting content top reached')
        e.preventDefault();
      }
    }
  }
});
<commit_msg>changed callback time<commit_after>Template.ooNativeScroller.created = function () {
  var self = this;
  self.scrollStartY = new Blaze.ReactiveVar(0);
  self.callbackInProgress = new Blaze.ReactiveVar(false);
  self.topShadow = new Blaze.ReactiveVar(false)
  self.bottomShadow = new Blaze.ReactiveVar(false)
};

Template.ooNativeScroller.rendered = function () {
    var self = this;
  if(self.data.inverted) {
    Meteor.setTimeout(function(){
      var height = self.firstNode.scrollHeight;
      $(self.firstNode).scrollTop(height);
    }, 200);
  }
};

Template.ooNativeScroller.helpers({
  isLoading : function () {
    // console.log('%c -- loading   ',  'background: #5D76DB; color: white; padding: 1px 15px 1px 5px;', Template.instance().callbackInProgress.get());
   return !Template.instance().callbackInProgress.get();
  }
});

Template.ooNativeScroller.events({
  'scroll': function(e, t) {
    var self = this;
    if (self.loadMore) {
      if (!t.callbackInProgress.get()) {
        var topOffset = e.currentTarget.scrollTop;
        var maxContentHeight = e.currentTarget.scrollHeight
        var itemHeight = e.currentTarget.clientHeight
        if ((topOffset + itemHeight) === maxContentHeight) {
          e.preventDefault();
          t.callbackInProgress.set(true)
          // remove shadow class
                var fn = ooForms[self.loadMore];

            if (typeof fn === 'function') {
              fn(function(result) {
                if (result) {
                  Meteor.setTimeout(function() {
                    t.callbackInProgress.set(false)
                  }, 200)
                } else {
                  t.callbackInProgress.set(false)
                }
              })
            } else {
              t.callbackInProgress.set(false)

            }

        } else {
          // remove shadow class
        }
      }
    }

  },
  'touchstart .js-noEdgeDrag': function(e, t) {
    // sets start position
    t.scrollStartY.set(e.originalEvent.touches[0].clientY)
  },
  'touchmove .js-noEdgeDrag': function (e, t) {
    var self = this;
    var scrollMoveY = e.originalEvent.changedTouches[0].clientY;
    var topOffset = e.currentTarget.scrollTop;
    var maxContentHeight = e.currentTarget.scrollHeight
    var itemHeight = e.currentTarget.clientHeight
    // Prevent dragging when cotent is scrolled to bottom
    if (t.scrollStartY.get() > scrollMoveY) {
        if ((topOffset + itemHeight) === maxContentHeight) {
          // console.log('preventing when bottom reached')
          e.preventDefault();
        }
    } else {
      // Prevent dragging when 'top' offset is 0 (content top reached)
      if (!topOffset) {
        // console.log('preveinting content top reached')
        e.preventDefault();
      }
    }
  }
});
<|endoftext|>


<commit_before>import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { fetchStatus } from '../../actions/statuses';
import MissingIndicator from '../../components/missing_indicator';
import DetailedStatus from './components/detailed_status';
import ActionBar from './components/action_bar';
import Column from '../ui/components/column';
import {
  favourite,
  unfavourite,
  reblog,
  unreblog,
  pin,
  unpin,
} from '../../actions/interactions';
import {
  replyCompose,
  mentionCompose,
} from '../../actions/compose';
import { deleteStatus } from '../../actions/statuses';
import { initReport } from '../../actions/reports';
import { makeGetStatus } from '../../selectors';
import { ScrollContainer } from 'react-router-scroll';
import ColumnBackButton from '../../components/column_back_button';
import StatusContainer from '../../containers/status_container';
import { openModal } from '../../actions/modal';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { HotKeys } from 'react-hotkeys';

const messages = defineMessages({
  deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
  deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
});

const makeMapStateToProps = () => {
  const getStatus = makeGetStatus();

  const mapStateToProps = (state, props) => ({
    status: getStatus(state, props.params.statusId),
    ancestorsIds: state.getIn(['contexts', 'ancestors', props.params.statusId]),
    descendantsIds: state.getIn(['contexts', 'descendants', props.params.statusId]),
    me: state.getIn(['meta', 'me']),
    boostModal: state.getIn(['meta', 'boost_modal']),
    deleteModal: state.getIn(['meta', 'delete_modal']),
    autoPlayGif: state.getIn(['meta', 'auto_play_gif']),
  });

  return mapStateToProps;
};

@injectIntl
@connect(makeMapStateToProps)
export default class Status extends ImmutablePureComponent {

  static contextTypes = {
    router: PropTypes.object,
  };

  static propTypes = {
    params: PropTypes.object.isRequired,
    dispatch: PropTypes.func.isRequired,
    status: ImmutablePropTypes.map,
    ancestorsIds: ImmutablePropTypes.list,
    descendantsIds: ImmutablePropTypes.list,
    me: PropTypes.string,
    boostModal: PropTypes.bool,
    deleteModal: PropTypes.bool,
    autoPlayGif: PropTypes.bool,
    intl: PropTypes.object.isRequired,
  };

  componentWillMount () {
    this.props.dispatch(fetchStatus(this.props.params.statusId));
  }

  componentWillReceiveProps (nextProps) {
    if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
      this.props.dispatch(fetchStatus(nextProps.params.statusId));
    }
  }

  handleFavouriteClick = (status) => {
    if (status.get('favourited')) {
      this.props.dispatch(unfavourite(status));
    } else {
      this.props.dispatch(favourite(status));
    }
  }

  handlePin = (status) => {
    if (status.get('pinned')) {
      this.props.dispatch(unpin(status));
    } else {
      this.props.dispatch(pin(status));
    }
  }

  handleReplyClick = (status) => {
    this.props.dispatch(replyCompose(status, this.context.router.history));
  }

  handleModalReblog = (status) => {
    this.props.dispatch(reblog(status));
  }

  handleReblogClick = (status, e) => {
    if (status.get('reblogged')) {
      this.props.dispatch(unreblog(status));
    } else {
      if (e.shiftKey || !this.props.boostModal) {
        this.handleModalReblog(status);
      } else {
        this.props.dispatch(openModal('BOOST', { status, onReblog: this.handleModalReblog }));
      }
    }
  }

  handleDeleteClick = (status) => {
    const { dispatch, intl } = this.props;

    if (!this.props.deleteModal) {
      dispatch(deleteStatus(status.get('id')));
    } else {
      dispatch(openModal('CONFIRM', {
        message: intl.formatMessage(messages.deleteMessage),
        confirm: intl.formatMessage(messages.deleteConfirm),
        onConfirm: () => dispatch(deleteStatus(status.get('id'))),
      }));
    }
  }

  handleMentionClick = (account, router) => {
    this.props.dispatch(mentionCompose(account, router));
  }

  handleOpenMedia = (media, index) => {
    this.props.dispatch(openModal('MEDIA', { media, index }));
  }

  handleOpenVideo = (media, time) => {
    this.props.dispatch(openModal('VIDEO', { media, time }));
  }

  handleReport = (status) => {
    this.props.dispatch(initReport(status.get('account'), status));
  }

  handleEmbed = (status) => {
    this.props.dispatch(openModal('EMBED', { url: status.get('url') }));
  }

  handleHotkeyMoveUp = () => {
    this.handleMoveUp(this.props.status.get('id'));
  }

  handleHotkeyMoveDown = () => {
    this.handleMoveDown(this.props.status.get('id'));
  }

  handleHotkeyReply = e => {
    e.preventDefault();
    this.handleReplyClick(this.props.status);
  }

  handleHotkeyFavourite = () => {
    this.handleFavouriteClick(this.props.status);
  }

  handleHotkeyBoost = () => {
    this.handleReblogClick(this.props.status);
  }

  handleHotkeyMention = e => {
    e.preventDefault();
    this.handleMentionClick(this.props.status);
  }

  handleHotkeyOpenProfile = () => {
    this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
  }

  handleMoveUp = id => {
    const { status, ancestorsIds, descendantsIds } = this.props;

    if (id === status.get('id')) {
      this._selectChild(ancestorsIds.size - 1);
    } else {
      let index = ancestorsIds.indexOf(id);

      if (index === -1) {
        index = descendantsIds.indexOf(id);
        this._selectChild(ancestorsIds.size + index);
      } else {
        this._selectChild(index - 1);
      }
    }
  }

  handleMoveDown = id => {
    const { status, ancestorsIds, descendantsIds } = this.props;

    if (id === status.get('id')) {
      this._selectChild(ancestorsIds.size + 1);
    } else {
      let index = ancestorsIds.indexOf(id);

      if (index === -1) {
        index = descendantsIds.indexOf(id);
        this._selectChild(ancestorsIds.size + index + 2);
      } else {
        this._selectChild(index + 1);
      }
    }
  }

  _selectChild (index) {
    const element = this.node.querySelectorAll('.focusable')[index];

    if (element) {
      element.focus();
    }
  }

  renderChildren (list) {
    return list.map(id => (
      <StatusContainer
        key={id}
        id={id}
        onMoveUp={this.handleMoveUp}
        onMoveDown={this.handleMoveDown}
      />
    ));
  }

  setRef = c => {
    this.node = c;
  }

  componentDidUpdate () {
    const { status, ancestorsIds } = this.props;

    if (status && ancestorsIds && ancestorsIds.size > 0) {
      const element = this.node.querySelectorAll('.focusable')[ancestorsIds.size];
      element.scrollIntoView();
    }
  }

  render () {
    let ancestors, descendants;
    const { status, ancestorsIds, descendantsIds, me, autoPlayGif } = this.props;

    if (status === null) {
      return (
        <Column>
          <ColumnBackButton />
          <MissingIndicator />
        </Column>
      );
    }

    if (ancestorsIds && ancestorsIds.size > 0) {
      ancestors =
{this.renderChildren(ancestorsIds)}
;
    }

    if (descendantsIds && descendantsIds.size > 0) {
      descendants =
{this.renderChildren(descendantsIds)}
;
    }

    const handlers = {
      moveUp: this.handleHotkeyMoveUp,
      moveDown: this.handleHotkeyMoveDown,
      reply: this.handleHotkeyReply,
      favourite: this.handleHotkeyFavourite,
      boost: this.handleHotkeyBoost,
      mention: this.handleHotkeyMention,
      openProfile: this.handleHotkeyOpenProfile,
    };

    return (
      <Column>
        <ColumnBackButton />

        <ScrollContainer scrollKey='thread'>

            {ancestors}

            <HotKeys handlers={handlers}>

                <DetailedStatus
                  status={status}
                  autoPlayGif={autoPlayGif}
                  me={me}
                  onOpenVideo={this.handleOpenVideo}
                  onOpenMedia={this.handleOpenMedia}
                />

                <ActionBar
                  status={status}
                  me={me}
                  onReply={this.handleReplyClick}
                  onFavourite={this.handleFavouriteClick}
                  onReblog={this.handleReblogClick}
                  onDelete={this.handleDeleteClick}
                  onMention={this.handleMentionClick}
                  onReport={this.handleReport}
                  onPin={this.handlePin}
                  onEmbed={this.handleEmbed}
                />

            </HotKeys>

            {descendants}

        </ScrollContainer>
      </Column>
    );
  }

}
<commit_msg>In thread view, only scroll first update, scroll to replied-to post (#5322)<commit_after>import React from 'react';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import ImmutablePropTypes from 'react-immutable-proptypes';
import { fetchStatus } from '../../actions/statuses';
import MissingIndicator from '../../components/missing_indicator';
import DetailedStatus from './components/detailed_status';
import ActionBar from './components/action_bar';
import Column from '../ui/components/column';
import {
  favourite,
  unfavourite,
  reblog,
  unreblog,
  pin,
  unpin,
} from '../../actions/interactions';
import {
  replyCompose,
  mentionCompose,
} from '../../actions/compose';
import { deleteStatus } from '../../actions/statuses';
import { initReport } from '../../actions/reports';
import { makeGetStatus } from '../../selectors';
import { ScrollContainer } from 'react-router-scroll';
import ColumnBackButton from '../../components/column_back_button';
import StatusContainer from '../../containers/status_container';
import { openModal } from '../../actions/modal';
import { defineMessages, injectIntl } from 'react-intl';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { HotKeys } from 'react-hotkeys';

const messages = defineMessages({
  deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
  deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
});

const makeMapStateToProps = () => {
  const getStatus = makeGetStatus();

  const mapStateToProps = (state, props) => ({
    status: getStatus(state, props.params.statusId),
    ancestorsIds: state.getIn(['contexts', 'ancestors', props.params.statusId]),
    descendantsIds: state.getIn(['contexts', 'descendants', props.params.statusId]),
    me: state.getIn(['meta', 'me']),
    boostModal: state.getIn(['meta', 'boost_modal']),
    deleteModal: state.getIn(['meta', 'delete_modal']),
    autoPlayGif: state.getIn(['meta', 'auto_play_gif']),
  });

  return mapStateToProps;
};

@injectIntl
@connect(makeMapStateToProps)
export default class Status extends ImmutablePureComponent {

  static contextTypes = {
    router: PropTypes.object,
  };

  static propTypes = {
    params: PropTypes.object.isRequired,
    dispatch: PropTypes.func.isRequired,
    status: ImmutablePropTypes.map,
    ancestorsIds: ImmutablePropTypes.list,
    descendantsIds: ImmutablePropTypes.list,
    me: PropTypes.string,
    boostModal: PropTypes.bool,
    deleteModal: PropTypes.bool,
    autoPlayGif: PropTypes.bool,
    intl: PropTypes.object.isRequired,
  };

  componentWillMount () {
    this.props.dispatch(fetchStatus(this.props.params.statusId));
  }

  componentWillReceiveProps (nextProps) {
    if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
      this._scrolledIntoView = false;
      this.props.dispatch(fetchStatus(nextProps.params.statusId));
    }
  }

  handleFavouriteClick = (status) => {
    if (status.get('favourited')) {
      this.props.dispatch(unfavourite(status));
    } else {
      this.props.dispatch(favourite(status));
    }
  }

  handlePin = (status) => {
    if (status.get('pinned')) {
      this.props.dispatch(unpin(status));
    } else {
      this.props.dispatch(pin(status));
    }
  }

  handleReplyClick = (status) => {
    this.props.dispatch(replyCompose(status, this.context.router.history));
  }

  handleModalReblog = (status) => {
    this.props.dispatch(reblog(status));
  }

  handleReblogClick = (status, e) => {
    if (status.get('reblogged')) {
      this.props.dispatch(unreblog(status));
    } else {
      if (e.shiftKey || !this.props.boostModal) {
        this.handleModalReblog(status);
      } else {
        this.props.dispatch(openModal('BOOST', { status, onReblog: this.handleModalReblog }));
      }
    }
  }

  handleDeleteClick = (status) => {
    const { dispatch, intl } = this.props;

    if (!this.props.deleteModal) {
      dispatch(deleteStatus(status.get('id')));
    } else {
      dispatch(openModal('CONFIRM', {
        message: intl.formatMessage(messages.deleteMessage),
        confirm: intl.formatMessage(messages.deleteConfirm),
        onConfirm: () => dispatch(deleteStatus(status.get('id'))),
      }));
    }
  }

  handleMentionClick = (account, router) => {
    this.props.dispatch(mentionCompose(account, router));
  }

  handleOpenMedia = (media, index) => {
    this.props.dispatch(openModal('MEDIA', { media, index }));
  }

  handleOpenVideo = (media, time) => {
    this.props.dispatch(openModal('VIDEO', { media, time }));
  }

  handleReport = (status) => {
    this.props.dispatch(initReport(status.get('account'), status));
  }

  handleEmbed = (status) => {
    this.props.dispatch(openModal('EMBED', { url: status.get('url') }));
  }

  handleHotkeyMoveUp = () => {
    this.handleMoveUp(this.props.status.get('id'));
  }

  handleHotkeyMoveDown = () => {
    this.handleMoveDown(this.props.status.get('id'));
  }

  handleHotkeyReply = e => {
    e.preventDefault();
    this.handleReplyClick(this.props.status);
  }

  handleHotkeyFavourite = () => {
    this.handleFavouriteClick(this.props.status);
  }

  handleHotkeyBoost = () => {
    this.handleReblogClick(this.props.status);
  }

  handleHotkeyMention = e => {
    e.preventDefault();
    this.handleMentionClick(this.props.status);
  }

  handleHotkeyOpenProfile = () => {
    this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
  }

  handleMoveUp = id => {
    const { status, ancestorsIds, descendantsIds } = this.props;

    if (id === status.get('id')) {
      this._selectChild(ancestorsIds.size - 1);
    } else {
      let index = ancestorsIds.indexOf(id);

      if (index === -1) {
        index = descendantsIds.indexOf(id);
        this._selectChild(ancestorsIds.size + index);
      } else {
        this._selectChild(index - 1);
      }
    }
  }

  handleMoveDown = id => {
    const { status, ancestorsIds, descendantsIds } = this.props;

    if (id === status.get('id')) {
      this._selectChild(ancestorsIds.size + 1);
    } else {
      let index = ancestorsIds.indexOf(id);

      if (index === -1) {
        index = descendantsIds.indexOf(id);
        this._selectChild(ancestorsIds.size + index + 2);
      } else {
        this._selectChild(index + 1);
      }
    }
  }

  _selectChild (index) {
    const element = this.node.querySelectorAll('.focusable')[index];

    if (element) {
      element.focus();
    }
  }

  renderChildren (list) {
    return list.map(id => (
      <StatusContainer
        key={id}
        id={id}
        onMoveUp={this.handleMoveUp}
        onMoveDown={this.handleMoveDown}
      />
    ));
  }

  setRef = c => {
    this.node = c;
  }

  componentDidUpdate () {
    if (this._scrolledIntoView) {
      return;
    }

    const { status, ancestorsIds } = this.props;

    if (status && ancestorsIds && ancestorsIds.size > 0) {
      const element = this.node.querySelectorAll('.focusable')[ancestorsIds.size - 1];

      element.scrollIntoView(true);
      this._scrolledIntoView = true;
    }
  }

  render () {
    let ancestors, descendants;
    const { status, ancestorsIds, descendantsIds, me, autoPlayGif } = this.props;

    if (status === null) {
      return (
        <Column>
          <ColumnBackButton />
          <MissingIndicator />
        </Column>
      );
    }

    if (ancestorsIds && ancestorsIds.size > 0) {
      ancestors =
{this.renderChildren(ancestorsIds)}
;
    }

    if (descendantsIds && descendantsIds.size > 0) {
      descendants =
{this.renderChildren(descendantsIds)}
;
    }

    const handlers = {
      moveUp: this.handleHotkeyMoveUp,
      moveDown: this.handleHotkeyMoveDown,
      reply: this.handleHotkeyReply,
      favourite: this.handleHotkeyFavourite,
      boost: this.handleHotkeyBoost,
      mention: this.handleHotkeyMention,
      openProfile: this.handleHotkeyOpenProfile,
    };

    return (
      <Column>
        <ColumnBackButton />

        <ScrollContainer scrollKey='thread'>

            {ancestors}

            <HotKeys handlers={handlers}>

                <DetailedStatus
                  status={status}
                  autoPlayGif={autoPlayGif}
                  me={me}
                  onOpenVideo={this.handleOpenVideo}
                  onOpenMedia={this.handleOpenMedia}
                />

                <ActionBar
                  status={status}
                  me={me}
                  onReply={this.handleReplyClick}
                  onFavourite={this.handleFavouriteClick}
                  onReblog={this.handleReblogClick}
                  onDelete={this.handleDeleteClick}
                  onMention={this.handleMentionClick}
                  onReport={this.handleReport}
                  onPin={this.handlePin}
                  onEmbed={this.handleEmbed}
                />

            </HotKeys>

            {descendants}

        </ScrollContainer>
      </Column>
    );
  }

}
<|endoftext|>