<commit_before>'use strict';

import { array } from 'metal';
import dom from 'metal-dom';
import { ComponentRenderer, EventsCollector } from 'metal-component';
import IncrementalDomAop from './IncrementalDomAop';

/**
 * Class responsible for rendering components via incremental dom.
 */
class IncrementalDomRenderer extends ComponentRenderer {
	/**
	 * @inheritDoc
	 */
	constructor(comp) {
		super(comp);

		this.changes_ = {};
		this.eventsCollector_ = new EventsCollector(comp);
		comp.on('attrChanged', this.handleAttrChanged_.bind(this));
		comp.on('detached', this.handleDetached_.bind(this));
	}

	/**
	 * Adds all inline listener attributes included in the given config.
	 * @param {!Array} listeners
	 * @protected
	 */
	addInlineListeners_(listeners) {
		for (var i = 0; i < listeners.length; i += 2) {
			var name = listeners[i];
			if (name.startsWith('data-on')) {
				this.listenersToAttach_.push({
					eventName: name.substr(7),
					fn: listeners[i + 1]
				});
			}
		}
	}

	/**
	 * Attaches any inline listeners found in the contents built via the last
	 * incremental dom patch.
	 * @protected
	 */
	attachInlineListeners_() {
		this.eventsCollector_.startCollecting();
		for (var i = 0; i < this.listenersToAttach_.length; i++) {
			var listener = this.listenersToAttach_[i];
			this.eventsCollector_.attachListener(listener.eventName, listener.fn);
		}
	}

	/**
	 * Disposes all sub components that were not found after an update anymore.
	 * @protected
	 */
	disposeUnusedSubComponents_() {
		var ids = Object.keys(this.component_.components);
		var unused = [];
		for (var i = 0; i < ids.length; i++) {
			if (!this.subComponentsFound_[ids[i]]) {
				unused.push(ids[i]);
			}
		}
		this.component_.disposeSubComponents(unused);
	}

	/**
	 * Gets the sub component referenced by the given tag and config data,
	 * creating it if it doesn't yet exist.
	 * @param {string} tag The tag name.
	 * @param {!Object} config The config object for the sub component.
	 * @return {!Component} The sub component.
	 * @protected
	 */
	getSubComponent_(tag, config) {
		var tagOrCtor = tag;
		if (tag === 'Component' && config.ctor) {
			tagOrCtor = config.ctor;
			config = config.data;
		}
		var comp = this.component_.addSubComponent(tagOrCtor, config);

		if (comp.wasRendered) {
			comp.setAttrs(config);
		}
		return comp;
	}

	/**
	 * Guarantees that the component's element has a parent. That's necessary
	 * when calling incremental dom's `patchOuter` for now, as otherwise it will
	 * throw an error if the element needs to be replaced.
	 * @return {Element} The parent, in case it was added.
	 * @protected
	 */
	guaranteeParent_() {
		var element = this.component_.element;
		if (!element || !element.parentNode) {
			var parent = document.createElement('div');
			if (element) {
				dom.append(parent, element);
			}
			return parent;
		}
	}

	/**
	 * Handles the `attrChanged` event. Makes sure that, when `attrsChanged` is
	 * fired, the component's contents will only be updated if the changed attr
	 * wasn't `element`, since that wouldn't cause a rerender.
	 * @param {!Object} data
	 * @protected
	 */
	handleAttrChanged_(data) {
		if (data.attrName !== 'element') {
			this.shouldUpdate_ = true;
			this.changes_[data.attrName] = data;
		}
	}

	/**
	 * Handles the `detached` listener. Removes all inline listeners.
	 * @protected
	 */
	handleDetached_() {
		this.eventsCollector_.detachAllListeners();
	}

	/**
	 * Handles an intercepted call to the `elementClose` function from incremental
	 * dom.
	 * @param {!function()} originalFn The original function before interception.
	 * @param {string} tag
	 * @protected
	 */
	handleInterceptedCloseCall_(originalFn, tag) {
		if (!this.isComponentTag_(tag)) {
			originalFn(tag);
		}
	}

	/**
	 * Handles an intercepted call to the `elementOpen` function from incremental
	 * dom.
	 * @param {!function()} originalFn The original function before interception.
	 * @param {string} tag
	 * @protected
	 */
	handleInterceptedOpenCall_(originalFn, tag) {
		var node;
		if (this.isComponentTag_(tag)) {
			node = this.handleSubComponentCall_.apply(this, arguments);
		} else {
			node = this.handleRegularCall_.apply(this, arguments);
		}
		return node;
	}

	/**
	 * Handles an intercepted call to the `elementOpen` function from incremental
	 * dom, done for a regular element. Adds any inline listeners found and makes
	 * sure that component root elements are always reused.
	 * @param {!function()} originalFn The original function before interception.
	 * @param {string} tag
	 * @param {?string} key
	 * @param {?Array} statics
	 * @protected
	 */
	handleRegularCall_(originalFn, tag, key, statics) {
		var attrsArr = array.slice(arguments, 4);
		this.addInlineListeners_((statics || []).concat(attrsArr));
		var node = originalFn.apply(null, array.slice(arguments, 1));
		if (!this.rootElementReached_) {
			this.rootElementReached_ = true;
			if (this.component_.element !== node) {
				this.component_.element = node;
			}
		}
		return node;
	}

	/**
	 * Handles an intercepted call to the `elementOpen` function from incremental
	 * dom, done for a sub component element. Creates and updates the appropriate
	 * sub component.
	 * @param {!function()} originalFn The original function before interception.
	 * @param {string} tag
	 * @param {?string} key
	 * @param {?Array} statics
	 * @protected
	 */
	handleSubComponentCall_(originalFn, tag, key, statics) {
		var config = {};
		var attrsArr = (statics || []).concat(array.slice(arguments, 4));
		for (var i = 0; i < attrsArr.length; i += 2) {
			config[attrsArr[i]] = attrsArr[i + 1];
		}
		var comp = this.renderSubComponent_(tag, config);
		return comp.element;
	}

	/**
	 * Checks if the given tag represents a metal component.
	 * @param {string} tag
	 * @protected
	 */
	isComponentTag_(tag) {
		return tag[0] === tag[0].toUpperCase();
	}

	/**
	 * Renders the renderer's component for the first time, patching its element
	 * through the incremental dom function calls done by `renderIncDom`.
	 */
	render() {
		this.patch();
	}

	/**
	 * Calls functions from `IncrementalDOM` to build the component element's
	 * content. Can be overriden by subclasses (for integration with template
	 * engines for example).
	 */
	renderIncDom() {
		IncrementalDOM.elementVoid('div', null, ['id', this.component_.id]);
	}

	/**
	 * Runs the incremental dom functions for rendering this component, but
	 * doesn't call `patch` yet. Rather, this will be the function that should be
	 * called by `patch`.
	 */
	renderWithoutPatch() {
		// Mark that there shouldn't be an update for attrs changed so far, since
		// render has already been called.
		this.shouldUpdate_ = false;
		this.changes_ = {};

		this.rootElementReached_ = false;
		this.subComponentsFound_ = {};
		this.listenersToAttach_ = [];
		IncrementalDomAop.startInterception(
			this.handleInterceptedOpenCall_.bind(this),
			this.handleInterceptedCloseCall_.bind(this)
		);
		this.renderIncDom();
		IncrementalDomAop.stopInterception();
		this.attachInlineListeners_();
	}

	/**
	 * Checks if the component should be updated with the current attr changes.
	 * Can be overridden by subclasses to provide customized behavior (only
	 * updating when a template attribute is changed for example).
	 * @param {!Object} changes
	 * @return {boolean}
	 */
	shouldUpdate() {
		return this.shouldUpdate_;
	}

	/**
	 * Patches the component's element with the incremental dom function calls
	 * done by `renderIncDom`.
	 */
	patch() {
		var tempParent = this.guaranteeParent_();
		if (tempParent) {
			IncrementalDOM.patch(tempParent, this.renderWithoutPatch.bind(this));
			dom.exitDocument(this.component_.element);
		} else {
			IncrementalDOM.patchOuter(this.component_.element, this.renderWithoutPatch.bind(this));
		}
	}

	/**
	 * Updates the renderer's component when attributes change, patching its
	 * element through the incremental dom function calls done by `renderIncDom`.
	 */
	update() {
		if (this.shouldUpdate(this.changes_)) {
			this.patch();
			this.eventsCollector_.detachUnusedListeners();
			this.disposeUnusedSubComponents_();
		}
	}

	/**
	 * This updates the sub component that is represented by the given data.
	 * The sub component is created, added to its parent and rendered. If it
	 * had already been rendered before though, it will only have its attributes
	 * updates instead.
	 * @param {string} tag The tag name.
	 * @param {!Object} config The config object for the sub component.
	 * @return {!Component} The updated sub component.
	 * @protected
	 */
	renderSubComponent_(tag, config) {
		var comp = this.getSubComponent_(tag, config);
		comp.getRenderer().renderWithoutPatch();
		if (!comp.wasRendered) {
			comp.renderAsSubComponent();
		}
		this.subComponentsFound_[comp.id] = true;
		return comp;
	}
}

export default IncrementalDomRenderer;
<commit_msg>Removes the necessity for calling super.shouldUpdate when overriding it<commit_after>'use strict';

import { array } from 'metal';
import dom from 'metal-dom';
import { ComponentRenderer, EventsCollector } from 'metal-component';
import IncrementalDomAop from './IncrementalDomAop';

/**
 * Class responsible for rendering components via incremental dom.
 */
class IncrementalDomRenderer extends ComponentRenderer {
	/**
	 * @inheritDoc
	 */
	constructor(comp) {
		super(comp);

		this.changes_ = {};
		this.eventsCollector_ = new EventsCollector(comp);
		comp.on('attrChanged', this.handleAttrChanged_.bind(this));
		comp.on('detached', this.handleDetached_.bind(this));
	}

	/**
	 * Adds all inline listener attributes included in the given config.
	 * @param {!Array} listeners
	 * @protected
	 */
	addInlineListeners_(listeners) {
		for (var i = 0; i < listeners.length; i += 2) {
			var name = listeners[i];
			if (name.startsWith('data-on')) {
				this.listenersToAttach_.push({
					eventName: name.substr(7),
					fn: listeners[i + 1]
				});
			}
		}
	}

	/**
	 * Attaches any inline listeners found in the contents built via the last
	 * incremental dom patch.
	 * @protected
	 */
	attachInlineListeners_() {
		this.eventsCollector_.startCollecting();
		for (var i = 0; i < this.listenersToAttach_.length; i++) {
			var listener = this.listenersToAttach_[i];
			this.eventsCollector_.attachListener(listener.eventName, listener.fn);
		}
	}

	/**
	 * Disposes all sub components that were not found after an update anymore.
	 * @protected
	 */
	disposeUnusedSubComponents_() {
		var ids = Object.keys(this.component_.components);
		var unused = [];
		for (var i = 0; i < ids.length; i++) {
			if (!this.subComponentsFound_[ids[i]]) {
				unused.push(ids[i]);
			}
		}
		this.component_.disposeSubComponents(unused);
	}

	/**
	 * Gets the sub component referenced by the given tag and config data,
	 * creating it if it doesn't yet exist.
	 * @param {string} tag The tag name.
	 * @param {!Object} config The config object for the sub component.
	 * @return {!Component} The sub component.
	 * @protected
	 */
	getSubComponent_(tag, config) {
		var tagOrCtor = tag;
		if (tag === 'Component' && config.ctor) {
			tagOrCtor = config.ctor;
			config = config.data;
		}
		var comp = this.component_.addSubComponent(tagOrCtor, config);

		if (comp.wasRendered) {
			comp.setAttrs(config);
		}
		return comp;
	}

	/**
	 * Guarantees that the component's element has a parent. That's necessary
	 * when calling incremental dom's `patchOuter` for now, as otherwise it will
	 * throw an error if the element needs to be replaced.
	 * @return {Element} The parent, in case it was added.
	 * @protected
	 */
	guaranteeParent_() {
		var element = this.component_.element;
		if (!element || !element.parentNode) {
			var parent = document.createElement('div');
			if (element) {
				dom.append(parent, element);
			}
			return parent;
		}
	}

	/**
	 * Handles the `attrChanged` event. Makes sure that, when `attrsChanged` is
	 * fired, the component's contents will only be updated if the changed attr
	 * wasn't `element`, since that wouldn't cause a rerender.
	 * @param {!Object} data
	 * @protected
	 */
	handleAttrChanged_(data) {
		if (data.attrName !== 'element') {
			this.changes_[data.attrName] = data;
		}
	}

	/**
	 * Handles the `detached` listener. Removes all inline listeners.
	 * @protected
	 */
	handleDetached_() {
		this.eventsCollector_.detachAllListeners();
	}

	/**
	 * Handles an intercepted call to the `elementClose` function from incremental
	 * dom.
	 * @param {!function()} originalFn The original function before interception.
	 * @param {string} tag
	 * @protected
	 */
	handleInterceptedCloseCall_(originalFn, tag) {
		if (!this.isComponentTag_(tag)) {
			originalFn(tag);
		}
	}

	/**
	 * Handles an intercepted call to the `elementOpen` function from incremental
	 * dom.
	 * @param {!function()} originalFn The original function before interception.
	 * @param {string} tag
	 * @protected
	 */
	handleInterceptedOpenCall_(originalFn, tag) {
		var node;
		if (this.isComponentTag_(tag)) {
			node = this.handleSubComponentCall_.apply(this, arguments);
		} else {
			node = this.handleRegularCall_.apply(this, arguments);
		}
		return node;
	}

	/**
	 * Handles an intercepted call to the `elementOpen` function from incremental
	 * dom, done for a regular element. Adds any inline listeners found and makes
	 * sure that component root elements are always reused.
	 * @param {!function()} originalFn The original function before interception.
	 * @param {string} tag
	 * @param {?string} key
	 * @param {?Array} statics
	 * @protected
	 */
	handleRegularCall_(originalFn, tag, key, statics) {
		var attrsArr = array.slice(arguments, 4);
		this.addInlineListeners_((statics || []).concat(attrsArr));
		var node = originalFn.apply(null, array.slice(arguments, 1));
		if (!this.rootElementReached_) {
			this.rootElementReached_ = true;
			if (this.component_.element !== node) {
				this.component_.element = node;
			}
		}
		return node;
	}

	/**
	 * Handles an intercepted call to the `elementOpen` function from incremental
	 * dom, done for a sub component element. Creates and updates the appropriate
	 * sub component.
	 * @param {!function()} originalFn The original function before interception.
	 * @param {string} tag
	 * @param {?string} key
	 * @param {?Array} statics
	 * @protected
	 */
	handleSubComponentCall_(originalFn, tag, key, statics) {
		var config = {};
		var attrsArr = (statics || []).concat(array.slice(arguments, 4));
		for (var i = 0; i < attrsArr.length; i += 2) {
			config[attrsArr[i]] = attrsArr[i + 1];
		}
		var comp = this.renderSubComponent_(tag, config);
		return comp.element;
	}

	/**
	 * Checks if the given tag represents a metal component.
	 * @param {string} tag
	 * @protected
	 */
	isComponentTag_(tag) {
		return tag[0] === tag[0].toUpperCase();
	}

	/**
	 * Renders the renderer's component for the first time, patching its element
	 * through the incremental dom function calls done by `renderIncDom`.
	 */
	render() {
		this.patch();
	}

	/**
	 * Calls functions from `IncrementalDOM` to build the component element's
	 * content. Can be overriden by subclasses (for integration with template
	 * engines for example).
	 */
	renderIncDom() {
		IncrementalDOM.elementVoid('div', null, ['id', this.component_.id]);
	}

	/**
	 * Runs the incremental dom functions for rendering this component, but
	 * doesn't call `patch` yet. Rather, this will be the function that should be
	 * called by `patch`.
	 */
	renderWithoutPatch() {
		// Mark that there shouldn't be an update for attrs changed so far, since
		// render has already been called.
		this.changes_ = {};

		this.rootElementReached_ = false;
		this.subComponentsFound_ = {};
		this.listenersToAttach_ = [];
		IncrementalDomAop.startInterception(
			this.handleInterceptedOpenCall_.bind(this),
			this.handleInterceptedCloseCall_.bind(this)
		);
		this.renderIncDom();
		IncrementalDomAop.stopInterception();
		this.attachInlineListeners_();
	}

	/**
	 * Checks if the component should be updated with the current attr changes.
	 * Can be overridden by subclasses to provide customized behavior (only
	 * updating when a template attribute is changed for example).
	 * @param {!Object} changes
	 * @return {boolean}
	 */
	shouldUpdate() {
		return true;
	}

	/**
	 * Patches the component's element with the incremental dom function calls
	 * done by `renderIncDom`.
	 */
	patch() {
		var tempParent = this.guaranteeParent_();
		if (tempParent) {
			IncrementalDOM.patch(tempParent, this.renderWithoutPatch.bind(this));
			dom.exitDocument(this.component_.element);
		} else {
			IncrementalDOM.patchOuter(this.component_.element, this.renderWithoutPatch.bind(this));
		}
	}

	/**
	 * Updates the renderer's component when attributes change, patching its
	 * element through the incremental dom function calls done by `renderIncDom`.
	 */
	update() {
		var changedAttrs = Object.keys(this.changes_);
		if (changedAttrs.length > 0 && this.shouldUpdate(this.changes_)) {
			this.patch();
			this.eventsCollector_.detachUnusedListeners();
			this.disposeUnusedSubComponents_();
		}
	}

	/**
	 * This updates the sub component that is represented by the given data.
	 * The sub component is created, added to its parent and rendered. If it
	 * had already been rendered before though, it will only have its attributes
	 * updates instead.
	 * @param {string} tag The tag name.
	 * @param {!Object} config The config object for the sub component.
	 * @return {!Component} The updated sub component.
	 * @protected
	 */
	renderSubComponent_(tag, config) {
		var comp = this.getSubComponent_(tag, config);
		comp.getRenderer().renderWithoutPatch();
		if (!comp.wasRendered) {
			comp.renderAsSubComponent();
		}
		this.subComponentsFound_[comp.id] = true;
		return comp;
	}
}

export default IncrementalDomRenderer;
<|endoftext|>


<commit_before>var log = require('./operation/log')
    , util = require('./util')
    , defineSubProperty = util.defineSubProperty
    , _ = util._
    , error = require('./error')
    , InternalSiestaError = error.InternalSiestaError
    , coreChanges = require('./changes')
    , notificationCentre = require('./notificationCentre').notificationCentre
    , cache = require('./cache');

var Logger = log.loggerWithName('ModelInstance');
Logger.setLevel(log.Level.warn);

function ModelInstance(model) {
    var self = this;
    this.model = model;

    util.subProperties(this, this.model, [
        'collection',
        'collectionName',
        '_attributeNames',
        {
            name: 'idField',
            property: 'id'
        },
        {
            name: 'modelName',
            property: 'name'
        }
    ]);

    Object.defineProperty(this, '_relationshipNames', {
        get: function () {
            var proxies = _.map(Object.keys(self.__proxies || {}), function (x) {return self.__proxies[x]});
            return _.map(proxies, function (p) {
                if (p.isForward) {
                    return p.forwardName;
                } else {
                    return p.reverseName;
                }
            });
        },
        enumerable: true,
        configurable: true
    });

    Object.defineProperty(this, 'dirty', {
        get: function () {
            if (siesta.ext.storageEnabled) {
                return self._id in siesta.ext.storage._unsavedObjectsHash;
            }
            else return undefined;
        },
        enumerable: true
    });
    this.removed = false;
}

_.extend(ModelInstance.prototype, {
    get: function (callback) {
        var deferred = window.q ? window.q.defer() : null;
        callback = util.cb(callback, deferred);
        callback(null, this);
        return deferred ? deferred.promise : null;
    },
    remove: function (callback, notification) {
        notification = notification == null ? true : notification;
        var deferred = window.q ? window.q.defer() : null;
        callback = util.cb(callback, deferred);
        cache.remove(this);
        this.removed = true;
        if (notification) {
            coreChanges.registerChange({
                collection: this.collectionName,
                model: this.model.name,
                _id: this._id,
                oldId: this._id,
                old: this,
                type: coreChanges.ChangeType.Remove,
                obj: this
            });
        }
        callback(null, this);
        return deferred ? deferred.promise : null;
    },
    restore: function (callback) {
        var deferred = window.q ? window.q.defer() : null;
        callback = util.cb(callback, deferred);
        if (this.removed) {
            cache.insert(this);
            this.removed = false;
        }
        coreChanges.registerChange({
            collection: this.collectionName,
            model: this.model.name,
            _id: this._id,
            newId: this._id,
            new: this,
            type: coreChanges.ChangeType.New,
            obj: this
        });
        callback(null, this);
        return deferred ? deferred.promise : null;
    }
});

_.extend(ModelInstance.prototype, {
    listen: function (fn) {
        notificationCentre.on(this._id, fn);
        return function () {
            this.removeListener(fn);
        }.bind(this);
    },
    listenOnce: function (fn) {
        return notificationCentre.once(this._id, fn);
    },
    removeListener: function (fn) {
        return notificationCentre.removeListener(this._id, fn);
    }
});

// Inspection
_.extend(ModelInstance.prototype, {
    getAttributes: function () {
        return _.extend({}, this.__values);
    },
    isInstanceOf: function (model) {
        return this.model == model || this.model.isDescendantOf(model);
    }
});

_.extend(ModelInstance.prototype, {
    _dumpString: function (reverseRelationships) {
        return JSON.stringify(this._dump(reverseRelationships, null, 4));
    },
    _dump: function (reverseRelationships) {
        reverseRelationships = reverseRelationships === undefined ? true : reverseRelationships;
        var dumped = _.extend({}, this.__values);
        //_.reduce(this.model.relationships, function (dumped, relationship) {
        //    var relationshipName;
        //    if (relationship.isReverse && reverseRelationships) {
        //        relationshipName = relationship.reverseName;
        //    }
        //    else if (!relationship.isReverse) {
        //        relationshipName = relationship.forwardName;
        //    }
        //    if (relationshipName) {
        //        var relationshipValue = this[relationshipName];
        //        if (util.isArray(relationshipValue)) {
        //            dumped[relationshipName] = _.map(relationshipValue, function (r) {
        //                return r._dump(false);
        //            });
        //        }
        //        else {
        //            dumped[relationshipName] = relationshipValue._dump(false);
        //         }
        //    }
        //    return dumped;
        //}.bind(this), dumped);
        dumped._rev = this._rev;
        dumped._id = this._id;
        return dumped;
    }
});

exports.ModelInstance = ModelInstance;
exports.dumpSaveQueues = function () {
    var dumped = {};
    for (var id in queues) {
        if (queues.hasOwnProperty(id)) {
            var queue = queues[id];
            dumped[id] = {
                numRunning: queue.numRunningOperations,
                queued: queue._queuedOperations.length
            };
        }
    }
    return dumped;
};<commit_msg>Cleaned ModelInstance constructor<commit_after>var log = require('./operation/log')
    , util = require('./util')
    , _ = util._
    , error = require('./error')
    , InternalSiestaError = error.InternalSiestaError
    , coreChanges = require('./changes')
    , notificationCentre = require('./notificationCentre').notificationCentre
    , cache = require('./cache');

var Logger = log.loggerWithName('ModelInstance');
Logger.setLevel(log.Level.warn);

function ModelInstance(model) {
    var self = this;
    this.model = model;

    util.subProperties(this, this.model, [
        'collection',
        'collectionName',
        '_attributeNames',
        {
            name: 'idField',
            property: 'id'
        },
        {
            name: 'modelName',
            property: 'name'
        }
    ]);

    Object.defineProperties(this, {
        _relationshipNames: {
            get: function () {
                var proxies = _.map(Object.keys(self.__proxies || {}), function (x) {return self.__proxies[x]});
                return _.map(proxies, function (p) {
                    if (p.isForward) {
                        return p.forwardName;
                    } else {
                        return p.reverseName;
                    }
                });
            },
            enumerable: true,
            configurable: true
        },
        dirty: {
            get: function () {
                if (siesta.ext.storageEnabled) {
                    return self._id in siesta.ext.storage._unsavedObjectsHash;
                }
                else return undefined;
            },
            enumerable: true
        }
    });

    this.removed = false;
}

_.extend(ModelInstance.prototype, {
    get: function (callback) {
        var deferred = window.q ? window.q.defer() : null;
        callback = util.cb(callback, deferred);
        callback(null, this);
        return deferred ? deferred.promise : null;
    },
    remove: function (callback, notification) {
        notification = notification == null ? true : notification;
        var deferred = window.q ? window.q.defer() : null;
        callback = util.cb(callback, deferred);
        cache.remove(this);
        this.removed = true;
        if (notification) {
            coreChanges.registerChange({
                collection: this.collectionName,
                model: this.model.name,
                _id: this._id,
                oldId: this._id,
                old: this,
                type: coreChanges.ChangeType.Remove,
                obj: this
            });
        }
        callback(null, this);
        return deferred ? deferred.promise : null;
    },
    restore: function (callback) {
        var deferred = window.q ? window.q.defer() : null;
        callback = util.cb(callback, deferred);
        if (this.removed) {
            cache.insert(this);
            this.removed = false;
        }
        coreChanges.registerChange({
            collection: this.collectionName,
            model: this.model.name,
            _id: this._id,
            newId: this._id,
            new: this,
            type: coreChanges.ChangeType.New,
            obj: this
        });
        callback(null, this);
        return deferred ? deferred.promise : null;
    }
});

_.extend(ModelInstance.prototype, {
    listen: function (fn) {
        notificationCentre.on(this._id, fn);
        return function () {
            this.removeListener(fn);
        }.bind(this);
    },
    listenOnce: function (fn) {
        return notificationCentre.once(this._id, fn);
    },
    removeListener: function (fn) {
        return notificationCentre.removeListener(this._id, fn);
    }
});

// Inspection
_.extend(ModelInstance.prototype, {
    getAttributes: function () {
        return _.extend({}, this.__values);
    },
    isInstanceOf: function (model) {
        return this.model == model || this.model.isDescendantOf(model);
    }
});

_.extend(ModelInstance.prototype, {
    _dumpString: function (reverseRelationships) {
        return JSON.stringify(this._dump(reverseRelationships, null, 4));
    },
    _dump: function (reverseRelationships) {
        reverseRelationships = reverseRelationships === undefined ? true : reverseRelationships;
        var dumped = _.extend({}, this.__values);
        //_.reduce(this.model.relationships, function (dumped, relationship) {
        //    var relationshipName;
        //    if (relationship.isReverse && reverseRelationships) {
        //        relationshipName = relationship.reverseName;
        //    }
        //    else if (!relationship.isReverse) {
        //        relationshipName = relationship.forwardName;
        //    }
        //    if (relationshipName) {
        //        var relationshipValue = this[relationshipName];
        //        if (util.isArray(relationshipValue)) {
        //            dumped[relationshipName] = _.map(relationshipValue, function (r) {
        //                return r._dump(false);
        //            });
        //        }
        //        else {
        //            dumped[relationshipName] = relationshipValue._dump(false);
        //         }
        //    }
        //    return dumped;
        //}.bind(this), dumped);
        dumped._rev = this._rev;
        dumped._id = this._id;
        return dumped;
    }
});

exports.ModelInstance = ModelInstance;
exports.dumpSaveQueues = function () {
    var dumped = {};
    for (var id in queues) {
        if (queues.hasOwnProperty(id)) {
            var queue = queues[id];
            dumped[id] = {
                numRunning: queue.numRunningOperations,
                queued: queue._queuedOperations.length
            };
        }
    }
    return dumped;
};<|endoftext|>


<commit_before>var Q = require("q"),
	PromiseUtil = require("./PromiseUtil");

// This data structure defines the page interface.
//
// Each item represents a method that page objects (and middleware) may override.
//
// The keys here are method names.
//
// The values are tuples containing:
//   - Number of arguments to the method.
//   - Default implementation of the method.
//   - Normalization function applied to method output.
//
// Note that each of these methods also receives an additional argument,
// which is the next implementation of the method in the call chain.
//     - Middleware implementations _should_ call this in most cases.*
//     - Page implementations _may_ call this (it will be the default implementation).
//
// * Consider carefully before deciding not to call `next()` in middleware.
// Other middleware (and the page itself) may exhibit undefined behavior if a
// given method is not called.  Generally, only skip calling `next()` for
// short-circuit responses (e.g. a redirect from `handleRoute`).
//
var PAGE_METHODS = {
	handleRoute        : [1, () => ({code: 200}), Q],
	getTitle           : [0, () => "", Q],
	getScripts         : [0, () => [], standardizeScripts],
	getSystemScripts   : [0, () => [], standardizeScripts],
	getHeadStylesheets : [0, () => [], standardizeStyles],
	getMetaTags        : [0, () => [], standardizeMetaTags],
	getLinkTags        : [0, () => [], standardizeLinkTags],
	getBase            : [0, () => null, Q],
	getBodyClasses     : [0, () => [], Q],
	getElements        : [0, () => [], standardizeElements],
	getResponseData    : [0, () => [], Q],
	handleComplete     : [0, () => null, Q],
};

// These `standardize*` functions show what will happen to the output of your
// page methods.
//
// For middleware authors: Be aware that these standardization functions will
// have been applied to the output of `next()` before you get access to it.
//
// These functions are also exposed via `PageUtil.standardize*`.

/**
 * This method takes in anything returned from a Page.getElements call and
 * returns the elements in a standardized format: an array of EarlyPromises of
 * ReactElements.
 */
function standardizeElements(elements) {

	// The return value could be a single element or an array.
	// First, let's make sure that it's an array.
	// Then, ensure that all elements are EarlyPromises.
	return PageUtil
		.makeArray(elements)
		.map(element => PromiseUtil.early(Q(element)));
}

function standardizeMetaTags(metaTags) {
	return PageUtil.makeArray(metaTags).map(metaTag => Q(metaTag));
}

function standardizeLinkTags(linkTags) {
	return PageUtil.makeArray(linkTags).map(linkTag => Q(linkTag));
}

function standardizeScripts(scripts) {
	return PageUtil.makeArray(scripts).map((script) => {
		if (script.href || script.text) {
			if (!script.type) script.type = "text/javascript";
			return script;
		}

		// if the answer was a string, let's make a script object
		return {href:script, type:"text/javascript"};
	})
}

function standardizeStyles(styles) {
	return PageUtil.makeArray(styles).map((style) => {
		if (style.href || style.text) {
			if (!style.type) style.type = "text/css";
			if (!style.media) style.media = "";

			return style;
		}

		// if the answer was a string, let's make a script object
		return {href:style, type:"text/css", media:""};
	})
}

var PageUtil = module.exports = {
	PAGE_METHODS,

	standardizeElements,
	standardizeMetaTags,
	standardizeScripts,
	standardizeStyles,

	/**
	 * Given an array of page instances, return an object that implements the page interface. This returned object's
	 * methods delegate to the first page in the pages array, passing the second page's implementation as the next
	 * variable (and the second page's implementation gets the third page's next, and so on).
	 */
	createPageChain(pages) {

		// This will be our return value.  It will be a mapping of
		// method names on page objects to chained function calls.
		var pageChain = {};

		for (var method in PAGE_METHODS){

			if (PAGE_METHODS.hasOwnProperty(method)){

				var [
					argumentCount,
					defaultImpl,
					standardizeReturnValue
				] = PAGE_METHODS[method];

				pageChain[method] = PageUtil
					.createObjectFunctionChain(
						pages,
						method,
						argumentCount,
						defaultImpl,
						standardizeReturnValue
					);
			}
		}

		return pageChain;
	},

	/**
	 * Given an array of objects and a function name, this returns a function that will call functionName on the first
	 * object, adding a next parameter to the end that is a function to call to the next object's implementation of functionName, and so on
	 * down the line. It is a way to make a middleware-like chain of functions, but the functions will be bound to the object in
	 * question from objects.
	 *
	 * The last object in the array will also receive a next parameter, which will call defaultImpl.
	 *
	 * standardizeReturnValue will be called on every return value from functionName so that next will always return the same type. This
	 * is useful when a function can return one of many different types.
	 */
	createObjectFunctionChain(objects, functionName, argumentCount, defaultImpl, standardizeReturnValue) {
		// our first order of business is to get an array of pure functions to chain; this involves finding
		// which objects have implementations for functionName, binding those implementations to the object
		// in question, and standardizing the return values.
		var functionsToChain = [];
		objects.forEach((object) => {
			if (object[functionName]) {
				functionsToChain.push(function() {
					return standardizeReturnValue(object[functionName].apply(object, arguments));
				});
			}
		});
		// the innermost implementation calls the default implementation and standardizes the result.
		functionsToChain.push(function() {
			return standardizeReturnValue(defaultImpl.apply(null, arguments));
		});

		// now we have an array of pure functions to chain.
		return PageUtil.createFunctionChain(functionsToChain, argumentCount);
	},

	createFunctionChain(functions, argumentCount) {
		if (functions.length === 0 ) {
			throw new Error("createFunctionChain cannot be called with zero-length array.");
		}

		// we start from the innermost implementation (i.e. the last in the array) and move outward. nextImpl holds
		// the function that will be used as the next argument for the function before it in the array.
		var nextImpl = functions[functions.length - 1];
		// now let's chain together the other implementations.
		for (var i = functions.length - 2; i >= 0; i--) {
			//
			nextImpl = PageUtil._addArgumentToFunction(functions[i], nextImpl, argumentCount);
		}
		return nextImpl;
	},

	// takes in a function fn and returns a function that calls fn with argument bound at the end of the argument list.
	_addArgumentToFunction(fn, argument, argumentCount) {
		if (argumentCount > 6) {
			throw new Error("_addArgumentToFunction only supports methods with up to 6 arguments");
		}
		return function(a, b, c, d, e, f) {
			var args = [a, b, c, d, e, f];
			args = args.slice(0, argumentCount);
			args.push(argument);
			return fn.apply(null, args);
		}
	},

	makeArray(valueOrArray) {
		if (!Array.isArray(valueOrArray)) {
			return [valueOrArray];
		}
		return valueOrArray;
	}
}<commit_msg>Better default `getResponseData` handler<commit_after>var Q = require("q"),
	PromiseUtil = require("./PromiseUtil");

// This data structure defines the page interface.
//
// Each item represents a method that page objects (and middleware) may override.
//
// The keys here are method names.
//
// The values are tuples containing:
//   - Number of arguments to the method.
//   - Default implementation of the method.
//   - Normalization function applied to method output.
//
// Note that each of these methods also receives an additional argument,
// which is the next implementation of the method in the call chain.
//     - Middleware implementations _should_ call this in most cases.*
//     - Page implementations _may_ call this (it will be the default implementation).
//
// * Consider carefully before deciding not to call `next()` in middleware.
// Other middleware (and the page itself) may exhibit undefined behavior if a
// given method is not called.  Generally, only skip calling `next()` for
// short-circuit responses (e.g. a redirect from `handleRoute`).
//
var PAGE_METHODS = {
	handleRoute        : [1, () => ({code: 200}), Q],
	getTitle           : [0, () => "", Q],
	getScripts         : [0, () => [], standardizeScripts],
	getSystemScripts   : [0, () => [], standardizeScripts],
	getHeadStylesheets : [0, () => [], standardizeStyles],
	getMetaTags        : [0, () => [], standardizeMetaTags],
	getLinkTags        : [0, () => [], standardizeLinkTags],
	getBase            : [0, () => null, Q],
	getBodyClasses     : [0, () => [], Q],
	getElements        : [0, () => [], standardizeElements],
	getResponseData    : [0, () => "", Q],
	handleComplete     : [0, () => null, Q],
};

// These `standardize*` functions show what will happen to the output of your
// page methods.
//
// For middleware authors: Be aware that these standardization functions will
// have been applied to the output of `next()` before you get access to it.
//
// These functions are also exposed via `PageUtil.standardize*`.

/**
 * This method takes in anything returned from a Page.getElements call and
 * returns the elements in a standardized format: an array of EarlyPromises of
 * ReactElements.
 */
function standardizeElements(elements) {

	// The return value could be a single element or an array.
	// First, let's make sure that it's an array.
	// Then, ensure that all elements are EarlyPromises.
	return PageUtil
		.makeArray(elements)
		.map(element => PromiseUtil.early(Q(element)));
}

function standardizeMetaTags(metaTags) {
	return PageUtil.makeArray(metaTags).map(metaTag => Q(metaTag));
}

function standardizeLinkTags(linkTags) {
	return PageUtil.makeArray(linkTags).map(linkTag => Q(linkTag));
}

function standardizeScripts(scripts) {
	return PageUtil.makeArray(scripts).map((script) => {
		if (script.href || script.text) {
			if (!script.type) script.type = "text/javascript";
			return script;
		}

		// if the answer was a string, let's make a script object
		return {href:script, type:"text/javascript"};
	})
}

function standardizeStyles(styles) {
	return PageUtil.makeArray(styles).map((style) => {
		if (style.href || style.text) {
			if (!style.type) style.type = "text/css";
			if (!style.media) style.media = "";

			return style;
		}

		// if the answer was a string, let's make a script object
		return {href:style, type:"text/css", media:""};
	})
}

var PageUtil = module.exports = {
	PAGE_METHODS,

	standardizeElements,
	standardizeMetaTags,
	standardizeScripts,
	standardizeStyles,

	/**
	 * Given an array of page instances, return an object that implements the page interface. This returned object's
	 * methods delegate to the first page in the pages array, passing the second page's implementation as the next
	 * variable (and the second page's implementation gets the third page's next, and so on).
	 */
	createPageChain(pages) {

		// This will be our return value.  It will be a mapping of
		// method names on page objects to chained function calls.
		var pageChain = {};

		for (var method in PAGE_METHODS){

			if (PAGE_METHODS.hasOwnProperty(method)){

				var [
					argumentCount,
					defaultImpl,
					standardizeReturnValue
				] = PAGE_METHODS[method];

				pageChain[method] = PageUtil
					.createObjectFunctionChain(
						pages,
						method,
						argumentCount,
						defaultImpl,
						standardizeReturnValue
					);
			}
		}

		return pageChain;
	},

	/**
	 * Given an array of objects and a function name, this returns a function that will call functionName on the first
	 * object, adding a next parameter to the end that is a function to call to the next object's implementation of functionName, and so on
	 * down the line. It is a way to make a middleware-like chain of functions, but the functions will be bound to the object in
	 * question from objects.
	 *
	 * The last object in the array will also receive a next parameter, which will call defaultImpl.
	 *
	 * standardizeReturnValue will be called on every return value from functionName so that next will always return the same type. This
	 * is useful when a function can return one of many different types.
	 */
	createObjectFunctionChain(objects, functionName, argumentCount, defaultImpl, standardizeReturnValue) {
		// our first order of business is to get an array of pure functions to chain; this involves finding
		// which objects have implementations for functionName, binding those implementations to the object
		// in question, and standardizing the return values.
		var functionsToChain = [];
		objects.forEach((object) => {
			if (object[functionName]) {
				functionsToChain.push(function() {
					return standardizeReturnValue(object[functionName].apply(object, arguments));
				});
			}
		});
		// the innermost implementation calls the default implementation and standardizes the result.
		functionsToChain.push(function() {
			return standardizeReturnValue(defaultImpl.apply(null, arguments));
		});

		// now we have an array of pure functions to chain.
		return PageUtil.createFunctionChain(functionsToChain, argumentCount);
	},

	createFunctionChain(functions, argumentCount) {
		if (functions.length === 0 ) {
			throw new Error("createFunctionChain cannot be called with zero-length array.");
		}

		// we start from the innermost implementation (i.e. the last in the array) and move outward. nextImpl holds
		// the function that will be used as the next argument for the function before it in the array.
		var nextImpl = functions[functions.length - 1];
		// now let's chain together the other implementations.
		for (var i = functions.length - 2; i >= 0; i--) {
			//
			nextImpl = PageUtil._addArgumentToFunction(functions[i], nextImpl, argumentCount);
		}
		return nextImpl;
	},

	// takes in a function fn and returns a function that calls fn with argument bound at the end of the argument list.
	_addArgumentToFunction(fn, argument, argumentCount) {
		if (argumentCount > 6) {
			throw new Error("_addArgumentToFunction only supports methods with up to 6 arguments");
		}
		return function(a, b, c, d, e, f) {
			var args = [a, b, c, d, e, f];
			args = args.slice(0, argumentCount);
			args.push(argument);
			return fn.apply(null, args);
		}
	},

	makeArray(valueOrArray) {
		if (!Array.isArray(valueOrArray)) {
			return [valueOrArray];
		}
		return valueOrArray;
	}
}<|endoftext|>


<commit_before>// How to use:
// getValidation("Example,CSV,content\na,b,c\n")
//  .then(function(validation) {console.log(validation)})

function getValidation(content) {
  request = require('request');
  content = new Buffer(content).toString("base64");
  content = "editor.csv;data:text/csv;base64," + content;
  return new Promise(function(resolve, reject) {
    request.post("http://csvlint.io/package.json", { formData: {"files_data[]": content } }, function(error, response, body) {

      if (error) return reject(error);

      var packageURL = JSON.parse(response.body).package.url;
      var interval = setInterval(function() {
        request.get(packageURL + ".json", function(error, response, body) {
          try {
            var validationURL = JSON.parse(body).package.validations[0].url;
            clearInterval(interval);
            request.get(validationURL + ".json", function(error, response, body) {
              if (error) return reject(error);
              resolve(JSON.parse(body));
            });
          } catch(e) {}
        });
      }, 1000);

    });
  });
}

function validate() {
  data = hot.getData().map(function(d) { return d.join(",") }).join("\n")
  getValidation(data).then(function(json_validation) {
    errors = json_validation.validation.errors
    warnings = json_validation.validation.warnings
    info_messages = json_validation.validation.info
    console.error(errors)
    console.warn(warnings)
    console.info(info_messages);
  });
}
<commit_msg>Comment explanation of validate()<commit_after>// How to use:
// getValidation("Example,CSV,content\na,b,c\n")
//  .then(function(validation) {console.log(validation)})

function getValidation(content) {
  request = require('request');
  content = new Buffer(content).toString("base64");
  content = "editor.csv;data:text/csv;base64," + content;
  return new Promise(function(resolve, reject) {
    request.post("http://csvlint.io/package.json", { formData: {"files_data[]": content } }, function(error, response, body) {

      if (error) return reject(error);

      var packageURL = JSON.parse(response.body).package.url;
      var interval = setInterval(function() {
        request.get(packageURL + ".json", function(error, response, body) {
          try {
            var validationURL = JSON.parse(body).package.validations[0].url;
            clearInterval(interval);
            request.get(validationURL + ".json", function(error, response, body) {
              if (error) return reject(error);
              resolve(JSON.parse(body));
            });
          } catch(e) {}
        });
      }, 1000);

    });
  });
}

// Splits validation returned from CSVLint into errors, warnings and info messages

function validate() {
  data = hot.getData().map(function(d) { return d.join(",") }).join("\n")
  getValidation(data).then(function(json_validation) {
    errors = json_validation.validation.errors
    warnings = json_validation.validation.warnings
    info_messages = json_validation.validation.info
    console.error(errors)
    console.warn(warnings)
    console.info(info_messages);
  });
}
<|endoftext|>


<commit_before>// ==UserScript==
// @name            Phim4400 Helper
// @name:vi         Phim4400 Helper
// @namespace       https://lelinhtinh.github.io
// @description     Xem online và tải phim trực tiếp tại Phim4400, bỏ qua quảng cáo.
// @description:vi  Xem online và tải phim trực tiếp tại Phim4400, bỏ qua quảng cáo.
// @version         1.1.0
// @icon            https://i.imgur.com/wRRkkqr.png
// @author          lelinhtinh
// @oujs:author     baivong
// @license         MIT; https://baivong.mit-license.org/license.txt
// @match           https://phim440.cf/*
// @match           https://phim4400.cf/*
// @match           https://phim4400.tv/*
// @require         https://cdnjs.cloudflare.com/ajax/libs/js-sha256/0.9.0/sha256.min.js
// @require         https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js?v=a834d46
// @noframes
// @supportURL      https://github.com/lelinhtinh/Userscript/issues
// @run-at          document-idle
// @grant           GM.xmlHttpRequest
// @grant           GM_xmlhttpRequest
// ==/UserScript==

/* global shortcut, sha256 */
(function() {
  'use strict';

  const upfileVn = document.querySelector('a[href*="upfile.vn"]');
  const upfileUs = document.querySelector('a[href*="upfile.us"]');
  const drivehub = document.querySelector('a[href*="drivehub.link"]');

  let linkDown = {
    upfileVn: {
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      getLink: response => {
        return JSON.parse(response).Link;
      },
    },
    drivehub: {
      method: 'GET',
      getLink: response => {
        let parser = new DOMParser();
        let html = parser.parseFromString(response, 'text/html');
        return html.getElementById('proceed').href;
      },
    },
    upfileUs: {
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
        'X-Requested-With': 'XMLHttpRequest',
      },
      data: 'type=file_check',
      getLink: response => {
        console.log(response);
        return JSON.parse(response).debug[0].file_source;
      },
    },
  };

  if (upfileVn !== null) {
    linkDown.upfileVn.url = upfileVn.href;
    linkDown.upfileVn.data = 'Token=' + sha256(upfileVn.href.split('://')[1].split('/')[1] + '7891').toUpperCase();
  }
  if (upfileUs !== null) linkDown.upfileUs.url = upfileUs.href;
  if (drivehub !== null) linkDown.drivehub.url = drivehub.href;

  let $videoFrame = document.createElement('video');
  $videoFrame.controls = true;
  $videoFrame.setAttribute('preload', 'auto');
  $videoFrame.setAttribute('width', '100%');
  const getVideo = type => {
    GM.xmlHttpRequest({
      url: linkDown[type].url,
      method: linkDown[type].method || 'POST',
      headers: linkDown[type].headers,
      data: linkDown[type].data,
      onload: response => {
        linkDown[type].link = linkDown[type].getLink(response.response);
        if ($videoFrame.canPlayType('video/mp4')) $videoFrame.setAttribute('src', linkDown[type].link);
        console.log(linkDown[type].link);
        if (document.getElementById('video-frame') !== null) {
          document.getElementById('video-frame').appendChild($videoFrame);
        }
      },
    });
  };
  let listServer = [];
  Object.keys(linkDown).forEach((btnSwitchServer, index) => {
    let serverLi = document.createElement('LI');
    let serverBtn = document.createElement('A');

    serverBtn.textContent = `SERVER ${index + 1}`;
    serverBtn.className = 'button-server';
    serverBtn.setAttribute('data-type', btnSwitchServer);
    serverLi.appendChild(serverBtn);
    listServer.push(serverLi);
  });

  let $listViewBtn = document.querySelector('.uk-accordion');

  if ($listViewBtn !== null) {
    let $videoTemplate = `

    `;
    let $videoFrame = document.querySelector('.xem-phim.uk-panel');
    if (document.querySelector('.xem-phim.uk-panel > ul') === null) {
      $videoFrame.insertAdjacentHTML('beforeend', $videoTemplate);
      let navSelectServer = document.querySelector('.uk-subnav.uk-subnav-pill');
      listServer.forEach(sv => {
        navSelectServer.appendChild(sv);
      });
    }
    getVideo('upfileVn');
    $listViewBtn.addEventListener('click', e => {
      e.preventDefault();
      if (e.target.classList.contains('button-server')) {
        getVideo(e.target.dataset.type);
      }
    });
  }

  //
  document.oncontextmenu = null;
  if (shortcut) {
    shortcut.remove('Ctrl+U');
    shortcut.remove('F12');
    shortcut.remove('Ctrl+Shift+I');
    shortcut.remove('Ctrl+S');
    shortcut.remove('Ctrl+Shift+C');
  }

  let $btn = document.querySelector('.button-phim'),
    link = document.querySelector('.fb-comments');

  if (link === null) return;
  link = link.dataset.href;
  if (link.indexOf('/phim/') === -1) return;

  if ($btn === null) {
    $btn = document.createElement('A');
    $btn.textContent = 'Tải Phim VIP';
    document.querySelector('.button-info').appendChild($btn);
  }

  $btn.className = 'button-phim uk-button uk-button-secondary uk-dropdown-right';
  $btn.setAttribute('target', '_top');

  $btn.href = link;
})();
<commit_msg>Complete function of Watch VIP video online<commit_after>// ==UserScript==
// @name            Phim4400 Helper
// @name:vi         Phim4400 Helper
// @namespace       https://lelinhtinh.github.io
// @description     Xem online và tải phim trực tiếp tại Phim4400, bỏ qua quảng cáo.
// @description:vi  Xem online và tải phim trực tiếp tại Phim4400, bỏ qua quảng cáo.
// @version         1.2.0
// @icon            https://i.imgur.com/wRRkkqr.png
// @author          lelinhtinh
// @oujs:author     baivong
// @license         MIT; https://baivong.mit-license.org/license.txt
// @match           https://phim440.cf/*
// @match           https://phim4400.cf/*
// @match           https://phim4400.tv/*
// @require         https://cdnjs.cloudflare.com/ajax/libs/js-sha256/0.9.0/sha256.min.js
// @require         https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js?v=a834d46
// @noframes
// @supportURL      https://github.com/lelinhtinh/Userscript/issues
// @run-at          document-idle
// @grant           GM.xmlHttpRequest
// @grant           GM_xmlhttpRequest
// ==/UserScript==

/* global shortcut, sha256 */
(function() {
  'use strict';

  document.oncontextmenu = null;
  if (shortcut) {
    shortcut.remove('Ctrl+U');
    shortcut.remove('F12');
    shortcut.remove('Ctrl+Shift+I');
    shortcut.remove('Ctrl+S');
    shortcut.remove('Ctrl+Shift+C');
  }

  if (location.pathname.indexOf('/phim/') === -1) {
    let $btn = document.querySelector('.button-phim'),
      link = document.querySelector('.fb-comments');

    if (link === null) return;
    link = link.dataset.href;
    if (link.indexOf('/phim/') === -1) return;

    if ($btn === null) {
      $btn = document.createElement('a');
      $btn.textContent = 'Xem Online và Tải Phim';
      document.querySelector('.button-info').appendChild($btn);
    }

    $btn.className = 'button-phim uk-button uk-button-secondary uk-dropdown-right';
    $btn.setAttribute('target', '_top');

    $btn.href = link;
    return;
  }

  const $videoWrap = document.querySelector('.xem-phim');
  if ($videoWrap.innerHTML.trim() !== '') return;

  function renderVideo(src) {
    let $video = document.querySelector('#vipPlayer');

    if ($video === null) {
      $video = document.createElement('video');
      $video.id = 'vipPlayer';
      $video.controls = true;
      $video.preload = 'auto';
      $video.setAttribute(
        'style',
        `
          height: 450px;
          width: 100%;
        `
      );
      $video.addEventListener('error', () => {
        $content.innerHTML = `

              <big>Không thể phát video lúc này!</big>

              Hãy thử chuyển sang server khác.

          `;
      });

      $content.innerHTML = '';
      $content.appendChild($video);
    }

    $video.src = src;
    $video.play();
  }

  function getVideo(host) {
    if (hostConfigs[host].src) {
      const $aActive = $tabs.querySelector('li.uk-active a');
      if ($aActive !== null && $aActive.dataset.host === host) return;

      renderVideo(hostConfigs[host].src);
      return;
    }

    if (host === 'upfileVn') {
      hostConfigs.upfileVn.data =
        'Token=' + sha256(hostConfigs.upfileVn.href.split('://')[1].split('/')[1] + '7891').toUpperCase();
    }

    GM.xmlHttpRequest({
      url: hostConfigs[host].href,
      method: hostConfigs[host].method || 'POST',
      headers: hostConfigs[host].headers,
      data: hostConfigs[host].data,

      onload: response => {
        let src = hostConfigs[host].getSrc(response.response);
        hostConfigs[host].src = src;
        renderVideo(src);
      },
    });
  }

  function playVideo(e) {
    e.preventDefault();
    e.stopPropagation();

    const $this = e.target,
      $active = $tabs.querySelector('li.uk-active');

    getVideo($this.dataset.host);

    if ($active !== null) $active.classList.remove('uk-active');
    $this.parentNode.classList.add('uk-active');
  }

  const hostConfigs = {
    upfileVn: {
      selector: 'a[href*="upfile.vn"]',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
      },
      getSrc: response => {
        return JSON.parse(response).Link;
      },
    },
    drivehub: {
      selector: 'a[href*="drivehub.link"]',
      method: 'GET',
      getSrc: response => {
        const parser = new DOMParser();
        const $html = parser.parseFromString(response, 'text/html');
        const $proceed = $html.querySelector('#proceed');

        return $proceed === null ? null : $proceed.href.replace(/\?e=download$/, '');
      },
    },
    upfileUs: {
      selector: 'a[href*="upfile.us"]',
      headers: {
        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
        'X-Requested-With': 'XMLHttpRequest',
      },
      data: 'type=file_check',
      getSrc: response => {
        return JSON.parse(response).debug[0].file_source;
      },
    },
  };

  const $tabs = document.createElement('ul');
  $tabs.className = 'uk-subnav uk-subnav-pill';

  let hostCount = 0;
  Object.keys(hostConfigs).forEach(host => {
    const currHost = hostConfigs[host];
    const $btn = document.querySelector('.uk-accordion-content ' + currHost.selector);
    if ($btn === null) return;
    hostConfigs[host].href = $btn.href;

    const li = document.createElement('li');
    const a = document.createElement('a');

    a.textContent = 'Server ' + ++hostCount;
    a.href = '#' + host;
    a.setAttribute('data-host', host);
    a.addEventListener('click', playVideo);

    li.appendChild(a);
    $tabs.appendChild(li);
  });

  const $contents = document.createElement('ul');
  $contents.className = 'uk-switcher uk-margin';

  const $content = document.createElement('li');
  $content.className = 'uk-active';
  $content.setAttribute(
    'style',
    `
      background: #000;
      border: 1px solid #6fb429;
      border-top: 0px solid #0000;
      box-sizing: border-box;
    `
  );
  $contents.appendChild($content);

  $videoWrap.appendChild($tabs);
  $videoWrap.appendChild($contents);

  $tabs.querySelector('a').click();
})();
<|endoftext|>


<commit_before>$(function() {
	var support_popup = 'width=780,height=460,toolbar=0,menubar=0,location=0,status=1,scrollbars=1,resizable=0';
	$('.support_popup').click(function() {
			//Get the destination URL and the class popup specs
			var popurl = $(this).attr('href');
			var popupSpecs = $(this).attr('class');
			//Create a "unique" name for the window using a random number
			var popupName = Math.floor(Math.random()*10000001);
			//Opens the pop-up window according to the specified specs
			newwindow=window.open(popurl,popupName,eval(popupSpecs));
			return false;
	});
})<commit_msg>tag test<commit_after>$(function() {
	var support_popup = 'width=780,height=460,toolbar=0,menubar=0,location=0,status=1,scrollbars=1,resizable=0';
	$('.support_popup').click(function() {
			//Get the destination URL and the class popup specs
			var popurl = $(this).attr('href');
			var popupSpecs = $(this).attr('class');
			//Create a "unique" name for the window using a random number
			var popupName = Math.floor(Math.random()*10000001);
			//Opens the pop-up window according to the specified specs
			newwindow=window.open(popurl,popupName,eval(popupSpecs));
			return false;
	});
})<|endoftext|>


<commit_before>//  avosion - Lightweight browser redirector for those who are addicted to the web of ideas
//  Copyright (C) 2017 David Ryack
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as published
//  by the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.

//  You should have received a copy of the GNU Affero General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>

// Catch requests before the browser sends them
chrome.webRequest.onBeforeRequest.addListener(
    function(details) {
        var url = details.url;

        return archiveUrlConstructor(url);
    },
    // TODO load from a simple config file - hardcoding is terrible
    {
        urls: [ "*://*.washingtonpost.com/*",
                "*://*.wapo.com/*",
                "*://*.arstechnica.com/*",
                "*://*.buzzfeed.com/*",
                "*://*.nytimes.com/*",
                "*://*.pjmedia.com/*",
                "*://*.amazon.com/*",
                "*://*.foxnews.com/*",
                "*://*.heatst.com/*",
                "*://*.dailykos.com/*",
                "*://*.gamespot.com/*",
                "*://*.espn.com/*",
                "*://*.variety.com/*",
                "*://*.abcnews/*",
                "*://*.abc.net.au/*",
                "*://*.ausgamers.com/*",
                "*://*.breitbart.com/*",
                "*://*.dailycaller.com/*",
                "*://*.dailymail.co.uk/*",
                "*://*.independent.co.uk/*",
                "*://*.msnbc.com/*",
                "*://*.nbcnews.com/*",
                "*://*.newyorker.com/*",
                "*://*.returnofkings.com/*",
                "*://*.rollingstones.com/*",
                "*://*.slate.com/*",
                "*://*.telegraph.co.uk/*",
                "*://*.yahoo.com/news/*",
                "*://*.zerohedge.com/*",
                "*://*.aftonbladet.se/*",
                "*://*.avclub.com/*",
                "*://*.birthmoviesdeath.com/*",
                "*://*.boingboing.net/*",
                "*://*.buzzfeed.com/*",
                "*://*.cracked.com/*",
                "*://*.dailydot.com/*",
                "*://*.dailystormer.is/*",
                "*://*.deadspin.com/*",
                "*://*.destructoid.com/*",
                "*://*.engadget.com/*",
                "*://*.fusion.net/*",
                "*://*.gamasutra.com/*",
                "*://*.gameplanet.co.nz/*",
                "*://*.gamesindustry.biz/*",
                "*://*.gawker.com/*",
                "*://*.gizmodo.com/*",
                "*://*.huffingtonpost.com/*",
                "*://*.io9.com/*",
                "*://*.infowars.com/*",
                "*://*.jalopnik.com/*",
                "*://*.jezebel.com/*",
                "*://*.kinja.com/*",
                "*://*.kotaku.com/*",
                "*://*.mediamatters.org/*",
                "*://*.motherjones.com/*",
                "*://*.neogaf.com/*",
                "*://*.newmediarockstars.com/*",
                "*://*.nymag.com/*",
                "*://*.offworld.com/*",
                "*://*.pcauthority.com.au/*",
                "*://*.pcgamer.com/*",
                "*://*.pointandclickbait.com/*",
                "*://*.polygon.com/*",
                "*://*.conservapedia.com/*",
                "*://*.rationalwiki.org/*",
                "*://*.rawstory.com/*",
                "*://*.recode.net/*",
                "*://*.rockpapershotgun.com/*",
                "*://*.salon.com/*",
                "*://*.splinternews.com/*",
                "*://*.takimag.com/*",
                "*://*.theblumpkin.com/*",
                "*://*.thedailybeast.com/*",
                "*://*.thegatewaypundit.com/*",
                "*://*.littlegreenfootballs.com/*",
                "*://*.theguardian.com/*",
                "*://*.themarysue.com/*",
                "*://*.theroot.com/*",
                "*://*.theverge.com/*",
                "*://*.vice.com/*",
                "*://*.vox.com/*",
                "*://*.wehuntedthemammoth.com/*",
                "*://*.wired.com/*",
                "*://*.houstonpress.com/*",
                "*://*.sfist.com/*",
                "*://*.laist.com/*",
                "*://*.gothamist.com/*",
                "*://*.worldstarhiphop.com/*",
                "*://*.latimes.com/*",
                "*://*.sfgate.com/*",
                "*://*.sfchronicle.com/*",
                "*://*.seattlepi.com/*",
                "*://*.cnn.com/*"
        ]
    },
    ['blocking']
);

// Build the archive.is request url
function archiveUrlConstructor(url){
    var archiver = 'https://archive.is/?run=1&url=';
    // TODO do i want to leave this stuff hardcoded for ease of use, or do I want to make it configurable per website?
    // possibly a combination where the user can configure websites they know of...  { "website"
    var pjmedia_singlepage = '?singlepage=true'; // avoid the irritating More button
    var pjmediaRegex = new RegExp(/(pjmedia\.com)/); // detect we're on pjmedia site
    var amazonReferralRegex = new RegExp(/(^.*amazon\.com.*(\/ref=|tag=|-19|-20|-21|-22|-23))/);
    var wlAmazonRegex = new RegExp(/(.*amazon\.com)/);

    if(url.match(amazonReferralRegex)){
        var finalUrl = url.split("?");

        // stop the *://*amazon.com/*/ref= crap
        if(finalUrl[0].match(/(\/ref=)/)) {
            finalUrl = url.split("/ref=");
            console.log("finalUrl.match() '/ref='");
            console.info(finalUrl);
        }

        console.info(finalUrl);
        return { redirectUrl: finalUrl[0] }; //FIXME appears to be falling through
    }   // amazon de-referral

    if(url.match(pjmediaRegex)) {
        console.info("hitting pjmedia");
        return {redirectUrl: archiver + url + pjmedia_singlepage};
    } // avoid pjmedia More button bullshit

    if(url.match(wlAmazonRegex)) { //TODO can probably be handled at the onBeforeRequest filter stage above
        return url;
    } // stop fucking with 'normal' amazon pages

    // send to archiver
    console.info("hitting standard archiver build");
    return {redirectUrl: archiver + url};  //FIXME amazon is borked when coming from archiver page - haven't examined the logic to think about this yet
}
// FEATURE Configure preferred archiver

// FEATURE Flag(?) to avoid amazon referrers

// FEATURE allow whitelisting amazon refferers
// assume referrers should be stopped

// FEATURE (doubtful) look for alternate links for youtube vids
// even if possible without more pain than i'm willing to accept, starting to go outside the original scope i'd intended

<commit_msg>comment out search tracking cleaning<commit_after>//  avosion - Lightweight browser redirector for those who are addicted to the web of ideas
//  Copyright (C) 2017 David Ryack
//
//  This program is free software: you can redistribute it and/or modify
//  it under the terms of the GNU Affero General Public License as published
//  by the Free Software Foundation, either version 3 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU Affero General Public License for more details.

//  You should have received a copy of the GNU Affero General Public License
//  along with this program.  If not, see <http://www.gnu.org/licenses/>

// Catch requests before the browser sends them
chrome.webRequest.onBeforeRequest.addListener(
    function(details) {
        var url = details.url;

        return archiveUrlConstructor(url);
    },
    // TODO load from a simple config file - hardcoding is terrible
    {
        urls: [ "*://*.washingtonpost.com/*",
                "*://*.wapo.com/*",
                "*://*.arstechnica.com/*",
                "*://*.buzzfeed.com/*",
                "*://*.nytimes.com/*",
                "*://*.pjmedia.com/*",
                "*://*.amazon.com/*",
                "*://*.foxnews.com/*",
                "*://*.heatst.com/*",
                "*://*.dailykos.com/*",
                "*://*.gamespot.com/*",
                "*://*.espn.com/*",
                "*://*.variety.com/*",
                "*://*.abcnews/*",
                "*://*.abc.net.au/*",
                "*://*.ausgamers.com/*",
                "*://*.breitbart.com/*",
                "*://*.dailycaller.com/*",
                "*://*.dailymail.co.uk/*",
                "*://*.independent.co.uk/*",
                "*://*.msnbc.com/*",
                "*://*.nbcnews.com/*",
                "*://*.newyorker.com/*",
                "*://*.returnofkings.com/*",
                "*://*.rollingstones.com/*",
                "*://*.slate.com/*",
                "*://*.telegraph.co.uk/*",
                "*://*.yahoo.com/news/*",
                "*://*.zerohedge.com/*",
                "*://*.aftonbladet.se/*",
                "*://*.avclub.com/*",
                "*://*.birthmoviesdeath.com/*",
                "*://*.boingboing.net/*",
                "*://*.buzzfeed.com/*",
                "*://*.cracked.com/*",
                "*://*.dailydot.com/*",
                "*://*.dailystormer.is/*",
                "*://*.deadspin.com/*",
                "*://*.destructoid.com/*",
                "*://*.engadget.com/*",
                "*://*.fusion.net/*",
                "*://*.gamasutra.com/*",
                "*://*.gameplanet.co.nz/*",
                "*://*.gamesindustry.biz/*",
                "*://*.gawker.com/*",
                "*://*.gizmodo.com/*",
                "*://*.huffingtonpost.com/*",
                "*://*.io9.com/*",
                "*://*.infowars.com/*",
                "*://*.jalopnik.com/*",
                "*://*.jezebel.com/*",
                "*://*.kinja.com/*",
                "*://*.kotaku.com/*",
                "*://*.mediamatters.org/*",
                "*://*.motherjones.com/*",
                "*://*.neogaf.com/*",
                "*://*.newmediarockstars.com/*",
                "*://*.nymag.com/*",
                "*://*.offworld.com/*",
                "*://*.pcauthority.com.au/*",
                "*://*.pcgamer.com/*",
                "*://*.pointandclickbait.com/*",
                "*://*.polygon.com/*",
                "*://*.conservapedia.com/*",
                "*://*.rationalwiki.org/*",
                "*://*.rawstory.com/*",
                "*://*.recode.net/*",
                "*://*.rockpapershotgun.com/*",
                "*://*.salon.com/*",
                "*://*.splinternews.com/*",
                "*://*.takimag.com/*",
                "*://*.theblumpkin.com/*",
                "*://*.thedailybeast.com/*",
                "*://*.thegatewaypundit.com/*",
                "*://*.littlegreenfootballs.com/*",
                "*://*.theguardian.com/*",
                "*://*.themarysue.com/*",
                "*://*.theroot.com/*",
                "*://*.theverge.com/*",
                "*://*.vice.com/*",
                "*://*.vox.com/*",
                "*://*.wehuntedthemammoth.com/*",
                "*://*.wired.com/*",
                "*://*.houstonpress.com/*",
                "*://*.sfist.com/*",
                "*://*.laist.com/*",
                "*://*.gothamist.com/*",
                "*://*.worldstarhiphop.com/*",
                "*://*.latimes.com/*",
                "*://*.sfgate.com/*",
                "*://*.sfchronicle.com/*",
                "*://*.seattlepi.com/*",
                "*://*.cnn.com/*"
        ]
    },
    ['blocking']
);

// Build the archive.is request url
function archiveUrlConstructor(url){
    var archiver = 'https://archive.is/?run=1&url=';
    // TODO do i want to leave this stuff hardcoded for ease of use, or do I want to make it configurable per website?
    // possibly a combination where the user can configure websites they know of...  { "website"
    var pjmedia_singlepage = '?singlepage=true'; // avoid the irritating More button
    var pjmediaRegex = new RegExp(/(pjmedia\.com)/); // detect we're on pjmedia site
    var amazonReferralRegex = new RegExp(/(^.*amazon\.com.*(\/ref=|tag=|-19|-20|-21|-22|-23))/);
    var wlAmazonRegex = new RegExp(/(.*amazon\.com)/);

    if(url.match(amazonReferralRegex)){
        var finalUrl = url.split("?");

        // FIXME too aggressive, might need to go away permanently...
        // stop the *://*amazon.com/*/ref= crap
        //if(finalUrl[0].match(/(\/ref=)/)) {
        //    finalUrl = url.split("/ref=");
        //    console.log("finalUrl.match() '/ref='");
        //    console.info(finalUrl);
        //}

        console.info(finalUrl);
        return { redirectUrl: finalUrl[0] }; //FIXME appears to be falling through
    }   // amazon de-referral

    if(url.match(pjmediaRegex)) {
        console.info("hitting pjmedia");
        return {redirectUrl: archiver + url + pjmedia_singlepage};
    } // avoid pjmedia More button bullshit

    if(url.match(wlAmazonRegex)) { //TODO can probably be handled at the onBeforeRequest filter stage above
        return url;
    } // stop fucking with 'normal' amazon pages

    // send to archiver
    console.info("hitting standard archiver build");
    return {redirectUrl: archiver + url};  //FIXME amazon is borked when coming from archiver page - haven't examined the logic to think about this yet
}
// FEATURE Configure preferred archiver

// FEATURE Flag(?) to avoid amazon referrers

// FEATURE allow whitelisting amazon refferers
// assume referrers should be stopped

// FEATURE (doubtful) look for alternate links for youtube vids
// even if possible without more pain than i'm willing to accept, starting to go outside the original scope i'd intended

<|endoftext|>


<commit_before>import React, { PropTypes } from 'react';
import MDReactComponent from 'markdown-react-js';
import cx from 'classnames';

import s from './Markdown.css';

/* eslint-disable react/prop-types, react/no-multi-comp */

const headingRegExp = /^h(\d)$/;

function handleIterate(Tag, props, children) {
	// Increase the level of headings
	let m = Tag.match(headingRegExp);
	if (m) {
		Tag = 'h' + (Number(m[1]) + 2);
	}

	// Add class name with tag name to all tags:
	if (!props.className) {
		props.className = s[Tag];
	}

	return <Tag {...props}>{children}</Tag>;
}

export default function Markdown({
	text,
	className
}) {
	let classes = cx(s.root, className);
	return (
		<MDReactComponent
			text={text}
			className={classes}
			onIterate={handleIterate}
		/>
	);
}

Markdown.propTypes = {
	text: PropTypes.string.isRequired,
	className: PropTypes.string
};
<commit_msg>Unescape inline code blocks (#71).<commit_after>import React, { PropTypes } from 'react';
import MDReactComponent from 'markdown-react-js';
import cx from 'classnames';
import unescapeHtml from 'lodash/unescape';

import s from './Markdown.css';

/* eslint-disable react/prop-types, react/no-multi-comp */

const headingRegExp = /^h(\d)$/;

function handleIterate(Tag, props, children) {
	// Increase the level of headings
	let m = Tag.match(headingRegExp);
	if (m) {
		Tag = 'h' + (Number(m[1]) + 2);
	}

	// Add class name with tag name to all tags:
	if (!props.className) {
		props.className = s[Tag];
	}

	// Unescape inline code blocks
	if (Tag === 'code' && !props['data-language']) {
		children = children.map(unescapeHtml);
	}

	return <Tag {...props}>{children}</Tag>;
}

export default function Markdown({
	text,
	className
}) {
	let classes = cx(s.root, className);

	return (
		<MDReactComponent
			text={text}
			className={classes}
			onIterate={handleIterate}
		/>
	);
}

Markdown.propTypes = {
	text: PropTypes.string.isRequired,
	className: PropTypes.string
};
<|endoftext|>


<commit_before>function asyncToGeneratorNext (gen, resolve, reject, _next, _throw, key, arg) {
  try {
    const info = gen[key](arg)

    if (info.done) {
      resolve(info.value)
    } else {
      Promise.resolve(info.value).then(_next, _throw)
    }
  } catch (e) {
    reject(e)
  }
}

export default function asyncToGenerator (fn) {
  return function (...args) {
    return Promise((resolve, reject) => {
      const gen = fn.apply(this, args)
      const _next = (value) =>
        asyncToGeneratorNext(gen, resolve, reject, _next, _throw, 'next', value)
      const _throw = (error) =>
        asyncToGeneratorNext(gen, resolve, reject, _next, _throw, 'throw', error)

      _next()
    })
  }
}
<commit_msg>feat: 添加async实现<commit_after>/**
 * async函数的实现
 *
 * 1. async函数是Generator函数的语法糖，等同于自执行的Generator函数。
 * 2. async函数返回一个新的Promise对象。
 */

function asyncToGeneratorNext (gen, resolve, reject, _next, _throw, key, arg) {
  try {
    // 获取generator对象
    const info = gen[key](arg)

    if (info.done) {
      // 完成所有断点时，执行成功操作
      resolve(info.value)
    } else {
      // 递归调用断点，使用Promise，保持断点的连贯性
      Promise.resolve(info.value).then(_next, _throw)
    }
  } catch (e) {
    // 某个断点报错时，执行失败操作
    reject(e)
  }
}

export default function asyncToGenerator (fn) {
  return function (...args) {
    return Promise((resolve, reject) => {
      const gen = fn.apply(this, args)
      const _next = (value) =>
        asyncToGeneratorNext(gen, resolve, reject, _next, _throw, 'next', value)
      const _throw = (error) =>
        asyncToGeneratorNext(gen, resolve, reject, _next, _throw, 'throw', error)

      _next()
    })
  }
}
<|endoftext|>


<commit_before>'use strict';
(function(module) {
  try {
    module = angular.module('tink.sticktotop');
  } catch (e) {
    module = angular.module('tink.sticktotop', ['tink.navigation']);
  }
  module.directive('tinkSticky',['$timeout','$window','fixedCont',function ($timeout,$window,fixedCont) {
   return {
    restrict: 'A',
    link: function (scope, element, attrs) {
      var level = 1;
      if(attrs.tinkLevel){
        level = parseInt(attrs.tinkLevel);
      }
      fixedCont.register(element,level);
    }
  };

}]).factory('fixedCont',['$window','$timeout',function($window,$timeout){

  var padding;

  /*
  Trigger update function while scrolling
   */
  $timeout(function(){
    padding = parseInt($('body').css('padding-top'));

    angular.element($window).bind('scroll.sticky', function() {
      update();
    });

    angular.element($window).bind('touchstart.sticky', function() {
      update();
    });

    angular.element($window).bind('touchmove.sticky', function(event) {
      update();
    });

    update();
    calculateValues();
  },250);

  var components=[];
  var stickyList=[];

  /*
  Loop through this while scrolling
   */
  function update(){
    var scrollTop = getScrollTop();//console.log(scrollTop)
    var lengthC = components.length;

    // $('#valueTop').html(scrollTop);

    // Go through list of all components
    // value = scrollTop of current object
    components.forEach(function(value,key){

      // Compensation
      scrollTop += value.extra - value.trigger;

      // Check if we have a next/previous element
      var next = key+1 < lengthC;
      var prev = key-1 > -1;
      var element = $(value.elem); // current

      //if not in viewport, go to next element
      if(scrollTop > value.top+element.outerHeight(true) && scrollTop>value.stop){
        removeSticky(value);
        return;
      }

      // If (viewport + sticky elements on same level) > scrollTop >= current element's top
      // Make sticky (or remove sticky)
      if(scrollTop> value.top && scrollTop < value.stop){
        addSticky(value);
      }else{
        removeSticky(value);
      }
    });
  }

  /*
  Make element sticky
   */
  function addSticky(obj){
    if(obj && obj.elem){
      var elem = $(obj.elem);
      if(isSticky(obj)===-1){
        var topHeight = padding;
        if(obj.extra){
          topHeight+= obj.extra;
        }
        obj.dummy = createDummy(elem);
        elem.after(obj.dummy);
        makeSticky(elem,topHeight);
        stickyList.push(obj);
      }
    }
  }

  /*
  Remove sticky
   */
  function removeSticky(obj){
    if(obj && obj.elem){
      var elem = $(obj.elem);
      var stickyIndex = isSticky(obj);
      if(stickyIndex > -1){

        var sticky = stickyList[stickyIndex];
        sticky.dummy.remove();
        $(sticky.elem).removeClass('sticky');
        stickyList.splice(stickyIndex,1);
     //   console.log(stickyIndex,obj,stickyList);
      }
    }
  }

  /*
  Dummy element to prevent content from 'jumping'
   */
  function createDummy(elem){
    elem = $(elem);
    return $( '<div>' ).height(elem.outerHeight(true));
  }

  /*
  Runs on startup to calculate all necessary values
  To do: make it also run on viewport resize
   */
  function calculateValues(){
    var lengthC = components.length;

    components.forEach(function(value,key){
      var next = key+1 < lengthC;
      value.extra = 0;
      value.trigger = undefined;

      // console.log($(value.elem).get(0).id);

      // Loop through previous components in order to define 'extra' (var extra = top coordinate where block sits when sticky)
      for(var j=key; j>0; j--){

        var pre = components[j-1];

        if(value.trigger === undefined){
          value.trigger = pre.extra;
        }
        if(pre && pre.level < value.level){
          value.extra = pre.extra + $(pre.elem).outerHeight(true);
          break;
        } else if(pre && pre.level === value.level) {
          value.extra = pre.extra;
          break;
        }
        // console.log(value.extra);
      }
      // console.log("--------");
      if(next){
        for(var i=key+1;i<lengthC;i++){
          var nextv = components[i];
          if(value.level >= nextv.level){
            value.stop = nextv.top;
            break;
          }
        }
        if(value.stop === undefined){
          value.stop = $(document).height() - $(window).height();
        }if(value.trigger === undefined){
          value.trigger = 0;
        }
      }else{
        value.stop = $(document).height() - $(window).height();
      }

    });

    console.log(components);
  }

  /*
  Checks if element is sticky and if yes, at which position
   */
  function isSticky(obj){
    var index = -1;
    for (var i = stickyList.length; i--;) {
      var objNext = stickyList[i];
       if($(obj.elem).get(0) === $(objNext.elem).get(0)){
        index=i;
        break;
      }
    };
    return index;
  }

  /*
  Helper function to get scrollTop
   */
  function getScrollTop() {
    var prop = 'pageYOffset',
      method = 'scrollTop';
    return $window ? (prop in $window) ? $window[ prop ] :
      $window.document.documentElement[ method ] :
      $window.document.body[ method ];
  }

  /*
  Help function make sticky
   */
  function makeSticky(elem,top){
    elem = $(elem);
    elem.addClass('sticky');
    elem.css('top',top+'px');
  }

  // Create scope for factory
  var ctrl = {};

  ctrl.register= function(element,level){
    var nakedEl = $(element).get(0);
      components.push({elem: $(element),top:$(element).position().top,level:level});
      components = components.sort(function(a, b){
          a = parseInt(a.top);
          b = parseInt(b.top);
          return a - b;
      });
  }

  return ctrl;

}]);
})();
<commit_msg>Implementing pushing blocks code (WIP)<commit_after>'use strict';
(function(module) {
  try {
    module = angular.module('tink.sticktotop');
  } catch (e) {
    module = angular.module('tink.sticktotop', ['tink.navigation']);
  }
  module.directive('tinkSticky',['$timeout','$window','fixedCont',function ($timeout,$window,fixedCont) {
   return {
    restrict: 'A',
    link: function (scope, element, attrs) {
      var level = 1;
      if(attrs.tinkLevel){
        level = parseInt(attrs.tinkLevel);
      }
      fixedCont.register(element,level);
    }
  };

}]).factory('fixedCont',['$window','$timeout',function($window,$timeout){

  var padding;

  /*
  Trigger update function while scrolling
   */
  $timeout(function(){
    padding = parseInt($('body').css('padding-top'));

    angular.element($window).bind('scroll.sticky', function() {
      update();
    });

    angular.element($window).bind('touchstart.sticky', function() {
      update();
    });

    angular.element($window).bind('touchmove.sticky', function(event) {
      update();
    });

    update();
    calculateValues();
  },250);

  var components=[];
  var stickyList=[];

  /*
  Loop through this while scrolling
   */
  function update(){
    var scrollTop = getScrollTop();//console.log(scrollTop)
    var lengthC = components.length;

    $('#valueTop').html(scrollTop);

    // Go through list of all components
    // value = scrollTop of current object
    components.forEach(function(value, key) {

      // Compensation
      scrollTop += value.extra - value.trigger;

      // Check if we have a next/previous element
      var next = key+1 < lengthC;
      var prev = key-1 > -1;
      var element = $(value.elem); // current

      //if not in viewport, go to next element
      if(scrollTop > value.top+element.outerHeight(true) && scrollTop > value.stop){
        removeSticky(value);
        return;
      }

      // Push me!
      /*
        The element:
        - must be sticky
        - the succeeding element should not be sticky itself
        - the succeeding element should not be of a higher level
       */
      if(element.hasClass('sticky')){
        // if(!$(components[key+1].elem).hasClass('sticky')) {
          if(components[key+1].level <= value.level) {
            var totalHeight = 0;
            stickyList.forEach(function(val, ke) {
              totalHeight += $(val.elem).outerHeight(true);
            });
            totalHeight -= $(components[key+1].elem).outerHeight(true);
            // replace (element.outerHeight(true)) with (totalHeight)
            if((components[key+1].top - element.outerHeight(true)) < scrollTop) {
              // console.log(element.get(0).style.background);
              // console.log(components[key+1].level);
              // console.log('hit');
            }
          }
          // console.log(scrollTop + element.outerHeight(true));
          // console.log($(components[key+1].elem));
          // console.log(components[key+1].top);
        // }
      }
      // if(next){
      //   pushLevels(value, components[key+1], scrollTop);
      // }

      // If (viewport + sticky elements on same level) > scrollTop >= current element's top
      // Make sticky (or remove sticky)
      // var nextOutherHeight = $(components[key+1].elem).outerHeight(true);

      if(scrollTop > value.top && scrollTop < value.stop){
        addSticky(value);

        // if(isSticky(value)===-1){
        //   if(prev){
        //     addSticky(value);
        //     var prevOutherHeight = $(components[key-1].elem).outerHeight(true);
        //     if(components[key-1].level !== value.level){
        //       addSticky(value);
        //     }else if(scrollTop>value.top && scrollTop< value.top+prevOutherHeight){console.log(element.get(0).style.background)
        //       // console.log(element.get(0).style.background,scrollTop,value.top)
        //       var diff = value.top-scrollTop;

        //      // $(components[key-1].elem).css('top',components[key-1].extra+prevOutherHeight+diff+'px');
        //      // $(components[key-1].elem).css('z-index',0)
        //     }else{
        //       removeSticky(components[key-1]);
        //       addSticky(value);
        //     }
        //   }else{
        //    addSticky(value);
        //   }
        // }

      }else{
        removeSticky(value);
      }
    });
  }

  // function pushLevels(obj1, obj2, scrollTop) {

  // }

  /*
  Make element sticky
   */
  function addSticky(obj){
    if(obj && obj.elem){
      var elem = $(obj.elem);
      if(isSticky(obj)===-1){
        var topHeight = padding;
        if(obj.extra){
          topHeight+= obj.extra;
        }
        obj.dummy = createDummy(elem);
        elem.after(obj.dummy);
        makeSticky(elem,topHeight);
        stickyList.push(obj);
      }
    }
  }

  /*
  Remove sticky
   */
  function removeSticky(obj){
    if(obj && obj.elem){
      var elem = $(obj.elem);
      var stickyIndex = isSticky(obj);
      if(stickyIndex > -1){

        var sticky = stickyList[stickyIndex];
        sticky.dummy.remove();
        $(sticky.elem).removeClass('sticky');
        stickyList.splice(stickyIndex,1);
      }
    }
  }

  /*
  Dummy element to prevent content from 'jumping'
   */
  function createDummy(elem){
    elem = $(elem);
    return $( '<div>' ).height(elem.outerHeight(true));
  }

  /*
  Runs on startup to calculate all necessary values
  To do: make it also run on viewport resize
   */
  function calculateValues(){
    var lengthC = components.length;

    components.forEach(function(value,key){
      var next = key+1 < lengthC;
      value.extra = 0;
      value.trigger = undefined;

      // console.log($(value.elem).get(0).id);

      // Loop through previous components in order to define 'extra' (var extra = top coordinate where block sits when sticky)
      for(var j=key; j>0; j--){

        var pre = components[j-1];

        if(value.trigger === undefined){
          value.trigger = pre.extra;
        }
        if(pre && pre.level < value.level){
          value.extra = pre.extra + $(pre.elem).outerHeight(true);
          break;
        } else if(pre && pre.level === value.level) {
          value.extra = pre.extra;
          break;
        }
        // console.log(value.extra);
      }
      // console.log("--------");
      if(next){
        for(var i=key+1;i<lengthC;i++){
          var nextv = components[i];
          if(value.level >= nextv.level){
            value.stop = nextv.top;
            break;
          }
        }
        if(value.stop === undefined){
          value.stop = $(document).height() - $(window).height();
        }if(value.trigger === undefined){
          value.trigger = 0;
        }
      }else{
        value.stop = $(document).height() - $(window).height();
      }

    });

    console.log(components);
  }

  /*
  Checks if element is sticky and if yes, at which position
   */
  function isSticky(obj){
    var index = -1;
    for (var i = stickyList.length; i--;) {
      var objNext = stickyList[i];
       if($(obj.elem).get(0) === $(objNext.elem).get(0)){
        index=i;
        break;
      }
    };
    return index;
  }

  /*
  Helper function to get scrollTop
   */
  function getScrollTop() {
    var prop = 'pageYOffset',
      method = 'scrollTop';
    return $window ? (prop in $window) ? $window[ prop ] :
      $window.document.documentElement[ method ] :
      $window.document.body[ method ];
  }

  /*
  Help function make sticky
   */
  function makeSticky(elem,top){
    elem = $(elem);
    elem.addClass('sticky');
    elem.css('top',top+'px');
  }

  // Create scope for factory
  var ctrl = {};

  ctrl.register= function(element,level){
    var nakedEl = $(element).get(0);
      components.push({elem: $(element),top:$(element).position().top,level:level});
      components = components.sort(function(a, b){
          a = parseInt(a.top);
          b = parseInt(b.top);
          return a - b;
      });
  }

  return ctrl;

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