<commit_before>/* picomarkdown
 *
 * Github:	https://github.com/developit/picomarkdown
 * Version: 0.1.0
 *
 * A slimmed-down fork of Micromarkdown:
 *
 * Copyright 2014, Simon Waldherr - http://simon.waldherr.eu/
 * Released under the MIT Licence
 * http://simon.waldherr.eu/license/mit/
 */
(function(root, factory) {
	if (typeof define==='function' && define.amd) {
		define([], factory);
	}
	else if (typeof exports==='object') {
		module.exports = factory();
	}
	else {
		root.picomarkdown = factory();
	}
}(this, function() {
	"use strict";

	var regs = {
		headline : /^(\#{1,6})([^\#\n]+)$/m,
		headline2 : /\n(.*?)\n(={3,}|-{3,})$/g,
		code : /\s\`\`\`\n?([^`]+)\`\`\`/g,
		hr : /^(?:([\*\-_] ?)+)\1\1$/gm,
		lists : /^((\s*((\*|\-)|\d(\.|\))) [^\n]+)\n)+/gm,
		bolditalic : /(?:([\*_~]{1,3}))([^\*_~\n]+[^\*_~\s])\1/g,
		links : /!?\[([^\]<>]+)\]\(([^ \)<>]+)( "[^\(\)\"]+")?\)/g,
		reflinks : /\[([^\]]+)\]\[([^\]]+)\]/g,
		mail : /<(([a-z0-9_\-\.])+\@([a-z0-9_\-\.])+\.([a-z]{2,7}))>/gmi,
		url : /<([a-zA-Z0-9@:%_\+.~#?&\/\/=]{2,256}\.[a-z]{2,4}\b(\/[\-a-zA-Z0-9@:%_\+.~#?&\/\/=]*)?)>/g
	};

	function cssClass(str, strict) {
		if (str.indexOf('/')<0 || strict===true) return '';

		var url = str.split('/');
		url = url[url[1].length===0 ? 2 : 0].split('.');
		return 'class="' + url[url.length - 2].replace(/[^\w\d]/g, '') + url[url.length - 1] + '" ';
	}

	function htmlEscape(str) {
		return String(str)
			.replace(/&/g, '&')
			.replace(/"/g, '"')
			.replace(/'/g, ''')
			.replace(/</g, '<')
			.replace(/>/g, '>');
	}

	/** Parse Markdown into HTML.
	 *  @param {String} markdown
	 *  @returns {String} html
	 */
	function picomarkdown(str, strict) {
		var line, nstatus = 0,
			status, cel, calign, indent, helper, helper1, helper2, count, repstr, stra, trashgc = [],
			casca = 0,
			i = 0,
			j = 0;
		str = '\n' + str + '\n';

		if (strict !== true) {
			regs.lists = /^((\s*(\*|\d\.) [^\n]+)\n)+/gm;
		}

		/* code */
		while ((stra = regs.code.exec(str)) !== null) {
			str = str.replace(stra[0], '\n' + htmlEscape(stra[1]).replace(/\n/gm, '
').replace(/\ /gm, ' ') + '\n');
		}

		/* headlines */
		while ((stra = regs.headline.exec(str))) {
			count = stra[1].length;
			str = str.replace(stra[0], '<h' + count + '>' + stra[2] + '</h' + count + '>' + '\n');
		}
		while ((stra = regs.headline2.exec(str))) {
			count = stra[2][0]==='=' ? 1 : 2;
			str = str.replace(stra[0], '<h' + count + '>' + stra[1] + '</h' + count + '>' + '\n');
		}

		/* lists */
		while ((stra = regs.lists.exec(str)) !== null) {
			casca = 0;
			if ((stra[0].trim().substr(0, 1) === '*') || (stra[0].trim().substr(0, 1) === '-')) {
				repstr = '
';
			} else {
				repstr = '
';
			}
			helper = stra[0].split('\n');
			helper1 = [];
			status = 0;
			indent = false;
			for (i = 0; i < helper.length; i++) {
				if ((line = /^((\s*)((\*|\-)|\d(\.|\))) ([^\n]+))/.exec(helper[i])) !== null) {
					if ((line[2] === undefined) || (line[2].length === 0)) {
						nstatus = 0;
					} else {
						if (indent === false) {
							indent = line[2].replace(/\t/, '		').length;
						}
						nstatus = Math.round(line[2].replace(/\t/, '		').length / indent);
					}
					while (status > nstatus) {
						repstr += helper1.pop();
						status--;
						casca--;
					}
					while (status < nstatus) {
						if ((line[0].trim().substr(0, 1) === '*') || (line[0].trim().substr(0, 1) === '-')) {
							repstr += '
';
							helper1.push('
');
						} else {
							repstr += '
';
							helper1.push('
');
						}
						status++;
						casca++;
					}
					repstr += '
' + line[6] + '
' + '\n';
				}
			}
			while (casca > 0) {
				repstr += '
';
				casca--;
			}
			if ((stra[0].trim().substr(0, 1) === '*') || (stra[0].trim().substr(0, 1) === '-')) {
				repstr += '
';
			} else {
				repstr += '
';
			}
			str = str.replace(stra[0], repstr + '\n');
		}

		/* bold and italic */
		for (i = 0; i < 3; i++) {
			while ((stra = regs.bolditalic.exec(str)) !== null) {
				repstr = [];
				if (stra[1] === '~~') {
					str = str.replace(stra[0], '' + stra[2] + '');
				} else {
					switch (stra[1].length) {
					case 1:
						repstr = ['', ''];
						break;
					case 2:
						repstr = ['', ''];
						break;
					case 3:
						repstr = ['', ''];
						break;
					}
					str = str.replace(stra[0], repstr[0] + stra[2] + repstr[1]);
				}
			}
		}

		/* links */
		while ((stra = regs.links.exec(str)) !== null) {
			if (stra[0].substr(0, 1) === '!') {
				str = str.replace(stra[0], '\n');
			} else {
				str = str.replace(stra[0], '' + stra[1] + '\n');
			}
		}
		while ((stra = regs.mail.exec(str)) !== null) {
			str = str.replace(stra[0], '' + stra[1] + '');
		}
		while ((stra = regs.url.exec(str)) !== null) {
			repstr = stra[1];
			if (repstr.indexOf('://') === -1) {
				repstr = 'http://' + repstr;
			}
			str = str.replace(stra[0], '' + repstr.replace(/(https:\/\/|http:\/\/|mailto:|ftp:\/\/)/gmi, '') + '');
		}
		while ((stra = regs.reflinks.exec(str)) !== null) {
			helper1 = new RegExp('\\[' + stra[2] + '\\]: ?([^ \n]+)', "gi");
			if ((helper = helper1.exec(str)) !== null) {
				str = str.replace(stra[0], '' + stra[1] + '');
				trashgc.push(helper[0]);
			}
		}
		for (i = 0; i < trashgc.length; i++) {
			str = str.replace(trashgc[i], '');
		}

		/* horizontal line */
		while ((stra = regs.hr.exec(str)) !== null) {
			str = str.replace(stra[0], '\n
\n');
		}

		str = str.replace(/( {2,}[\n]{1,}|\n\n+)/g, '\n

\n');
		return str;
	}

	picomarkdown.parse = picomarkdown;
	return picomarkdown;
}));
<commit_msg>Removed next line character for link<commit_after>/* picomarkdown
 *
 * Github:	https://github.com/developit/picomarkdown
 * Version: 0.1.0
 *
 * A slimmed-down fork of Micromarkdown:
 *
 * Copyright 2014, Simon Waldherr - http://simon.waldherr.eu/
 * Released under the MIT Licence
 * http://simon.waldherr.eu/license/mit/
 */
(function(root, factory) {
	if (typeof define==='function' && define.amd) {
		define([], factory);
	}
	else if (typeof exports==='object') {
		module.exports = factory();
	}
	else {
		root.picomarkdown = factory();
	}
}(this, function() {
	"use strict";

	var regs = {
		headline : /^(\#{1,6})([^\#\n]+)$/m,
		headline2 : /\n(.*?)\n(={3,}|-{3,})$/g,
		code : /\s\`\`\`\n?([^`]+)\`\`\`/g,
		hr : /^(?:([\*\-_] ?)+)\1\1$/gm,
		lists : /^((\s*((\*|\-)|\d(\.|\))) [^\n]+)\n)+/gm,
		bolditalic : /(?:([\*_~]{1,3}))([^\*_~\n]+[^\*_~\s])\1/g,
		links : /!?\[([^\]<>]+)\]\(([^ \)<>]+)( "[^\(\)\"]+")?\)/g,
		reflinks : /\[([^\]]+)\]\[([^\]]+)\]/g,
		mail : /<(([a-z0-9_\-\.])+\@([a-z0-9_\-\.])+\.([a-z]{2,7}))>/gmi,
		url : /<([a-zA-Z0-9@:%_\+.~#?&\/\/=]{2,256}\.[a-z]{2,4}\b(\/[\-a-zA-Z0-9@:%_\+.~#?&\/\/=]*)?)>/g
	};

	function cssClass(str, strict) {
		if (str.indexOf('/')<0 || strict===true) return '';

		var url = str.split('/');
		url = url[url[1].length===0 ? 2 : 0].split('.');
		return 'class="' + url[url.length - 2].replace(/[^\w\d]/g, '') + url[url.length - 1] + '" ';
	}

	function htmlEscape(str) {
		return String(str)
			.replace(/&/g, '&')
			.replace(/"/g, '"')
			.replace(/'/g, ''')
			.replace(/</g, '<')
			.replace(/>/g, '>');
	}

	/** Parse Markdown into HTML.
	 *  @param {String} markdown
	 *  @returns {String} html
	 */
	function picomarkdown(str, strict) {
		var line, nstatus = 0,
			status, cel, calign, indent, helper, helper1, helper2, count, repstr, stra, trashgc = [],
			casca = 0,
			i = 0,
			j = 0;
		str = '\n' + str + '\n';

		if (strict !== true) {
			regs.lists = /^((\s*(\*|\d\.) [^\n]+)\n)+/gm;
		}

		/* code */
		while ((stra = regs.code.exec(str)) !== null) {
			str = str.replace(stra[0], '\n' + htmlEscape(stra[1]).replace(/\n/gm, '
').replace(/\ /gm, ' ') + '\n');
		}

		/* headlines */
		while ((stra = regs.headline.exec(str))) {
			count = stra[1].length;
			str = str.replace(stra[0], '<h' + count + '>' + stra[2] + '</h' + count + '>' + '\n');
		}
		while ((stra = regs.headline2.exec(str))) {
			count = stra[2][0]==='=' ? 1 : 2;
			str = str.replace(stra[0], '<h' + count + '>' + stra[1] + '</h' + count + '>' + '\n');
		}

		/* lists */
		while ((stra = regs.lists.exec(str)) !== null) {
			casca = 0;
			if ((stra[0].trim().substr(0, 1) === '*') || (stra[0].trim().substr(0, 1) === '-')) {
				repstr = '
';
			} else {
				repstr = '
';
			}
			helper = stra[0].split('\n');
			helper1 = [];
			status = 0;
			indent = false;
			for (i = 0; i < helper.length; i++) {
				if ((line = /^((\s*)((\*|\-)|\d(\.|\))) ([^\n]+))/.exec(helper[i])) !== null) {
					if ((line[2] === undefined) || (line[2].length === 0)) {
						nstatus = 0;
					} else {
						if (indent === false) {
							indent = line[2].replace(/\t/, '		').length;
						}
						nstatus = Math.round(line[2].replace(/\t/, '		').length / indent);
					}
					while (status > nstatus) {
						repstr += helper1.pop();
						status--;
						casca--;
					}
					while (status < nstatus) {
						if ((line[0].trim().substr(0, 1) === '*') || (line[0].trim().substr(0, 1) === '-')) {
							repstr += '
';
							helper1.push('
');
						} else {
							repstr += '
';
							helper1.push('
');
						}
						status++;
						casca++;
					}
					repstr += '
' + line[6] + '
' + '\n';
				}
			}
			while (casca > 0) {
				repstr += '
';
				casca--;
			}
			if ((stra[0].trim().substr(0, 1) === '*') || (stra[0].trim().substr(0, 1) === '-')) {
				repstr += '
';
			} else {
				repstr += '
';
			}
			str = str.replace(stra[0], repstr + '\n');
		}

		/* bold and italic */
		for (i = 0; i < 3; i++) {
			while ((stra = regs.bolditalic.exec(str)) !== null) {
				repstr = [];
				if (stra[1] === '~~') {
					str = str.replace(stra[0], '' + stra[2] + '');
				} else {
					switch (stra[1].length) {
					case 1:
						repstr = ['', ''];
						break;
					case 2:
						repstr = ['', ''];
						break;
					case 3:
						repstr = ['', ''];
						break;
					}
					str = str.replace(stra[0], repstr[0] + stra[2] + repstr[1]);
				}
			}
		}

		/* links */
		while ((stra = regs.links.exec(str)) !== null) {
			if (stra[0].substr(0, 1) === '!') {
				str = str.replace(stra[0], '\n');
			} else {
				str = str.replace(stra[0], '' + stra[1] + '');
			}
		}
		while ((stra = regs.mail.exec(str)) !== null) {
			str = str.replace(stra[0], '' + stra[1] + '');
		}
		while ((stra = regs.url.exec(str)) !== null) {
			repstr = stra[1];
			if (repstr.indexOf('://') === -1) {
				repstr = 'http://' + repstr;
			}
			str = str.replace(stra[0], '' + repstr.replace(/(https:\/\/|http:\/\/|mailto:|ftp:\/\/)/gmi, '') + '');
		}
		while ((stra = regs.reflinks.exec(str)) !== null) {
			helper1 = new RegExp('\\[' + stra[2] + '\\]: ?([^ \n]+)', "gi");
			if ((helper = helper1.exec(str)) !== null) {
				str = str.replace(stra[0], '' + stra[1] + '');
				trashgc.push(helper[0]);
			}
		}
		for (i = 0; i < trashgc.length; i++) {
			str = str.replace(trashgc[i], '');
		}

		/* horizontal line */
		while ((stra = regs.hr.exec(str)) !== null) {
			str = str.replace(stra[0], '\n
\n');
		}

		str = str.replace(/( {2,}[\n]{1,}|\n\n+)/g, '\n

\n');
		return str;
	}

	picomarkdown.parse = picomarkdown;
	return picomarkdown;
}));
<|endoftext|>


<commit_before>// Generated by CoffeeScript 1.6.3
'use strict';
var fetchAndParse, https, xml2js, restler;

https = require('https');
restler = require('restler');

xml2js = require('xml2js');

fetchAndParse = function (params, cb) {
    var req, url;
    url = "https://ws.geonorge.no/SKWS3Index/ssr/sok?" + params;
    req = https.get(url, function (res) {
        var parser;
        parser = new xml2js.Parser({
            explicitArray: false,
            ignoreAttrs: true
        });
        parser.addListener('end', function (r) {
            return cb(null, r);
        });
        return res.on('data', function (d) {
            return parser.parseString(d);
        });
    });
    return req.on('error', function (e) {
        return cb(e);
    });
};

module.exports = function (search, cb) {
    if (typeof search === 'object') {
        return fetchAndParse(search, cb);
    } else if (typeof search === 'string') {
        return fetchAndParse("navn=" + search + "&epsgKode=4326", cb);
    } else if (typeof search === 'number') {
        return fetchAndParse("ssrId=" + search + "&epsgKode=4326", cb);
    } else {
        return cb(new Error('Unsupported search type'));
    }
};
<commit_msg>Remove leggacy SSR proxy route<commit_after><|endoftext|>


<commit_before>/*'use strict';

const Sequelize = require('sequelize');
const db = require('../../index');

const Page = db.define('page', {
    name: {
        type: Sequelize.STRING,
        allowNull: false,
        validate: {
        notEmpty: true,
        }
    },
    status: {
        type: Sequelize.BOOLEAN,
        allowNull: false,
        validate: {
        notEmpty: true,
        }
    }
});

module.exports = Page;*/<commit_msg>small changes<commit_after>'use strict';

const Sequelize = require('sequelize');
const db = require('../../db');

const Page = db.define('page', {
    name: {
        type: Sequelize.STRING,
        allowNull: false,
        validate: {
        notEmpty: true,
        }
    },
    status: {
        type: Sequelize.BOOLEAN,
        allowNull: false,
        validate: {
        notEmpty: true,
        }
    }
});

module.exports = Page;<|endoftext|>


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

var express = require('express');

var app = module.exports = express.createServer();
var morgan = require('morgan');

//Stuff
// Hook Socket.io into Express
var io = require('socket.io').listen(app, {log: false});
//var io = require('socket.io').listen(app);

// Configuration

app.configure(function(){
  app.use(express.static(__dirname + '/app'));
  app.use('/static', express.static(__dirname + '/build'));
});

app.configure('development', function(){
  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
  app.use(morgan()); // { stream: filestream }
});

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

var socket = require('./server/socket')(io);
io.on('connection', socket);

app.settings.env = 'production';
app.listen(5000, function(){
  console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);
});
<commit_msg>Added in a require statement for the new user object.<commit_after>
/**
 * Module dependencies.
 */

var express = require('express');

var app = module.exports = express.createServer();
var morgan = require('morgan');
var User = require('./server/model.js').User;

//Stuff
// Hook Socket.io into Express
var io = require('socket.io').listen(app, {log: false});
//var io = require('socket.io').listen(app);

// Configuration

app.configure(function(){
  app.use(express.static(__dirname + '/app'));
  app.use('/static', express.static(__dirname + '/build'));
});

app.configure('development', function(){
  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));
  app.use(morgan()); // { stream: filestream }
});

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

var socket = require('./server/socket')(io);
io.on('connection', socket);

app.settings.env = 'production';
app.listen(5000, function(){
  console.log("Express server listening on port %d in %s mode", app.address().port, app.settings.env);
});
<|endoftext|>


<commit_before>/**
 * Track Overall Percentile (1st, 2nd, 3rd, and 4th) of Completion
 * This event triggers each quarter of a video.
 *
 * Example Usage:
 * player.on('tracking:first-quarter', (e, data) => console.log(data))
 * player.on('tracking:second-quarter', (e, data) => console.log(data))
 * player.on('tracking:third-quarter', (e, data) => console.log(data))
 * player.on('tracking:fourth-quarter', (e, data) => console.log(data))
 *
 * Data Attributes:
 * => pauseCount:       Total number of Pause events triggered
 * => seekCount:        Total number of Seek events triggered
 * => currentTime:      Current second video is on
 * => duration:         Total duration of video
 *
 * @function PercentileTracking
 * @param    {Object} [config={}]
 *           An object of config left to the plugin author to define.
 */

const PercentileTracking = function(config) {
  const player = this;
  let first = false;
  let second = false;
  let third = false;
  let duration = 0;
  let pauseCount = 0;
  let seekCount = 0;

  const reset = function(e) {
    first = false;
    second = false;
    third = false;
    duration = 0;
    pauseCount = 0;
    seekCount = 0;
  };

  const incPause = () => pauseCount++;
  const incSeek = () => seekCount++;

  player.on('dispose', reset);
  player.on('loadstart', reset);
  player.on('tracking:pause', incPause);
  player.on('tracking:seek', incSeek);
  player.on('timeupdate', function() {
    const curTime = +player.currentTime().toFixed(0);
    const data = {
      seekCount,
      pauseCount,
      currentTime: curTime,
      duration
    };

    switch (curTime) {
    case first:
      first = false;
      player.trigger('tracking:first-quarter', data);
      break;
    case second:
      second = false;
      player.trigger('tracking:second-quarter', data);
      break;
    case third:
      third = false;
      player.trigger('tracking:third-quarter', data);
      break;
    }

  });
  player.on('ended', function() {
    const data = {
      seekCount,
      pauseCount,
      currentTime: duration,
      duration
    };

    player.trigger('tracking:fourth-quarter', data);
  });

  player.on('durationchange', function() {
    duration = +player.duration().toFixed(0);
    if (duration > 0) {
      const quarter = (duration / 4).toFixed(0);

      first = +quarter;
      second = +quarter * 2;
      third = +quarter * 3;
    }
  });
};

export default PercentileTracking;
<commit_msg>Add failsafe to get duration during `timeupdate` (#20)<commit_after>/**
 * Track Overall Percentile (1st, 2nd, 3rd, and 4th) of Completion
 * This event triggers each quarter of a video.
 *
 * Example Usage:
 * player.on('tracking:first-quarter', (e, data) => console.log(data))
 * player.on('tracking:second-quarter', (e, data) => console.log(data))
 * player.on('tracking:third-quarter', (e, data) => console.log(data))
 * player.on('tracking:fourth-quarter', (e, data) => console.log(data))
 *
 * Data Attributes:
 * => pauseCount:       Total number of Pause events triggered
 * => seekCount:        Total number of Seek events triggered
 * => currentTime:      Current second video is on
 * => duration:         Total duration of video
 *
 * @function PercentileTracking
 * @param    {Object} [config={}]
 *           An object of config left to the plugin author to define.
 */

const PercentileTracking = function(config) {
  const player = this;
  let first = false;
  let second = false;
  let third = false;
  let duration = null;
  let pauseCount = 0;
  let seekCount = 0;

  const reset = function(e) {
    first = false;
    second = false;
    third = false;
    duration = null;
    pauseCount = 0;
    seekCount = 0;
  };

  const incPause = () => pauseCount++;
  const incSeek = () => seekCount++;

  const getDuration = function() {
    duration = +player.duration().toFixed(0);
    if (duration > 0) {
      const quarter = (duration / 4).toFixed(0);

      first = +quarter;
      second = +quarter * 2;
      third = +quarter * 3;
    }
  };

  player.on('dispose', reset);
  player.on('loadstart', reset);
  player.on('tracking:pause', incPause);
  player.on('tracking:seek', incSeek);
  player.on('timeupdate', function() {
    if (duration === null) {
      getDuration();
    }

    const curTime = +player.currentTime().toFixed(0);
    const data = {
      seekCount,
      pauseCount,
      currentTime: curTime,
      duration
    };

    switch (curTime) {
    case first:
      first = false;
      player.trigger('tracking:first-quarter', data);
      break;
    case second:
      second = false;
      player.trigger('tracking:second-quarter', data);
      break;
    case third:
      third = false;
      player.trigger('tracking:third-quarter', data);
      break;
    }

  });
  player.on('ended', function() {
    const data = {
      seekCount,
      pauseCount,
      currentTime: duration,
      duration
    };

    player.trigger('tracking:fourth-quarter', data);
  });

  player.on('durationchange', getDuration);
};

export default PercentileTracking;
<|endoftext|>


<commit_before>export Link from './Link'
export Match from './Match'
export Miss from './Miss'
export NavigationPrompt from 'react-history/Prompt'
export Redirect from './Redirect'

// High-level wrappers
export BrowserRouter from './BrowserRouter'
export HashRouter from './HashRouter'
export MemoryRouter from './MemoryRouter'
export ServerRouter from './ServerRouter'

// Low-level building block
export StaticRouter from './StaticRouter'

// Util for server rendering "pre-render match"
export matchPattern from './matchPattern'
<commit_msg>Explicitly export createServerRenderContext (#3820)<commit_after>export Link from './Link'
export Match from './Match'
export Miss from './Miss'
export NavigationPrompt from 'react-history/Prompt'
export Redirect from './Redirect'

// High-level wrappers
export BrowserRouter from './BrowserRouter'
export HashRouter from './HashRouter'
export MemoryRouter from './MemoryRouter'
export ServerRouter from './ServerRouter'

// Low-level building block
export StaticRouter from './StaticRouter'

// Util for server rendering "pre-render match"
export matchPattern from './matchPattern'

// Util for server rendering context
export createServerRenderContext from './createServerRenderContext'
<|endoftext|>


<commit_before>import Ember from 'ember';
import Model from 'ember-data/model';
import ValidationEngine from 'ghost-admin/mixins/validation-engine';
import attr from 'ember-data/attr';
import boundOneWay from 'ghost-admin/utils/bound-one-way';
import computed, {equal, filterBy} from 'ember-computed';
import injectService from 'ember-service/inject';
import moment from 'moment';
import observer from 'ember-metal/observer';
import {BLANK_DOC} from 'ghost-admin/components/gh-markdown-editor';
import {belongsTo, hasMany} from 'ember-data/relationships';
import {isBlank} from 'ember-utils';

// ember-cli-shims doesn't export these so we must get them manually
const {Comparable, compare} = Ember;

function statusCompare(postA, postB) {
    let status1 = postA.get('status');
    let status2 = postB.get('status');

    // if any of those is empty
    if (!status1 && !status2) {
        return 0;
    }

    if (!status1 && status2) {
        return -1;
    }

    if (!status2 && status1) {
        return 1;
    }

    // We have to make sure, that scheduled posts will be listed first
    // after that, draft and published will be sorted alphabetically and don't need
    // any manual comparison.

    if (status1 === 'scheduled' && (status2 === 'draft' || status2 === 'published')) {
        return -1;
    }

    if (status2 === 'scheduled' && (status1 === 'draft' || status1 === 'published')) {
        return 1;
    }

    return compare(status1.valueOf(), status2.valueOf());
}

function publishedAtCompare(postA, postB) {
    let published1 = postA.get('publishedAtUTC');
    let published2 = postB.get('publishedAtUTC');

    if (!published1 && !published2) {
        return 0;
    }

    if (!published1 && published2) {
        return -1;
    }

    if (!published2 && published1) {
        return 1;
    }

    return compare(published1.valueOf(), published2.valueOf());
}

export default Model.extend(Comparable, ValidationEngine, {
    config: injectService(),
    ghostPaths: injectService(),
    clock: injectService(),
    settings: injectService(),

    validationType: 'post',

    uuid: attr('string'),
    title: attr('string', {defaultValue: ''}),
    slug: attr('string'),
    mobiledoc: attr('json-string', {defaultValue: () => BLANK_DOC}),
    html: attr('string'),
    featureImage: attr('string'),
    featured: attr('boolean', {defaultValue: false}),
    page: attr('boolean', {defaultValue: false}),
    plaintext: attr('string'),
    status: attr('string', {defaultValue: 'draft'}),
    locale: attr('string'),
    metaTitle: attr('string'),
    metaDescription: attr('string'),
    author: belongsTo('user', {async: true}),
    authorId: attr('string'),
    updatedAtUTC: attr('moment-utc'),
    updatedBy: attr(),
    publishedAtUTC: attr('moment-utc'),
    publishedBy: belongsTo('user', {async: true}),
    createdAtUTC: attr('moment-utc'),
    createdBy: attr(),
    tags: hasMany('tag', {
        embedded: 'always',
        async: false
    }),
    url: attr('string'),

    scratch: null,
    titleScratch: null,

    // HACK: used for validation so that date/time can be validated based on
    // eventual status rather than current status
    statusScratch: null,

    // For use by date/time pickers - will be validated then converted to UTC
    // on save. Updated by an observer whenever publishedAtUTC changes.
    // Everything that revolves around publishedAtUTC only cares about the saved
    // value so this should be almost entirely internal
    publishedAtBlogDate: '',
    publishedAtBlogTime: '',

    metaTitleScratch: boundOneWay('metaTitle'),
    metaDescriptionScratch: boundOneWay('metaDescription'),

    isPublished: equal('status', 'published'),
    isDraft: equal('status', 'draft'),
    internalTags: filterBy('tags', 'isInternal', true),
    isScheduled: equal('status', 'scheduled'),

    absoluteUrl: computed('url', 'ghostPaths.url', 'config.blogUrl', function () {
        let blogUrl = this.get('config.blogUrl');
        let postUrl = this.get('url');
        return this.get('ghostPaths.url').join(blogUrl, postUrl);
    }),

    previewUrl: computed('uuid', 'ghostPaths.url', 'config.blogUrl', 'config.routeKeywords.preview', function () {
        let blogUrl = this.get('config.blogUrl');
        let uuid = this.get('uuid');
        let previewKeyword = this.get('config.routeKeywords.preview');
        // New posts don't have a preview
        if (!uuid) {
            return '';
        }
        return this.get('ghostPaths.url').join(blogUrl, previewKeyword, uuid);
    }),

    // check every second to see if we're past the scheduled time
    // will only re-compute if this property is being observed elsewhere
    pastScheduledTime: computed('isScheduled', 'publishedAtUTC', 'clock.second', function () {
        if (this.get('isScheduled')) {
            let now = moment.utc();
            let publishedAtUTC = this.get('publishedAtUTC') || now;
            let pastScheduledTime = publishedAtUTC.diff(now, 'hours', true) < 0;

            // force a recompute
            this.get('clock.second');

            return pastScheduledTime;
        } else {
            return false;
        }
    }),

    publishedAtBlogTZ: computed('publishedAtBlogDate', 'publishedAtBlogTime', 'settings.activeTimezone', {
        get() {
            return this._getPublishedAtBlogTZ();
        },
        set(key, value) {
            let momentValue = value ? moment(value) : null;
            this._setPublishedAtBlogStrings(momentValue);
            return this._getPublishedAtBlogTZ();
        }
    }),

    _getPublishedAtBlogTZ() {
        let publishedAtUTC = this.get('publishedAtUTC');
        let publishedAtBlogDate = this.get('publishedAtBlogDate');
        let publishedAtBlogTime = this.get('publishedAtBlogTime');
        let blogTimezone = this.get('settings.activeTimezone');

        if (!publishedAtUTC && isBlank(publishedAtBlogDate) && isBlank(publishedAtBlogTime)) {
            return null;
        }

        if (publishedAtBlogDate && publishedAtBlogTime) {
            let publishedAtBlog = moment.tz(`${publishedAtBlogDate} ${publishedAtBlogTime}`, blogTimezone);
            return publishedAtBlog;
        } else {
            return moment.tz(this.get('publishedAtUTC'), blogTimezone);
        }
    },

    _setPublishedAtBlogTZ: observer('publishedAtUTC', 'settings.activeTimezone', function () {
        let publishedAtUTC = this.get('publishedAtUTC');
        this._setPublishedAtBlogStrings(publishedAtUTC);
    }).on('init'),

    _setPublishedAtBlogStrings(momentDate) {
        if (momentDate) {
            let blogTimezone = this.get('settings.activeTimezone');
            let publishedAtBlog = moment.tz(momentDate, blogTimezone);

            this.set('publishedAtBlogDate', publishedAtBlog.format('YYYY-MM-DD'));
            this.set('publishedAtBlogTime', publishedAtBlog.format('HH:mm'));
        } else {
            this.set('publishedAtBlogDate', '');
            this.set('publishedAtBlogTime', '');
        }
    },

    // remove client-generated tags, which have `id: null`.
    // Ember Data won't recognize/update them automatically
    // when returned from the server with ids.
    // https://github.com/emberjs/data/issues/1829
    updateTags() {
        let tags = this.get('tags');
        let oldTags = tags.filterBy('id', null);

        tags.removeObjects(oldTags);
        oldTags.invoke('deleteRecord');
    },

    isAuthoredByUser(user) {
        return user.get('id') === this.get('authorId');
    },

    // a custom sort function is needed in order to sort the posts list the same way the server would:
    //     status: scheduled, draft, published
    //     publishedAt: DESC
    //     updatedAt: DESC
    //     id: DESC
    compare(postA, postB) {
        let updated1 = postA.get('updatedAtUTC');
        let updated2 = postB.get('updatedAtUTC');
        let idResult,
            publishedAtResult,
            statusResult,
            updatedAtResult;

        // when `updatedAt` is undefined, the model is still
        // being written to with the results from the server
        if (postA.get('isNew') || !updated1) {
            return -1;
        }

        if (postB.get('isNew') || !updated2) {
            return 1;
        }

        // TODO: revisit the ID sorting because we no longer have auto-incrementing IDs
        idResult = compare(postA.get('id'), postB.get('id'));
        statusResult = statusCompare(postA, postB);
        updatedAtResult = compare(updated1.valueOf(), updated2.valueOf());
        publishedAtResult = publishedAtCompare(postA, postB);

        if (statusResult === 0) {
            if (publishedAtResult === 0) {
                if (updatedAtResult === 0) {
                    // This should be DESC
                    return idResult * -1;
                }
                // This should be DESC
                return updatedAtResult * -1;
            }
            // This should be DESC
            return publishedAtResult * -1;
        }

        return statusResult;
    },

    // this is a hook added by the ValidationEngine mixin and is called after
    // successful validation and before this.save()
    //
    // the publishedAtBlog{Date/Time} strings are set separately so they can be
    // validated, grab that time if it exists and set the publishedAtUTC
    beforeSave() {
        let publishedAtBlogTZ = this.get('publishedAtBlogTZ');
        let publishedAtUTC = publishedAtBlogTZ ? publishedAtBlogTZ.utc() : null;

        this.set('publishedAtUTC', publishedAtUTC);
    }
});
<commit_msg>🐛  fix date with seconds (#753)<commit_after>import Ember from 'ember';
import Model from 'ember-data/model';
import ValidationEngine from 'ghost-admin/mixins/validation-engine';
import attr from 'ember-data/attr';
import boundOneWay from 'ghost-admin/utils/bound-one-way';
import computed, {equal, filterBy} from 'ember-computed';
import injectService from 'ember-service/inject';
import moment from 'moment';
import observer from 'ember-metal/observer';
import {BLANK_DOC} from 'ghost-admin/components/gh-markdown-editor';
import {belongsTo, hasMany} from 'ember-data/relationships';
import {isBlank} from 'ember-utils';

// ember-cli-shims doesn't export these so we must get them manually
const {Comparable, compare} = Ember;

function statusCompare(postA, postB) {
    let status1 = postA.get('status');
    let status2 = postB.get('status');

    // if any of those is empty
    if (!status1 && !status2) {
        return 0;
    }

    if (!status1 && status2) {
        return -1;
    }

    if (!status2 && status1) {
        return 1;
    }

    // We have to make sure, that scheduled posts will be listed first
    // after that, draft and published will be sorted alphabetically and don't need
    // any manual comparison.

    if (status1 === 'scheduled' && (status2 === 'draft' || status2 === 'published')) {
        return -1;
    }

    if (status2 === 'scheduled' && (status1 === 'draft' || status1 === 'published')) {
        return 1;
    }

    return compare(status1.valueOf(), status2.valueOf());
}

function publishedAtCompare(postA, postB) {
    let published1 = postA.get('publishedAtUTC');
    let published2 = postB.get('publishedAtUTC');

    if (!published1 && !published2) {
        return 0;
    }

    if (!published1 && published2) {
        return -1;
    }

    if (!published2 && published1) {
        return 1;
    }

    return compare(published1.valueOf(), published2.valueOf());
}

export default Model.extend(Comparable, ValidationEngine, {
    config: injectService(),
    ghostPaths: injectService(),
    clock: injectService(),
    settings: injectService(),

    validationType: 'post',

    uuid: attr('string'),
    title: attr('string', {defaultValue: ''}),
    slug: attr('string'),
    mobiledoc: attr('json-string', {defaultValue: () => BLANK_DOC}),
    html: attr('string'),
    featureImage: attr('string'),
    featured: attr('boolean', {defaultValue: false}),
    page: attr('boolean', {defaultValue: false}),
    plaintext: attr('string'),
    status: attr('string', {defaultValue: 'draft'}),
    locale: attr('string'),
    metaTitle: attr('string'),
    metaDescription: attr('string'),
    author: belongsTo('user', {async: true}),
    authorId: attr('string'),
    updatedAtUTC: attr('moment-utc'),
    updatedBy: attr(),
    publishedAtUTC: attr('moment-utc'),
    publishedBy: belongsTo('user', {async: true}),
    createdAtUTC: attr('moment-utc'),
    createdBy: attr(),
    tags: hasMany('tag', {
        embedded: 'always',
        async: false
    }),
    url: attr('string'),

    scratch: null,
    titleScratch: null,

    // HACK: used for validation so that date/time can be validated based on
    // eventual status rather than current status
    statusScratch: null,

    // For use by date/time pickers - will be validated then converted to UTC
    // on save. Updated by an observer whenever publishedAtUTC changes.
    // Everything that revolves around publishedAtUTC only cares about the saved
    // value so this should be almost entirely internal
    publishedAtBlogDate: '',
    publishedAtBlogTime: '',

    metaTitleScratch: boundOneWay('metaTitle'),
    metaDescriptionScratch: boundOneWay('metaDescription'),

    isPublished: equal('status', 'published'),
    isDraft: equal('status', 'draft'),
    internalTags: filterBy('tags', 'isInternal', true),
    isScheduled: equal('status', 'scheduled'),

    absoluteUrl: computed('url', 'ghostPaths.url', 'config.blogUrl', function () {
        let blogUrl = this.get('config.blogUrl');
        let postUrl = this.get('url');
        return this.get('ghostPaths.url').join(blogUrl, postUrl);
    }),

    previewUrl: computed('uuid', 'ghostPaths.url', 'config.blogUrl', 'config.routeKeywords.preview', function () {
        let blogUrl = this.get('config.blogUrl');
        let uuid = this.get('uuid');
        let previewKeyword = this.get('config.routeKeywords.preview');
        // New posts don't have a preview
        if (!uuid) {
            return '';
        }
        return this.get('ghostPaths.url').join(blogUrl, previewKeyword, uuid);
    }),

    // check every second to see if we're past the scheduled time
    // will only re-compute if this property is being observed elsewhere
    pastScheduledTime: computed('isScheduled', 'publishedAtUTC', 'clock.second', function () {
        if (this.get('isScheduled')) {
            let now = moment.utc();
            let publishedAtUTC = this.get('publishedAtUTC') || now;
            let pastScheduledTime = publishedAtUTC.diff(now, 'hours', true) < 0;

            // force a recompute
            this.get('clock.second');

            return pastScheduledTime;
        } else {
            return false;
        }
    }),

    publishedAtBlogTZ: computed('publishedAtBlogDate', 'publishedAtBlogTime', 'settings.activeTimezone', {
        get() {
            return this._getPublishedAtBlogTZ();
        },
        set(key, value) {
            let momentValue = value ? moment(value) : null;
            this._setPublishedAtBlogStrings(momentValue);
            return this._getPublishedAtBlogTZ();
        }
    }),

    _getPublishedAtBlogTZ() {
        let publishedAtUTC = this.get('publishedAtUTC');
        let publishedAtBlogDate = this.get('publishedAtBlogDate');
        let publishedAtBlogTime = this.get('publishedAtBlogTime');
        let blogTimezone = this.get('settings.activeTimezone');

        if (!publishedAtUTC && isBlank(publishedAtBlogDate) && isBlank(publishedAtBlogTime)) {
            return null;
        }

        if (publishedAtBlogDate && publishedAtBlogTime) {
            let publishedAtBlog = moment.tz(`${publishedAtBlogDate} ${publishedAtBlogTime}`, blogTimezone);

            /**
             * Note:
             * If you create a post and publish it, we send seconds to the database.
             * If you edit the post afterwards, ember would send the date without seconds, because
             * the `publishedAtUTC` is based on `publishedAtBlogTime`, which is only in seconds.
             * The date time picker doesn't use seconds.
             *
             * This condition prevents the case:
             *   - you edit a post, but you don't change the published_at time
             *   - we keep the original date with seconds
             *
             * See https://github.com/TryGhost/Ghost/issues/8603#issuecomment-309538395.
             */
            if (publishedAtBlog.diff(publishedAtUTC.clone().startOf('minutes')) === 0) {
                return publishedAtUTC;
            }

            return publishedAtBlog;
        } else {
            return moment.tz(this.get('publishedAtUTC'), blogTimezone);
        }
    },

    _setPublishedAtBlogTZ: observer('publishedAtUTC', 'settings.activeTimezone', function () {
        let publishedAtUTC = this.get('publishedAtUTC');
        this._setPublishedAtBlogStrings(publishedAtUTC);
    }).on('init'),

    _setPublishedAtBlogStrings(momentDate) {
        if (momentDate) {
            let blogTimezone = this.get('settings.activeTimezone');
            let publishedAtBlog = moment.tz(momentDate, blogTimezone);

            this.set('publishedAtBlogDate', publishedAtBlog.format('YYYY-MM-DD'));
            this.set('publishedAtBlogTime', publishedAtBlog.format('HH:mm'));
        } else {
            this.set('publishedAtBlogDate', '');
            this.set('publishedAtBlogTime', '');
        }
    },

    // remove client-generated tags, which have `id: null`.
    // Ember Data won't recognize/update them automatically
    // when returned from the server with ids.
    // https://github.com/emberjs/data/issues/1829
    updateTags() {
        let tags = this.get('tags');
        let oldTags = tags.filterBy('id', null);

        tags.removeObjects(oldTags);
        oldTags.invoke('deleteRecord');
    },

    isAuthoredByUser(user) {
        return user.get('id') === this.get('authorId');
    },

    // a custom sort function is needed in order to sort the posts list the same way the server would:
    //     status: scheduled, draft, published
    //     publishedAt: DESC
    //     updatedAt: DESC
    //     id: DESC
    compare(postA, postB) {
        let updated1 = postA.get('updatedAtUTC');
        let updated2 = postB.get('updatedAtUTC');
        let idResult,
            publishedAtResult,
            statusResult,
            updatedAtResult;

        // when `updatedAt` is undefined, the model is still
        // being written to with the results from the server
        if (postA.get('isNew') || !updated1) {
            return -1;
        }

        if (postB.get('isNew') || !updated2) {
            return 1;
        }

        // TODO: revisit the ID sorting because we no longer have auto-incrementing IDs
        idResult = compare(postA.get('id'), postB.get('id'));
        statusResult = statusCompare(postA, postB);
        updatedAtResult = compare(updated1.valueOf(), updated2.valueOf());
        publishedAtResult = publishedAtCompare(postA, postB);

        if (statusResult === 0) {
            if (publishedAtResult === 0) {
                if (updatedAtResult === 0) {
                    // This should be DESC
                    return idResult * -1;
                }
                // This should be DESC
                return updatedAtResult * -1;
            }
            // This should be DESC
            return publishedAtResult * -1;
        }

        return statusResult;
    },

    // this is a hook added by the ValidationEngine mixin and is called after
    // successful validation and before this.save()
    //
    // the publishedAtBlog{Date/Time} strings are set separately so they can be
    // validated, grab that time if it exists and set the publishedAtUTC
    beforeSave() {
        let publishedAtBlogTZ = this.get('publishedAtBlogTZ');
        let publishedAtUTC = publishedAtBlogTZ ? publishedAtBlogTZ.utc() : null;
        this.set('publishedAtUTC', publishedAtUTC);
    }
});
<|endoftext|>


<commit_before>var brain = require('brain');

var drama = function(dbot) {
    var dbot = dbot;
    var last = {};
    var options = {
        'backend': {
            'type': 'Redis',
            'options': {
                'hostname': 'localhost',
                'port': 6379,
                'name': 'dbotdrama'
            }
        },

        'thresholds': {
            'drama': 3,
            'beinganasshole': 3,
            'sd': 3, // self depracating
            'normal': 1
        },

        'def': 'normal'
    };
    var bayes = new brain.BayesianClassifier(options);

    var commands = {
        '~train': function(data, params) {
            if(data.user == dbot.admin || data.user == 'golem' || data.user == 'Sam') {
                bayes.train(last[params[1]][params[2]], params[3]);
                dbot.say(data.channel, 'Last thing ' + params[2] + ' said in ' +
                        params[1] + ' (' +  last[params[1]][params[2]] + ') classified as \'' + params[3] + '\'');
            }
        },

        '~rtrain': function(data, params) {
            if(data.user == dbot.admin || data.user == 'golem' || data.user == 'Sam') {
                var category = params[1];
                params.splice(0, 2);
                var msg = params.join(' ');
                bayes.train(msg, category);
                dbot.say(data.channel, '\'' + msg + '\' classified as \'' + category + '\'');
            }
        }
    }

    return {
        'onLoad': function() {
            return commands;
        },

        'listener': function(data) {
            var category = bayes.classify(data.message, function(category) {
                console.log(category + '!');
            }.bind(this));

            if(last.hasOwnProperty(data.channel)) {
               last[data.channel][data.user] = data.message;
            } else {
                last[data.channel] = { };
                last[data.channel][data.user] = data.message;
            }
        },

        'on': 'PRIVMSG'
    };
}

exports.fetch = function(dbot) {
    return drama(dbot);
};
<commit_msg>classify<commit_after>var brain = require('brain');

var drama = function(dbot) {
    var dbot = dbot;
    var last = {};
    var options = {
        'backend': {
            'type': 'Redis',
            'options': {
                'hostname': 'localhost',
                'port': 6379,
                'name': 'dbotdrama'
            }
        },

        'thresholds': {
            'drama': 3,
            'beinganasshole': 3,
            'sd': 3, // self depracating
            'normal': 1
        },

        'def': 'normal'
    };
    var bayes = new brain.BayesianClassifier(options);

    var commands = {
        '~train': function(data, params) {
            if(data.user == dbot.admin || data.user == 'golem' || data.user == 'Sam') {
                bayes.train(last[params[1]][params[2]], params[3]);
                dbot.say(data.channel, 'Last thing ' + params[2] + ' said in ' +
                        params[1] + ' (' +  last[params[1]][params[2]] + ') classified as \'' + params[3] + '\'');
            }
        },

        '~rtrain': function(data, params) {
            if(data.user == dbot.admin || data.user == 'golem' || data.user == 'Sam') {
                var category = params[1];
                params.splice(0, 2);
                var msg = params.join(' ');
                bayes.train(msg, category);
                dbot.say(data.channel, '\'' + msg + '\' classified as \'' + category + '\'');
            },

            '~classify': function() {
                bayes.classify(data.message, function(category) {
                    dbot.say(data.channel, 'Classified as: ' + category + '!');
                }.bind(this));
            }
        }
    }

    return {
        'onLoad': function() {
            return commands;
        },

        'listener': function(data) {
            var category = bayes.classify(data.message, function(category) {
                console.log(category + '!');
            }.bind(this));

            if(last.hasOwnProperty(data.channel)) {
               last[data.channel][data.user] = data.message;
            } else {
                last[data.channel] = { };
                last[data.channel][data.user] = data.message;
            }
        },

        'on': 'PRIVMSG'
    };
}

exports.fetch = function(dbot) {
    return drama(dbot);
};
<|endoftext|>


<commit_before>module.exports = {
    ark: {
        list: [
            'arkstone',
            'dodo-blue',
            'dodo-cyan',
            'dodo-default',
            'dodo-lime',
            'dodo-red',
            'dodo-yellow',
            'mushroom',
            'woodsign',
        ],
        type: 'author_flair_css_class',
    },
    battlefield1: {
        list: [
            'ctemod',
            'origin',
            'pc',
            'ps4',
            'psn',
            'xbox',
            'xboxone',
        ],
        type: 'author_flair_css_class',
    },
    conan: {
        list: [
            'mod',
            'volunteer mod',
        ],
        type: 'author_flair_text',
    },
    csgo: {
        list: [
            '/r/globaloffensive janitor',
            '/r/globaloffensive moderator',
            '/r/globaloffensive monsorator',
            '#r/globaloffensive admin',
            '3dmax fan',
            '400k hype',
            '5 year subreddit veteran',
            'astana dragons fan',
            'astralis fan',
            'baggage veteran',
            'banner competition #1 & #3 winner',
            'banner competition #1 winner',
            'banner competition #2 first place winner',
            'banner competition #2 second place winner',
            'banner competition #2 third place winner',
            'banner competition #3 fifth place',
            'banner competition #3 fourth place',
            'banner competition #3 second place',
            'banner competition #3 third place',
            'best ever moderator',
            'bloodhound',
            'bravado gaming fan',
            'bravo',
            'cache veteran',
            'cheeky moderator',
            'chroma',
            'clan-mystik fan',
            'cloud9 fan',
            'cloud9 g2a fan',
            'cobblestone veteran',
            'complexity fan',
            'copenhagen wolves fan',
            'counter logic gaming fan',
            'dat team fan',
            'don\'t make me moderate you',
            'dust 2 veteran',
            'envyus fan',
            'epsilon esports fan',
            'esc gaming fan',
            'extra life tournament caster',
            'extra life tournament finalist',
            'faze clan fan',
            'flipsid3 tactics fan',
            'flipside tactics fan',
            'fnatic fan',
            'fnatic fanatic',
            'g2 esports fan',
            'gambit gaming fan',
            'gamers2 fan',
            'genuine 500k party pin',
            'godsent fan',
            'guardian 2',
            'guardian elite',
            'guardian',
            'hellraisers fan',
            'https://youtu.be/hX21XHcdlM4?t=15s',
            'ibuypower fan',
            'inferno veteran',
            'italy veteran',
            'keyd stars fan',
            'kinguin fan',
            'ldlc fan',
            'legendary chicken master',
            'legendary lobster master',
            'lgb esports fan',
            'london consipracy fan',
            'london conspiracy fan',
            'luminosity gaming fan',
            'militia veteran',
            'mirage veteran',
            'moderator',
            'mousesports fan',
            'myxmg fan',
            'n!faculty fan',
            'natus vincere fan',
            'ninjas in pyjamas fan',
            'north fan',
            'nuke vetera',
            'nuke veteran',
            'office veteran',
            'official dunkmaster',
            'one bot to rule them all',
            'optic gaming fan',
            'overpass veteran',
            'penta esports fan',
            'penta sports fan',
            'phoenix',
            'planetkey dynamics fan',
            'r/GlobalOffensive Zookeeper',
            'reason gaming fan',
            'recursive fan',
            'renegades fan',
            'sk gaming fan',
            'splyce fan',
            'tactics',
            'team astralis fan',
            'team dignitas fan',
            'team ebettle fan',
            'team envyus fan',
            'team immunity fan',
            'team kinguin fan',
            'team liquid fan',
            'team solomid fan',
            'team wolf fan',
            'titan fan',
            'top notch moderation or your money back',
            'train veteran',
            'trial moderator',
            'tsm kinguin fan',
            'universal soldiers fan',
            'valeria',
            'verygames fan',
            'vexed gaming fan',
            'victory',
            'virtus pro fan',
            'virtus.pro fan',
            'vox eminor fan',
            'worst ever moderator',
            'xapso fan',
        ],
        type: 'author_flair_text',
    },
    elite: {
        list: [
            'bot img',
            'cmdr img alliance',
            'cmdr img cobra',
            'cmdr img empire',
            'cmdr img federation',
            'cmdr img sidey',
            'cmdr img skull',
            'cmdr img viper',
            'cmdr star',
            'cmdr',
            'competent cmdr',
            'dangerous cmdr',
            'empire cmdr img',
            'harmless cmdr',
            'img viper cmdr',
            'mostlyharmless cmdr',
            'novice cmdr',
            'star',
        ],
        type: 'author_flair_css_class',
    },
    pubg: {
        list: [
            'igncustom',
            'npan',
            'trophy',
        ],
        type: 'author_flair_css_class',
    },
    rainbow6: {
        list: [
            'ash',
            'ashnew',
            'bandit',
            'banditnew',
            'blackbeard',
            'blackbeardnew',
            'blitz',
            'blitznew',
            'buck',
            'bucknew',
            'capitao',
            'capitaonew',
            'castle',
            'castlenew',
            'caveira',
            'caveiranew',
            'continuum',
            'dexunited',
            'doc',
            'docnew',
            'echo',
            'echonew',
            'elevate',
            'eraeternity',
            'euronicsgaming',
            'flair-for-bot',
            'frost',
            'frostnew',
            'fuze',
            'fuzenew',
            'gifu',
            'glaz',
            'glaznew',
            'hibana',
            'hibananew',
            'iq',
            'iqnew',
            'jackal',
            'jackalnew',
            'jager',
            'jagernew',
            'kapkan',
            'kapkannew',
            'mira',
            'miranew',
            'mod',
            'montagne',
            'montagnenew',
            'mute',
            'mutenew',
            'pulse',
            'pulsenew',
            'recruit',
            'recruitnew',
            'rook',
            'rooknew',
            'santosdexterity',
            'sledge',
            'sledgenew',
            'smoke',
            'smokenew',
            'tachanka',
            'tachankanew',
            'thatcher',
            'thatchernew',
            'thermite',
            'thermitenew',
            'twitch',
            'twitchnew',
            'valkyrie',
            'valkyrienew',
        ],
        type: 'author_flair_css_class',
    },
    rimworld: {
        list: [
            'fun',
            'gold',
            'granite',
            'jade',
            'limestone',
            'marble',
            'mod',
            'mod2',
            'plasteel',
            'pmfaf',
            'sandstone',
            'ship',
            'silver',
            'slate',
            'steel',
            'uranium',
            'war',
            'wood',
        ],
        type: 'author_flair_css_class',
    },
};
<commit_msg>Update flairs for PUBG<commit_after>module.exports = {
    ark: {
        list: [
            'arkstone',
            'dodo-blue',
            'dodo-cyan',
            'dodo-default',
            'dodo-lime',
            'dodo-red',
            'dodo-yellow',
            'mushroom',
            'woodsign',
        ],
        type: 'author_flair_css_class',
    },
    battlefield1: {
        list: [
            'ctemod',
            'origin',
            'pc',
            'ps4',
            'psn',
            'xbox',
            'xboxone',
        ],
        type: 'author_flair_css_class',
    },
    conan: {
        list: [
            'mod',
            'volunteer mod',
        ],
        type: 'author_flair_text',
    },
    csgo: {
        list: [
            '/r/globaloffensive janitor',
            '/r/globaloffensive moderator',
            '/r/globaloffensive monsorator',
            '#r/globaloffensive admin',
            '3dmax fan',
            '400k hype',
            '5 year subreddit veteran',
            'astana dragons fan',
            'astralis fan',
            'baggage veteran',
            'banner competition #1 & #3 winner',
            'banner competition #1 winner',
            'banner competition #2 first place winner',
            'banner competition #2 second place winner',
            'banner competition #2 third place winner',
            'banner competition #3 fifth place',
            'banner competition #3 fourth place',
            'banner competition #3 second place',
            'banner competition #3 third place',
            'best ever moderator',
            'bloodhound',
            'bravado gaming fan',
            'bravo',
            'cache veteran',
            'cheeky moderator',
            'chroma',
            'clan-mystik fan',
            'cloud9 fan',
            'cloud9 g2a fan',
            'cobblestone veteran',
            'complexity fan',
            'copenhagen wolves fan',
            'counter logic gaming fan',
            'dat team fan',
            'don\'t make me moderate you',
            'dust 2 veteran',
            'envyus fan',
            'epsilon esports fan',
            'esc gaming fan',
            'extra life tournament caster',
            'extra life tournament finalist',
            'faze clan fan',
            'flipsid3 tactics fan',
            'flipside tactics fan',
            'fnatic fan',
            'fnatic fanatic',
            'g2 esports fan',
            'gambit gaming fan',
            'gamers2 fan',
            'genuine 500k party pin',
            'godsent fan',
            'guardian 2',
            'guardian elite',
            'guardian',
            'hellraisers fan',
            'https://youtu.be/hX21XHcdlM4?t=15s',
            'ibuypower fan',
            'inferno veteran',
            'italy veteran',
            'keyd stars fan',
            'kinguin fan',
            'ldlc fan',
            'legendary chicken master',
            'legendary lobster master',
            'lgb esports fan',
            'london consipracy fan',
            'london conspiracy fan',
            'luminosity gaming fan',
            'militia veteran',
            'mirage veteran',
            'moderator',
            'mousesports fan',
            'myxmg fan',
            'n!faculty fan',
            'natus vincere fan',
            'ninjas in pyjamas fan',
            'north fan',
            'nuke vetera',
            'nuke veteran',
            'office veteran',
            'official dunkmaster',
            'one bot to rule them all',
            'optic gaming fan',
            'overpass veteran',
            'penta esports fan',
            'penta sports fan',
            'phoenix',
            'planetkey dynamics fan',
            'r/GlobalOffensive Zookeeper',
            'reason gaming fan',
            'recursive fan',
            'renegades fan',
            'sk gaming fan',
            'splyce fan',
            'tactics',
            'team astralis fan',
            'team dignitas fan',
            'team ebettle fan',
            'team envyus fan',
            'team immunity fan',
            'team kinguin fan',
            'team liquid fan',
            'team solomid fan',
            'team wolf fan',
            'titan fan',
            'top notch moderation or your money back',
            'train veteran',
            'trial moderator',
            'tsm kinguin fan',
            'universal soldiers fan',
            'valeria',
            'verygames fan',
            'vexed gaming fan',
            'victory',
            'virtus pro fan',
            'virtus.pro fan',
            'vox eminor fan',
            'worst ever moderator',
            'xapso fan',
        ],
        type: 'author_flair_text',
    },
    elite: {
        list: [
            'bot img',
            'cmdr img alliance',
            'cmdr img cobra',
            'cmdr img empire',
            'cmdr img federation',
            'cmdr img sidey',
            'cmdr img skull',
            'cmdr img viper',
            'cmdr star',
            'cmdr',
            'competent cmdr',
            'dangerous cmdr',
            'empire cmdr img',
            'harmless cmdr',
            'img viper cmdr',
            'mostlyharmless cmdr',
            'novice cmdr',
            'star',
        ],
        type: 'author_flair_css_class',
    },
    pubg: {
        list: [
            'gpan',
            'igncustom',
            'npan',
            'trophy',
        ],
        type: 'author_flair_css_class',
    },
    rainbow6: {
        list: [
            'ash',
            'ashnew',
            'bandit',
            'banditnew',
            'blackbeard',
            'blackbeardnew',
            'blitz',
            'blitznew',
            'buck',
            'bucknew',
            'capitao',
            'capitaonew',
            'castle',
            'castlenew',
            'caveira',
            'caveiranew',
            'continuum',
            'dexunited',
            'doc',
            'docnew',
            'echo',
            'echonew',
            'elevate',
            'eraeternity',
            'euronicsgaming',
            'flair-for-bot',
            'frost',
            'frostnew',
            'fuze',
            'fuzenew',
            'gifu',
            'glaz',
            'glaznew',
            'hibana',
            'hibananew',
            'iq',
            'iqnew',
            'jackal',
            'jackalnew',
            'jager',
            'jagernew',
            'kapkan',
            'kapkannew',
            'mira',
            'miranew',
            'mod',
            'montagne',
            'montagnenew',
            'mute',
            'mutenew',
            'pulse',
            'pulsenew',
            'recruit',
            'recruitnew',
            'rook',
            'rooknew',
            'santosdexterity',
            'sledge',
            'sledgenew',
            'smoke',
            'smokenew',
            'tachanka',
            'tachankanew',
            'thatcher',
            'thatchernew',
            'thermite',
            'thermitenew',
            'twitch',
            'twitchnew',
            'valkyrie',
            'valkyrienew',
        ],
        type: 'author_flair_css_class',
    },
    rimworld: {
        list: [
            'fun',
            'gold',
            'granite',
            'jade',
            'limestone',
            'marble',
            'mod',
            'mod2',
            'plasteel',
            'pmfaf',
            'sandstone',
            'ship',
            'silver',
            'slate',
            'steel',
            'uranium',
            'war',
            'wood',
        ],
        type: 'author_flair_css_class',
    },
};
<|endoftext|>


<commit_before>/*
 * ------------------------------------------
 * 分页器控件基类封装实现文件
 * @version  1.0
 * @author   genify(caijf@corp.netease.com)
 * ------------------------------------------
 */
var f = function(){
    // variable declaration
    var _  = NEJ.P,
        _o = NEJ.O,
        _r = NEJ.R,
        _e = _('nej.e'),
        _u = _('nej.u'),
        _p = _('nej.ui'),
        _pro,
        _seed_css,
        _seed_page;
    if (!!_p._$$AbstractPager) return;
    /**
     * 分页器控件基类封装

     * 页面结构举例
     * [code type="html"]
     *
page

     *
page

     * [/code]
     * 脚本举例
     * [code]
     *   // 第一步：继承此类，新建一个子类
     *   _p._$$Pager = NEJ.C();
     *   _proPager = _p._$$Pager._$extend(_p._$$AbstractPager);
     *
     *   // 调用父类reset方法后，实例化一个NEJ.P('nej.ut')._$page对象，首尾页用数字表示
     *   // 表现为,上一页 1 2 3.. 10 下一页
     *   // 或者实例化一个page.simple对象，首尾页需要辅助，数字可能不会出现
     *   // 表现为,首页 上一页 5 6 7 8 9 10 下一页 末页
     *   _proPager.__reset = function(_options){
     *       this.__supReset(_options);
     *       this.__page = _t._$$Page._$allocate(this.__popt);
     *   };
     *
     *   // reset之前生成需要的页码结构
     *   _proPager.__initNode = function(){
     *
     *   };
     *
     *   // reset之前生成需要的页码结构
     *   _proPager.__initNodeTemplate = function(){
     *      // _seed_html根据需求定制
     *      this.__seed_html = _seed_html;
     *   };
     *
     *   // 第二步：生成一个pager实例
     *   // 总页数10，默认第一页
     *   var _pager = p._$$Pager._$allocate({
     *       parent:'pagerCnt',
     *       onchange: function(_event){},
     *       total: 10,
     *       index:1
     *   });
     * [/code]
     * @class   {nej.ui._$$AbstractPager} 分页器控件封装
     * @extends {nej.ui._$$Abstract}
     * @param   {Object} 可选配置参数，已处理参数列表如下
     * @config  {Number} index  当前页码
     * @config  {Number} total  总页码数
     * @config  {Number} number 显示页数
     * @config  {Number} limit  总页数限制
     * @config  {Object} label  按钮文案，{prev:'<',next:'>'}
     *
     * [hr]
     * 页码切换事件，输入{last:3,index:1,total:12}
     * @event  {onchange}
     * @param  {Object} 页码状态对象
     * @config {Number} last  上一次的页码
     * @config {Number} index 当前要切换的页面
     * @config {Number} total 总页面数
     *
     */
    _p._$$AbstractPager = NEJ.C();
    _pro = _p._$$AbstractPager._$extend(_p._$$Abstract);
    /**
     * 控件重置
     * @protected
     * @method {__reset}
     * @param  {Object} 可选配置参数
     * @return {Void}
     */
    _pro.__reset = function(_options){
        this.__bopt = NEJ.X({},_options);
        this.__popt = NEJ.X({},_options);
        delete this.__bopt.onchange;
        this.__popt.onchange =
            this.__onChange._$bind(this);
        this.__supReset(_options);
        this.__doResetNumber({
            number:_options.number,
            label:_options.label||_o
        });
    };
    /**
     * 控件销毁
     * @protected
     * @method {__destroy}
     * @return {Void}
     */
    _pro.__destroy = function(){
        this.__supDestroy();
        if (!!this.__page){
            this.__page._$recycle();
            delete this.__page;
        }
        delete this.__bopt;
        delete this.__popt;
        this._$unbind();
        this.__body.innerHTML = ' ';
    };
    /**
     * 初始化外观信息
     * @protected
     * @method {__initXGui}
     * @return {Void}
     */
    _pro.__initXGui = function(){
        this.__seed_css  = _seed_css;
    };
    /**
     * 重置页码数
     * @return {Void}
     */
    _pro.__doResetNumber = function(_data){
        _e._$renderHtmlTemplate(
            this.__body,_seed_page,_data
        );
        var _seed = _e._$getHtmlTemplateSeed();
        this.__popt.list = _e._$getByClassName(
                this.__body,
                'js-i-'+_seed
        );
        this.__popt.pbtn = (
            _e._$getByClassName(
                this.__body,
                'js-p-'+_seed
            )||_r
        )[0];
        this.__popt.nbtn = (
            _e._$getByClassName(
                this.__body,
                'js-n-'+_seed
            )||_r
        )[0];
    };
    /**
     * 初始化节点
     * @protected
     * @method {__initNode}
     * @return {Void}
     */
    _pro.__initNode = function(){
        this.__supInitNode();
    };
    /**
     * 生成页码列表html代码
     * @protected
     * @method {__doGenPageListXhtml}
     * @param  {Object} 页码列表信息
     * @return {String} 页码列表html代码
     */
    _pro.__doGenPageListXhtml = function(_data){
        return _e._$getHtmlTemplate(_seed_page,_data);
    };
    /**
     * 页面变化触发事件
     * @protected
     * @method {__onChange}
     * @param  {Object} 事件对象
     * @return {Void}
     */
    _pro.__onChange = function(_event){
        if (this.__flag) return;
        var _index = _event.index,
            _total = _event.total;
        // sync pagers
        this.__flag = !0;
        this._$updatePage(_index,_total);
        _u._$forEach(this.__binders,
            function(_pager){
                _pager._$updatePage(_index,_total);
            });
        this.__flag = !1;
        this._$dispatchEvent('onchange',_event);
    };
    /**
     * 绑定联动分页器

     * 脚本举例
     * [code]
     *   // 绑定一个联动翻页器
     *   _pager._$bind('pagerCnt2')
     * [/code]
     * @method {_$bind}
     * @param  {String|Node} 联动分页器父容器
     * @return {nej.ui._$$AbstractPager}
     */
    _pro._$bind = function(_parent){
        _parent = _e._$get(_parent);
        if (!_parent) return this;
        var _opt = NEJ.X(
            {},this.__bopt
        );
        _opt.parent = _parent;
        _opt.index = this._$getIndex();
        _opt.total = this._$getTotal();
        var _pager = this.constructor._$allocate(_opt);
        _pager._$setEvent('onchange',this.__popt.onchange);
        if (!this.__binders) this.__binders = [];
        this.__binders.push(_pager);
        return this;
    };
    /**
     * 解除联动分页器

     * 脚本举例
     * [code]
     *   // 解绑所以联动翻页器
     *   _pager._$unbind()
     * [/code]
     * @method {_$unbind}
     * @return {nej.ui._$$AbstractPager}
     */
    _pro._$unbind = (function(){
        var _doRemove = function(_pager,_index,_list){
            _pager._$recycle();
            _list.splice(_index,1);
        };
        return function(){
            _u._$reverseEach(this.__binders,_doRemove);
        };
    })();
    /**
     * 跳转至指定页码

     * 脚本举例
     * [code]
     *   // 设置页码到第二页
     *   _pager._$setIndex(2)
     * [/code]
     * @method {_$setIndex}
     * @param  {Number} 页码
     * @return {nej.ui._$$AbstractPager}
     */
    _pro._$setIndex = function(_index){
        if (!this.__page) return;
        this.__page._$setIndex(_index);
    };
    /**
     * 取当前页码

     * 脚本举例
     * [code]
     *   // 取当前页码
     *   _pager._$getIndex()
     * [/code]
     * @method {_$getIndex}
     * @return {Number} 当前页码
     */
    _pro._$getIndex = function(){
        if (!this.__page) return 1;
        return this.__page._$getIndex();
    };
    /**
     * 取总页数

     * 脚本举例
     * [code]
     *   // 取总页数
     *   _pager._$getTotal()
     * [/code]
     * @method {_$getTotal}
     * @return {Number} 总页数
     */
    _pro._$getTotal = function(){
        if (!this.__page) return 1;
        return this.__page._$getTotal();
    };
    /**
     * 更新页码信息

     * 脚本举例
     * [code]
     *   // 重新设置默认页和总页数
     *   _pager._$updatePage(2,10);
     * [/code]
     * @method {_$updatePage}
     * @param  {Number} 当前页码
     * @param  {Number} 总页码数
     * @return {nej.ui._$$AbstractPager}
     */
    _pro._$updatePage = function(_index,_total){
        if (!this.__page) return;
        this.__page._$updatePage(_index,_total);
    };
    /**
     * 更新总页数
     * @return {Void}
     */
    _pro._$updateTotal = function(_total){
        if (!this.__page) return;
        this.__page._$updateTotal(_total);
    };
    // ui css text
    _seed_css = _e._$pushCSSText('\
        .#<uispace>{font-size:12px;line-height:160%;}\
        .#<uispace> a{margin:0 2px;padding:2px 8px;color:#333;border:1px solid #aaa;text-decoration:none;}\
        .#<uispace> .js-disabled{cursor:default;}\
        .#<uispace> .js-selected{cursor:default;background:#bbb;}\
    ');
    _seed_page = _e._$addHtmlTemplate('\
        {trim}\
        {if !defined("noprv")||!noprv}\
        ${label.prev||"上一页"}\
        {/if}\
        {list 1..number as x}\
        \
        {/list}\
        {if !defined("nonxt")||!nonxt}\
        ${label.next||"下一页"}\
        {/if}\
        {/trim}\
    ');
};
NEJ.define(
    '{lib}ui/pager/pager.base.js',[
    '{lib}ui/base.js'
],f);<commit_msg>recycle util pager first for ui pager destroy<commit_after>/*
 * ------------------------------------------
 * 分页器控件基类封装实现文件
 * @version  1.0
 * @author   genify(caijf@corp.netease.com)
 * ------------------------------------------
 */
var f = function(){
    // variable declaration
    var _  = NEJ.P,
        _o = NEJ.O,
        _r = NEJ.R,
        _e = _('nej.e'),
        _u = _('nej.u'),
        _p = _('nej.ui'),
        _pro,
        _seed_css,
        _seed_page;
    if (!!_p._$$AbstractPager) return;
    /**
     * 分页器控件基类封装

     * 页面结构举例
     * [code type="html"]
     *
page

     *
page

     * [/code]
     * 脚本举例
     * [code]
     *   // 第一步：继承此类，新建一个子类
     *   _p._$$Pager = NEJ.C();
     *   _proPager = _p._$$Pager._$extend(_p._$$AbstractPager);
     *
     *   // 调用父类reset方法后，实例化一个NEJ.P('nej.ut')._$page对象，首尾页用数字表示
     *   // 表现为,上一页 1 2 3.. 10 下一页
     *   // 或者实例化一个page.simple对象，首尾页需要辅助，数字可能不会出现
     *   // 表现为,首页 上一页 5 6 7 8 9 10 下一页 末页
     *   _proPager.__reset = function(_options){
     *       this.__supReset(_options);
     *       this.__page = _t._$$Page._$allocate(this.__popt);
     *   };
     *
     *   // reset之前生成需要的页码结构
     *   _proPager.__initNode = function(){
     *
     *   };
     *
     *   // reset之前生成需要的页码结构
     *   _proPager.__initNodeTemplate = function(){
     *      // _seed_html根据需求定制
     *      this.__seed_html = _seed_html;
     *   };
     *
     *   // 第二步：生成一个pager实例
     *   // 总页数10，默认第一页
     *   var _pager = p._$$Pager._$allocate({
     *       parent:'pagerCnt',
     *       onchange: function(_event){},
     *       total: 10,
     *       index:1
     *   });
     * [/code]
     * @class   {nej.ui._$$AbstractPager} 分页器控件封装
     * @extends {nej.ui._$$Abstract}
     * @param   {Object} 可选配置参数，已处理参数列表如下
     * @config  {Number} index  当前页码
     * @config  {Number} total  总页码数
     * @config  {Number} number 显示页数
     * @config  {Number} limit  总页数限制
     * @config  {Object} label  按钮文案，{prev:'<',next:'>'}
     *
     * [hr]
     * 页码切换事件，输入{last:3,index:1,total:12}
     * @event  {onchange}
     * @param  {Object} 页码状态对象
     * @config {Number} last  上一次的页码
     * @config {Number} index 当前要切换的页面
     * @config {Number} total 总页面数
     *
     */
    _p._$$AbstractPager = NEJ.C();
    _pro = _p._$$AbstractPager._$extend(_p._$$Abstract);
    /**
     * 控件重置
     * @protected
     * @method {__reset}
     * @param  {Object} 可选配置参数
     * @return {Void}
     */
    _pro.__reset = function(_options){
        this.__bopt = NEJ.X({},_options);
        this.__popt = NEJ.X({},_options);
        delete this.__bopt.onchange;
        this.__popt.onchange =
            this.__onChange._$bind(this);
        this.__supReset(_options);
        this.__doResetNumber({
            number:_options.number,
            label:_options.label||_o
        });
    };
    /**
     * 控件销毁
     * @protected
     * @method {__destroy}
     * @return {Void}
     */
    _pro.__destroy = function(){
        if (!!this.__page){
            this.__page._$recycle();
            delete this.__page;
        }
        this.__supDestroy();
        delete this.__bopt;
        delete this.__popt;
        this._$unbind();
        this.__body.innerHTML = ' ';
    };
    /**
     * 初始化外观信息
     * @protected
     * @method {__initXGui}
     * @return {Void}
     */
    _pro.__initXGui = function(){
        this.__seed_css  = _seed_css;
    };
    /**
     * 重置页码数
     * @return {Void}
     */
    _pro.__doResetNumber = function(_data){
        _e._$renderHtmlTemplate(
            this.__body,_seed_page,_data
        );
        var _seed = _e._$getHtmlTemplateSeed();
        this.__popt.list = _e._$getByClassName(
                this.__body,
                'js-i-'+_seed
        );
        this.__popt.pbtn = (
            _e._$getByClassName(
                this.__body,
                'js-p-'+_seed
            )||_r
        )[0];
        this.__popt.nbtn = (
            _e._$getByClassName(
                this.__body,
                'js-n-'+_seed
            )||_r
        )[0];
    };
    /**
     * 初始化节点
     * @protected
     * @method {__initNode}
     * @return {Void}
     */
    _pro.__initNode = function(){
        this.__supInitNode();
    };
    /**
     * 生成页码列表html代码
     * @protected
     * @method {__doGenPageListXhtml}
     * @param  {Object} 页码列表信息
     * @return {String} 页码列表html代码
     */
    _pro.__doGenPageListXhtml = function(_data){
        return _e._$getHtmlTemplate(_seed_page,_data);
    };
    /**
     * 页面变化触发事件
     * @protected
     * @method {__onChange}
     * @param  {Object} 事件对象
     * @return {Void}
     */
    _pro.__onChange = function(_event){
        if (this.__flag) return;
        var _index = _event.index,
            _total = _event.total;
        // sync pagers
        this.__flag = !0;
        this._$updatePage(_index,_total);
        _u._$forEach(this.__binders,
            function(_pager){
                _pager._$updatePage(_index,_total);
            });
        this.__flag = !1;
        this._$dispatchEvent('onchange',_event);
    };
    /**
     * 绑定联动分页器

     * 脚本举例
     * [code]
     *   // 绑定一个联动翻页器
     *   _pager._$bind('pagerCnt2')
     * [/code]
     * @method {_$bind}
     * @param  {String|Node} 联动分页器父容器
     * @return {nej.ui._$$AbstractPager}
     */
    _pro._$bind = function(_parent){
        _parent = _e._$get(_parent);
        if (!_parent) return this;
        var _opt = NEJ.X(
            {},this.__bopt
        );
        _opt.parent = _parent;
        _opt.index = this._$getIndex();
        _opt.total = this._$getTotal();
        var _pager = this.constructor._$allocate(_opt);
        _pager._$setEvent('onchange',this.__popt.onchange);
        if (!this.__binders) this.__binders = [];
        this.__binders.push(_pager);
        return this;
    };
    /**
     * 解除联动分页器

     * 脚本举例
     * [code]
     *   // 解绑所以联动翻页器
     *   _pager._$unbind()
     * [/code]
     * @method {_$unbind}
     * @return {nej.ui._$$AbstractPager}
     */
    _pro._$unbind = (function(){
        var _doRemove = function(_pager,_index,_list){
            _pager._$recycle();
            _list.splice(_index,1);
        };
        return function(){
            _u._$reverseEach(this.__binders,_doRemove);
        };
    })();
    /**
     * 跳转至指定页码

     * 脚本举例
     * [code]
     *   // 设置页码到第二页
     *   _pager._$setIndex(2)
     * [/code]
     * @method {_$setIndex}
     * @param  {Number} 页码
     * @return {nej.ui._$$AbstractPager}
     */
    _pro._$setIndex = function(_index){
        if (!this.__page) return;
        this.__page._$setIndex(_index);
    };
    /**
     * 取当前页码

     * 脚本举例
     * [code]
     *   // 取当前页码
     *   _pager._$getIndex()
     * [/code]
     * @method {_$getIndex}
     * @return {Number} 当前页码
     */
    _pro._$getIndex = function(){
        if (!this.__page) return 1;
        return this.__page._$getIndex();
    };
    /**
     * 取总页数

     * 脚本举例
     * [code]
     *   // 取总页数
     *   _pager._$getTotal()
     * [/code]
     * @method {_$getTotal}
     * @return {Number} 总页数
     */
    _pro._$getTotal = function(){
        if (!this.__page) return 1;
        return this.__page._$getTotal();
    };
    /**
     * 更新页码信息

     * 脚本举例
     * [code]
     *   // 重新设置默认页和总页数
     *   _pager._$updatePage(2,10);
     * [/code]
     * @method {_$updatePage}
     * @param  {Number} 当前页码
     * @param  {Number} 总页码数
     * @return {nej.ui._$$AbstractPager}
     */
    _pro._$updatePage = function(_index,_total){
        if (!this.__page) return;
        this.__page._$updatePage(_index,_total);
    };
    /**
     * 更新总页数
     * @return {Void}
     */
    _pro._$updateTotal = function(_total){
        if (!this.__page) return;
        this.__page._$updateTotal(_total);
    };
    // ui css text
    _seed_css = _e._$pushCSSText('\
        .#<uispace>{font-size:12px;line-height:160%;}\
        .#<uispace> a{margin:0 2px;padding:2px 8px;color:#333;border:1px solid #aaa;text-decoration:none;}\
        .#<uispace> .js-disabled{cursor:default;}\
        .#<uispace> .js-selected{cursor:default;background:#bbb;}\
    ');
    _seed_page = _e._$addHtmlTemplate('\
        {trim}\
        {if !defined("noprv")||!noprv}\
        ${label.prev||"上一页"}\
        {/if}\
        {list 1..number as x}\
        \
        {/list}\
        {if !defined("nonxt")||!nonxt}\
        ${label.next||"下一页"}\
        {/if}\
        {/trim}\
    ');
};
NEJ.define(
    '{lib}ui/pager/pager.base.js',[
    '{lib}ui/base.js'
],f);<|endoftext|>


<commit_before>/*
	gmail module - send mails to Gmail server via OAuth2.0
	based on the gmail-sender-oauth npm

	ref: https://www.npmjs.com/package/gmail-sender-oauth

	Usage:
		this module will replace original behavior in UTIL.emailText if loaded

		steps to use:
			1. prepare a "client_secret.json" file in /keys directory (of scalra module or project)
			2. generate ServerAuthCode
			3. generate AccessToken

		see: https://www.npmjs.com/package/gmail-sender-oauth#authentication-with-access-token
		for how to perform step #2 and #3

			4. set generated ServerAuthCode and AccessToken into scalra's /config.js

				// mail server config
				EMAIL_CONFIG: {
					 user:   	'<GMAIL_USERNAME>',
					 password:	'<GMAIL_USERPASS>',
					 host:		'smtp.gmail.com',
					 ssl:		true,
					 gmailServerAuthCode: '<SERVER_AUTH_CODE>',
					 gmailAccessToken: {"access_token":"","refresh_token":"","token_type":"Bearer","expiry_date":0000}
				},
*/

// init gmail OAuth2.0 access
var gmailSender = require('gmail-sender-oauth');
var gmailApiSync = require('gmail-api-sync');
var l_gmailAccessToken = undefined;

//
//  gmail.js
//
//	send mails to Gmail
//
//	history:
//		2017-07-04	first version
//
// module object
var l_module = exports.module = {};
var l_name = 'Module.gmail';

//-----------------------------------------
// API definitions
//
//-----------------------------------------

SR.API.add('_gmailText', {
	from:		'string',
	to:			'string',
	subject:	'string',
	body:		'string'
}, function (args, onDone) {

	if (!l_gmailAccessToken) {
		return onDone('no gmail accessToken found!');
	}

	gmailSender.send(l_gmailAccessToken, args, function (err, resp) {
		if (err) {
			LOG.error(err, l_name);
			return onDone(err);
		}
		LOG.warn('email sent to Gmail with id: ' + resp.id, l_name);
		onDone(null, resp.id);
	});
})

//-----------------------------------------
// Server Event Handling
//
//-----------------------------------------

// module init
l_module.start = function (config, onDone) {
	// process config & verify correctness here
	if (typeof SR.Settings.EMAIL_CONFIG.gmailServerAuthCode === 'undefined') {
		return onDone();
	}

	var onAccessToken = function () {
		LOG.warn('accessToken: ' + JSON.stringify(l_gmailAccessToken), l_name);

		// replace UTIL.emailText
		UTIL.emailText = function (msg, onD) {
			SR.API._gmailText({
				from:		msg.from,
				to:			msg.to,
				subject:	msg.subject,
				body:		msg.text,
			}, onD)
		}

		UTIL.safeCall(onDone, null);
	}

	var loadSecret = function () {
		gmailSender.setClientSecretsFile(key_path);

		// get accessToken if available
		if (SR.Settings.EMAIL_CONFIG.gmailAccessToken) {
			l_gmailAccessToken = SR.Settings.EMAIL_CONFIG.gmailAccessToken;
			return onAccessToken();
		}

		// generate accessToken
		var serverAuthCode = SR.Settings.EMAIL_CONFIG.gmailServerAuthCode;

		gmailApiSync.setClientSecretsFile(key_path);
		gmailApiSync.getNewAccesToken(serverAuthCode,function (err, token){
			if (err) {
				LOG.error(err, l_name);
				return onDone(err);
			}

			l_gmailAccessToken = token;
			onAccessToken();
		});
	}

	// load client_secret.json file
	var key_path = SR.path.resolve(SR.Settings.PROJECT_PATH, 'keys', 'client_secret.json');
	SR.fs.stat(key_path, function (err) {
		if (err) {
			key_path = SR.path.resolve(SR.Settings.SR_PATH, 'keys', 'client_secret.json');
			SR.fs.stat(key_path, function (err) {
				if (err) {
					LOG.error('cannot find client_secret.json under either project or scalra directory', l_name);
					return onDone('client_secret.json not found');
				}
				loadSecret();
			})
		}
		loadSecret();
	});
}

// module shutdown
l_module.stop = function (onDone) {
	// close / release resources used
	UTIL.safeCall(onDone);
}
<commit_msg>syhu/scalra: add: more detailed desc on how to enable gmail module's config<commit_after>/*
	gmail module - send mails to Gmail server via OAuth2.0
	based on the gmail-sender-oauth npm

	ref: https://www.npmjs.com/package/gmail-sender-oauth

	Usage:
		this module will replace original behavior in UTIL.emailText if loaded

		steps to use:
			1. prepare a "client_secret.json" file in /keys directory (of scalra module or project)
				see: https://developers.google.com/gmail/api/quickstart/nodejs

			2. generate ServerAuthCode
			3. generate AccessToken

		see: https://www.npmjs.com/package/gmail-sender-oauth#authentication-with-access-token
		for how to perform step #2 and #3
		NOTE: the file "client_secret.json" should be placed in same directory as the generator code,
		when generating ServerAuthCode

			4. set generated ServerAuthCode and AccessToken into scalra's /config.js

				// mail server config
				EMAIL_CONFIG: {
					 user:   	'<GMAIL_USERNAME>',
					 password:	'<GMAIL_USERPASS>',
					 host:		'smtp.gmail.com',
					 ssl:		true,
					 gmailServerAuthCode: '<SERVER_AUTH_CODE>',
					 gmailAccessToken: {"access_token":"","refresh_token":"","token_type":"Bearer","expiry_date":0000}
				},
*/

// init gmail OAuth2.0 access
var gmailSender = require('gmail-sender-oauth');
var gmailApiSync = require('gmail-api-sync');
var l_gmailAccessToken = undefined;

//
//  gmail.js
//
//	send mails to Gmail
//
//	history:
//		2017-07-04	first version
//
// module object
var l_module = exports.module = {};
var l_name = 'Module.gmail';

//-----------------------------------------
// API definitions
//
//-----------------------------------------

SR.API.add('_gmailText', {
	from:		'string',
	to:			'string',
	subject:	'string',
	body:		'string'
}, function (args, onDone) {

	if (!l_gmailAccessToken) {
		return onDone('no gmail accessToken found!');
	}

	gmailSender.send(l_gmailAccessToken, args, function (err, resp) {
		if (err) {
			LOG.error(err, l_name);
			return onDone(err);
		}
		LOG.warn('email sent to Gmail with id: ' + resp.id, l_name);
		onDone(null, resp.id);
	});
})

//-----------------------------------------
// Server Event Handling
//
//-----------------------------------------

// module init
l_module.start = function (config, onDone) {
	// process config & verify correctness here
	if (typeof SR.Settings.EMAIL_CONFIG.gmailServerAuthCode === 'undefined') {
		return onDone();
	}

	var onAccessToken = function () {
		LOG.warn('accessToken: ' + JSON.stringify(l_gmailAccessToken), l_name);

		// replace UTIL.emailText
		UTIL.emailText = function (msg, onD) {
			SR.API._gmailText({
				from:		msg.from,
				to:			msg.to,
				subject:	msg.subject,
				body:		msg.text,
			}, onD)
		}

		UTIL.safeCall(onDone, null);
	}

	var loadSecret = function () {
		gmailSender.setClientSecretsFile(key_path);

		// get accessToken if available
		if (SR.Settings.EMAIL_CONFIG.gmailAccessToken) {
			l_gmailAccessToken = SR.Settings.EMAIL_CONFIG.gmailAccessToken;
			return onAccessToken();
		}

		// generate accessToken
		var serverAuthCode = SR.Settings.EMAIL_CONFIG.gmailServerAuthCode;

		gmailApiSync.setClientSecretsFile(key_path);
		gmailApiSync.getNewAccesToken(serverAuthCode,function (err, token){
			if (err) {
				LOG.error(err, l_name);
				return onDone(err);
			}

			l_gmailAccessToken = token;
			onAccessToken();
		});
	}

	// load client_secret.json file
	var key_path = SR.path.resolve(SR.Settings.PROJECT_PATH, 'keys', 'client_secret.json');
	SR.fs.stat(key_path, function (err) {
		if (err) {
			key_path = SR.path.resolve(SR.Settings.SR_PATH, 'keys', 'client_secret.json');
			SR.fs.stat(key_path, function (err) {
				if (err) {
					LOG.error('cannot find client_secret.json under either project or scalra directory', l_name);
					return onDone('client_secret.json not found');
				}
				loadSecret();
			})
		}
		loadSecret();
	});
}

// module shutdown
l_module.stop = function (onDone) {
	// close / release resources used
	UTIL.safeCall(onDone);
}
<|endoftext|>


<commit_before>"use strict";

// vendor
var express = require('express');
var http = require('http');
var app = express();
// setup helpers
var globals = require("./globals");
globals.install();

var config = require_core("server/config");

var package_json = require_core("../package.json");
var app_name = package_json.name;

// setup() fills these in
var socket,
    http_server,
    https_server;

function setup() {
  // Better stack traces
  require("longjohn");

  if (config.behind_proxy) {
    app.enable('trust proxy');
  }

  socket = require_core("server/socket");

  http_server = http.createServer(app);

  // Setup an HTTPS server
  var auth = require_core("server/auth");
  https_server = auth.setup_ssl_server(app);

  http.globalAgent.maxSockets = config.max_http_sockets;

  // Add timestamps
  require("./console").install();

  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));

  // Setup authentication
  app.use(express.cookieParser());

  var session = require("./session");
  session.install(app);

  // Opportunity for Authorization and other stuff
  var main = require_app("main");
  if (main.setup_app) {
    main.setup_app(app);
  }

  // parse POST request body bits
  app.use(express.bodyParser());

  //app.use(express.logger());
  app.use(express.compress());

  // setup static helpers
  app.use(express.static('app/static'));
  app.use(express.static('core/static'));

  // setup error handling
  //var errorHandlers = require_core("server/error_handlers");
  //app.use(errorHandlers.default);

  // lib
  var routes = require('./routes');
  routes.setup(app);
}

function setup_services(options) {
  setup();
}

function try_restart(server, port) {
  var retries = 0;

  return function(e) {
    if (e.code === 'EADDRINUSE') {
      console.log('Port', port, 'in use, retrying...');
      setTimeout(function () {
        try { server.close(); } catch(e) {}

        if (retries > 5) {
          console.log("Couldn't listen on port", port, ", exiting.");
          process.exit();
        }

        retries += 1;
        server.listen(port);
      }, 2000);
    }
  };
}
module.exports = {
  name: app_name,
  run: function() {
    var services = { web_server: true };
    if (!config.separate_services) { services.collector = true; }
    setup_services(services);

    var http_port = config.http_port;
    var https_port = config.https_port;
    socket.setup_io(app, http_server);
    http_server.listen(http_port);
    http_server.on('error', try_restart(http_server, http_port));

    console.log("Listening for HTTP connections on port", http_port);

    require_app("main").setup(services);

    // Setting up SSL server
    if (https_server && https_port) {
      console.log("Listening for HTTPS connections on port", https_port);
      socket.setup_io(app, https_server);
      https_server.listen(https_port);
      https_server.on('error', try_restart(https_server, https_port));
    }
    // End SSL Server
  },

  run_collector: function() {
    setup_services({
      collector: true
    });
  }
};
<commit_msg>add caching age<commit_after>"use strict";

// vendor
var express = require('express');
var http = require('http');
var app = express();
// setup helpers
var globals = require("./globals");
globals.install();

var config = require_core("server/config");

var package_json = require_core("../package.json");
var app_name = package_json.name;

// setup() fills these in
var socket,
    http_server,
    https_server;

function setup() {
  // Better stack traces
  require("longjohn");

  if (config.behind_proxy) {
    app.enable('trust proxy');
  }

  socket = require_core("server/socket");

  http_server = http.createServer(app);

  // Setup an HTTPS server
  var auth = require_core("server/auth");
  https_server = auth.setup_ssl_server(app);

  http.globalAgent.maxSockets = config.max_http_sockets;

  // Add timestamps
  require("./console").install();

  app.use(express.errorHandler({ dumpExceptions: true, showStack: true }));

  // Setup authentication
  app.use(express.cookieParser());

  var session = require("./session");
  session.install(app);

  // Opportunity for Authorization and other stuff
  var main = require_app("main");
  if (main.setup_app) {
    main.setup_app(app);
  }

  // parse POST request body bits
  app.use(express.bodyParser());

  //app.use(express.logger());
  app.use(express.compress());

  // setup static helpers
  var oneDay = 1000 * 60 * 60 * 24;
  var oneYear = oneDay * 365;
  app.use(express.static('app/static', { maxAge: oneYear }));
  app.use(express.static('core/static', { maxAge: oneYear }));

  // setup error handling
  //var errorHandlers = require_core("server/error_handlers");
  //app.use(errorHandlers.default);

  // lib
  var routes = require('./routes');
  routes.setup(app);
}

function setup_services(options) {
  setup();
}

function try_restart(server, port) {
  var retries = 0;

  return function(e) {
    if (e.code === 'EADDRINUSE') {
      console.log('Port', port, 'in use, retrying...');
      setTimeout(function () {
        try { server.close(); } catch(e) {}

        if (retries > 5) {
          console.log("Couldn't listen on port", port, ", exiting.");
          process.exit();
        }

        retries += 1;
        server.listen(port);
      }, 2000);
    }
  };
}
module.exports = {
  name: app_name,
  run: function() {
    var services = { web_server: true };
    if (!config.separate_services) { services.collector = true; }
    setup_services(services);

    var http_port = config.http_port;
    var https_port = config.https_port;
    socket.setup_io(app, http_server);
    http_server.listen(http_port);
    http_server.on('error', try_restart(http_server, http_port));

    console.log("Listening for HTTP connections on port", http_port);

    require_app("main").setup(services);

    // Setting up SSL server
    if (https_server && https_port) {
      console.log("Listening for HTTPS connections on port", https_port);
      socket.setup_io(app, https_server);
      https_server.listen(https_port);
      https_server.on('error', try_restart(https_server, https_port));
    }
    // End SSL Server
  },

  run_collector: function() {
    setup_services({
      collector: true
    });
  }
};
<|endoftext|>