<commit_before>/**
* Module dependencies.
*/

var express    = require('express'),
    formidable = require('formidable'),
    awssum     = require('awssum'),
    amazon     = awssum.load('amazon/amazon'),
    S3         = awssum.load('amazon/s3').S3,
    fs         = require('fs'),
    redis      = require('redis-url'),
    crypto     = require('crypto'),
    XRegExp    = require('xregexp').XRegExp,
    _          = require("underscore")._,
    Pusher     = require('pusher'),
    AWS        = require('aws-sdk');

var s3 = new S3({
  'accessKeyId'     : process.env.AWS_KEY,
  'secretAccessKey' : process.env.AWS_SECRET,
  'region'          : process.env.AWS_REGION || amazon.US_EAST_1
});

var app = module.exports = express();

var db = redis.createClient(process.env.REDISTOGO_URL);
var pusher;
var AsciiOnlyRegexp = XRegExp("[^\\s\\_\\-\\.\\p{Ascii}]+", "g")

app.set('access-control', {
  allowOrigin: process.env.ALLOW_ORIGIN || '*',
  allowMethods: 'OPTIONS, GET, POST'
});

app.set('bucket', process.env.AWS_BUCKET);
app.set('policy', process.env.AWS_POLICY || 'public-read');
app.set('use-ssl', process.env.USE_SSL);
app.set('upload-path', process.env.UPLOAD_PATH || 'uploads');

app.set('secret', process.env.SECURITY_SECRET);
app.set('secret_expiration', process.env.SECURITY_SECRET_EXPIRATION || 600); // 5 minutes of expiration

app.set('port', process.env.PORT || 5000)
app.set('pusherChannelName', process.env.PUSHER_CHANNEL_NAME || 'cloud-uploader')

function log() {
  if (app.get('debug')) {
    console.log.apply(console, arguments);
  }
}

if (process.env.PUSHER_APP_ID && process.env.PUSHER_KEY && process.env.PUSHER_SECRET) {
  pusher = new Pusher({
    appId:  process.env.PUSHER_APP_ID,
    key:    process.env.PUSHER_KEY,
    secret: process.env.PUSHER_SECRET
  });
} else {
  console.log('PUSHER_APP_ID, PUSHER_KEY and PUSHER_SECRET are not set, pusher feature will not be available');
}

function FileInfo(file, options) {
  this.name = XRegExp.replace(file.name, AsciiOnlyRegexp, '');
  this.size = file.size;
  this.type = file.type;
  this.delete_type = 'DELETE';

  host   = options.bucket + '.s3.amazonaws.com';
  useSSL = options.useSSL && options.useSSL != 'false'

  baseUrl = (useSSL ? 'https:' : 'http:') + '//' + host + '/' + options.uploadPath + '/';
  this.url = this.delete_url = baseUrl + encodeURIComponent(this.name);

  if (!_(['public-read', 'public-read-write']).include(options.policy)) {
    var key = options.uploadPath + '/' + encodeURIComponent(this.name);
    this.url = signedUrl(key)
  }
}

function signedUrl(key) {
  var credentials = new AWS.Credentials(process.env.AWS_KEY, process.env.AWS_SECRET),
      awsS3 = new AWS.S3(credentials)

  return awsS3.getSignedUrl('getObject', {Bucket: process.env.AWS_BUCKET, Key: key})
}

function setDefaultHeaders(response) {
  response.setHeader(
    'Access-Control-Allow-Origin',
    app.get('access-control').allowOrigin
  );

  response.setHeader(
    'Access-Control-Allow-Methods',
    app.get('access-control').allowMethods
  );
}

function contentTypeFor(request) {
  return request.headers.accept.indexOf('application/json') !== -1 ? 'application/json' : 'text/plain';
}

function handleResult(request, response, result, redirect) {
  var jsonResult = JSON.stringify(result)

  if (redirect) {
    var redirectURL = redirect.replace(/%s/, encodeURIComponent(jsonResult));
    log('Redirecting to ' + redirectURL)
    response.redirect(redirectURL)
  } else {
    log('Returning content ' + jsonResult)
    setDefaultHeaders(response);

    response.set('Content-Type', contentTypeFor(request));
    response.send(jsonResult);
  }
}

function validToken(value) {
  var secret           = app.get('secret'),
      expiration       = app.get('secret_expiration'),
      currentTimestamp = Math.round(new Date().getTime()/1000),
      timestamp        = parseInt(value.substr(0,  10), 10),
      random           = value.substr(10, 10),
      hash             = value.substr(20),
      shasum           = crypto.createHash('sha1')

  shasum.update('' + timestamp + random + secret)
  digest = shasum.digest('hex')

  return timestamp + expiration > currentTimestamp && hash == digest
}

// Configuration

app.configure('development', function(){
  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
  app.use(express.logger());

  app.set('debug', true);
});

app.configure('production', function(){
  app.use(express.errorHandler());
  app.set('debug', false);
});

// Routes

app.get('/status', function(request, response) {
  setDefaultHeaders(response);

  if (request.query.file) {
    db.get(request.query.file, function (error, redis_response) {
      error = redis_response == 'error' ? 'upload-failed' : error

      if (!error) {
        // key is deleted when upload is finished, so it should have an empty response
        response.jsonp({finished_uploading: _(redis_response).isEmpty()})
      } else {
        response.jsonp(500, [{error: error}])
      }
    })
  } else {
    response.jsonp(400, [{error: 'You must pass a file parameter.'}])
  }
});

app.options('/upload', function (request, response) {
  setDefaultHeaders(response);
  response.end();
});

app.post('/upload', function(request, response) {
  var form          = new formidable.IncomingForm(),
      secret        = app.get('secret'),
      valid         = _(secret).isUndefined(),
      expireUpload  = 10 * 24 * 60 * 60, // 10 days
      channelName   = app.get('pusherChannelName')
      uploadOptions = {
        bucket:       app.get('bucket'),
        uploadPath:   app.get('upload-path'),
        policy:       app.get('policy'),
        useSSL:       app.get('use-ssl')
      };

  log('starting upload');

  form.maxFieldsSize = 20 * 1024 * 1024; // 20mb
  form.keepExtensions = true;

  form.on('file', function(name, file) {
    if (!valid) {
      log('Invalid token given, not uploading file to amazon')
      return
    }

    var startTime = new Date(),
        filename  = XRegExp.replace(file.name, AsciiOnlyRegexp, ''),
        options = {
          BucketName : uploadOptions.bucket,
          ObjectName : uploadOptions.uploadPath + '/' + filename,
          ContentLength : file.size,
          ContentType: file.type,
          Body : fs.createReadStream(file.path),
          Acl: uploadOptions.policy
        },
        fileInfo = new FileInfo(file, uploadOptions)

    log("%s uploaded successfully, sending it to to the %s bucket in s3", filename, uploadOptions.bucket);

    log('Public file URL: %s', fileInfo.url);

    db.setex(filename, expireUpload, JSON.stringify(fileInfo));

    log('Sending file with options: ', options)
    s3.PutObject(options, function(errors, data) {
      var elapsed = (new Date() - startTime) / 1000;

      log('S3 upload complete in %s seconds.', elapsed);
      log('Errors: ', errors);
      log('Data: ', data);

      if (errors) {
        db.setex(filename, expireUpload, 'error');
      } else {
        db.del(filename);
      }

      fs.unlink(file.path);

      if (pusher) {
        if (errors) {
          log('amazon upload failed, triggering upload-failed on pusher for ' + filename);
          pusher.trigger(channelName, 'upload-failed', errors);
        } else {
          log('amazon upload completed, triggering upload-completed on pusher for ' + filename)
          pusher.trigger(channelName, 'upload-completed', JSON.stringify(file));
        }
      }
    });
  });

  form.on('error', function(err) {
    log('formidable error', err);
  });

  form.on('field', function(name, value) {
    log('field received: %s=%s', name, value);

    if (secret && name == 'token') {
      valid = validToken(value)
    } else if (name == 'channel') {
      channelName = value
    } else {
      uploadOptions[name] = value // allows upload parameter to be overridden on a per-upload basis
    }
  });

  form.on('end', function() {
    log('formidable end');
  });

  form.parse(request, function(err, fields, files) {
    if (valid) {
      var filesInfo = _(files).map(function (file) {
        return new FileInfo(file, uploadOptions)
      });

      handleResult(request, response, filesInfo, fields.redirect)
    } else {
      response.jsonp(403, [{error: 'Forbidden'}])
    }
  });
});

app.listen(app.get('port'));
console.log("Express server listening on port %d in %s mode", app.get('port'), app.settings.env);
<commit_msg>Setting expiration time in signed url<commit_after>/**
* Module dependencies.
*/

var express    = require('express'),
    formidable = require('formidable'),
    awssum     = require('awssum'),
    amazon     = awssum.load('amazon/amazon'),
    S3         = awssum.load('amazon/s3').S3,
    fs         = require('fs'),
    redis      = require('redis-url'),
    crypto     = require('crypto'),
    XRegExp    = require('xregexp').XRegExp,
    _          = require("underscore")._,
    Pusher     = require('pusher'),
    AWS        = require('aws-sdk');

var s3 = new S3({
  'accessKeyId'     : process.env.AWS_KEY,
  'secretAccessKey' : process.env.AWS_SECRET,
  'region'          : process.env.AWS_REGION || amazon.US_EAST_1
});

var app = module.exports = express();

var db = redis.createClient(process.env.REDISTOGO_URL);
var pusher;
var AsciiOnlyRegexp = XRegExp("[^\\s\\_\\-\\.\\p{Ascii}]+", "g")

app.set('access-control', {
  allowOrigin: process.env.ALLOW_ORIGIN || '*',
  allowMethods: 'OPTIONS, GET, POST'
});

app.set('bucket', process.env.AWS_BUCKET);
app.set('policy', process.env.AWS_POLICY || 'public-read');
app.set('use-ssl', process.env.USE_SSL);
app.set('upload-path', process.env.UPLOAD_PATH || 'uploads');
app.set('signed-url-expiration', process.env.SIGNED_URL_EXPIRATION || 900)

app.set('secret', process.env.SECURITY_SECRET);
app.set('secret_expiration', process.env.SECURITY_SECRET_EXPIRATION || 600); // 5 minutes of expiration

app.set('port', process.env.PORT || 5000)
app.set('pusherChannelName', process.env.PUSHER_CHANNEL_NAME || 'cloud-uploader')

function log() {
  if (app.get('debug')) {
    console.log.apply(console, arguments);
  }
}

if (process.env.PUSHER_APP_ID && process.env.PUSHER_KEY && process.env.PUSHER_SECRET) {
  pusher = new Pusher({
    appId:  process.env.PUSHER_APP_ID,
    key:    process.env.PUSHER_KEY,
    secret: process.env.PUSHER_SECRET
  });
} else {
  console.log('PUSHER_APP_ID, PUSHER_KEY and PUSHER_SECRET are not set, pusher feature will not be available');
}

function FileInfo(file, options) {
  this.name = XRegExp.replace(file.name, AsciiOnlyRegexp, '');
  this.size = file.size;
  this.type = file.type;
  this.delete_type = 'DELETE';

  host   = options.bucket + '.s3.amazonaws.com';
  useSSL = options.useSSL && options.useSSL != 'false'

  baseUrl = (useSSL ? 'https:' : 'http:') + '//' + host + '/' + options.uploadPath + '/';
  this.url = this.delete_url = baseUrl + encodeURIComponent(this.name);

  if (!_(['public-read', 'public-read-write']).include(options.policy)) {
    var key = options.uploadPath + '/' + encodeURIComponent(this.name);
    this.url = signedUrl(key, options)
  }
}

function signedUrl(key, options) {
  var credentials = new AWS.Credentials(process.env.AWS_KEY, process.env.AWS_SECRET),
      awsS3 = new AWS.S3(credentials)

  return awsS3.getSignedUrl('getObject', {Bucket: options.bucket, Key: key, Expires: options.signedUrlExpiration})
}

function setDefaultHeaders(response) {
  response.setHeader(
    'Access-Control-Allow-Origin',
    app.get('access-control').allowOrigin
  );

  response.setHeader(
    'Access-Control-Allow-Methods',
    app.get('access-control').allowMethods
  );
}

function contentTypeFor(request) {
  return request.headers.accept.indexOf('application/json') !== -1 ? 'application/json' : 'text/plain';
}

function handleResult(request, response, result, redirect) {
  var jsonResult = JSON.stringify(result)

  if (redirect) {
    var redirectURL = redirect.replace(/%s/, encodeURIComponent(jsonResult));
    log('Redirecting to ' + redirectURL)
    response.redirect(redirectURL)
  } else {
    log('Returning content ' + jsonResult)
    setDefaultHeaders(response);

    response.set('Content-Type', contentTypeFor(request));
    response.send(jsonResult);
  }
}

function validToken(value) {
  var secret           = app.get('secret'),
      expiration       = app.get('secret_expiration'),
      currentTimestamp = Math.round(new Date().getTime()/1000),
      timestamp        = parseInt(value.substr(0,  10), 10),
      random           = value.substr(10, 10),
      hash             = value.substr(20),
      shasum           = crypto.createHash('sha1')

  shasum.update('' + timestamp + random + secret)
  digest = shasum.digest('hex')

  return timestamp + expiration > currentTimestamp && hash == digest
}

// Configuration

app.configure('development', function(){
  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
  app.use(express.logger());

  app.set('debug', true);
});

app.configure('production', function(){
  app.use(express.errorHandler());
  app.set('debug', false);
});

// Routes

app.get('/status', function(request, response) {
  setDefaultHeaders(response);

  if (request.query.file) {
    db.get(request.query.file, function (error, redis_response) {
      error = redis_response == 'error' ? 'upload-failed' : error

      if (!error) {
        // key is deleted when upload is finished, so it should have an empty response
        response.jsonp({finished_uploading: _(redis_response).isEmpty()})
      } else {
        response.jsonp(500, [{error: error}])
      }
    })
  } else {
    response.jsonp(400, [{error: 'You must pass a file parameter.'}])
  }
});

app.options('/upload', function (request, response) {
  setDefaultHeaders(response);
  response.end();
});

app.post('/upload', function(request, response) {
  var form          = new formidable.IncomingForm(),
      secret        = app.get('secret'),
      valid         = _(secret).isUndefined(),
      expireUpload  = 10 * 24 * 60 * 60, // 10 days
      channelName   = app.get('pusherChannelName')
      uploadOptions = {
        bucket:              app.get('bucket'),
        uploadPath:          app.get('upload-path'),
        policy:              app.get('policy'),
        useSSL:              app.get('use-ssl'),
        signedUrlExpiration: app.get('signed-url-expiration')
      };

  log('starting upload');

  form.maxFieldsSize = 20 * 1024 * 1024; // 20mb
  form.keepExtensions = true;

  form.on('file', function(name, file) {
    if (!valid) {
      log('Invalid token given, not uploading file to amazon')
      return
    }

    var startTime = new Date(),
        filename  = XRegExp.replace(file.name, AsciiOnlyRegexp, ''),
        options = {
          BucketName : uploadOptions.bucket,
          ObjectName : uploadOptions.uploadPath + '/' + filename,
          ContentLength : file.size,
          ContentType: file.type,
          Body : fs.createReadStream(file.path),
          Acl: uploadOptions.policy
        },
        fileInfo = new FileInfo(file, uploadOptions)

    log("%s uploaded successfully, sending it to to the %s bucket in s3", filename, uploadOptions.bucket);

    log('Public file URL: %s', fileInfo.url);

    db.setex(filename, expireUpload, JSON.stringify(fileInfo));

    log('Sending file with options: ', options)
    s3.PutObject(options, function(errors, data) {
      var elapsed = (new Date() - startTime) / 1000;

      log('S3 upload complete in %s seconds.', elapsed);
      log('Errors: ', errors);
      log('Data: ', data);

      if (errors) {
        db.setex(filename, expireUpload, 'error');
      } else {
        db.del(filename);
      }

      fs.unlink(file.path);

      if (pusher) {
        if (errors) {
          log('amazon upload failed, triggering upload-failed on pusher for ' + filename);
          pusher.trigger(channelName, 'upload-failed', errors);
        } else {
          log('amazon upload completed, triggering upload-completed on pusher for ' + filename)
          pusher.trigger(channelName, 'upload-completed', JSON.stringify(file));
        }
      }
    });
  });

  form.on('error', function(err) {
    log('formidable error', err);
  });

  form.on('field', function(name, value) {
    log('field received: %s=%s', name, value);

    if (secret && name == 'token') {
      valid = validToken(value)
    } else if (name == 'channel') {
      channelName = value
    } else {
      uploadOptions[name] = value // allows upload parameter to be overridden on a per-upload basis
    }
  });

  form.on('end', function() {
    log('formidable end');
  });

  form.parse(request, function(err, fields, files) {
    if (valid) {
      var filesInfo = _(files).map(function (file) {
        return new FileInfo(file, uploadOptions)
      });

      handleResult(request, response, filesInfo, fields.redirect)
    } else {
      response.jsonp(403, [{error: 'Forbidden'}])
    }
  });
});

app.listen(app.get('port'));
console.log("Express server listening on port %d in %s mode", app.get('port'), app.settings.env);
<|endoftext|>


<commit_before>import { SOURCE_FILTER_BY_SNAPSHOT, SOURCE_FILTER_BY_TIMESPAN } from '../../../actions/sourceActions';

const INITIAL_STATE = {
  snapshotId: null,
  timespanId: null,
};

function info(state = INITIAL_STATE, action) {
  switch (action.type) {
    case SOURCE_FILTER_BY_SNAPSHOT:
      return Object.assign({}, state, {
        ...state,
        snapshotId: parseInt(action.payload.id, 10),
      });
    case SOURCE_FILTER_BY_TIMESPAN:
      return Object.assign({}, state, {
        ...state,
        timespanId: parseInt(action.payload.id, 10),
      });
    default:
      return state;
  }
}

export default info;
<commit_msg>remove unused file from sources<commit_after><|endoftext|>


<commit_before>import React from 'react';

import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import * as charActions from '../../actions/charGenActions';
import * as apiActions from '../../actions/apiActions';

import TextInput from '../../components/textInput';
import SelectList from '../../components/selectList';

class BasicsPage extends React.Component {

    componentDidMount(){
        const {actions} = this.props;
        actions.apiActions.fetchDataIfNeeded('alignments');
        actions.apiActions.fetchDataIfNeeded('classes');
        actions.apiActions.fetchDataIfNeeded('races');
    }

    render() {

        const {actions, character, alignments, races, classes} = this.props;

        return (

                <TextInput
                    labelText="Name: "
                    value={character.name}
                    id="name"
                    name="name"
                    onChange={(e) => actions.charActions.updateValue('name', e.target.value)}
                    />

                <SelectList
                    labelText="Race: "
                    id="race"
                    name="selectedRace"
                    options={races || [{id: '123', name: 'Loading'}]}
                    value={character.race.id}
                    onChange={(e) => actions.charActions.fetchCharData('race', `races/${e.target.value}`)}
                />

                <SelectList
                    labelText="Class: "
                    id="class"
                    options={classes || [{id: '123', name: 'Loading'}]}
                    name="selectedClass"
                    onChange={(e) => actions.charActions.fetchCharData('class', `races/${e.target.value}`,character.level)}
                    value={character.class.id}
                />

                <SelectList
                    labelText="Alignment: "
                    id="alignment"
                    options={alignments || [{id: '123', name: 'Loading'}]}
                    onChange={(e) => actions.charActions.updateValue('alignment', e.target.value)}
                    value={character.alignment}
                />

            );
        }
    }

BasicsPage.propTypes = {
  actions: React.PropTypes.object.isRequired,
  alignments: React.PropTypes.array,
  character: React.PropTypes.object.isRequired,
  classes: React.PropTypes.array,
  races: React.PropTypes.array
};

function mapStateToProps(state){
    return {
        alignments: state.api.alignments,
        character: state.character,
        classes: state.api.classes,
        races: state.api.races
    };
}

function mapDispatchToProps(dispatch) {
    return {
        actions: {
            apiActions: bindActionCreators(apiActions, dispatch),
            charActions: bindActionCreators(charActions, dispatch)
        }
    };
}

export default connect(
    mapStateToProps,
    mapDispatchToProps
)(BasicsPage);
<commit_msg>switch races to class<commit_after>import React from 'react';

import {connect} from 'react-redux';
import {bindActionCreators} from 'redux';
import * as charActions from '../../actions/charGenActions';
import * as apiActions from '../../actions/apiActions';

import TextInput from '../../components/textInput';
import SelectList from '../../components/selectList';

class BasicsPage extends React.Component {

    componentDidMount(){
        const {actions} = this.props;
        actions.apiActions.fetchDataIfNeeded('alignments');
        actions.apiActions.fetchDataIfNeeded('classes');
        actions.apiActions.fetchDataIfNeeded('races');
    }

    render() {

        const {actions, character, alignments, races, classes} = this.props;

        return (

                <TextInput
                    labelText="Name: "
                    value={character.name}
                    id="name"
                    name="name"
                    onChange={(e) => actions.charActions.updateValue('name', e.target.value)}
                    />

                <SelectList
                    labelText="Race: "
                    id="race"
                    name="selectedRace"
                    options={races || [{id: '123', name: 'Loading'}]}
                    value={character.race.id}
                    onChange={(e) => actions.charActions.fetchCharData('race', `races/${e.target.value}`)}
                />

                <SelectList
                    labelText="Class: "
                    id="class"
                    options={classes || [{id: '123', name: 'Loading'}]}
                    name="selectedClass"
                    onChange={(e) => actions.charActions.fetchCharData('class', `classes/${e.target.value}`,character.level)}
                    value={character.class.id}
                />

                <SelectList
                    labelText="Alignment: "
                    id="alignment"
                    options={alignments || [{id: '123', name: 'Loading'}]}
                    onChange={(e) => actions.charActions.updateValue('alignment', e.target.value)}
                    value={character.alignment}
                />

            );
        }
    }

BasicsPage.propTypes = {
  actions: React.PropTypes.object.isRequired,
  alignments: React.PropTypes.array,
  character: React.PropTypes.object.isRequired,
  classes: React.PropTypes.array,
  races: React.PropTypes.array
};

function mapStateToProps(state){
    return {
        alignments: state.api.alignments,
        character: state.character,
        classes: state.api.classes,
        races: state.api.races
    };
}

function mapDispatchToProps(dispatch) {
    return {
        actions: {
            apiActions: bindActionCreators(apiActions, dispatch),
            charActions: bindActionCreators(charActions, dispatch)
        }
    };
}

export default connect(
    mapStateToProps,
    mapDispatchToProps
)(BasicsPage);
<|endoftext|>


<commit_before>const http = require('http')
const path = require('path')
const fs = require('fs')
const express = require('express')
const favicon = require('serve-favicon')
const cookieParser = require('cookie-parser')
const bodyParser = require('body-parser')
const proxyMiddleware = require('http-proxy-middleware')
const md = new require('markdown-it')()
const opn = require('opn')
const body = require('stream-body')

const config = require('./config')

let app = express()

// allow cross-origin ajax request
app.all('*', (req, res, next) => {
  res.header('Access-Control-Allow-Origin', '*')
  res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept')
  res.header('Access-Control-Allow-Methods','PUT,POST,GET,DELETE,OPTIONS')
  next()
})

// app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')))

// enable uploading large file
app.use(bodyParser.json({ limit: '100000kb' }))
app.use(bodyParser.urlencoded({
  extended: false,
  limit: '100000kb'
}))

app.use(cookieParser())
app.use(config.root, express.static(path.join(__dirname, 'public'), {
  index: 'index.html',
  maxAge: 60 * 60 * 1000
}))

app.get('/yakima', (req, res, next) => {
  fs.readFile(path.join(__dirname, 'README.md'), (err, data) => {
    if (err) {
      console.log(err)
      return
    }
    const readme = md.render(data.toString())
    res.send(readme)
  })
})

// proxy api requests
Object.keys(config.proxyTable).forEach(context => {
  let options = config.proxyTable[context]
  options = {
    target: options,
    changeOrigin: true,
    onProxyRes (proxyRes, req, res) {
      body.parse(proxyRes, (err, data) => {
        const fileName = `${req.url.split('#')[0].split('?')[0].split(/^\//)[1].replace(/\//g, '-')}.json`
        fs.writeFile(path.join(__dirname, 'mock', 'proxy', fileName), JSON.stringify(data, null, 2), err => {
          if (err) { console.log(err) }
        })
      })
    }
  }
  app.use(proxyMiddleware(context, options))
})

// response row json file content
Object.keys(config.jsonTable).forEach(context => {
  let fileName = config.jsonTable[context]
  app.all(context, (req, res, next) => {
    fs.readFile(path.join(__dirname, 'mock', 'json', `${fileName}.json`), (err, data) => {
      if (err) {
        console.log(err)
        return
      }
      res.json(JSON.parse(data.toString()))
    })
  })
})

// response custom content
Object.keys(config.customTable).forEach(context => {
  app.all(context, (req, res, next) => {
    const filePathAndName = path.join(__dirname, 'mock', 'custom', `${config.customTable[context]}.js`)
    const content = require(filePathAndName)
    res.json(content)
  })
})

// catch 404 and forward to error handler
app.use((req, res, next) => {
  var err = new Error('Not Found')
  err.status = 404
  next(err)
})

// error handlers (will print stacktrace)
app.use((err, req, res, next) => {
  res.status(err.status || 500).json({
    message: err.message,
    error: err
  })
})

app.set('port', config.port)

const server = http.createServer(app)

server.listen(config.port)
server.on('error', onError)
server.on('listening', onListening)

// Event listener for HTTP server "error" event.
function onError (error) {
  if (error.syscall !== 'listen') {
    throw error
  }

  const bind = typeof port === 'string'
    ? 'Pipe ' + port
    : 'Port ' + port

  // handle specific listen errors with friendly messages
  switch (error.code) {
    case 'EACCES':
      console.error(bind + ' requires elevated privileges')
      process.exit(1)
      break
    case 'EADDRINUSE':
      console.error(bind + ' is already in use')
      process.exit(1)
      break
    default:
      throw error
  }
}

// Event listener for HTTP server "listening" event.
function onListening () {
  const addr = server.address()
  const bind = typeof addr === 'string'
    ? 'pipe ' + addr
    : 'port ' + addr.port
  console.log('Listening on ' + bind)
  // opn(`http://localhost:${config.port}/yakima`)
}
<commit_msg>prints request info in server-end console<commit_after>const http = require('http')
const path = require('path')
const fs = require('fs')
const express = require('express')
const favicon = require('serve-favicon')
const cookieParser = require('cookie-parser')
const bodyParser = require('body-parser')
const proxyMiddleware = require('http-proxy-middleware')
const md = new require('markdown-it')()
const opn = require('opn')
const body = require('stream-body')

const config = require('./config')

let app = express()

// allow cross-origin ajax request
app.all('*', (req, res, next) => {
  console.log(`[${req.method}] ${req.url} ${new Date()}`)
  res.header('Access-Control-Allow-Origin', '*')
  res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept')
  res.header('Access-Control-Allow-Methods','PUT,POST,GET,DELETE,OPTIONS')
  next()
})

// app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')))

// enable uploading large file
app.use(bodyParser.json({ limit: '100000kb' }))
app.use(bodyParser.urlencoded({
  extended: false,
  limit: '100000kb'
}))

app.use(cookieParser())
app.use(config.root, express.static(path.join(__dirname, 'public'), {
  index: 'index.html',
  maxAge: 60 * 60 * 1000
}))

app.get('/yakima', (req, res, next) => {
  fs.readFile(path.join(__dirname, 'README.md'), (err, data) => {
    if (err) {
      console.log(err)
      return
    }
    const readme = md.render(data.toString())
    res.send(readme)
  })
})

// proxy api requests
Object.keys(config.proxyTable).forEach(context => {
  let options = config.proxyTable[context]
  options = {
    target: options,
    changeOrigin: true,
    onProxyRes (proxyRes, req, res) {
      body.parse(proxyRes, (err, data) => {
        const fileName = `${req.url.split('#')[0].split('?')[0].split(/^\//)[1].replace(/\//g, '-')}.json`
        fs.writeFile(path.join(__dirname, 'mock', 'proxy', fileName), JSON.stringify(data, null, 2), err => {
          if (err) { console.log(err) }
        })
      })
    }
  }
  app.use(proxyMiddleware(context, options))
})

// response row json file content
Object.keys(config.jsonTable).forEach(context => {
  let fileName = config.jsonTable[context]
  app.all(context, (req, res, next) => {
    fs.readFile(path.join(__dirname, 'mock', 'json', `${fileName}.json`), (err, data) => {
      if (err) {
        console.log(err)
        return
      }
      res.json(JSON.parse(data.toString()))
    })
  })
})

// response custom content
Object.keys(config.customTable).forEach(context => {
  app.all(context, (req, res, next) => {
    const filePathAndName = path.join(__dirname, 'mock', 'custom', `${config.customTable[context]}.js`)
    const content = require(filePathAndName)
    res.json(content)
  })
})

// catch 404 and forward to error handler
app.use((req, res, next) => {
  var err = new Error('Not Found')
  err.status = 404
  next(err)
})

// error handlers (will print stacktrace)
app.use((err, req, res, next) => {
  res.status(err.status || 500).json({
    message: err.message,
    error: err
  })
})

app.set('port', config.port)

const server = http.createServer(app)

server.listen(config.port)
server.on('error', onError)
server.on('listening', onListening)

// Event listener for HTTP server "error" event.
function onError (error) {
  if (error.syscall !== 'listen') {
    throw error
  }

  const bind = typeof port === 'string'
    ? 'Pipe ' + port
    : 'Port ' + port

  // handle specific listen errors with friendly messages
  switch (error.code) {
    case 'EACCES':
      console.error(bind + ' requires elevated privileges')
      process.exit(1)
      break
    case 'EADDRINUSE':
      console.error(bind + ' is already in use')
      process.exit(1)
      break
    default:
      throw error
  }
}

// Event listener for HTTP server "listening" event.
function onListening () {
  const addr = server.address()
  const bind = typeof addr === 'string'
    ? 'pipe ' + addr
    : 'port ' + addr.port
  console.log('Listening on ' + bind)
  // opn(`http://localhost:${config.port}/yakima`)
}

<|endoftext|>


<commit_before>// TODO:
// move typedWord from state to redux store, and possibly other
// state variables as well

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

import TypingTest from '../components/typing-test';
import * as actions from '../actions/action-creators';

class TypingTestContainer extends Component {
  constructor() {
    super();
    this.start = this.start.bind(this);
    this.stop = this.stop.bind(this);
    this.handleKeyPress = this.handleKeyPress.bind(this);
    this.WPM = this.WPM.bind(this);

    /* We keep in local state everything related to the actual act of typing.
     * Anything else (typing test level and word-level things)
     * will be in redux store.
     *
     * TODO: move raw text to redux store
     */
    this.state = {
      text: "some and all will be program totally in last man standing" +
      " having to type is life begin in man totally casting sun in three of" +
      " the most up",
      words: [],
      typedWord: "", // The current word as typed by the user
      totalWords: 0,
      currentChar: 0,
      correctCharCount: 0,
      wrongCharCount: 0,
    };
  }

  componentDidMount() {
    this.props.setText(this.state.text);
    // Transform the text into on array of words
    this.setState({words: this.state.text.split(" ")});
    this.setState({ minutes: 1, seconds: 0 });
  }

  start() {
    this.props.start(Date.now(), this.state.text);
  }

  stop() {
    this.props.stop(Date.now(), this.WPM());
  }

  WPM() {
  }

  handleKeyPress(e) {
    if (this.props.typingTest.finished)
      return;

    let {
      typedWord,
      totalWords,
      currentChar,
      correctCharCount,
      wrongCharCount } = this.state;

    // Get the word that's currently being typed
    const word = this.state.words[totalWords];

    if (e.key === "Tab")
      e.preventDefault();

    if (e.key === 'Shift' || e.key === 'Ctrl' || e.key === 'Alt')
      return;

    // Start when typing starts
    if (!this.props.typingTest.inProgress &&
      e.key !== 'Shift' &&
      e.key !== 'Tab')
      this.start();

    // Handle backspace
    if (e.key === 'Backspace') {
      // Already at the beginning of the current word?
      if (currentChar === 0)
        return;

      // Is this a correct char?
      if (this.state.typedWord[currentChar - 1] === word[currentChar - 1])
        correctCharCount--;
      else
        wrongCharCount--;

      currentChar--;
      typedWord = typedWord.slice(0, typedWord.length - 1);
    } else if (e.key === ' ' || e.key === 'Enter') {
      // Count as incorrectly typed characters any letters that were
      // left to type
      wrongCharCount += word.length - currentChar;

      // If this was the last word, then stop the typing test,
      // otherwise move on to next word
      typedWord = "";
      totalWords++;
      currentChar = 0;
      if (totalWords === this.state.words.length)
        this.stop();
      else
        this.props.wordTyped();
    } else {
      // Handle other keys than backspace, space or enter
      if (e.key.length > 1)
        return; // Discard non-alphanumeric

      if (e.key === word[currentChar])
        correctCharCount++;
      else
        wrongCharCount++;

      currentChar++;
      typedWord += e.key;

      // Stop if all the text was typed (the last character being correct)
      if (this.state.totalWords === this.state.words.length - 1 &&
        typedWord.length === word.length &&
        typedWord.slice(-1) === word.slice(-1)) {

          this.stop();
        }
    }
    this.setState({
      correctCharCount,
      wrongCharCount,
      currentChar,
      typedWord,
      totalWords,
    });
  }

  render() {
    return(
      <TypingTest
        typingTest={this.props.typingTest}
        typedWord={this.state.typedWord}
        correctChars={this.state.correctCharCount}
        wrongChars={this.state.wrongCharCount}
        onKeyPress={this.handleKeyPress}/>
    );
  }
}

function mapStateToProps(state) {
  return { typingTest: state.typingTest };
}

function mapDispatchToProps(dispatch) {
  return bindActionCreators(actions, dispatch);
}

export default connect(mapStateToProps, mapDispatchToProps)(TypingTestContainer);
<commit_msg>generate random words for content<commit_after>// TODO:
// move typedWord from state to redux store, and possibly other
// state variables as well

import React, { Component } from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';

import TypingTest from '../components/typing-test';
import * as actions from '../actions/action-creators';

const corpus =
  "the be to of and a in that have I it for not on with" +
  " he as you do at this but his by from they we say her she or an will my one" +
  " all would there their what so up out if about who get which go me when" +
  " make can like time no just him know take people into year your good some" +
  " could them see other than then now look only come its over think also" +
  " back after use two how our work first well way even new want because any" +
  " these give day most us";

const wordList = corpus.split(" ");

class TypingTestContainer extends Component {
  constructor() {
    super();
    this.start = this.start.bind(this);
    this.stop = this.stop.bind(this);
    this.handleKeyPress = this.handleKeyPress.bind(this);
    this.WPM = this.WPM.bind(this);

    /* We keep in local state everything related to the actual act of typing.
     * Anything else (typing test level and word-level things)
     * will be in redux store.
     *
     * TODO: move raw text to redux store
     */
    this.state = {
      text: "some and all will be program totally in last man standing" +
      " having to type is life begin in man totally casting sun in three of" +
      " the most up",
      words: [],
      typedWord: "", // The current word as typed by the user
      totalWords: 0,
      currentChar: 0,
      correctCharCount: 0,
      wrongCharCount: 0,
    };
  }

  componentDidMount() {
    //this.props.setText(this.state.text);
    // Transform the text into on array of words
    //this.setState({words: this.state.text.split(" ")});
    const text = this.getRandomText.bind(this)(50);
    this.setState({ text });
    this.setState({ words: text.split(" ") },
      function() { this.props.setText(this.state.text) }
    );

    this.setState({ minutes: 1, seconds: 0 });
  }

  getRandomText(n) {
    // arg n: how many words to generate
    const rand = (min, max) => Math.floor(Math.random() * (max - min)) + min;
    let text = [];
    for (let i = 0; i < n; i++) {
      text.push(wordList[rand(0, wordList.length)]);
    }
    return text.join(" ");
  }

  start() {
    this.props.start(Date.now(), this.state.text);
  }

  stop() {
    this.props.stop(Date.now(), this.WPM());
  }

  WPM() {
  }

  handleKeyPress(e) {
    if (this.props.typingTest.finished)
      return;

    let {
      typedWord,
      totalWords,
      currentChar,
      correctCharCount,
      wrongCharCount } = this.state;

    // Get the word that's currently being typed
    const word = this.state.words[totalWords];

    if (e.key === "Tab")
      e.preventDefault();

    if (e.key === 'Shift' || e.key === 'Ctrl' || e.key === 'Alt')
      return;

    // Start when typing starts
    if (!this.props.typingTest.inProgress &&
      e.key !== 'Shift' &&
      e.key !== 'Tab')
      this.start();

    // Handle backspace
    if (e.key === 'Backspace') {
      // Already at the beginning of the current word?
      if (currentChar === 0)
        return;

      // Is this a correct char?
      if (this.state.typedWord[currentChar - 1] === word[currentChar - 1])
        correctCharCount--;
      else
        wrongCharCount--;

      currentChar--;
      typedWord = typedWord.slice(0, typedWord.length - 1);
    } else if (e.key === ' ' || e.key === 'Enter') {
      // Count as incorrectly typed characters any letters that were
      // left to type
      wrongCharCount += word.length - currentChar;

      // If this was the last word, then stop the typing test,
      // otherwise move on to next word
      typedWord = "";
      totalWords++;
      currentChar = 0;
      if (totalWords === this.state.words.length)
        this.stop();
      else
        this.props.wordTyped();
    } else {
      // Handle other keys than backspace, space or enter
      if (e.key.length > 1)
        return; // Discard non-alphanumeric

      if (e.key === word[currentChar])
        correctCharCount++;
      else
        wrongCharCount++;

      currentChar++;
      typedWord += e.key;

      // Stop if all the text was typed (the last character being correct)
      if (this.state.totalWords === this.state.words.length - 1 &&
        typedWord.length === word.length &&
        typedWord.slice(-1) === word.slice(-1)) {

          this.stop();
        }
    }
    this.setState({
      correctCharCount,
      wrongCharCount,
      currentChar,
      typedWord,
      totalWords,
    });
  }

  render() {
    return(
      <TypingTest
        typingTest={this.props.typingTest}
        typedWord={this.state.typedWord}
        correctChars={this.state.correctCharCount}
        wrongChars={this.state.wrongCharCount}
        onKeyPress={this.handleKeyPress}/>
    );
  }
}

function mapStateToProps(state) {
  return { typingTest: state.typingTest };
}

function mapDispatchToProps(dispatch) {
  return bindActionCreators(actions, dispatch);
}

export default connect(mapStateToProps, mapDispatchToProps)(TypingTestContainer);
<|endoftext|>


<commit_before><commit_msg>it was conflicted with another app that use process.env.PORT=8080<commit_after><|endoftext|>


<commit_before>const path = require('path')

const colors = require('material-ui/styles/colors')
const electron = require('electron')
const React = require('react')
const PropTypes = require('prop-types')

const remote = electron.remote

const RaisedButton = require('material-ui/RaisedButton').default
const TextField = require('material-ui/TextField').default

// Lets you pick a file or directory.
// Uses the system Open File dialog.
// You can't edit the text field directly.
class PathSelector extends React.Component {
  static propTypes () {
    return {
      className: PropTypes.string,
      dialog: PropTypes.object,
      id: PropTypes.string,
      onChange: PropTypes.func,
      title: PropTypes.string.isRequired,
      value: PropTypes.string
    }
  }

  constructor (props) {
    super(props)
    this.handleClick = this.handleClick.bind(this)
  }

  handleClick () {
    const opts = Object.assign({
      defaultPath: this.props.value && path.dirname(this.props.value),
      properties: ['openFile', 'openDirectory']
    }, this.props.dialog)

    remote.dialog.showOpenDialog(
      remote.getCurrentWindow(),
      opts,
      (filenames) => {
        if (!Array.isArray(filenames)) return
        this.props.onChange && this.props.onChange(filenames[0])
      }
    )
  }

  render () {
    const id = this.props.title.replace(' ', '-').toLowerCase()
    const wrapperStyle = {
      alignItems: 'center',
      display: 'flex',
      width: '100%'
    }
    const labelStyle = {
      flex: '0 auto',
      marginRight: 10,
      overflow: 'hidden',
      textOverflow: 'ellipsis',
      whiteSpace: 'nowrap'
    }
    const textareaStyle = {
      color: colors.grey50
    }
    const textFieldStyle = {
      flex: '1'
    }
    const text = this.props.value || ''
    const buttonStyle = {
      marginLeft: 10
    }

    return (

          {this.props.title}:

        <TextField
          className='control' disabled id={id} value={text}
          inputStyle={textareaStyle} style={textFieldStyle}
        />
        <RaisedButton
          className='control' label='Change' onClick={this.handleClick}
          style={buttonStyle}
        />

    )
  }
}

module.exports = PathSelector
<commit_msg>Handle null paths in path selector (#1704)<commit_after>const path = require('path')

const colors = require('material-ui/styles/colors')
const electron = require('electron')
const React = require('react')
const PropTypes = require('prop-types')

const remote = electron.remote

const RaisedButton = require('material-ui/RaisedButton').default
const TextField = require('material-ui/TextField').default

// Lets you pick a file or directory.
// Uses the system Open File dialog.
// You can't edit the text field directly.
class PathSelector extends React.Component {
  static propTypes () {
    return {
      className: PropTypes.string,
      dialog: PropTypes.object,
      id: PropTypes.string,
      onChange: PropTypes.func,
      title: PropTypes.string.isRequired,
      value: PropTypes.string
    }
  }

  constructor (props) {
    super(props)
    this.handleClick = this.handleClick.bind(this)
  }

  handleClick () {
    const opts = Object.assign({
      defaultPath: path.dirname(this.props.value || ''),
      properties: ['openFile', 'openDirectory']
    }, this.props.dialog)

    remote.dialog.showOpenDialog(
      remote.getCurrentWindow(),
      opts,
      (filenames) => {
        if (!Array.isArray(filenames)) return
        this.props.onChange && this.props.onChange(filenames[0])
      }
    )
  }

  render () {
    const id = this.props.title.replace(' ', '-').toLowerCase()
    const wrapperStyle = {
      alignItems: 'center',
      display: 'flex',
      width: '100%'
    }
    const labelStyle = {
      flex: '0 auto',
      marginRight: 10,
      overflow: 'hidden',
      textOverflow: 'ellipsis',
      whiteSpace: 'nowrap'
    }
    const textareaStyle = {
      color: colors.grey50
    }
    const textFieldStyle = {
      flex: '1'
    }
    const text = this.props.value || ''
    const buttonStyle = {
      marginLeft: 10
    }

    return (

          {this.props.title}:

        <TextField
          className='control' disabled id={id} value={text}
          inputStyle={textareaStyle} style={textFieldStyle}
        />
        <RaisedButton
          className='control' label='Change' onClick={this.handleClick}
          style={buttonStyle}
        />

    )
  }
}

module.exports = PathSelector
<|endoftext|>


<commit_before>/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

"use strict";

var EXPORTED_SYMBOLS = [ "ThinBookmarks" ];

Components.utils.import("resource://gre/modules/Services.jsm");

Components.utils.import("chrome://thinbookmarks/content/javascript/sfab/CSSBuilder.js");
Components.utils.import("chrome://thinbookmarks/content/javascript/sfab/DynamicStyleSheets.js");
Components.utils.import("chrome://thinbookmarks/content/javascript/sfab/Preferences.js");

var ThinBookmarks = {
	preferences : new Preferences(),

	styleSheets : new DynamicStyleSheets(),

	destroy : function() {
		this.preferences.destroy();
		this.styleSheets.unregisterAll();
	},

	init : function() {
		this.styleSheets.init();
		this.styleSheets.registerPath("main", "resource://thinbookmarks/content/css/main.css");

		this.preferences.init("extensions.org.bonsaimind.thinbookmarks.");

		this.initPreferences();
	},

	initPreferences : function() {
		var _this = this;

		this.preferences.registerBool("bookmarks.icon.hide", false, function(name, value) {
			if (value) {
				var css = new CSSBuilder("#PlacesToolbarItems > .bookmark-item:not([type]) > .toolbarbutton-icon")
						.hide();
				_this.styleSheets.registerForBrowser(name, css.toCSS());
			} else {
				_this.styleSheets.unregister(name);
			}
		});
		this.preferences.registerBool("bookmarks.text.hide", true, function(name, value) {
			if (value) {
				var css = new CSSBuilder("#PlacesToolbarItems > .bookmark-item:not([type]) > .toolbarbutton-text")
						.hide();
				_this.styleSheets.registerForBrowser(name, css.toCSS());
			} else {
				_this.styleSheets.unregister(name);
			}
		});

		this.preferences.registerBool("dropdown.hideopenallintabs", false, function(name, value) {
			if (value) {
				var css = new CSSBuilder("#PlacesToolbarItems menuitem[class=\"openintabs-menuitem\"]")
				css = css.addSelector("#PlacesToolbarItems menuseparator[class=\"bookmarks-actions-menuseparator\"]");
				css = css.hide();
				_this.styleSheets.registerForBrowser(name, css.toCSS());
			} else {
				_this.styleSheets.unregister(name);
			}
		});
		this.preferences.registerInt("dropdown.minwidth", 0, function(name, value) {
			var css = new CSSBuilder("#PlacesToolbarItems scrollbox").minWidth(value);
			_this.styleSheets.registerForBrowser(name, css.toCSS());
		});
		this.preferences.registerBool("dropdown.scrollbar", false, function(name, value) {
			if (value) {
				var cssScrollbox = new CSSBuilder("#PlacesToolbarItems scrollbox").add("overflow-y", "auto");
				var cssButton = new CSSBuilder("#PlacesToolbarItems autorepeatbutton").hide();
				_this.styleSheets.registerForBrowser(name, cssScrollbox.toCSS() + cssButton.toCSS());
			} else {
				_this.styleSheets.unregister(name);
			}
		});

		this.preferences
				.registerBool("folders.dropdown.hide", true, function(name, value) {
					if (value) {
						var css = new CSSBuilder("#PlacesToolbarItems > .bookmark-item[type=menu] > .toolbarbutton-menu-dropmarker")
								.hide();
						_this.styleSheets.registerForBrowser(name, css.toCSS());
					} else {
						_this.styleSheets.unregister(name);
					}
				});
		this.preferences.registerBool("folders.icon.hide", true, function(name, value) {
			if (value) {
				var css = new CSSBuilder("#PlacesToolbarItems > .bookmark-item[type=menu] > .toolbarbutton-icon")
						.hide();
				_this.styleSheets.registerForBrowser(name, css.toCSS());
			} else {
				_this.styleSheets.unregister(name);
			}
		});
		this.preferences.registerBool("folders.text.hide", false, function(name, value) {
			if (value) {
				var css = new CSSBuilder("#PlacesToolbarItems > .bookmark-item[type=menu] > .toolbarbutton-text")
						.hide();
				_this.styleSheets.registerForBrowser(name, css.toCSS());
			} else {
				_this.styleSheets.unregister(name);
			}
		});

		this.preferences.registerInt("height", 24, function(name, value) {
			var css = new CSSBuilder("#PersonalToolbar").addSelector("#PlacesToolbar")
			css = css.addSelector("#PersonalToolbar > *").addSelector("#PlacesToolbarItems > *")
			css = css.forceHeight(value);

			var cssChildren = new CSSBuilder("#PersonalToolbar > *").addSelector("#PlacesToolbarItems > *")
			cssChildren = cssChildren.maxHeight(value);

			_this.styleSheets.registerForBrowser(name, css.toCSS());
			_this.styleSheets.registerForBrowser(name + ".children", cssChildren.toCSS());
		});

		this.preferences.registerInt("items.icon.padding.bottom", -7, function(name, value) {
			var css = new CSSBuilder("#PlacesToolbarItems > .bookmark-item > .toolbarbutton-icon")
					.autoPadding("bottom", value);
			_this.styleSheets.registerForBrowser(name, css.toCSS());
		});
		this.preferences.registerInt("items.icon.padding.top", -7, function(name, value) {
			var css = new CSSBuilder("#PlacesToolbarItems > .bookmark-item > .toolbarbutton-icon")
					.autoPadding("top", value);
			_this.styleSheets.registerForBrowser(name, css.toCSS());
		});
		this.preferences.registerInt("items.padding", 0, function(name, value) {
			var css = new CSSBuilder("#PlacesToolbarItems > .bookmark-item").margin("right", value);
			_this.styleSheets.registerForBrowser(name, css.toCSS());
		});
		this.preferences.registerInt("items.text.padding.top", -1, function(name, value) {
			var css = new CSSBuilder("#PlacesToolbarItems > .bookmark-item > .toolbarbutton-text")
					.autoPadding("top", value);
			_this.styleSheets.registerForBrowser(name, css.toCSS());
		});

		this.preferences.registerInt("padding.bottom", 0, function(name, value) {
			var css = new CSSBuilder("#PlacesToolbarItems").autoPadding("bottom", value);
			_this.styleSheets.registerForBrowser(name, css.toCSS());
		});
		this.preferences.registerInt("padding.top", 0, function(name, value) {
			var css = new CSSBuilder("#PlacesToolbarItems").autoPadding("top", value);
			_this.styleSheets.registerForBrowser(name, css.toCSS());
		});
	}
};
<commit_msg>Formattng and whitespace.<commit_after>/*
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
 */

"use strict";

var EXPORTED_SYMBOLS = [ "ThinBookmarks" ];

Components.utils.import("resource://gre/modules/Services.jsm");

Components.utils.import("chrome://thinbookmarks/content/javascript/sfab/CSSBuilder.js");
Components.utils.import("chrome://thinbookmarks/content/javascript/sfab/DynamicStyleSheets.js");
Components.utils.import("chrome://thinbookmarks/content/javascript/sfab/Preferences.js");

var ThinBookmarks = {
	preferences : new Preferences(),

	styleSheets : new DynamicStyleSheets(),

	destroy : function() {
		this.preferences.destroy();
		this.styleSheets.unregisterAll();
	},

	init : function() {
		this.styleSheets.init();
		this.styleSheets.registerPath("main", "resource://thinbookmarks/content/css/main.css");

		this.preferences.init("extensions.org.bonsaimind.thinbookmarks.");

		this.initPreferences();
	},

	initPreferences : function() {
		// We need to extras this into a local variable to make it available
		// to the callbacks.
		var _this = this;

		this.preferences.registerBool("bookmarks.icon.hide", false, function(name, value) {
			if (value) {
				var css = new CSSBuilder("#PlacesToolbarItems > .bookmark-item:not([type]) > .toolbarbutton-icon");
				css = css.hide();
				_this.styleSheets.registerForBrowser(name, css.toCSS());
			} else {
				_this.styleSheets.unregister(name);
			}
		});
		this.preferences.registerBool("bookmarks.text.hide", true, function(name, value) {
			if (value) {
				var css = new CSSBuilder("#PlacesToolbarItems > .bookmark-item:not([type]) > .toolbarbutton-text");
				css = css.hide();
				_this.styleSheets.registerForBrowser(name, css.toCSS());
			} else {
				_this.styleSheets.unregister(name);
			}
		});

		this.preferences.registerBool("dropdown.hideopenallintabs", false, function(name, value) {
			if (value) {
				var css = new CSSBuilder("#PlacesToolbarItems menuitem[class=\"openintabs-menuitem\"]")
				css = css.addSelector("#PlacesToolbarItems menuseparator[class=\"bookmarks-actions-menuseparator\"]");
				css = css.hide();
				_this.styleSheets.registerForBrowser(name, css.toCSS());
			} else {
				_this.styleSheets.unregister(name);
			}
		});
		this.preferences.registerInt("dropdown.minwidth", 0, function(name, value) {
			var css = new CSSBuilder("#PlacesToolbarItems scrollbox").minWidth(value);
			_this.styleSheets.registerForBrowser(name, css.toCSS());
		});
		this.preferences.registerBool("dropdown.scrollbar", false, function(name, value) {
			if (value) {
				var cssScrollbox = new CSSBuilder("#PlacesToolbarItems scrollbox").add("overflow-y", "auto");
				var cssButton = new CSSBuilder("#PlacesToolbarItems autorepeatbutton").hide();
				_this.styleSheets.registerForBrowser(name, cssScrollbox.toCSS() + cssButton.toCSS());
			} else {
				_this.styleSheets.unregister(name);
			}
		});

		this.preferences
				.registerBool("folders.dropdown.hide", true, function(name, value) {
					if (value) {
						var css = new CSSBuilder("#PlacesToolbarItems > .bookmark-item[type=menu] > .toolbarbutton-menu-dropmarker");
						css = css.hide();
						_this.styleSheets.registerForBrowser(name, css.toCSS());
					} else {
						_this.styleSheets.unregister(name);
					}
				});
		this.preferences.registerBool("folders.icon.hide", true, function(name, value) {
			if (value) {
				var css = new CSSBuilder("#PlacesToolbarItems > .bookmark-item[type=menu] > .toolbarbutton-icon");
				css = css.hide();
				_this.styleSheets.registerForBrowser(name, css.toCSS());
			} else {
				_this.styleSheets.unregister(name);
			}
		});
		this.preferences.registerBool("folders.text.hide", false, function(name, value) {
			if (value) {
				var css = new CSSBuilder("#PlacesToolbarItems > .bookmark-item[type=menu] > .toolbarbutton-text");
				css = css.hide();
				_this.styleSheets.registerForBrowser(name, css.toCSS());
			} else {
				_this.styleSheets.unregister(name);
			}
		});

		this.preferences.registerInt("height", 24, function(name, value) {
			var css = new CSSBuilder("#PersonalToolbar").addSelector("#PlacesToolbar")
			css = css.addSelector("#PersonalToolbar > *").addSelector("#PlacesToolbarItems > *")
			css = css.forceHeight(value);

			var cssChildren = new CSSBuilder("#PersonalToolbar > *").addSelector("#PlacesToolbarItems > *")
			cssChildren = cssChildren.maxHeight(value);

			_this.styleSheets.registerForBrowser(name, css.toCSS());
			_this.styleSheets.registerForBrowser(name + ".children", cssChildren.toCSS());
		});

		this.preferences.registerInt("items.icon.padding.bottom", -7, function(name, value) {
			var css = new CSSBuilder("#PlacesToolbarItems > .bookmark-item > .toolbarbutton-icon");
			css = css.autoPadding("bottom", value);
			_this.styleSheets.registerForBrowser(name, css.toCSS());
		});
		this.preferences.registerInt("items.icon.padding.top", -7, function(name, value) {
			var css = new CSSBuilder("#PlacesToolbarItems > .bookmark-item > .toolbarbutton-icon");
			css = css.autoPadding("top", value);
			_this.styleSheets.registerForBrowser(name, css.toCSS());
		});
		this.preferences.registerInt("items.padding", 0, function(name, value) {
			var css = new CSSBuilder("#PlacesToolbarItems > .bookmark-item").margin("right", value);
			_this.styleSheets.registerForBrowser(name, css.toCSS());
		});
		this.preferences.registerInt("items.text.padding.top", -1, function(name, value) {
			var css = new CSSBuilder("#PlacesToolbarItems > .bookmark-item > .toolbarbutton-text");
			css = css.autoPadding("top", value);
			_this.styleSheets.registerForBrowser(name, css.toCSS());
		});

		this.preferences.registerInt("padding.bottom", 0, function(name, value) {
			var css = new CSSBuilder("#PlacesToolbarItems").autoPadding("bottom", value);
			_this.styleSheets.registerForBrowser(name, css.toCSS());
		});
		this.preferences.registerInt("padding.top", 0, function(name, value) {
			var css = new CSSBuilder("#PlacesToolbarItems").autoPadding("top", value);
			_this.styleSheets.registerForBrowser(name, css.toCSS());
		});
	}
};

<|endoftext|>


<commit_before>/*jslint node: true */
'use strict';

var express = require('express'),
	app = express(),
	controllers = require('./controllers').controllers,
	config = require('./configure.json'),
	server;

// Configure
var set = function (obj) { Object.keys(obj).forEach(function (s) { app.set(s, obj[s]); }); };

// All environments
app.configure(function () {
	Object.keys(config).filter(function (c) { return c !== 'development' && c !== 'production'; })
					   .forEach(function (c) { app.set(c, config[c]); });
});

// Development only
app.configure('development', function () {
	set(config.development);
});

// Production only
app.configure('production', function () {
	set(config.production);
});

// Controllers or routes
controllers.forEach(function (c) { app[c.verb](c.route, c.fn); });

// Start server
server = app.listen(app.get('port'));
console.log('Listening on port %d in %s mode...', app.get('port'), app.get('env'));
<commit_msg>I forgot<commit_after>/*jslint node: true */
'use strict';

var express = require('express'),
	app = express(),
	controllers = require('./controllers').controllers,
	config = require('./configure.json'),
	server;

// Configure
var set = function (obj) { Object.keys(obj).forEach(function (s) { app.set(s, obj[s]); }); };

// All environments
app.configure(function () {
	set(Object.keys(config).filter(function (c) { return c !== 'development' && c !== 'production'; }));
});

// Development only
app.configure('development', function () {
	set(config.development);
});

// Production only
app.configure('production', function () {
	set(config.production);
});

// Controllers or routes
controllers.forEach(function (c) { app[c.verb](c.route, c.fn); });

// Start server
server = app.listen(app.get('port'));
console.log('Listening on port %d in %s mode...', app.get('port'), app.get('env'));
<|endoftext|>


<commit_before><commit_msg>Pass the environment to the view<commit_after><|endoftext|>


<commit_before><commit_msg>Fix function call.<commit_after><|endoftext|>


<commit_before>// import { isArray, isObject } from 'orbit/lib/objects';
// import JSONAPISource from './jsonapi-source';
//
// /**
//  Source for accessing a JSON API compliant RESTful API with AJAX using the
//  official patch extension
//
//  @class JSONAPIPatchSource
//  @extends Source
//  @namespace OC
//  @param schema
//  @param options
//  @constructor
//  */
// export default JSONAPISource.extend({
//
//   /////////////////////////////////////////////////////////////////////////////
//   // Internals
//   /////////////////////////////////////////////////////////////////////////////
//
//   _transformAdd: function(operation) {
//     var _this = this;
//     var type = operation.path[0];
//     var id = operation.path[1];
//
//     var remoteOp = {
//       op: 'add',
//       path: '/-',
//       value: this.serializer.serializeRecord(type, operation.value)
//     };
//
//     return this.ajax(this.resourceURL(type), 'PATCH', {data: [ remoteOp ]}).then(
//       function(raw) {
//         if (raw && isArray(raw)) {
//           _this.deserialize(type, id, raw[0], operation);
//         } else {
//           _this._transformCache(operation);
//         }
//       }
//     );
//   },
//
//   _transformReplace: function(operation) {
//     var _this = this;
//     var type = operation.path[0];
//     var id = operation.path[1];
//     var value = operation.value;
//
//     var remoteOp = {
//       op: 'replace',
//       path: '/',
//       value: this.serializer.serializeRecord(type, value)
//     };
//
//     return this.ajax(this.resourceURL(type, id), 'PATCH', {data: [ remoteOp ]}).then(
//       function(raw) {
//         if (raw && isArray(raw)) {
//           _this.deserialize(type, id, raw[0], operation);
//         } else {
//           _this._transformCache(operation);
//         }
//       }
//     );
//   },
//
//   _transformRemove: function(operation) {
//     var _this = this;
//     var type = operation.path[0];
//     var id = operation.path[1];
//
//     var remoteOp = {
//       op: 'remove',
//       path: '/'
//     };
//
//     return this.ajax(this.resourceURL(type, id), 'PATCH', {data: [ remoteOp ]}).then(
//       function() {
//         _this._transformCache(operation);
//       }
//     );
//   },
//
//   _transformAddLink: function(operation) {
//     var _this = this;
//
//     var type = operation.path[0];
//     var id = operation.path[1];
//     var link = operation.path[3];
//     var relId = operation.path[4] || operation.value;
//     var linkDef = this.schema.linkDefinition(type, link);
//     var relType = linkDef.model;
//     var relResourceId = this.serializer.resourceId(relType, relId);
//     var remoteOp;
//
//     if (linkDef.type === 'hasMany') {
//       remoteOp = {
//         op: 'add',
//         path: '/-',
//         value: relResourceId
//       };
//     } else {
//       remoteOp = {
//         op: 'replace',
//         path: '/',
//         value: relResourceId
//       };
//     }
//
//     return this.ajax(this.resourceLinkURL(type, id, link), 'PATCH', {data: [ remoteOp ]}).then(
//       function() {
//         _this._transformCache(operation);
//       }
//     );
//   },
//
//   _transformRemoveLink: function(operation) {
//     var _this = this;
//
//     var type = operation.path[0];
//     var id = operation.path[1];
//     var link = operation.path[3];
//     var linkDef = this.schema.linkDefinition(type, link);
//     var remoteOp;
//
//     if (linkDef.type === 'hasMany') {
//       var relId = operation.path[4];
//       var relType = linkDef.model;
//       var relResourceId = this.serializer.resourceId(relType, relId);
//
//       remoteOp = {
//         op: 'remove',
//         path: '/' + relResourceId
//       };
//     } else {
//       remoteOp = {
//         op: 'remove',
//         path: '/'
//       };
//     }
//
//     return this.ajax(this.resourceLinkURL(type, id, link), 'PATCH', {data: [ remoteOp ]}).then(
//       function() {
//         _this._transformCache(operation);
//       }
//     );
//   },
//
//   _transformReplaceLink: function(operation) {
//     var _this = this;
//
//     var type = operation.path[0];
//     var id = operation.path[1];
//     var link = operation.path[3];
//     var relId = operation.path[4] || operation.value;
//
//     // Convert a map of ids to an array
//     if (isObject(relId)) {
//       relId = Object.keys(relId);
//     }
//
//     var linkDef = this.schema.linkDefinition(type, link);
//     var relType = linkDef.model;
//     var relResourceId = this.serializer.resourceId(relType, relId);
//     var remoteOp;
//
//     remoteOp = {
//       op: 'replace',
//       path: '/',
//       value: relResourceId
//     };
//
//     return this.ajax(this.resourceLinkURL(type, id, link), 'PATCH', {data: [ remoteOp ]}).then(
//       function() {
//         _this._transformCache(operation);
//       }
//     );
//   },
//
//   _transformUpdateAttribute: function(operation) {
//     var _this = this;
//     var type = operation.path[0];
//     var id = operation.path[1];
//     var attr = operation.path[2];
//
//     var remoteOp = {
//       op: 'replace',
//       path: '/' + attr,
//       value: operation.value
//     };
//
//     return this.ajax(this.resourceURL(type, id), 'PATCH', {data: [ remoteOp ]}).then(
//       function() {
//         _this._transformCache(operation);
//       }
//     );
//   },
//
//   ajaxContentType: function(url, method) {
//     return 'application/vnd.api+json; ext=jsonpatch; charset=utf-8';
//   }
// });
<commit_msg>Remove JSONAPIPatchSource for now.<commit_after><|endoftext|>


<commit_before>var getCssRulesByProperty = require('./getCssRulesByProperty');
var defaultStylesheet = require('./defaultStylesheet');
var stylePropObjectComparator = require('./stylePropObjectComparator');
var memoizeSync = require('memoizesync');

var FONT_PROPS = [
    'font-family',
    'font-weight',
    'font-style'
];

var DEFAULTS = {
    'font-style': 'normal',
    'font-weight': 400,
    'font-family': 'serif'
};

function getFontRulesWithDefaultStylesheetApplied(htmlAsset) {
    var stylesheets = htmlAsset.assetGraph.collectAssetsPreOrder(htmlAsset)
        .filter(function (asset) {
            return asset.type === 'Css'
                && asset.isExternalizable === true; // exclude inline style attributes
        })
        .map(function (asset) { return asset.text; });

    var fontPropRules = [defaultStylesheet].concat(stylesheets)
        .map(function (stylesheet) {
            return getCssRulesByProperty(FONT_PROPS, stylesheet);
        })
        .reduce(function (rules, current) {
            Object.keys(current).forEach(function (prop) {
                if (!rules[prop]) {
                    rules[prop] = [];
                }

                rules[prop] = rules[prop].concat(current[prop]);
            });

            return rules;
        }, {});

    Object.keys(fontPropRules).forEach(function (prop) {
        fontPropRules[prop].sort(stylePropObjectComparator(fontPropRules[prop]));
    });

    return fontPropRules;
}

function getMemoizedElementStyleResolver(fontPropRules) {
    var getComputedStyle = memoizeSync(function (node, idArray) {
        var localFontPropRules = Object.assign({}, fontPropRules);
        var props = Object.keys(fontPropRules);
        var result = {};

        if (node.getAttribute('style')) {
            var attributeStyles = getCssRulesByProperty(FONT_PROPS, 'bogusselector { ' + node.getAttribute('style') + ' }');

            Object.keys(attributeStyles).forEach(function (prop) {
                if (attributeStyles[prop].length > 0) {
                    var concatRules = attributeStyles[prop].concat(localFontPropRules[prop]);
                    localFontPropRules[prop] = concatRules.sort(stylePropObjectComparator(concatRules));
                }
            });
        }

        props.forEach(function (prop) {
            var match = localFontPropRules[prop].find(function (rule) {
                if (rule.styleAttribute) {
                    return true;
                }

                return node.matches(rule.selector);
            });

            if (match) {
                result[prop] = match.value;
            } else if (node.parentNode && node.tagName !== 'BODY') {
                result[prop] = getComputedStyle(node.parentNode, idArray.slice(0, -1))[prop];
            } else {
                result[prop] = undefined;
            }
        });

        return result;
    });

    return getComputedStyle;
}

function getTextByFontProperties(htmlAsset) {
    if (!htmlAsset || htmlAsset.type !== 'Html'  || !htmlAsset.assetGraph) {
        throw new Error('Argument must be a Html-asset and be in an assetGraph');
    }

    var fontPropRules = getFontRulesWithDefaultStylesheetApplied(htmlAsset);
    var getComputedStyle = getMemoizedElementStyleResolver(fontPropRules);

    var document = htmlAsset.parseTree;

    var textNodes = [];
    var excludedNodes = ['STYLE', 'SCRIPT'];

    (function traversePreOrder(node, idArray) {
        if (node.nodeType === 1) {
            if (!idArray) {
                idArray = [0];
            }

            var currentIndex = 0;
            var child = node.firstChild;

            while (child) {
                if (child.nodeType === 3 && child.textContent.trim()) {
                    textNodes.push({
                        node: child,
                        parentId: idArray
                    });
                }

                if (child.nodeType === 1 && !(excludedNodes.includes(child.tagName))) {
                    traversePreOrder(child, idArray.concat(currentIndex));
                    currentIndex += 1;
                }

                child = child.nextSibling;
            }
        }
    }(document.body));

    var propsByTextNodes = textNodes.map(function (textNode) {
        return {
            text: textNode.node.textContent.trim(),
            props: getComputedStyle(textNode.node.parentNode, textNode.parentId)
        };
    });

    return propsByTextNodes;
}

module.exports = getTextByFontProperties;
<commit_msg>Comment out font prop defaults until in use<commit_after>var getCssRulesByProperty = require('./getCssRulesByProperty');
var defaultStylesheet = require('./defaultStylesheet');
var stylePropObjectComparator = require('./stylePropObjectComparator');
var memoizeSync = require('memoizesync');

var FONT_PROPS = [
    'font-family',
    'font-weight',
    'font-style'
];

// var DEFAULTS = {
//     'font-style': 'normal',
//     'font-weight': 400,
//     'font-family': 'serif'
// };

function getFontRulesWithDefaultStylesheetApplied(htmlAsset) {
    var stylesheets = htmlAsset.assetGraph.collectAssetsPreOrder(htmlAsset)
        .filter(function (asset) {
            return asset.type === 'Css'
                && asset.isExternalizable === true; // exclude inline style attributes
        })
        .map(function (asset) { return asset.text; });

    var fontPropRules = [defaultStylesheet].concat(stylesheets)
        .map(function (stylesheet) {
            return getCssRulesByProperty(FONT_PROPS, stylesheet);
        })
        .reduce(function (rules, current) {
            Object.keys(current).forEach(function (prop) {
                if (!rules[prop]) {
                    rules[prop] = [];
                }

                rules[prop] = rules[prop].concat(current[prop]);
            });

            return rules;
        }, {});

    Object.keys(fontPropRules).forEach(function (prop) {
        fontPropRules[prop].sort(stylePropObjectComparator(fontPropRules[prop]));
    });

    return fontPropRules;
}

function getMemoizedElementStyleResolver(fontPropRules) {
    var getComputedStyle = memoizeSync(function (node, idArray) {
        var localFontPropRules = Object.assign({}, fontPropRules);
        var props = Object.keys(fontPropRules);
        var result = {};

        if (node.getAttribute('style')) {
            var attributeStyles = getCssRulesByProperty(FONT_PROPS, 'bogusselector { ' + node.getAttribute('style') + ' }');

            Object.keys(attributeStyles).forEach(function (prop) {
                if (attributeStyles[prop].length > 0) {
                    var concatRules = attributeStyles[prop].concat(localFontPropRules[prop]);
                    localFontPropRules[prop] = concatRules.sort(stylePropObjectComparator(concatRules));
                }
            });
        }

        props.forEach(function (prop) {
            var match = localFontPropRules[prop].find(function (rule) {
                if (rule.styleAttribute) {
                    return true;
                }

                return node.matches(rule.selector);
            });

            if (match) {
                result[prop] = match.value;
            } else if (node.parentNode && node.tagName !== 'BODY') {
                result[prop] = getComputedStyle(node.parentNode, idArray.slice(0, -1))[prop];
            } else {
                result[prop] = undefined;
            }
        });

        return result;
    });

    return getComputedStyle;
}

function getTextByFontProperties(htmlAsset) {
    if (!htmlAsset || htmlAsset.type !== 'Html'  || !htmlAsset.assetGraph) {
        throw new Error('Argument must be a Html-asset and be in an assetGraph');
    }

    var fontPropRules = getFontRulesWithDefaultStylesheetApplied(htmlAsset);
    var getComputedStyle = getMemoizedElementStyleResolver(fontPropRules);

    var document = htmlAsset.parseTree;

    var textNodes = [];
    var excludedNodes = ['STYLE', 'SCRIPT'];

    (function traversePreOrder(node, idArray) {
        if (node.nodeType === 1) {
            if (!idArray) {
                idArray = [0];
            }

            var currentIndex = 0;
            var child = node.firstChild;

            while (child) {
                if (child.nodeType === 3 && child.textContent.trim()) {
                    textNodes.push({
                        node: child,
                        parentId: idArray
                    });
                }

                if (child.nodeType === 1 && !(excludedNodes.includes(child.tagName))) {
                    traversePreOrder(child, idArray.concat(currentIndex));
                    currentIndex += 1;
                }

                child = child.nextSibling;
            }
        }
    }(document.body));

    var propsByTextNodes = textNodes.map(function (textNode) {
        return {
            text: textNode.node.textContent.trim(),
            props: getComputedStyle(textNode.node.parentNode, textNode.parentId)
        };
    });

    return propsByTextNodes;
}

module.exports = getTextByFontProperties;
<|endoftext|>


<commit_before>'use strict'
const Topic = require('../service').Topic;
const User = require('../service').User;
const at = require('../common/at.js');
const TopicCollect = require('../service').TopicCollect;
const Reply = require('../service').Reply;
const cache = require('../middleware/cache.js');

module.exports = {
  create: (ctx) => {
    return ctx.render('topic/edit');
  },
  index: (ctx) => {
    let topic_id = ctx.params.tid;
    let currentUser = ctx.session.user;

    return Promise.join(
      Topic.getById(topic_id).then((topic) => {
        if (!topic) throw '此话题不存在或已被删除。';
        return Promise.join(
          at.linkUsers(topic.content),
          User.getById(topic.author_id),
          Reply.getRepliesByTopicId(topic._id),
          // get author_other_topics
          Topic.findByQuery({
            author_id: topic.author_id,
            _id: {
              '$nin': [topic._id]
            }
          }, {
            limit: 5,
            sort: '-last_reply_at'
          }),
          (linkedContent, author, author_other_topics) => {
            if (!author) throw '话题的作者丢了。';
            topic.visit_count += 1;
            Topic.update({_id:topic._id},{visit_count:topic.visit_count});

            topic.author = author;
            topic.replies = replies;
            // 点赞数排名第三的回答，它的点赞数就是阈值
            topic.reply_up_threshold = (() => {
              let allUpCount = replies.map((reply) => {
                return reply.ups && reply.ups.length || 0;
              });
              allUpCount,sort((pre, next) => {
                return next - pre;
              });

              let threshold = allUpCount[2] || 0;
              if (threshold < 3) threshold = 3;
              return threshold;
            })();

            return {
              topic: topic,
              author_other_topics: author_other_topics
            };
          }
        )
      }),
      // 取0回复的主题
      cache.get('no_reply_topics', () => {
        return Topic.findByQuery({
          reply_count: 0,
          tab: {
            $ne: 'job'
          }
        }, {
          limit: 5,
          sort: '-create_at'
        });
      }, 60 * 1),
      currentUser ? TopicCollect.getTopicCollect(currentUser._id, topic_id) : null,
      ({ topic, author_other_topics }, no_reply_topics, is_collect) => {
        return ctx.render('topic/index', {
          topic: topic,
          author_other_topics: author_other_topics,
          no_reply_topics: no_reply_topics,
          is_uped: (user, reply) => {
            if (!reply.ups) return false;
            return reply.ups.indexOf(user._id) !== -1;
          },
          is_collect: is_collect
        });
      }
    ).catch((e) => {
      if (typeof e === 'string') {
        logger.error(`getFullTopic error topic_id: ${topic_id}`);
        return ctx.renderError(e);
      }
      throw e;
    })
  },
  top: () => {},
  good: () => {},
  showEdit: () => {},
  lock: () => {},
  delete: () => {},
  put: (ctx) => {
    let title = ctx.query.title;
    let tab = ctx.query.tab;
    let content = ctx.query.content;
    let user = ctx.session.user;
    user.score += 5;
    user.topic_count += 1;

    return Promise.join(
      Topic.newAndSave(title, content, tab, user._id),
      //User.getById(ctx.session.user._id);
      User.update({
        _id: user.id
      }, {
        score: user.score,
        topic_count: user.topic_count
      }),
      (topic) => {
        //发送at消息`
        //at.sendMessageToMentionUsers(content, topic._id, user._id);
        ctx.redirect(`/topic/${topic._id}`);
      }
    );
  },
  update: () => {},
  collect: () => {},
  de_collect: () => {},
  upload: () => {}
};
<commit_msg>fix(controller/topic): fix method names and variables<commit_after>'use strict'
const Topic = require('../service').Topic;
const User = require('../service').User;
const at = require('../common/at.js');
const TopicCollect = require('../service').TopicCollect;
const Reply = require('../service').Reply;
const cache = require('../middleware/cache.js');
const logger = require('../middleware/logger.js');

module.exports = {
  create: (ctx) => {
    return ctx.render('topic/edit');
  },
  index: (ctx) => {
    let topic_id = ctx.params.tid;
    let currentUser = ctx.session.user;
    return Promise.join(
      Topic.getById(topic_id).then((topic) => {
        if (!topic) throw '此话题不存在或已被删除。';
        return Promise.join(
          at.linkUsers(topic.content),
          User.getById(topic.author_id),
          Reply.findByTopicId(topic._id),
          // get author_other_topics
          Topic.findByQuery({
            author_id: topic.author_id,
            _id: {
              '$nin': [topic._id]
            }
          }, {
            limit: 5
          }),
          (linkedContent, author, replies, author_other_topics) => {
            if (!author) throw '话题的作者丢了。';
            topic.visit_count += 1;
            Topic.update({ _id: topic._id }, { visit_count: topic.visit_count });

            topic.author = author;
            topic.replies = replies;
            // 点赞数排名第三的回答，它的点赞数就是阈值
            topic.reply_up_threshold = (() => {
              let allUpCount = replies.map((reply) => {
                return reply.ups && reply.ups.length || 0;
              }).sort((pre, next) => {
                return next - pre;
              });

              let threshold = allUpCount[2] || 0;
              if (threshold < 3) threshold = 3;
              return threshold;
            })();

            return {
              topic: topic,
              author_other_topics: author_other_topics
            };
          }
        )
      }),
      // 取0回复的主题
      cache.get('no_reply_topics', () => {
        return Topic.findByQuery({
          reply_count: 0,
          tab: {
            $ne: 'job'
          }
        }, {
          limit: 5,
          sort: '-create_at'
        });
      }, 60 * 1),
      currentUser ? TopicCollect.getByQuery({user_id:currentUser._id, topic_id:topic_id}) : null,
      ({ topic, author_other_topics }, no_reply_topics, is_collect) => {
        return ctx.render('topic/index', {
          topic: topic,
          author_other_topics: author_other_topics,
          no_reply_topics: no_reply_topics,
          is_uped: (user, reply) => {
            if (!reply.ups) return false;
            return reply.ups.indexOf(user._id) !== -1;
          },
          is_collect: is_collect
        });
      }
    ).catch((e) => {
      if (typeof e === 'string') {
        logger.error(`getFullTopic error topic_id: ${topic_id}`);
        return ctx.renderError(e);
      }
      throw e;
    })
  },
  top: () => {},
  good: () => {},
  showEdit: () => {},
  lock: () => {},
  delete: () => {},
  put: (ctx) => {
    let title = ctx.query.title;
    let tab = ctx.query.tab;
    let content = ctx.query.content;
    let user = ctx.session.user;
    user.score += 5;
    user.topic_count += 1;

    return Promise.join(
      Topic.newAndSave(title, content, tab, user._id),
      //User.getById(ctx.session.user._id);
      User.update({
        _id: user.id
      }, {
        score: user.score,
        topic_count: user.topic_count
      }),
      (topic) => {
        //发送at消息`
        //at.sendMessageToMentionUsers(content, topic._id, user._id);
        ctx.redirect(`/topic/${topic._id}`);
      }
    );
  },
  update: () => {},
  collect: () => {},
  de_collect: () => {},
  upload: () => {}
};
<|endoftext|>