<commit_before>var compress = require('compression')
var cors = require('cors')
var debug = require('debug')('webtorrent-www:web')
var downgrade = require('downgrade')
var express = require('express')
var fs = require('fs')
var highlight = require('highlight.js')
var http = require('http')
var jade = require('jade')
var marked = require('marked')
var multer = require('multer')
var path = require('path')
var semver = require('semver')
var unlimited = require('unlimited')
var url = require('url')

var config = require('../config')

var APP_VERSION = require('webtorrent-desktop/package.json').version
var RELEASE_PATH = 'https://github.com/feross/webtorrent-desktop/releases/download'

unlimited()

var app = express()
var server = http.createServer(app)

jade.filters.markdown = marked

marked.setOptions({
  highlight: function (code, lang) {
    var h = lang
      ? highlight.highlight(lang, code)
      : highlight.highlightAuto(code)
    return '
' + h.value + '
'
  }
})

// Templating
app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'jade')
app.set('x-powered-by', false)
app.engine('jade', jade.renderFile)

// Trust the X-Forwarded-* headers from http-proxy
app.enable('trust proxy')

app.use(compress())

app.use(function (req, res, next) {
  // Force SSL
  if (config.isProd && req.protocol !== 'https') {
    return res.redirect('https://' + (req.hostname || 'webtorrent.io') + req.url)
  }

  // Redirect www to non-www
  if (config.isProd && req.hostname === 'www.webtorrent.io') {
    return res.redirect('https://webtorrent.io' + req.url)
  }

  // Use HTTP Strict Transport Security
  // Lasts 1 year, incl. subdomains, allow browser preload list
  if (config.isProd) {
    res.header(
      'Strict-Transport-Security',
      'max-age=31536000; includeSubDomains; preload'
    )
  }

  // Add cross-domain header for fonts, required by spec, Firefox, and IE.
  var extname = path.extname(url.parse(req.url).pathname)
  if (['.eot', '.ttf', '.otf', '.woff'].indexOf(extname) >= 0) {
    res.header('Access-Control-Allow-Origin', '*')
  }

  // Prevents IE and Chrome from MIME-sniffing a response. Reduces exposure to
  // drive-by download attacks on sites serving user uploaded content.
  res.header('X-Content-Type-Options', 'nosniff')

  // Prevent rendering of site within a frame.
  res.header('X-Frame-Options', 'DENY')

  // Enable the XSS filter built into most recent web browsers. It's usually
  // enabled by default anyway, so role of this headers is to re-enable for this
  // particular website if it was disabled by the user.
  res.header('X-XSS-Protection', '1; mode=block')

  // Force IE to use latest rendering engine or Chrome Frame
  res.header('X-UA-Compatible', 'IE=Edge,chrome=1')

  next()
})

/**
 * Enable CORS preflight, and cache it for 1 hour. This is necessary to support
 * requests from another domain with the "Range" HTTP header.
 */
app.options('/torrents/*', cors({ maxAge: 60 * 60 }))

app.get('/torrents/*', cors(), express.static(path.join(__dirname, '../static')))

app.use(express.static(path.join(__dirname, '../static')))

app.get('/', function (req, res) {
  res.render('home', { rawTitle: 'WebTorrent - Streaming browser torrent client' })
})

app.get('/desktop', function (req, res) {
  res.render('desktop', {
    cls: 'desktop',
    rawTitle: 'WebTorrent Desktop',
    version: APP_VERSION
  })
})

app.get('/intro', function (req, res) {
  res.render('intro', { rawTitle: 'WebTorrent Tutorial - Get Started' })
})

app.get('/docs', function (req, res) {
  res.render('docs', { rawTitle: 'WebTorrent API Documentation' })
})

app.get('/faq', function (req, res) {
  res.render('faq', { rawTitle: 'WebTorrent FAQ' })
})

app.get('/create', function (req, res) {
  res.render('create', { title: 'Create a .torrent file' })
})

app.get('/logs', function (req, res) {
  res.redirect(301, 'https://botbot.me/freenode/webtorrent/')
})

var crashReportsPath = path.join(__dirname, '..', 'crash-reports')
var upload = multer({ dest: crashReportsPath }).single('upload_file_minidump')

app.post('/desktop/crash-report', upload, function (req, res) {
  req.body.filename = req.file.filename
  var crashLog = JSON.stringify(req.body, undefined, 2)

  fs.writeFile(req.file.path + '.json', crashLog, function (err) {
    if (err) return console.error('Error saving crash report: ' + err.message)
    console.log('Saved crash report:\n\t' + crashLog)
  })

  res.end()
})

app.get('/desktop/announcement', function (req, res) {
  res.status(204).end()
})

// Deprecated: WebTorrent Desktop v0.0.0 - 0.2.0 use this update URL
app.get('/app/update/?*', function (req, res) {
  res.redirect(301, req.url.replace('/app/', '/desktop/'))
})

// WebTorrent.app OS X auto-update endpoint
app.get('/desktop/update', function (req, res) {
  var version = req.query.version
  logUpdateCheck({
    date: (new Date()).toString(),
    platform: req.query.platform,
    version: version,
    ip: req.ip
  })
  if (semver.lt(version, APP_VERSION)) {
    // Update is required. Send update JSON.
    // Response format docs: https://github.com/Squirrel/Squirrel.Mac#update-json-format
    res.status(200).send({
      name: 'WebTorrent v' + APP_VERSION,
      url: `${RELEASE_PATH}/v${APP_VERSION}/WebTorrent-v${APP_VERSION}-darwin.zip`,
      version: APP_VERSION
    })
  } else {
    // No update required. User is on latest app version.
    res.status(204).end()
  }
})

// WebTorrent.app Windows auto-update endpoint
app.get('/desktop/update/*', function (req, res) {
  var pathname = url.parse(req.url).pathname
  var file = pathname.replace(/^\/desktop\/update\//i, '')
  var fileVersion
  if (file === 'RELEASES') {
    fileVersion = APP_VERSION
    logUpdateCheck({
      date: (new Date()).toString(),
      platform: req.query.platform,
      version: req.query.version,
      ip: req.ip
    })
  } else {
    var match = /-(\d+\.\d+\.\d+)-/.exec(file)
    fileVersion = match && match[1]
  }
  if (!fileVersion) {
    return res.status(404).end()
  }
  var redirectURL = `${RELEASE_PATH}/v${fileVersion}/${file}`
  res.redirect(302, redirectURL)
})

function logUpdateCheck (log) {
  console.log('UPDATE CHECK: ' + JSON.stringify(log))
}

app.get('*', function (req, res) {
  res.status(404).render('error', {
    title: '404 Not Found',
    message: '404 Not Found'
  })
})

// error handling middleware
app.use(function (err, req, res, next) {
  console.error(err.stack)
  res.status(500).render('error', {
    title: '500 Server Error',
    message: '500 Server Error'
  })
})

server.listen(config.ports.web, function () {
  debug('listening on port ' + server.address().port)
  downgrade()
  process.send('ready')
})
<commit_msg>fix 500 error on invalid semver version<commit_after>var compress = require('compression')
var cors = require('cors')
var debug = require('debug')('webtorrent-www:web')
var downgrade = require('downgrade')
var express = require('express')
var fs = require('fs')
var highlight = require('highlight.js')
var http = require('http')
var jade = require('jade')
var marked = require('marked')
var multer = require('multer')
var path = require('path')
var semver = require('semver')
var unlimited = require('unlimited')
var url = require('url')

var config = require('../config')

var APP_VERSION = require('webtorrent-desktop/package.json').version
var RELEASE_PATH = 'https://github.com/feross/webtorrent-desktop/releases/download'

unlimited()

var app = express()
var server = http.createServer(app)

jade.filters.markdown = marked

marked.setOptions({
  highlight: function (code, lang) {
    var h = lang
      ? highlight.highlight(lang, code)
      : highlight.highlightAuto(code)
    return '
' + h.value + '
'
  }
})

// Templating
app.set('views', path.join(__dirname, 'views'))
app.set('view engine', 'jade')
app.set('x-powered-by', false)
app.engine('jade', jade.renderFile)

// Trust the X-Forwarded-* headers from http-proxy
app.enable('trust proxy')

app.use(compress())

app.use(function (req, res, next) {
  // Force SSL
  if (config.isProd && req.protocol !== 'https') {
    return res.redirect('https://' + (req.hostname || 'webtorrent.io') + req.url)
  }

  // Redirect www to non-www
  if (config.isProd && req.hostname === 'www.webtorrent.io') {
    return res.redirect('https://webtorrent.io' + req.url)
  }

  // Use HTTP Strict Transport Security
  // Lasts 1 year, incl. subdomains, allow browser preload list
  if (config.isProd) {
    res.header(
      'Strict-Transport-Security',
      'max-age=31536000; includeSubDomains; preload'
    )
  }

  // Add cross-domain header for fonts, required by spec, Firefox, and IE.
  var extname = path.extname(url.parse(req.url).pathname)
  if (['.eot', '.ttf', '.otf', '.woff'].indexOf(extname) >= 0) {
    res.header('Access-Control-Allow-Origin', '*')
  }

  // Prevents IE and Chrome from MIME-sniffing a response. Reduces exposure to
  // drive-by download attacks on sites serving user uploaded content.
  res.header('X-Content-Type-Options', 'nosniff')

  // Prevent rendering of site within a frame.
  res.header('X-Frame-Options', 'DENY')

  // Enable the XSS filter built into most recent web browsers. It's usually
  // enabled by default anyway, so role of this headers is to re-enable for this
  // particular website if it was disabled by the user.
  res.header('X-XSS-Protection', '1; mode=block')

  // Force IE to use latest rendering engine or Chrome Frame
  res.header('X-UA-Compatible', 'IE=Edge,chrome=1')

  next()
})

/**
 * Enable CORS preflight, and cache it for 1 hour. This is necessary to support
 * requests from another domain with the "Range" HTTP header.
 */
app.options('/torrents/*', cors({ maxAge: 60 * 60 }))

app.get('/torrents/*', cors(), express.static(path.join(__dirname, '../static')))

app.use(express.static(path.join(__dirname, '../static')))

app.get('/', function (req, res) {
  res.render('home', { rawTitle: 'WebTorrent - Streaming browser torrent client' })
})

app.get('/desktop', function (req, res) {
  res.render('desktop', {
    cls: 'desktop',
    rawTitle: 'WebTorrent Desktop',
    version: APP_VERSION
  })
})

app.get('/intro', function (req, res) {
  res.render('intro', { rawTitle: 'WebTorrent Tutorial - Get Started' })
})

app.get('/docs', function (req, res) {
  res.render('docs', { rawTitle: 'WebTorrent API Documentation' })
})

app.get('/faq', function (req, res) {
  res.render('faq', { rawTitle: 'WebTorrent FAQ' })
})

app.get('/create', function (req, res) {
  res.render('create', { title: 'Create a .torrent file' })
})

app.get('/logs', function (req, res) {
  res.redirect(301, 'https://botbot.me/freenode/webtorrent/')
})

var crashReportsPath = path.join(__dirname, '..', 'crash-reports')
var upload = multer({ dest: crashReportsPath }).single('upload_file_minidump')

app.post('/desktop/crash-report', upload, function (req, res) {
  req.body.filename = req.file.filename
  var crashLog = JSON.stringify(req.body, undefined, 2)

  fs.writeFile(req.file.path + '.json', crashLog, function (err) {
    if (err) return console.error('Error saving crash report: ' + err.message)
    console.log('Saved crash report:\n\t' + crashLog)
  })

  res.end()
})

app.get('/desktop/announcement', function (req, res) {
  res.status(204).end()
})

// Deprecated: WebTorrent Desktop v0.0.0 - 0.2.0 use this update URL
app.get('/app/update/?*', function (req, res) {
  res.redirect(301, req.url.replace('/app/', '/desktop/'))
})

// WebTorrent.app OS X auto-update endpoint
app.get('/desktop/update', function (req, res) {
  var version = req.query.version
  logUpdateCheck({
    date: (new Date()).toString(),
    platform: req.query.platform,
    version: version,
    ip: req.ip
  })
  if (!semver.valid(version) || semver.lt(version, APP_VERSION)) {
    // Update is required. Send update JSON.
    // Response format docs: https://github.com/Squirrel/Squirrel.Mac#update-json-format
    res.status(200).send({
      name: 'WebTorrent v' + APP_VERSION,
      url: `${RELEASE_PATH}/v${APP_VERSION}/WebTorrent-v${APP_VERSION}-darwin.zip`,
      version: APP_VERSION
    })
  } else {
    // No update required. User is on latest app version.
    res.status(204).end()
  }
})

// WebTorrent.app Windows auto-update endpoint
app.get('/desktop/update/*', function (req, res) {
  var pathname = url.parse(req.url).pathname
  var file = pathname.replace(/^\/desktop\/update\//i, '')
  var fileVersion
  if (file === 'RELEASES') {
    fileVersion = APP_VERSION
    logUpdateCheck({
      date: (new Date()).toString(),
      platform: req.query.platform,
      version: req.query.version,
      ip: req.ip
    })
  } else {
    var match = /-(\d+\.\d+\.\d+)-/.exec(file)
    fileVersion = match && match[1]
  }
  if (!fileVersion) {
    return res.status(404).end()
  }
  var redirectURL = `${RELEASE_PATH}/v${fileVersion}/${file}`
  res.redirect(302, redirectURL)
})

function logUpdateCheck (log) {
  console.log('UPDATE CHECK: ' + JSON.stringify(log))
}

app.get('*', function (req, res) {
  res.status(404).render('error', {
    title: '404 Not Found',
    message: '404 Not Found'
  })
})

// error handling middleware
app.use(function (err, req, res, next) {
  console.error(err.stack)
  res.status(500).render('error', {
    title: '500 Server Error',
    message: '500 Server Error'
  })
})

server.listen(config.ports.web, function () {
  debug('listening on port ' + server.address().port)
  downgrade()
  process.send('ready')
})
<|endoftext|>


<commit_before>var _ = require('underscore');
var PgErrorHandler = require('../postgresql/error_handler');

module.exports = function errorMiddleware() {
    return function error(err, req, res, next) {
        if (isTimeoutError(err)) {
            pgErrorHandler = createTimeoutError();
        } else {
            pgErrorHandler = createPgError(err);
        }

        var msg = pgErrorHandler.getResponse();

        if (global.settings.environment === 'development') {
            msg.stack = err.stack;
        }

        if (global.settings.environment !== 'test') {
            // TODO: email this Exception report
            console.error("EXCEPTION REPORT: " + err.stack);
        }

        // Force inline content disposition
        res.header("Content-Disposition", 'inline');

        if (req && req.profiler) {
            req.profiler.done('finish');
            res.header('X-SQLAPI-Profiler', req.profiler.toJSONString());
        }

        setErrorHeader(msg, pgErrorHandler.http_status, res);

        res.header('Content-Type', 'application/json; charset=utf-8');
        res.status(getStatusError(pgErrorHandler, req));
        if (req.query && req.query.callback) {
            res.jsonp(msg);
        } else {
            res.json(msg);
        }

        if (req && req.profiler) {
            res.req.profiler.sendStats();
        }

        next();
    };
};

function getStatusError(pgErrorHandler, req) {

    var statusError = pgErrorHandler.http_status;

    // JSONP has to return 200 status error
    if (req && req.query && req.query.callback) {
        statusError = 200;
    }

    return statusError;
}

function setErrorHeader(err, statusCode, res) {
    let errorsLog = Object.assign({}, err);

    errorsLog.statusCode = statusCode || 200;
    errorsLog.message = errorsLog.error[0];
    delete errorsLog.error;

    res.set('X-SQLAPI-Errors', stringifyForLogs(errorsLog));
}

/**
 * Remove problematic nested characters
 * from object for logs RegEx
 *
 * @param {Object} object
 */
function stringifyForLogs(object) {
    Object.keys(object).map(key => {
        if (typeof object[key] === 'string') {
            object[key] = object[key].replace(/[^a-zA-Z0-9]/g, ' ');
        } else if (typeof object[key] === 'object') {
            stringifyForLogs(object[key]);
        } else if (object[key] instanceof Array) {
            for (let element of object[key]) {
                stringifyForLogs(element);
            }
        }
    });

    return JSON.stringify(object);
}

function isTimeoutError(err) {
    return err.message && (
        err.message.indexOf('statement timeout') > -1 ||
        err.message.indexOf('RuntimeError: Execution of function interrupted by signal') > -1
    );
}

function createTimeoutError() {
    return new PgErrorHandler(
        'You are over platform\'s limits. Please contact us to know more details',
        429,
        'limit',
        'datasource'
    );
}

function createPgError(err) {
    return new PgErrorHandler(
        err.message,
        PgErrorHandler.getStatus(err),
        err.context,
        err.detail,
        err.hint,
        PgErrorHandler.getName(err)
    );
}
<commit_msg>extracting error type check<commit_after>var _ = require('underscore');
var PgErrorHandler = require('../postgresql/error_handler');

module.exports = function errorMiddleware() {
    return function error(err, req, res, next) {
        let pgErrorHandler = getErrorHandler(err);

        var msg = pgErrorHandler.getResponse();

        if (global.settings.environment === 'development') {
            msg.stack = err.stack;
        }

        if (global.settings.environment !== 'test') {
            // TODO: email this Exception report
            console.error("EXCEPTION REPORT: " + err.stack);
        }

        // Force inline content disposition
        res.header("Content-Disposition", 'inline');

        if (req && req.profiler) {
            req.profiler.done('finish');
            res.header('X-SQLAPI-Profiler', req.profiler.toJSONString());
        }

        setErrorHeader(msg, pgErrorHandler.http_status, res);

        res.header('Content-Type', 'application/json; charset=utf-8');
        res.status(getStatusError(pgErrorHandler, req));
        if (req.query && req.query.callback) {
            res.jsonp(msg);
        } else {
            res.json(msg);
        }

        if (req && req.profiler) {
            res.req.profiler.sendStats();
        }

        next();
    };
};

function getStatusError(pgErrorHandler, req) {

    var statusError = pgErrorHandler.http_status;

    // JSONP has to return 200 status error
    if (req && req.query && req.query.callback) {
        statusError = 200;
    }

    return statusError;
}

function setErrorHeader(err, statusCode, res) {
    let errorsLog = Object.assign({}, err);

    errorsLog.statusCode = statusCode || 200;
    errorsLog.message = errorsLog.error[0];
    delete errorsLog.error;

    res.set('X-SQLAPI-Errors', stringifyForLogs(errorsLog));
}

/**
 * Remove problematic nested characters
 * from object for logs RegEx
 *
 * @param {Object} object
 */
function stringifyForLogs(object) {
    Object.keys(object).map(key => {
        if (typeof object[key] === 'string') {
            object[key] = object[key].replace(/[^a-zA-Z0-9]/g, ' ');
        } else if (typeof object[key] === 'object') {
            stringifyForLogs(object[key]);
        } else if (object[key] instanceof Array) {
            for (let element of object[key]) {
                stringifyForLogs(element);
            }
        }
    });

    return JSON.stringify(object);
}

function getErrorHandler (err) {
    if (isTimeoutError(err)) {
        return createTimeoutError();
    } else {
        return createPgError(err);
    }
}

function isTimeoutError(err) {
    return err.message && (
        err.message.indexOf('statement timeout') > -1 ||
        err.message.indexOf('RuntimeError: Execution of function interrupted by signal') > -1
    );
}

function createTimeoutError() {
    return new PgErrorHandler(
        'You are over platform\'s limits. Please contact us to know more details',
        429,
        'limit',
        'datasource'
    );
}

function createPgError(err) {
    return new PgErrorHandler(
        err.message,
        PgErrorHandler.getStatus(err),
        err.context,
        err.detail,
        err.hint,
        PgErrorHandler.getName(err)
    );
}
<|endoftext|>


<commit_before>import {DocumentAccessRightsDialog} from "../access_rights"
import {addAlert, FileDialog, NewFolderDialog} from "../../common"

export const bulkMenuModel = () => ({
    content: [
        {
            title: gettext('Move selected'),
            tooltip: gettext('Move the documents that have been selected.'),
            action: overview => {
                const ids = overview.getSelected()
                const docs = ids.map(id => overview.documentList.find(doc => doc.id === id))
                if (docs.length) {
                    const dialog = new FileDialog({
                        title: docs.length > 1 ? gettext('Move documents') : gettext('Move document'),
                        movingFiles: docs,
                        allFiles: overview.documentList,
                        moveUrl: '/api/document/move/',
                        successMessage: gettext('Document has been moved'),
                        errorMessage: gettext('Could not move document'),
                        succcessCallback: (file, path) => {
                            file.path = path
                            overview.initTable()
                        }
                    })
                    dialog.init()
                }
            },
            disabled: overview => !overview.getSelected().length || overview.app.isOffline(),
            order: 1
        },
        {
            title: gettext('Share selected'),
            tooltip: gettext('Share the documents that have been selected.'),
            action: overview => {
                const ids = overview.getSelected()
                const ownIds = ids.filter(id => overview.documentList.find(doc => doc.id === id).is_owner)
                if (ownIds.length !== ids.length) {
                    addAlert('error', gettext('You cannot share documents of other users'))
                }
                if (ownIds.length) {
                    const dialog = new DocumentAccessRightsDialog(
                        ids,
                        overview.teamMembers,
                        memberDetails => overview.teamMembers.push(memberDetails)
                    )
                    dialog.init()
                }
            },
            disabled: overview => !overview.getSelected().length || overview.app.isOffline(),
            order: 1
        },
        {
            title: gettext('Copy selected'),
            tooltip: gettext('Copy the documents that have been selected.'),
            action: overview => {
                const ids = overview.getSelected()
                if (ids.length) {
                    overview.mod.actions.copyFiles(ids)
                }
            },
            disabled: overview => !overview.getSelected().length || overview.app.isOffline(),
            order: 2
        },
        {
            title: gettext('Export selected as Epub'),
            tooltip: gettext('Export the documents that have been selected as Epub files.'),
            action: overview => {
                const ids = overview.getSelected()
                if (ids.length) {
                    overview.mod.actions.downloadEpubFiles(ids)
                }
            },
            disabled: overview => !overview.getSelected().length || overview.app.isOffline(),
            order: 3
        },
        {
            title: gettext('Export selected as HTML'),
            tooltip: gettext('Export the documents that have been selected as HTML files.'),
            action: overview => {
                const ids = overview.getSelected()
                if (ids.length) {
                    overview.mod.actions.downloadHtmlFiles(ids)
                }
            },
            disabled: overview => !overview.getSelected().length || overview.app.isOffline(),
            order: 4
        },
        {
            title: gettext('Export selected as LaTeX'),
            tooltip: gettext('Export the documents that have been selected as LaTeX files.'),
            action: overview => {
                const ids = overview.getSelected()
                if (ids.length) {
                    overview.mod.actions.downloadLatexFiles(ids)
                }
            },
            disabled: overview => !overview.getSelected().length || overview.app.isOffline(),
            order: 5
        },
        {
            title: gettext('Export selected as JATS'),
            tooltip: gettext('Export the documents that have been selected as JATS files.'),
            action: overview => {
                const ids = overview.getSelected()
                if (ids.length) {
                    overview.mod.actions.downloadJATSFiles(ids)
                }
            },
            disabled: overview => !overview.getSelected().length || overview.app.isOffline(),
            order: 6
        },
        {
            title: gettext('Export selected as FIDUS'),
            tooltip: gettext('Export the documents that have been selected as FIDUS files.'),
            action: overview => {
                const ids = overview.getSelected()
                if (ids.length) {
                    overview.mod.actions.downloadNativeFiles(ids)
                }
            },
            disabled: overview => !overview.getSelected().length || overview.app.isOffline(),
            order: 7
        },
        {
            title: gettext('Delete selected'),
            tooltip: gettext('Delete the documents that have been selected.'),
            action: overview => {
                const ids = overview.getSelected()
                const ownIds = ids.filter(id => overview.documentList.find(doc => doc.id === id).is_owner)
                if (ownIds.length !== ids.length) {
                    addAlert('error', gettext('You cannot delete documents of other users'))
                }
                if (ownIds.length) {
                    overview.mod.actions.deleteDocumentDialog(ownIds)
                }
            },
            disabled: overview => !overview.getSelected().length || overview.app.isOffline(),
            order: 8
        }
    ]
})

let currentlySearching = false

export const menuModel = () => ({
    content: [
        {
            type: 'text',
            id: 'new_document',
            title: gettext('Create new document'),
            action: overview => overview.goToNewDocument('new'),
            order: 1
        },
        {
            type: 'text',
            title: gettext('Create new folder'),
            action: overview => {
                const dialog = new NewFolderDialog(folderName => {
                    overview.path = overview.path + folderName + '/'
                    window.history.pushState({}, "", '/documents' + overview.path)
                    overview.initTable()
                })
                dialog.open()
            },
            order: 2
        },
        {
            type: 'text',
            title: gettext('Upload Fidus document'),
            action: overview => overview.mod.actions.importFidus(),
            order: 3
        },
        {
            type: 'search',
            icon: 'search',
            title: gettext('Search documents'),
            input: (overview, text) => {
                if (text.length && !currentlySearching) {
                    overview.initTable(true)
                    currentlySearching = true
                } else if (!text.length && currentlySearching) {
                    overview.initTable(false)
                    currentlySearching = false
                }
                overview.table.search(text)
            },
            order: 4
        }
    ]
})
<commit_msg>table search init<commit_after>import {DocumentAccessRightsDialog} from "../access_rights"
import {addAlert, FileDialog, NewFolderDialog} from "../../common"

export const bulkMenuModel = () => ({
    content: [
        {
            title: gettext('Move selected'),
            tooltip: gettext('Move the documents that have been selected.'),
            action: overview => {
                const ids = overview.getSelected()
                const docs = ids.map(id => overview.documentList.find(doc => doc.id === id))
                if (docs.length) {
                    const dialog = new FileDialog({
                        title: docs.length > 1 ? gettext('Move documents') : gettext('Move document'),
                        movingFiles: docs,
                        allFiles: overview.documentList,
                        moveUrl: '/api/document/move/',
                        successMessage: gettext('Document has been moved'),
                        errorMessage: gettext('Could not move document'),
                        succcessCallback: (file, path) => {
                            file.path = path
                            overview.initTable()
                        }
                    })
                    dialog.init()
                }
            },
            disabled: overview => !overview.getSelected().length || overview.app.isOffline(),
            order: 1
        },
        {
            title: gettext('Share selected'),
            tooltip: gettext('Share the documents that have been selected.'),
            action: overview => {
                const ids = overview.getSelected()
                const ownIds = ids.filter(id => overview.documentList.find(doc => doc.id === id).is_owner)
                if (ownIds.length !== ids.length) {
                    addAlert('error', gettext('You cannot share documents of other users'))
                }
                if (ownIds.length) {
                    const dialog = new DocumentAccessRightsDialog(
                        ids,
                        overview.teamMembers,
                        memberDetails => overview.teamMembers.push(memberDetails)
                    )
                    dialog.init()
                }
            },
            disabled: overview => !overview.getSelected().length || overview.app.isOffline(),
            order: 1
        },
        {
            title: gettext('Copy selected'),
            tooltip: gettext('Copy the documents that have been selected.'),
            action: overview => {
                const ids = overview.getSelected()
                if (ids.length) {
                    overview.mod.actions.copyFiles(ids)
                }
            },
            disabled: overview => !overview.getSelected().length || overview.app.isOffline(),
            order: 2
        },
        {
            title: gettext('Export selected as Epub'),
            tooltip: gettext('Export the documents that have been selected as Epub files.'),
            action: overview => {
                const ids = overview.getSelected()
                if (ids.length) {
                    overview.mod.actions.downloadEpubFiles(ids)
                }
            },
            disabled: overview => !overview.getSelected().length || overview.app.isOffline(),
            order: 3
        },
        {
            title: gettext('Export selected as HTML'),
            tooltip: gettext('Export the documents that have been selected as HTML files.'),
            action: overview => {
                const ids = overview.getSelected()
                if (ids.length) {
                    overview.mod.actions.downloadHtmlFiles(ids)
                }
            },
            disabled: overview => !overview.getSelected().length || overview.app.isOffline(),
            order: 4
        },
        {
            title: gettext('Export selected as LaTeX'),
            tooltip: gettext('Export the documents that have been selected as LaTeX files.'),
            action: overview => {
                const ids = overview.getSelected()
                if (ids.length) {
                    overview.mod.actions.downloadLatexFiles(ids)
                }
            },
            disabled: overview => !overview.getSelected().length || overview.app.isOffline(),
            order: 5
        },
        {
            title: gettext('Export selected as JATS'),
            tooltip: gettext('Export the documents that have been selected as JATS files.'),
            action: overview => {
                const ids = overview.getSelected()
                if (ids.length) {
                    overview.mod.actions.downloadJATSFiles(ids)
                }
            },
            disabled: overview => !overview.getSelected().length || overview.app.isOffline(),
            order: 6
        },
        {
            title: gettext('Export selected as FIDUS'),
            tooltip: gettext('Export the documents that have been selected as FIDUS files.'),
            action: overview => {
                const ids = overview.getSelected()
                if (ids.length) {
                    overview.mod.actions.downloadNativeFiles(ids)
                }
            },
            disabled: overview => !overview.getSelected().length || overview.app.isOffline(),
            order: 7
        },
        {
            title: gettext('Delete selected'),
            tooltip: gettext('Delete the documents that have been selected.'),
            action: overview => {
                const ids = overview.getSelected()
                const ownIds = ids.filter(id => overview.documentList.find(doc => doc.id === id).is_owner)
                if (ownIds.length !== ids.length) {
                    addAlert('error', gettext('You cannot delete documents of other users'))
                }
                if (ownIds.length) {
                    overview.mod.actions.deleteDocumentDialog(ownIds)
                }
            },
            disabled: overview => !overview.getSelected().length || overview.app.isOffline(),
            order: 8
        }
    ]
})

let currentlySearching = false

export const menuModel = () => ({
    content: [
        {
            type: 'text',
            id: 'new_document',
            title: gettext('Create new document'),
            action: overview => overview.goToNewDocument('new'),
            order: 1
        },
        {
            type: 'text',
            title: gettext('Create new folder'),
            action: overview => {
                const dialog = new NewFolderDialog(folderName => {
                    overview.path = overview.path + folderName + '/'
                    window.history.pushState({}, "", '/documents' + overview.path)
                    overview.initTable()
                })
                dialog.open()
            },
            order: 2
        },
        {
            type: 'text',
            title: gettext('Upload Fidus document'),
            action: overview => overview.mod.actions.importFidus(),
            order: 3
        },
        {
            type: 'search',
            icon: 'search',
            title: gettext('Search documents'),
            input: (overview, text) => {
                if (text.length && !currentlySearching) {
                    overview.initTable(true)
                    currentlySearching = true
                    overview.table.on(
                        'datatable.init',
                        () => overview.table.search(text)
                    )
                } else if (!text.length && currentlySearching) {
                    overview.initTable(false)
                    currentlySearching = false
                } else if (text.length) {
                    overview.table.search(text)
                }
            },
            order: 4
        }
    ]
})
<|endoftext|>


<commit_before>/**
 * Copyright 2014 IBM Corp. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

var extend         = require('extend');
var requestFactory = require('../../lib/requestwrapper');
var isStream       = require('isstream');
var pick           = require('object.pick');
var omit           = require('object.omit');

function DocumentConversion(options) {
  // Default URL
  var serviceDefaults = {
    url: 'https://gateway.watsonplatform.net/document-conversion-experimental/api'
  };

  // Replace default options with user provided
  this._options = extend(serviceDefaults, options);
}

DocumentConversion.prototype.conversion_target = {
  ANSWER_UNITS: 'ANSWER_UNITS',
  NORMALIZED_HTML: 'NORMALIZED_HTML',
  NORMALIZED_TEXT: 'NORMALIZED_TEXT'
};

function fixupContentType(params) {
  if (params.file && params.file.path && /.html?$/.test(params.file.path)) {
    params.file = {
      value: params.file,
      options: {
        contentType: 'text/html; charset=utf-8'
      }
    };
  }
}

/**
 * One-off convert an attached document OR convert a previously uploaded document by ID
 *
 * To convert a previously uploaded document, set params.document_id
 *
 * @param  {Object} params.conversion_target Must be set to one of ['ANSWER_UNITS', 'NORMALIZED_HTML', 'NORMALIZED_TEXT']
 * @param  {Object} [params.document_id] Id of previously uploaded document to convert
 * @param  {ReadableStream} [params.file] The document file to convert.
 */
DocumentConversion.prototype.convert = function(params, callback) {
  params = params || {};
  if (!params.conversion_target || !DocumentConversion.prototype.conversion_target[params.conversion_target]) {
    var keys = Object.keys(DocumentConversion.prototype.conversion_target);
    var values = keys.map(function(v) { return DocumentConversion.prototype.conversion_target[v]; });

    callback(new Error('Missing required parameters: conversion_target. Possible values are: ' + values.join(', ')));
    return;
  }

  if (!params.file && !params.document_id) {
    callback(new Error('Missing required parameters: either params.file or params.document_id must be specified'));
    return;
  }

  if (params.file && !isStream(params.file) && !params.file.value) {
    callback(new Error('Missing required parameters: file is not a standard Node.js Stream'));
    return;
  }

  var parameters = {
    options: {
      method: 'POST',
      url: '/v1/convert_document',
      json: true
    },
    defaultOptions: this._options
  };

  // send the parameters in the body or as formData depending on the request
  if (params.file) {
    fixupContentType(params);
    parameters.options.formData = {
      file: params.file,
      config: {
        value: JSON.stringify(omit(params,['file'])),
        options: {
          contentType: 'application/json; charset=utf-8'
        }
      }
    };
  } else {
    parameters.options.body = params;
  }

  return requestFactory(parameters, callback);
};

// give a clear error message for the deprecated methods
['getOutput', 'getOutputs', 'getJobLog', 'getJobs', 'getJob', 'createJob', 'getBatchDocument', 'getBatchDocuments',
  'addDocumentToBatch', 'getDocument', 'getDocuments', 'uploadDocument', 'getBatchDocuments', 'updateBatch', 'getBatch', 'createBatch', 'getBatches'].forEach(function(name) {
    DocumentConversion.prototype[name] = function deprecated() {
      throw new Error('The DocumentConversion.' + name + '() method was deprecated and is no longer available, please use convert() instead.');
  }
});

module.exports = DocumentConversion;
<commit_msg>Update v1-experimental.js<commit_after>/**
 * Copyright 2014 IBM Corp. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

var extend         = require('extend');
var requestFactory = require('../../lib/requestwrapper');
var isStream       = require('isstream');
var pick           = require('object.pick');
var omit           = require('object.omit');

function DocumentConversion(options) {
  // Default URL
  var serviceDefaults = {
    url: 'https://gateway.watsonplatform.net/document-conversion-experimental/api'
  };

  // Replace default options with user provided
  this._options = extend(serviceDefaults, options);
}

DocumentConversion.prototype.conversion_target = {
  ANSWER_UNITS: 'ANSWER_UNITS',
  NORMALIZED_HTML: 'NORMALIZED_HTML',
  NORMALIZED_TEXT: 'NORMALIZED_TEXT'
};

function fixupContentType(params) {
  if (params.file && params.file.path && /.html?$/.test(params.file.path)) {
    params.file = {
      value: params.file,
      options: {
        contentType: 'text/html; charset=utf-8'
      }
    };
  }
}

/**
 * One-off convert an attached document OR convert a previously uploaded document by ID
 *
 * To convert a previously uploaded document, set params.document_id
 *
 * @param  {Object} params.conversion_target Must be set to one of ['ANSWER_UNITS', 'NORMALIZED_HTML', 'NORMALIZED_TEXT']
 * @param  {ReadableStream} [params.file] The document file to convert.
 */
DocumentConversion.prototype.convert = function(params, callback) {
  params = params || {};
  if (!params.conversion_target || !DocumentConversion.prototype.conversion_target[params.conversion_target]) {
    var keys = Object.keys(DocumentConversion.prototype.conversion_target);
    var values = keys.map(function(v) { return DocumentConversion.prototype.conversion_target[v]; });

    callback(new Error('Missing required parameters: conversion_target. Possible values are: ' + values.join(', ')));
    return;
  }

  if (!params.file && !params.document_id) {
    callback(new Error('Missing required parameters: either params.file or params.document_id must be specified'));
    return;
  }

  if (params.file && !isStream(params.file) && !params.file.value) {
    callback(new Error('Missing required parameters: file is not a standard Node.js Stream'));
    return;
  }

  var parameters = {
    options: {
      method: 'POST',
      url: '/v1/convert_document',
      json: true
    },
    defaultOptions: this._options
  };

  // send the parameters in the body or as formData depending on the request
  if (params.file) {
    fixupContentType(params);
    parameters.options.formData = {
      file: params.file,
      config: {
        value: JSON.stringify(omit(params,['file'])),
        options: {
          contentType: 'application/json; charset=utf-8'
        }
      }
    };
  } else {
    parameters.options.body = params;
  }

  return requestFactory(parameters, callback);
};

// give a clear error message for the deprecated methods
['getOutput', 'getOutputs', 'getJobLog', 'getJobs', 'getJob', 'createJob', 'getBatchDocument', 'getBatchDocuments',
  'addDocumentToBatch', 'getDocument', 'getDocuments', 'uploadDocument', 'getBatchDocuments', 'updateBatch', 'getBatch', 'createBatch', 'getBatches'].forEach(function(name) {
    DocumentConversion.prototype[name] = function deprecated() {
      throw new Error('The DocumentConversion.' + name + '() method was deprecated and is no longer available, please use convert() instead.');
  }
});

module.exports = DocumentConversion;
<|endoftext|>


<commit_before>// register the directive with your app module
var app = angular.module('ddApp', ['ngAnimate', 'ui.bootstrap', 'rzModule']);

// function referenced by the drop target
app.controller('ddController', ['$scope', '$uibModal', '$log', '$timeout', function($scope, $uibModal, $log, $timeout){

    $scope.height = 4;
    $scope.width = 4;
    $scope.length = 7;
    $scope.range = function(n){
        arr = [];
        for (var i=1; i<=n; i++) {
            arr.push(i);
        }
        return arr;
    }

    $scope.z = 1;
    $scope.numberOfDropTiles = 2;
    $scope.placedDropTiles = 0;

    $scope.tiles = {};

    $scope.tiles["2,3,1"] = {rot: 180, image: 'tiles/tile-5.png', dropTile: false,
                             items: {gaps: 0, obstacles: 0, speedbumps: 0, intersections: 0},
                             scored: {gaps: [], obstacles: [], speedbumps: [], intersections: [], reach: [false]}};
    $scope.tiles["3,3,1"] = {rot: 90, image: 'tiles/tile-5.png', dropTile: false,
                             items: {gaps: 0, obstacles: 0, speedbumps: 0, intersections: 1},
                             scored: {gaps: [], obstacles: [], speedbumps: [], intersections: [false]}};
    $scope.tiles["3,2,1"] = {rot: 0, image: 'tiles/tile-5.png', dropTile: false,
                             items: {gaps: 0, obstacles: 1, speedbumps: 0, intersections: 0},
                             scored: {gaps: [], obstacles: [false], speedbumps: [], intersections: []}};
    $scope.tiles["2,2,1"] = {rot: 270, image: 'tiles/tile-5.png', dropTile: false,
                             items: {gaps: 0, obstacles: 0, speedbumps: 0, intersections: 0},
                             scored: {gaps: [], obstacles: [], speedbumps: [], intersections: [], reach: [false,false]}};

    $scope.startedScoring = false;

    $scope.startScoring = function(){
        // Start/stop scoring
        $scope.startedScoring = !$scope.startedScoring;
    }

    $scope.score = 123;
    $scope.showedUp = false;

    $scope.LoPs = [];
    $scope.decrement = function(index){
        if($scope.LoPs[index])
            $scope.LoPs[index]--;
        else
            $scope.LoPs[index] = 0;
        if($scope.LoPs[index] < 0)
            $scope.LoPs[index] = 0;
    }
    $scope.increment = function(index){
        if($scope.LoPs[index])
            $scope.LoPs[index]++;
        else
            $scope.LoPs[index] = 1;
        if($scope.LoPs[index] >= 3)
            $timeout(function(){alert("The team *may* move to next drop tile now.");},20);
    }

    // Verified time by timekeeper
    $scope.minutes = 0;
    $scope.seconds = 0;

    $scope.startedTime = false;
    $scope.time = 0;

    var tick = function() {
        $scope.time += 1000;
        if($scope.startedTime)
            $timeout(tick, 1000);
    }

    $scope.startTime = function(){
        // Start/stop timer
        $scope.startedTime = !$scope.startedTime;
        if($scope.startedTime){
            // Start the timer
            $timeout(tick, $scope.tickInterval);
        }
    }

    $scope.totalNumberOf = function(objects){
        return objects.gaps + objects.speedbumps + objects.obstacles + objects.intersections;
    }

    $scope.doScoring = function(x,y,z){
        var tile = $scope.tiles[x+','+y+','+z];
        // If this is not a created tile
        if(!tile)
            return;

        var total = $scope.totalNumberOf(tile.items);

        // If the run is not started, we can place drop pucks on this tile
        if(!$scope.startedScoring){
            // We can only place drop markers on tiles without scoring elements (rule 3.3.4)
            if(total > 0){
                alert("Place drop markers on tiles without scoring elements (rule 3.3.4)");
            }else{
                if($scope.numberOfDropTiles - $scope.placedDropTiles > 0) {
                    tile.dropTile = !tile.dropTile;
                    if(tile.dropTile)
                        $scope.placedDropTiles++;
                    else
                        $scope.placedDropTiles--;
                }else if(tile.dropTile){
                    tile.dropTile = false;
                    $scope.placedDropTiles--;
                }
            }

        // Match has started!
        }else{
            // Add the number of possible passes for drop tiles
            if(tile.dropTile) {
                total += tile.scored.reach.length;
            }

            if(total > 1){
                // Show modal
                $scope.open(x,y,z);
            }else if(total==1){
                if(tile.items.gaps>0)
                    tile.scored.gaps[0] = !tile.scored.gaps[0];
                else if(tile.items.speedbumps)
                    tile.scored.speedbumps[0] = !tile.scored.speedbumps[0];
                else if(tile.items.obstacles)
                    tile.scored.obstacles[0] = !tile.scored.obstacles[0];
                else if(tile.items.intersections)
                    tile.scored.intersections[0] = !tile.scored.intersections[0];
                else if(tile.dropTile)
                    tile.scored.reach[0] = !tile.scored.reach[0];
            }
        }
    }

    $scope.sliderOptions = {
        floor: 1,
        ceil: $scope.height,
        showSelectionBar: true,
        showTicksValues: true
    };

    $scope.open = function(x,y,z) {
        var modalInstance = $uibModal.open({
            animation: true,
            templateUrl: 'line_judge_modal.html',
            controller: 'ModalInstanceCtrl',
            size: 'sm',
            resolve: {
                tile: function () {
                    return $scope.tiles[x+','+y+','+z];
                }
            }
        });
    };

}]);

// Please note that $uibModalInstance represents a modal window (instance) dependency.
// It is not the same as the $uibModal service used above.

app.controller('ModalInstanceCtrl', function ($scope, $uibModalInstance, tile) {
    $scope.tile = tile;
    $scope.ok = function () {
        $uibModalInstance.close();
    };

    $scope.cancel = function () {
        $uibModalInstance.dismiss('cancel');
    };
});

app.directive('tile', function() {
    return {
        scope: {
            tile: '='
        },
        restrict: 'E',
        templateUrl: 'tile.html',
        link : function($scope, element, attrs){

            $scope.tileStatus = function(tile){
                // If this is a non-existent tile
                if(!tile)
                    return;
                var successfully = 0;
                var possible = 0;

                var count = function(list){
                    for(var i = 0; i < list.length; i++){
                        if(list[i])
                            successfully++;
                        possible++;
                    }
                }
                count(tile.scored.gaps);
                count(tile.scored.speedbumps);
                count(tile.scored.intersections);
                count(tile.scored.obstacles);
                if(tile.dropTile)
                    count(tile.scored.reach);

                if(possible > 0 && successfully == possible)
                    return "done";
                else if(successfully > 0)
                    return "halfdone";
                else if(possible > 0)
                    return "undone";
                else
                    return "";
            }

        }
    };
});

<commit_msg>Change to 0-indexed tiles<commit_after>// register the directive with your app module
var app = angular.module('ddApp', ['ngAnimate', 'ui.bootstrap', 'rzModule']);

// function referenced by the drop target
app.controller('ddController', ['$scope', '$uibModal', '$log', '$timeout', function($scope, $uibModal, $log, $timeout){

    $scope.height = 4;
    $scope.width = 4;
    $scope.length = 7;
    $scope.range = function(n){
        arr = [];
        for (var i=0; i < n; i++) {
            arr.push(i);
        }
        return arr;
    }

    $scope.z = 0;
    $scope.numberOfDropTiles = 2;
    $scope.placedDropTiles = 0;

    $scope.tiles = {};

    $scope.tiles["2,3,0"] = {rot: 180, image: 'tiles/tile-5.png', dropTile: false,
                             items: {gaps: 0, obstacles: 0, speedbumps: 0, intersections: 0},
                             scored: {gaps: [], obstacles: [], speedbumps: [], intersections: [], reach: [false]}};
    $scope.tiles["3,3,0"] = {rot: 90, image: 'tiles/tile-5.png', dropTile: false,
                             items: {gaps: 0, obstacles: 0, speedbumps: 0, intersections: 1},
                             scored: {gaps: [], obstacles: [], speedbumps: [], intersections: [false]}};
    $scope.tiles["3,2,0"] = {rot: 0, image: 'tiles/tile-5.png', dropTile: false,
                             items: {gaps: 0, obstacles: 1, speedbumps: 0, intersections: 0},
                             scored: {gaps: [], obstacles: [false], speedbumps: [], intersections: []}};
    $scope.tiles["2,2,0"] = {rot: 270, image: 'tiles/tile-5.png', dropTile: false,
                             items: {gaps: 0, obstacles: 0, speedbumps: 0, intersections: 0},
                             scored: {gaps: [], obstacles: [], speedbumps: [], intersections: [], reach: [false,false]}};

    $scope.startedScoring = false;

    $scope.startScoring = function(){
        // Start/stop scoring
        $scope.startedScoring = !$scope.startedScoring;
    }

    $scope.score = 123;
    $scope.showedUp = false;

    $scope.LoPs = [];
    $scope.decrement = function(index){
        if($scope.LoPs[index])
            $scope.LoPs[index]--;
        else
            $scope.LoPs[index] = 0;
        if($scope.LoPs[index] < 0)
            $scope.LoPs[index] = 0;
    }
    $scope.increment = function(index){
        if($scope.LoPs[index])
            $scope.LoPs[index]++;
        else
            $scope.LoPs[index] = 1;
        if($scope.LoPs[index] >= 3)
            $timeout(function(){alert("The team *may* move to next drop tile now.");},20);
    }

    // Verified time by timekeeper
    $scope.minutes = 0;
    $scope.seconds = 0;

    $scope.startedTime = false;
    $scope.time = 0;

    var tick = function() {
        $scope.time += 1000;
        if($scope.startedTime)
            $timeout(tick, 1000);
    }

    $scope.startTime = function(){
        // Start/stop timer
        $scope.startedTime = !$scope.startedTime;
        if($scope.startedTime){
            // Start the timer
            $timeout(tick, $scope.tickInterval);
        }
    }

    $scope.totalNumberOf = function(objects){
        return objects.gaps + objects.speedbumps + objects.obstacles + objects.intersections;
    }

    $scope.doScoring = function(x,y,z){
        var tile = $scope.tiles[x+','+y+','+z];
        // If this is not a created tile
        if(!tile)
            return;

        var total = $scope.totalNumberOf(tile.items);

        // If the run is not started, we can place drop pucks on this tile
        if(!$scope.startedScoring){
            // We can only place drop markers on tiles without scoring elements (rule 3.3.4)
            if(total > 0){
                alert("Place drop markers on tiles without scoring elements (rule 3.3.4)");
            }else{
                if($scope.numberOfDropTiles - $scope.placedDropTiles > 0) {
                    tile.dropTile = !tile.dropTile;
                    if(tile.dropTile)
                        $scope.placedDropTiles++;
                    else
                        $scope.placedDropTiles--;
                }else if(tile.dropTile){
                    tile.dropTile = false;
                    $scope.placedDropTiles--;
                }
            }

        // Match has started!
        }else{
            // Add the number of possible passes for drop tiles
            if(tile.dropTile) {
                total += tile.scored.reach.length;
            }

            if(total > 1){
                // Show modal
                $scope.open(x,y,z);
            }else if(total==1){
                if(tile.items.gaps>0)
                    tile.scored.gaps[0] = !tile.scored.gaps[0];
                else if(tile.items.speedbumps)
                    tile.scored.speedbumps[0] = !tile.scored.speedbumps[0];
                else if(tile.items.obstacles)
                    tile.scored.obstacles[0] = !tile.scored.obstacles[0];
                else if(tile.items.intersections)
                    tile.scored.intersections[0] = !tile.scored.intersections[0];
                else if(tile.dropTile)
                    tile.scored.reach[0] = !tile.scored.reach[0];
            }
        }
    }

    $scope.sliderOptions = {
        floor: 0,
        ceil: $scope.height-1,
        showSelectionBar: true,
        showTicksValues: true
    };

    $scope.open = function(x,y,z) {
        var modalInstance = $uibModal.open({
            animation: true,
            templateUrl: 'line_judge_modal.html',
            controller: 'ModalInstanceCtrl',
            size: 'sm',
            resolve: {
                tile: function () {
                    return $scope.tiles[x+','+y+','+z];
                }
            }
        });
    };

}]);

// Please note that $uibModalInstance represents a modal window (instance) dependency.
// It is not the same as the $uibModal service used above.

app.controller('ModalInstanceCtrl', function ($scope, $uibModalInstance, tile) {
    $scope.tile = tile;
    $scope.ok = function () {
        $uibModalInstance.close();
    };

    $scope.cancel = function () {
        $uibModalInstance.dismiss('cancel');
    };
});

app.directive('tile', function() {
    return {
        scope: {
            tile: '='
        },
        restrict: 'E',
        templateUrl: 'tile.html',
        link : function($scope, element, attrs){

            $scope.tileStatus = function(tile){
                // If this is a non-existent tile
                if(!tile)
                    return;
                var successfully = 0;
                var possible = 0;

                var count = function(list){
                    for(var i = 0; i < list.length; i++){
                        if(list[i])
                            successfully++;
                        possible++;
                    }
                }
                count(tile.scored.gaps);
                count(tile.scored.speedbumps);
                count(tile.scored.intersections);
                count(tile.scored.obstacles);
                if(tile.dropTile)
                    count(tile.scored.reach);

                if(possible > 0 && successfully == possible)
                    return "done";
                else if(successfully > 0)
                    return "halfdone";
                else if(possible > 0)
                    return "undone";
                else
                    return "";
            }

        }
    };
});

<|endoftext|>


<commit_before>import cp from 'child_process';
import queue from 'queue';
import ms from 'ms';
import {yarn, plugs} from '../config/paths';

export default {
  install: fn => {
    const spawnQueue = queue({concurrency: 1});
    function yarnFn(args, cb) {
      const env = {
        NODE_ENV: 'production',
        ELECTRON_RUN_AS_NODE: 'true'
      };
      spawnQueue.push(end => {
        const cmd = [process.execPath, yarn].concat(args).join(' ');
        //eslint-disable-next-line no-console
        console.log('Launching yarn:', cmd);

        cp.execFile(
          process.execPath,
          [yarn].concat(args),
          {
            cwd: plugs.base,
            env,
            timeout: ms('5m'),
            maxBuffer: 1024 * 1024
          },
          (err, stdout, stderr) => {
            if (err) {
              cb(stderr);
            } else {
              cb(null);
            }
            end();
            spawnQueue.start();
          }
        );
      });

      spawnQueue.start();
    }

    yarnFn(['install', '--no-emoji', '--no-lockfile', '--cache-folder', plugs.cache], err => {
      if (err) {
        return fn(err);
      }
      fn(null);
    });
  }
};
<commit_msg>change export syntax in app/plugins/install.js<commit_after>import cp from 'child_process';
import queue from 'queue';
import ms from 'ms';
import {yarn, plugs} from '../config/paths';

export const install = fn => {
  const spawnQueue = queue({concurrency: 1});
  function yarnFn(args, cb) {
    const env = {
      NODE_ENV: 'production',
      ELECTRON_RUN_AS_NODE: 'true'
    };
    spawnQueue.push(end => {
      const cmd = [process.execPath, yarn].concat(args).join(' ');
      //eslint-disable-next-line no-console
      console.log('Launching yarn:', cmd);

      cp.execFile(
        process.execPath,
        [yarn].concat(args),
        {
          cwd: plugs.base,
          env,
          timeout: ms('5m'),
          maxBuffer: 1024 * 1024
        },
        (err, stdout, stderr) => {
          if (err) {
            cb(stderr);
          } else {
            cb(null);
          }
          end();
          spawnQueue.start();
        }
      );
    });

    spawnQueue.start();
  }

  yarnFn(['install', '--no-emoji', '--no-lockfile', '--cache-folder', plugs.cache], err => {
    if (err) {
      return fn(err);
    }
    fn(null);
  });
};
<|endoftext|>


<commit_before>/**
 * AJAX Nette Framework plugin for jQuery
 *
 * @copyright Copyright (c) 2009, 2010 Jan Marek
 * @copyright Copyright (c) 2009, 2010 David Grudl
 * @copyright Copyright (c) 2012 Vojtěch Dobeš
 * @license MIT
 */

(function($, undefined) {

if (typeof $ != 'function') return;

var nette = function () {
	var inner = {
		self: this,
		initialized: false,
		contexts: {},
		extensions: {},
		on: {
			init: {},
			load: {},
			before: {},
			start: {},
			success: {},
			complete: {},
			error: {}
		},
		fire: function () {
			var result = true;
			var args = Array.prototype.slice.call(arguments);
			var name = args.shift();
			args.push(inner.self);
			$.each(inner.on[name], function (index, reaction) {
				var temp = reaction.apply(inner.contexts[index], args);
				return result = (temp === undefined || temp);
			});
			return result;
		},
		explicitNoAjax: false,
		requestHandler: function (e) {
			e.stopPropagation();

			// thx to @vrana
			var explicitNoAjax = e.button || e.ctrlKey || e.shiftKey || e.altKey || e.metaKey;

			var $el = $(this), $form, isForm, isSubmit, data = {};
			if ((isForm = $el.is('form')) || (isSubmit = $el.is(':submit'))) {
				if (isSubmit) {
					$form = $el.closest('form');
					data[$el.attr('name')] = $el.val() || '';
				} else if (isForm) {
					$form = $el;
				} else {
					return;
				}

				if (explicitNoAjax && isSubmit) {
					inner.explicitNoAjax = true;
					return;
				} else if (isForm && inner.explicitNoAjax) {
					inner.explicitNoAjax = false;
					return;
				}

				if ($form.get(0).onsubmit && !$form.get(0).onsubmit()) return null;

				var values = $form.serializeArray();
				for (var i = 0; i < values.length; i++) {
					var name = values[i].name;
					if (name in data) {
						var val = data[name];
						if (!(val instanceof Array)) {
							val = [val];
						}
						val.push(values[i].value);
						data[name] = val;
					} else {
						data[name] = values[i].value;
					}
				}
			} else if (explicitNoAjax) return;

			// thx to @vrana
			if (/:|^#/.test($form ? $form.attr('action') : $el.attr('href'))) return;

			if (inner.fire('before', this)) {
				e.preventDefault();
				var xhr = $.ajax({
					url: $form ? $form.attr('action') : this.href,
					data: data,
					type: $form ? $form.attr('method') : 'get'
				}).done(function (payload) {
					inner.fire('success', payload);
				}).fail(function () {
					inner.fire('error');
				}).always(function () {
					inner.fire('complete');
				});
				inner.fire('start', xhr);
			}
		}
	};

	/**
	 * Allows manipulation with extensions.
	 * When called with 1. argument only, it returns extension with given name.
	 * When called with 2. argument equal to false, it removes extension entirely.
	 * When called with 2. argument equal to hash of event callbacks, it adds new extension.
	 *
	 * @param  {string} Name of extension
	 * @param  {bool|object|null} Set of callbacks for any events OR false for removing extension.
	 * @param  {object|null} Context for added extension
	 * @return {$.nette} Provides a fluent interface
	 */
	this.ext = function (name, callbacks, context) {
		if (inner.initialized) throw 'Cannot manipulate nette-ajax extensions after initialization.';

		if (callbacks === undefined) {
			inner.extensions[name];
		} else if (!callbacks) {
			inner.extensions[name] = undefined;
		} else if (inner.extensions[name]) {
			throw 'Cannot override already registered nette-ajax extension.';
		} else {
			inner.extensions[name] = {
				name: name,
				on: callbacks,
				context: context
			};
		}
		return this;
	};

	/**
	 * Initializes the plugin. Forbids any further modifications of extensions.
	 * Fires 'init' event, then 'load' event.
	 * When called with any arguments, it will overried default 'init' extension
	 * with provided callbacks.
	 *
	 * @param  {function|object|null} Callback for 'load' event or entire set of callbacks for any events
	 * @param  {object|null} Context provided for callbacks in first argument
	 * @return {$.nette} Provides a fluent interface
	 */
	this.init = function (load, loadContext) {
		if (inner.initialized) throw 'Cannot initialize nette-ajax twice.';

		if (typeof load == 'function') {
			this.ext('init', null);
			this.ext('init', {
				load: load
			}, loadContext);
		} else if (typeof load == 'object') {
			this.ext('init', null);
			this.ext('init', load, loadContext);
		} else if (load !== undefined) {
			throw 'Argument of init() can be function or function-hash only.';
		}

		inner.initialized = true;

		$.each(inner.extensions, function (index, extension) {
			$.each(['init', 'load', 'before', 'start', 'success', 'complete', 'error'], function (index, reaction) {
				if (extension !== undefined) {
					if (extension.on[reaction] !== undefined) {
						inner.on[reaction][extension.name] = extension.on[reaction];
					}
					inner.contexts[extension.name] = $.extend(extension.context ? extension.context : {}, {
						ext: function (name) {
							var ext = inner.contexts[name];
							if (!ext) throw "Extension '" + extension.name + "' depends on disabled extension '" + name + "'.";
							return ext;
						}
					});
				}
			});
		});

		inner.fire('init');
		this.load();
		return this;
	};

	/**
	 * Fires 'load' event
	 *
	 * @return {$.nette} Provides a fluent interface
	 */
	this.load = function () {
		inner.fire('load', inner.requestHandler);
		return this;
	};
};

$.nette = new ($.extend(nette, $.nette ? $.nette : {}));

// default snippet handler
$.nette.ext('snippets', {
	success: function (payload) {
		if (payload.snippets) {
			for (var i in payload.snippets) {
				this.updateSnippet(i, payload.snippets[i]);
			}
		}
	}
}, {
	updateSnippet: function (id, html) {
		var $el = this.getElement(id);
		// Fix for setting document title in IE
		if ($el.eq(0).tagName == 'TITLE') {
			document.title = html;
		} else {
			this.applySnippet($el, html);
		}
	},
	getElement: function (id) {
		return $('#' + this.escapeSelector(id));
	},
	applySnippet: function ($el, html) {
		$el.html(html);
	},
	escapeSelector: function (selector) {
		// thx to @uestla (https://github.com/uestla)
		return selector.replace(/[\!"#\$%&'\(\)\*\+,\.\/:;<=>\?@\[\\\]\^`\{\|\}~]/g, '\\$&');
	}
});

// support $this->redirect()
$.nette.ext('redirect', {
	success: function (payload) {
		if (payload.redirect) {
			window.location.href = payload.redirect;
			return false;
		}
	}
});

// change URL (requires HTML5)
$.nette.ext('history', {
	before: function (ui) {
		var $el = $(ui);
		if ($el.is('a')) {
			this.href = ui.href;
		}
	},
	success: function (payload) {
		if (payload.url) {
			this.href = payload.url;
		}
		if (!payload.signal && window.history && history.pushState && this.href) {
			history.pushState({href: this.href}, '', this.href);
		}
	}
}, {href: null});

// current page state
$.nette.ext('state', {
	success: function (payload) {
		if (payload.state) {
			this.state = payload.state;
		}
	}
}, {state: null});

// abort last request if new started
$.nette.ext('unique', {
	start: function (xhr) {
		if (this.req) {
			this.xhr.abort();
		}
		this.xhr = xhr;
	},
	complete: function () {
		this.xhr = null;
	}
}, {xhr: null});

// option to abort by ESC (thx to @vrana)
$.nette.ext('abort', {
	init: function () {
		$('body').keydown($.proxy(function (e) {
			if (this.xhr && (e.keyCode == 27 // Esc
			&& !(e.ctrlKey || e.shiftKey || e.altKey || e.metaKey))
			) {
				this.xhr.abort();
			}
		}, this));
	},
	start: function (xhr) {
		this.xhr = xhr;
	},
	complete: function () {
		this.xhr = null;
	}
}, {xhr: null});

// default ajaxification (can be overriden in init())
$.nette.ext('init', {
	load: function (rh) {
		$(this.linkSelector).off('click', rh).on('click', rh);
		var $forms = $(this.formSelector);
		$forms.off('submit', rh).on('submit', rh);
		$forms.off('click', ':submit', rh).on('click', ':submit', rh);
	},
	success: function () {
		$.nette.load();
	}
}, {
	linkSelector: 'a.ajax',
	formSelector: 'form.ajax'
});

})(jQuery);
<commit_msg>$.ajax() called with 'beforeSend' option<commit_after>/**
 * AJAX Nette Framework plugin for jQuery
 *
 * @copyright Copyright (c) 2009, 2010 Jan Marek
 * @copyright Copyright (c) 2009, 2010 David Grudl
 * @copyright Copyright (c) 2012 Vojtěch Dobeš
 * @license MIT
 */

(function($, undefined) {

if (typeof $ != 'function') return;

var nette = function () {
	var inner = {
		self: this,
		initialized: false,
		contexts: {},
		extensions: {},
		on: {
			init: {},
			load: {},
			before: {},
			start: {},
			success: {},
			complete: {},
			error: {}
		},
		fire: function () {
			var result = true;
			var args = Array.prototype.slice.call(arguments);
			var name = args.shift();
			args.push(inner.self);
			$.each(inner.on[name], function (index, reaction) {
				var temp = reaction.apply(inner.contexts[index], args);
				return result = (temp === undefined || temp);
			});
			return result;
		},
		explicitNoAjax: false,
		requestHandler: function (e) {
			e.stopPropagation();

			// thx to @vrana
			var explicitNoAjax = e.button || e.ctrlKey || e.shiftKey || e.altKey || e.metaKey;

			var $el = $(this), $form, isForm, isSubmit, data = {};
			if ((isForm = $el.is('form')) || (isSubmit = $el.is(':submit'))) {
				if (isSubmit) {
					$form = $el.closest('form');
					data[$el.attr('name')] = $el.val() || '';
				} else if (isForm) {
					$form = $el;
				} else {
					return;
				}

				if (explicitNoAjax && isSubmit) {
					inner.explicitNoAjax = true;
					return;
				} else if (isForm && inner.explicitNoAjax) {
					inner.explicitNoAjax = false;
					return;
				}

				if ($form.get(0).onsubmit && !$form.get(0).onsubmit()) return null;

				var values = $form.serializeArray();
				for (var i = 0; i < values.length; i++) {
					var name = values[i].name;
					if (name in data) {
						var val = data[name];
						if (!(val instanceof Array)) {
							val = [val];
						}
						val.push(values[i].value);
						data[name] = val;
					} else {
						data[name] = values[i].value;
					}
				}
			} else if (explicitNoAjax) return;

			// thx to @vrana
			if (/:|^#/.test($form ? $form.attr('action') : $el.attr('href'))) return;

			$.ajax({
				beforeSend: $.proxy(function (xhr) {
					if (inner.fire('before', this)) {
						e.preventDefault();
						inner.fire('start', xhr);
					} else return false;
				}, this),
				url: $form ? $form.attr('action') : this.href,
				data: data,
				type: $form ? $form.attr('method') : 'get'
			}).done(function (payload) {
				inner.fire('success', payload);
			}).fail(function () {
				inner.fire('error');
			}).always(function () {
				inner.fire('complete');
			});
		}
	};

	/**
	 * Allows manipulation with extensions.
	 * When called with 1. argument only, it returns extension with given name.
	 * When called with 2. argument equal to false, it removes extension entirely.
	 * When called with 2. argument equal to hash of event callbacks, it adds new extension.
	 *
	 * @param  {string} Name of extension
	 * @param  {bool|object|null} Set of callbacks for any events OR false for removing extension.
	 * @param  {object|null} Context for added extension
	 * @return {$.nette} Provides a fluent interface
	 */
	this.ext = function (name, callbacks, context) {
		if (inner.initialized) throw 'Cannot manipulate nette-ajax extensions after initialization.';

		if (callbacks === undefined) {
			inner.extensions[name];
		} else if (!callbacks) {
			inner.extensions[name] = undefined;
		} else if (inner.extensions[name]) {
			throw 'Cannot override already registered nette-ajax extension.';
		} else {
			inner.extensions[name] = {
				name: name,
				on: callbacks,
				context: context
			};
		}
		return this;
	};

	/**
	 * Initializes the plugin. Forbids any further modifications of extensions.
	 * Fires 'init' event, then 'load' event.
	 * When called with any arguments, it will overried default 'init' extension
	 * with provided callbacks.
	 *
	 * @param  {function|object|null} Callback for 'load' event or entire set of callbacks for any events
	 * @param  {object|null} Context provided for callbacks in first argument
	 * @return {$.nette} Provides a fluent interface
	 */
	this.init = function (load, loadContext) {
		if (inner.initialized) throw 'Cannot initialize nette-ajax twice.';

		if (typeof load == 'function') {
			this.ext('init', null);
			this.ext('init', {
				load: load
			}, loadContext);
		} else if (typeof load == 'object') {
			this.ext('init', null);
			this.ext('init', load, loadContext);
		} else if (load !== undefined) {
			throw 'Argument of init() can be function or function-hash only.';
		}

		inner.initialized = true;

		$.each(inner.extensions, function (index, extension) {
			$.each(['init', 'load', 'before', 'start', 'success', 'complete', 'error'], function (index, reaction) {
				if (extension !== undefined) {
					if (extension.on[reaction] !== undefined) {
						inner.on[reaction][extension.name] = extension.on[reaction];
					}
					inner.contexts[extension.name] = $.extend(extension.context ? extension.context : {}, {
						ext: function (name) {
							var ext = inner.contexts[name];
							if (!ext) throw "Extension '" + extension.name + "' depends on disabled extension '" + name + "'.";
							return ext;
						}
					});
				}
			});
		});

		inner.fire('init');
		this.load();
		return this;
	};

	/**
	 * Fires 'load' event
	 *
	 * @return {$.nette} Provides a fluent interface
	 */
	this.load = function () {
		inner.fire('load', inner.requestHandler);
		return this;
	};
};

$.nette = new ($.extend(nette, $.nette ? $.nette : {}));

// default snippet handler
$.nette.ext('snippets', {
	success: function (payload) {
		if (payload.snippets) {
			for (var i in payload.snippets) {
				this.updateSnippet(i, payload.snippets[i]);
			}
		}
	}
}, {
	updateSnippet: function (id, html) {
		var $el = this.getElement(id);
		// Fix for setting document title in IE
		if ($el.eq(0).tagName == 'TITLE') {
			document.title = html;
		} else {
			this.applySnippet($el, html);
		}
	},
	getElement: function (id) {
		return $('#' + this.escapeSelector(id));
	},
	applySnippet: function ($el, html) {
		$el.html(html);
	},
	escapeSelector: function (selector) {
		// thx to @uestla (https://github.com/uestla)
		return selector.replace(/[\!"#\$%&'\(\)\*\+,\.\/:;<=>\?@\[\\\]\^`\{\|\}~]/g, '\\$&');
	}
});

// support $this->redirect()
$.nette.ext('redirect', {
	success: function (payload) {
		if (payload.redirect) {
			window.location.href = payload.redirect;
			return false;
		}
	}
});

// change URL (requires HTML5)
$.nette.ext('history', {
	before: function (ui) {
		var $el = $(ui);
		if ($el.is('a')) {
			this.href = ui.href;
		}
	},
	success: function (payload) {
		if (payload.url) {
			this.href = payload.url;
		}
		if (!payload.signal && window.history && history.pushState && this.href) {
			history.pushState({href: this.href}, '', this.href);
		}
	}
}, {href: null});

// current page state
$.nette.ext('state', {
	success: function (payload) {
		if (payload.state) {
			this.state = payload.state;
		}
	}
}, {state: null});

// abort last request if new started
$.nette.ext('unique', {
	start: function (xhr) {
		if (this.req) {
			this.xhr.abort();
		}
		this.xhr = xhr;
	},
	complete: function () {
		this.xhr = null;
	}
}, {xhr: null});

// option to abort by ESC (thx to @vrana)
$.nette.ext('abort', {
	init: function () {
		$('body').keydown($.proxy(function (e) {
			if (this.xhr && (e.keyCode == 27 // Esc
			&& !(e.ctrlKey || e.shiftKey || e.altKey || e.metaKey))
			) {
				this.xhr.abort();
			}
		}, this));
	},
	start: function (xhr) {
		this.xhr = xhr;
	},
	complete: function () {
		this.xhr = null;
	}
}, {xhr: null});

// default ajaxification (can be overriden in init())
$.nette.ext('init', {
	load: function (rh) {
		$(this.linkSelector).off('click', rh).on('click', rh);
		var $forms = $(this.formSelector);
		$forms.off('submit', rh).on('submit', rh);
		$forms.off('click', ':submit', rh).on('click', ':submit', rh);
	},
	success: function () {
		$.nette.load();
	}
}, {
	linkSelector: 'a.ajax',
	formSelector: 'form.ajax'
});

})(jQuery);
<|endoftext|>


<commit_before>// Type for each LINK
var LINK = {id: 'string', metadata: 'object', label: 'string', url: 'string'};

var PERMALINK = {format: 'string', custom: 'string', isCustom: 'boolean'};

// KEY           TYPE       WRITE   PUBLIC
var DECLARATION = {
  id:           ['string',  false,  false],
  owner:        ['string',  false,  false],
  handle:       ['string',  true,   true],
  client:       ['string',  true,   false],
  title:        ['string',  true,   true],
  isDisabled:   ['boolean', true,   false],
  avatar:       ['string',  true,   true],
  roundAvatar:  ['boolean', true,   true],
  cssURL:       ['string',  false,  true],
  scriptURL:    ['string',  false,  true],
  template:     ['string',  true,   false],
  menu:         [[LINK],    true,   true],
  domain:       ['string',  true,   true],
  permalink:    [PERMALINK, true,   true],
  dateFormat:   ['string',  true,   true],
  dateDisplay:  ['string',  true,   true],
  timeZone:     ['string',  true,   true],
  hideDates:    ['boolean', true,   true],
  plugins:      ['object',  true,   true],
  cacheID:      ['number',  false,  true]
};

var MODEL = {TYPE: {}, PUBLIC: [], WRITEABLE: []};

for (var i in DECLARATION)
  MODEL.TYPE[i] = DECLARATION[i][0];

for (var i in DECLARATION)
  if (DECLARATION[i][1])
    MODEL.WRITEABLE.push(i);

for (var i in DECLARATION)
  if (DECLARATION[i][2])
    MODEL.PUBLIC.push(i);

module.exports = MODEL;<commit_msg>Added option to require SSL<commit_after>// Type for each LINK
var LINK = {id: 'string', metadata: 'object', label: 'string', url: 'string'};

var PERMALINK = {format: 'string', custom: 'string', isCustom: 'boolean'};

// KEY           TYPE       WRITE   PUBLIC
var DECLARATION = {
  id:           ['string',  false,  false],
  owner:        ['string',  false,  false],
  handle:       ['string',  true,   true],
  client:       ['string',  true,   false],
  title:        ['string',  true,   true],
  isDisabled:   ['boolean', true,   false],
  avatar:       ['string',  true,   true],
  roundAvatar:  ['boolean', true,   true],
  cssURL:       ['string',  false,  true],
  scriptURL:    ['string',  false,  true],
  template:     ['string',  true,   false],
  menu:         [[LINK],    true,   true],
  domain:       ['string',  true,   true],
  forceSSL:     ['boolean', true,   false],
  permalink:    [PERMALINK, true,   true],
  dateFormat:   ['string',  true,   true],
  dateDisplay:  ['string',  true,   true],
  timeZone:     ['string',  true,   true],
  hideDates:    ['boolean', true,   true],
  plugins:      ['object',  true,   true],
  cacheID:      ['number',  false,  true]
};

var MODEL = {TYPE: {}, PUBLIC: [], WRITEABLE: []};

for (var i in DECLARATION)
  MODEL.TYPE[i] = DECLARATION[i][0];

for (var i in DECLARATION)
  if (DECLARATION[i][1])
    MODEL.WRITEABLE.push(i);

for (var i in DECLARATION)
  if (DECLARATION[i][2])
    MODEL.PUBLIC.push(i);

module.exports = MODEL;<|endoftext|>


<commit_before>'use strict';

var express      = require('express');
var configs      = require('../configs/global.configs');
var Article      = require('../models/Article');
var User         = require('../models/User');
var Category     = require('../models/Category');
var _            = require('lodash');
var moment       = require('moment');
var path         = require('path');
var fs           = require('fs');
var cheerio      = require('cheerio');
var sanitizeHtml = require('sanitize-html');

var router = express.Router();

Article.belongsTo(User, {foreignKey:'user_id', as:'User'});
Article.belongsTo(Category, {foreignKey:'category_id', as:'Category'});

module.exports = function(validator){

	router.get('/articles/new', function(req, res, next){ //create placeholder for new article
		var result = validator(req, res);

		if(result.success){

			let now = moment();

			Article.create({
				title: 'new article',
				valid: 1,
				start_time: now.format('YYYY-MM-DD HH:mm:ss'),
				approved: 0,
				at_top: 0,
				user_id: result.user_id
			})
			.then(function(article){
				article.category_id = null;
				res.json({
					success: true,
					message: 'Article created',
					record: article
				});
			})
			.catch(function(error){
				res.json({
					success: false,
					error: error,
					message: 'Unable to create article'
				});
			});

		} else {
			res.json(result);
		}
	});

	router.get('/articles/:id', function(req, res, next) {
		var result = validator(req, res);

		if(result.success){

			var id = req.params.id;

			if(!id){
				throw new Error('Invalid article ID');
			}

			Article.findOne({where: {id: id}, valid: 1}).then(function(result){

				if(!result){
					throw new Error('Invalid article');
				}

				res.json({
					success: true,
					record: result
				});

			}).catch(function(error){
				console.error(error);

				res.json({
					success: false,
					error: error
				});
			});

		} else {
			res.json(result);
		}
	});

	router.get('/articles', function(req, res, next) {

		var result = validator(req, res);

		if(result.success){

			var pageIndex  = parseInt(req.query.pageIndex);
			var pageSize   = parseInt(req.query.pageSize);
			var sortField  = _.trim(_.escape(req.query.sortField));
			var sortOrder  = _.trim(_.escape(req.query.sortOrder));

			var conditions          = { valid: 1 };
			var category_conditions = { valid: 1 };
			var user_conditions     = { valid: 1 };
			var order               = 'id desc';
			var filter              = '';

			for(filter in req.query){
				if(_.indexOf(['pageIndex','pageSize', 'sortField', 'sortOrder'], filter) === -1){

					var f_value = _.trim(_.escape(req.query[filter]));

					if(f_value){

						if(filter === 'category_name'){
							category_conditions['title'] = { '$like': '%' + f_value + '%' }
						} else if(filter === 'author'){
							user_conditions['username'] = { '$like': '%' + f_value + '%' }
						} else {
							conditions[filter] = { '$like': '%' + f_value + '%' };
						}
					}
				}
			}

			if(sortField){
				if(sortField === 'category_name'){
					sortField = '`Category`.`title`';
				}

				if(sortField === 'author'){
					sortField = '`User`.`username`';
				}
				order = sortField + ' ' + sortOrder;
			}

			pageIndex = !isNaN(pageIndex) ? pageIndex - 1 : 0;
			pageSize  = !isNaN(pageSize) ? pageSize : 20;

			Article.findAndCountAll({
				where:   conditions,
				order:   order,
				offset:  (pageIndex * pageSize),
				limit:   pageSize,
				attributes:['title', 'id', 'abstract', 'created', 'modified', 'thumbnail', 'video_url', 'at_top'],
				include: [
					{ model:Category, as:'Category', required: true, where: category_conditions, attributes:['title', 'id'] },
					{ model:User, as:'User', required: true, where: user_conditions, attributes:['username', 'id'] }
				]
			}).then(function(result){

				res.json({
					success:    true,
					data:       result.rows,
					itemsCount: result.count
				});

			}).catch(function(error){

				console.error(error);

				res.json({
					success: false,
					error: error
				});
			});

		} else {
			res.json(result);
		}
	});

	router.put('/articles', function(req, res, next) {
		var result = validator(req, res);

		if(result.success){

			var record = req.body;
			var id     = record.id;

			Article.find({where:{id:id}})
			.then(function(article){

				//find the first image in the content
				var $document = cheerio.load(record.content);
				var first_img = $document('img').first();

				if(first_img){
					record.thumbnail = first_img.attr('src');
				}

				record.content = sanitizeHtml(record.content, {
					allowedTags: [
					  'h1','h2','h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol',
					  'nl', 'li', 'b', 'i', 'strong', 'em', 'strike', 'hr', 'br', 'div', 'img',
					  'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'iframe',
					  'font', 'span'
					],
					allowedAttributes: {
					  a:    [ 'href', 'name', 'target', 'title'],
					  img:  [ 'src' ],
					  font: [ 'face', 'style'],
					  span: [ 'style']
					},
					// Lots of these won't come up by default because we don't allow them
					selfClosing: [ 'img', 'br', 'hr', 'area', 'base', 'basefont', 'input', 'link', 'meta' ],
					// URL schemes we permit
					allowedSchemes: [ 'http', 'https', 'mailto' ],
					allowProtocolRelative: true
				}).replace(/mso-fareast-font-family:標楷體/g, '')
				  .replace(/mso-fareast-font-family: 新細明體;/g, '')
				  .replace(/font-family: 標楷體;/g, '')
				  .replace(/mso-fareast-theme-font:minor-fareast;/g, '')
				  .replace(/mso-bidi-font-family:Arial;/g, '')
				  .replace(/mso-fareast-language:ZH-TW/g, '')
				  .replace(/font-family: CorpoS;/g, '')
				  .replace(/font-family: 新細明體/g, '')
				  .replace(/undefined, serif;/g, '')
				  .replace(/font-family: 新細明體, serif;/g, '')
				  .replace(/ , serif;/g, '');

				article.updateAttributes(record).then(function(article){

					//clear the cache if any
					var cache_file_name = path.join(__dirname, '../public/articles/read', id + '.html');

					if(fs.existsSync(cache_file_name)){
						fs.unlink(cache_file_name);
					}

					res.json({
						success: true,
						record: article
					});
				})

				.catch(function(error){

					console.info('error @ /articles PUT:', error);

					res.json({
						success: false,
						error: error
					});
				});
			})
			.catch(function(error){

				console.info('error @ /articles PUT:', error);

				res.json({
					success: false,
					error: error
				});
			});

		}else {
			res.json(result);
		}
	});

	router.delete('/articles', function(req, res, next) {
		var result = validator(req, res);

		if(result.success){

			var record = req.body;
			var id     = record.id;

			Article.find({where:{id:id}})
			.then(function(article){

				article.updateAttributes({valid:0})
				.then(function(article){
					res.json(article);
				})
				.catch(function(error){
					console.info('Error @article delete:', error);
					res.json({
						success: false,
						error: error
					});
				});
			})
			.catch(function(error){
				console.info('Error @article delete:', error);
				res.json({
					success: false,
					error: error
				});
			});

		}else {
			res.json(result);
		}
	});

	return router;
}<commit_msg>Added HTML sanitization<commit_after>'use strict';

var express      = require('express');
var configs      = require('../configs/global.configs');
var Article      = require('../models/Article');
var User         = require('../models/User');
var Category     = require('../models/Category');
var _            = require('lodash');
var moment       = require('moment');
var path         = require('path');
var fs           = require('fs');
var cheerio      = require('cheerio');
var sanitizeHtml = require('sanitize-html');

var router = express.Router();

Article.belongsTo(User, {foreignKey:'user_id', as:'User'});
Article.belongsTo(Category, {foreignKey:'category_id', as:'Category'});

module.exports = function(validator){

	router.get('/articles/new', function(req, res, next){ //create placeholder for new article
		var result = validator(req, res);

		if(result.success){

			let now = moment();

			Article.create({
				title: 'new article',
				valid: 1,
				start_time: now.format('YYYY-MM-DD HH:mm:ss'),
				approved: 0,
				at_top: 0,
				user_id: result.user_id
			})
			.then(function(article){
				article.category_id = null;
				res.json({
					success: true,
					message: 'Article created',
					record: article
				});
			})
			.catch(function(error){
				res.json({
					success: false,
					error: error,
					message: 'Unable to create article'
				});
			});

		} else {
			res.json(result);
		}
	});

	router.get('/articles/:id', function(req, res, next) {
		var result = validator(req, res);

		if(result.success){

			var id = req.params.id;

			if(!id){
				throw new Error('Invalid article ID');
			}

			Article.findOne({where: {id: id}, valid: 1}).then(function(result){

				if(!result){
					throw new Error('Invalid article');
				}

				res.json({
					success: true,
					record: result
				});

			}).catch(function(error){
				console.error(error);

				res.json({
					success: false,
					error: error
				});
			});

		} else {
			res.json(result);
		}
	});

	router.get('/articles', function(req, res, next) {

		var result = validator(req, res);

		if(result.success){

			var pageIndex  = parseInt(req.query.pageIndex);
			var pageSize   = parseInt(req.query.pageSize);
			var sortField  = _.trim(_.escape(req.query.sortField));
			var sortOrder  = _.trim(_.escape(req.query.sortOrder));

			var conditions          = { valid: 1 };
			var category_conditions = { valid: 1 };
			var user_conditions     = { valid: 1 };
			var order               = 'id desc';
			var filter              = '';

			for(filter in req.query){
				if(_.indexOf(['pageIndex','pageSize', 'sortField', 'sortOrder'], filter) === -1){

					var f_value = _.trim(_.escape(req.query[filter]));

					if(f_value){

						if(filter === 'category_name'){
							category_conditions['title'] = { '$like': '%' + f_value + '%' }
						} else if(filter === 'author'){
							user_conditions['username'] = { '$like': '%' + f_value + '%' }
						} else {
							conditions[filter] = { '$like': '%' + f_value + '%' };
						}
					}
				}
			}

			if(sortField){
				if(sortField === 'category_name'){
					sortField = '`Category`.`title`';
				}

				if(sortField === 'author'){
					sortField = '`User`.`username`';
				}
				order = sortField + ' ' + sortOrder;
			}

			pageIndex = !isNaN(pageIndex) ? pageIndex - 1 : 0;
			pageSize  = !isNaN(pageSize) ? pageSize : 20;

			Article.findAndCountAll({
				where:   conditions,
				order:   order,
				offset:  (pageIndex * pageSize),
				limit:   pageSize,
				attributes:['title', 'id', 'abstract', 'created', 'modified', 'thumbnail', 'video_url', 'at_top'],
				include: [
					{ model:Category, as:'Category', required: true, where: category_conditions, attributes:['title', 'id'] },
					{ model:User, as:'User', required: true, where: user_conditions, attributes:['username', 'id'] }
				]
			}).then(function(result){

				res.json({
					success:    true,
					data:       result.rows,
					itemsCount: result.count
				});

			}).catch(function(error){

				console.error(error);

				res.json({
					success: false,
					error: error
				});
			});

		} else {
			res.json(result);
		}
	});

	router.put('/articles', function(req, res, next) {
		var result = validator(req, res);

		if(result.success){

			var record = req.body;
			var id     = record.id;

			Article.find({where:{id:id}})
			.then(function(article){

				//find the first image in the content
				var $document = cheerio.load(record.content);
				var first_img = $document('img').first();

				if(first_img){
					record.thumbnail = first_img.attr('src');
				}

				record.content = sanitizeHtml(record.content, {
					allowedTags: [
					  'h1','h2','h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol',
					  'nl', 'li', 'b', 'i', 'strong', 'em', 'strike', 'hr', 'br', 'div', 'img',
					  'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'iframe',
					  'font', 'span'
					],
					allowedAttributes: {
					  a:    [ 'href', 'name', 'target', 'title'],
					  img:  [ 'src' ],
					  font: [ 'face', 'style'],
					  span: [ 'style']
					},
					// Lots of these won't come up by default because we don't allow them
					selfClosing: [ 'img', 'br', 'hr', 'area', 'base', 'basefont', 'input', 'link', 'meta' ],
					// URL schemes we permit
					allowedSchemes: [ 'http', 'https', 'mailto' ],
					allowProtocolRelative: true
				}).replace(/mso-fareast-font-family:標楷體/g, '')
				  .replace(/mso-fareast-font-family: 新細明體;/g, '')
				  .replace(/font-family: 標楷體;/g, '')
				  .replace(/mso-fareast-theme-font:minor-fareast;/g, '')
				  .replace(/mso-bidi-font-family:Arial;/g, '')
				  .replace(/mso-fareast-language:ZH-TW/g, '')
				  .replace(/font-family: CorpoS;/g, '')
				  .replace(/font-family: 新細明體/g, '')
				  .replace(/undefined, serif;/g, '')
				  .replace(/font-family: 新細明體, serif;/g, '')
				  .replace(/ , serif;/g, '')
				  .replace(/font-family:"新細明體",serif;/g, '')
				  .replace(/mso-ascii-theme-font:minor-fareast;mso-fareast-theme-font:/g, '')
				  .replace(/minor-fareast;mso-hansi-theme-font:minor-fareast;/g, '')
				  .replace(/mso-fareast-theme-font:minor-fareast;/g, '');;

				article.updateAttributes(record).then(function(article){

					//clear the cache if any
					var cache_file_name = path.join(__dirname, '../public/articles/read', id + '.html');

					if(fs.existsSync(cache_file_name)){
						fs.unlink(cache_file_name);
					}

					res.json({
						success: true,
						record: article
					});
				})

				.catch(function(error){

					console.info('error @ /articles PUT:', error);

					res.json({
						success: false,
						error: error
					});
				});
			})
			.catch(function(error){

				console.info('error @ /articles PUT:', error);

				res.json({
					success: false,
					error: error
				});
			});

		}else {
			res.json(result);
		}
	});

	router.delete('/articles', function(req, res, next) {
		var result = validator(req, res);

		if(result.success){

			var record = req.body;
			var id     = record.id;

			Article.find({where:{id:id}})
			.then(function(article){

				article.updateAttributes({valid:0})
				.then(function(article){
					res.json(article);
				})
				.catch(function(error){
					console.info('Error @article delete:', error);
					res.json({
						success: false,
						error: error
					});
				});
			})
			.catch(function(error){
				console.info('Error @article delete:', error);
				res.json({
					success: false,
					error: error
				});
			});

		}else {
			res.json(result);
		}
	});

	return router;
}<|endoftext|>


<commit_before>var http   = require('http'),
    common = require('../common'),
    passes = exports;

/*!
 * Array of passes.
 *
 * A `pass` is just a function that is executed on `req, socket, options`
 * so that you can easily add new checks while still keeping the base
 * flexible.
 */

/*
 * Websockets Passes
 *
 */

var passes = exports;

[
/**
 * WebSocket requests must have the `GET` method and
 * the `upgrade:websocket` header
 */

function checkMethodAndHeader (req, socket) {
  if (req.method !== 'GET' || !req.headers.upgrade) {
    socket.destroy();  return true;
  }

  if (req.headers.upgrade.toLowerCase() !== 'websocket') {
    socket.destroy();  return true;
  }
},

/**
 * Setup socket
 *
 */

function setupSocket(req, socket) {
  socket.setTimeout(0);
  socket.setNoDelay(true);

  socket.setKeepAlive(true, 0);
},

/**
 * Sets `x-forwarded-*` headers if specified in config.
 *
 */

function XHeaders(req, socket, options) {
  if(!options.xfwd) return;

  var values = {
    for  : req.connection.remoteAddsockets || req.socket.remoteAddsockets,
    port : req.connection.remotePort || req.socket.remotePort,
    proto: req.connection.pair ? 'wss' : 'ws'
  };

  ['for', 'port', 'proto'].forEach(function(header) {
    req.headers['x-forwarded-' + header] =
      (req.headers['x-forwarded-' + header] || '') +
      (req.headers['x-forwarded-' + header] ? ',' : '') +
      values[header]
  });
},

/**
 *
 *
 */
function stream(req, socket, options, head) {
  var r = http.request(
    common.setupOutgoing(options.ssl || {}, options, req)
  );

  r.on('upgrade', function(proxyRes, proxySocket, proxyHead) {
    if (proxyHead && proxyHead.length) proxySocket.unshift(proxyHead);

    socket.write('HTTP/1.1 101 Switching Protocols\r\n');
    socket.write(Object.keys(proxyRes.headers).map(function(i) {
      return i + ": " + proxyRes.headers[i];
    }).join('\r\n') + '\r\n\r\n');
    proxySocket.pipe(socket).pipe(proxySocket);
  });

  r.end();
}

] // <--
  .forEach(function(func) {
    passes[func.name] = func;
  });
<commit_msg>[fix] naming convention<commit_after>var http   = require('http'),
    common = require('../common'),
    passes = exports;

/*!
 * Array of passes.
 *
 * A `pass` is just a function that is executed on `req, socket, options`
 * so that you can easily add new checks while still keeping the base
 * flexible.
 */

/*
 * Websockets Passes
 *
 */

var passes = exports;

[
/**
 * WebSocket requests must have the `GET` method and
 * the `upgrade:websocket` header
 */

function checkMethodAndHeader (req, socket) {
  if (req.method !== 'GET' || !req.headers.upgrade) {
    socket.destroy();  return true;
  }

  if (req.headers.upgrade.toLowerCase() !== 'websocket') {
    socket.destroy();  return true;
  }
},

/**
 * Setup socket
 *
 */

function setupSocket(req, socket) {
  socket.setTimeout(0);
  socket.setNoDelay(true);

  socket.setKeepAlive(true, 0);
},

/**
 * Sets `x-forwarded-*` headers if specified in config.
 *
 */

function XHeaders(req, socket, options) {
  if(!options.xfwd) return;

  var values = {
    for  : req.connection.remoteAddsockets || req.socket.remoteAddsockets,
    port : req.connection.remotePort || req.socket.remotePort,
    proto: req.connection.pair ? 'wss' : 'ws'
  };

  ['for', 'port', 'proto'].forEach(function(header) {
    req.headers['x-forwarded-' + header] =
      (req.headers['x-forwarded-' + header] || '') +
      (req.headers['x-forwarded-' + header] ? ',' : '') +
      values[header]
  });
},

/**
 *
 *
 */
function stream(req, socket, options, head) {
  var proxyReq = http.request(
    common.setupOutgoing(options.ssl || {}, options, req)
  );

  proxyReq.on('upgrade', function(proxyRes, proxySocket, proxyHead) {
    if (proxyHead && proxyHead.length) proxySocket.unshift(proxyHead);

    socket.write('HTTP/1.1 101 Switching Protocols\r\n');
    socket.write(Object.keys(proxyRes.headers).map(function(i) {
      return i + ": " + proxyRes.headers[i];
    }).join('\r\n') + '\r\n\r\n');
    proxySocket.pipe(socket).pipe(proxySocket);
  });

  proxyReq.end();
}

] // <--
  .forEach(function(func) {
    passes[func.name] = func;
  });
<|endoftext|>