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

var databank = require('databank'),
    _ = require('underscore'),
    DatabankObject = databank.DatabankObject,
    Stamper = require('../stamper').Stamper,
    bcrypt  = require('bcrypt'),
    Step = require('step'),
    Person = require('./person').Person,
    Stream = require('./stream').Stream,
    Activity = require('./activity').Activity,
    ActivityObject = require('./activityobject').ActivityObject,
    URLMaker = require('../urlmaker').URLMaker,
    NoSuchThingError = databank.NoSuchThingError;

var User = DatabankObject.subClass('user');

exports.User = User;

// for updating

User.prototype.beforeUpdate = function(props, callback) {

    // XXX: required, but immutable. Boooooo.

    if (!_(props).has('nickname')) {
        callback(new Error("'nickname' property is required"), null);
        return;
    } else if (props.nickname !== this.nickname) {
        callback(new Error("'nickname' is immutable"), null);
        return;
    } else {
        delete props.nickname;
    }

    // XXX: required. Seems not strictly necessary, but whatever.

    if (!_(props).has('password')) {
        callback(new Error("'password' property is required"), null);
        return;
    }

    // Callers must omit or leave equal

    if (_(props).has('published')) {
        if (props.published !== this.published) {
            callback(new Error("'published' is autogenerated and immutable"), null);
            return;
        } else {
            delete props.published;
        }
    }

    // Callers must omit or leave equal

    if (_(props).has('profile')) {
        // XXX: we should probably do some deep-equality check
        if (!_(props.profile).has('id') || props.profile.id !== this.profile.id ||
            !_(props.profile).has('objectType') || props.profile.objectType !== this.profile.objectType ||
            !_(props.profile).has('displayName') || props.profile.displayName !== this.profile.displayName) {
            callback(new Error("'profile' is immutable"), null);
            return;
        } else {
            delete props.profile;
        }
    }

    // Callers must omit or leave equal

    if (_(props).has('updated') && props.updated !== this.updated) {
        callback(new Error("'updated' is autogenerated"), null);
        return;
    }

    props.updated = Stamper.stamp();

    Step(
        function() {
            bcrypt.genSalt(10, this);
        },
        function(err, salt) {
            if (err) throw err;
            bcrypt.hash(props.password, salt, this);
        },
        function(err, hash) {
            if (err) {
                callback(err, null);
            } else {
                props.passwordHash = hash;
                delete props.password;
                callback(null, props);
            }
        }
    );
};

// For creating

User.beforeCreate = function(props, callback) {

    if (!props.nickname || !props.password) {
        callback(new Error('Gotta have a nickname and a password.'), null);
        return;
    }

    var now = Stamper.stamp();

    props.published = props.updated = now;

    Step(
        function() {
            bcrypt.genSalt(10, this);
        },
        function(err, salt) {
            if (err) throw err;
            bcrypt.hash(props.password, salt, this);
        },
        function(err, hash) {
            if (err) throw err;
            props.passwordHash = hash;
            delete props.password;
            Person.create({'preferredUsername': props.nickname,
                           url: URLMaker.makeURL(props.nickname),
                           displayName: props.nickname}, this);
        },
        function(err, person) {
            if (err) throw err;
            props.profile = {
                objectType: 'person',
                id: person.id
            };

            Stream.create({name: props.nickname + "-inbox"}, this.parallel());
            Stream.create({name: props.nickname + "-outbox"}, this.parallel());
            Stream.create({name: props.nickname + "-followers"}, this.parallel());
            Stream.create({name: props.nickname + "-following"}, this.parallel());
        },
        function(err, inbox, outbox, followers, following) {
            callback(null, props);
        }
    );
};

User.prototype.sanitize = function() {
    delete this.password;
    delete this.passwordHash;
};

User.prototype.getProfile = function(callback) {
    var user = this;
    Step(
        function() {
            ActivityObject.getObject(user.profile.objectType, user.profile.id, this);
        },
        function(err, profile) {
            if (err) {
                callback(err, null);
            } else {
                callback(null, profile);
            }
        }
    );
};

User.prototype.getStream = function(start, end, callback) {
    var bank = User.bank(),
        user = this,
        ids, activities;

    Step(
        function() {
            Stream.get(user.nickname + '-outbox', this);
        },
        function(err, outbox) {
            if (err) throw err;
            outbox.getIDs(start, end, this);
        },
        function(err, results) {
            if (err) throw err;
            ids = results;
            if (ids.length === 0) {
                callback(null, []);
            } else {
                Activity.readAll(ids, this);
            }
        },
        function(err, map) {
            var i, group = this.group();
            if (err) throw err;
            activities = [];
            // Try to get it in the right order
            for (i = 0; i < ids.length; i++) {
                activities.push(map[ids[i]]);
            }
            // FIXME: batch fetch all actors and objects
            activities.forEach(function(el, i, arr) {
                // remove internal uuid info, if any
                delete el.uuid;
                delete el.actor;
                el.expand(group());
            });
        },
        function(err) {
            if (err) {
                if (err instanceof NoSuchThingError) {
                    callback(null, []);
                } else {
                    callback(err, null);
                }
            } else {
                callback(null, activities);
            }
        }
    );
};

User.prototype.getInbox = function(start, end, callback) {

    var bank = User.bank(),
        user = this,
        activities = null,
        ids;

    Step(
        function() {
            Stream.get(user.nickname + '-inbox', this);
        },
        function(err, inbox) {
            if (err) throw err;
            inbox.getIDs(start, end, this);
        },
        function(err, results) {
            if (err) throw err;
            ids = results;
            if (ids.length === 0) {
                callback(null, []);
            } else {
                Activity.readAll(ids, this);
            }
        },
        function(err, map) {
            var i, group = this.group();
            if (err) throw err;
            activities = [];
            // Try to get it in the right order
            for (i = 0; i < ids.length; i++) {
                activities.push(map[ids[i]]);
            }
            // FIXME: batch fetch all actors and objects
            activities.forEach(function(el, i, arr) {
                // remove internal uuid info, if any
                delete el.uuid;
                el.expand(group());
            });
        },
        function(err) {
            if (err) {
                if (err instanceof NoSuchThingError) {
                    callback(null, []);
                } else {
                    callback(err, null);
                }
            } else {
                callback(null, activities);
            }
        }
    );
};

User.prototype.getFollowers = function(start, end, callback) {
    this.getPeople(this.nickname + '-followers', start, end, callback);
};

User.prototype.getFollowing = function(start, end, callback) {
    this.getPeople(this.nickname + '-following', start, end, callback);
};

User.prototype.getPeople = function(stream, start, end, callback) {

    var user = this, ids;

    Step(
        function() {
            Stream.get(stream, this);
        },
        function(err, pstream) {
            if (err) throw err;
            pstream.getIDs(start, end, this);
        },
        function(err, results) {
            if (err) throw err;
            ids = results;
            if (ids.length === 0) {
                callback(null, []);
            } else {
                Person.readAll(ids, this);
            }
        },
        function(err, map) {
            var i, people = [];
            if (err) {
                if (err instanceof NoSuchThingError) {
                    callback(null, []);
                } else {
                    callback(err, null);
                }
            } else {
                people = new Array(ids.length);
                // Try to get it in the right order
                for (i = 0; i < ids.length; i++) {
                    people[i] = map[ids[i]];
                }
                callback(null, people);
            }
        }
    );
};

User.prototype.followerCount = function(callback) {
    Stream.count(this.nickname + '-followers', callback);
};

User.prototype.followingCount = function(callback) {
    Stream.count(this.nickname + '-following', callback);
};

User.prototype.follow = function(id, callback) {
    var user = this;
    Step(
        function() {
            Stream.get(user.nickname + "-following", this);
        },
        function(err, stream) {
            if (err) throw err;
            stream.deliver(id, this);
        },
        function(err) {
            if (err) throw err;
            User.fromPerson(id, this);
        },
        function(err, other) {
            if (err) throw err;
            if (!other) {
                // XXX: Remote follow
                callback(null);
            } else {
                Stream.get(other.nickname + "-followers", this);
            }
        },
        function(err, stream) {
            if (err) throw err;
            stream.deliver(user.profile.id, this);
        },
        function(err) {
            if (err) {
                callback(err);
            } else {
                callback(null);
            }
        }
    );
};

User.prototype.stopFollowing = function(id, callback) {
    var user = this;
    Step(
        function() {
            Stream.get(user.nickname + "-following", this);
        },
        function(err, stream) {
            if (err) throw err;
            stream.remove(id, this);
        },
        function(err) {
            if (err) throw err;
            User.fromPerson(id, this);
        },
        function(err, other) {
            if (err) throw err;
            if (!other) {
                // XXX: Remote follow
                callback(null);
            } else {
                Stream.get(other.nickname + "-followers", this);
            }
        },
        function(err, stream) {
            if (err) throw err;
            stream.remove(user.profile.id, this);
        },
        function(err) {
            if (err) {
                callback(err);
            } else {
                callback(null);
            }
        }
    );
};

User.prototype.expand = function(callback) {
    var user = this;

    ActivityObject.expandProperty(user, 'profile', callback);
};

User.fromPerson = function(id, callback) {
    Step(
        function() {
            User.search({"profile.id": id}, this);
        },
        function(err, results) {
            if (err) {
                callback(err, null);
            } else if (results.length === 0) {
                callback(null, null);
            } else {
                callback(null, results[0]);
            }
        }
    );
};

User.prototype.addToOutbox = function(activity, callback) {
    var user = this;
    Step(
        function() {
            Stream.get(user.nickname + "-outbox", this);
        },
        function(err, stream) {
            if (err) throw err;
            stream.deliver(activity.id, callback);
        }
    );
};

User.prototype.addToInbox = function(activity, callback) {
    var user = this;
    Step(
        function() {
            Stream.get(user.nickname + "-inbox", this);
        },
        function(err, stream) {
            if (err) throw err;
            stream.deliver(activity.id, callback);
        }
    );
};

// Check the credentials for a user
// callback takes args:
// - err: if there's an error (NB: null if credentials don't match)
// - user: User object or null if credentials don't match

User.checkCredentials = function(nickname, password, callback) {
    var user = null;

    Step(
        function() {
            User.get(nickname, this);
        },
        function(err, result) {
            if (err) {
                if (err instanceof NoSuchThingError) {
                    callback(null, null);
                    return; // done
                } else {
                    throw err;
                }
            } else {
                user = result;
                bcrypt.compare(password, user.passwordHash, this);
            }
        },
        function(err, res) {
            if (err) {
                callback(err, null);
            } else if (!res) {
                callback(null, null);
            } else {
                // Don't percolate that hash around
                user.sanitize();
                callback(null, user);
            }
        }
    );
};

User.schema = {'pkey': 'nickname',
               'fields': ['passwordHash',
                          'published',
                          'updated',
                          'profile'],
               'indices': ['profile.id']};
<commit_msg>Create, delete Edge objects on follow/unfollow<commit_after>// user.js
//
// A local user; distinct from a person
//
// Copyright 2011,2012 StatusNet Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

var databank = require('databank'),
    _ = require('underscore'),
    DatabankObject = databank.DatabankObject,
    Stamper = require('../stamper').Stamper,
    bcrypt  = require('bcrypt'),
    Step = require('step'),
    Person = require('./person').Person,
    Stream = require('./stream').Stream,
    Activity = require('./activity').Activity,
    ActivityObject = require('./activityobject').ActivityObject,
    Edge = require('./edge').Edge,
    URLMaker = require('../urlmaker').URLMaker,
    NoSuchThingError = databank.NoSuchThingError;

var User = DatabankObject.subClass('user');

exports.User = User;

// for updating

User.prototype.beforeUpdate = function(props, callback) {

    // XXX: required, but immutable. Boooooo.

    if (!_(props).has('nickname')) {
        callback(new Error("'nickname' property is required"), null);
        return;
    } else if (props.nickname !== this.nickname) {
        callback(new Error("'nickname' is immutable"), null);
        return;
    } else {
        delete props.nickname;
    }

    // XXX: required. Seems not strictly necessary, but whatever.

    if (!_(props).has('password')) {
        callback(new Error("'password' property is required"), null);
        return;
    }

    // Callers must omit or leave equal

    if (_(props).has('published')) {
        if (props.published !== this.published) {
            callback(new Error("'published' is autogenerated and immutable"), null);
            return;
        } else {
            delete props.published;
        }
    }

    // Callers must omit or leave equal

    if (_(props).has('profile')) {
        // XXX: we should probably do some deep-equality check
        if (!_(props.profile).has('id') || props.profile.id !== this.profile.id ||
            !_(props.profile).has('objectType') || props.profile.objectType !== this.profile.objectType ||
            !_(props.profile).has('displayName') || props.profile.displayName !== this.profile.displayName) {
            callback(new Error("'profile' is immutable"), null);
            return;
        } else {
            delete props.profile;
        }
    }

    // Callers must omit or leave equal

    if (_(props).has('updated') && props.updated !== this.updated) {
        callback(new Error("'updated' is autogenerated"), null);
        return;
    }

    props.updated = Stamper.stamp();

    Step(
        function() {
            bcrypt.genSalt(10, this);
        },
        function(err, salt) {
            if (err) throw err;
            bcrypt.hash(props.password, salt, this);
        },
        function(err, hash) {
            if (err) {
                callback(err, null);
            } else {
                props.passwordHash = hash;
                delete props.password;
                callback(null, props);
            }
        }
    );
};

// For creating

User.beforeCreate = function(props, callback) {

    if (!props.nickname || !props.password) {
        callback(new Error('Gotta have a nickname and a password.'), null);
        return;
    }

    var now = Stamper.stamp();

    props.published = props.updated = now;

    Step(
        function() {
            bcrypt.genSalt(10, this);
        },
        function(err, salt) {
            if (err) throw err;
            bcrypt.hash(props.password, salt, this);
        },
        function(err, hash) {
            if (err) throw err;
            props.passwordHash = hash;
            delete props.password;
            Person.create({'preferredUsername': props.nickname,
                           url: URLMaker.makeURL(props.nickname),
                           displayName: props.nickname}, this);
        },
        function(err, person) {
            if (err) throw err;
            props.profile = {
                objectType: 'person',
                id: person.id
            };

            Stream.create({name: props.nickname + "-inbox"}, this.parallel());
            Stream.create({name: props.nickname + "-outbox"}, this.parallel());
            Stream.create({name: props.nickname + "-followers"}, this.parallel());
            Stream.create({name: props.nickname + "-following"}, this.parallel());
        },
        function(err, inbox, outbox, followers, following) {
            callback(null, props);
        }
    );
};

User.prototype.sanitize = function() {
    delete this.password;
    delete this.passwordHash;
};

User.prototype.getProfile = function(callback) {
    var user = this;
    Step(
        function() {
            ActivityObject.getObject(user.profile.objectType, user.profile.id, this);
        },
        function(err, profile) {
            if (err) {
                callback(err, null);
            } else {
                callback(null, profile);
            }
        }
    );
};

User.prototype.getStream = function(start, end, callback) {
    var bank = User.bank(),
        user = this,
        ids, activities;

    Step(
        function() {
            Stream.get(user.nickname + '-outbox', this);
        },
        function(err, outbox) {
            if (err) throw err;
            outbox.getIDs(start, end, this);
        },
        function(err, results) {
            if (err) throw err;
            ids = results;
            if (ids.length === 0) {
                callback(null, []);
            } else {
                Activity.readAll(ids, this);
            }
        },
        function(err, map) {
            var i, group = this.group();
            if (err) throw err;
            activities = [];
            // Try to get it in the right order
            for (i = 0; i < ids.length; i++) {
                activities.push(map[ids[i]]);
            }
            // FIXME: batch fetch all actors and objects
            activities.forEach(function(el, i, arr) {
                // remove internal uuid info, if any
                delete el.uuid;
                delete el.actor;
                el.expand(group());
            });
        },
        function(err) {
            if (err) {
                if (err instanceof NoSuchThingError) {
                    callback(null, []);
                } else {
                    callback(err, null);
                }
            } else {
                callback(null, activities);
            }
        }
    );
};

User.prototype.getInbox = function(start, end, callback) {

    var bank = User.bank(),
        user = this,
        activities = null,
        ids;

    Step(
        function() {
            Stream.get(user.nickname + '-inbox', this);
        },
        function(err, inbox) {
            if (err) throw err;
            inbox.getIDs(start, end, this);
        },
        function(err, results) {
            if (err) throw err;
            ids = results;
            if (ids.length === 0) {
                callback(null, []);
            } else {
                Activity.readAll(ids, this);
            }
        },
        function(err, map) {
            var i, group = this.group();
            if (err) throw err;
            activities = [];
            // Try to get it in the right order
            for (i = 0; i < ids.length; i++) {
                activities.push(map[ids[i]]);
            }
            // FIXME: batch fetch all actors and objects
            activities.forEach(function(el, i, arr) {
                // remove internal uuid info, if any
                delete el.uuid;
                el.expand(group());
            });
        },
        function(err) {
            if (err) {
                if (err instanceof NoSuchThingError) {
                    callback(null, []);
                } else {
                    callback(err, null);
                }
            } else {
                callback(null, activities);
            }
        }
    );
};

User.prototype.getFollowers = function(start, end, callback) {
    this.getPeople(this.nickname + '-followers', start, end, callback);
};

User.prototype.getFollowing = function(start, end, callback) {
    this.getPeople(this.nickname + '-following', start, end, callback);
};

User.prototype.getPeople = function(stream, start, end, callback) {

    var user = this, ids;

    Step(
        function() {
            Stream.get(stream, this);
        },
        function(err, pstream) {
            if (err) throw err;
            pstream.getIDs(start, end, this);
        },
        function(err, results) {
            if (err) throw err;
            ids = results;
            if (ids.length === 0) {
                callback(null, []);
            } else {
                Person.readAll(ids, this);
            }
        },
        function(err, map) {
            var i, people = [];
            if (err) {
                if (err instanceof NoSuchThingError) {
                    callback(null, []);
                } else {
                    callback(err, null);
                }
            } else {
                people = new Array(ids.length);
                // Try to get it in the right order
                for (i = 0; i < ids.length; i++) {
                    people[i] = map[ids[i]];
                }
                callback(null, people);
            }
        }
    );
};

User.prototype.followerCount = function(callback) {
    Stream.count(this.nickname + '-followers', callback);
};

User.prototype.followingCount = function(callback) {
    Stream.count(this.nickname + '-following', callback);
};

User.prototype.follow = function(id, callback) {
    var user = this;
    Step(
        function() {
            Edge.create({from: user.profile.id, to: id}, this);
        },
        function(err, edge) {
            if (err) throw err;
            Stream.get(user.nickname + "-following", this);
        },
        function(err, stream) {
            if (err) throw err;
            stream.deliver(id, this);
        },
        function(err) {
            if (err) throw err;
            User.fromPerson(id, this);
        },
        function(err, other) {
            if (err) throw err;
            if (!other) {
                // XXX: Remote follow
                callback(null);
            } else {
                Stream.get(other.nickname + "-followers", this);
            }
        },
        function(err, stream) {
            if (err) throw err;
            stream.deliver(user.profile.id, this);
        },
        function(err) {
            if (err) {
                callback(err);
            } else {
                callback(null);
            }
        }
    );
};

User.prototype.stopFollowing = function(id, callback) {
    var user = this;
    Step(
        function() {
            Edge.search({from: user.profile.id, to: id}, this);
        },
        function(err, edges) {
            if (err) throw err;
            if (edges.length === 0) {
                throw new NoSuchThingError('edge', user.profile.id + '/' + id);
            }
            edges[0].del(this);
        },
        function(err) {
            if (err) throw err;
            Stream.get(user.nickname + "-following", this);
        },
        function(err, stream) {
            if (err) throw err;
            stream.remove(id, this);
        },
        function(err) {
            if (err) throw err;
            User.fromPerson(id, this);
        },
        function(err, other) {
            if (err) throw err;
            if (!other) {
                // XXX: Remote follow
                callback(null);
            } else {
                Stream.get(other.nickname + "-followers", this);
            }
        },
        function(err, stream) {
            if (err) throw err;
            stream.remove(user.profile.id, this);
        },
        function(err) {
            if (err) {
                callback(err);
            } else {
                callback(null);
            }
        }
    );
};

User.prototype.expand = function(callback) {
    var user = this;

    ActivityObject.expandProperty(user, 'profile', callback);
};

User.fromPerson = function(id, callback) {
    Step(
        function() {
            User.search({"profile.id": id}, this);
        },
        function(err, results) {
            if (err) {
                callback(err, null);
            } else if (results.length === 0) {
                callback(null, null);
            } else {
                callback(null, results[0]);
            }
        }
    );
};

User.prototype.addToOutbox = function(activity, callback) {
    var user = this;
    Step(
        function() {
            Stream.get(user.nickname + "-outbox", this);
        },
        function(err, stream) {
            if (err) throw err;
            stream.deliver(activity.id, callback);
        }
    );
};

User.prototype.addToInbox = function(activity, callback) {
    var user = this;
    Step(
        function() {
            Stream.get(user.nickname + "-inbox", this);
        },
        function(err, stream) {
            if (err) throw err;
            stream.deliver(activity.id, callback);
        }
    );
};

// Check the credentials for a user
// callback takes args:
// - err: if there's an error (NB: null if credentials don't match)
// - user: User object or null if credentials don't match

User.checkCredentials = function(nickname, password, callback) {
    var user = null;

    Step(
        function() {
            User.get(nickname, this);
        },
        function(err, result) {
            if (err) {
                if (err instanceof NoSuchThingError) {
                    callback(null, null);
                    return; // done
                } else {
                    throw err;
                }
            } else {
                user = result;
                bcrypt.compare(password, user.passwordHash, this);
            }
        },
        function(err, res) {
            if (err) {
                callback(err, null);
            } else if (!res) {
                callback(null, null);
            } else {
                // Don't percolate that hash around
                user.sanitize();
                callback(null, user);
            }
        }
    );
};

User.schema = {'pkey': 'nickname',
               'fields': ['passwordHash',
                          'published',
                          'updated',
                          'profile'],
               'indices': ['profile.id']};
<|endoftext|>


<commit_before>const randomBackground = () => {
  let bgImg = document.getElementById('bg')
  let baseURL = 'https://source.unsplash.com/random/'
  let resolution = `${screen.width}x${screen.height}`
  let imageURL = baseURL.concat(resolution)

  caches.match(imageURL).then(response => {
    bgImg.src = response.url
  }).catch(() => {
    fetch(imageURL).then(response => {
      if (response.ok) {
        bgImg.src = response.url
        caches.open('bg-next').then(cache => {
          cache.put(imageURL, response)
        })
      }
    })
  })

  fetch(imageURL).then(response => {
    caches.open('bg-next').then(cache => {
      cache.put(imageURL, response)
    })
  })
}

const vimLike = () => {
  let home = document.getElementById('logo')
  if (window.location.pathname == '/') { // In post list
    let sections = document.getElementsByName('focusable')
    let i = -1
    window.addEventListener('keypress', event => {
      if (event.key == 'j' && i < sections.length - 1) {
        sections[++i].focus()
      } else if (event.key == 'k') {
        if (i == -1 || i == 0) {
          i = 1
        }
        sections[--i].focus()
      } else if (event.key == 'G') {
        i = sections.length - 1
        sections[i].focus()
      } else if (event.key == 'o') {
        sections[i].click()
      } else if (event.key == 'u') {
        home.click()
      }
    })
  } else { // In article
    let article = document.getElementById('article')
    window.addEventListener('keypress', event => {
      if (event.key == 'j') {
        scrollBy(0, 27)
      } else if (event.key == 'k') {
        scrollBy(0, -27)
      } else if (event.key == 'J') {
        scrollBy({
          top: 240,
          behavior: "smooth"
        })
      } else if (event.key == 'K') {
        scrollBy({
          top: -240,
          behavior: "smooth"
        })
      } else if (event.key == 'G') {
        scroll({
          top: article.offsetHeight,
          behavior: "smooth"
        })
      } else if (event.key == 'u') {
        home.click()
      }
    })
  }
}

const lightUp = () => {
  let bg = document.getElementById("bg")
  bg.style.animation = "fadein 4s"
  bg.style.opacity = 1
  let article = document.getElementById('article')
  if (article) {
    article.classList.add('light')
  }
  greeting.style.color = "#eeeeee"
  greeting.style.transition = "opacity 1.5s"
  greeting.style.opacity = 0
}

window.desktopInit = () => {
  randomBackground()
  vimLike()
  let inners = [
    "It's my personal blog",
    "Anyway..",
    "Have a good time!!",
  ]
  let greeting = document.getElementById("greeting")
  greeting.style.transition = "color 1s"
  let i = 0
  if (sessionStorage.getItem('light')) {
    lightUp()
  } else {
    greeting.addEventListener("click", () => {
      if (i < inners.length) {
        greeting.innerHTML = inners[i++]
      }
      if (i == inners.length) {
        lightUp()
        sessionStorage.setItem('light', true)
      }
    })
  }
}

if (screen.width > 480) {
  window.desktopInit()
}

// Register Service Worker
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/sw.js').then(registration => {
      console.log('ServiceWorker registration successful.')
    }).catch(error => {
      console.log('ServiceWorker registration failed: ', error)
    })
  })
}
<commit_msg>Add tips about vim-like shortcuts in console<commit_after>const randomBackground = () => {
  let bgImg = document.getElementById('bg')
  let baseURL = 'https://source.unsplash.com/random/'
  let resolution = `${screen.width}x${screen.height}`
  let imageURL = baseURL.concat(resolution)

  caches.match(imageURL).then(response => {
    bgImg.src = response.url
  }).catch(() => {
    fetch(imageURL).then(response => {
      if (response.ok) {
        bgImg.src = response.url
        caches.open('bg-next').then(cache => {
          cache.put(imageURL, response)
        })
      }
    })
  })

  fetch(imageURL).then(response => {
    caches.open('bg-next').then(cache => {
      cache.put(imageURL, response)
    })
  })
}

const vimLike = () => {
  let home = document.getElementById('logo')
  if (window.location.pathname == '/') { // In post list
    let sections = document.getElementsByName('focusable')
    let i = -1
    window.addEventListener('keypress', event => {
      if (event.key == 'j' && i < sections.length - 1) {
        sections[++i].focus()
      } else if (event.key == 'k') {
        if (i == -1 || i == 0) {
          i = 1
        }
        sections[--i].focus()
      } else if (event.key == 'G') {
        i = sections.length - 1
        sections[i].focus()
      } else if (event.key == 'o') {
        sections[i].click()
      } else if (event.key == 'u') {
        home.click()
      }
    })
  } else { // In article
    let article = document.getElementById('article')
    window.addEventListener('keypress', event => {
      if (event.key == 'j') {
        scrollBy(0, 27)
      } else if (event.key == 'k') {
        scrollBy(0, -27)
      } else if (event.key == 'J') {
        scrollBy({
          top: 240,
          behavior: "smooth"
        })
      } else if (event.key == 'K') {
        scrollBy({
          top: -240,
          behavior: "smooth"
        })
      } else if (event.key == 'G') {
        scroll({
          top: article.offsetHeight,
          behavior: "smooth"
        })
      } else if (event.key == 'u') {
        home.click()
      }
    })
  }
}

const lightUp = () => {
  let bg = document.getElementById('bg')
  bg.style.animation = "fadein 4s"
  bg.style.opacity = 1
  let article = document.getElementById('article')
  if (article) {
    article.classList.add('light')
  }
  greeting.style.color = "#eeeeee"
  greeting.style.transition = "opacity 1.5s"
  greeting.style.opacity = 0
}

window.desktopInit = () => {
  randomBackground()
  vimLike()
  let inners = [
    "It's my personal blog",
    "Anyway..",
    "Have a good time!!"
  ]
  let greeting = document.getElementById('greeting')
  greeting.style.transition = "color 1s"
  let i = 0
  if (sessionStorage.getItem('light')) {
    lightUp()
  } else {
    greeting.addEventListener('click', () => {
      if (i < inners.length) {
        greeting.innerHTML = inners[i++]
      }
      if (i == inners.length) {
        lightUp()
        sessionStorage.setItem('light', true)
      }
    })
  }
}

if (screen.width > 480) {
  window.desktopInit()
}

// Register Service Worker
if ('serviceWorker' in navigator) {
  window.addEventListener('load', () => {
    navigator.serviceWorker.register('/sw.js').then(registration => {
      console.log('ServiceWorker registration successful.')
    }).catch(error => {
      console.log('ServiceWorker registration failed: ', error)
    })
  })
}

// Tips
console.log(
  'If you are a loyal vimer, tell you a good news!\n' +
  'You can actually use vim-like shortcuts to control this web page.\n\n' +
  'j  Scroll down / Select below\n' +
  'k  Scroll up / Select above\n' +
  'o  Open the selected post\n' +
  'u  Up to the home URL\n\n' +
  'And in article pages, you can also:\n\n' +
  'J  Scroll down faster\n' +
  'K  Scroll up faster\n\n' +
  'Happy Viming :)'
)<|endoftext|>


<commit_before>

(function () {
	var width = 1160,
	    height = 500;

	var color = d3.scale.category20();

	var radius = d3.scale.sqrt()
	    .range([0, 6]);

	var atomSelected;
	var atomClicked = function (dataPoint) {
	 	console.log ("atom:", dataPoint);
	 	/*console.log ("this:", this);*/
	 	/*console.log("this.firstElementChild:", this.firstElementChild);*/
	 	/*console.log("this.firstElementChild+d3:", d3.select(this.firstElementChild));*/

	 	if (dataPoint.symbol === "H")
	 		return;

	 	if (atomSelected)
	 		atomSelected.style("filter", "");

	 	atomSelected = d3.select(this)
	 					 			.select("circle")
	 						    .style("filter", "url(#nodeGlow)");
	};

	var bondSelected;
	var bondClicked = function (dataPoint) {
	 	console.log ("bond:", dataPoint);
	 	/*console.log ("this:", this);
	 	console.log("this.firstElementChild:", this.firstElementChild);
	 	console.log("this.firstElementChild+d3:", d3.select(this.firstElementChild));*/

	 	if (bondSelected)
	 		bondSelected.style("filter", "");

	 	bondSelected = d3.select(this)
	 									 .select("line")
	 						    	 .style("filter", "url(#lineGlow)");
	};

	var generateRandomID = function () {
		return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
		  var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
		  return v.toString(16);
		});
	}

	var nodeGlow = glow("nodeGlow").rgb("#0000A0").stdDeviation(7);
	var lineGlow = glow("lineGlow").rgb("#000").stdDeviation(7);

	var svg = d3.select("#moleculeDisplay").append("svg")
	    .attr("width", width)
	    .attr("height", height)
	    .call(nodeGlow)
	    .call(lineGlow);

  var getRandomInt = function (min, max) {
	  return Math.floor(Math.random() * (max - min + 1) + min);
	}

	d3.json("graph.json", function(graph) {
	  console.log(graph);
	  var nodesList = graph.nodes;
	  var linksList = graph.links;

	  var force = d3.layout.force()
	    						.nodes(nodesList)
	    						.links(linksList)
	    						.size([width, height])
	    						.charge(-400)
	    						.linkDistance(function(d) { return radius(d.source.size) + radius(d.target.size) + 20; })
	    						.on("tick", tick);

	  var links = force.links(),
	  		nodes = force.nodes(),
	  		link = svg.selectAll(".link"),
	  		node = svg.selectAll(".node");

	  buildMolecule();

	  function buildMolecule () {
	  	// Update link data
	  	link = link.data(links, function (d) {return d.id; });

		  // Create new links
		  link.enter().insert("g", ".node")
		      .attr("class", "link")
		      .each(function(d) {
		      	// Add bond line
		      	d3.select(this)
		      		.append("line")
							.style("stroke-width", function(d) { return (d.bond * 2 - 1) * 2 + "px"; });

						// If double add second line
						d3.select(this)
							.filter(function(d) { return d.bond > 1; }).append("line")
							.attr("class", "separator");

						// Give bond the power to be selected
						d3.select(this)
							.on("click", bondClicked);
		      });

		  // Delete removed links
		  link.exit().remove();

		  // Update node data
	  	node = node.data(nodes, function (d) {return d.id; });

	    // Create new nodes
		  node.enter().append("g")
		      .attr("class", "node")
		      .each(function(d) {
		      	// Add node circle
			      d3.select(this)
			      	.append("circle")
		      		.attr("r", function(d) { return radius(d.size); })
		      		.style("fill", function(d) { return color(d.symbol); });

		        // Add atom symbol
			      d3.select(this)
			      	.append("text")
							.attr("dy", ".35em")
							.attr("text-anchor", "middle")
							.text(function(d) { return d.symbol; });

						// Give atom the power to be selected
						d3.select(this)
							.on("click", atomClicked);

						// Grant atom the power of gravity
						d3.select(this)
							.call(force.drag);
			    });

		  // Delete removed nodes
	    node.exit().remove();

		  force.start();
	  }

	  window.addAtom = function (atomType) {
	  	console.log("Adding new atom:", atomType);
	  	if (!atomType) {
	  		Messenger().post({
				  message: 'Internal error :(',
				  type: 'error',
				  showCloseButton: true
				});
				return;
	  	}
	  	else if (!atomSelected) {
				Messenger().post({
				  message: 'No atom selected.',
				  type: 'error',
				  showCloseButton: true
				});
				return;
			}
			else if (!canHaveNewBond(getAtomData(atomSelected))) {
				Messenger().post({
				  message: 'Atom can\'t take anymore bonds.',
				  type: 'error',
				  showCloseButton: true
				});
			}
			else
	  		updateMolecule(atomType, atomDB[atomType].size);
	  };

	 	function canHaveNewBond (atom) {
	 		return atom.bonds < atomDB[atom.symbol].lonePairs;
	 	}

	 	function getAtomData (d3Atom) {
	 		return d3Atom[0][0].parentNode.__data__;
	 	}

	 	function addHydrogens (atom, numHydrogens) {
	 		console.log('numHydrogens:', numHydrogens);
	 		var newHydrogen = function () {
	 			return {
		 			symbol: 'H',
		 			size: '1',
		 			bonds: 1,
		 			id: generateRandomID (),
		 			x: atom.x + getRandomInt (-15, 15),
		 			y: atom.y + getRandomInt (-15, 15)
		 		};
	 		};
	 		var tempHydrogen;
	 		for (var i = 0; i < numHydrogens; i++) {
	 			tempHydrogen = newHydrogen();
	 			nodes.push(tempHydrogen);
	 			links.push({source: atom, target: tempHydrogen, bond: 1, id: generateRandomID()});
	 		}
	 	}

	 	function removeHydrogen (oldAtom) {
	 		var target, source;
	 		var bondsArr = getBonds(oldAtom.id);
	 		for (var i = bondsArr.length - 1; i >= 0; i--) {
	 			target = bondsArr[i].target, source = bondsArr[i].source;
				if (target.symbol === 'H' || source.symbol === 'H' ) {
					var hydroId = source.symbol === 'H'?
																		source.id:
																		target.id;
					removeAtom(hydroId);
					return;
				}
	 		}
	 	}

	 	function removeAtom (id) {
	 		var atomToRemove = retriveAtom(id);
	 		var bondsArr = getBonds(id);
	 		var atomsArr = [atomToRemove.id];

	 		for (var i = bondsArr.length - 1; i >= 0; i--) {
	 			if (bondsArr[i].source.symbol === 'H')
	 				atomsArr.push(bondsArr[i].source.id);
	 			else if (bondsArr[i].target.symbol === 'H')
	 				atomsArr.push(bondsArr[i].target.id);
	 			else {
						var nonHydrogenAtom = bondsArr[i].target.id !== id ?
																									 	'target':
																										'source';

						bondsArr[i][nonHydrogenAtom].bonds -= bondsArr[i].bond;
		 				addHydrogens(bondsArr[i][nonHydrogenAtom], bondsArr[i].bond);
	 			}
	 			bondsArr[i] = bondsArr[i].id;
	 		}

	 		var spliceOut = function (arr, removeArr) {
		 		for (var i = arr.length - 1; i >= 0; i--) {
		 				if (removeArr.indexOf(arr[i].id) !== -1) {
		 					arr.splice(i, 1);
		 				}
		 		}
		 		return arr;
		 	};

	 		// Remove atoms marked
	 		nodes = spliceOut (nodes, atomsArr);

	 		// Remove bonds marked
	 		links = spliceOut (links, bondsArr);

	 	};

	 	var retriveAtom = function  (atomID) {
	 		for (var i = nodes.length - 1; i >= 0; i--) {
	 			if (nodes[i].id === atomID)
	 				return nodes[i];
	 		}
	 		return null;
	 	};

	  function updateMolecule (atomType, atomSize) {
			var newAtom = {
						symbol: atomType,
						size: atomSize,
						x: getAtomData(atomSelected).x + getRandomInt (-15, 15),
						y: getAtomData(atomSelected).y + getRandomInt (-15, 15),
						id: generateRandomID (), // Need to make sure is unique
						bonds: 1
					},
		  		n = nodes.push(newAtom);

		  getAtomData(atomSelected).bonds++; // Increment bond count on selected atom
		 	addHydrogens(newAtom, atomDB[atomType].lonePairs - 1); // Adds hydrogens to new atom
		 	removeHydrogen(getAtomData(atomSelected)); // Remove hydrogen from selected atom

		  links.push({
		  	source: newAtom,
		  	target: getAtomData(atomSelected),
		  	bond: 1,
		  	id: generateRandomID()
		  }); // Need to make sure is unique

	  	buildMolecule();
	  }

	  var getBonds = function (atomID) {
	  	var arr = [];
	  	for (var i = links.length - 1; i >= 0; i--) {
	  		if (links[i].source.id === atomID || links[i].target.id === atomID)
	  			arr.push(links[i]);
	  	}
	  	return arr;
	  }

	  /*
		 * Deal with in diff branch
	   */
	  window.deleteAtom = function () {
	  	var oneNonHydrogenBond = function (atom) {
	  		var atomBonds = getBonds(atom.id);
	  		var counter = 0;
	  		for (var i = atomBonds.length - 1; i >= 0; i--) {
	  			if (atomBonds[i].source.symbol !== 'H' && atomBonds[i].target.symbol !== 'H')
	  				counter++;
	  		}
	  		return counter === 1;
	  	};

	  	if (!atomSelected) {
				Messenger().post({
				  message: 'No Atom Selected',
				  type: 'error',
				  showCloseButton: true
				});
				return;
			}
			else if (!oneNonHydrogenBond(getAtomData(atomSelected))) {
				Messenger().post({
				  message: 'Atom Must have only one non-hydrogen bond to be removed',
				  type: 'error',
				  showCloseButton: true
				});
				return;
			}

			removeAtom(getAtomData(atomSelected).id);
			buildMolecule ();
	  };

	  function tick() {
	  	//Update old and new elements
	    link.selectAll("line")
	        .attr("x1", function(d) { return d.source.x; })
	        .attr("y1", function(d) { return d.source.y; })
	        .attr("x2", function(d) { return d.target.x; })
	        .attr("y2", function(d) { return d.target.y; });

	    node.attr("transform", function(d) {return "translate(" + d.x + "," + d.y + ")"; });
	  }

	});
})();<commit_msg>Cleanup and bug fix (thanks to @andreiciubotariu for spotting it)<commit_after>

(function () {
	var width = 1160,
	    height = 500;

	var color = d3.scale.category20();

	var radius = d3.scale.sqrt()
	    .range([0, 6]);

	var atomSelected;
	var atomClicked = function (dataPoint) {
	 	console.log ("atom:", dataPoint);
	 	/*console.log ("this:", this);*/
	 	/*console.log("this.firstElementChild:", this.firstElementChild);*/
	 	/*console.log("this.firstElementChild+d3:", d3.select(this.firstElementChild));*/

	 	if (dataPoint.symbol === "H")
	 		return;

	 	if (atomSelected)
	 		atomSelected.style("filter", "");

	 	atomSelected = d3.select(this)
	 					 			.select("circle")
	 						    .style("filter", "url(#nodeGlow)");
	};

	var bondSelected;
	var bondClicked = function (dataPoint) {
	 	console.log ("bond:", dataPoint);
	 	/*console.log ("this:", this);
	 	console.log("this.firstElementChild:", this.firstElementChild);
	 	console.log("this.firstElementChild+d3:", d3.select(this.firstElementChild));*/

	 	if (bondSelected)
	 		bondSelected.style("filter", "");

	 	bondSelected = d3.select(this)
	 									 .select("line")
	 						    	 .style("filter", "url(#lineGlow)");
	};

	var generateRandomID = function () {
		return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
		  var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
		  return v.toString(16);
		});
	}

	var nodeGlow = glow("nodeGlow").rgb("#0000A0").stdDeviation(7);
	var lineGlow = glow("lineGlow").rgb("#000").stdDeviation(7);

	var svg = d3.select("#moleculeDisplay").append("svg")
	    .attr("width", width)
	    .attr("height", height)
	    .call(nodeGlow)
	    .call(lineGlow);

  var getRandomInt = function (min, max) {
	  return Math.floor(Math.random() * (max - min + 1) + min);
	}

	d3.json("graph.json", function(graph) {
	  var nodesList = graph.nodes;
	  var linksList = graph.links;

	  var force = d3.layout.force()
	    						.nodes(nodesList)
	    						.links(linksList)
	    						.size([width, height])
	    						.charge(-400)
	    						.linkDistance(function(d) { return radius(d.source.size) + radius(d.target.size) + 20; })
	    						.on("tick", tick);

	  var links = force.links(),
	  		nodes = force.nodes(),
	  		link = svg.selectAll(".link"),
	  		node = svg.selectAll(".node");

	  buildMolecule();

	  function buildMolecule () {
	  	// Update link data
	  	link = link.data(links, function (d) {return d.id; });

		  // Create new links
		  link.enter().insert("g", ".node")
		      .attr("class", "link")
		      .each(function(d) {
		      	// Add bond line
		      	d3.select(this)
		      		.append("line")
							.style("stroke-width", function(d) { return (d.bond * 2 - 1) * 2 + "px"; });

						// If double add second line
						d3.select(this)
							.filter(function(d) { return d.bond > 1; }).append("line")
							.attr("class", "separator");

						// Give bond the power to be selected
						d3.select(this)
							.on("click", bondClicked);
		      });

		  // Delete removed links
		  link.exit().remove();

		  // Update node data
	  	node = node.data(nodes, function (d) {return d.id; });

	    // Create new nodes
		  node.enter().append("g")
		      .attr("class", "node")
		      .each(function(d) {
		      	// Add node circle
			      d3.select(this)
			      	.append("circle")
		      		.attr("r", function(d) { return radius(d.size); })
		      		.style("fill", function(d) { return color(d.symbol); });

		        // Add atom symbol
			      d3.select(this)
			      	.append("text")
							.attr("dy", ".35em")
							.attr("text-anchor", "middle")
							.text(function(d) { return d.symbol; });

						// Give atom the power to be selected
						d3.select(this)
							.on("click", atomClicked);

						// Grant atom the power of gravity
						d3.select(this)
							.call(force.drag);
			    });

		  // Delete removed nodes
	    node.exit().remove();

		  force.start();
	  }

	  window.addAtom = function (atomType) {
	  	if (!atomType) {
	  		Messenger().post({
				  message: 'Internal error :(',
				  type: 'error',
				  showCloseButton: true
				});
				return;
	  	}
	  	else if (!atomSelected) {
				Messenger().post({
				  message: 'No Atom Selected',
				  type: 'error',
				  showCloseButton: true
				});
				return;
			}
			else if (!canHaveNewBond(getAtomData(atomSelected))) {
				Messenger().post({
				  message: 'Atom Can\'t Take Anymore Bonds',
				  type: 'error',
				  showCloseButton: true
				});
			}
			else
	  		addNewAtom(atomType, atomDB[atomType].size);
	  };

	 	function canHaveNewBond (atom) {
	 		return atom.bonds < atomDB[atom.symbol].lonePairs;
	 	}

	 	function getAtomData (d3Atom) {
	 		return d3Atom[0][0].parentNode.__data__;
	 	}

	 	function addHydrogens (atom, numHydrogens) {
	 		var newHydrogen = function () {
	 			return {
		 			symbol: 'H',
		 			size: '1',
		 			bonds: 1,
		 			id: generateRandomID (),
		 			x: atom.x + getRandomInt (-15, 15),
		 			y: atom.y + getRandomInt (-15, 15)
		 		};
	 		};
	 		var tempHydrogen;
	 		for (var i = 0; i < numHydrogens; i++) {
	 			tempHydrogen = newHydrogen();
	 			nodes.push(tempHydrogen);
	 			links.push({
	 				source: atom,
	 				target: tempHydrogen,
	 				bond: 1,
	 				id: generateRandomID()
	 			});
	 		}
	 	}

	 	function removeHydrogen (oldAtom) {
	 		var target, source, bondsArr = getBonds(oldAtom.id);
	 		for (var i = bondsArr.length - 1; i >= 0; i--) {
	 			target = bondsArr[i].target, source = bondsArr[i].source;
				if (target.symbol === 'H' || source.symbol === 'H' ) {
					var hydroId = source.symbol === 'H'?
																		source.id:
																		target.id;
					removeAtom(hydroId);
					return;
				}
	 		}
	 	}

	 	function removeAtom (id) {
	 		var atomToRemove = retriveAtom(id);
	 		var bondsArr = getBonds(id);
	 		var atomsArr = [atomToRemove.id];

	 		for (var i = bondsArr.length - 1; i >= 0; i--) {
	 			// Add atom that is a hydrogen
	 			if (bondsArr[i].source.symbol === 'H')
	 				atomsArr.push(bondsArr[i].source.id);
	 			else if (bondsArr[i].target.symbol === 'H')
	 				atomsArr.push(bondsArr[i].target.id);
	 			else {
	 					// Give non-hydrogen bonded atom it's lone pairs back
						var nonHydrogenAtom = bondsArr[i].target.id !== id ?
																									 	'target':
																										'source';

						bondsArr[i][nonHydrogenAtom].bonds -= bondsArr[i].bond;
		 				addHydrogens(bondsArr[i][nonHydrogenAtom], bondsArr[i].bond);
	 			}
	 			// Convert atom obj to id for later processing
	 			bondsArr[i] = bondsArr[i].id;
	 		}

	 		var spliceOut = function (arr, removeArr) {
		 		for (var i = arr.length - 1; i >= 0; i--) {
		 				if (removeArr.indexOf(arr[i].id) !== -1) {
		 					arr.splice(i, 1);
		 				}
		 		}
		 		return arr;
		 	};

	 		// Remove atoms marked
	 		nodes = spliceOut (nodes, atomsArr);

	 		// Remove bonds marked
	 		links = spliceOut (links, bondsArr);

	 	};

	 	var retriveAtom = function  (atomID) {
	 		for (var i = nodes.length - 1; i >= 0; i--) {
	 			if (nodes[i].id === atomID)
	 				return nodes[i];
	 		}
	 		return null;
	 	};

	  function addNewAtom (atomType, atomSize) {
			var newAtom = {
						symbol: atomType,
						size: atomSize,
						x: getAtomData(atomSelected).x + getRandomInt (-15, 15),
						y: getAtomData(atomSelected).y + getRandomInt (-15, 15),
						id: generateRandomID (), // Need to make sure is unique
						bonds: 1
					},
		  		n = nodes.push(newAtom);

		  getAtomData(atomSelected).bonds++; // Increment bond count on selected atom
		 	addHydrogens(newAtom, atomDB[atomType].lonePairs - 1); // Adds hydrogens to new atom
		 	removeHydrogen(getAtomData(atomSelected)); // Remove hydrogen from selected atom

		  links.push({
		  	source: newAtom,
		  	target: getAtomData(atomSelected),
		  	bond: 1,
		  	id: generateRandomID()
		  }); // Need to make sure is unique

	  	buildMolecule();
	  }

	  var getBonds = function (atomID) {
	  	var arr = [];
	  	for (var i = links.length - 1; i >= 0; i--) {
	  		if (links[i].source.id === atomID || links[i].target.id === atomID)
	  			arr.push(links[i]);
	  	}
	  	return arr;
	  }

	  /*
		 * Deal with in diff branch
	   */
	  window.deleteAtom = function () {
	  	var oneNonHydrogenBond = function (atom) {
	  		var atomBonds = getBonds(atom.id);
	  		var counter = 0;
	  		for (var i = atomBonds.length - 1; i >= 0; i--) {
	  			if (atomBonds[i].source.symbol !== 'H' && atomBonds[i].target.symbol !== 'H')
	  				counter++;
	  		}
	  		return counter === 1;
	  	};

	  	if (!atomSelected) {
				Messenger().post({
				  message: 'No Atom Selected',
				  type: 'error',
				  showCloseButton: true
				});
				return;
			}
			else if (!oneNonHydrogenBond(getAtomData(atomSelected))) {
				Messenger().post({
				  message: 'Atom Must have only one non-hydrogen bond to be removed',
				  type: 'error',
				  showCloseButton: true
				});
				return;
			}

			removeAtom(getAtomData(atomSelected).id);
			atomSelected = null;
			buildMolecule ();
	  };

	  function tick() {
	  	//Update old and new elements
	    link.selectAll("line")
	        .attr("x1", function(d) { return d.source.x; })
	        .attr("y1", function(d) { return d.source.y; })
	        .attr("x2", function(d) { return d.target.x; })
	        .attr("y2", function(d) { return d.target.y; });

	    node.attr("transform", function(d) {return "translate(" + d.x + "," + d.y + ")"; });
	  }

	});
})();<|endoftext|>


<commit_before>var socket = io();
var partner = false;

$(function() {
  $('.selectpicker').selectpicker();

  /**
   * Handles the submission of the user's yiffing preferences
   * and informs the server to find a partner.
   */
  $('#userSettings').submit(function(e) {
    e.preventDefault();

    var gender        = $('#userGender').val();
    var species       = $('#userSpecies').val();
    var kinks         = $('#userKinks').val();
    var matchGender   = $('#partnerGender').val();
    var matchSpecies  = $('#partnerSpecies').val();

    if (gender === '') {
      alert('Please select your gender.');
      return false;
    }
    if (species === '') {
      alert('Please select your species.');
      return false;
    }
    if (!matchGender) {
      alert('Please select the gender your seeking.');
      return false;
    }
    if (!matchSpecies) {
      alert('Please select the species your seeking.');
      return false;
    }
    if (!kinks) {
      alert('Please select the kinks you are interested in.');
      return false;
    }

    $("#welcome").hide();
    $("#chat").show();

    // Clear current message history
    $("#messages").empty();

    // Tell the server to find the user a yiffing partner
    socket.emit('find partner', [
      { 'gender': gender },
      { 'species': species },
      { 'kinks': kinks },
      { 'matchGender': matchGender },
      { 'matchSpecies': matchSpecies }
    ]);
  });

  /**
   * Handles the submission of a message using the chatbox
   * and informs the server to send a message to the partner.
   */
  $('#messageBox').submit(function(e) {
    e.preventDefault();

    var message = $('#message').val();

    if (message === '') {
      alert('Please enter a message.');
      return false;
    }

    if (message.length > 2000) {
      alert('Are you trying to send a novel? Calm down and shorten your message.');
      return false;
    }

    if (partner === false) {
      $('#message').val('');

      alert('You are not connected to a partner yet.');
      return false;
    }

    socket.emit('send message', message);

    newMessage(message, 'user');

    $('#message').val('');
  });

  /**
   * Updates the users online count.
   * @param  Integer count The amount of users online.
   */
  socket.on('update user count', function(count) {
    $('#userCount').text(count);
  });

  /**
   * Informs the user that they've been connected with a yiffing partner.
   * @param  Object data The partner's yiffing preferences.
   */
  socket.on('partner connected', function(data) {
    newMessage('You have been connected with a yiffing partner!');
    newMessage("Your partner is a "+data.gender+" "+data.species+" interested in: "+data.kinks+".");
    partner = true;
  });

  /**
   * Informs the user that their yiffing partner has disconnected.
   */
  socket.on('partner disconnected', function() {
    newMessage('Your partner has disconnected!');
    partner = false;
  });

  /**
   * Informs the user that no yiffing partner has been found that matches their preferences.
   */
  socket.on('no match', function() {
    newMessage('Sorry, we are unable to match you with a partner. Please either continue to wait, or modify your yiffing preferences.');
  });

  /**
   * Handles the recieving of a message for the user.
   * @param  String data The message contents.
   * @param  String type The type of message.
   */
  socket.on('recieve message', function(data) {
    newMessage(data.message, 'partner');

    $.titleAlert("New Message!", {
        requireBlur:true,
        stopOnFocus:true,
        duration:10000,
        interval:500
    });
  });
});

/**
 * Auto scroll chat window to bottom.
 */
function autoScroll() {
  $("#messages").scrollTop($("#messages")[0].scrollHeight);
}

/**
 * Handles the creation of a new message in the chat window.
 * @param  String message The message to be displayed.
 * @param  String type    The type of message to display.
 */
function newMessage(message, type) {
  if(type === 'user')
    $('#messages').append($('
').text(message));
  else if (type === 'partner')
    $('#messages').append($('
').text(message));
  else
    $('#messages').append($('
').text(message));

  autoScroll();
}<commit_msg>Added auto detect scheme to io.connect() on client.<commit_after>var socket = io.connect('//localhost');
var partner = false;

$(function() {
  $('.selectpicker').selectpicker();

  /**
   * Handles the submission of the user's yiffing preferences
   * and informs the server to find a partner.
   */
  $('#userSettings').submit(function(e) {
    e.preventDefault();

    var gender        = $('#userGender').val();
    var species       = $('#userSpecies').val();
    var kinks         = $('#userKinks').val();
    var matchGender   = $('#partnerGender').val();
    var matchSpecies  = $('#partnerSpecies').val();

    if (gender === '') {
      alert('Please select your gender.');
      return false;
    }
    if (species === '') {
      alert('Please select your species.');
      return false;
    }
    if (!matchGender) {
      alert('Please select the gender your seeking.');
      return false;
    }
    if (!matchSpecies) {
      alert('Please select the species your seeking.');
      return false;
    }
    if (!kinks) {
      alert('Please select the kinks you are interested in.');
      return false;
    }

    $("#welcome").hide();
    $("#chat").show();

    // Clear current message history
    $("#messages").empty();

    // Tell the server to find the user a yiffing partner
    socket.emit('find partner', [
      { 'gender': gender },
      { 'species': species },
      { 'kinks': kinks },
      { 'matchGender': matchGender },
      { 'matchSpecies': matchSpecies }
    ]);
  });

  /**
   * Handles the submission of a message using the chatbox
   * and informs the server to send a message to the partner.
   */
  $('#messageBox').submit(function(e) {
    e.preventDefault();

    var message = $('#message').val();

    if (message === '') {
      alert('Please enter a message.');
      return false;
    }

    if (message.length > 2000) {
      alert('Are you trying to send a novel? Calm down and shorten your message.');
      return false;
    }

    if (partner === false) {
      $('#message').val('');

      alert('You are not connected to a partner yet.');
      return false;
    }

    socket.emit('send message', message);

    newMessage(message, 'user');

    $('#message').val('');
  });

  /**
   * Updates the users online count.
   * @param  Integer count The amount of users online.
   */
  socket.on('update user count', function(count) {
    $('#userCount').text(count);
  });

  /**
   * Informs the user that they've been connected with a yiffing partner.
   * @param  Object data The partner's yiffing preferences.
   */
  socket.on('partner connected', function(data) {
    newMessage('You have been connected with a yiffing partner!');
    newMessage("Your partner is a "+data.gender+" "+data.species+" interested in: "+data.kinks+".");
    partner = true;
  });

  /**
   * Informs the user that their yiffing partner has disconnected.
   */
  socket.on('partner disconnected', function() {
    newMessage('Your partner has disconnected!');
    partner = false;
  });

  /**
   * Informs the user that no yiffing partner has been found that matches their preferences.
   */
  socket.on('no match', function() {
    newMessage('Sorry, we are unable to match you with a partner. Please either continue to wait, or modify your yiffing preferences.');
  });

  /**
   * Handles the recieving of a message for the user.
   * @param  String data The message contents.
   * @param  String type The type of message.
   */
  socket.on('recieve message', function(data) {
    newMessage(data.message, 'partner');

    $.titleAlert("New Message!", {
        requireBlur:true,
        stopOnFocus:true,
        duration:10000,
        interval:500
    });
  });
});

/**
 * Auto scroll chat window to bottom.
 */
function autoScroll() {
  $("#messages").scrollTop($("#messages")[0].scrollHeight);
}

/**
 * Handles the creation of a new message in the chat window.
 * @param  String message The message to be displayed.
 * @param  String type    The type of message to display.
 */
function newMessage(message, type) {
  if(type === 'user')
    $('#messages').append($('
').text(message));
  else if (type === 'partner')
    $('#messages').append($('
').text(message));
  else
    $('#messages').append($('
').text(message));

  autoScroll();
}<|endoftext|>


<commit_before>	$(document).ready(function(){
	    //navbar
	    $(window).scroll(function(){
	    	if($(window).scrollTop() > $('#navbar').innerHeight()){
	    		$('#navbar').addClass('floatNav') ;
	    	}else{
	    		$('#navbar').removeClass('floatNav') ;
	    	}
	    }) ;
	    $('body').scrollspy({
	    	target : '#navbar'
	    }) ;

		// to ease when click '#' url
		$(function() {
            $('a[href*=#]:not([href=#])').click(function() {
                if (location.pathname.replace(/^\//,'') == this.pathname.replace(/^\//,'') && location.hostname == this.hostname) {

                    var target = $(this.hash);
                    target = target.length ? target : $('[name=' + this.hash.slice(1) +']');
                    if (target.length) {
                        $('html,body').animate({
                            scrollTop: target.offset().top
                        }, 1000);
                        return false;
                    }
                }
            });
        });

		//owl carousel
		var owl = $('.carousel-1');
		owl.owlCarousel({
		    responsive:{
		        0:{
		            items:1
		        },
		        600:{
		            items:3
		        },
		        1000:{
		            items:3
		        }
		    },
		    loop:true,
		    margin:10,
		    autoplay:true,
		    autoplayTimeout:5000,
		    autoplayHoverPause:true
		});

		$('.carousel-1').owlCarousel({
		    loop:true,
		    margin:10,
		    nav:true,
		    responsive:{
		        0:{
		            items:1
		        },
		        600:{
		            items:1
		        },
		        1000:{
		            items:3
		        }
		    }
		})

		$('.carousel-2').owlCarousel({
		    loop:true,
		    margin:10,
		    nav:true,
		    responsive:{
		        0:{
		            items:1
		        },
		        600:{
		            items:2
		        },
		        1000:{
		            items:4
		        }
		    }
		})

		var owl2 = $('.carousel-3');
		owl2.owlCarousel({
		    items:1,
		    loop:true,
		    margin:10,
		    autoplay:true,
		    autoplayTimeout:5000,
		    autoplayHoverPause:true
		});

		//count
		$(window).scroll(function(){
	    	if($(window).scrollTop() > $('#navbar').innerHeight()){
	    		$('#navbar').addClass('floatNav') ;
	    	}else{
	    		$('#navbar').removeClass('floatNav') ;
	    	}
	    }) ;
		$('.count').each(function () {
		    $(this).prop('Counter',0).animate({
		        Counter: $(this).text()
		    }, {
		        duration: 2000,
		        easing: 'swing',
		        step: function (now) {
		            $(this).text(Math.ceil(now));
		        }
		    });
		});

		//lightcase
		$('a[data-rel^=lightcase]').lightcase();

	});<commit_msg>Refine main.js<commit_after>	$(document).ready(function(){
	    //navbar
	    $(window).scroll(function(){
	    	if($(window).scrollTop() > $('#navbar').innerHeight()){
	    		$('#navbar').addClass('floatNav') ;
	    	}else{
	    		$('#navbar').removeClass('floatNav') ;
	    	}
	    }) ;
	    $('body').scrollspy({
	    	target : '#navbar'
	    }) ;

		// to ease when click '#' url
		$(document).on('click', 'a', function(event){
		    event.preventDefault();

		    $('html, body').animate({
		        scrollTop: $( $.attr(this, 'href') ).offset().top
		    }, 1000);
		});

		//owl carousel
		var owl = $('.carousel-1');
		owl.owlCarousel({
		    responsive:{
		        0:{
		            items:1
		        },
		        600:{
		            items:3
		        },
		        1000:{
		            items:3
		        }
		    },
		    loop:true,
		    margin:10,
		    autoplay:true,
		    autoplayTimeout:5000,
		    autoplayHoverPause:true
		});

		$('.carousel-1').owlCarousel({
		    loop:true,
		    margin:10,
		    nav:true,
		    responsive:{
		        0:{
		            items:1
		        },
		        600:{
		            items:1
		        },
		        1000:{
		            items:3
		        }
		    }
		})

		$('.carousel-2').owlCarousel({
		    loop:true,
		    margin:10,
		    nav:true,
		    responsive:{
		        0:{
		            items:1
		        },
		        600:{
		            items:2
		        },
		        1000:{
		            items:4
		        }
		    }
		})

		var owl2 = $('.carousel-3');
		owl2.owlCarousel({
		    items:1,
		    loop:true,
		    margin:10,
		    autoplay:true,
		    autoplayTimeout:5000,
		    autoplayHoverPause:true
		});

		//count
		$(window).scroll(function(){
	    	if($(window).scrollTop() > $('#navbar').innerHeight()){
	    		$('#navbar').addClass('floatNav') ;
	    	}else{
	    		$('#navbar').removeClass('floatNav') ;
	    	}
	    }) ;
		$('.count').each(function () {
		    $(this).prop('Counter',0).animate({
		        Counter: $(this).text()
		    }, {
		        duration: 2000,
		        easing: 'swing',
		        step: function (now) {
		            $(this).text(Math.ceil(now));
		        }
		    });
		});

		//lightcase
		$('a[data-rel^=lightcase]').lightcase();

	});<|endoftext|>


<commit_before>/*
 * The MIT License
 *
 * Copyright (c) 2016 - 2017, Eduardo Fernández
 *
 */

// Ajax periodic calls (ajax function is in views/main.php)
var ajaxInterval = setInterval(ajaxCall, 10000); // Time: 10s

// change a var value for AJAX call
function tabNum(n) {
    clearInterval(ajaxInterval); // stop making calls
    window.tab = n;
    ajaxCall();
    ajaxInterval = setInterval(ajaxCall, 10000); // Time: 10s
}

// change .active clas from tabs
var selector, elems, makeActive;

selector = '#tabs li';

elems = document.querySelectorAll(selector);

makeActive = function() {
    for (var i = 0; i < elems.length; i++)
        elems[i].classList.remove('active');

    this.classList.add('active');
};

for (var i = 0; i < elems.length; i++)
    elems[i].addEventListener('mousedown', makeActive);

// generate a random color for the "add task" modal
function colorGen() {
    var text = "#";
    var charset = "ABCDEF0123456789";

    for (var i = 0; i < 6; i++)
        text += charset.charAt(Math.floor(Math.random() * charset.length));

    document.getElementById("inColor").setAttribute("value", text);
}

// clear textarea from the form when closed
function eraseText() {
    document.getElementById("addtasktextarea").value = "";
}

// copy tab id and paste in the form (to make the first form tab visible always be the selected)
function catch_tab(tab_id) {
    document.getElementById("tabselect").value = tab_id;
}

// send add task form
$('#addtaskform').submit(function(ev) {
    $.ajax({
        type: $('#addtaskform').attr('method'),
        url: $('#addtaskform').attr('action'),
        data: $('#addtaskform').serialize(),
        success: function(data) {
            alert('ok');
        }
    });

    ajaxCall();
    $('#addTask').modal('hide');
    eraseText();
});

/////// OLD BUT GOLD, no lo estoy usando, puede servir ///////

// script que oculta los elementos de id mensaje a los 3 segundos
setTimeout("document.getElementById(\"mensaje\").style.display = \"none\"", 3000);

// este script solo te deja meter numeros en un input donde metas onkeypress="return valida(event)"
function writenum(e) {
    tecla = (document.all) ? e.keyCode : e.which;
    //Tecla de retroceso para borrar, siempre la permite
    if (tecla == 8) {
        return true;
    }
    // Patron de entrada, en este caso solo acepta numeros
    patron = /[]/;
    tecla_final = String.fromCharCode(tecla);
    return patron.test(tecla_final);
}
// script para mostrar menu de selecion de fecha y hora
$(function() {
    $('#datetimepicker1').datetimepicker({
        format: 'DD/MM/YYYY H:mm',
        locale: 'es'
    });
});
$(function() {
    $('#datetimepicker2').datetimepicker({
        format: 'DD/MM/YYYY',
        locale: 'es'
    });
});

// script contador (hacia atrás) de minutos y segundos (ids JScuentaMin y JScuentaSeg) AUN NO FUNCIONA CORRECTAMENTE
/* $tiempom = document.getElementById("JScuentaMin").innerHTML;
 * $tiempos = document.getElementById("JScuentaSeg").innerHTML;
 *
 *  function cuenta() {
 *
 *  	document.getElementById("JScuentaSeg").innerHTML = $tiempos;
 *
 *  	if(($tiempos == 0) && ($tiempom > 0)) {
 *  		$tiempom--;
 *  		document.getElementById("JScuentaMin").innerHTML = $tiempom;
 *  		$tiempos = 60;
 *  	}
 *  	if (($tiempos == -1) && ($tiempom == 0)) {
 *  		clearInterval($cuentaSeg);
 *  		document.getElementById("JScuentaMin").innerHTML = "";
 *  		document.getElementById("JScuentaSeg").innerHTML = "Tiempo Acabado";
 *  	}
 *  	$tiempos--;
 *  }
 *
 * $CuentaSeg = setInterval("cuenta()", 1000);
 */

// esta funcion copia de un elemento a otro el valor para abrir el editor de la tarea
function coge_id(element) {
    // coge el id de la tarea en el click
    var tarea_id = element.id;
    // con el id pilla la tarea con ese name
    var tarea = document.getElementsByName(tarea_id)[0].innerHTML;
    // todo esto es na mas que para el color
    var color_element = document.getElementById(tarea_id).getElementsByClassName("col-xs-2")[0];
    style = window.getComputedStyle(color_element),
        tarea_color = style.getPropertyValue('background-color');
    // coge el id de la categoria y la recorta para que sea solo el numero (id de la categoria)
    var categoria = document.getElementsByName(tarea_id)[0].parentNode.parentNode.parentNode.parentNode.id;
    var categoria_id = categoria.substr(1);
    // y la imprime en el formilario que se abre
    document.getElementById("JScoge_id").setAttribute("value", tarea_id);
    document.getElementById("JScoge_tarea").innerHTML = tarea;
    document.getElementById("JScoge_color").style.backgroundColor = tarea_color;
    document.getElementById("JScoge_cat").children[0].children[categoria_id - 1].setAttribute("selected", "selected");
}<commit_msg>fixed preventDefault<commit_after>/*
 * The MIT License
 *
 * Copyright (c) 2016 - 2017, Eduardo Fernández
 *
 */

// Ajax periodic calls (ajax function is in views/main.php)
var ajaxInterval = setInterval(ajaxCall, 10000); // Time: 10s

// change a var value for AJAX call
function tabNum(n) {
    clearInterval(ajaxInterval); // stop making calls
    window.tab = n;
    ajaxCall();
    ajaxInterval = setInterval(ajaxCall, 10000); // Time: 10s
}

// change .active clas from tabs
var selector, elems, makeActive;

selector = '#tabs li';

elems = document.querySelectorAll(selector);

makeActive = function() {
    for (var i = 0; i < elems.length; i++)
        elems[i].classList.remove('active');

    this.classList.add('active');
};

for (var i = 0; i < elems.length; i++)
    elems[i].addEventListener('mousedown', makeActive);

// generate a random color for the "add task" modal
function colorGen() {
    var text = "#";
    var charset = "ABCDEF0123456789";

    for (var i = 0; i < 6; i++)
        text += charset.charAt(Math.floor(Math.random() * charset.length));

    document.getElementById("inColor").setAttribute("value", text);
}

// clear textarea from the form when closed
function eraseText() {
    document.getElementById("addtasktextarea").value = "";
}

// copy tab id and paste in the form (to make the first form tab visible always be the selected)
function catch_tab(tab_id) {
    document.getElementById("tabselect").value = tab_id;
}

// send add task form
$('#addtaskform').submit(function(ev) {
    $.ajax({
        type: $('#addtaskform').attr('method'),
        url: $('#addtaskform').attr('action'),
        data: $('#addtaskform').serialize(),
        success: function(data) {
            alert('ok');
        }
    });

    ev.preventDefault();
    ajaxCall();
    $('#addTask').modal('hide');
    eraseText();
});

/////// OLD BUT GOLD, no lo estoy usando, puede servir ///////

// script que oculta los elementos de id mensaje a los 3 segundos
setTimeout("document.getElementById(\"mensaje\").style.display = \"none\"", 3000);

// este script solo te deja meter numeros en un input donde metas onkeypress="return valida(event)"
function writenum(e) {
    tecla = (document.all) ? e.keyCode : e.which;
    //Tecla de retroceso para borrar, siempre la permite
    if (tecla == 8) {
        return true;
    }
    // Patron de entrada, en este caso solo acepta numeros
    patron = /[]/;
    tecla_final = String.fromCharCode(tecla);
    return patron.test(tecla_final);
}
// script para mostrar menu de selecion de fecha y hora
$(function() {
    $('#datetimepicker1').datetimepicker({
        format: 'DD/MM/YYYY H:mm',
        locale: 'es'
    });
});
$(function() {
    $('#datetimepicker2').datetimepicker({
        format: 'DD/MM/YYYY',
        locale: 'es'
    });
});

// script contador (hacia atrás) de minutos y segundos (ids JScuentaMin y JScuentaSeg) AUN NO FUNCIONA CORRECTAMENTE
/* $tiempom = document.getElementById("JScuentaMin").innerHTML;
 * $tiempos = document.getElementById("JScuentaSeg").innerHTML;
 *
 *  function cuenta() {
 *
 *  	document.getElementById("JScuentaSeg").innerHTML = $tiempos;
 *
 *  	if(($tiempos == 0) && ($tiempom > 0)) {
 *  		$tiempom--;
 *  		document.getElementById("JScuentaMin").innerHTML = $tiempom;
 *  		$tiempos = 60;
 *  	}
 *  	if (($tiempos == -1) && ($tiempom == 0)) {
 *  		clearInterval($cuentaSeg);
 *  		document.getElementById("JScuentaMin").innerHTML = "";
 *  		document.getElementById("JScuentaSeg").innerHTML = "Tiempo Acabado";
 *  	}
 *  	$tiempos--;
 *  }
 *
 * $CuentaSeg = setInterval("cuenta()", 1000);
 */

// esta funcion copia de un elemento a otro el valor para abrir el editor de la tarea
function coge_id(element) {
    // coge el id de la tarea en el click
    var tarea_id = element.id;
    // con el id pilla la tarea con ese name
    var tarea = document.getElementsByName(tarea_id)[0].innerHTML;
    // todo esto es na mas que para el color
    var color_element = document.getElementById(tarea_id).getElementsByClassName("col-xs-2")[0];
    style = window.getComputedStyle(color_element),
        tarea_color = style.getPropertyValue('background-color');
    // coge el id de la categoria y la recorta para que sea solo el numero (id de la categoria)
    var categoria = document.getElementsByName(tarea_id)[0].parentNode.parentNode.parentNode.parentNode.id;
    var categoria_id = categoria.substr(1);
    // y la imprime en el formilario que se abre
    document.getElementById("JScoge_id").setAttribute("value", tarea_id);
    document.getElementById("JScoge_tarea").innerHTML = tarea;
    document.getElementById("JScoge_color").style.backgroundColor = tarea_color;
    document.getElementById("JScoge_cat").children[0].children[categoria_id - 1].setAttribute("selected", "selected");
}<|endoftext|>


<commit_before>$(document).ready(function () {

    var parse_str = function (str) {
        return str.replace(new RegExp('\r?\n', 'g'), '
');
    };

    var shuffle = function (array) {
        return array.sort(function () {
            return 0.5 - Math.random();
        });
    };

    var create_options = function (options_data) {
        var options = [];

        for (var index = 0; index < options_data.length; index++) {
            var option_data = options_data[index];
            var option = new QuestionOption(option_data.title, option_data.correct, false);
            options.push(option);
        }

        return options;
    };

    var create_questions = function (questions_data) {
        var questions = [];

        for (var index = 0; index < questions_data.length; index++) {
            var question_data = questions_data[index];
            var options = create_options(question_data['options']);
            var question = new Question(question_data['question'], options, question_data['explain']);
            questions.push(question);
        }

        return questions;
    };

    var create_random_questions = function (questions_data) {
        var questions = [];

        for (var index = 0; index < questions_data.length; index++) {
            var question_data = questions_data[index];
            var options = shuffle(create_options(question_data['options']));
            var question = new Question(question_data['question'], options, question_data['explain']);
            questions.push(question);
        }

        return shuffle(questions);
    };

    var update_questions_count = function (quiz) {
        $('.total-questions').text(quiz.getQuestionsCount());
    };

    var update_current_question_number = function (quiz) {
        $('.question-index').text(quiz.getCurrentQuestionNumber());
    };

    var update_question_html = function (quiz) {
        var question = quiz.getCurrentQuestion();

        $('.question-title').html(parse_str(question.title));
        $('.question-explain-text').html(parse_str(question.explain));
    };

    var disable_options = function (question) {
        var options_list = $('.options-list');
        var options = question.options;
        for (var index = 0; index < options.length; index++) {
            options_list.children().eq(index).find('.btn').addClass('disabled');
        }
    };

    var highlight_options = function (question) {
        var options_list = $('.options-list');
        var options = question.options;
        for (var index = 0; index < options.length; index++) {
            var option = options[index];
            if (option.selected && (false === option.correct)) {
                options_list.children().eq(index).find('.btn').addClass('btn-danger');
            }

            if (option.correct) {
                options_list.children().eq(index).find('.btn').addClass('btn-success');
            }
        }
    };

    var hide_show_explain = function (quiz) {
        var question = quiz.getCurrentQuestion();
        if (quiz.isAnswered(question) && (false === quiz.isCorrect(question))) {
            $('.question-explain').show();
        } else {
            $('.question-explain').hide();
        }
    };

    var show_random_questions = function () {
        return $("#randomize").is(":checked");
    };

    var get_first_not_answered_index = function (quiz) {
        var questions = quiz.getQuestions();

        for (var index = 0; index < questions.length; index++) {
            var question = questions[index];

            if (false === quiz.isAnswered(question)) {
                return index;
            }
        }

        return null;
    };

    var answered_correctly = function (quiz) {
        var questions = quiz.getQuestions();
        var correct = 0;
        for (var index = 0; index < questions.length; index++) {
            var question = questions[index];

            if (quiz.isCorrect(question)) {
                correct++;
            }
        }

        return correct;
    };

    var score_shown = false;

    var show_score = function (quiz) {
        score_shown = true;

        var correct = answered_correctly(quiz);
        var total = quiz.getQuestionsCount();
        var percent = (correct * 100 / total);
        var percent_fixed = Math.round(percent * 100) / 100;

        $('.results .percent').text(percent_fixed);
        $('.results .correct').text(correct);
        $('.results .progress-bar').css('width', percent_fixed + '%');

        $('.quiz').hide();
        $('.results').show();
    };

    var next_action = function (quiz) {
        var index = get_first_not_answered_index(quiz);

        if (quiz.getCurrentQuestionNumber() === quiz.getQuestionsCount()) {
            if (null !== index) {
                quiz.goTo(index);
            } else {
                show_score(quiz);
            }
        } else {
            if ((null === index) && (false === score_shown)) {
                show_score(quiz);
            } else {
                quiz.next();
            }
        }
    };

    var click_answer_disabled = false;

    var click_answer = function (quiz, index, question) {
        return function () {
            if (!click_answer_disabled) {
                click_answer_disabled = true;
                if (0 === $(this).find('.disabled').length) {
                    var answers = [index];

                    quiz.setAnswers(quiz.getCurrentQuestion(), answers);

                    highlight_options(question);
                    disable_options(question);

                    if (quiz.isCorrect(question)) {
                        setTimeout(function () {
                            click_answer_disabled = false;
                            next_action(quiz);
                        }, 500);
                    } else {
                        click_answer_disabled = false;
                    }
                } else {
                    click_answer_disabled = false;
                    next_action(quiz);
                }
            }
        }
    };

    var update_question_options_html = function (quiz) {
        var question = quiz.getCurrentQuestion();
        var options = question.options;

        var options_list = $('.options-list');
        var option_template = options_list.data('option');

        for (var index = 0; index < options.length; index++) {
            var option = options[index];
            var $option = $(option_template.replace('{{ title }}', option.title));

            if (quiz.isAnswered(quiz.getCurrentQuestion())) {
                $option.find('.btn').addClass('disabled');
            }

            $option.click(click_answer(quiz, index, question));

            options_list.append($option);
        }

        if (quiz.isAnswered(question)) {
            highlight_options(question);
        }
    };

    var clear_options = function () {
        $('.options-list').html('');
    };

    var load_quiz = function (data_path) {
        $.getJSON(data_path, function (data) {

            if (show_random_questions() === true) {
                var questions = create_random_questions(data['questions']);
            }
            else {
                var questions = create_questions(data['questions']);
            }

            var quiz = new Quiz();

            quiz.addPreLoadCallback(clear_options);

            quiz.addPostLoadCallback(update_questions_count);
            quiz.addPostLoadCallback(update_current_question_number);

            quiz.addPreChangeQuestionCallback(clear_options);

            quiz.addPostSetAnswersCallback(hide_show_explain);

            quiz.addPostChangeQuestionCallback(update_current_question_number);
            quiz.addPostChangeQuestionCallback(update_question_html);
            quiz.addPostChangeQuestionCallback(update_question_options_html);
            quiz.addPostChangeQuestionCallback(hide_show_explain);

            quiz.load(questions);

            $('.quiz .next').click(function () {
                next_action(quiz)
            });
            $('.quiz .previous').click(quiz.prev);
        });
    };

    $('.results').hide();
    $('.quiz').hide();

    $('.start .btn').click(function () {
        $('.start').hide();
        var data_file = $("select[name='data']:first").val();
        load_quiz("data/" + data_file);
        $(".quiz").show();
    });

    $('.results .previous').click(function () {
        $('.results').hide();
        $('.quiz').show();
    });
});
<commit_msg>cs fixes<commit_after>$(document).ready(function () {

    /**
     * Convert some special chars to html.
     *
     * @param {string} str
     * @returns {string}
     */
    var parse_str = function (str) {
        return str.replace(new RegExp('\r?\n', 'g'), '
');
    };

    /**
     * Randomize array order.
     *
     * @param {Array.<>} array
     * @returns {Array.<>}
     */
    var shuffle = function (array) {
        return array.sort(function () {
            return 0.5 - Math.random();
        });
    };

    /**
     * Create options objects from json object.
     *
     * @param {Object} options_data
     * @returns {Array.<QuestionOption>}
     */
    var create_options = function (options_data) {
        var options = [];

        for (var index = 0; index < options_data.length; index++) {
            var option_data = options_data[index];
            var option = new QuestionOption(option_data.title, option_data.correct, false);
            options.push(option);
        }

        return options;
    };

    /**
     * Create question objects from json object.
     *
     * @param {Object} questions_data
     * @returns {Array.<Question>}
     */
    var create_questions = function (questions_data) {
        var questions = [];

        for (var index = 0; index < questions_data.length; index++) {
            var question_data = questions_data[index];
            var options = create_options(question_data['options']);
            var question = new Question(question_data['question'], options, question_data['explain']);
            questions.push(question);
        }

        return questions;
    };

    /**
     * Create randomized order question objects from json object.
     *
     * @param {Object} questions_data
     * @returns {Array.<Question>}
     */
    var create_random_questions = function (questions_data) {
        var questions = [];

        for (var index = 0; index < questions_data.length; index++) {
            var question_data = questions_data[index];
            var options = shuffle(create_options(question_data['options']));
            var question = new Question(question_data['question'], options, question_data['explain']);
            questions.push(question);
        }

        return shuffle(questions);
    };

    /**
     * Update question count in GUI.
     *
     * @param {Quiz} quiz
     */
    var update_questions_count = function (quiz) {
        $('.total-questions').text(quiz.getQuestionsCount());
    };

    /**
     * Update current question number in GUI.
     *
     * @param {Quiz} quiz
     */
    var update_current_question_number = function (quiz) {
        $('.question-index').text(quiz.getCurrentQuestionNumber());
    };

    /**
     * Show current question in GUI.
     *
     * @param {Quiz} quiz
     */
    var update_question_html = function (quiz) {
        var question = quiz.getCurrentQuestion();

        $('.question-title').html(parse_str(question.title));
        $('.question-explain-text').html(parse_str(question.explain));
    };

    /**
     * Disable question option on GUI.
     *
     * @param {Question} question
     */
    var disable_options = function (question) {
        var options_list = $('.options-list');
        var options = question.options;
        for (var index = 0; index < options.length; index++) {
            options_list.children().eq(index).find('.btn').addClass('disabled');
        }
    };

    /**
     * Highlight correct/incorrect question option in GUI.
     *
     * @param {Question} question
     */
    var highlight_options = function (question) {
        var options_list = $('.options-list');
        var options = question.options;
        for (var index = 0; index < options.length; index++) {
            var option = options[index];
            if (option.selected && (false === option.correct)) {
                options_list.children().eq(index).find('.btn').addClass('btn-danger');
            }

            if (option.correct) {
                options_list.children().eq(index).find('.btn').addClass('btn-success');
            }
        }
    };

    /**
     * Show/hide question explain in GUI.
     *
     * @param {Quiz} quiz
     */
    var hide_show_explain = function (quiz) {
        var question = quiz.getCurrentQuestion();
        if (quiz.isAnswered(question) && (false === quiz.isCorrect(question))) {
            $('.question-explain').show();
        } else {
            $('.question-explain').hide();
        }
    };

    /**
     * Check if need to randomize question.
     *
     * @returns {boolean}
     */
    var show_random_questions = function () {
        return $("#randomize").is(":checked");
    };

    /**
     * Get first not answered question index in quiz.
     *
     * @param {Quiz} quiz
     * @returns {int|null}
     */
    var get_first_not_answered_index = function (quiz) {
        var questions = quiz.getQuestions();

        for (var index = 0; index < questions.length; index++) {
            var question = questions[index];

            if (false === quiz.isAnswered(question)) {
                return index;
            }
        }

        return null;
    };

    /**
     * Get correctly answered questions count in quiz.
     *
     * @param {Quiz} quiz
     * @returns {int}
     */
    var answered_correctly = function (quiz) {
        var questions = quiz.getQuestions();
        var correct = 0;
        for (var index = 0; index < questions.length; index++) {
            var question = questions[index];

            if (quiz.isCorrect(question)) {
                correct++;
            }
        }

        return correct;
    };

    /**
     * Is score already shown.
     *
     * @type {boolean}
     */
    var score_shown = false;

    /**
     * Show score in GUI.
     *
     * @param {Quiz} quiz
     */
    var show_score = function (quiz) {
        score_shown = true;

        var correct = answered_correctly(quiz);
        var total = quiz.getQuestionsCount();
        var percent = (correct * 100 / total);
        var percent_fixed = Math.round(percent * 100) / 100;

        $('.results .percent').text(percent_fixed);
        $('.results .correct').text(correct);
        $('.results .progress-bar').css('width', percent_fixed + '%');

        $('.quiz').hide();
        $('.results').show();
    };

    /**
     * Move forward in quiz.
     *
     * @param {Quiz} quiz
     */
    var next_action = function (quiz) {
        var index = get_first_not_answered_index(quiz);

        if (quiz.getCurrentQuestionNumber() === quiz.getQuestionsCount()) {
            if (null !== index) {
                quiz.goTo(index);
            } else {
                show_score(quiz);
            }
        } else {
            if ((null === index) && (false === score_shown)) {
                show_score(quiz);
            } else {
                quiz.next();
            }
        }
    };

    /**
     * Is question options click temporary disabled.
     *
     * @type {boolean}
     */
    var click_answer_disabled = false;

    /**
     * Select answer.
     *
     * @param {Quiz} quiz
     * @param {int} index - selected answer index
     * @param {Question} question
     * @returns {Function}
     */
    var click_answer = function (quiz, index, question) {
        return function () {
            if (!click_answer_disabled) {
                click_answer_disabled = true;
                if (0 === $(this).find('.disabled').length) {
                    var answers = [index];

                    quiz.setAnswers(quiz.getCurrentQuestion(), answers);

                    highlight_options(question);
                    disable_options(question);

                    if (quiz.isCorrect(question)) {
                        setTimeout(function () {
                            click_answer_disabled = false;
                            next_action(quiz);
                        }, 500);
                    } else {
                        click_answer_disabled = false;
                    }
                } else {
                    click_answer_disabled = false;
                    next_action(quiz);
                }
            }
        }
    };

    /**
     * Show current question Options in GUI.
     *
     * @param {Quiz} quiz
     */
    var update_question_options_html = function (quiz) {
        var question = quiz.getCurrentQuestion();
        var options = question.options;

        var options_list = $('.options-list');
        var option_template = options_list.data('option');

        for (var index = 0; index < options.length; index++) {
            var option = options[index];
            var $option = $(option_template.replace('{{ title }}', option.title));

            if (quiz.isAnswered(quiz.getCurrentQuestion())) {
                $option.find('.btn').addClass('disabled');
            }

            $option.click(click_answer(quiz, index, question));

            options_list.append($option);
        }

        if (quiz.isAnswered(question)) {
            highlight_options(question);
        }
    };

    /**
     * Clear question options list in GUI.
     */
    var clear_options = function () {
        $('.options-list').html('');
    };

    /**
     * Load quiz data json.
     *
     * @param {string} data_path
     */
    var load_quiz = function (data_path) {
        $.getJSON(data_path, function (data) {
            var questions = [];

            if (true === show_random_questions()) {
                questions = create_random_questions(data['questions']);
            } else {
                questions = create_questions(data['questions']);
            }

            var quiz = new Quiz();

            quiz.addPreLoadCallback(clear_options);

            quiz.addPostLoadCallback(update_questions_count);
            quiz.addPostLoadCallback(update_current_question_number);

            quiz.addPreChangeQuestionCallback(clear_options);

            quiz.addPostSetAnswersCallback(hide_show_explain);

            quiz.addPostChangeQuestionCallback(update_current_question_number);
            quiz.addPostChangeQuestionCallback(update_question_html);
            quiz.addPostChangeQuestionCallback(update_question_options_html);
            quiz.addPostChangeQuestionCallback(hide_show_explain);

            quiz.load(questions);

            $('.quiz .next').click(function () {
                next_action(quiz)
            });
            $('.quiz .previous').click(quiz.prev);
        });
    };

    /**
     * Initialize default GUI state.
     */
    var init_gui_state = function() {
        $('.results').hide();
        $('.quiz').hide();
    };

    /**
     * Bind action to GUI elements.
     */
    var bind_gui_items = function() {
        $('.start .btn').click(function () {
            $('.start').hide();
            var data_file = $("select[name='data']:first").val();
            load_quiz("data/" + data_file);
            $(".quiz").show();
        });

        $('.results .previous').click(function () {
            $('.results').hide();
            $('.quiz').show();
        });
    };

    init_gui_state();
    bind_gui_items();
});
<|endoftext|>


<commit_before>if (typeof(Worker) !== "undefined") {
    // Yes! Web worker support!
    // Code highlight
    addEventListener('load', function() {
      var code = document.querySelector('code');
      var worker = new Worker('worker-highlight.js');
      worker.onmessage = function(event) { code.innerHTML = event.data; }
      worker.postMessage(code.textContent);
    })
} else {
    console.log("Browser not supported!")
}
<commit_msg>update js<commit_after>if (typeof(Worker) !== "undefined") {
    // Yes! Web worker support!
    // Code highlight
    addEventListener('load', function() {
      var code = document.querySelector('code');
      var worker = new Worker('/assets/js/worker-highlight.js');
      worker.onmessage = function(event) { code.innerHTML = event.data; }
      worker.postMessage(code.textContent);
    })
} else {
    console.log("Browser not supported!")
}
<|endoftext|>