<commit_before>const autoprefixer    = require('autoprefixer')
const babelify        = require('babelify')
const brfs            = require('brfs')
const browserify      = require('browserify')
const coffee_reactify = require('coffee-reactify')
const crypto          = require('crypto')
const envify          = require('envify/custom')
const fs              = require('fs-extra')
const path            = require('path')
const postcss         = require('postcss')
const Sass            = require('node-sass')
const SDKError        = require('./SDKError')
const sqwish          = require('sqwish')
const sys             = require('sys')
const UglifyJS        = require('uglify-js')
const walkSync        = require('./walkSync')
const { formatProjectPath } = SDKError

const ERROR_STYLES = 'display: block; font-family: monospace; border-bottom: 3px solid red; padding: 24px; background-color: white; white-space: pre;'

function compileCoffee (source_path, dest_path, project_directory, cb) {
    SDKError.log(SDKError.colors.grey(`Compiling (coffee): ${ source_path.replace(project_directory, '') }`))
    const b = browserify([source_path])
    const compiled = b.transform(
        coffee_reactify
    ).transform(
        envify({ NODE_ENV: process.env.NODE_ENV })
    ).transform(brfs).bundle( (err, compiled) => {
        let compilation_error
        if (err) {
            console.log(err)
            compilation_error = err
            // console.error(err)
            compilation_error.formatted = `CoffeeScript compilation error:
file: ${ err.filename }

${ err.toString() }`
            compiled = `document.body.innerHTML = '<style>body { ${ ERROR_STYLES } }</style><p>' + decodeURIComponent("${ encodeURIComponent(compilation_error.formatted) }") + '</p>'`
        }
        if ('production' === process.env.NODE_ENV) {
            SDKError.log(SDKError.colors.grey(`Minifying ${ source_path.replace(project_directory,'') }`))
            compiled = UglifyJS.minify(compiled.toString(), { fromString: true }).code
        }
        fs.writeFile(dest_path, compiled, (err) => {
            if (err) {
                throw err
            }
            cb(compilation_error)
        })
    })
}

function compileSass (source_path, dest_path, project_directory, cb) {
    SDKError.log(SDKError.colors.grey(`Compiling (sass): ${ source_path.replace(project_directory, '') }`))
    Sass.render({
        file: source_path,
        includePaths: [
            project_directory,
            path.join(project_directory, 'node_modules', 'proof-sdk', 'stylesheets'),
        ],
    } , (err, compiled) => {
            let output
            let sass_error
            if (err) {
                sass_error = err
                let _err_message = err.toString()
                _err_message += '\\A    file: ' + err.file.replace(project_directory, '')
                _err_message += '\\A    line: ' + err.line.toString() + ', column: ' + err.column.toString()
                output = `body::before{ content: '${ _err_message }'; ${ ERROR_STYLES }}`
            } else {
                output = compiled.css
            }
            const _prefixing = postcss([autoprefixer]).process(output)
            _prefixing.then( (result) => {
                compiled = result.css
                if ('production' === process.env.NODE_ENV) {
                    SDKError.log(SDKError.colors.grey(`Minifying ${ source_path.replace(project_directory,'') }`))
                    compiled = sqwish.minify(compiled.toString())
                }
                fs.writeFile(dest_path, compiled, (err) => {
                    if (err) {
                        throw err
                    }
                    cb(sass_error)
                })
            }).catch( (err) => {
                if (err) {
                    throw err
                }
            })
    })
}

function compileJS (source_path, dest_path, project_directory, cb) {
    SDKError.log(SDKError.colors.grey(`Compiling (js/x): ${ source_path.replace(project_directory, '') }`))
    const b = browserify([source_path], { extensions: ['.js', '.jsx', '.es', '.es6']})
    const compiled = b.transform(
        babelify,
        {
            presets: [
                // Require these directly so they can be properly
                // discovered by babel.
                require('babel-preset-react'),
                require('babel-preset-env'),
            ]
        }
    ).transform(
        { global: true },
        envify({ NODE_ENV: process.env.NODE_ENV })
    ).transform(brfs).bundle( (err, compiled) => {
        let compilation_error
        if (err) {
            compilation_error = err
            // console.error(err)
            compilation_error.formatted = `JS compilation error:
file: ${ err.filename }
line: ${ err.loc.line }, column: ${ err.loc.column }

${ err.toString() }`
            compiled = `document.body.innerHTML = '<style>body { ${ ERROR_STYLES } }</style><p>' + decodeURIComponent("${ encodeURIComponent(compilation_error.formatted) }") + '</p>'`
        }
        file_data = compiled.toString()
        if ('production' === process.env.NODE_ENV) {
            SDKError.log(SDKError.colors.grey(`Minifying ${ source_path.replace(project_directory,'') }`))
            file_data = UglifyJS.minify(file_data.toString(), { fromString: true }).code
        }
        fs.writeFile(dest_path, file_data, (err) => {
            if (err) {
                throw err
            }
            cb(compilation_error)
        })
    })
}

function copyAndMinifyJS (source, destination, project_directory, callback) {
    fs.readFile(source, (err, file_data) => {
        if (err) {
            throw err
        }
        if ('production' === process.env.NODE_ENV) {
            SDKError.log(SDKError.colors.grey(`Minifying ${ source.replace(project_directory,'') }`))
            file_data = UglifyJS.minify(file_data.toString(), { fromString: true }).code
        }
        fs.writeFile(destination, file_data, (err) => {
            if (err) {
                throw err
            }
            callback()
        })
    })
}

function copyAndMinifyCSS (source, destination, project_directory, callback) {
    fs.readFile(source, (err, file_data) => {
        if (err) {
            throw err
        }
        if ('production' === process.env.NODE_ENV) {
            SDKError.log(SDKError.colors.grey(`Minifying ${ source.replace(project_directory,'') }`))
            file_data = sqwish.minify(file_data.toString())
        }
        fs.writeFile(destination, file_data, (err) => {
            if (err) {
                throw err
            }
            callback()
        })
    })
}

function copyAsset (source, destination, project_directory, callback) {
    SDKError.log(SDKError.colors.grey(`Copying ${ source.replace(project_directory,'') }`))
    fs.copy(source, destination, () => {
        callback()
    })
}

function processAsset (opts) {
    SDKError.log(`Processing asset: ${ formatProjectPath(opts.project_directory, opts.asset) }`)
    let dest_path = opts.asset.replace(opts.asset_source_dir, opts.asset_cache_dir)
    const path_parts = dest_path.split('.')
    switch (path_parts.pop()) {
        case 'coffee':
            path_parts.push('js')
            dest_path = path_parts.join('.')
            compileCoffee(opts.asset, dest_path, opts.project_directory, (err) => {
                opts.callback(err)
            })
            break
        case 'sass':
        case 'scss':
            path_parts.push('css')
            dest_path = path_parts.join('.')
            compileSass(opts.asset, dest_path, opts.project_directory, (err) => {
                opts.callback(err)
            })
            break
        case 'jsx':
            path_parts.push('js')
            dest_path = path_parts.join('.')
            compileJS(opts.asset, dest_path, opts.project_directory, (err) => {
                opts.callback(err)
            })
            break
        case 'js':
            compileJS(opts.asset, dest_path, opts.project_directory, (err) => {
                opts.callback(err)
            })
            break
        case 'css':
            copyAndMinifyCSS(opts.asset, dest_path, opts.project_directory, () => {
                opts.callback()
            })
            break
        default:
            copyAsset(opts.asset, dest_path, opts.project_directory, () => {
                opts.callback()
            })
    }
}

function copyAssetsToBuild (project_directory, asset_cache_dir, asset_dest_dir) {
    const _to_copy = []
    const _names = ['script.js', 'style.css']
    walkSync(asset_cache_dir).forEach( (f) => {
        // The file is script.js, style.css, or a non-script/-style asset.
        if (['js', 'css'].indexOf(f.split('.').pop()) === -1 || _names.indexOf(f.split('/').pop()) > -1) {
            const dest_path = f.replace(asset_cache_dir, asset_dest_dir)
            _to_copy.push({ source: f, destination: dest_path })
        }
    })

    SDKError.log(`Copying ${ SDKError.colors.green(_to_copy.length) } assets to build: ${ formatProjectPath(project_directory, asset_dest_dir) }`)

    _to_copy.forEach( (f) => {
        fs.copySync(f.source, f.destination)
        compileAssets.files_emitted.push(f.destination)
    })
}

// All assets get compiled and placed into .asset-cache. This allows <Asset>
// tags in the compiler to inline certain files. However, only script.js and
// style.css (and any non-js/-css) get copied into the build output folder.
function compileAssets (opts) {
    // Reset the count each run.
    compileAssets.files_emitted = []

    const {
        project_directory,
        build_directory,
        callback,
        hash_files,
        project_config,
        build_cache,
    } = opts

    const { allow_asset_errors } = opts.command_options

    if (build_cache && build_cache.get('assets/*')) {
        callback(build_cache.get('assets/*'))
        return
    }

    const asset_source_dir    = path.join(project_directory, 'assets')
    const asset_cache_dir     = path.join(project_directory, '.asset-cache')
    if (project_config.ROOT_PREFIX) {
        asset_dest_dir = path.join(build_directory, project_config.ROOT_PREFIX, 'assets')
    } else {
        asset_dest_dir = path.join(build_directory, 'assets')
    }

    // Asset folder is not strictly required, so only warn if it doesn't exist.
    if (!fs.existsSync(asset_source_dir)) {
        SDKError.warn('assets', 'No project ./assets/ folder found.')
        callback && callback(null)
        return
    }

    let asset_hash = null
    if (hash_files) {
        // Hash all the compiled assets, not just the auto ones.
        const compiled_assets = walkSync(asset_source_dir)
        const hash = crypto.createHash('md5')
        compiled_assets.sort()
        compiled_assets.forEach( (asset_path) => {
            const _source_content = fs.readFileSync(asset_path)
            hash.update(_source_content, 'binary')
        })
        asset_hash = hash.digest('hex')
        asset_dest_dir = path.join(asset_dest_dir, asset_hash)
    }

    build_cache && build_cache.set('assets/*', asset_hash)

    fs.ensureDirSync(asset_cache_dir)
    fs.ensureDirSync(asset_dest_dir)

    // Ignore _ prefixed files to prevent trying to compile imported sass files
    // on their own.
    const assets = walkSync(asset_source_dir, ['_','.'])
    let to_process = assets.length

    assets.forEach((asset) => {
        processAsset({
            asset_source_dir    : asset_source_dir,
            asset_cache_dir     : asset_cache_dir,
            asset_dest_dir      : asset_dest_dir,
            asset               : asset,
            project_directory   : project_directory,
            callback: (compilation_error) => {
                if (null != compilation_error) {
                    console.log('\n')
                    console.error(compilation_error.formatted || compilation_error)
                    console.log('\n')
                    if (!allow_asset_errors) {
                        process.exit(1)
                    }
                }
                to_process -= 1
                if (0 === to_process) {
                    copyAssetsToBuild(project_directory, asset_cache_dir, asset_dest_dir)
                    callback && callback(asset_hash)
                }
            },
        })
    })
}

compileAssets.files_emitted = []

// Export processAsset so it can be used directly, bypassing path definitions.
compileAssets.processAsset = processAsset

compileAssets.includeAssets = (opts) => {
    const {
        project_directory,
        build_directory,
        assets,
        asset_hash,
    } = opts

    assets.forEach( (f) => {
        let _f = f.split('.')
        const _ext = _f.pop()
        switch (_ext) {
            case 'coffee':
            case 'cjsx':
            case 'jsx':
                _f.push('js')
                break
            case 'sass':
            case 'scss':
                _f.push('css')
                break
            default:
                _f.push(_ext)
        }
        _f = _f.join('.')
        const _source = path.join(project_directory, '.asset-cache', _f)
        let _dest
        if (asset_hash) {
            _dest = path.join(build_directory, 'assets', asset_hash, _f)
        } else {
            _dest = path.join(build_directory, 'assets', _f)
        }
        if (!fs.existsSync(_source)) {
            throw new SDKError('assets.emitAssets', `Asset specified by emitAssets not found: ${ f }`)
        }
        SDKError.log(SDKError.colors.grey(`Including asset: ${ f }`))
        fs.copySync(_source, _dest)
        compileAssets.files_emitted.push(_dest)
    })
}
module.exports = compileAssets<commit_msg>Fix naming consistency of “compilation_error”<commit_after>const autoprefixer    = require('autoprefixer')
const babelify        = require('babelify')
const brfs            = require('brfs')
const browserify      = require('browserify')
const coffee_reactify = require('coffee-reactify')
const crypto          = require('crypto')
const envify          = require('envify/custom')
const fs              = require('fs-extra')
const path            = require('path')
const postcss         = require('postcss')
const Sass            = require('node-sass')
const SDKError        = require('./SDKError')
const sqwish          = require('sqwish')
const sys             = require('sys')
const UglifyJS        = require('uglify-js')
const walkSync        = require('./walkSync')
const { formatProjectPath } = SDKError

const ERROR_STYLES = 'display: block; font-family: monospace; border-bottom: 3px solid red; padding: 24px; background-color: white; white-space: pre;'

function compileCoffee (source_path, dest_path, project_directory, cb) {
    SDKError.log(SDKError.colors.grey(`Compiling (coffee): ${ source_path.replace(project_directory, '') }`))
    const b = browserify([source_path])
    const compiled = b.transform(
        coffee_reactify
    ).transform(
        envify({ NODE_ENV: process.env.NODE_ENV })
    ).transform(brfs).bundle( (err, compiled) => {
        let compilation_error = null
        if (err) {
            console.log(err)
            compilation_error = err
            // console.error(err)
            compilation_error.formatted = `CoffeeScript compilation error:
file: ${ err.filename }

${ err.toString() }`
            compiled = `document.body.innerHTML = '<style>body { ${ ERROR_STYLES } }</style><p>' + decodeURIComponent("${ encodeURIComponent(compilation_error.formatted) }") + '</p>'`
        }
        if ('production' === process.env.NODE_ENV) {
            SDKError.log(SDKError.colors.grey(`Minifying ${ source_path.replace(project_directory,'') }`))
            compiled = UglifyJS.minify(compiled.toString(), { fromString: true }).code
        }
        fs.writeFile(dest_path, compiled, (err) => {
            if (err) {
                throw err
            }
            cb(compilation_error)
        })
    })
}

function compileSass (source_path, dest_path, project_directory, cb) {
    SDKError.log(SDKError.colors.grey(`Compiling (sass): ${ source_path.replace(project_directory, '') }`))
    Sass.render({
        file: source_path,
        includePaths: [
            project_directory,
            path.join(project_directory, 'node_modules', 'proof-sdk', 'stylesheets'),
        ],
    } , (err, compiled) => {
            let output
            let compilation_error = null
            if (err) {
                compilation_error = err
                let _err_message = err.toString()
                _err_message += '\\A    file: ' + err.file.replace(project_directory, '')
                _err_message += '\\A    line: ' + err.line.toString() + ', column: ' + err.column.toString()
                output = `body::before{ content: '${ _err_message }'; ${ ERROR_STYLES }}`
            } else {
                output = compiled.css
            }
            const _prefixing = postcss([autoprefixer]).process(output)
            _prefixing.then( (result) => {
                compiled = result.css
                if ('production' === process.env.NODE_ENV) {
                    SDKError.log(SDKError.colors.grey(`Minifying ${ source_path.replace(project_directory,'') }`))
                    compiled = sqwish.minify(compiled.toString())
                }
                fs.writeFile(dest_path, compiled, (err) => {
                    if (err) {
                        throw err
                    }
                    cb(compilation_error)
                })
            }).catch( (err) => {
                if (err) {
                    throw err
                }
            })
    })
}

function compileJS (source_path, dest_path, project_directory, cb) {
    SDKError.log(SDKError.colors.grey(`Compiling (js/x): ${ source_path.replace(project_directory, '') }`))
    const b = browserify([source_path], { extensions: ['.js', '.jsx', '.es', '.es6']})
    const compiled = b.transform(
        babelify,
        {
            presets: [
                // Require these directly so they can be properly
                // discovered by babel.
                require('babel-preset-react'),
                require('babel-preset-env'),
            ]
        }
    ).transform(
        { global: true },
        envify({ NODE_ENV: process.env.NODE_ENV })
    ).transform(brfs).bundle( (err, compiled) => {
        let compilation_error = null
        if (err) {
            compilation_error = err
            // console.error(err)
            compilation_error.formatted = `JS compilation error:
file: ${ err.filename }
line: ${ err.loc.line }, column: ${ err.loc.column }

${ err.toString() }`
            compiled = `document.body.innerHTML = '<style>body { ${ ERROR_STYLES } }</style><p>' + decodeURIComponent("${ encodeURIComponent(compilation_error.formatted) }") + '</p>'`
        }
        file_data = compiled.toString()
        if ('production' === process.env.NODE_ENV) {
            SDKError.log(SDKError.colors.grey(`Minifying ${ source_path.replace(project_directory,'') }`))
            file_data = UglifyJS.minify(file_data.toString(), { fromString: true }).code
        }
        fs.writeFile(dest_path, file_data, (err) => {
            if (err) {
                throw err
            }
            cb(compilation_error)
        })
    })
}

function copyAndMinifyJS (source, destination, project_directory, callback) {
    fs.readFile(source, (err, file_data) => {
        if (err) {
            throw err
        }
        if ('production' === process.env.NODE_ENV) {
            SDKError.log(SDKError.colors.grey(`Minifying ${ source.replace(project_directory,'') }`))
            file_data = UglifyJS.minify(file_data.toString(), { fromString: true }).code
        }
        fs.writeFile(destination, file_data, (err) => {
            if (err) {
                throw err
            }
            callback()
        })
    })
}

function copyAndMinifyCSS (source, destination, project_directory, callback) {
    fs.readFile(source, (err, file_data) => {
        if (err) {
            throw err
        }
        if ('production' === process.env.NODE_ENV) {
            SDKError.log(SDKError.colors.grey(`Minifying ${ source.replace(project_directory,'') }`))
            file_data = sqwish.minify(file_data.toString())
        }
        fs.writeFile(destination, file_data, (err) => {
            if (err) {
                throw err
            }
            callback()
        })
    })
}

function copyAsset (source, destination, project_directory, callback) {
    SDKError.log(SDKError.colors.grey(`Copying ${ source.replace(project_directory,'') }`))
    fs.copy(source, destination, () => {
        callback()
    })
}

function processAsset (opts) {
    SDKError.log(`Processing asset: ${ formatProjectPath(opts.project_directory, opts.asset) }`)
    let dest_path = opts.asset.replace(opts.asset_source_dir, opts.asset_cache_dir)
    const path_parts = dest_path.split('.')
    switch (path_parts.pop()) {
        case 'coffee':
            path_parts.push('js')
            dest_path = path_parts.join('.')
            compileCoffee(opts.asset, dest_path, opts.project_directory, (err) => {
                opts.callback(err)
            })
            break
        case 'sass':
        case 'scss':
            path_parts.push('css')
            dest_path = path_parts.join('.')
            compileSass(opts.asset, dest_path, opts.project_directory, (err) => {
                opts.callback(err)
            })
            break
        case 'jsx':
            path_parts.push('js')
            dest_path = path_parts.join('.')
            compileJS(opts.asset, dest_path, opts.project_directory, (err) => {
                opts.callback(err)
            })
            break
        case 'js':
            compileJS(opts.asset, dest_path, opts.project_directory, (err) => {
                opts.callback(err)
            })
            break
        case 'css':
            copyAndMinifyCSS(opts.asset, dest_path, opts.project_directory, () => {
                opts.callback()
            })
            break
        default:
            copyAsset(opts.asset, dest_path, opts.project_directory, () => {
                opts.callback()
            })
    }
}

function copyAssetsToBuild (project_directory, asset_cache_dir, asset_dest_dir) {
    const _to_copy = []
    const _names = ['script.js', 'style.css']
    walkSync(asset_cache_dir).forEach( (f) => {
        // The file is script.js, style.css, or a non-script/-style asset.
        if (['js', 'css'].indexOf(f.split('.').pop()) === -1 || _names.indexOf(f.split('/').pop()) > -1) {
            const dest_path = f.replace(asset_cache_dir, asset_dest_dir)
            _to_copy.push({ source: f, destination: dest_path })
        }
    })

    SDKError.log(`Copying ${ SDKError.colors.green(_to_copy.length) } assets to build: ${ formatProjectPath(project_directory, asset_dest_dir) }`)

    _to_copy.forEach( (f) => {
        fs.copySync(f.source, f.destination)
        compileAssets.files_emitted.push(f.destination)
    })
}

// All assets get compiled and placed into .asset-cache. This allows <Asset>
// tags in the compiler to inline certain files. However, only script.js and
// style.css (and any non-js/-css) get copied into the build output folder.
function compileAssets (opts) {
    // Reset the count each run.
    compileAssets.files_emitted = []

    const {
        project_directory,
        build_directory,
        callback,
        hash_files,
        project_config,
        build_cache,
    } = opts

    const { allow_asset_errors } = opts.command_options

    if (build_cache && build_cache.get('assets/*')) {
        callback(build_cache.get('assets/*'))
        return
    }

    const asset_source_dir    = path.join(project_directory, 'assets')
    const asset_cache_dir     = path.join(project_directory, '.asset-cache')
    if (project_config.ROOT_PREFIX) {
        asset_dest_dir = path.join(build_directory, project_config.ROOT_PREFIX, 'assets')
    } else {
        asset_dest_dir = path.join(build_directory, 'assets')
    }

    // Asset folder is not strictly required, so only warn if it doesn't exist.
    if (!fs.existsSync(asset_source_dir)) {
        SDKError.warn('assets', 'No project ./assets/ folder found.')
        callback && callback(null)
        return
    }

    let asset_hash = null
    if (hash_files) {
        // Hash all the compiled assets, not just the auto ones.
        const compiled_assets = walkSync(asset_source_dir)
        const hash = crypto.createHash('md5')
        compiled_assets.sort()
        compiled_assets.forEach( (asset_path) => {
            const _source_content = fs.readFileSync(asset_path)
            hash.update(_source_content, 'binary')
        })
        asset_hash = hash.digest('hex')
        asset_dest_dir = path.join(asset_dest_dir, asset_hash)
    }

    build_cache && build_cache.set('assets/*', asset_hash)

    fs.ensureDirSync(asset_cache_dir)
    fs.ensureDirSync(asset_dest_dir)

    // Ignore _ prefixed files to prevent trying to compile imported sass files
    // on their own.
    const assets = walkSync(asset_source_dir, ['_','.'])
    let to_process = assets.length

    assets.forEach((asset) => {
        processAsset({
            asset_source_dir    : asset_source_dir,
            asset_cache_dir     : asset_cache_dir,
            asset_dest_dir      : asset_dest_dir,
            asset               : asset,
            project_directory   : project_directory,
            callback: (compilation_error) => {
                if (null != compilation_error) {
                    console.log('\n')
                    console.error(compilation_error.formatted || compilation_error)
                    console.log('\n')
                    if (!allow_asset_errors) {
                        process.exit(1)
                    }
                }
                to_process -= 1
                if (0 === to_process) {
                    copyAssetsToBuild(project_directory, asset_cache_dir, asset_dest_dir)
                    callback && callback(asset_hash)
                }
            },
        })
    })
}

compileAssets.files_emitted = []

// Export processAsset so it can be used directly, bypassing path definitions.
compileAssets.processAsset = processAsset

compileAssets.includeAssets = (opts) => {
    const {
        project_directory,
        build_directory,
        assets,
        asset_hash,
    } = opts

    assets.forEach( (f) => {
        let _f = f.split('.')
        const _ext = _f.pop()
        switch (_ext) {
            case 'coffee':
            case 'cjsx':
            case 'jsx':
                _f.push('js')
                break
            case 'sass':
            case 'scss':
                _f.push('css')
                break
            default:
                _f.push(_ext)
        }
        _f = _f.join('.')
        const _source = path.join(project_directory, '.asset-cache', _f)
        let _dest
        if (asset_hash) {
            _dest = path.join(build_directory, 'assets', asset_hash, _f)
        } else {
            _dest = path.join(build_directory, 'assets', _f)
        }
        if (!fs.existsSync(_source)) {
            throw new SDKError('assets.emitAssets', `Asset specified by emitAssets not found: ${ f }`)
        }
        SDKError.log(SDKError.colors.grey(`Including asset: ${ f }`))
        fs.copySync(_source, _dest)
        compileAssets.files_emitted.push(_dest)
    })
}
module.exports = compileAssets<|endoftext|>


<commit_before>import gql from 'graphql-tag';
import { t, ngettext, msgid } from 'ttag';
import Tooltip from './Tooltip';
import { makeStyles } from '@material-ui/core/styles';
import isValid from 'date-fns/isValid';
import { format, formatDistanceToNow } from 'lib/dateWithLocale';
import { TYPE_ICON } from 'constants/replyType';

const useStyles = makeStyles(theme => ({
  info: {
    color: theme.palette.secondary[200],
    '&:not(:first-child)': {
      marginLeft: 6,
      paddingLeft: 6,
      borderLeft: `1px solid ${theme.palette.secondary[200]}`,
    },
  },
  opinion: {
    display: 'flex',
    alignItems: 'center',
    '&:not(:first-child)': {
      paddingLeft: 15,
    },
    '& > span:nth-child(2)': {
      paddingLeft: 4,
    },
  },
  optionIcon: {
    fontSize: 14,
  },
}));

export default function ArticleInfo({ article }) {
  const createdAt = new Date(article.createdAt);
  const { replyRequestCount, replyCount } = article;
  const timeAgoStr = formatDistanceToNow(createdAt);

  const opinions = (
    article?.articleReplies.map(({ reply }) => reply.type) || []
  ).reduce((result, current) => {
    if (result[current]) {
      result[current] += 1;
    } else {
      result[current] = 1;
    }
    return result;
  }, {});

  const classes = useStyles();

  return (

        {ngettext(
          msgid`${replyRequestCount} occurrence`,
          `${replyRequestCount} occurrences`,
          replyRequestCount
        )}

      {article.replyCount > 0 && (
        <Tooltip
          title={
            <>
              {Object.entries(opinions).map(([k, v]) => {
                const IconComponent = TYPE_ICON[k];
                return (

                    <IconComponent className={classes.optionIcon} />
                    {v}

                );
              })}
            </>
          }
          arrow
        >

            {ngettext(
              msgid`${replyCount} response`,
              `${replyCount} responses`,
              replyCount
            )}

        </Tooltip>
      )}
      {isValid(createdAt) && (
        <Tooltip title={format(createdAt)} arrow>
          {t`${timeAgoStr} ago`}
        </Tooltip>
      )}

  );
}

ArticleInfo.fragments = {
  articleInfo: gql`
    fragment ArticleInfo on Article {
      replyRequestCount
      replyCount
      createdAt
    }
  `,
};
<commit_msg>Fix dev "cannot invoke map of undefined" bug ArticleInfo should query for articleReplies as it is using articleReply data<commit_after>import gql from 'graphql-tag';
import { t, ngettext, msgid } from 'ttag';
import Tooltip from './Tooltip';
import { makeStyles } from '@material-ui/core/styles';
import isValid from 'date-fns/isValid';
import { format, formatDistanceToNow } from 'lib/dateWithLocale';
import { TYPE_ICON } from 'constants/replyType';

const useStyles = makeStyles(theme => ({
  info: {
    color: theme.palette.secondary[200],
    '&:not(:first-child)': {
      marginLeft: 6,
      paddingLeft: 6,
      borderLeft: `1px solid ${theme.palette.secondary[200]}`,
    },
  },
  opinion: {
    display: 'flex',
    alignItems: 'center',
    '&:not(:first-child)': {
      paddingLeft: 15,
    },
    '& > span:nth-child(2)': {
      paddingLeft: 4,
    },
  },
  optionIcon: {
    fontSize: 14,
  },
}));

export default function ArticleInfo({ article }) {
  const createdAt = new Date(article.createdAt);
  const { replyRequestCount, replyCount } = article;
  const timeAgoStr = formatDistanceToNow(createdAt);

  const opinions = (
    article?.articleReplies.map(({ reply }) => reply.type) || []
  ).reduce((result, current) => {
    if (result[current]) {
      result[current] += 1;
    } else {
      result[current] = 1;
    }
    return result;
  }, {});

  const classes = useStyles();

  return (

        {ngettext(
          msgid`${replyRequestCount} occurrence`,
          `${replyRequestCount} occurrences`,
          replyRequestCount
        )}

      {article.replyCount > 0 && (
        <Tooltip
          title={
            <>
              {Object.entries(opinions).map(([k, v]) => {
                const IconComponent = TYPE_ICON[k];
                return (

                    <IconComponent className={classes.optionIcon} />
                    {v}

                );
              })}
            </>
          }
          arrow
        >

            {ngettext(
              msgid`${replyCount} response`,
              `${replyCount} responses`,
              replyCount
            )}

        </Tooltip>
      )}
      {isValid(createdAt) && (
        <Tooltip title={format(createdAt)} arrow>
          {t`${timeAgoStr} ago`}
        </Tooltip>
      )}

  );
}

ArticleInfo.fragments = {
  articleInfo: gql`
    fragment ArticleInfo on Article {
      replyRequestCount
      replyCount
      createdAt
      articleReplies(status: NORMAL) {
        reply {
          type
        }
      }
    }
  `,
};
<|endoftext|>


<commit_before>import React, { PureComponent, createElement } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import omit from 'lodash.omit';
import {
  IconChevronUpSmallOutline,
  IconChevronDownSmallOutline,
  IconWarningBadgedSmallFilled,
} from '@teamleader/ui-icons';
import Box, { omitBoxProps, pickBoxProps } from '../box';
import Button from '../button';
import Counter from '../counter';
import { TextSmall } from '../typography';
import theme from './theme.css';

export default class Input extends PureComponent {
  static getDerivedStateFromProps(nextProps, prevState) {
    if (nextProps.value !== undefined) {
      const newValue = Input.parseValue(nextProps.value || '', nextProps);

      if (newValue !== prevState.value) {
        return {
          value: newValue,
        };
      }
    }

    return null;
  }

  static parseValue(value, { type, min, max }) {
    return type === 'number' ? Input.toNumber(value, min, max) : value;
  }

  static toNumber(number, min, max) {
    let float = parseFloat(number);

    if (isNaN(float) || !isFinite(float)) {
      float = 0;
    }

    float = Math.min(Math.max(float, min), max);

    return float;
  }

  state = {
    value: '',
  };

  handleChange = event => {
    this.updateValue(event);
  };

  handleIncreaseValue = event => {
    this.updateStep(event, 1);
  };

  handleDecreaseValue = event => {
    this.updateStep(event, -1);
  };

  updateValue(event, rawValue, triggerOnChange = true) {
    const { onChange } = this.props;
    const value = Input.parseValue(rawValue || event.target.value, this.props);

    this.setState({
      value,
    });

    if (triggerOnChange && onChange) {
      onChange(event, value);
    }
  }

  formatNumber(number) {
    const { min, max } = this.props;
    return String(Input.toNumber(number, min, max));
  }

  updateStep(event, n) {
    const { step } = this.props;
    const { value = 0 } = this.state;
    this.updateValue(event, value + step * n);
  }

  renderInput() {
    const { bold, max, min, step, type, ...otherProps } = this.props;
    const { value } = this.state;

    const classNames = cx(theme['input'], {
      [theme['is-bold']]: bold,
    });

    const otherPropsWithoutBoxProps = omitBoxProps(otherProps);
    const restProps = omit(otherPropsWithoutBoxProps, [
      'className',
      'connectedLeft',
      'connectedRight',
      'counter',
      'helpText',
      'icon',
      'iconPlacement',
      'inverse',
      'onChange',
      'size',
      'spinner',
      'value',
    ]);

    const numberTypeProps = {
      max,
      min,
      step,
      value: value && this.formatNumber(value),
    };

    const props = {
      className: classNames,
      onChange: this.handleChange,
      type,
      value,
      ...restProps,
      ...(type === 'number' && numberTypeProps),
    };

    return <input {...props} />;
  }

  renderCounter() {
    if (this.props.counter) {
      return <Counter className={theme['counter']} count={this.props.counter} color="ruby" size="small" />;
    }
  }

  renderHelpText() {
    const { helpText, inverse } = this.props;

    if (helpText) {
      return (
        <TextSmall color={inverse ? 'white' : 'neutral'} marginTop={1} soft>
          {helpText}
        </TextSmall>
      );
    }
  }

  renderSpinnerControls() {
    const { disabled, readOnly, spinner, type } = this.props;

    const props = {
      disabled: disabled || readOnly,
    };

    if (type === 'number' && spinner) {
      return (

          <Button
            className={theme['spinner-up']}
            icon={<IconChevronUpSmallOutline />}
            onClick={this.handleIncreaseValue}
            {...props}
          />
          <Button
            className={theme['spinner-down']}
            icon={<IconChevronDownSmallOutline />}
            onClick={this.handleDecreaseValue}
            {...props}
          />

      );
    }
  }

  renderValidationMessage() {
    return (
      <TextSmall className={theme['validation-text']} marginTop={2} display="flex">
        <Box element="span" className={theme['validation-icon']}>
          <IconWarningBadgedSmallFilled />
        </Box>
        <Box element="span" marginLeft={1}>
          {this.props.error}
        </Box>
      </TextSmall>
    );
  }

  render() {
    const {
      className,
      connectedLeft,
      connectedRight,
      counter,
      disabled,
      error,
      icon,
      iconPlacement,
      inverse,
      size,
      spinner,
      readOnly,
      type,
      ...others
    } = this.props;

    const classNames = cx(
      theme['wrapper'],
      theme[`is-${size}`],
      {
        [theme[`has-icon-${iconPlacement}`]]: icon,
        [theme['has-counter']]: counter,
        [theme['has-error']]: error,
        [theme['has-connected-left']]: connectedLeft,
        [theme['has-connected-right']]: connectedRight,
        [theme['has-spinner']]: type === 'number' && spinner,
        [theme['is-inverse']]: inverse,
        [theme['is-disabled']]: disabled,
        [theme['is-read-only']]: readOnly,
      },
      className,
    );

    const inputWrapperClassnames = cx(theme['input-wrapper'], {
      [theme['has-error']]: error,
    });

    const rest = pickBoxProps(others);

    return (
      <Box className={classNames} {...rest}>

          {connectedLeft}

            {icon &&
              createElement(icon, {
                className: theme['icon'],
              })}
            {this.renderInput()}
            {this.renderCounter()}
            {this.renderSpinnerControls()}

          {connectedRight}

        {error ? this.renderValidationMessage() : this.renderHelpText()}
      </Box>
    );
  }
}

Input.propTypes = {
  /** Boolean indicating whether the input text should render in bold. */
  bold: PropTypes.bool,
  /** Sets a class name for the wrapper to give custom styles. */
  className: PropTypes.string,
  connectedLeft: PropTypes.element,
  connectedRight: PropTypes.element,
  /** The number to render as a counter inside the input. */
  counter: PropTypes.number,
  /** Boolean indicating whether the input should render as disabled. */
  disabled: PropTypes.bool,
  /** The text string/element to use as error message below the input. */
  error: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
  /** The text string to use as help text below the input. */
  helpText: PropTypes.string,
  /** The icon displayed inside the input. */
  icon: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
  /** The position of the icon inside the input. */
  iconPlacement: PropTypes.oneOf(['left', 'right']),
  /** Boolean indicating whether the input should render as inverse. */
  inverse: PropTypes.bool,
  max: PropTypes.number,
  min: PropTypes.number,
  /** Boolean indicating whether to number type input should render spinner controls */
  spinner: PropTypes.bool,
  /** Callback function that is fired when the component's value changes. */
  onChange: PropTypes.func,
  /** Boolean indicating whether the input should render as read only. */
  readOnly: PropTypes.bool,
  /** Size of the input element. */
  size: PropTypes.oneOf(['small', 'medium', 'large']),
  /** Limit increment value for numeric inputs. */
  step: PropTypes.number,
  /** Type of the input element. It can be a valid HTML5 input type. */
  type: PropTypes.string,
  /** Current value of the input element. */
  value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
};

Input.defaultProps = {
  iconPlacement: 'left',
  inverse: false,
  disabled: false,
  min: Number.MIN_SAFE_INTEGER,
  max: Number.MAX_SAFE_INTEGER,
  readOnly: false,
  size: 'medium',
  spinner: true,
  step: 1,
};
<commit_msg>Don't parse value initially or on every value change<commit_after>import React, { PureComponent, createElement } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import omit from 'lodash.omit';
import {
  IconChevronUpSmallOutline,
  IconChevronDownSmallOutline,
  IconWarningBadgedSmallFilled,
} from '@teamleader/ui-icons';
import Box, { omitBoxProps, pickBoxProps } from '../box';
import Button from '../button';
import Counter from '../counter';
import { TextSmall } from '../typography';
import theme from './theme.css';

export default class Input extends PureComponent {
  static getDerivedStateFromProps(nextProps, prevState) {
    if (nextProps.value !== undefined) {
      const newValue = nextProps.value || '';

      if (newValue !== prevState.value) {
        return {
          value: newValue,
        };
      }
    }

    return null;
  }

  static parseValue(value, { type, min, max }) {
    return type === 'number' ? Input.toNumber(value, min, max) : value;
  }

  static toNumber(number, min, max) {
    let float = parseFloat(number);

    if (isNaN(float) || !isFinite(float)) {
      float = 0;
    }

    float = Math.min(Math.max(float, min), max);

    return float;
  }

  state = {
    value: '',
  };

  handleChange = event => {
    this.updateValue(event);
  };

  handleIncreaseValue = event => {
    this.updateStep(event, 1);
  };

  handleDecreaseValue = event => {
    this.updateStep(event, -1);
  };

  updateValue(event, rawValue, triggerOnChange = true) {
    const { onChange } = this.props;
    const value = rawValue || event.target.value;

    this.setState({
      value,
    });

    if (triggerOnChange && onChange) {
      onChange(event, value);
    }
  }

  formatNumber(number) {
    const { min, max } = this.props;
    return String(Input.toNumber(number, min, max));
  }

  updateStep(event, n) {
    const { step } = this.props;
    const { value = 0 } = this.state;

    const parsedValue = Input.parseValue(value + step * n, this.props);

    if (value !== parsedValue) {
      this.updateValue(event, parsedValue);
    }
  }

  renderInput() {
    const { bold, max, min, step, type, ...otherProps } = this.props;
    const { value } = this.state;

    const classNames = cx(theme['input'], {
      [theme['is-bold']]: bold,
    });

    const otherPropsWithoutBoxProps = omitBoxProps(otherProps);
    const restProps = omit(otherPropsWithoutBoxProps, [
      'className',
      'connectedLeft',
      'connectedRight',
      'counter',
      'helpText',
      'icon',
      'iconPlacement',
      'inverse',
      'onChange',
      'size',
      'spinner',
      'value',
    ]);

    const numberTypeProps = {
      max,
      min,
      step,
      value: value && this.formatNumber(value),
    };

    const props = {
      className: classNames,
      onChange: this.handleChange,
      type,
      value,
      ...restProps,
      ...(type === 'number' && numberTypeProps),
    };

    return <input {...props} />;
  }

  renderCounter() {
    if (this.props.counter) {
      return <Counter className={theme['counter']} count={this.props.counter} color="ruby" size="small" />;
    }
  }

  renderHelpText() {
    const { helpText, inverse } = this.props;

    if (helpText) {
      return (
        <TextSmall color={inverse ? 'white' : 'neutral'} marginTop={1} soft>
          {helpText}
        </TextSmall>
      );
    }
  }

  renderSpinnerControls() {
    const { disabled, readOnly, spinner, type } = this.props;

    const props = {
      disabled: disabled || readOnly,
    };

    if (type === 'number' && spinner) {
      return (

          <Button
            className={theme['spinner-up']}
            icon={<IconChevronUpSmallOutline />}
            onClick={this.handleIncreaseValue}
            {...props}
          />
          <Button
            className={theme['spinner-down']}
            icon={<IconChevronDownSmallOutline />}
            onClick={this.handleDecreaseValue}
            {...props}
          />

      );
    }
  }

  renderValidationMessage() {
    return (
      <TextSmall className={theme['validation-text']} marginTop={2} display="flex">
        <Box element="span" className={theme['validation-icon']}>
          <IconWarningBadgedSmallFilled />
        </Box>
        <Box element="span" marginLeft={1}>
          {this.props.error}
        </Box>
      </TextSmall>
    );
  }

  render() {
    const {
      className,
      connectedLeft,
      connectedRight,
      counter,
      disabled,
      error,
      icon,
      iconPlacement,
      inverse,
      size,
      spinner,
      readOnly,
      type,
      ...others
    } = this.props;

    const classNames = cx(
      theme['wrapper'],
      theme[`is-${size}`],
      {
        [theme[`has-icon-${iconPlacement}`]]: icon,
        [theme['has-counter']]: counter,
        [theme['has-error']]: error,
        [theme['has-connected-left']]: connectedLeft,
        [theme['has-connected-right']]: connectedRight,
        [theme['has-spinner']]: type === 'number' && spinner,
        [theme['is-inverse']]: inverse,
        [theme['is-disabled']]: disabled,
        [theme['is-read-only']]: readOnly,
      },
      className,
    );

    const inputWrapperClassnames = cx(theme['input-wrapper'], {
      [theme['has-error']]: error,
    });

    const rest = pickBoxProps(others);

    return (
      <Box className={classNames} {...rest}>

          {connectedLeft}

            {icon &&
              createElement(icon, {
                className: theme['icon'],
              })}
            {this.renderInput()}
            {this.renderCounter()}
            {this.renderSpinnerControls()}

          {connectedRight}

        {error ? this.renderValidationMessage() : this.renderHelpText()}
      </Box>
    );
  }
}

Input.propTypes = {
  /** Boolean indicating whether the input text should render in bold. */
  bold: PropTypes.bool,
  /** Sets a class name for the wrapper to give custom styles. */
  className: PropTypes.string,
  connectedLeft: PropTypes.element,
  connectedRight: PropTypes.element,
  /** The number to render as a counter inside the input. */
  counter: PropTypes.number,
  /** Boolean indicating whether the input should render as disabled. */
  disabled: PropTypes.bool,
  /** The text string/element to use as error message below the input. */
  error: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
  /** The text string to use as help text below the input. */
  helpText: PropTypes.string,
  /** The icon displayed inside the input. */
  icon: PropTypes.oneOfType([PropTypes.func, PropTypes.element]),
  /** The position of the icon inside the input. */
  iconPlacement: PropTypes.oneOf(['left', 'right']),
  /** Boolean indicating whether the input should render as inverse. */
  inverse: PropTypes.bool,
  max: PropTypes.number,
  min: PropTypes.number,
  /** Boolean indicating whether to number type input should render spinner controls */
  spinner: PropTypes.bool,
  /** Callback function that is fired when the component's value changes. */
  onChange: PropTypes.func,
  /** Boolean indicating whether the input should render as read only. */
  readOnly: PropTypes.bool,
  /** Size of the input element. */
  size: PropTypes.oneOf(['small', 'medium', 'large']),
  /** Limit increment value for numeric inputs. */
  step: PropTypes.number,
  /** Type of the input element. It can be a valid HTML5 input type. */
  type: PropTypes.string,
  /** Current value of the input element. */
  value: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
};

Input.defaultProps = {
  iconPlacement: 'left',
  inverse: false,
  disabled: false,
  min: Number.MIN_SAFE_INTEGER,
  max: Number.MAX_SAFE_INTEGER,
  readOnly: false,
  size: 'medium',
  spinner: true,
  step: 1,
};
<|endoftext|>


<commit_before>(function (d3, sl) {
    'use strict';

    sl.series.ohlc = function (drawMethod) {

        // Configurable attributes
        var xScale = d3.time.scale(),
            yScale = d3.scale.linear(),
            tickWidth = 5;

        // Function to return
        var ohlc;

        // Accessor functions
        var open = function (d) {
                return yScale(d.open);
            },
            high = function (d) {
                return yScale(d.high);
            },
            low = function (d) {
                return yScale(d.low);
            },
            close = function (d) {
                return yScale(d.close);
            },
            date = function (d) {
                return xScale(d.date);
            };

        // Up/down day logic
        var isUpDay = function(d) {
            return d.close > d.open;
        };
        var isDownDay = function (d) {
            return d.close < d.open;
        };
        var isStaticDay = function (d) {
            return d.close === d.open;
        };

        // Path drawing
        var makeBarPath = function (d) {
            var moveToLow = 'M' + date(d) + ',' + low(d),
                verticalToHigh = 'V' + high(d),
                openTick = 'M' + date(d) + "," + open(d) + 'h' + (-tickWidth),
                closeTick = 'M' + date(d) + "," + close(d) + 'h' + tickWidth;
            return moveToLow + verticalToHigh + openTick + closeTick;
        };

        var makeConcatPath = function (data) {
            var path = 'M0,0';
            data.forEach(function (d) {
                path += makeBarPath(d);
            });
            return path;
        };

        // For ohlcConcatBarPaths
        var makeConcatPathElement = function(series, elementClass, data, filterFunction) {
            var concatPath;
            var filteredData = data.filter(function (d) {
                return filterFunction(d);
            });
            concatPath = series.selectAll('.' + elementClass)
                .data([filteredData]);

            concatPath.enter()
                .append('path')
                .classed(elementClass, true);

            concatPath
                .attr('d', makeConcatPath(filteredData));

            concatPath.exit().remove();
        };

        // Common series element
        var makeSeriesElement = function (selection, data) {
            var series = d3.select(selection).selectAll('.ohlc-series').data([data]);
            series.enter().append('g').classed('ohlc-series', true);
            return series;
        };

        // Draw ohlc bars as groups of svg lines
        var ohlcLineGroups = function (selection) {
            selection.each(function (data) {
                var series = makeSeriesElement(this, data);

                var bars = series.selectAll('.bar')
                    .data(data, function (d) {
                        return d.date;
                    });

                // Enter
                var barEnter = bars.enter().append('g').classed('bar', true);
                barEnter.append('line').classed('high-low-line', true);
                barEnter.append('line').classed('open-tick', true);
                barEnter.append('line').classed('close-tick', true);

                // Update
                bars.classed({
                    'up-day': isUpDay,
                    'down-day': isDownDay,
                    'static-day': isStaticDay
                });
                bars.select('.high-low-line').attr({x1: date, y1: low, x2: date, y2: high });
                bars.select('.open-tick').attr({
                    x1: function (d) { return date(d) - tickWidth; },
                    y1: open,
                    x2: date,
                    y2: open
                });
                bars.select('.close-tick').attr({
                    x1: date,
                    y1: close,
                    x2: function (d) { return date(d) + tickWidth; },
                    y2: close
                });

                // Exit
                bars.exit().remove();
            });
        };

        // Draw ohlc bars as svg paths
        var ohlcBarPaths = function (selection) {
            selection.each(function (data) {
                var series = makeSeriesElement(this, data);

                var bars = series.selectAll('.bar')
                    .data(data, function (d) {
                        return d.date;
                    });

                bars.enter()
                    .append('path')
                    .classed('bar', true);

                bars.classed({
                    'up-day': isUpDay,
                    'down-day': isDownDay,
                    'static-day': isStaticDay
                });

                bars.attr('d', function (d) {
                    return makeBarPath(d);
                });

                bars.exit().remove();
            });
        };

        // Draw the complete series of ohlc bars using 3 paths
        var ohlcConcatBarPaths = function (selection) {
            selection.each(function (data) {
                var series = makeSeriesElement(this, data);
                makeConcatPathElement(series, 'up-days', data, isUpDay);
                makeConcatPathElement(series, 'down-days', data, isDownDay);
                makeConcatPathElement(series, 'static-days', data, isStaticDay);
            });
        };

        switch (drawMethod) {
            case 'groups': ohlc = ohlcLineGroups; break;
            case 'paths': ohlc = ohlcBarPaths; break;
            case 'concatpaths': ohlc = ohlcConcatBarPaths; break;
            default: ohlc = ohlcConcatBarPaths;
        }

        ohlc.xScale = function (value) {
            if (!arguments.length) {
                return xScale;
            }
            xScale = value;
            return ohlc;
        };

        ohlc.yScale = function (value) {
            if (!arguments.length) {
                return yScale;
            }
            yScale = value;
            return ohlc;
        };

        ohlc.tickWidth = function (value) {
            if (!arguments.length) {
                return tickWidth;
            }
            tickWidth = value;
            return ohlc;
        };

        return ohlc;
    };
}(d3, sl));<commit_msg>d3-financial-components-16 Add default colours to ohlc bars.<commit_after>(function (d3, sl) {
    'use strict';

    sl.series.ohlc = function (drawMethod) {

        // Configurable attributes
        var xScale = d3.time.scale(),
            yScale = d3.scale.linear(),
            tickWidth = 5;

        // Function to return
        var ohlc;

        // Accessor functions
        var open = function (d) {
                return yScale(d.open);
            },
            high = function (d) {
                return yScale(d.high);
            },
            low = function (d) {
                return yScale(d.low);
            },
            close = function (d) {
                return yScale(d.close);
            },
            date = function (d) {
                return xScale(d.date);
            };

        // Up/down day logic
        var isUpDay = function(d) {
            return d.close > d.open;
        };
        var isDownDay = function (d) {
            return d.close < d.open;
        };
        var isStaticDay = function (d) {
            return d.close === d.open;
        };

        var barColour = function(d) {
            if (isUpDay(d)) {
                return 'green';
            } else if (isDownDay(d)) {
                return 'red';
            } else {
                return 'black';
            }
        };

        // Path drawing
        var makeBarPath = function (d) {
            var moveToLow = 'M' + date(d) + ',' + low(d),
                verticalToHigh = 'V' + high(d),
                openTick = 'M' + date(d) + "," + open(d) + 'h' + (-tickWidth),
                closeTick = 'M' + date(d) + "," + close(d) + 'h' + tickWidth;
            return moveToLow + verticalToHigh + openTick + closeTick;
        };

        var makeConcatPath = function (data) {
            var path = 'M0,0';
            data.forEach(function (d) {
                path += makeBarPath(d);
            });
            return path;
        };

        // Filters data, and draws a series of ohlc bars from the result as a single path.
        var makeConcatPathElement = function(series, elementClass, colour, data, filterFunction) {
            var concatPath;
            var filteredData = data.filter(function (d) {
                return filterFunction(d);
            });
            concatPath = series.selectAll('.' + elementClass)
                .data([filteredData]);

            concatPath.enter()
                .append('path')
                .classed(elementClass, true);

            concatPath
                .attr('d', makeConcatPath(filteredData))
                .attr('stroke', colour);

            concatPath.exit().remove();
        };

        // Common series element
        var makeSeriesElement = function (selection, data) {
            var series = d3.select(selection).selectAll('.ohlc-series').data([data]);
            series.enter().append('g').classed('ohlc-series', true);
            return series;
        };

        // Draw ohlc bars as groups of svg lines
        var ohlcLineGroups = function (selection) {
            selection.each(function (data) {
                var series = makeSeriesElement(this, data);

                var bars = series.selectAll('.bar')
                    .data(data, function (d) {
                        return d.date;
                    });

                // Enter
                var barEnter = bars.enter().append('g').classed('bar', true);
                barEnter.append('line').classed('high-low-line', true);
                barEnter.append('line').classed('open-tick', true);
                barEnter.append('line').classed('close-tick', true);

                // Update
                bars.classed({
                    'up-day': isUpDay,
                    'down-day': isDownDay,
                    'static-day': isStaticDay
                });

                bars.attr('stroke', barColour);

                bars.select('.high-low-line').attr({x1: date, y1: low, x2: date, y2: high });
                bars.select('.open-tick').attr({
                    x1: function (d) { return date(d) - tickWidth; },
                    y1: open,
                    x2: date,
                    y2: open
                });
                bars.select('.close-tick').attr({
                    x1: date,
                    y1: close,
                    x2: function (d) { return date(d) + tickWidth; },
                    y2: close
                });

                // Exit
                bars.exit().remove();
            });
        };

        // Draw ohlc bars as svg paths
        var ohlcBarPaths = function (selection) {
            selection.each(function (data) {
                var series = makeSeriesElement(this, data);

                var bars = series.selectAll('.bar')
                    .data(data, function (d) {
                        return d.date;
                    });

                bars.enter()
                    .append('path')
                    .classed('bar', true);

                bars.classed({
                    'up-day': isUpDay,
                    'down-day': isDownDay,
                    'static-day': isStaticDay
                });

                bars.attr({
                    'stroke': barColour,
                    'd': makeBarPath
                });

                bars.exit().remove();
            });
        };

        // Draw the complete series of ohlc bars using 3 paths
        var ohlcConcatBarPaths = function (selection) {
            selection.each(function (data) {
                var series = makeSeriesElement(this, data);
                makeConcatPathElement(series, 'up-days', 'green', data, isUpDay);
                makeConcatPathElement(series, 'down-days', 'red' ,data, isDownDay);
                makeConcatPathElement(series, 'static-days', 'black', data, isStaticDay);
            });
        };

        switch (drawMethod) {
            case 'line': ohlc = ohlcLineGroups; break;
            case 'path': ohlc = ohlcBarPaths; break;
            case 'paths': ohlc = ohlcConcatBarPaths; break;
            default: ohlc = ohlcBarPaths;
        }

        ohlc.xScale = function (value) {
            if (!arguments.length) {
                return xScale;
            }
            xScale = value;
            return ohlc;
        };

        ohlc.yScale = function (value) {
            if (!arguments.length) {
                return yScale;
            }
            yScale = value;
            return ohlc;
        };

        ohlc.tickWidth = function (value) {
            if (!arguments.length) {
                return tickWidth;
            }
            tickWidth = value;
            return ohlc;
        };

        return ohlc;
    };
}(d3, sl));<|endoftext|>


<commit_before>import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import ActivableRenderer from '../hoc/ActivableRenderer';
import { Button, IconButton } from '../button';
import Portal from '../hoc/Portal';
import { IconCloseMediumOutline } from '@teamleader/ui-icons';
import theme from './theme.css';

const factory = (Button, IconButton) => {
  class Toast extends PureComponent {
    static propTypes = {
      action: PropTypes.string,
      active: PropTypes.bool,
      children: PropTypes.node,
      className: PropTypes.string,
      label: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
      onClick: PropTypes.func,
      onTimeout: PropTypes.func,
      timeout: PropTypes.number,
      type: PropTypes.oneOf(['accept', 'cancel', 'warning']),
    };

    componentDidMount() {
      if (this.props.active && this.props.timeout) {
        this._scheduleTimeout(this.props);
      }
    }

    componentWillReceiveProps(nextProps) {
      if (nextProps.active && nextProps.timeout) {
        this._scheduleTimeout(nextProps);
      }
    }

    componentWillUnmount() {
      clearTimeout(this.currentTimeout);
    }

    _scheduleTimeout = props => {
      const { onTimeout, timeout } = props;

      if (this.currentTimeout) {
        clearTimeout(this.currentTimeout);
      }

      this.currentTimeout = setTimeout(() => {
        if (onTimeout) {
          onTimeout();
        }

        this.currentTimeout = null;
      }, timeout);
    };

    render() {
      const { action, active, children, className, label, onClick, type } = this.props;

      const classNames = cx(
        theme['toast'],
        theme[type],
        {
          [theme['active']]: active,
        },
        className,
      );

      return (
        <Portal>

              {label}
              {children}

            {onClick ? (
              action ? (
                <Button className={theme['button']} label={action} level="outline" onClick={onClick} size="small" />
              ) : (
                <IconButton
                  className={theme['icon-button']}
                  icon={<IconCloseMediumOutline />}
                  color="white"
                  onClick={onClick}
                />
              )
            ) : null}

        </Portal>
      );
    }
  }

  return ActivableRenderer()(Toast);
};

const Toast = factory(Button, IconButton);

export default Toast;
export { factory as toastFactory, Toast };
<commit_msg>Converted our custom Portal and ActivableRenderer to a React v16 portal<commit_after>import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import { Button, IconButton } from '../button';
import { createPortal } from 'react-dom';
import { IconCloseMediumOutline } from '@teamleader/ui-icons';
import theme from './theme.css';

const factory = (Button, IconButton) => {
  class Toast extends PureComponent {
    static propTypes = {
      action: PropTypes.string,
      active: PropTypes.bool,
      children: PropTypes.node,
      className: PropTypes.string,
      label: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
      onClick: PropTypes.func,
      onTimeout: PropTypes.func,
      timeout: PropTypes.number,
      type: PropTypes.oneOf(['accept', 'cancel', 'warning']),
    };

    constructor() {
      super(...arguments);

      this.toastRoot = document.createElement('div');
      this.toastRoot.id = 'toast-root';
    }

    componentDidMount() {
      document.body.appendChild(this.toastRoot);

      if (this.props.active && this.props.timeout) {
        this._scheduleTimeout(this.props);
      }
    }

    componentWillReceiveProps(nextProps) {
      if (nextProps.active && nextProps.timeout) {
        this._scheduleTimeout(nextProps);
      }
    }

    componentWillUnmount() {
      clearTimeout(this.currentTimeout);
      document.body.removeChild(this.toastRoot);
    }

    _scheduleTimeout = props => {
      const { onTimeout, timeout } = props;

      if (this.currentTimeout) {
        clearTimeout(this.currentTimeout);
      }

      this.currentTimeout = setTimeout(() => {
        if (onTimeout) {
          onTimeout();
        }

        this.currentTimeout = null;
      }, timeout);
    };

    render() {
      const { action, active, children, className, label, onClick, type } = this.props;

      const classNames = cx(
        theme['toast'],
        theme[type],
        {
          [theme['active']]: active,
        },
        className,
      );

      const toast = (

            {label}
            {children}

          {onClick ? (
            action ? (
              <Button className={theme['button']} label={action} level="outline" onClick={onClick} size="small" />
            ) : (
              <IconButton
                className={theme['icon-button']}
                icon={<IconCloseMediumOutline />}
                color="white"
                onClick={onClick}
              />
            )
          ) : null}

      );

      return createPortal(toast, this.toastRoot);
    }
  }

  return Toast;
};

const Toast = factory(Button, IconButton);

export default Toast;
export { factory as toastFactory, Toast };
<|endoftext|>


<commit_before>/* global test:false, expect:false, describe:false, */

const { getProtectedHttpAgent, FORBIDDEN_IP_ADDRESS } = require('../../src/server/helpers/request')
const request = require('request')

describe('test protected request Agent', () => {
  test('allows URLs without IP addresses', (done) => {
    const options = {
      uri: 'https://www.transloadit.com',
      method: 'GET',
      agentClass: getProtectedHttpAgent('https', true)
    }

    request(options, (err) => {
      if (err) {
        expect(err.message).not.toEqual(FORBIDDEN_IP_ADDRESS)
        done()
      } else {
        done()
      }
    })
  })

  test('blocks private http IP address', (done) => {
    const options = {
      uri: 'http://172.20.10.4:8090',
      method: 'GET',
      agentClass: getProtectedHttpAgent('http', true)
    }

    request(options, (err) => {
      expect(err).toBeInstanceOf(Error)
      expect(err.message).toEqual(FORBIDDEN_IP_ADDRESS)
      done()
    })
  })

  test('blocks private https IP address', (done) => {
    const options = {
      uri: 'https://172.20.10.4:8090',
      method: 'GET',
      agentClass: getProtectedHttpAgent('https', true)
    }

    request(options, (err) => {
      expect(err).toBeInstanceOf(Error)
      expect(err.message).toEqual(FORBIDDEN_IP_ADDRESS)
      done()
    })
  })

  test('blocks localhost IP address', (done) => {
    const options = {
      uri: 'http://127.0.0.1:8090',
      method: 'GET',
      agentClass: getProtectedHttpAgent('http', true)
    }

    request(options, (err) => {
      expect(err).toBeInstanceOf(Error)
      expect(err.message).toEqual(FORBIDDEN_IP_ADDRESS)
      done()
    })
  })

  test('blocks URLs that have DNS pinned to a private IP address', (done) => {
    const options = {
      uri: 'http://127.0.0.1.xip.io:8090',
      method: 'GET',
      agentClass: getProtectedHttpAgent('http', true)
    }

    request(options, (err) => {
      expect(err).toBeInstanceOf(Error)
      expect(err.message.startsWith(FORBIDDEN_IP_ADDRESS)).toEqual(true)
      done()
    })
  })
})
<commit_msg>test: improve test conditions for IP blocker<commit_after>/* global test:false, expect:false, describe:false, */

const { getProtectedHttpAgent, FORBIDDEN_IP_ADDRESS } = require('../../src/server/helpers/request')
const request = require('request')

describe('test protected request Agent', () => {
  test('allows URLs without IP addresses', (done) => {
    const options = {
      uri: 'https://www.transloadit.com',
      method: 'GET',
      agentClass: getProtectedHttpAgent('https', true)
    }

    request(options, (err) => {
      if (err) {
        expect(err.message).not.toEqual(FORBIDDEN_IP_ADDRESS)
        expect(err.message.startsWith(FORBIDDEN_IP_ADDRESS)).toEqual(false)
        done()
      } else {
        done()
      }
    })
  })

  test('blocks private http IP address', (done) => {
    const options = {
      uri: 'http://172.20.10.4:8090',
      method: 'GET',
      agentClass: getProtectedHttpAgent('http', true)
    }

    request(options, (err) => {
      expect(err).toBeInstanceOf(Error)
      expect(err.message).toEqual(FORBIDDEN_IP_ADDRESS)
      done()
    })
  })

  test('blocks private https IP address', (done) => {
    const options = {
      uri: 'https://172.20.10.4:8090',
      method: 'GET',
      agentClass: getProtectedHttpAgent('https', true)
    }

    request(options, (err) => {
      expect(err).toBeInstanceOf(Error)
      expect(err.message).toEqual(FORBIDDEN_IP_ADDRESS)
      done()
    })
  })

  test('blocks localhost IP address', (done) => {
    const options = {
      uri: 'http://127.0.0.1:8090',
      method: 'GET',
      agentClass: getProtectedHttpAgent('http', true)
    }

    request(options, (err) => {
      expect(err).toBeInstanceOf(Error)
      expect(err.message).toEqual(FORBIDDEN_IP_ADDRESS)
      done()
    })
  })

  test('blocks URLs that have DNS pinned to a private IP address', (done) => {
    const options = {
      uri: 'http://127.0.0.1.xip.io:8090',
      method: 'GET',
      agentClass: getProtectedHttpAgent('http', true)
    }

    request(options, (err) => {
      expect(err).toBeTruthy(Error)
      expect(err.message.startsWith(FORBIDDEN_IP_ADDRESS)).toEqual(true)
      done()
    })
  })
})
<|endoftext|>


<commit_before>/*
This module contains functions for generating lists of items that,
when clicked, show corresponding content or trigger corresponding
logic. Here are three examples.

### CT.panel.simple(pnames, keystring, itemnode, panelnode, cbs)
This function wraps CT.panel.load(), supporting a subset of load()'s
options (the simple ones). It supports the following args, of which
only the first is required:

    - pnames (string array): short for 'panel names'
    - keystring (string, default: 'sb'): identifier for collections of content/lister nodes
    - itemnode (node, default: CT.dom.id(keystring + "items")): parent node for lister items
    - panelnode (node, default: CT.dom.id(keystring + "panels")): parent node for content panels
    - cbs (function array, optional): callbacks to invoke on lister item click post- panel swap

### CT.panel.pager(getContent, request, limit, colClass, dataClass, ks)
This function generates and returns a node containing a
(CT.Pager-backed) paging lister node and a corresponding content panel.

    - getContent (function): combined with CT.panel.simple() in pager's renderCb
    - request (function): pager's requestCb
    - limit (number, default: 20): pager's limit (chunk size)
    - colClass (string, optional): class of generated pager (list selector) node
    - dataClass (string, optional): class of generated data (content panel) node
    - ks (string, default: "p" + CT.Pager._id): keystring of data and list nodes

### CT.panel.triggerList(data, cb, node)
This function fills the 'node' node with a list of clickable items, each of
which triggers cb(d), where d is the corresponding object in the 'data' array.

    - data (object array): data set used to generate list
      - for d in data: link content equals d.label || d.title
    - cb (function): the callback to invoke when an item is clicked
    - node (node): the list parent node
*/

CT.panel = {
	"lastClicked": {},
	"accordion": function(data, triggerCb, activeClass, content, condition, notick, rmbutton, ricon, noactive) {
		var n = CT.dom.node(null, null, "tabbed", "ac" + data.name);
		n.add = function(tdata, makeFirst, addAlpha) {
			var i, inserted, t = CT.panel.trigger(tdata, triggerCb,
				activeClass, content, condition, rmbutton, ricon, noactive);
			if (makeFirst)
				n.insertBefore(t, n.firstChild);
			else if (addAlpha) {
				for (i = 0; i < n.childNodes.length; i++) {
					if (n.childNodes[i].data.name > t.data.name) {
						n.insertBefore(t, n.childNodes[i]);
						inserted = true;
						break;
					}
				}
				if (!inserted)
					n.appendChild(t);
			} else
				n.appendChild(t);
			return t;
		};
		n._drops = [];
		n.expand = function() {
			n._drops.forEach(function(d) {
				d.expand();
			});
		};
		n.retract = function() {
			n._drops.forEach(function(d) {
				d.retract();
			});
		};
		data.forEach(function(d) {
			if (d.children) {
				var tick = CT.dom.node(">", "div", "inline-block" + (notick && " hidden" || "")),
					t = CT.dom.node(),
					tline = [tick, CT.dom.link(content ? content(d) : d.name, null, null, "inline-block pointer acheader")];
				if (ricon)
					tline.unshift(CT.dom.node(ricon(d), "div", "right rpaddedsmall"));
				CT.dom.setContent(t, tline);
				t.content = CT.panel.accordion(d.children, triggerCb,
					activeClass, content, condition, notick, rmbutton, ricon, noactive);
				n.appendChild(t);
				n._drops.push(new CT.Drop({
					anchor: t,
					parent: n,
					relative: true,
					constrained: false,
					setNodeClick: false,
					content: t.content,
					onShow: function() {
						CT.trans.rotate(tick, { duration: 100, degrees: 90 });
					},
					onHide: function() {
						CT.trans.rotate(tick, { duration: 100, degrees: 0 });
					}
				}));
			} else
				n.add(d);
		});
		return n;
	},
	"trigger": function(data, cb, activeClass, content, condition, rmbutton, ricon, noactive) {
		var label = data.label || data.title || data.name || data.topic || data,
			key = data.key || label,
			n = CT.dom.node(null, "div", "tlitem", key && ("tl" + key.replace(/ /g, "")));
		activeClass = activeClass || "activetab";
		n.data = data;
		data.node = n;
		n.select = function() {
			!noactive && CT.dom.each(n.parentNode, function(c) {
				if (c == n)
					c.classList.add(activeClass);
				else
					c.classList.remove(activeClass);
				c.className = (c == n) ? activeClass : "";
			});
		};
		n.trigger = function() {
			if (condition && !condition()) return;
			n.select();
			cb(data);
		};
		n.remove = function() {
			CT.dom.remove(n);
			n.onRemove && n.onRemove();
		};
		n.nameNode = CT.dom.link(content ? content(data) : label, n.trigger);
		n.rename = function(name) {
			data.label = name;
			n.id = "tl" + name.replace(/ /g, "");
			CT.dom.setContent(n.nameNode, name);
		};
		if (ricon)
			n.appendChild(CT.dom.node(ricon(data), "div", "right rpaddedsmall"));
		if (rmbutton)
			n.appendChild(CT.dom.link("X", n.remove, null, "right"));
		n.appendChild(n.nameNode);
		return n;
	},
	"triggerList": function(data, cb, node, activeClass, content, condition, rmbutton, ricon, noactive) {
		node = node || CT.dom.node();
		node.postAdd = function(d, trigger) {
			var t = CT.panel.trigger(d, cb, activeClass, content, condition, rmbutton, ricon, noactive);
			node.appendChild(t);
			(trigger === true) && t.trigger();
		};
		node.preAdd = function(d, trigger) {
			var t = CT.panel.trigger(d, cb, activeClass, content, condition, rmbutton, ricon, noactive);
			if (node.childNodes.length)
				node.insertBefore(t, node.firstChild);
			else
				node.appendChild(t);
			trigger && t.trigger();
		};
		data.forEach(node.postAdd);
		return node;
	},
	"swap": function(key, trysidepanel, keystring, noitem) {
	    keystring = keystring || "sb";
	    key = key.replace(/ /g, "");
	    var items = CT.dom.className(keystring + "panel");
	    for (var i = 0; i < items.length; i++)
	        items[i].className = keystring + "panel hidden";
	    CT.dom.id(keystring +  "panel" + key, true).className = keystring + "panel";
	    if (trysidepanel) {
	        var n = CT.dom.id(keystring + "panel" + key + "Side");
	        if (n) n.className = keystring+"panel";
	        var n2 = CT.dom.id(keystring + "panel" + key + "Side2");
	        if (n2) n2.className = keystring + "panel";
	    }
	    if (!noitem)
	        CT.panel.select(key, keystring);
	},
	"drill": function(keymap) { // keystring: key
		for (var k in keymap)
			CT.panel.swap(keymap[k], false, k);
	},
	"select": function(key, keystring) {
	    keystring = keystring || "sb";
	    var items = CT.dom.className(keystring + "item");
	    for (var i = 0; i < items.length; i++)
	        items[i].className = items[i].className.replace(" activetab", "");
	    if (key) {
	        CT.dom.id(keystring + "item" + key, true).className += " activetab";
	        CT.panel.lastClicked[keystring] = key;
	    }
	    CT.mobile && CT.mobile.mobileSnap();
	},
	"selectLister": function(newkey, oldkey, newhtml) {
	    if (oldkey)
	        CT.dom.id("ll" + oldkey).className = "pointer";
	    var newnode = CT.dom.id("ll" + newkey);
	    newnode.className = "pointer activetab";
	    if (newhtml)
	        newnode.firstChild.innerHTML = newhtml;
	    CT.mobile && CT.mobile.mobileSnap();
	},
	"add": function(key, trysidepanel, keystring, itemnode, panelnode, nospace, icon, cb, condition) {
	    nospace = nospace || key.replace(/ /g, "");
	    keystring = keystring || "sb";
	    if (!CT.dom.id(keystring + "panel" + nospace)) {
	        var n = CT.dom.node("", "div", keystring + "panel hidden", keystring + "panel" + nospace);
	        n.appendChild(CT.dom.node(key, "div", "bigger blue bold bottompadded"));
	        n.appendChild(CT.dom.node("", "div", "", keystring+"content"+nospace));
	        (panelnode || CT.dom.id(keystring+"panels")).appendChild(n);
	    }
	    var i = CT.dom.id(keystring+"item"+nospace);
	    if (i)
	        i.style.display = "block";
	    else {
	        itemnode = itemnode || CT.dom.id(keystring+"items");
	        var clickfunc = function() {
	        	if (condition && !condition()) return;
	            CT.panel.swap(nospace, trysidepanel, keystring);
	            cb && cb();
	        };
	        if (icon)
	            itemnode.appendChild(CT.dom.node(CT.dom.img(icon,
	                null, clickfunc), "div", "lfloat shiftleft"));
	        itemnode.appendChild(CT.dom.node(CT.dom.link(key, clickfunc),
	            "div", keystring + "item pointer", keystring + "item" + nospace));
	        if (icon)
	            itemnode.appendChild(CT.dom.node("", "div", "clearnode"));
	    }
	},
	"rename": function(oldkey, newkey, keystring, item2keystring) {
		keystring = keystring || "sb";
		var nsold = oldkey.replace(/ /g, ""),
			nsnew = newkey.replace(/ /g, ""),
			item = CT.dom.id(keystring + "item" + nsold),
			panel = CT.dom.id(keystring + "panel" + nsold),
			content = CT.dom.id(keystring + "content" + nsold);
		item.id = keystring + "item" + nsnew;
		panel.id = keystring + "panel" + nsnew;
		if (content) content.id = keystring + "content" + nsnew;
		item.firstChild.innerHTML = panel.firstChild.innerHTML = newkey;
		if (item2keystring) {
			var item2 = CT.dom.id(item2keystring + "item" + nsold);
			item2.id = nsnew;
			item2.firstChild.innerHTML = newkey;
		}
	},
	"remove": function(key, keystring) {
	    var nospace = key.replace(/ /g, "");
	    var p = CT.dom.id(keystring+"panel"+nospace);
	    if (p) p.style.display = "none";
	    var l = CT.dom.id(keystring+"item"+nospace);
	    if (l) l.style.display = "none";
	},
	"load": function(pnames, trysidepanel, keystring, itemnode, panelnode, nospaces, icons, noclear, stillswap, cbs, condition) {
	    keystring = keystring || "sb";
	    if (!noclear)
	        (itemnode || CT.dom.id(keystring+"items")).innerHTML = "";
	    for (var i = 0; i < pnames.length; i++)
	        CT.panel.add(pnames[i], trysidepanel, keystring, itemnode,
	            panelnode, nospaces && nospaces[i] || null,
	            icons && icons[i] || null, cbs && cbs[i] || null, condition);
	    if (pnames.length && (stillswap || (!itemnode && !noclear)))
	        CT.panel.swap(pnames[0], trysidepanel, keystring);
	},
	"simple": function(pnames, keystring, itemnode, panelnode, cbs, condition) {
		CT.panel.load(pnames, null, keystring, itemnode, panelnode, null, null, null, true, cbs, condition);
	},
	"pager": function(getContent, request, limit, colClass, dataClass, ks) {
		var keystring = ks || ("p" + CT.Pager._id),
			content = CT.dom.node(null, null, dataClass, keystring + "panels"),
			sideBar = CT.dom.node(),
			pager = new CT.Pager(function(data) {
				var dnames = data.map(function(d) { return d.label; });
				CT.panel.simple(dnames, keystring, sideBar, content);
				data.forEach(function(d) {
					CT.dom.setContent(CT.dom.id(keystring + "content"
						+ d.label.replace(/ /g, ""),
						true), getContent(d));
				});
				return sideBar;
			}, request, limit, colClass, keystring + "items"),
			n = CT.dom.node([pager.node, content]);
		n.pager = pager;
		return n;
	},
	"alternatebg": function(n, watchforicons, resetonbreak) {
	    n = n || document.getElementById("sbitems");
	    if (watchforicons) {
	        var switcher = 1;
	        for (var i = 0; i < n.childNodes.length; i++) {
	            var c = n.childNodes[i];
	            if (c.className.indexOf("lfloat") == -1 && c.className.indexOf("clearnode") == -1) {
	                if (resetonbreak && c.nodeName == "BR") {
	                    switcher = 1;
	                    continue;
	                }
	                if (switcher == 1)
	                    c.style.background = "#899fb0";
	                switcher *= -1;
	            }
	        }
	    }
	    else {
	        for (var i = 0; i < n.childNodes.length; i+=2)
	            n.childNodes[i].style.background = "#899fb0";
	    }
	}
};<commit_msg>CT.panel.rename(): skip item2 if it isn't there<commit_after>/*
This module contains functions for generating lists of items that,
when clicked, show corresponding content or trigger corresponding
logic. Here are three examples.

### CT.panel.simple(pnames, keystring, itemnode, panelnode, cbs)
This function wraps CT.panel.load(), supporting a subset of load()'s
options (the simple ones). It supports the following args, of which
only the first is required:

    - pnames (string array): short for 'panel names'
    - keystring (string, default: 'sb'): identifier for collections of content/lister nodes
    - itemnode (node, default: CT.dom.id(keystring + "items")): parent node for lister items
    - panelnode (node, default: CT.dom.id(keystring + "panels")): parent node for content panels
    - cbs (function array, optional): callbacks to invoke on lister item click post- panel swap

### CT.panel.pager(getContent, request, limit, colClass, dataClass, ks)
This function generates and returns a node containing a
(CT.Pager-backed) paging lister node and a corresponding content panel.

    - getContent (function): combined with CT.panel.simple() in pager's renderCb
    - request (function): pager's requestCb
    - limit (number, default: 20): pager's limit (chunk size)
    - colClass (string, optional): class of generated pager (list selector) node
    - dataClass (string, optional): class of generated data (content panel) node
    - ks (string, default: "p" + CT.Pager._id): keystring of data and list nodes

### CT.panel.triggerList(data, cb, node)
This function fills the 'node' node with a list of clickable items, each of
which triggers cb(d), where d is the corresponding object in the 'data' array.

    - data (object array): data set used to generate list
      - for d in data: link content equals d.label || d.title
    - cb (function): the callback to invoke when an item is clicked
    - node (node): the list parent node
*/

CT.panel = {
	"lastClicked": {},
	"accordion": function(data, triggerCb, activeClass, content, condition, notick, rmbutton, ricon, noactive) {
		var n = CT.dom.node(null, null, "tabbed", "ac" + data.name);
		n.add = function(tdata, makeFirst, addAlpha) {
			var i, inserted, t = CT.panel.trigger(tdata, triggerCb,
				activeClass, content, condition, rmbutton, ricon, noactive);
			if (makeFirst)
				n.insertBefore(t, n.firstChild);
			else if (addAlpha) {
				for (i = 0; i < n.childNodes.length; i++) {
					if (n.childNodes[i].data.name > t.data.name) {
						n.insertBefore(t, n.childNodes[i]);
						inserted = true;
						break;
					}
				}
				if (!inserted)
					n.appendChild(t);
			} else
				n.appendChild(t);
			return t;
		};
		n._drops = [];
		n.expand = function() {
			n._drops.forEach(function(d) {
				d.expand();
			});
		};
		n.retract = function() {
			n._drops.forEach(function(d) {
				d.retract();
			});
		};
		data.forEach(function(d) {
			if (d.children) {
				var tick = CT.dom.node(">", "div", "inline-block" + (notick && " hidden" || "")),
					t = CT.dom.node(),
					tline = [tick, CT.dom.link(content ? content(d) : d.name, null, null, "inline-block pointer acheader")];
				if (ricon)
					tline.unshift(CT.dom.node(ricon(d), "div", "right rpaddedsmall"));
				CT.dom.setContent(t, tline);
				t.content = CT.panel.accordion(d.children, triggerCb,
					activeClass, content, condition, notick, rmbutton, ricon, noactive);
				n.appendChild(t);
				n._drops.push(new CT.Drop({
					anchor: t,
					parent: n,
					relative: true,
					constrained: false,
					setNodeClick: false,
					content: t.content,
					onShow: function() {
						CT.trans.rotate(tick, { duration: 100, degrees: 90 });
					},
					onHide: function() {
						CT.trans.rotate(tick, { duration: 100, degrees: 0 });
					}
				}));
			} else
				n.add(d);
		});
		return n;
	},
	"trigger": function(data, cb, activeClass, content, condition, rmbutton, ricon, noactive) {
		var label = data.label || data.title || data.name || data.topic || data,
			key = data.key || label,
			n = CT.dom.node(null, "div", "tlitem", key && ("tl" + key.replace(/ /g, "")));
		activeClass = activeClass || "activetab";
		n.data = data;
		data.node = n;
		n.select = function() {
			!noactive && CT.dom.each(n.parentNode, function(c) {
				if (c == n)
					c.classList.add(activeClass);
				else
					c.classList.remove(activeClass);
				c.className = (c == n) ? activeClass : "";
			});
		};
		n.trigger = function() {
			if (condition && !condition()) return;
			n.select();
			cb(data);
		};
		n.remove = function() {
			CT.dom.remove(n);
			n.onRemove && n.onRemove();
		};
		n.nameNode = CT.dom.link(content ? content(data) : label, n.trigger);
		n.rename = function(name) {
			data.label = name;
			n.id = "tl" + name.replace(/ /g, "");
			CT.dom.setContent(n.nameNode, name);
		};
		if (ricon)
			n.appendChild(CT.dom.node(ricon(data), "div", "right rpaddedsmall"));
		if (rmbutton)
			n.appendChild(CT.dom.link("X", n.remove, null, "right"));
		n.appendChild(n.nameNode);
		return n;
	},
	"triggerList": function(data, cb, node, activeClass, content, condition, rmbutton, ricon, noactive) {
		node = node || CT.dom.node();
		node.postAdd = function(d, trigger) {
			var t = CT.panel.trigger(d, cb, activeClass, content, condition, rmbutton, ricon, noactive);
			node.appendChild(t);
			(trigger === true) && t.trigger();
		};
		node.preAdd = function(d, trigger) {
			var t = CT.panel.trigger(d, cb, activeClass, content, condition, rmbutton, ricon, noactive);
			if (node.childNodes.length)
				node.insertBefore(t, node.firstChild);
			else
				node.appendChild(t);
			trigger && t.trigger();
		};
		data.forEach(node.postAdd);
		return node;
	},
	"swap": function(key, trysidepanel, keystring, noitem) {
	    keystring = keystring || "sb";
	    key = key.replace(/ /g, "");
	    var items = CT.dom.className(keystring + "panel");
	    for (var i = 0; i < items.length; i++)
	        items[i].className = keystring + "panel hidden";
	    CT.dom.id(keystring +  "panel" + key, true).className = keystring + "panel";
	    if (trysidepanel) {
	        var n = CT.dom.id(keystring + "panel" + key + "Side");
	        if (n) n.className = keystring+"panel";
	        var n2 = CT.dom.id(keystring + "panel" + key + "Side2");
	        if (n2) n2.className = keystring + "panel";
	    }
	    if (!noitem)
	        CT.panel.select(key, keystring);
	},
	"drill": function(keymap) { // keystring: key
		for (var k in keymap)
			CT.panel.swap(keymap[k], false, k);
	},
	"select": function(key, keystring) {
	    keystring = keystring || "sb";
	    var items = CT.dom.className(keystring + "item");
	    for (var i = 0; i < items.length; i++)
	        items[i].className = items[i].className.replace(" activetab", "");
	    if (key) {
	        CT.dom.id(keystring + "item" + key, true).className += " activetab";
	        CT.panel.lastClicked[keystring] = key;
	    }
	    CT.mobile && CT.mobile.mobileSnap();
	},
	"selectLister": function(newkey, oldkey, newhtml) {
	    if (oldkey)
	        CT.dom.id("ll" + oldkey).className = "pointer";
	    var newnode = CT.dom.id("ll" + newkey);
	    newnode.className = "pointer activetab";
	    if (newhtml)
	        newnode.firstChild.innerHTML = newhtml;
	    CT.mobile && CT.mobile.mobileSnap();
	},
	"add": function(key, trysidepanel, keystring, itemnode, panelnode, nospace, icon, cb, condition) {
	    nospace = nospace || key.replace(/ /g, "");
	    keystring = keystring || "sb";
	    if (!CT.dom.id(keystring + "panel" + nospace)) {
	        var n = CT.dom.node("", "div", keystring + "panel hidden", keystring + "panel" + nospace);
	        n.appendChild(CT.dom.node(key, "div", "bigger blue bold bottompadded"));
	        n.appendChild(CT.dom.node("", "div", "", keystring+"content"+nospace));
	        (panelnode || CT.dom.id(keystring+"panels")).appendChild(n);
	    }
	    var i = CT.dom.id(keystring+"item"+nospace);
	    if (i)
	        i.style.display = "block";
	    else {
	        itemnode = itemnode || CT.dom.id(keystring+"items");
	        var clickfunc = function() {
	        	if (condition && !condition()) return;
	            CT.panel.swap(nospace, trysidepanel, keystring);
	            cb && cb();
	        };
	        if (icon)
	            itemnode.appendChild(CT.dom.node(CT.dom.img(icon,
	                null, clickfunc), "div", "lfloat shiftleft"));
	        itemnode.appendChild(CT.dom.node(CT.dom.link(key, clickfunc),
	            "div", keystring + "item pointer", keystring + "item" + nospace));
	        if (icon)
	            itemnode.appendChild(CT.dom.node("", "div", "clearnode"));
	    }
	},
	"rename": function(oldkey, newkey, keystring, item2keystring) {
		keystring = keystring || "sb";
		var nsold = oldkey.replace(/ /g, ""),
			nsnew = newkey.replace(/ /g, ""),
			item = CT.dom.id(keystring + "item" + nsold),
			panel = CT.dom.id(keystring + "panel" + nsold),
			content = CT.dom.id(keystring + "content" + nsold);
		item.id = keystring + "item" + nsnew;
		panel.id = keystring + "panel" + nsnew;
		if (content) content.id = keystring + "content" + nsnew;
		item.firstChild.innerHTML = panel.firstChild.innerHTML = newkey;
		var item2 = item2keystring && CT.dom.id(item2keystring + "item" + nsold);
		if (item2) {
			item2.id = nsnew;
			item2.firstChild.innerHTML = newkey;
		}
	},
	"remove": function(key, keystring) {
	    var nospace = key.replace(/ /g, "");
	    var p = CT.dom.id(keystring+"panel"+nospace);
	    if (p) p.style.display = "none";
	    var l = CT.dom.id(keystring+"item"+nospace);
	    if (l) l.style.display = "none";
	},
	"load": function(pnames, trysidepanel, keystring, itemnode, panelnode, nospaces, icons, noclear, stillswap, cbs, condition) {
	    keystring = keystring || "sb";
	    if (!noclear)
	        (itemnode || CT.dom.id(keystring+"items")).innerHTML = "";
	    for (var i = 0; i < pnames.length; i++)
	        CT.panel.add(pnames[i], trysidepanel, keystring, itemnode,
	            panelnode, nospaces && nospaces[i] || null,
	            icons && icons[i] || null, cbs && cbs[i] || null, condition);
	    if (pnames.length && (stillswap || (!itemnode && !noclear)))
	        CT.panel.swap(pnames[0], trysidepanel, keystring);
	},
	"simple": function(pnames, keystring, itemnode, panelnode, cbs, condition) {
		CT.panel.load(pnames, null, keystring, itemnode, panelnode, null, null, null, true, cbs, condition);
	},
	"pager": function(getContent, request, limit, colClass, dataClass, ks) {
		var keystring = ks || ("p" + CT.Pager._id),
			content = CT.dom.node(null, null, dataClass, keystring + "panels"),
			sideBar = CT.dom.node(),
			pager = new CT.Pager(function(data) {
				var dnames = data.map(function(d) { return d.label; });
				CT.panel.simple(dnames, keystring, sideBar, content);
				data.forEach(function(d) {
					CT.dom.setContent(CT.dom.id(keystring + "content"
						+ d.label.replace(/ /g, ""),
						true), getContent(d));
				});
				return sideBar;
			}, request, limit, colClass, keystring + "items"),
			n = CT.dom.node([pager.node, content]);
		n.pager = pager;
		return n;
	},
	"alternatebg": function(n, watchforicons, resetonbreak) {
	    n = n || document.getElementById("sbitems");
	    if (watchforicons) {
	        var switcher = 1;
	        for (var i = 0; i < n.childNodes.length; i++) {
	            var c = n.childNodes[i];
	            if (c.className.indexOf("lfloat") == -1 && c.className.indexOf("clearnode") == -1) {
	                if (resetonbreak && c.nodeName == "BR") {
	                    switcher = 1;
	                    continue;
	                }
	                if (switcher == 1)
	                    c.style.background = "#899fb0";
	                switcher *= -1;
	            }
	        }
	    }
	    else {
	        for (var i = 0; i < n.childNodes.length; i+=2)
	            n.childNodes[i].style.background = "#899fb0";
	    }
	}
};<|endoftext|>


<commit_before>/**
 * Project
 *
 * @module      ::  Model
 * @description ::  This model represents project on taskboard.
 */
module.exports = {
    attributes: {
        managerId: { // This is relation to user model
            type:       'integer',
            required:   true
        },
        title: {
            type:       'string',
            required:   true
        },
        description: {
            type:       'text',
            required:   true
        },
        dateStart: {
            type:       'date',
            required:   true
        },
        dateEnd: {
            type:       'date',
            required:   true
        },

        dateStartObject: function() {
            return new Date(this.dateStart);
        },
        dateEndObject: function() {
            return new Date(this.dateEnd);
        },
        dateStartFormatted: function() {
            return this.dateStartObject().format('yyyy-mm-dd');
        },
        dateEndFormatted: function() {
            return this.dateEndObject().format('yyyy-mm-dd');
        }
    }
};
<commit_msg>Simplified formatted date attributes.<commit_after>/**
 * Project
 *
 * @module      ::  Model
 * @description ::  This model represents project on taskboard.
 */
module.exports = {
    attributes: {
        managerId: { // This is relation to user model
            type:       'integer',
            required:   true
        },
        title: {
            type:       'string',
            required:   true
        },
        description: {
            type:       'text',
            required:   true
        },
        dateStart: {
            type:       'date',
            required:   true
        },
        dateEnd: {
            type:       'date',
            required:   true
        },

        dateStartObject: function() {
            return new Date(this.dateStart);
        },
        dateEndObject: function() {
            return new Date(this.dateEnd);
        },
        dateStartFormatted: function() {
            return this.dateStartObject().format('isoDate');
        },
        dateEndFormatted: function() {
            return this.dateEndObject().format('isoDate');
        }
    }
};
<|endoftext|>


<commit_before>/**
 * Student.js
 *
 * @description :: TODO: You might write a short summary of how this model works and what it represents here.
 * @docs        :: http://sailsjs.org/documentation/concepts/models-and-orm/models
 */

module.exports = {

  attributes: {
    login: {
      type:"string",
      required:true,
    },
    firstName:"string",
    lastName:"string",
    mailAddress:"string",
    personalWebsite:"string",
  }
};

<commit_msg>enrichissement model student<commit_after>/**
 * Student.js
 *
 * @description :: TODO: You might write a short summary of how this model works and what it represents here.
 * @docs        :: http://sailsjs.org/documentation/concepts/models-and-orm/models
 */

module.exports = {

  attributes: {
    login: {
      type:"string",
      required:true,
      unique: true,
    },
    firstName:"string",
    lastName:"string",
    mailAddress:"string",
    personalWebsite:"string",
    classLevel: {
      type:'integer',
      enum:[3,4,5]
    }
  }
};

<|endoftext|>