<commit_before>/**
 * The controller communication manager:
 *  - manages the socket.io link
 *  - provides API to the backend device to use by views
 *
 * (c) 2014 Edouard Lafargue, ed@lafargue.name
 * All rights reserved.
 */

define(function(require) {
    "use strict";

    // linkManager is a reference to the parent link manager
    return function(linkManager) {

        var self = this;
        var lm = linkManager;
        var streaming = false;
        var streamingText = false;
        var livePoller = null; // Reference to the timer for live streaming
        var textPoller = null; // This is a poller to read from the radio TB buffer.

        var kxpa100 = false; // Keep track of the presence of an amplifier

        //////
        //  Standard API:
        // All link managers need this function:
        //////
        this.setBackendDriver = function() {
            lm.socket.emit('driver','elecraft');
        }

        //////
        // End of standard API
        //////

        // All commands below are fully free and depend on
        // the instrument's capabilities

        this.startTextStream = function() {
            //        this.streamingText = true;
            this.textPoller = setInterval(this.queryTB.bind(this), 700);
            return true;
        }

        this.stopTextStream = function() {
            if (typeof this.textPoller != 'undefined') {
                clearInterval(this.textPoller);
    //            this.streamingText = false;
            }
            return true;
        }

        this.sendText = function(text) {
            this.cc('KY ' + text +';');
        }

        this.queryTB = function() {
            this.cc('TB;');
        }

        this.cc = function(d) {
            lm.socket.emit('controllerCommand',d);
        }

        this.screen = function(n) {
            lm.socket.emit('controllerCommand', 'S:' + n);
        }

        this.getRequestedPower = function() {
            this.cc('PC;');
        }

        this.getMode = function() {
            this.cc('MD;');
        }

        this.setMode = function(code) {
            this.cc('MD' + code + ';');
        }

        this.setSubmode = function(submode) {
            var submodes = { "DATA A":"0", "AFSK A":"1", "FSK D":"2", "PSK D":"3"};
            this.cc('DT' + submodes[submode] + ';');
        }

        this.setVFO = function(f, vfo) {
            var freq = ("00000000000" + (parseInt(f*1e6).toString())).slice(-11); // Nifty, eh ?
            if (freq.indexOf("N") > -1) { // detect "NaN" in the string
                console.log("Invalid VFO spec");
                this.cc((vfo == 'A' || vfo == 'a') ? 'FA;' : 'FB;');
            } else {
                console.log("VFO" + vfo + ": " + freq);
                this.cc(((vfo == 'A' || vfo == 'a') ? 'FA' : 'FB') + freq + ';');
            }
            this.cc('BN;'); // Refresh band number (radio does not send it automatically)
        }

        this.setPower = function(p) {
            var pwr = ("000" + (parseInt(p).toString())).slice(-3); // Nifty, eh ?
            if (pwr.indexOf("N") > -1) { // detect "NaN" in the pwr
                this.cc('PC;');
            } else {
                console.log('PC' + pwr + ';');
                this.cc('PC'+ pwr + ';');
            }
        }

        this.setAG = function(ag) {
            var gain = ("000" + ag).slice(-3);
            this.cc('AG' + gain + ';');
        }

        this.setMG = function(mg) {
            var gain = ("000" + mg).slice(-3);
            this.cc('MG' + gain + ';');
        }

        this.setRG = function(rg) {
            // Need to translate "-60 to 0" into "190 to 250"
            this.cc('RG' + (rg+250) + ';');
        }

        this.setBW = function(bw) { // Bandwidth in kHz (0 to 4.0)
            var bandwidth = ("0000" + Math.floor(bw*100)).slice(-4);
            this.cc('BW' + bandwidth + ';');
        }

        this.setCT = function(ct) { // Center frequency
            var center = ("0000" + Math.floor(ct*1000)).slice(-4);
            this.cc('IS ' + center + ';'); // Note the space!
        }

        this.setBand = function(band) {
            // We use a band number in meters (with a "m"), this function translates into the KX3 values:
            var bands= { "160m":"00", "80m":"01", "60m":"02", "40m":"03", "30m":"04", "20m":"05", "17m":"06", "15m":"07", "12m":"08", "10m":"09", "6m":"10", "2m": "16" };
            var bandcode = bands[band];
            if (typeof(bandcode) != 'undefined') {
                this.cc('BN' + bandcode + ';');
            }
        }

        console.log('Started Elecraft link manager driver..');

    };

});

<commit_msg>Bugfix: make band change work again.<commit_after>/**
 * The controller communication manager:
 *  - manages the socket.io link
 *  - provides API to the backend device to use by views
 *
 * (c) 2014 Edouard Lafargue, ed@lafargue.name
 * All rights reserved.
 */

define(function(require) {
    "use strict";

    // linkManager is a reference to the parent link manager
    return function(linkManager) {

        var self = this;
        var lm = linkManager;
        var streaming = false;
        var streamingText = false;
        var livePoller = null; // Reference to the timer for live streaming
        var textPoller = null; // This is a poller to read from the radio TB buffer.

        var kxpa100 = false; // Keep track of the presence of an amplifier

        //////
        //  Standard API:
        // All link managers need this function:
        //////
        this.setBackendDriver = function() {
            lm.socket.emit('driver','elecraft');
        }

        //////
        // End of standard API
        //////

        // All commands below are fully free and depend on
        // the instrument's capabilities

        this.startTextStream = function() {
            //        this.streamingText = true;
            this.textPoller = setInterval(this.queryTB.bind(this), 700);
            return true;
        }

        this.stopTextStream = function() {
            if (typeof this.textPoller != 'undefined') {
                clearInterval(this.textPoller);
    //            this.streamingText = false;
            }
            return true;
        }

        this.sendText = function(text) {
            this.cc('KY ' + text +';');
        }

        this.queryTB = function() {
            this.cc('TB;');
        }

        this.cc = function(d) {
            lm.socket.emit('controllerCommand',d);
        }

        this.screen = function(n) {
            lm.socket.emit('controllerCommand', 'S:' + n);
        }

        this.getRequestedPower = function() {
            this.cc('PC;');
        }

        this.getMode = function() {
            this.cc('MD;');
        }

        this.setMode = function(code) {
            this.cc('MD' + code + ';');
        }

        this.setSubmode = function(submode) {
            var submodes = { "DATA A":"0", "AFSK A":"1", "FSK D":"2", "PSK D":"3"};
            this.cc('DT' + submodes[submode] + ';');
        }

        this.setVFO = function(f, vfo) {
            var freq = ("00000000000" + (parseInt(f*1e6).toString())).slice(-11); // Nifty, eh ?
            if (freq.indexOf("N") > -1) { // detect "NaN" in the string
                console.log("Invalid VFO spec");
                this.cc((vfo == 'A' || vfo == 'a') ? 'FA;' : 'FB;');
            } else {
                console.log("VFO" + vfo + ": " + freq);
                this.cc(((vfo == 'A' || vfo == 'a') ? 'FA' : 'FB') + freq + ';');
            }
            this.cc('BN;'); // Refresh band number (radio does not send it automatically)
        }

        this.setPower = function(p) {
            var pwr = ("000" + (parseInt(p).toString())).slice(-3); // Nifty, eh ?
            if (pwr.indexOf("N") > -1) { // detect "NaN" in the pwr
                this.cc('PC;');
            } else {
                console.log('PC' + pwr + ';');
                this.cc('PC'+ pwr + ';');
            }
        }

        this.setAG = function(ag) {
            var gain = ("000" + ag).slice(-3);
            this.cc('AG' + gain + ';');
        }

        this.setMG = function(mg) {
            var gain = ("000" + mg).slice(-3);
            this.cc('MG' + gain + ';');
        }

        this.setRG = function(rg) {
            // Need to translate "-60 to 0" into "190 to 250"
            this.cc('RG' + (rg+250) + ';');
        }

        this.setBW = function(bw) { // Bandwidth in kHz (0 to 4.0)
            var bandwidth = ("0000" + Math.floor(bw*100)).slice(-4);
            this.cc('BW' + bandwidth + ';');
        }

        this.setCT = function(ct) { // Center frequency
            var center = ("0000" + Math.floor(ct*1000)).slice(-4);
            this.cc('IS ' + center + ';'); // Note the space!
        }

        this.setBand = function(band) {
            // We use a band number in meters (with a "m"), this function translates into the KX3 values:
            var bands= { "160":"00", "80":"01", "60":"02", "40":"03", "30":"04", "20":"05", "17":"06", "15":"07", "12":"08", "10":"09", "6":"10", "2": "16" };
            var bandcode = bands[band];
            if (typeof(bandcode) != 'undefined') {
                this.cc('BN' + bandcode + ';');
            }
        }

        console.log('Started Elecraft link manager driver..');

    };

});

<|endoftext|>


<commit_before>/**
 * Author: krasu
 * Date: 9/18/13
 * Time: 14:21 AM
 */
var mongoose = require('mongoose'),
    async = require('async'),
    _ = require('lodash'),
    notifier = require('../utils/requests-notifications'),
    Requester = mongoose.model('Requester'),
    Schema = mongoose.Schema

var RequestSchema = new Schema({
    supporters: [
        {type: Schema.ObjectId, ref: 'Requester'}
    ],

    project: {
        ref: {type: Schema.ObjectId, ref: 'Project'},
        githubId: {type: Number},
        name: String,
        methodsSet: {type: Boolean, default: false},
        methodsSetAt: Date
    },

    maintainer: {
        user: {type: Schema.ObjectId, ref: 'User'},
        org: {type: Schema.ObjectId, ref: 'Organization'},
        name: {type: String, trim: true },
        email: {type: String, trim: true },
        notified: {type: Boolean, default: false},
        notifiedAt: Date
    },

    satisfied: {type: Boolean, default: false},
    updatedAt: Date,
    createdAt: Date
})

RequestSchema.pre('save', function (next) {
    if (!this.createdAt) this.createdAt = new Date;
    if (this.maintainer) {
        if (this.maintainer.notified && !this.maintainer.notifiedAt) {
            this.maintainer.notifiedAt = new Date
        }
    }
    next();
});

RequestSchema.statics.satisfy = function (project, cb) {
    if (!project.hasDonateMethods()) return cb('No donate methods')
    notifier.notifyRequesters(project, cb)
}

RequestSchema.methods.supportNotifyMaintainer = function (cb) {
    if (!this.maintainer.email) return cb('Can\'t notify without email')
    notifier.supportNotifyMaintainer(this, _.bind(function (error) {
        if (error) return cb('Failed to notify maintainer')

        this.maintainer.notified = true
        this.save(cb)
    }, this))
}

RequestSchema.statics.updateRequesterEmail = function (user, projectData, ip, altEmail, cb) {
    var self = this, query = {satisfied: false}

    if (projectData) {
        query['project.ref'] = projectData._id
    } else {
        query['project.githubId '] = projectData.githubId
    }

    async.waterfall([
        function (callback) {
            self.findOne(query).exec(callback)
        },
        function (request, callback) {
            Requester.findOne(getUserQuery(user, ip, request), callback)
        },
        function (requester, callback) {
            requester.email = altEmail
            requester.save(callback)
        }
    ], cb)
}

RequestSchema.statics.request = function (user, project, ip, altEmail, cb) {
    var self = this
    var isAnon = !user
    var isNew = true
    var request = {
        project: {
            ref: project._id,
            name: project.name,
            githubId: project.githubId,
            methodsSet: project.hasDonateMethods()
        },
        maintainer: {
            name: project.owner.username,
            notified: false
        }
    }
    var supporter = {
        email: (!isAnon && user.email) || altEmail,
        isAnon: isAnon,
        ip: ip
    }

    if (!isAnon) {
        supporter.ref = user._id
        supporter.username = user.username
        supporter.email = user.email || ''
    }

    if (project.owner.user) request.maintainer.user = project.owner.user
    if (project.owner.org) request.maintainer.org = project.owner.org

    async.series([
        function (callback) {
            project.getOwner(function (error, owner) {
                if (error) return callback(error)
                if (owner && owner.email) request.maintainer.email = owner.email
                callback()
            })
        },
        function (callback) {
            self.findOne({'project.ref': project._id, satisfied: false}, function (error, entry) {
                error && console.error(error)
                if (error) return callback('Server error')
                if (!entry) return callback()
                isNew = false
                request = entry
                return callback()
            })
        },
        function (callback) {
            if (!isNew) return callback()
            self.create(request, function (error, entry) {
                error && console.error(error)
                if (error) return callback('Server error')
                request = entry
                callback()
            })
        },
        function (callback) {
            _.merge(supporter, {request: request._id})

            Requester.findOne(getUserQuery(user, ip, request), function (error, entry) {
                if (error) return callback('Server error')
                if (entry) return callback('You already sent request for this project')

                Requester.create(supporter, function (err, requester) {
                    if (err) return callback(err)
                    request.supporters.push(requester)
                    callback(err, requester)
                })
            })
        },
        function (callback) {
            if (!request.maintainer.email) {
                return notifier.notifySupport(request, project, user, callback)
            } else {
                notifier.notifyMaintainer(request, project, user, callback)
            }
        }
    ], function (error) {
        if (error) return cb(error)

        if (request.maintainer.email) {
            request.maintainer.notified = !!request.maintainer.email
        }

        request.updatedAt = new Date
        request.save(cb)
    })
}

mongoose.model('Request', RequestSchema)

function getUserQuery(user, ip, request) {
    var isAnon = !user

    var query = {request: request._id}

    if (isAnon) {
        query.isAnon = true
        query.ip = ip
    } else {
        query.ref = user._id
    }

    return query
}<commit_msg>maintainer get only one notification per project<commit_after>/**
 * Author: krasu
 * Date: 9/18/13
 * Time: 14:21 AM
 */
var mongoose = require('mongoose'),
    async = require('async'),
    _ = require('lodash'),
    notifier = require('../utils/requests-notifications'),
    Requester = mongoose.model('Requester'),
    Schema = mongoose.Schema

var RequestSchema = new Schema({
    supporters: [
        {type: Schema.ObjectId, ref: 'Requester'}
    ],

    project: {
        ref: {type: Schema.ObjectId, ref: 'Project'},
        githubId: {type: Number},
        name: String,
        methodsSet: {type: Boolean, default: false},
        methodsSetAt: Date
    },

    maintainer: {
        user: {type: Schema.ObjectId, ref: 'User'},
        org: {type: Schema.ObjectId, ref: 'Organization'},
        name: {type: String, trim: true },
        email: {type: String, trim: true },
        notified: {type: Boolean, default: false},
        notifiedAt: Date
    },

    satisfied: {type: Boolean, default: false},
    updatedAt: Date,
    createdAt: Date
})

RequestSchema.pre('save', function (next) {
    if (!this.createdAt) this.createdAt = new Date;
    if (this.maintainer) {
        if (this.maintainer.notified && !this.maintainer.notifiedAt) {
            this.maintainer.notifiedAt = new Date
        }
    }
    next();
});

RequestSchema.statics.satisfy = function (project, cb) {
    if (!project.hasDonateMethods()) return cb('No donate methods')
    notifier.notifyRequesters(project, cb)
}

RequestSchema.methods.supportNotifyMaintainer = function (cb) {
    if (!this.maintainer.email) return cb('Can\'t notify without email')
    notifier.supportNotifyMaintainer(this, _.bind(function (error) {
        if (error) return cb('Failed to notify maintainer')

        this.maintainer.notified = true
        this.save(cb)
    }, this))
}

RequestSchema.statics.updateRequesterEmail = function (user, projectData, ip, altEmail, cb) {
    var self = this, query = {satisfied: false}

    if (projectData) {
        query['project.ref'] = projectData._id
    } else {
        query['project.githubId '] = projectData.githubId
    }

    async.waterfall([
        function (callback) {
            self.findOne(query).exec(callback)
        },
        function (request, callback) {
            Requester.findOne(getUserQuery(user, ip, request), callback)
        },
        function (requester, callback) {
            requester.email = altEmail
            requester.save(callback)
        }
    ], cb)
}

RequestSchema.statics.request = function (user, project, ip, altEmail, cb) {
    var self = this
    var isAnon = !user
    var isNew = true
    var request = {
        project: {
            ref: project._id,
            name: project.name,
            githubId: project.githubId,
            methodsSet: project.hasDonateMethods()
        },
        maintainer: {
            name: project.owner.username,
            notified: false
        }
    }
    var supporter = {
        email: (!isAnon && user.email) || altEmail,
        isAnon: isAnon,
        ip: ip
    }

    if (!isAnon) {
        supporter.ref = user._id
        supporter.username = user.username
        supporter.email = user.email || ''
    }

    if (project.owner.user) request.maintainer.user = project.owner.user
    if (project.owner.org) request.maintainer.org = project.owner.org

    async.series([
        function (callback) {
            project.getOwner(function (error, owner) {
                if (error) return callback(error)
                if (owner && owner.email) request.maintainer.email = owner.email
                callback()
            })
        },
        function (callback) {
            self.findOne({'project.ref': project._id, satisfied: false}, function (error, entry) {
                error && console.error(error)
                if (error) return callback('Server error')
                if (!entry) return callback()
                isNew = false
                request = entry
                return callback()
            })
        },
        function (callback) {
            if (!isNew) return callback()
            self.create(request, function (error, entry) {
                error && console.error(error)
                if (error) return callback('Server error')
                request = entry
                callback()
            })
        },
        function (callback) {
            _.merge(supporter, {request: request._id})

            Requester.findOne(getUserQuery(user, ip, request), function (error, entry) {
                if (error) return callback('Server error')
                if (entry) return callback('You already sent request for this project')

                Requester.create(supporter, function (err, requester) {
                    if (err) return callback(err)
                    request.supporters.push(requester)
                    callback(err, requester)
                })
            })
        },
        function (callback) {
            if (!request.maintainer.email) {
                return notifier.notifySupport(request, project, user, callback)
            }
            if (request.maintainer.email && !request.maintainer.notified) {
                notifier.notifyMaintainer(request, project, user, callback)
            }
        }
    ], function (error) {
        if (error) return cb(error)

        if (request.maintainer.email) {
            request.maintainer.notified = !!request.maintainer.email
        }

        request.updatedAt = new Date
        request.save(cb)
    })
}

mongoose.model('Request', RequestSchema)

function getUserQuery(user, ip, request) {
    var isAnon = !user

    var query = {request: request._id}

    if (isAnon) {
        query.isAnon = true
        query.ip = ip
    } else {
        query.ref = user._id
    }

    return query
}<|endoftext|>


<commit_before>var debug = require('debug')('scope');
var should = require('should');
var fs = require('fs');
var Path = require('path');
var URL = require('url');
var cookie = require('cookie');
var express = require('express');

var runner = require('../spawner');
var scope = require('../scope')({
	privateKey: fs.readFileSync(Path.join(__dirname, 'fixtures/private.pem')).toString(),
	publicKey: fs.readFileSync(Path.join(__dirname, 'fixtures/public.pem')).toString(),
	maxAge: 3600,
	issuer: "test"
});

var ports = {
	app: 3000,
	ngx: 3001,
	memc: 3002
};

describe("Scope", function suite() {
	var servers, app;
	var testPath = '/scope-test';
	var testPathNotGranted = '/scope-not-granted-test';
	var counters = {};

	function count(uri, inc) {
		if (typeof uri != "string") {
			if (uri.get) uri = uri.protocol + '://' + uri.get('Host') + uri.path;
			else uri = URL.format(Object.assign({
				protocol: 'http',
				hostname: 'localhost',
				pathname: uri.path
			}, uri));
		}
		var counter = counters[uri];
		if (counter == null) counter = counters[uri] = 0;
		if (inc) counters[uri] += inc;
		return counters[uri];
	}

	before(function(done) {
		servers = runner(ports, done);

		app = express();
		app.server = app.listen(ports.app);

		app.post('/login', function(req, res, next) {
			var givemeScope = req.query.scope;
			var scopes = {
				"user-44": true,
				bookWriter: {
					write: true
				},
				bookReader: {
					read: true
				}
			};
			if (givemeScope) scopes = {[givemeScope]: true};
			var bearer = scope.login(res, {id: 44, scopes: scopes});
			res.send({
				bearer: bearer // used in the test
			});
		});

		app.post('/logout', function(req, res, next) {
			scope.logout(res);
			res.sendStatus(204);
		});

		app.get(testPath, scope.restrict('bookReader', 'bookSecond'), function(req, res, next) {
			count(req, 1);
			res.send({
				value: (req.path || '/').substring(1),
				date: new Date()
			});
		});

		app.get(testPathNotGranted, scope.restrict('bookReaderWhat'), function(req, res, next) {
			count(req, 1);
			res.send({
				value: (req.path || '/').substring(1),
				date: new Date()
			});
		});
	});

	after(function(done) {
		app.server.close();
		servers.close(done);
	});

	beforeEach(function() {
		counters = {};
	});

	it("should get 401 when accessing a protected url without proxy", function() {
		var req = {
			port: ports.app,
			path: testPath
		};
		return runner.get(req).then(function(res) {
			res.statusCode.should.equal(401);
			count(req).should.equal(0);
		});
	});

	it("should log in and get read access to a url without proxy", function() {
		var headers = {};
		var req = {
			headers: headers,
			port: ports.app,
			path: testPath
		};
		return runner.post({
			port: ports.app,
			path: '/login'
		}).then(function(res) {
			res.headers.should.have.property('set-cookie');
			var cookies = cookie.parse(res.headers['set-cookie'][0]);
			headers.Cookie = cookie.serialize("bearer", cookies.bearer);
			return runner.get(req);
		}).then(function(res) {
			res.headers.should.have.property('x-cache-scope', 'bookReader, bookSecond');
			count(req).should.equal(1);
		});
	});

	it("should log in and not get read access to another url without proxy", function() {
		var headers = {};
		var req = {
			headers: headers,
			port: ports.app,
			path: testPathNotGranted
		};
		return runner.post({
			port: ports.app,
			path: '/login'
		}).then(function(res) {
			res.headers.should.have.property('set-cookie');
			var cookies = cookie.parse(res.headers['set-cookie'][0]);
			headers.Cookie = cookie.serialize("bearer", cookies.bearer);
			return runner.get(req);
		}).then(function(res) {
			res.statusCode.should.equal(403);
			count(req).should.equal(0);
		});
	});

	it("should log in, access, then log out, and be denied access without proxy", function() {
		var headers = {};
		return runner.post({
			port: ports.app,
			path: '/login'
		}).then(function(res) {
			res.headers.should.have.property('set-cookie');
			var cookies = cookie.parse(res.headers['set-cookie'][0]);
			headers.Cookie = cookie.serialize("bearer", cookies.bearer);
			return runner.get({
				headers: headers,
				port: ports.app,
				path: testPath
			});
		}).then(function(res) {
			res.statusCode.should.equal(200);
			return runner.post({
				headers: headers,
				port: ports.app,
				path: "/logout"
			});
		}).then(function(res) {
			res.headers.should.have.property('set-cookie');
			var cookies = cookie.parse(res.headers['set-cookie'][0]);
			headers.Cookie = cookie.serialize("bearer", cookies.bearer);
			return runner.get({
				headers: headers,
				port: ports.app,
				path: testPath
			});
		}).then(function(res) {
			res.statusCode.should.equal(401);
		});
	});

	it("should get 401 when accessing a protected url with proxy", function() {
		var req = {
			port: ports.ngx,
			path: testPath
		};
		return runner.get(req).then(function(res) {
			res.statusCode.should.equal(401);
			count(req).should.equal(0);
		});
	});

	it("should log in and get read access to a url and hit the cache with proxy", function() {
		var headers = {};
		var req = {
			headers: headers,
			port: ports.ngx,
			path: testPath
		};
		return runner.post({
			port: ports.ngx,
			path: '/login'
		}).then(function(res) {
			res.headers.should.have.property('set-cookie');
			var cookies = cookie.parse(res.headers['set-cookie'][0]);
			headers.Cookie = cookie.serialize("bearer", cookies.bearer);
			return runner.get(req);
		}).then(function(res) {
			res.headers.should.have.property('x-cache-scope', 'bookReader, bookSecond');
			res.statusCode.should.equal(200);
			count(req).should.equal(1);
			return runner.get(req);
		}).then(function(res) {
			res.statusCode.should.equal(200);
			// because it should be a cache hit
			count(req).should.equal(1);
		});
	});

	it("should log in and not get read access to another url with proxy", function() {
		var headers = {};
		var req = {
			headers: headers,
			port: ports.ngx,
			path: testPathNotGranted
		};
		return runner.post({
			port: ports.ngx,
			path: '/login'
		}).then(function(res) {
			res.headers.should.have.property('set-cookie');
			var cookies = cookie.parse(res.headers['set-cookie'][0]);
			headers.Cookie = cookie.serialize("bearer", cookies.bearer);
			return runner.get(req);
		}).then(function(res) {
			res.statusCode.should.equal(403);
			count(req).should.equal(0);
		});
	});

	it("should log in, access, then log out, and be denied access with proxy", function() {
		var headers = {};
		return runner.post({
			port: ports.ngx,
			path: '/login'
		}).then(function(res) {
			res.headers.should.have.property('set-cookie');
			var cookies = cookie.parse(res.headers['set-cookie'][0]);
			headers.Cookie = cookie.serialize("bearer", cookies.bearer);
			return runner.get({
				headers: headers,
				port: ports.ngx,
				path: testPath
			});
		}).then(function(res) {
			res.statusCode.should.equal(200);
			return runner.post({
				headers: headers,
				port: ports.ngx,
				path: "/logout"
			});
		}).then(function(res) {
			res.headers.should.have.property('set-cookie');
			var cookies = cookie.parse(res.headers['set-cookie'][0]);
			headers.Cookie = cookie.serialize("bearer", cookies.bearer);
			return runner.get({
				headers: headers,
				port: ports.ngx,
				path: testPath
			});
		}).then(function(res) {
			res.statusCode.should.equal(401);
		});
	});

	it("should log in with different scopes and cache each variant with proxy", function() {
		var headers = {};
		var req = {
			headers: headers,
			port: ports.ngx,
			path: testPath
		};
		var firstDate;
		return runner.post({
			port: ports.ngx,
			path: '/login?scope=bookReader'
		}).then(function(res) {
			res.headers.should.have.property('set-cookie');
			var cookies = cookie.parse(res.headers['set-cookie'][0]);
			headers.Cookie = cookie.serialize("bearer", cookies.bearer);
			return runner.get(req);
		}).then(function(res) {
			res.statusCode.should.equal(200);
			firstDate = res.body.date;
			return runner.post({
				port: ports.ngx,
				path: '/login?scope=bookSecond'
			});
		}).then(function(res) {
			res.headers.should.have.property('set-cookie');
			var cookies = cookie.parse(res.headers['set-cookie'][0]);
			headers.Cookie = cookie.serialize("bearer", cookies.bearer);
			return runner.get(req);
		}).then(function(res) {
			res.statusCode.should.equal(200);
			res.body.date.should.not.equal(firstDate);
		});
	});
});
<commit_msg>Test handshake removal<commit_after>var debug = require('debug')('scope');
var should = require('should');
var fs = require('fs');
var Path = require('path');
var URL = require('url');
var cookie = require('cookie');
var express = require('express');

var runner = require('../spawner');
var scope = require('../scope')({
	privateKey: fs.readFileSync(Path.join(__dirname, 'fixtures/private.pem')).toString(),
	publicKey: fs.readFileSync(Path.join(__dirname, 'fixtures/public.pem')).toString(),
	maxAge: 3600,
	issuer: "test"
});

var ports = {
	app: 3000,
	ngx: 3001,
	memc: 3002
};

describe("Scope", function suite() {
	var servers, app;
	var testPath = '/scope-test';
	var testPathNotGranted = '/scope-not-granted-test';
	var counters = {};

	function count(uri, inc) {
		if (typeof uri != "string") {
			if (uri.get) uri = uri.protocol + '://' + uri.get('Host') + uri.path;
			else uri = URL.format(Object.assign({
				protocol: 'http',
				hostname: 'localhost',
				pathname: uri.path
			}, uri));
		}
		var counter = counters[uri];
		if (counter == null) counter = counters[uri] = 0;
		if (inc) counters[uri] += inc;
		return counters[uri];
	}

	before(function(done) {
		servers = runner(ports, done);

		app = express();
		app.server = app.listen(ports.app);

		app.post('/login', function(req, res, next) {
			var givemeScope = req.query.scope;
			var scopes = {
				"user-44": true,
				bookWriter: {
					write: true
				},
				bookReader: {
					read: true
				}
			};
			if (givemeScope) scopes = {[givemeScope]: true};
			var bearer = scope.login(res, {id: 44, scopes: scopes});
			res.send({
				bearer: bearer // used in the test
			});
		});

		app.post('/logout', function(req, res, next) {
			scope.logout(res);
			res.sendStatus(204);
		});

		app.get(testPath, scope.restrict('bookReader', 'bookSecond'), function(req, res, next) {
			count(req, 1);
			res.send({
				value: (req.path || '/').substring(1),
				date: new Date()
			});
		});

		app.get(testPathNotGranted, scope.restrict('bookReaderWhat'), function(req, res, next) {
			count(req, 1);
			res.send({
				value: (req.path || '/').substring(1),
				date: new Date()
			});
		});
	});

	after(function(done) {
		app.server.close();
		servers.close(done);
	});

	beforeEach(function() {
		counters = {};
	});

	it("should get 401 when accessing a protected url without proxy", function() {
		var req = {
			port: ports.app,
			path: testPath
		};
		return runner.get(req).then(function(res) {
			res.statusCode.should.equal(401);
			count(req).should.equal(0);
		});
	});

	it("should log in and get read access to a url without proxy", function() {
		var headers = {};
		var req = {
			headers: headers,
			port: ports.app,
			path: testPath
		};
		return runner.post({
			port: ports.app,
			path: '/login'
		}).then(function(res) {
			res.headers.should.have.property('set-cookie');
			var cookies = cookie.parse(res.headers['set-cookie'][0]);
			headers.Cookie = cookie.serialize("bearer", cookies.bearer);
			return runner.get(req);
		}).then(function(res) {
			res.headers.should.have.property('x-cache-scope', 'bookReader, bookSecond');
			count(req).should.equal(1);
		});
	});

	it("should log in and not get read access to another url without proxy", function() {
		var headers = {};
		var req = {
			headers: headers,
			port: ports.app,
			path: testPathNotGranted
		};
		return runner.post({
			port: ports.app,
			path: '/login'
		}).then(function(res) {
			res.headers.should.have.property('set-cookie');
			var cookies = cookie.parse(res.headers['set-cookie'][0]);
			headers.Cookie = cookie.serialize("bearer", cookies.bearer);
			return runner.get(req);
		}).then(function(res) {
			res.statusCode.should.equal(403);
			count(req).should.equal(0);
		});
	});

	it("should log in, access, then log out, and be denied access without proxy", function() {
		var headers = {};
		return runner.post({
			port: ports.app,
			path: '/login'
		}).then(function(res) {
			res.headers.should.have.property('set-cookie');
			var cookies = cookie.parse(res.headers['set-cookie'][0]);
			headers.Cookie = cookie.serialize("bearer", cookies.bearer);
			return runner.get({
				headers: headers,
				port: ports.app,
				path: testPath
			});
		}).then(function(res) {
			res.statusCode.should.equal(200);
			return runner.post({
				headers: headers,
				port: ports.app,
				path: "/logout"
			});
		}).then(function(res) {
			res.headers.should.have.property('set-cookie');
			var cookies = cookie.parse(res.headers['set-cookie'][0]);
			headers.Cookie = cookie.serialize("bearer", cookies.bearer);
			return runner.get({
				headers: headers,
				port: ports.app,
				path: testPath
			});
		}).then(function(res) {
			res.statusCode.should.equal(401);
		});
	});

	it("should get 401 when accessing a protected url with proxy", function() {
		var req = {
			port: ports.ngx,
			path: testPath
		};
		return runner.get(req).then(function(res) {
			res.statusCode.should.equal(401);
			count(req).should.equal(0);
		});
	});

	it("should log in and get read access to a url and hit the cache with proxy", function() {
		var headers = {};
		var req = {
			headers: headers,
			port: ports.ngx,
			path: testPath
		};
		return runner.post({
			port: ports.ngx,
			path: '/login'
		}).then(function(res) {
			res.headers.should.have.property('set-cookie');
			var cookies = cookie.parse(res.headers['set-cookie'][0]);
			headers.Cookie = cookie.serialize("bearer", cookies.bearer);
			return runner.get(req);
		}).then(function(res) {
			res.headers.should.not.have.property('x-cache-key-handshake');
			res.headers.should.have.property('x-cache-scope', 'bookReader, bookSecond');
			res.statusCode.should.equal(200);
			count(req).should.equal(1);
			return runner.get(req);
		}).then(function(res) {
			res.statusCode.should.equal(200);
			// because it should be a cache hit
			count(req).should.equal(1);
		});
	});

	it("should log in and not get read access to another url with proxy", function() {
		var headers = {};
		var req = {
			headers: headers,
			port: ports.ngx,
			path: testPathNotGranted
		};
		return runner.post({
			port: ports.ngx,
			path: '/login'
		}).then(function(res) {
			res.headers.should.have.property('set-cookie');
			var cookies = cookie.parse(res.headers['set-cookie'][0]);
			headers.Cookie = cookie.serialize("bearer", cookies.bearer);
			return runner.get(req);
		}).then(function(res) {
			res.statusCode.should.equal(403);
			count(req).should.equal(0);
		});
	});

	it("should log in, access, then log out, and be denied access with proxy", function() {
		var headers = {};
		return runner.post({
			port: ports.ngx,
			path: '/login'
		}).then(function(res) {
			res.headers.should.have.property('set-cookie');
			var cookies = cookie.parse(res.headers['set-cookie'][0]);
			headers.Cookie = cookie.serialize("bearer", cookies.bearer);
			return runner.get({
				headers: headers,
				port: ports.ngx,
				path: testPath
			});
		}).then(function(res) {
			res.statusCode.should.equal(200);
			return runner.post({
				headers: headers,
				port: ports.ngx,
				path: "/logout"
			});
		}).then(function(res) {
			res.headers.should.have.property('set-cookie');
			var cookies = cookie.parse(res.headers['set-cookie'][0]);
			headers.Cookie = cookie.serialize("bearer", cookies.bearer);
			return runner.get({
				headers: headers,
				port: ports.ngx,
				path: testPath
			});
		}).then(function(res) {
			res.statusCode.should.equal(401);
		});
	});

	it("should log in with different scopes and cache each variant with proxy", function() {
		var headers = {};
		var req = {
			headers: headers,
			port: ports.ngx,
			path: testPath
		};
		var firstDate;
		return runner.post({
			port: ports.ngx,
			path: '/login?scope=bookReader'
		}).then(function(res) {
			res.headers.should.have.property('set-cookie');
			var cookies = cookie.parse(res.headers['set-cookie'][0]);
			headers.Cookie = cookie.serialize("bearer", cookies.bearer);
			return runner.get(req);
		}).then(function(res) {
			res.statusCode.should.equal(200);
			firstDate = res.body.date;
			return runner.post({
				port: ports.ngx,
				path: '/login?scope=bookSecond'
			});
		}).then(function(res) {
			res.headers.should.have.property('set-cookie');
			var cookies = cookie.parse(res.headers['set-cookie'][0]);
			headers.Cookie = cookie.serialize("bearer", cookies.bearer);
			return runner.get(req);
		}).then(function(res) {
			res.statusCode.should.equal(200);
			res.body.date.should.not.equal(firstDate);
		});
	});
});
<|endoftext|>


<commit_before>/*
 * Copyright 2010 Mark Watkinson
 *
 * This file is part of Luminous.
 *
 * Luminous is free software: you can redistribute it and/or
 * modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Luminous is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Luminous.  If not, see <http://www.gnu.org/licenses/>.
 */

 /**
   * This simply adds some extras to Luminous elements via a jQUery
   * plugin. The extras are currently a toggleable line-highlighting
   * on click
   */

(function($) {
    "use strict";

    if (typeof $ === 'undefined') { return; }

    /****************************************************************
     * UTILITY FUNCTIONS *
     ****************************************************************/

    // determines if the given element is a line element of luminous
    function isLine($line) {
        return $line.is('pre > span') && $line.parents('.luminous').length > 0;
    }

    function highlightLine($line) {
        // FIXME this 'highlighted_line' class needs to be renamed
        $line.toggleClass('highlight');
    }

    function highlightLineByIndex($luminous, index) {
        var $line = $luminous.find('pre > span').eq(index);
        highlightLine($line);
    }

    function highlightLineByNumber($luminous, number) {
        // the line's index must take into account the initial line number
        var offset = parseInt($luminous.find('>pre').data('startline'), 10);
        if (isNaN(offset)) offset = 0;
        highlightLineByIndex($luminous, number - offset);
    }

    function toggleHighlightAndPlain($luminous, forceState) {
        var data = $luminous.data('luminous'),
            state = data.code.active,
            $elem = $luminous.find('>pre'),
            toSetCode, toSetState;

        if (forceState === 'plain') state = 'highlighted';
        else if (forceState === 'highlighted') state = 'plain';

        toSetCode = (state === 'plain')? data.code.highlighted : data.code.plain;
        toSetState = (state === 'plain')? 'highlighted' : 'plain';

        $elem.html(toSetCode);
    }

    function toggleLineNumbers($luminous, forceState) {
        var className = 'line-no-hidden',
            $element = $luminous.find('>.code'),
            hasNumbers = $element.hasClass(className),
            show = !hasNumbers;

        if (forceState === true || forceState === false) show = forceState;
        if (show) {
            $element.removeClass(className);
        } else {
            $element.addClass(className);
        }
    }

    // binds the event handlers to a luminous element
    function bindLuminousExtras($element) {
        var highlightLinesData, highlightLines, data = {};
        if (!$element.is('.luminous')) { return false; }
        else if ($element.is('.bound')) { return true; }

        $element.addClass('bound');

        $element.click(function(ev) {
            var $t = $(ev.target);
            var $lines = $t.parents().add($t).
                    filter(function() { return isLine($(this)); }),
                 $line;
            if ($lines.length > 0) {
                $line = $lines.eq(0);
                highlightLine($line);
            }
        });

        // highlight all the initial lines
        highlightLinesData = $element.find('>pre').data('highlightlines') || "";
        highlightLines = highlightLinesData.split(",");
        $.each(highlightLines, function(i, element) {
             var lineNo = parseInt(element, 10);
             if (!isNaN(lineNo)) {
                 highlightLineByNumber($element, lineNo);
            }
        });

        data.code = {};
        data.code.highlighted = $element.find('>pre').html();

        data.code.plain = '';
        $element.find('>pre>span').each(function(i, e) {
            var line = $(e).text();
            line = line
                    .replace(/&/g, '&amp')
                    .replace(/>/g, '>')
                    .replace(/</g, '<');

            data.code.plain += '' + line + '';
        });
        data.code.active = 'highlighted';

        $element.data('luminous', data);

    }

    /****************************************************************
     * JQUERY PLUGIN *
     ***************************************************************/

    $.fn.luminous = function(optionsOrCommand /* variadic */) {

        var args = Array.prototype.slice.call(arguments);

        return $(this).each(function() {
            var $luminous = $(this);

            // no instructions - bind everything
            if (!optionsOrCommand) {
                bindLuminousExtras($luminous);
                return;
            }

            // $('.luminous').luminous('highlightLine', [2, 3]);
            if (optionsOrCommand === 'highlightLine') {
                var lineNumbers = args[1];
                if (!$.isArray(lineNumbers))
                    lineNumbers = [lineNumbers];

                $.each(lineNumbers, function(index, el) {
                    highlightLineByNumber($luminous, el);
                });

                return;
            }
            else if (optionsOrCommand === 'show') {
                // args[1] should be 'highlighted' or 'plain'
                toggleHighlightAndPlain($luminous, args[1]);
            }
            else if (optionsOrCommand === 'showLineNumbers') {
                toggleLineNumbers($luminous, args[1]);
            }

        });
    };

    $(document).ready(function() {
        $('.luminous').luminous();
    });

}(jQuery));<commit_msg>remove old/wrong comment<commit_after>/*
 * Copyright 2010 Mark Watkinson
 *
 * This file is part of Luminous.
 *
 * Luminous is free software: you can redistribute it and/or
 * modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Luminous is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Luminous.  If not, see <http://www.gnu.org/licenses/>.
 */

 /**
   * This simply adds some extras to Luminous elements via a jQUery
   * plugin. The extras are currently a toggleable line-highlighting
   * on click
   */

(function($) {
    "use strict";

    if (typeof $ === 'undefined') { return; }

    /****************************************************************
     * UTILITY FUNCTIONS *
     ****************************************************************/

    // determines if the given element is a line element of luminous
    function isLine($line) {
        return $line.is('pre > span') && $line.parents('.luminous').length > 0;
    }

    function highlightLine($line) {
        $line.toggleClass('highlight');
    }

    function highlightLineByIndex($luminous, index) {
        var $line = $luminous.find('pre > span').eq(index);
        highlightLine($line);
    }

    function highlightLineByNumber($luminous, number) {
        // the line's index must take into account the initial line number
        var offset = parseInt($luminous.find('>pre').data('startline'), 10);
        if (isNaN(offset)) offset = 0;
        highlightLineByIndex($luminous, number - offset);
    }

    function toggleHighlightAndPlain($luminous, forceState) {
        var data = $luminous.data('luminous'),
            state = data.code.active,
            $elem = $luminous.find('>pre'),
            toSetCode, toSetState;

        if (forceState === 'plain') state = 'highlighted';
        else if (forceState === 'highlighted') state = 'plain';

        toSetCode = (state === 'plain')? data.code.highlighted : data.code.plain;
        toSetState = (state === 'plain')? 'highlighted' : 'plain';

        $elem.html(toSetCode);
    }

    function toggleLineNumbers($luminous, forceState) {
        var className = 'line-no-hidden',
            $element = $luminous.find('>.code'),
            hasNumbers = $element.hasClass(className),
            show = !hasNumbers;

        if (forceState === true || forceState === false) show = forceState;
        if (show) {
            $element.removeClass(className);
        } else {
            $element.addClass(className);
        }
    }

    // binds the event handlers to a luminous element
    function bindLuminousExtras($element) {
        var highlightLinesData, highlightLines, data = {};
        if (!$element.is('.luminous')) { return false; }
        else if ($element.is('.bound')) { return true; }

        $element.addClass('bound');

        $element.click(function(ev) {
            var $t = $(ev.target);
            var $lines = $t.parents().add($t).
                    filter(function() { return isLine($(this)); }),
                 $line;
            if ($lines.length > 0) {
                $line = $lines.eq(0);
                highlightLine($line);
            }
        });

        // highlight all the initial lines
        highlightLinesData = $element.find('>pre').data('highlightlines') || "";
        highlightLines = highlightLinesData.split(",");
        $.each(highlightLines, function(i, element) {
             var lineNo = parseInt(element, 10);
             if (!isNaN(lineNo)) {
                 highlightLineByNumber($element, lineNo);
            }
        });

        data.code = {};
        data.code.highlighted = $element.find('>pre').html();

        data.code.plain = '';
        $element.find('>pre>span').each(function(i, e) {
            var line = $(e).text();
            line = line
                    .replace(/&/g, '&amp')
                    .replace(/>/g, '>')
                    .replace(/</g, '<');

            data.code.plain += '' + line + '';
        });
        data.code.active = 'highlighted';

        $element.data('luminous', data);

    }

    /****************************************************************
     * JQUERY PLUGIN *
     ***************************************************************/

    $.fn.luminous = function(optionsOrCommand /* variadic */) {

        var args = Array.prototype.slice.call(arguments);

        return $(this).each(function() {
            var $luminous = $(this);

            // no instructions - bind everything
            if (!optionsOrCommand) {
                bindLuminousExtras($luminous);
                return;
            }

            // $('.luminous').luminous('highlightLine', [2, 3]);
            if (optionsOrCommand === 'highlightLine') {
                var lineNumbers = args[1];
                if (!$.isArray(lineNumbers))
                    lineNumbers = [lineNumbers];

                $.each(lineNumbers, function(index, el) {
                    highlightLineByNumber($luminous, el);
                });

                return;
            }
            else if (optionsOrCommand === 'show') {
                // args[1] should be 'highlighted' or 'plain'
                toggleHighlightAndPlain($luminous, args[1]);
            }
            else if (optionsOrCommand === 'showLineNumbers') {
                toggleLineNumbers($luminous, args[1]);
            }

        });
    };

    $(document).ready(function() {
        $('.luminous').luminous();
    });

}(jQuery));<|endoftext|>


<commit_before>import React, { useEffect, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import Button from 'components/Button';
import UsernameText from 'components/Texts/UsernameText';
import Comments from 'components/Comments';
import DropdownButton from 'components/Buttons/DropdownButton';
import Textarea from 'components/Texts/Textarea';
import LongText from 'components/Texts/LongText';
import ConfirmModal from 'components/Modals/ConfirmModal';
import Icon from 'components/Icon';
import Input from 'components/Texts/Input';
import DifficultyBar from 'components/DifficultyBar';
import DifficultyModal from 'components/Modals/DifficultyModal';
import Link from 'components/Link';
import withContext from 'components/Wrappers/withContext';
import Context from './Context';
import { connect } from 'react-redux';
import {
  deleteSubject,
  editSubject,
  loadComments
} from 'helpers/requestHelpers';
import {
  cleanString,
  stringIsEmpty,
  addEmoji,
  finalizeEmoji
} from 'helpers/stringHelpers';
import { timeSince } from 'helpers/timeStampHelpers';
import { Color } from 'constants/css';

SubjectPanel.propTypes = {
  attachStar: PropTypes.func.isRequired,
  authLevel: PropTypes.number,
  canDelete: PropTypes.bool,
  canEdit: PropTypes.bool,
  canEditDifficulty: PropTypes.bool,
  comments: PropTypes.array.isRequired,
  description: PropTypes.string,
  difficulty: PropTypes.number,
  dispatch: PropTypes.func.isRequired,
  editRewardComment: PropTypes.func.isRequired,
  id: PropTypes.number.isRequired,
  onLoadSubjectComments: PropTypes.func.isRequired,
  onLoadMoreComments: PropTypes.func.isRequired,
  loadMoreCommentsButton: PropTypes.bool.isRequired,
  myId: PropTypes.number,
  numComments: PropTypes.string,
  onDelete: PropTypes.func.isRequired,
  onEditDone: PropTypes.func.isRequired,
  onSubjectDelete: PropTypes.func.isRequired,
  onSubjectEditDone: PropTypes.func.isRequired,
  onLikeClick: PropTypes.func.isRequired,
  onLoadMoreReplies: PropTypes.func.isRequired,
  onUploadComment: PropTypes.func.isRequired,
  onUploadReply: PropTypes.func.isRequired,
  profileTheme: PropTypes.string,
  rootDifficulty: PropTypes.number,
  setSubjectDifficulty: PropTypes.func.isRequired,
  timeStamp: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
    .isRequired,
  title: PropTypes.string.isRequired,
  type: PropTypes.string.isRequired,
  userId: PropTypes.number,
  username: PropTypes.string.isRequired,
  uploaderAuthLevel: PropTypes.number.isRequired,
  contentId: PropTypes.number.isRequired
};

function SubjectPanel({
  attachStar,
  authLevel,
  canDelete,
  canEdit,
  canEditDifficulty,
  contentId,
  description,
  dispatch,
  id,
  title,
  difficulty,
  editRewardComment,
  uploaderAuthLevel,
  username,
  userId,
  timeStamp,
  numComments,
  myId,
  profileTheme,
  comments,
  loadMoreCommentsButton,
  onLikeClick,
  onLoadMoreComments,
  onDelete,
  onEditDone,
  onLoadMoreReplies,
  onLoadSubjectComments,
  onSubjectDelete,
  onSubjectEditDone,
  onUploadComment,
  onUploadReply,
  rootDifficulty,
  setSubjectDifficulty,
  type
}) {
  const [expanded, setExpanded] = useState(false);
  const [onEdit, setOnEdit] = useState(false);
  const [confirmModalShown, setConfirmModalShown] = useState(false);
  const [difficultyModalShown, setDifficultyModalShown] = useState(false);
  const [editedTitle, setEditedTitle] = useState(cleanString(title));
  const [editedDescription, setEditedDescription] = useState(description || '');
  const [editDoneButtonDisabled, setEditDoneButtonDisabled] = useState(true);
  const userIsUploader = myId === userId;
  const userCanEditThis =
    (canEdit || canDelete) && authLevel > uploaderAuthLevel;
  const editButtonEnabled = userIsUploader || userCanEditThis;
  const themeColor = profileTheme || 'logoBlue';
  const CommentsRef = useRef(null);

  useEffect(() => {
    const titleIsEmpty = stringIsEmpty(editedTitle);
    const titleChanged = editedTitle !== title;
    const descriptionChanged = editedDescription !== description;
    const editDoneButtonDisabled =
      titleIsEmpty || (!titleChanged && !descriptionChanged);
    setEditDoneButtonDisabled(editDoneButtonDisabled);
  }, [editedTitle, editedDescription]);

  return (

      {difficulty > 0 && <DifficultyBar difficulty={difficulty} />}

          {!onEdit && (
            <Link
              to={`/subjects/${id}`}
              style={{
                fontSize: '2.5rem',
                color: Color.green(),
                fontWeight: 'bold'
              }}
            >
              {cleanString(title)}
            </Link>
          )}
          {editButtonEnabled && !onEdit && (
            <DropdownButton
              skeuomorphic
              color="darkerGray"
              direction="left"
              menuProps={renderMenuProps()}
            />
          )}

        {!onEdit && !!description && (
          <LongText style={{ padding: '1rem 0' }}>{description}</LongText>
        )}
        {onEdit && (
          <form onSubmit={event => event.preventDefault()}>
            <Input
              autoFocus
              type="text"
              placeholder="Enter Title..."
              value={editedTitle}
              onChange={text => {
                setEditedTitle(text);
              }}
              onKeyUp={event => setEditedTitle(addEmoji(event.target.value))}
            />
          </form>
        )}
        {onEdit && (

            <Textarea
              placeholder="Enter Description (Optional)"
              style={{ marginTop: '1rem' }}
              minRows={5}
              value={editedDescription}
              onChange={event => {
                setEditedDescription(event.target.value);
              }}
            />

              <Button
                transparent
                style={{
                  fontSize: '1.7rem',
                  marginRight: '1rem'
                }}
                onClick={() => {
                  setOnEdit(false);
                  setEditedTitle(cleanString(title));
                  setEditedDescription(description);
                }}
              >
                Cancel
              </Button>
              <Button
                color="blue"
                style={{
                  fontSize: '1.8rem'
                }}
                onClick={handleEditDone}
                disabled={editDoneButtonDisabled}
              >
                Done
              </Button>

        )}
        {!onEdit && (

            {expanded ? (
              <Comments
                inputAreaInnerRef={CommentsRef}
                commentsLoadLimit={10}
                commentsShown={expanded}
                inputTypeLabel={'response'}
                comments={comments}
                loadMoreButton={loadMoreCommentsButton}
                userId={myId}
                onAttachStar={attachStar}
                onCommentSubmit={onUploadComment}
                onDelete={onDelete}
                onEditDone={onEditDone}
                onLikeClick={onLikeClick}
                onLoadMoreComments={loadMoreComments}
                onLoadMoreReplies={onLoadMoreReplies}
                onReplySubmit={onUploadReply}
                onRewardCommentEdit={editRewardComment}
                contentId={id}
                parent={{
                  id,
                  difficulty,
                  rootObj: {
                    id: contentId,
                    difficulty: rootDifficulty,
                    type
                  },
                  type: 'subject'
                }}
              />
            ) : (

                <Button
                  filled
                  color={themeColor}
                  style={{ fontSize: '2rem' }}
                  onClick={onExpand}
                >
                  <Icon icon="comment-alt" />

                    Respond
                    {numComments && numComments > 0 ? ` (${numComments})` : ''}

                </Button>

            )}

        )}

          By{' '}

            <UsernameText
              user={{
                username,
                id: userId
              }}
            />
          {' '}
           |  Published {timeSince(timeStamp)}

      {confirmModalShown && (
        <ConfirmModal
          onHide={() => setConfirmModalShown(false)}
          title="Remove Subject"
          onConfirm={deleteThis}
        />
      )}
      {difficultyModalShown && (
        <DifficultyModal
          type="subject"
          contentId={id}
          difficulty={difficulty}
          onSubmit={data => {
            setSubjectDifficulty(data);
            setDifficultyModalShown(false);
          }}
          onHide={() => setDifficultyModalShown(false)}
        />
      )}

  );

  function renderMenuProps() {
    const menuProps = [
      {
        label: 'Edit',
        onClick: () => setOnEdit(true)
      }
    ];
    if (canEditDifficulty) {
      menuProps.push({
        label: 'Set Reward Level',
        onClick: () => setDifficultyModalShown(true)
      });
    }
    menuProps.push({
      label: 'Remove',
      onClick: () => setConfirmModalShown(true)
    });
    return menuProps;
  }

  function loadMoreComments(data) {
    onLoadMoreComments({ data, subjectId: id });
  }

  async function deleteThis() {
    try {
      await deleteSubject({ subjectId: id, dispatch });
      setConfirmModalShown(false);
      onSubjectDelete(id);
    } catch (error) {
      return console.error(error);
    }
  }

  async function onExpand() {
    setExpanded(true);
    try {
      const data = await loadComments({ type: 'subject', id, limit: 10 });
      CommentsRef.current.focus();
      onLoadSubjectComments({ data, subjectId: id });
    } catch (error) {
      console.error(error.response || error);
    }
  }

  async function handleEditDone() {
    const editedSubject = await editSubject({
      subjectId: id,
      editedTitle: finalizeEmoji(editedTitle),
      editedDescription: finalizeEmoji(editedDescription),
      dispatch
    });
    onSubjectEditDone({ editedSubject, subjectId: id });
    setOnEdit(false);
    setEditDoneButtonDisabled(true);
  }
}

export default connect(
  state => ({
    myId: state.UserReducer.userId,
    authLevel: state.UserReducer.authLevel,
    canDelete: state.UserReducer.canDelete,
    canEdit: state.UserReducer.canEdit,
    canEditDifficulty: state.UserReducer.canEditDifficulty,
    profileTheme: state.UserReducer.profileTheme
  }),
  dispatch => ({ dispatch })
)(withContext({ Component: SubjectPanel, Context }));
<commit_msg>minor fix<commit_after>import React, { useEffect, useRef, useState } from 'react';
import PropTypes from 'prop-types';
import Button from 'components/Button';
import UsernameText from 'components/Texts/UsernameText';
import Comments from 'components/Comments';
import DropdownButton from 'components/Buttons/DropdownButton';
import Textarea from 'components/Texts/Textarea';
import LongText from 'components/Texts/LongText';
import ConfirmModal from 'components/Modals/ConfirmModal';
import Icon from 'components/Icon';
import Input from 'components/Texts/Input';
import DifficultyBar from 'components/DifficultyBar';
import DifficultyModal from 'components/Modals/DifficultyModal';
import Link from 'components/Link';
import withContext from 'components/Wrappers/withContext';
import Context from './Context';
import { connect } from 'react-redux';
import {
  deleteSubject,
  editSubject,
  loadComments
} from 'helpers/requestHelpers';
import {
  cleanString,
  stringIsEmpty,
  addEmoji,
  finalizeEmoji
} from 'helpers/stringHelpers';
import { timeSince } from 'helpers/timeStampHelpers';
import { Color } from 'constants/css';

SubjectPanel.propTypes = {
  attachStar: PropTypes.func.isRequired,
  authLevel: PropTypes.number,
  canDelete: PropTypes.bool,
  canEdit: PropTypes.bool,
  canEditDifficulty: PropTypes.bool,
  comments: PropTypes.array.isRequired,
  description: PropTypes.string,
  difficulty: PropTypes.number,
  dispatch: PropTypes.func.isRequired,
  editRewardComment: PropTypes.func.isRequired,
  id: PropTypes.number.isRequired,
  onLoadSubjectComments: PropTypes.func.isRequired,
  onLoadMoreComments: PropTypes.func.isRequired,
  loadMoreCommentsButton: PropTypes.bool.isRequired,
  myId: PropTypes.number,
  numComments: PropTypes.string,
  onDelete: PropTypes.func.isRequired,
  onEditDone: PropTypes.func.isRequired,
  onSubjectDelete: PropTypes.func.isRequired,
  onSubjectEditDone: PropTypes.func.isRequired,
  onLikeClick: PropTypes.func.isRequired,
  onLoadMoreReplies: PropTypes.func.isRequired,
  onUploadComment: PropTypes.func.isRequired,
  onUploadReply: PropTypes.func.isRequired,
  profileTheme: PropTypes.string,
  rootDifficulty: PropTypes.number,
  setSubjectDifficulty: PropTypes.func.isRequired,
  timeStamp: PropTypes.oneOfType([PropTypes.string, PropTypes.number])
    .isRequired,
  title: PropTypes.string.isRequired,
  type: PropTypes.string.isRequired,
  userId: PropTypes.number,
  username: PropTypes.string.isRequired,
  uploaderAuthLevel: PropTypes.number.isRequired,
  contentId: PropTypes.number.isRequired
};

function SubjectPanel({
  attachStar,
  authLevel,
  canDelete,
  canEdit,
  canEditDifficulty,
  contentId,
  description,
  dispatch,
  id,
  title,
  difficulty,
  editRewardComment,
  uploaderAuthLevel,
  username,
  userId,
  timeStamp,
  numComments,
  myId,
  profileTheme,
  comments,
  loadMoreCommentsButton,
  onLikeClick,
  onLoadMoreComments,
  onDelete,
  onEditDone,
  onLoadMoreReplies,
  onLoadSubjectComments,
  onSubjectDelete,
  onSubjectEditDone,
  onUploadComment,
  onUploadReply,
  rootDifficulty,
  setSubjectDifficulty,
  type
}) {
  const [expanded, setExpanded] = useState(false);
  const [onEdit, setOnEdit] = useState(false);
  const [confirmModalShown, setConfirmModalShown] = useState(false);
  const [difficultyModalShown, setDifficultyModalShown] = useState(false);
  const [editedTitle, setEditedTitle] = useState(cleanString(title));
  const [editedDescription, setEditedDescription] = useState(description || '');
  const [editDoneButtonDisabled, setEditDoneButtonDisabled] = useState(true);
  const userIsUploader = myId === userId;
  const userCanEditThis =
    (canEdit || canDelete) && authLevel > uploaderAuthLevel;
  const editButtonEnabled = userIsUploader || userCanEditThis;
  const themeColor = profileTheme || 'logoBlue';
  const CommentsRef = useRef(null);

  useEffect(() => {
    const titleIsEmpty = stringIsEmpty(editedTitle);
    const titleChanged = editedTitle !== title;
    const descriptionChanged = editedDescription !== description;
    const editDoneButtonDisabled =
      titleIsEmpty || (!titleChanged && !descriptionChanged);
    setEditDoneButtonDisabled(editDoneButtonDisabled);
  }, [editedTitle, editedDescription]);

  return (

      {difficulty > 0 && <DifficultyBar difficulty={difficulty} />}

          {!onEdit && (
            <Link
              to={`/subjects/${id}`}
              style={{
                fontSize: '2.5rem',
                color: Color.green(),
                fontWeight: 'bold'
              }}
            >
              {cleanString(title)}
            </Link>
          )}
          {editButtonEnabled && !onEdit && (
            <DropdownButton
              skeuomorphic
              color="darkerGray"
              direction="left"
              menuProps={renderMenuProps()}
            />
          )}

        {!onEdit && !!description && (
          <LongText style={{ padding: '1rem 0' }}>{description}</LongText>
        )}
        {onEdit && (
          <form onSubmit={event => event.preventDefault()}>
            <Input
              autoFocus
              type="text"
              placeholder="Enter Title..."
              value={editedTitle}
              onChange={text => {
                setEditedTitle(text);
              }}
              onKeyUp={event => setEditedTitle(addEmoji(event.target.value))}
            />
          </form>
        )}
        {onEdit && (

            <Textarea
              placeholder="Enter Description (Optional)"
              style={{ marginTop: '1rem' }}
              minRows={5}
              value={editedDescription}
              onChange={event => {
                setEditedDescription(event.target.value);
              }}
            />

              <Button
                transparent
                style={{
                  fontSize: '1.7rem',
                  marginRight: '1rem'
                }}
                onClick={() => {
                  setOnEdit(false);
                  setEditedTitle(cleanString(title));
                  setEditedDescription(description);
                }}
              >
                Cancel
              </Button>
              <Button
                color="blue"
                style={{
                  fontSize: '1.8rem'
                }}
                onClick={handleEditDone}
                disabled={editDoneButtonDisabled}
              >
                Done
              </Button>

        )}
        {!onEdit && (

            {expanded ? (
              <Comments
                inputAreaInnerRef={CommentsRef}
                commentsLoadLimit={10}
                commentsShown={expanded}
                inputTypeLabel={'response'}
                comments={comments}
                loadMoreButton={loadMoreCommentsButton}
                userId={myId}
                onAttachStar={attachStar}
                onCommentSubmit={onUploadComment}
                onDelete={onDelete}
                onEditDone={onEditDone}
                onLikeClick={onLikeClick}
                onLoadMoreComments={loadMoreComments}
                onLoadMoreReplies={onLoadMoreReplies}
                onReplySubmit={onUploadReply}
                onRewardCommentEdit={editRewardComment}
                contentId={id}
                parent={{
                  id,
                  difficulty,
                  rootObj: {
                    id: contentId,
                    difficulty: rootDifficulty,
                    type
                  },
                  type: 'subject'
                }}
              />
            ) : (

                <Button
                  skeuomorphic
                  color={themeColor}
                  style={{ fontSize: '2rem' }}
                  onClick={onExpand}
                >
                  <Icon icon="comment-alt" />

                    Respond
                    {numComments && numComments > 0 ? ` (${numComments})` : ''}

                </Button>

            )}

        )}

          By{' '}

            <UsernameText
              user={{
                username,
                id: userId
              }}
            />
          {' '}
           |  Published {timeSince(timeStamp)}

      {confirmModalShown && (
        <ConfirmModal
          onHide={() => setConfirmModalShown(false)}
          title="Remove Subject"
          onConfirm={deleteThis}
        />
      )}
      {difficultyModalShown && (
        <DifficultyModal
          type="subject"
          contentId={id}
          difficulty={difficulty}
          onSubmit={data => {
            setSubjectDifficulty(data);
            setDifficultyModalShown(false);
          }}
          onHide={() => setDifficultyModalShown(false)}
        />
      )}

  );

  function renderMenuProps() {
    const menuProps = [
      {
        label: 'Edit',
        onClick: () => setOnEdit(true)
      }
    ];
    if (canEditDifficulty) {
      menuProps.push({
        label: 'Set Reward Level',
        onClick: () => setDifficultyModalShown(true)
      });
    }
    menuProps.push({
      label: 'Remove',
      onClick: () => setConfirmModalShown(true)
    });
    return menuProps;
  }

  function loadMoreComments(data) {
    onLoadMoreComments({ data, subjectId: id });
  }

  async function deleteThis() {
    try {
      await deleteSubject({ subjectId: id, dispatch });
      setConfirmModalShown(false);
      onSubjectDelete(id);
    } catch (error) {
      return console.error(error);
    }
  }

  async function onExpand() {
    setExpanded(true);
    try {
      const data = await loadComments({ type: 'subject', id, limit: 10 });
      CommentsRef.current.focus();
      onLoadSubjectComments({ data, subjectId: id });
    } catch (error) {
      console.error(error.response || error);
    }
  }

  async function handleEditDone() {
    const editedSubject = await editSubject({
      subjectId: id,
      editedTitle: finalizeEmoji(editedTitle),
      editedDescription: finalizeEmoji(editedDescription),
      dispatch
    });
    onSubjectEditDone({ editedSubject, subjectId: id });
    setOnEdit(false);
    setEditDoneButtonDisabled(true);
  }
}

export default connect(
  state => ({
    myId: state.UserReducer.userId,
    authLevel: state.UserReducer.authLevel,
    canDelete: state.UserReducer.canDelete,
    canEdit: state.UserReducer.canEdit,
    canEditDifficulty: state.UserReducer.canEditDifficulty,
    profileTheme: state.UserReducer.profileTheme
  }),
  dispatch => ({ dispatch })
)(withContext({ Component: SubjectPanel, Context }));
<|endoftext|>


<commit_before>/**
 * Created by binarylu on 3/18/16.
 */

var github_api = 'https://api.github.com';

var token = "";
var user = "";
var repo = "";
var commit_cond = "";
var default_comment = "";

var postfix = {
    c: ".c",
    cpp: ".cpp",
    java: ".java",
    python: ".py",
    csharp: ".cs",
    javascript: ".js",
    ruby: ".rb",
    swift: ".swift",
    golang: ".go",
    base: ".sh",
    mysql: ".sql"
};

$(function(){
    var reg = new RegExp('submissions');
    if (window.location.pathname.match(reg) !== null)
        return false;
    chrome.storage.sync.get({
        token: '',
        oauth_token: '',
        user: '',
        repo_name: '',
        commit: ["accepted", "time limit exceeded", "compile error", "runtime error"],
        comment: ''
    }, function(items) {
        if(chrome.runtime.lastError) {
            console.log(chrome.runtime.lastError.message);
            return;
        }
        token = items.oauth_token;
        user = items.user;
        repo = items.repo_name;
        commit_cond = items.commit;
        default_comment = items.comment;

        upload_problem();
        if (items.token !== '' && items.oauth_token === '') {
            var $bulletin = $("");
            $("code-button").after($bulletin);
            var content = "LeetCode Extension does not support personal access token any more, please go to option page to login with OAuth.";
            $("#bulletin").html(content);
            $("#bulletin").css("color", "red");
        }

        if (token && user && repo) {
            add_node();
            $("#readme_button").click(restore);
            // $("#commit_readme").click({from: "commit_readme"}, commit);
            // $("#commit_question").click({from: "commit_question"}, commit);
            document.getElementById("commit_readme").onclick=function(){commit("commit_readme");};
            document.getElementById("commit_question").onclick=function(){commit("commit_question");};
            // submit button
            $("#button1").click(function() {
                $("#commit_status").html("");
            });
            $("#filename").val(get_filename());
            $('select[name=lang]').change(function () {
                var ext = get_extension();
                var filename = get_filename();
                filename = filename.substring(0, filename.lastIndexOf(".")) + ext;
                $("#filename").val(filename);
            });
            // result of submit
            $("#result-state").bind("DOMSubtreeModified", function() {
                var state = get_state("submit");
                if (state !== "" && $.inArray(state, commit_cond) != -1) {
                    commit("");
                }
            });
        } else {
            console.log("token: " + token);
            console.log("user: " + token);
            console.log("repo: " + token);
        }
    });
});

function add_node() {
    var $buttons = $("" +
        "<button type='button' class='btn btn-success' id='commit_question'>Add Question</button> " +
        "<button type='button' class='btn btn-success' data-toggle='modal' data-target='#readme' id='readme_button'>Add 'README.md'</button>" +
        "");
    var $div = $('' +
        '
' +
            '<form class="form-inline">' +
                '
' +
                    '
' +
                        '
' +
                            '' +
                                'Filename	' +
                            '' +
                                '<input type="text" class="form-control" id="filename" placeholder="Filename">	' +
                            '' +
                                'Comment	' +
                            '' +
                                '<input style="width:100%" type="text" class="form-control" id="code_message" placeholder="Input the comment for git commits">	' +
                        '
' +
                    '
' +
                '
' +
            '</form>' +
        '
');
    var $modal = $('' +
        '
' +
            '
' +
                '
' +
                    '
' +
                        '<button type="button" class="close" data-dismiss="modal" aria-label="Close">×</button>' +
                        '
README.md
' +
                    '
' +
                    '
' +
                        'The following content will be commit to your Github repository.' +
                        '<textarea id="readme_content" class="form-control" rows="10"></textarea>
' +
                        '<input id="readme_message" type="text" class="form-control" placeholder="Input comments for git commit">' +
                    '
' +
                    '
' +
                        '' +
                        '<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>' +
                        '<button type="button" class="btn btn-primary" id="commit_readme">Commit</button>' +
                    '
' +
                '
' +
            '
' +
        '
');
    var $code = $("" +
        "<textarea id='code_content' style='display:none'></textarea>");

    $("code-button").after($buttons);
    $("div.action").before($div);
    $("body").append($modal).append($code);
    $("#code_message").val(default_comment);

    /* Only by this, the variable in original page can be got.
    * Even using jQuery like before can not get the variable in original page */
    var script = document.createElement("script");
    script.innerHTML = "var lxk_editor = {};" +
        "$(function(){" +
            "$('.editor').attr('id', 'ace_editor_id');" +
            "lxk_editor = ace.edit('ace_editor_id');" +
            "get_code();" +
            "$('select[name=lang]').change(get_code);" +
            "$('#button0').click(get_code);" +
            "$('#button1').click(get_code);" +
        "});" +
        "function get_code() {" +
            "$('#code_content').val(lxk_editor.getValue());" +
        "}";
    document.body.appendChild(script);
}

function commit(from) {
    $("#commit_status").html("");
    var filename = "";
    if (from == "commit_readme") {
        filename = "README.md";
    } else if (from == "commit_question") {
        filename = "Question.md";
    } else {
        filename = get_filename();
    }
    filename = get_path() + "/" + filename;
    get_file(filename, update_file, update_file);
}

function restore() {
    var filename = "README.md";
    filename = get_path() + "/" + filename;
    get_file(filename, restore_file);
    $("#readme_message").val(default_comment);
}

function get_file(filename, fsucc, ferr) {
    $.ajax({
        url: github_api + '/repos/' + user  + '/' + repo + '/contents/' + filename,
        type: 'get',
        dataType: 'json',
        async: true,
        beforeSend: function(request) {
            request.setRequestHeader("Authorization", "token " + token);
        },
        success: function(jsonData) {
            if (typeof(jsonData)=='undefined' || !jsonData) jsonData = {};
            var sha = jsonData.sha;
            var file_content = jsonData.content;
            fsucc(filename, sha, file_content);
        },
        error: function(err) {
            if (err.status == 404) {
                if(typeof ferr === "function") {
                    ferr(filename);
                }
            } else {
                set_status(filename.substr(path.length + 1), repo, "err");
            }
        }
    });
}

function restore_file(filename, sha, file_content) {
    if (filename == get_path() + "/README.md") {
        $("#readme_content").val(Base64.decode(file_content));
    }
}

function update_file(filename, sha) {
    var path = get_path();
    var content = "";
    var message = "";
    if (filename == path + "/README.md") {
        content = $("#readme_content").val();
        message = $("#readme_message").val();
        if (message === "") {
            message = default_comment;
        }
        message = message.replace(/\{state\}/g, "");
    } else if (filename == path + "/Question.md") {
        content = "# " + $(".question-title:first").children(":first").html() + "\n\n";
        content += "[Original Page](" + window.location.href + ")\n\n";
        content += toMarkdown($(".question-content:first").html());
        message = default_comment.replace(/\{state\}/g, "");
    } else {
        content = $("#code_content").val();
        message = $("#code_message").val();
        if (message === "") {
            message = default_comment;
        }
    }
    message = parse_comment(message);

    $.ajax({
        url: github_api + '/repos/' + user  + '/' + repo + '/contents/' + filename,
        type: 'put',
        dataType: 'json',
        async: true,
        data: JSON.stringify({
            message: message,
            content: Base64.encode(content),
            sha: sha
        }),
        beforeSend: function(request) {
            request.setRequestHeader("Authorization", "token " + token);
        },
        success: function() {
            set_status(filename.substr(path.length + 1), repo, "succ");
        },
        error: function() {
            set_status(filename.substr(path.length + 1), repo, "err");
        }
    });
}

function set_status(filename, repo, status) {
    var $obj = filename == "README.md" ? $("#readme_alert") : $("#commit_status");
    if (status == "succ") {
        $obj.html('Commit "' + filename + '" to repository "' + repo + '" successfully');
        $obj.css("color", "green");
        setTimeout(function() {
            $obj.html("");
        }, 10000);
    } else {
        $obj.html("Fail to commit " + filename);
        $obj.css("color", "red");
    }
}

function get_path() {
    return $(".question-title:first").children(":first").html().replace(/ /g, "-").replace(/\./g, "");
}

function get_filename() {
    var filename;
    filename = $("#filename").val();
    if (filename === "") {
        var ext = get_extension();
        filename = "solution" + ext;
    }
    return filename;
}

function get_extension() {
    var ext = postfix[$("select[name=lang]").val()];
    if (!ext) {
        ext = ".code";
    }
    return ext;
}

function get_state(button) {
    var state = "";
    if (button === "submit") {
        state = $("#result-state").html().replace(/(^\s*)|(\s*$)/g, "").toLocaleLowerCase();
    } else if (button === "run") {
        state = $("#result_state").html().replace(/(^\s*)|(\s*$)/g, "").toLocaleLowerCase();
    } else {
        return;
    }
    if (state === "" || state === "pending" || state === "judging" || state === "failed") {
        state = "";
    }
    return state;
}

function parse_comment(comment) {
    var title = $(".question-title:first").children(":first").html();
    var state = $("#result-state").html().replace(/(^\s*)|(\s*$)/g, "");
    return comment.replace(/\{title\}/g, title).replace(/\{state\}/g, state);
}

function upload_problem() {
    var problem = {};
    var $question = $(".question-content:first").clone();
    if ($question.children("div:first").find("a:first").html() === "Subscribe")
        $question.children("div:first").remove();

    problem.title = $(".question-title:first").children(":first").html().replace(/^\d+\. */, "");
    problem.url = window.location.href;
    problem.content = Base64.encode($.trim($question.html()));
    problem.difficulty = $(".total-submit:last strong").html();

    var companies = [];
    if ($("#company_tags").length > 0) {
        $("#company_tags").next().children().each(function() {
            companies.push($(this).html());
        });
    }

    var tags = [];
    if ($("#tags").length > 0) {
        $("#tags").next().children().each(function () {
            tags.push($(this).html());
        });
    }

    var similarities = [];
    if ($("#similar").length > 0) {
        $("#similar").next().children().each(function () {
            similarities.push($(this).html());
        });
    }

    var contributor = {};
    contributor.github = user;
    contributor.leetcode = $.trim($(".dropdown-toggle:last").text());
    contributor.version = chrome.runtime.getManifest().version;

    var data = {};
    data.problem = problem;
    data.md5 = md5(JSON.stringify(problem));
    data.companies = companies.sort();
    data.tags = tags.sort();
    data.similarities = similarities.sort();
    //data.locked = false;
    data.contributor = contributor;
    data.create_time = new Date();

    $.ajax({
        url: "https://chrome-ext.luxiakun.com/leetcode-ext/problem",
        type: 'post',
        contentType: "application/json",
        dataType: 'json',
        async: true,
        data: JSON.stringify(data),
        success: function (jsonData) {
            // window.open('', '_self', '');
            // window.close();
        },
        error: function(err) {
            console.log(err);
        }
    });
}<commit_msg>add problem id;bugfix for remove subscribe<commit_after>/**
 * Created by binarylu on 3/18/16.
 */

var github_api = 'https://api.github.com';

var token = "";
var user = "";
var repo = "";
var commit_cond = "";
var default_comment = "";

var postfix = {
    c: ".c",
    cpp: ".cpp",
    java: ".java",
    python: ".py",
    csharp: ".cs",
    javascript: ".js",
    ruby: ".rb",
    swift: ".swift",
    golang: ".go",
    base: ".sh",
    mysql: ".sql"
};

$(function(){
    var reg = new RegExp('submissions');
    if (window.location.pathname.match(reg) !== null)
        return false;
    chrome.storage.sync.get({
        token: '',
        oauth_token: '',
        user: '',
        repo_name: '',
        commit: ["accepted", "time limit exceeded", "compile error", "runtime error"],
        comment: ''
    }, function(items) {
        if(chrome.runtime.lastError) {
            console.log(chrome.runtime.lastError.message);
            return;
        }
        token = items.oauth_token;
        user = items.user;
        repo = items.repo_name;
        commit_cond = items.commit;
        default_comment = items.comment;

        upload_problem();
        if (items.token !== '' && items.oauth_token === '') {
            var $bulletin = $("");
            $("code-button").after($bulletin);
            var content = "LeetCode Extension does not support personal access token any more, please go to option page to login with OAuth.";
            $("#bulletin").html(content);
            $("#bulletin").css("color", "red");
        }

        if (token && user && repo) {
            add_node();
            $("#readme_button").click(restore);
            // $("#commit_readme").click({from: "commit_readme"}, commit);
            // $("#commit_question").click({from: "commit_question"}, commit);
            document.getElementById("commit_readme").onclick=function(){commit("commit_readme");};
            document.getElementById("commit_question").onclick=function(){commit("commit_question");};
            // submit button
            $("#button1").click(function() {
                $("#commit_status").html("");
            });
            $("#filename").val(get_filename());
            $('select[name=lang]').change(function () {
                var ext = get_extension();
                var filename = get_filename();
                filename = filename.substring(0, filename.lastIndexOf(".")) + ext;
                $("#filename").val(filename);
            });
            // result of submit
            $("#result-state").bind("DOMSubtreeModified", function() {
                var state = get_state("submit");
                if (state !== "" && $.inArray(state, commit_cond) != -1) {
                    commit("");
                }
            });
        } else {
            console.log("token: " + token);
            console.log("user: " + token);
            console.log("repo: " + token);
        }
    });
});

function add_node() {
    var $buttons = $("" +
        "<button type='button' class='btn btn-success' id='commit_question'>Add Question</button> " +
        "<button type='button' class='btn btn-success' data-toggle='modal' data-target='#readme' id='readme_button'>Add 'README.md'</button>" +
        "");
    var $div = $('' +
        '
' +
            '<form class="form-inline">' +
                '
' +
                    '
' +
                        '
' +
                            '' +
                                'Filename	' +
                            '' +
                                '<input type="text" class="form-control" id="filename" placeholder="Filename">	' +
                            '' +
                                'Comment	' +
                            '' +
                                '<input style="width:100%" type="text" class="form-control" id="code_message" placeholder="Input the comment for git commits">	' +
                        '
' +
                    '
' +
                '
' +
            '</form>' +
        '
');
    var $modal = $('' +
        '
' +
            '
' +
                '
' +
                    '
' +
                        '<button type="button" class="close" data-dismiss="modal" aria-label="Close">×</button>' +
                        '
README.md
' +
                    '
' +
                    '
' +
                        'The following content will be commit to your Github repository.' +
                        '<textarea id="readme_content" class="form-control" rows="10"></textarea>
' +
                        '<input id="readme_message" type="text" class="form-control" placeholder="Input comments for git commit">' +
                    '
' +
                    '
' +
                        '' +
                        '<button type="button" class="btn btn-default" data-dismiss="modal">Close</button>' +
                        '<button type="button" class="btn btn-primary" id="commit_readme">Commit</button>' +
                    '
' +
                '
' +
            '
' +
        '
');
    var $code = $("" +
        "<textarea id='code_content' style='display:none'></textarea>");

    $("code-button").after($buttons);
    $("div.action").before($div);
    $("body").append($modal).append($code);
    $("#code_message").val(default_comment);

    /* Only by this, the variable in original page can be got.
    * Even using jQuery like before can not get the variable in original page */
    var script = document.createElement("script");
    script.innerHTML = "var lxk_editor = {};" +
        "$(function(){" +
            "$('.editor').attr('id', 'ace_editor_id');" +
            "lxk_editor = ace.edit('ace_editor_id');" +
            "get_code();" +
            "$('select[name=lang]').change(get_code);" +
            "$('#button0').click(get_code);" +
            "$('#button1').click(get_code);" +
        "});" +
        "function get_code() {" +
            "$('#code_content').val(lxk_editor.getValue());" +
        "}";
    document.body.appendChild(script);
}

function commit(from) {
    $("#commit_status").html("");
    var filename = "";
    if (from == "commit_readme") {
        filename = "README.md";
    } else if (from == "commit_question") {
        filename = "Question.md";
    } else {
        filename = get_filename();
    }
    filename = get_path() + "/" + filename;
    get_file(filename, update_file, update_file);
}

function restore() {
    var filename = "README.md";
    filename = get_path() + "/" + filename;
    get_file(filename, restore_file);
    $("#readme_message").val(default_comment);
}

function get_file(filename, fsucc, ferr) {
    $.ajax({
        url: github_api + '/repos/' + user  + '/' + repo + '/contents/' + filename,
        type: 'get',
        dataType: 'json',
        async: true,
        beforeSend: function(request) {
            request.setRequestHeader("Authorization", "token " + token);
        },
        success: function(jsonData) {
            if (typeof(jsonData)=='undefined' || !jsonData) jsonData = {};
            var sha = jsonData.sha;
            var file_content = jsonData.content;
            fsucc(filename, sha, file_content);
        },
        error: function(err) {
            if (err.status == 404) {
                if(typeof ferr === "function") {
                    ferr(filename);
                }
            } else {
                set_status(filename.substr(path.length + 1), repo, "err");
            }
        }
    });
}

function restore_file(filename, sha, file_content) {
    if (filename == get_path() + "/README.md") {
        $("#readme_content").val(Base64.decode(file_content));
    }
}

function update_file(filename, sha) {
    var path = get_path();
    var content = "";
    var message = "";
    if (filename == path + "/README.md") {
        content = $("#readme_content").val();
        message = $("#readme_message").val();
        if (message === "") {
            message = default_comment;
        }
        message = message.replace(/\{state\}/g, "");
    } else if (filename == path + "/Question.md") {
        content = "# " + $(".question-title:first").children(":first").html() + "\n\n";
        content += "[Original Page](" + window.location.href + ")\n\n";
        content += toMarkdown($(".question-content:first").html());
        message = default_comment.replace(/\{state\}/g, "");
    } else {
        content = $("#code_content").val();
        message = $("#code_message").val();
        if (message === "") {
            message = default_comment;
        }
    }
    message = parse_comment(message);

    $.ajax({
        url: github_api + '/repos/' + user  + '/' + repo + '/contents/' + filename,
        type: 'put',
        dataType: 'json',
        async: true,
        data: JSON.stringify({
            message: message,
            content: Base64.encode(content),
            sha: sha
        }),
        beforeSend: function(request) {
            request.setRequestHeader("Authorization", "token " + token);
        },
        success: function() {
            set_status(filename.substr(path.length + 1), repo, "succ");
        },
        error: function() {
            set_status(filename.substr(path.length + 1), repo, "err");
        }
    });
}

function set_status(filename, repo, status) {
    var $obj = filename == "README.md" ? $("#readme_alert") : $("#commit_status");
    if (status == "succ") {
        $obj.html('Commit "' + filename + '" to repository "' + repo + '" successfully');
        $obj.css("color", "green");
        setTimeout(function() {
            $obj.html("");
        }, 10000);
    } else {
        $obj.html("Fail to commit " + filename);
        $obj.css("color", "red");
    }
}

function get_path() {
    return $(".question-title:first").children(":first").html().replace(/ /g, "-").replace(/\./g, "");
}

function get_filename() {
    var filename;
    filename = $("#filename").val();
    if (filename === "") {
        var ext = get_extension();
        filename = "solution" + ext;
    }
    return filename;
}

function get_extension() {
    var ext = postfix[$("select[name=lang]").val()];
    if (!ext) {
        ext = ".code";
    }
    return ext;
}

function get_state(button) {
    var state = "";
    if (button === "submit") {
        state = $("#result-state").html().replace(/(^\s*)|(\s*$)/g, "").toLocaleLowerCase();
    } else if (button === "run") {
        state = $("#result_state").html().replace(/(^\s*)|(\s*$)/g, "").toLocaleLowerCase();
    } else {
        return;
    }
    if (state === "" || state === "pending" || state === "judging" || state === "failed") {
        state = "";
    }
    return state;
}

function parse_comment(comment) {
    var title = $(".question-title:first").children(":first").html();
    var state = $("#result-state").html().replace(/(^\s*)|(\s*$)/g, "");
    return comment.replace(/\{title\}/g, title).replace(/\{state\}/g, state);
}

function upload_problem() {
    var problem = {};
    var $question = $(".question-content:first").clone();

    $question.children("div").each(function() {
        if ($(this).find("a:first").length !== 0 && $(this).find("a:first").html() === "Subscribe") {
            $(this).remove();
        }
    });

    var title = $(".question-title:first").children(":first").html();
    problem.id = title.match(/^\d+/)[0];
    problem.title = title.replace(/^\d+\. */, "");
    problem.url = window.location.href;
    problem.content = Base64.encode($.trim($question.html()));
    problem.difficulty = $(".total-submit:last strong").html();

    var companies = [];
    if ($("#company_tags").length > 0) {
        $("#company_tags").next().children().each(function() {
            companies.push($(this).html());
        });
    }

    var tags = [];
    if ($("#tags").length > 0) {
        $("#tags").next().children().each(function () {
            tags.push($(this).html());
        });
    }

    var similarities = [];
    if ($("#similar").length > 0) {
        $("#similar").next().children().each(function () {
            similarities.push($(this).html());
        });
    }

    var contributor = {};
    contributor.github = user;
    contributor.leetcode = $.trim($(".dropdown-toggle:last").text());
    contributor.version = chrome.runtime.getManifest().version;

    var data = {};
    data.problem = problem;
    data.md5 = md5(JSON.stringify(problem));
    data.companies = companies.sort();
    data.tags = tags.sort();
    data.similarities = similarities.sort();
    //data.locked = false;
    data.contributor = contributor;
    data.create_time = new Date();

    $.ajax({
        url: "https://chrome-ext.luxiakun.com/leetcode-ext/problem",
        type: 'post',
        contentType: "application/json",
        dataType: 'json',
        async: true,
        data: JSON.stringify(data),
        success: function (jsonData) {
            // window.open('', '_self', '');
            // window.close();
        },
        error: function(err) {
            console.log(err);
        }
    });
}<|endoftext|>