<commit_before>import { fromJS } from 'immutable';
import { handleActions } from 'redux-actions';
import { actions, DEFAULT_POLLING_INTERVAL } from '../constants';

const initialState = fromJS({
  polling: false,
  pollUrl: null,
  error: null,
  interval: DEFAULT_POLLING_INTERVAL,
  captchaRequired: false,
});

const startPolling = (state, { payload }) => {
  if (payload && payload.url) {
    return state.merge({ polling: true, pollUrl: payload.url });
  }

  return state.set('polling', true);
};

const stopPolling = (state) => (
  state.set('polling', false)
);

const setPollingInterval = (state, { payload }) => {
  if (typeof payload !== 'number') {
    return state.set('error',
                     new Error(`setPollingInterval called with an incorrect payload: ${payload}`));
  }

  return state.set('interval', payload);
};

const setPollUrl = (state, { payload }) => {
  if (typeof payload !== 'string') {
    return state.set('error',
                     new Error(`setPollUrl called with an incorrect payload: ${payload}`));
  }

  return state.set('pollUrl', payload);
};

const captchaRequired = (state) => (
  state.set('captchaRequired', true)
);

const rateLimitExceeded = (state) => {
  let newState = (state.get('interval') + 1000);
  return state.set('interval', newState); // keep adding 1 second till we are not rate limited
};

// Seems as good a way to clear "captcha required" as any
const iconClicked = (state) => (
  state.set('captchaRequired', false)
);

const polling = handleActions({
  [actions.START_POLLING]: startPolling,
  [actions.STOP_POLLING]: stopPolling,
  [actions.SET_POLLING_INTERVAL]: setPollingInterval,
  [actions.SET_POLL_URL]: setPollUrl,
  [actions.ASSIGN_WORK]: stopPolling,
  [actions.CAPTCHA_REQUIRED]: captchaRequired,
  [actions.RATE_LIMIT_EXCEEDED]: rateLimitExceeded,
  [actions.ICON_CLICKED]: iconClicked,
}, initialState);

export default polling;
<commit_msg>time increase bump<commit_after>import { fromJS } from 'immutable';
import { handleActions } from 'redux-actions';
import { actions, DEFAULT_POLLING_INTERVAL } from '../constants';

const initialState = fromJS({
  polling: false,
  pollUrl: null,
  error: null,
  interval: DEFAULT_POLLING_INTERVAL,
  captchaRequired: false,
});

const startPolling = (state, { payload }) => {
  if (payload && payload.url) {
    return state.merge({ polling: true, pollUrl: payload.url });
  }

  return state.set('polling', true);
};

const stopPolling = (state) => (
  state.set('polling', false)
);

const setPollingInterval = (state, { payload }) => {
  if (typeof payload !== 'number') {
    return state.set('error',
                     new Error(`setPollingInterval called with an incorrect payload: ${payload}`));
  }

  return state.set('interval', payload);
};

const setPollUrl = (state, { payload }) => {
  if (typeof payload !== 'string') {
    return state.set('error',
                     new Error(`setPollUrl called with an incorrect payload: ${payload}`));
  }

  return state.set('pollUrl', payload);
};

const captchaRequired = (state) => (
  state.set('captchaRequired', true)
);

const rateLimitExceeded = (state) => {
  const newState = (state.get('interval') + (3 * 1000));
  return state.set('interval', newState); // keep adding 3 second till we are not rate limited
};

// Seems as good a way to clear "captcha required" as any
const iconClicked = (state) => (
  state.set('captchaRequired', false)
);

const polling = handleActions({
  [actions.START_POLLING]: startPolling,
  [actions.STOP_POLLING]: stopPolling,
  [actions.SET_POLLING_INTERVAL]: setPollingInterval,
  [actions.SET_POLL_URL]: setPollUrl,
  [actions.ASSIGN_WORK]: stopPolling,
  [actions.CAPTCHA_REQUIRED]: captchaRequired,
  [actions.RATE_LIMIT_EXCEEDED]: rateLimitExceeded,
  [actions.ICON_CLICKED]: iconClicked,
}, initialState);

export default polling;
<|endoftext|>


<commit_before>import assign from 'object-assign';
import dashCase from '../util/dash-case';
import data from '../util/data';
import empty from '../util/empty';

// TODO Split apart createNativePropertyDefinition function.

function getLinkedAttribute (name, attr) {
  return attr === true ? dashCase(name) : attr;
}

function createNativePropertyDefinition (name, opts) {
  let prop = {
    configurable: true,
    enumerable: true
  };

  prop.created = function (elem, initialValue) {
    let info = data(elem, `api/property/${name}`);
    info.linkedAttribute = getLinkedAttribute(name, opts.attribute);
    info.removeAttribute = elem.removeAttribute;
    info.setAttribute = elem.setAttribute;
    info.updatingProperty = false;

    if (typeof opts.default === 'function') {
      info.defaultValue = opts.default(elem);
    } else if (!empty(opts.default)) {
      info.defaultValue = opts.default;
    }

    // TODO Refactor
    if (info.linkedAttribute) {
      if (!info.attributeMap) {
        info.attributeMap = {};

        elem.removeAttribute = function (attrName) {
          info.updatingAttribute = true;
          info.removeAttribute.call(this, attrName);

          if (attrName in info.attributeMap) {
            const propertyName = info.attributeMap[attrName];
            elem[propertyName] = undefined;
          }

          info.updatingAttribute = false;
        };

        elem.setAttribute = function (attrName, attrValue) {
          info.updatingAttribute = true;
          info.setAttribute.call(this, attrName, attrValue);

          if (attrName in info.attributeMap) {
            const propertyName = info.attributeMap[attrName];
            attrValue = String(attrValue);
            elem[propertyName] = opts.deserialize(attrValue);
          }

          info.updatingAttribute = false;
        };
      }

      info.attributeMap[info.linkedAttribute] = name;
    }

    if (initialValue === undefined) {
      if (info.linkedAttribute && elem.hasAttribute(info.linkedAttribute)) {
        let attributeValue = elem.getAttribute(info.linkedAttribute);
        initialValue = opts.deserialize(attributeValue);
      } else {
        initialValue = info.defaultValue;
      }
    }

    info.internalValue = initialValue;

    if (typeof opts.created === 'function') {
      opts.created(elem, initialValue);
    }
  };

  prop.get = function () {
    const info = data(this, `api/property/${name}`);
    const internalValue = info.internalValue;

    if (opts.get) {
      return opts.get(this, { name, internalValue });
    }

    return internalValue;
  };

  prop.init = function () {
    const info = data(this, `api/property/${name}`);
    const init = info.internalValue;
    this[name] = typeof init === 'undefined' ? this[name] : init;
  };

  prop.set = function (newValue) {
    let info = data(this, `api/property/${name}`);
    let oldValue;

    if (info.updatingProperty) {
      return;
    }

    info.updatingProperty = true;

    if (typeof opts.coerce === 'function') {
      newValue = opts.coerce(newValue);
    }

    info.internalValue = empty(newValue) ? info.defaultValue : newValue;

    if (info.linkedAttribute && !info.updatingAttribute) {
      let serializedValue = opts.serialize(newValue);
      if (empty(serializedValue)) {
        info.removeAttribute.call(this, info.linkedAttribute);
      } else {
        info.setAttribute.call(this, info.linkedAttribute, serializedValue);
      }
    }

    let changeData = {
      name: name,
      newValue: newValue,
      oldValue: oldValue
    };

    if (typeof opts.set === 'function') {
      opts.set(this, changeData);
    }

    info.updatingProperty = false;
  };

  return prop;
}

export default function (opts) {
  opts = opts || {};

  if (typeof opts === 'function') {
    opts = { coerce: opts };
  }

  return function (name) {
    return createNativePropertyDefinition(name, assign({
      deserialize: value => value,
      serialize: value => value
    }, opts));
  };
}
<commit_msg>Abstract out data getting with namespace.<commit_after>import assign from 'object-assign';
import dashCase from '../util/dash-case';
import data from '../util/data';
import empty from '../util/empty';

// TODO Split apart createNativePropertyDefinition function.

function getData (elem, name) {
  return data(elem, `api/property/${name}`);
}

function getLinkedAttribute (name, attr) {
  return attr === true ? dashCase(name) : attr;
}

function createNativePropertyDefinition (name, opts) {
  let prop = {
    configurable: true,
    enumerable: true
  };

  prop.created = function (elem, initialValue) {
    let info = getData(elem, name);
    info.linkedAttribute = getLinkedAttribute(name, opts.attribute);
    info.removeAttribute = elem.removeAttribute;
    info.setAttribute = elem.setAttribute;
    info.updatingProperty = false;

    if (typeof opts.default === 'function') {
      info.defaultValue = opts.default(elem);
    } else if (!empty(opts.default)) {
      info.defaultValue = opts.default;
    }

    // TODO Refactor
    if (info.linkedAttribute) {
      if (!info.attributeMap) {
        info.attributeMap = {};

        elem.removeAttribute = function (attrName) {
          info.updatingAttribute = true;
          info.removeAttribute.call(this, attrName);

          if (attrName in info.attributeMap) {
            const propertyName = info.attributeMap[attrName];
            elem[propertyName] = undefined;
          }

          info.updatingAttribute = false;
        };

        elem.setAttribute = function (attrName, attrValue) {
          info.updatingAttribute = true;
          info.setAttribute.call(this, attrName, attrValue);

          if (attrName in info.attributeMap) {
            const propertyName = info.attributeMap[attrName];
            attrValue = String(attrValue);
            elem[propertyName] = opts.deserialize(attrValue);
          }

          info.updatingAttribute = false;
        };
      }

      info.attributeMap[info.linkedAttribute] = name;
    }

    if (initialValue === undefined) {
      if (info.linkedAttribute && elem.hasAttribute(info.linkedAttribute)) {
        let attributeValue = elem.getAttribute(info.linkedAttribute);
        initialValue = opts.deserialize(attributeValue);
      } else {
        initialValue = info.defaultValue;
      }
    }

    info.internalValue = initialValue;

    if (typeof opts.created === 'function') {
      opts.created(elem, initialValue);
    }
  };

  prop.get = function () {
    const info = getData(this, name);
    const internalValue = info.internalValue;

    if (opts.get) {
      return opts.get(this, { name, internalValue });
    }

    return internalValue;
  };

  prop.init = function () {
    const info = getData(this, name);
    const init = info.internalValue;
    this[name] = typeof init === 'undefined' ? this[name] : init;
  };

  prop.set = function (newValue) {
    const info = getData(this, name);
    let oldValue;

    if (info.updatingProperty) {
      return;
    }

    info.updatingProperty = true;

    if (typeof opts.coerce === 'function') {
      newValue = opts.coerce(newValue);
    }

    info.internalValue = empty(newValue) ? info.defaultValue : newValue;

    if (info.linkedAttribute && !info.updatingAttribute) {
      let serializedValue = opts.serialize(newValue);
      if (empty(serializedValue)) {
        info.removeAttribute.call(this, info.linkedAttribute);
      } else {
        info.setAttribute.call(this, info.linkedAttribute, serializedValue);
      }
    }

    let changeData = {
      name: name,
      newValue: newValue,
      oldValue: oldValue
    };

    if (typeof opts.set === 'function') {
      opts.set(this, changeData);
    }

    info.updatingProperty = false;
  };

  return prop;
}

export default function (opts) {
  opts = opts || {};

  if (typeof opts === 'function') {
    opts = { coerce: opts };
  }

  return function (name) {
    return createNativePropertyDefinition(name, assign({
      deserialize: value => value,
      serialize: value => value
    }, opts));
  };
}
<|endoftext|>


<commit_before>(function(exports) {
    "use strict";

    var Stage = new Class({
        initialize: function(redrawFunc) {
            this._redrawFunc = redrawFunc;
            this._needsRedraw = new Region();
            this._triggeredRedraw = false;

            this._actors = [];
        },

        $addActor: function(actor) {
            this._actors.push(actor);
        },

        $removeActor: function(actor) {
            var idx = this._actors.indexOf(actor);
            this._actors.splice(idx, 1);
        },

        queueRedraw: function(geometry) {
            this._needsRedraw.union_rect(this._needsRedraw, geometry.x, geometry.y, geometry.width, geometry.height);
            if (!this._triggeredRedraw) {
                this._redrawFunc();
                this._triggeredRedraw = true;
            }
        },

        draw: function(ctx) {
            if (this._triggeredRedraw) {
                CanvasUtil.pathFromRegion(ctx, this._needsRedraw);
                ctx.clip();
                ctx.beginPath();
            }

            this._actors.forEach(function(actor) {
                actor.draw(ctx);
            });

            this._needsRedraw.clear();
            this._triggeredRedraw = false;
        },
    });

    var WindowActor = new Class({
        initialize: function(stage, server, windowId) {
            this._stage = stage;
            this._stage.$addActor(this);

            var connection = server.connect();
            this._port = connection.clientPort;
            this._port.addEventListener("message", function(messageEvent) {
                this._handleEvent(messageEvent.data);
            }.bind(this));
            this._display = connection.display;

            this._windowId = windowId;
            this._display.selectInput({ windowId: this._windowId,
                                        events: ["StructureNotify", "SubstructureNotify", "Damage"] });

            this._geometry = this._display.getGeometry({ drawableId: this._windowId });
            this._stage.queueRedraw(this._geometry);
        },

        _destroy: function() {
            this._display.disconnect();

            this._stage.$removeActor(this);
            this._stage.queueRedraw(this._geometry);
            this._stage = null;
        },

        _setNewGeometry: function(newGeometry) {
            // Redraw where the window was
            this._stage.queueRedraw(this._geometry);

            if (newGeometry.x !== undefined)
                this._geometry.x = newGeometry.x;
            if (newGeometry.y !== undefined)
                this._geometry.y = newGeometry.y;
            if (newGeometry.width !== undefined)
                this._geometry.width = newGeometry.width;
            if (newGeometry.height !== undefined)
                this._geometry.height = newGeometry.height;

            // Redraw where the window is
            this._stage.queueRedraw(this._geometry);
        },

        _handleEvent: function(event) {
            if (event.windowId != this._windowId)
                return;

            switch (event.type) {
            case "Damage":
                return this._stage.queueRedraw(this._geometry);
            case "ConfigureNotify":
                return this._setNewGeometry(event);
            case "UnmapNotify":
                return this._destroy();
            }
        },

        draw: function(ctx) {
            var pixmapId = this._display.nameWindowPixmap({ windowId: this._windowId });
            var image = this._display.getPixmapImage({ pixmapId: pixmapId });
            ctx.save();
            ctx.translate(this._geometry.x, this._geometry.y);

            var bg = this._display.getProperty({ windowId: this._windowId, name: 'BACKGROUND' });
            if (!bg)
                ctx.globalAlpha = 0.5;

            ctx.drawImage(image, 0, 0);
            ctx.restore();
            this._display.freePixmap({ drawableId: pixmapId });
        },
    });

    var CompositingManager = new Class({
        initialize: function(server) {
            this._server = server;

            var connection = server.connect();
            this._port = connection.clientPort;
            this._port.addEventListener("message", function(messageEvent) {
                this._handleEvent(messageEvent.data);
            }.bind(this));
            this._display = connection.display;

            this._stage = new Stage(this._onRedraw.bind(this));

            this._display.selectInput({ windowId: this._display.rootWindowId,
                                        events: ["SubstructureNotify", "Expose"] });
            var query = this._display.queryTree({ windowId: this._display.rootWindowId });
            query.children.forEach(this._addWindow.bind(this));
        },

        _onRedraw: function() {
            this._display.invalidateWindow({ windowId: this._display.rootWindowId,
                                             includeChildren: true });
        },

        _addWindow: function(windowId) {
            var attrs = this._display.getAttributes({ windowId: windowId });
            if (attrs.mapState != "Viewable")
                return;

            this._display.redirectWindow({ windowId: windowId, mode: "manual" });
            var actor = new WindowActor(this._stage, this._server, windowId);
        },

        _mapNotify: function(event) {
            var windowId = event.windowId;
            this._addWindow(windowId);
        },

        _draw: function() {
            this._display.drawTo(this._display.rootWindowId, function(ctx) {
                this._stage.draw(ctx);
            }.bind(this));
        },

        _handleEvent: function(event) {
            switch (event.type) {
            case "MapNotify":
                return this._mapNotify(event);
            case "Expose":
                if (event.count == 0)
                    return this._draw();
            }
        },
    });

    exports.CompositingManager = CompositingManager;

})(window);
<commit_msg>cm: Allow passing in a toplevel window ID<commit_after>(function(exports) {
    "use strict";

    var Stage = new Class({
        initialize: function(redrawFunc) {
            this._redrawFunc = redrawFunc;
            this._needsRedraw = new Region();
            this._triggeredRedraw = false;

            this._actors = [];
        },

        $addActor: function(actor) {
            this._actors.push(actor);
        },

        $removeActor: function(actor) {
            var idx = this._actors.indexOf(actor);
            this._actors.splice(idx, 1);
        },

        queueRedraw: function(geometry) {
            this._needsRedraw.union_rect(this._needsRedraw, geometry.x, geometry.y, geometry.width, geometry.height);
            if (!this._triggeredRedraw) {
                this._redrawFunc();
                this._triggeredRedraw = true;
            }
        },

        draw: function(ctx) {
            if (this._triggeredRedraw) {
                CanvasUtil.pathFromRegion(ctx, this._needsRedraw);
                ctx.clip();
                ctx.beginPath();
            }

            this._actors.forEach(function(actor) {
                actor.draw(ctx);
            });

            this._needsRedraw.clear();
            this._triggeredRedraw = false;
        },
    });

    var WindowActor = new Class({
        initialize: function(stage, server, windowId) {
            this._stage = stage;
            this._stage.$addActor(this);

            var connection = server.connect();
            this._port = connection.clientPort;
            this._port.addEventListener("message", function(messageEvent) {
                this._handleEvent(messageEvent.data);
            }.bind(this));
            this._display = connection.display;

            this._windowId = windowId;
            this._display.selectInput({ windowId: this._windowId,
                                        events: ["StructureNotify", "SubstructureNotify", "Damage"] });

            this._geometry = this._display.getGeometry({ drawableId: this._windowId });
            this._stage.queueRedraw(this._geometry);
        },

        _destroy: function() {
            this._display.disconnect();

            this._stage.$removeActor(this);
            this._stage.queueRedraw(this._geometry);
            this._stage = null;
        },

        _setNewGeometry: function(newGeometry) {
            // Redraw where the window was
            this._stage.queueRedraw(this._geometry);

            if (newGeometry.x !== undefined)
                this._geometry.x = newGeometry.x;
            if (newGeometry.y !== undefined)
                this._geometry.y = newGeometry.y;
            if (newGeometry.width !== undefined)
                this._geometry.width = newGeometry.width;
            if (newGeometry.height !== undefined)
                this._geometry.height = newGeometry.height;

            // Redraw where the window is
            this._stage.queueRedraw(this._geometry);
        },

        _handleEvent: function(event) {
            if (event.windowId != this._windowId)
                return;

            switch (event.type) {
            case "Damage":
                return this._stage.queueRedraw(this._geometry);
            case "ConfigureNotify":
                return this._setNewGeometry(event);
            case "UnmapNotify":
                return this._destroy();
            }
        },

        draw: function(ctx) {
            var pixmapId = this._display.nameWindowPixmap({ windowId: this._windowId });
            var image = this._display.getPixmapImage({ pixmapId: pixmapId });
            ctx.save();
            ctx.translate(this._geometry.x, this._geometry.y);

            var bg = this._display.getProperty({ windowId: this._windowId, name: 'BACKGROUND' });
            if (!bg)
                ctx.globalAlpha = 0.5;

            ctx.drawImage(image, 0, 0);
            ctx.restore();
            this._display.freePixmap({ drawableId: pixmapId });
        },
    });

    var CompositingManager = new Class({
        initialize: function(server, toplevelWindowId) {
            this._server = server;

            var connection = server.connect();
            this._port = connection.clientPort;
            this._port.addEventListener("message", function(messageEvent) {
                this._handleEvent(messageEvent.data);
            }.bind(this));
            this._display = connection.display;

            this._stage = new Stage(this._onRedraw.bind(this));

            this._toplevelWindowId = toplevelWindowId;
            this._display.selectInput({ windowId: toplevelWindowId,
                                        events: ["SubstructureNotify", "Expose"] });
            var query = this._display.queryTree({ windowId: toplevelWindowId });
            query.children.forEach(this._addWindow.bind(this));
        },

        _onRedraw: function() {
            this._display.invalidateWindow({ windowId: this._toplevelWindowId,
                                             includeChildren: true });
        },

        _addWindow: function(windowId) {
            var attrs = this._display.getAttributes({ windowId: windowId });
            if (attrs.mapState != "Viewable")
                return;

            this._display.redirectWindow({ windowId: windowId, mode: "manual" });
            var actor = new WindowActor(this._stage, this._server, windowId);
        },

        _mapNotify: function(event) {
            var windowId = event.windowId;
            this._addWindow(windowId);
        },

        _draw: function() {
            this._display.drawTo(this._toplevelWindowId, function(ctx) {
                this._stage.draw(ctx);
            }.bind(this));
        },

        _handleEvent: function(event) {
            switch (event.type) {
            case "MapNotify":
                return this._mapNotify(event);
            case "Expose":
                if (event.count == 0)
                    return this._draw();
            }
        },
    });

    exports.CompositingManager = CompositingManager;

})(window);
<|endoftext|>


<commit_before>new Webos.ScriptFile('usr/lib/webos/config.js');

Webos.Theme = function WTheme(configFile) {
	var data = configFile.data();

	var defaults = {
		desktop: '',
		icons: '',
		background: '',
		animations: 0
	};
	data = $.extend({}, defaults, data);

	if (typeof data.animations == 'string') {
		data.animations = parseInt(data.animations) || 0;
	}

	Webos.Model.call(this, data);

	this._configFile = configFile;
};
Webos.Theme.prototype = {
	load: function(callback) {
		callback = Webos.Callback.toCallback(callback);
		var that = this;

		new W.ServerCall({
			'class': 'ThemeController',
			'method': 'loadCss',
			'arguments': {
				'theme': that.get('desktop'),
				'ui': Webos.UserInterface.Booter.current().name()
			}
		}).load(new Webos.Callback(function(response) {
			that._setAnimations();
			var css = response.getData().css;
			for (var index in css) {
				Webos.Stylesheet.insertCss(css[index], '#'+W.UserInterface.Booter.current().element().attr('id'));
			}
			that._loadBackground();
			Webos.Theme._current = that;
			callback.success();
		}, callback.error));
	},
	applyBackgroundOn: function(el) {
		var $el = $(el);

		var bgImg = Webos.File.get(this.get('background')).get('realpath'),
		bgColor = this.get('backgroundColor'),
		bgRepeat = this.get('backgroundRepeat'),
		bgCover = (bgRepeat == 'no-repeat') ? true : false;

		if (this.get('hideBackground')) {
			$el.css('background-color', bgColor);
			return;
		}

		$el.css('background', 'url("'+bgImg+'") '+bgRepeat+' center center '+bgColor);

		if (bgCover) {
			$el
				.css('-webkit-background-size', 'cover')
				.css('-moz-background-size', 'cover')
				.css('-o-background-size', 'cover')
				.css('background-size', 'cover')
				.css('filter', 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\''+bgImg+'\', sizingMethod=\'scale\')')
				.css('-ms-filter', '"progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\''+bgImg+'\', sizingMethod=\'scale\')"');
		}
	},
	_loadBackground: function() {
		this.applyBackgroundOn(Webos.UserInterface.Booter.current().element());
	},
	_setAnimations: function() {
		$.fx.off = !this.get('animations');
	},
	background: function() {
		var bg = this._get('background');
		if (!bg) {
			bg = Webos.Theme.defaultBackground();
		}
		return bg;
	},
	backgroundColor: function() {
		return this._get('backgroundColor') || 'black';
	},
	backgroundRepeat: function() {
		return this._get('backgroundRepeat') || 'no-repeat';
	},
	hideBackground: function() {
		return (this._get('hideBackground') ? true : false);
	},
	setDesktop: function(value) {
		this._set('desktop', String(value));
		return true;
	},
	setBackground: function(value) {
		value = String(value);
		if (value == Webos.Theme.defaultBackground()) {
			value = '';
		}
		this._set('background', value);
		return true;
	},
	setIcons: function(value) {
		this._set('icons', String(value));
		return true;
	},
	setAnimations: function(value) {
		this._set('animations', (value) ? 1 : 0);
		return true;
	},
	sync: function(callback) {
		callback = Webos.Callback.toCallback(callback);

		var that = this;

		var data = {}, nbrChanges = 0;
		for (var key in this._unsynced) {
			if (this._unsynced[key].state === 1) {
				this._unsynced[key].state = 2;
				data[key] = this._unsynced[key].value;
				this._configFile.set(key, data[key]);
				nbrChanges++;
			}
		}

		if (nbrChanges == 0) {
			callback.success(this);
			return;
		}

		this._configFile.sync([function() {
			for (var key in that._unsynced) {
				if (that._unsynced[key].state === 2) {
					that._data[key] = that._unsynced[key].value;
					delete that._unsynced[key];

					switch (key) {
						case 'background':
							that._loadBackground();
							break;
						case 'animations':
							that._setAnimations();
							break;
					}

					that.notify('update', { key: key, value: that._data[key].value });
				}
			}
			callback.success(that);
		}, callback.error]);
	}
};
Webos.inherit(Webos.Theme, Webos.Model);

Webos.Theme._current = null;
Webos.Theme.current = function() {
	return Webos.Theme._current || new Webos.Theme(Webos.ConfigFile.get('~/.theme/'+Webos.UserInterface.Booter.current().name()+'/config.xml'));
};
Webos.Theme._defaultBackground = 'usr/share/images/backgrounds/default.jpg';
Webos.Theme.defaultBackground = function() {
	return Webos.Theme._defaultBackground;
};
Webos.Theme.setDefaultBackground = function(bg) {
	Webos.Theme._defaultBackground = bg;
};

Webos.Theme.get = function(callback) {
	callback = Webos.Callback.toCallback(callback);

	var ui = Webos.UserInterface.Booter.current().name();
	Webos.ConfigFile.loadUserConfig('~/.theme/'+ui+'/config.xml', '/usr/etc/uis/'+ui+'/config.xml', [function(config) {
		var theme = new Webos.Theme(config);
		callback.success(theme);
	}, callback.error]);
};

Webos.Theme.backgroundsDir = '/usr/share/images/backgrounds/';
Webos.Theme.iconsDir = '/usr/share/icons/themes/';
Webos.Theme.getAvailable = function(module, callback) {
	callback = Webos.Callback.toCallback(callback);

	new W.ServerCall({
		'class': 'ThemeController',
		'method': 'getAvailable',
		'arguments': {
			'ui': W.UserInterface.Booter.current().name()
		}
	}).load(new Webos.Callback(function(response) {
		callback.success(response.getData());
	}, function(response) {
		callback.error(response);
	}));
};<commit_msg>Optimizes Webos.Theme : now doesn't load the same background twice.<commit_after>new Webos.ScriptFile('usr/lib/webos/config.js');

Webos.Theme = function WTheme(configFile) {
	var data = configFile.data();

	var defaults = {
		desktop: '',
		icons: '',
		background: '',
		animations: 0
	};
	data = $.extend({}, defaults, data);

	if (typeof data.animations == 'string') {
		data.animations = parseInt(data.animations) || 0;
	}

	Webos.Model.call(this, data);

	this._configFile = configFile;
};
Webos.Theme.prototype = {
	load: function(callback) {
		callback = Webos.Callback.toCallback(callback);
		var that = this;

		new W.ServerCall({
			'class': 'ThemeController',
			'method': 'loadCss',
			'arguments': {
				'theme': that.get('desktop'),
				'ui': Webos.UserInterface.Booter.current().name()
			}
		}).load(new Webos.Callback(function(response) {
			that._setAnimations();
			var css = response.getData().css;
			for (var index in css) {
				Webos.Stylesheet.insertCss(css[index], '#'+W.UserInterface.Booter.current().element().attr('id'));
			}
			that._loadBackground();
			Webos.Theme._current = that;
			callback.success();
		}, callback.error));
	},
	applyBackgroundOn: function(el) {
		var $el = $(el);

		var bgImg = Webos.File.get(this.get('background')).get('realpath'),
		bgColor = this.get('backgroundColor'),
		bgRepeat = this.get('backgroundRepeat'),
		bgCover = (bgRepeat == 'no-repeat') ? true : false;

		if (this.get('hideBackground')) {
			$el.css('background-color', bgColor);
			return;
		}

		$el.css('background', 'url("'+bgImg+'") '+bgRepeat+' center center '+bgColor);

		if (bgCover) {
			$el
				.css('-webkit-background-size', 'cover')
				.css('-moz-background-size', 'cover')
				.css('-o-background-size', 'cover')
				.css('background-size', 'cover')
				.css('filter', 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\''+bgImg+'\', sizingMethod=\'scale\')')
				.css('-ms-filter', '"progid:DXImageTransform.Microsoft.AlphaImageLoader(src=\''+bgImg+'\', sizingMethod=\'scale\')"');
		}
	},
	_loadBackground: function() {
		if (Webos.Theme._current) {
			if (Webos.Theme._current.get('background') == this.get('background')) {
				return;
			}
		}

		this.applyBackgroundOn(Webos.UserInterface.Booter.current().element());
	},
	_setAnimations: function() {
		$.fx.off = !this.get('animations');
	},
	background: function() {
		var bg = this._get('background');
		if (!bg) {
			bg = Webos.Theme.defaultBackground();
		}
		return bg;
	},
	backgroundColor: function() {
		return this._get('backgroundColor') || 'black';
	},
	backgroundRepeat: function() {
		return this._get('backgroundRepeat') || 'no-repeat';
	},
	hideBackground: function() {
		return (this._get('hideBackground') ? true : false);
	},
	setDesktop: function(value) {
		this._set('desktop', String(value));
		return true;
	},
	setBackground: function(value) {
		value = String(value);
		if (value == Webos.Theme.defaultBackground()) {
			value = '';
		}
		this._set('background', value);
		return true;
	},
	setIcons: function(value) {
		this._set('icons', String(value));
		return true;
	},
	setAnimations: function(value) {
		this._set('animations', (value) ? 1 : 0);
		return true;
	},
	sync: function(callback) {
		callback = Webos.Callback.toCallback(callback);

		var that = this;

		var data = {}, nbrChanges = 0;
		for (var key in this._unsynced) {
			if (this._unsynced[key].state === 1) {
				this._unsynced[key].state = 2;
				data[key] = this._unsynced[key].value;
				this._configFile.set(key, data[key]);
				nbrChanges++;
			}
		}

		if (nbrChanges == 0) {
			callback.success(this);
			return;
		}

		this._configFile.sync([function() {
			for (var key in that._unsynced) {
				if (that._unsynced[key].state === 2) {
					that._data[key] = that._unsynced[key].value;
					delete that._unsynced[key];

					switch (key) {
						case 'background':
							that._loadBackground();
							break;
						case 'animations':
							that._setAnimations();
							break;
					}

					that.notify('update', { key: key, value: that._data[key].value });
				}
			}
			callback.success(that);
		}, callback.error]);
	}
};
Webos.inherit(Webos.Theme, Webos.Model);

Webos.Theme._current = null;
Webos.Theme.current = function() {
	return Webos.Theme._current || new Webos.Theme(Webos.ConfigFile.get('~/.theme/'+Webos.UserInterface.Booter.current().name()+'/config.xml'));
};
Webos.Theme._defaultBackground = '/usr/share/images/backgrounds/default.jpg';
Webos.Theme.defaultBackground = function() {
	return Webos.Theme._defaultBackground;
};
Webos.Theme.setDefaultBackground = function(bg) {
	Webos.Theme._defaultBackground = bg;
};

Webos.Theme.get = function(callback) {
	callback = Webos.Callback.toCallback(callback);

	var ui = Webos.UserInterface.Booter.current().name();
	Webos.ConfigFile.loadUserConfig('~/.theme/'+ui+'/config.xml', '/usr/etc/uis/'+ui+'/config.xml', [function(config) {
		var theme = new Webos.Theme(config);
		callback.success(theme);
	}, callback.error]);
};

Webos.Theme.backgroundsDir = '/usr/share/images/backgrounds/';
Webos.Theme.iconsDir = '/usr/share/icons/themes/';
Webos.Theme.getAvailable = function(module, callback) {
	callback = Webos.Callback.toCallback(callback);

	new W.ServerCall({
		'class': 'ThemeController',
		'method': 'getAvailable',
		'arguments': {
			'ui': W.UserInterface.Booter.current().name()
		}
	}).load(new Webos.Callback(function(response) {
		callback.success(response.getData());
	}, function(response) {
		callback.error(response);
	}));
};<|endoftext|>


<commit_before>var urlParams;

$(function() {
  resizeWindow();
  parseUrlParameters();
  authenticate();
  check_for_existing_bookmark_details();
  FastClick.attach(document.body);
  setUpFormSubmission();
  setUpTagAutoComplete();
  get_suggested_tags();
  update_user_tags();
  $('input#tags').focus();
  Ladda.bind('button[type=submit]');
});

/** Ensure window is tall enough to show all form elements. */
function resizeWindow() {
  var min_width = 600;
  var min_height = 700;
  if (window.outerHeight < min_height) {
    window.resizeTo(min_width, min_height);
  }

  if (document.addEventListener) {
    document.addEventListener('keydown', winclose, false);
  } else {
    document.attachEvent('onkeydown', winclose);
  }
}

/** Parse URL query parameters into urlParams hash. */
function parseUrlParameters() {
  (window.onpopstate = function () {
    var match,
        pl     = /\+/g,  // Regex for replacing addition symbol with a space
        search = /([^&=]+)=?([^&]*)/g,
        decode = function (s) { return decodeURIComponent(s.replace(pl, " ")); },
        query  = window.location.search.substring(1);

    urlParams = {};
    while (match = search.exec(query))
       urlParams[decode(match[1])] = decode(match[2]);
  })();

  /* Set form inputs to values passed via URL query parameters. */
  $('input#url').val(urlParams['url']);
  $('input#title').val(urlParams['title']);
  $('textarea#description').val(urlParams['description']);
  $('#suggestion_row, #suggested').hide(); // We’ll show it again later if there are any suggestions
}

function authenticate() {
  if (!(urlParams['user'] && urlParams['token'])) {
    alert("You must provide both ‘user’ and ‘token’ parameters to this page to allow it to use the Pinboard API.");
    $('#submit').addClass('fail');
    $('#submit').prop('disabled', true)
    $('#spinner').addClass('hidden');
  }
}

function auth_token() {
  return urlParams['user'] + ':' + urlParams['token'];
}

function serialized_inputs() {
  var serialized_inputs = $('#url, #title, #description, #tags').serialize();
  if ($('#private').prop('checked')) {
    serialized_inputs += '&shared=no';
  }
  if ($('#toread').prop('checked')) {
    serialized_inputs += '&toread=yes';
  }
  return serialized_inputs;
}

/** Check for pre-existing bookmark for this URL. */
function check_for_existing_bookmark_details() {
  if (!urlParams['url']) { return; }
  var bookmark_details_api = "https://pinboard-bridge.herokuapp.com/posts/get?format=json&auth_token=" + auth_token() + "&url=" + urlParams['url'];

  $.get(bookmark_details_api, 'json')
    .done(function(response) {
      if (response['posts'].length < 1) { return; }
      var bookmark = response['posts'][0];

      $('input#title').val(bookmark['description']);
      $('textarea#description').val(bookmark['extended']);
      $('input#tags').val(bookmark['tags']);

      if (bookmark['shared'] == 'no') {
        $('#private').prop('checked', true);
      }
      if (bookmark['toread'] == 'yes') {
        $('#toread').prop('checked', true);
      }
      if (bookmark['time']) {
        var date = new Date(bookmark['time']);
        $('#bookmark-status').text("Previously saved on " + date.getFullYear() + "/" + date.getMonth() + "/" + date.getDate());
      }
      $('#submit span.text').text('Update bookmark');
      $('#spinner').addClass('hidden');
    })

    .fail(function(response) {
      if (response.status == '401') {
        alert("401 Unauthorised. Please check your username and API access token.");
      }
    });
}

/** Submit the form with Ajax */
function setUpFormSubmission() {
  $('#post-to-pinboard').on('submit', function(event) {
    event.preventDefault();

    var post_bookmark_api = "https://pinboard-bridge.herokuapp.com/posts/add?format=json&auth_token=" + auth_token() + "&" + serialized_inputs();

    $.get(post_bookmark_api, 'json')
      .done(function(response) {
        if (response['result_code'] == 'done') {
          console.log("Bookmark saved correctly.");
          Ladda.stopAll();
          $('#submit').addClass('success');
          setTimeout(function() {
            window.close();
            $('#submit').removeClass('success'); // for windows that aren't popups
          }, 900);
        } else if (response['result_code'] == 'must provide title') {
          Ladda.stopAll();
          $('#submit').addClass('fail');
          setTimeout(function() {
            $('#submit').removeClass('fail'); // let user try again
          }, 1900);
          $('#title').focus();
        }
      })

      .fail(function(response) {
        Ladda.stopAll();
        $('#submit').addClass('fail');
        if (response.status == '401') {
          alert("401 Unauthorised. Please check your username and API access token.");
        }
      });
  });

  $('input, textarea').on('blur', function() {
    $('body').animate({ scrollTop: 0 }, 200);
  });
}

function setUpTagAutoComplete() {
  console.log('Setting up autocomplete.');

  $('input#tags').selectize({
    delimiter: ' ',
    create: true,
    openOnFocus: false,
    persist: false,
    maxItems: null,
    valueField: 'label',
    labelField: 'label',
    searchField: ['label', 'value'],
    options: [
      {label: "System Administrator", value: "1" },
      {label: "Software Tester", value: "3" },
      {label: "Software Developer", value: "4" },
      {label: "Senior Developer", value: "5" },
      {label: "Cloud Developer", value: "6" },
      {label: "Wordpress Designer", value: "7" },
    ]
  });
}

function get_suggested_tags() {
  if (!urlParams['url']) { return; }
  var suggested_tags_api = "https://pinboard-bridge.herokuapp.com/posts/suggest?format=json&url=" + urlParams['url'] + "&auth_token=" + auth_token();

  $('#spinner').removeClass('hidden');
  $.get(suggested_tags_api, function(data) {
    show_suggested_tags(data);
    $('#spinner').addClass('hidden');
  }, 'json');
}

function update_user_tags() {
  if (!localStorage.tags) {
    localStorage['tags'] = JSON.stringify([]);
    var all_tags_api = "https://pinboard-bridge.herokuapp.com/tags/get?format=json&auth_token=" + auth_token();

    $.get(all_tags_api, 'json')
      .done(function(response) {
        console.log(response);
        localStorage['tags'] = JSON.stringify(response);
        localStorage['tags-updated'] = new Date();
      })

      .fail(function(response) {
        if (response.status == '401') {
          alert("401 Unauthorised. Please check your username and API access token.");
        }
      });
  } else {
    console.log('Have tags');
    console.log(JSON.parse(localStorage['tags']));
  }
}

function prepareDatabase(ready, error) {
  return openDatabase('tags', '1.0', 'Local tag lookup cache', (50 * 1024 * 1024), function(db) {
      db.changeVersion('', '1.0', function (t) {
          t.executeSql('CREATE TABLE tagids (id, name)');
      }, error);
  });
}

function show_suggested_tags(tag_suggestions) {
  if (!tag_suggestions) { return; }
  tag_suggestions = tag_suggestions[0]['popular'].concat(tag_suggestions[1]['recommended']); // flatten JSON
  tag_suggestions = $.unique(tag_suggestions); // filter out duplicates
  tag_suggestions = removeSpuriousResults(tag_suggestions); // empty the array if they are the default/broken suggestions

  var suggested_tags = [];
  for (var i = 0; i < tag_suggestions.length; i++) {
    var suggested_tag = tag_suggestions[i];
    var escaped = pin_escape(suggested_tag);
    var cooked  = pin_cook(suggested_tag);
    var suggested_tag = '<button type="button" class="suggested_tag" onclick="add_tag(\''  +
                escaped + '\'); $(this).hide(); return false;">' + cooked + '</button>';
    suggested_tags.push(suggested_tag);
  }

  if (suggested_tags.length) {
    $('#suggested').append(suggested_tags.join(" "));
    $('#suggested').show(800);
  } else {
    $('#suggestion_row th').addClass('none').text('No tag suggestions for this page.');
    $('#suggestion_row').show();
  }
}

/** Remove default set of tags that are suggested by the Pinboard API when there are no good suggestions. */
function removeSpuriousResults(tag_suggestions) {
  if ($.inArray('facebook') && $.inArray('googlereader') && $.inArray('ifttt') && $.inArray('objective-c') &&
      $.inArray('twitter') && $.inArray('twitterlink') && $.inArray('WSH') && $.inArray('music') &&
      $.inArray('04:22PM') && $.inArray('1960s')) {
    return [];
  } else {
    return tag_suggestions;
  }
}

function pin_escape(s) {
  s = s.replace(/\\/g, "\\\\");
  s = s.replace(/'/g, "\\'"); // "
  s = s.replace(/"/g, '"'); // "
  return s;
}

function pin_cook(s) {
  s = s.replace(/</g, '<');
  s = s.replace(/>/g, '>');
  s = s.replace(/"/g, '"');  //"
  return s;
}

RegExp.escape = function(text) {
  return text.replace(/[-[\]{}()*+?.,\\\\'"^$|#\s]/g, "\\$&"); //"'
}

function add_tag(tag) {
  var field = $('#tags');
  var curr = field.val();
  var tag_regex = new RegExp( "(\\b|\\s)" + RegExp.escape(tag) + "(\\b|\\s)");
  if (curr.match(tag_regex) === null) {
    // TODO handle case when tag is at start of field
    field.val(field.val() + " " + tag);
  }
  return false;
}

function pin_sort(a,b) {
  var l = a.toLowerCase ? a.toLowerCase() : a;
  var r = b.toLowerCase ? b.toLowerCase() : b;
  if (l == r) { return 0; }
  if (l > r) { return 1; }
  return -1;
}

function winclose(e) {
  var code;
  if (e.keyCode) {
    code = e.keyCode;
  } else {
    if (e.which) {
      code = e.which;
    }
  }
  if (code == 27) {
    if (!(pin_tagcomplete && pin_tagcomplete.active())) {
      window.close();
    }
  }
}<commit_msg>Fix broken calculaton of spurious tags.<commit_after>var urlParams;

$(function() {
  resizeWindow();
  parseUrlParameters();
  authenticate();
  check_for_existing_bookmark_details();
  FastClick.attach(document.body);
  setUpFormSubmission();
  setUpTagAutoComplete();
  get_suggested_tags();
  update_user_tags();
  $('input#tags').focus();
  Ladda.bind('button[type=submit]');
});

/** Ensure window is tall enough to show all form elements. */
function resizeWindow() {
  var min_width = 600;
  var min_height = 700;
  if (window.outerHeight < min_height) {
    window.resizeTo(min_width, min_height);
  }

  if (document.addEventListener) {
    document.addEventListener('keydown', winclose, false);
  } else {
    document.attachEvent('onkeydown', winclose);
  }
}

/** Parse URL query parameters into urlParams hash. */
function parseUrlParameters() {
  (window.onpopstate = function () {
    var match,
        pl     = /\+/g,  // Regex for replacing addition symbol with a space
        search = /([^&=]+)=?([^&]*)/g,
        decode = function (s) { return decodeURIComponent(s.replace(pl, " ")); },
        query  = window.location.search.substring(1);

    urlParams = {};
    while (match = search.exec(query))
       urlParams[decode(match[1])] = decode(match[2]);
  })();

  /* Set form inputs to values passed via URL query parameters. */
  $('input#url').val(urlParams['url']);
  $('input#title').val(urlParams['title']);
  $('textarea#description').val(urlParams['description']);
  $('#suggestion_row, #suggested').hide(); // We’ll show it again later if there are any suggestions
}

function authenticate() {
  if (!(urlParams['user'] && urlParams['token'])) {
    alert("You must provide both ‘user’ and ‘token’ parameters to this page to allow it to use the Pinboard API.");
    $('#submit').addClass('fail');
    $('#submit').prop('disabled', true)
    $('#spinner').addClass('hidden');
  }
}

function auth_token() {
  return urlParams['user'] + ':' + urlParams['token'];
}

function serialized_inputs() {
  var serialized_inputs = $('#url, #title, #description, #tags').serialize();
  if ($('#private').prop('checked')) {
    serialized_inputs += '&shared=no';
  }
  if ($('#toread').prop('checked')) {
    serialized_inputs += '&toread=yes';
  }
  return serialized_inputs;
}

/** Check for pre-existing bookmark for this URL. */
function check_for_existing_bookmark_details() {
  if (!urlParams['url']) { return; }
  var bookmark_details_api = "https://pinboard-bridge.herokuapp.com/posts/get?format=json&auth_token=" + auth_token() + "&url=" + urlParams['url'];

  $.get(bookmark_details_api, 'json')
    .done(function(response) {
      if (response['posts'].length < 1) { return; }
      var bookmark = response['posts'][0];

      $('input#title').val(bookmark['description']);
      $('textarea#description').val(bookmark['extended']);
      $('input#tags').val(bookmark['tags']);

      if (bookmark['shared'] == 'no') {
        $('#private').prop('checked', true);
      }
      if (bookmark['toread'] == 'yes') {
        $('#toread').prop('checked', true);
      }
      if (bookmark['time']) {
        var date = new Date(bookmark['time']);
        $('#bookmark-status').text("Previously saved on " + date.getFullYear() + "/" + date.getMonth() + "/" + date.getDate());
      }
      $('#submit span.text').text('Update bookmark');
      $('#spinner').addClass('hidden');
    })

    .fail(function(response) {
      if (response.status == '401') {
        alert("401 Unauthorised. Please check your username and API access token.");
      }
    });
}

/** Submit the form with Ajax */
function setUpFormSubmission() {
  $('#post-to-pinboard').on('submit', function(event) {
    event.preventDefault();

    var post_bookmark_api = "https://pinboard-bridge.herokuapp.com/posts/add?format=json&auth_token=" + auth_token() + "&" + serialized_inputs();

    $.get(post_bookmark_api, 'json')
      .done(function(response) {
        if (response['result_code'] == 'done') {
          console.log("Bookmark saved correctly.");
          Ladda.stopAll();
          $('#submit').addClass('success');
          setTimeout(function() {
            window.close();
            $('#submit').removeClass('success'); // for windows that aren't popups
          }, 900);
        } else if (response['result_code'] == 'must provide title') {
          Ladda.stopAll();
          $('#submit').addClass('fail');
          setTimeout(function() {
            $('#submit').removeClass('fail'); // let user try again
          }, 1900);
          $('#title').focus();
        }
      })

      .fail(function(response) {
        Ladda.stopAll();
        $('#submit').addClass('fail');
        if (response.status == '401') {
          alert("401 Unauthorised. Please check your username and API access token.");
        }
      });
  });

  $('input, textarea').on('blur', function() {
    $('body').animate({ scrollTop: 0 }, 200);
  });
}

function setUpTagAutoComplete() {
  console.log('Setting up autocomplete.');

  $('input#tags').selectize({
    delimiter: ' ',
    create: true,
    openOnFocus: false,
    persist: false,
    maxItems: null,
    valueField: 'label',
    labelField: 'label',
    searchField: ['label', 'value'],
    options: [
      {label: "System Administrator", value: "1" },
      {label: "Software Tester", value: "3" },
      {label: "Software Developer", value: "4" },
      {label: "Senior Developer", value: "5" },
      {label: "Cloud Developer", value: "6" },
      {label: "Wordpress Designer", value: "7" },
    ]
  });
}

function get_suggested_tags() {
  if (!urlParams['url']) { return; }
  var suggested_tags_api = "https://pinboard-bridge.herokuapp.com/posts/suggest?format=json&url=" + urlParams['url'] + "&auth_token=" + auth_token();

  $('#spinner').removeClass('hidden');
  $.get(suggested_tags_api, function(data) {
    show_suggested_tags(data);
    $('#spinner').addClass('hidden');
  }, 'json');
}

function update_user_tags() {
  if (!localStorage.tags) {
    localStorage['tags'] = JSON.stringify([]);
    var all_tags_api = "https://pinboard-bridge.herokuapp.com/tags/get?format=json&auth_token=" + auth_token();

    $.get(all_tags_api, 'json')
      .done(function(response) {
        console.log(response);
        localStorage['tags'] = JSON.stringify(response);
        localStorage['tags-updated'] = new Date();
      })

      .fail(function(response) {
        if (response.status == '401') {
          alert("401 Unauthorised. Please check your username and API access token.");
        }
      });
  } else {
    console.log('Have tags');
    console.log(JSON.parse(localStorage['tags']));
  }
}

function prepareDatabase(ready, error) {
  return openDatabase('tags', '1.0', 'Local tag lookup cache', (50 * 1024 * 1024), function(db) {
      db.changeVersion('', '1.0', function (t) {
          t.executeSql('CREATE TABLE tagids (id, name)');
      }, error);
  });
}

function show_suggested_tags(tag_suggestions) {
  if (!tag_suggestions) { return; }
  tag_suggestions = tag_suggestions[0]['popular'].concat(tag_suggestions[1]['recommended']); // flatten JSON
  tag_suggestions = $.unique(tag_suggestions); // filter out duplicates
  tag_suggestions = removeSpuriousResults(tag_suggestions); // empty the array if they are the default/broken suggestions

  var suggested_tags = [];
  for (var i = 0; i < tag_suggestions.length; i++) {
    var suggested_tag = tag_suggestions[i];
    var escaped = pin_escape(suggested_tag);
    var cooked  = pin_cook(suggested_tag);
    var suggested_tag = '<button type="button" class="suggested_tag" onclick="add_tag(\''  +
                escaped + '\'); $(this).hide(); return false;">' + cooked + '</button>';
    suggested_tags.push(suggested_tag);
  }

  if (suggested_tags.length) {
    $('#suggested').append(suggested_tags.join(" "));
    $('#suggested').show(800);
  } else {
    $('#suggestion_row th').addClass('none').text('No tag suggestions for this page.');
    $('#suggestion_row').show();
  }
}

/** Remove default set of tags that are suggested by the Pinboard API when there are no good suggestions. */
function removeSpuriousResults(tag_suggestions) {
  if ($.inArray('facebook', tag_suggestions) >= 0 && $.inArray('googlereader', tag_suggestions) >= 0 &&
      $.inArray('ifttt', tag_suggestions) >= 0 && $.inArray('objective-c', tag_suggestions) >= 0 &&
      $.inArray('twitter', tag_suggestions) >= 0 && $.inArray('twitterlink', tag_suggestions) >= 0 &&
      $.inArray('WSH', tag_suggestions) >= 0 && $.inArray('music', tag_suggestions) >= 0 &&
      $.inArray('04:22PM', tag_suggestions) >= 0 && $.inArray('1960s', tag_suggestions) >= 0) {
    return [];
  } else {
    return tag_suggestions;
  }
}

function pin_escape(s) {
  s = s.replace(/\\/g, "\\\\");
  s = s.replace(/'/g, "\\'"); // "
  s = s.replace(/"/g, '"'); // "
  return s;
}

function pin_cook(s) {
  s = s.replace(/</g, '<');
  s = s.replace(/>/g, '>');
  s = s.replace(/"/g, '"');  //"
  return s;
}

RegExp.escape = function(text) {
  return text.replace(/[-[\]{}()*+?.,\\\\'"^$|#\s]/g, "\\$&"); //"'
}

function add_tag(tag) {
  var field = $('#tags');
  var curr = field.val();
  var tag_regex = new RegExp( "(\\b|\\s)" + RegExp.escape(tag) + "(\\b|\\s)");
  if (curr.match(tag_regex) === null) {
    // TODO handle case when tag is at start of field
    field.val(field.val() + " " + tag);
  }
  return false;
}

function pin_sort(a,b) {
  var l = a.toLowerCase ? a.toLowerCase() : a;
  var r = b.toLowerCase ? b.toLowerCase() : b;
  if (l == r) { return 0; }
  if (l > r) { return 1; }
  return -1;
}

function winclose(e) {
  var code;
  if (e.keyCode) {
    code = e.keyCode;
  } else {
    if (e.which) {
      code = e.which;
    }
  }
  if (code == 27) {
    if (!(pin_tagcomplete && pin_tagcomplete.active())) {
      window.close();
    }
  }
}<|endoftext|>


<commit_before>import React from 'react'
import {Link} from 'react-router'
import Immutable from 'immutable'

import UndoButton from './undoButton'
import RedoButton from './redoButton'

import SearchButton from './searchButton'
import GraduationStatus from './graduationStatus'

import studentActions from '../flux/studentActions'

let Sidebar = React.createClass({
	propTypes: {
		student: React.PropTypes.instanceOf(Immutable.Record).isRequired,
	},

	toggleSearch() {
		this.setState({isSearching: !this.state.isSearching})
	},

	getInitialState() {
		return {
			isSearching: false,
		}
	},

	render() {
		// console.log('Sidebar#render')
		let student = this.props.student

		let ActiveSidebarComponent = GraduationStatus
		let props = {student}
		if (this.state.isSearching) {
			ActiveSidebarComponent = SearchButton
			props.toggle = this.toggleSearch
		}

		let sidebar = (<aside className='sidebar'>
			<menu className='student-buttons'>
				<Link to='/' className='back sidebar-btn'>Students</Link>
				<button className='search sidebar-btn' onClick={this.toggleSearch}>Search</button>
				<Link className='sidebar-btn' to='download' params={{id: student.id}}>Download</Link>
				<button className='sidebar-btn'
					onClick={() => studentActions.resetStudentToDemo(student.id)}>
					Revert to Demo
				</button>
				<UndoButton className='sidebar-btn' />
				<RedoButton className='sidebar-btn' />
			</menu>
			<ActiveSidebarComponent {...props} />
		</aside>)

		return sidebar
	},
})

export default Sidebar
<commit_msg>always render both types of sidebar<commit_after>import React from 'react'
import {Link} from 'react-router'
import Immutable from 'immutable'

import UndoButton from './undoButton'
import RedoButton from './redoButton'

import SearchButton from './searchButton'
import GraduationStatus from './graduationStatus'

import studentActions from '../flux/studentActions'

let Sidebar = React.createClass({
	propTypes: {
		student: React.PropTypes.instanceOf(Immutable.Record).isRequired,
	},

	toggleSearch() {
		this.setState({isSearching: !this.state.isSearching})
	},

	getInitialState() {
		return {
			isSearching: false,
		}
	},

	render() {
		// console.log('Sidebar#render')

		return (<aside className='sidebar'>
			<menu className='student-buttons'>
				<Link to='/' className='back sidebar-btn'>Students</Link>
				<button className='search sidebar-btn' onClick={this.toggleSearch}>Search</button>
				<Link className='sidebar-btn' to='download' params={{id: this.props.student.id}}>Download</Link>
				<button className='sidebar-btn'
					onClick={() => studentActions.resetStudentToDemo(this.props.student.id)}>
					Revert to Demo
				</button>
				<UndoButton className='sidebar-btn' />
				<RedoButton className='sidebar-btn' />
			</menu>

			<GraduationStatus
				isHidden={this.state.isSearching}
				student={this.props.student} />

			<SearchButton
				isHidden={!this.state.isSearching}
				toggle={this.toggleSearch} />
		</aside>)
	}
}
<|endoftext|>


<commit_before>/**
 * @class Extension
 */

EZ3.Extension = function(gl) {
  this.elementIndexUInt = gl.getExtension('OES_element_index_uint');
  this.vertexArrayObject = gl.getExtension('OES_vertex_array_object');
  this.standardDerivates = gl.getExtension('OES_standard_derivatives');
};
<commit_msg>Updated Extensions.js<commit_after>/**
 * @class Extension
 */

EZ3.Extension = function(gl) {
  this.elementIndexUInt = gl.getExtension('OES_element_index_uint');
  this.vertexArrayObject = gl.getExtension('OES_vertex_array_object');
  this.standardDerivates = gl.getExtension('OES_standard_derivatives');
  this.shaderLOD = gl.getExtension('EXT_shader_texture_lod');
};
<|endoftext|>


<commit_before>var urlParams;

$(function() {
  resizeWindow();
  parseUrlParameters();
  authenticate();
  FastClick.attach(document.body);
  setUpFormSubmission();
  setUpTagAutoComplete();
  check_for_existing_bookmark_details();
  get_suggested_tags();
  update_user_tags();
  $('input#tags')[0].selectize.focus();
  Ladda.bind('button[type=submit]');
});

/** Ensure window is tall enough to show all form elements. */
function resizeWindow() {
  var min_width = 600;
  var min_height = 700;
  if (window.outerHeight < min_height) {
    window.resizeTo(min_width, min_height);
  }

  if (document.addEventListener) {
    document.addEventListener('keydown', winclose, false);
  } else {
    document.attachEvent('onkeydown', winclose);
  }
}

/** Parse URL query parameters into urlParams hash. */
function parseUrlParameters() {
  (window.onpopstate = function () {
    var match,
        pl     = /\+/g,  // Regex for replacing addition symbol with a space
        search = /([^&=]+)=?([^&]*)/g,
        decode = function (s) { return decodeURIComponent(s.replace(pl, " ")); },
        query  = window.location.search.substring(1);

    urlParams = {};
    while (match = search.exec(query))
       urlParams[decode(match[1])] = decode(match[2]);
  })();

  /* Set form inputs to values passed via URL query parameters. */
  $('input#url').val(urlParams['url']);
  $('input#title').val(urlParams['title']);
  $('textarea#description').val(urlParams['description']);
  $('#suggestion_row, #suggested').hide(); // We’ll show it again later if there are any suggestions
}

function authenticate() {
  if (!(urlParams['user'] && urlParams['token'])) {
    alert("You must provide both ‘user’ and ‘token’ parameters to this page to allow it to use the Pinboard API.");
    $('#submit').addClass('fail');
    $('#submit').prop('disabled', true)
    $('#spinner').addClass('hidden');
  }
}

function auth_token() {
  return urlParams['user'] + ':' + urlParams['token'];
}

function serialized_inputs() {
  var serialized_inputs = $('#url, #title, #description, #tags').serialize();
  if ($('#private').prop('checked')) {
    serialized_inputs += '&shared=no';
  }
  if ($('#toread').prop('checked')) {
    serialized_inputs += '&toread=yes';
  }
  return serialized_inputs;
}

/** Check for pre-existing bookmark for this URL. */
function check_for_existing_bookmark_details() {
  if (!urlParams['url']) { return; }
  var bookmark_details_api = "https://pinboard-bridge.herokuapp.com/posts/get?format=json&auth_token=" + auth_token() + "&url=" + urlParams['url'];

  $.get(bookmark_details_api, 'json')
    .done(function(response) {
      if (response['posts'].length < 1) { return; }
      var bookmark = response['posts'][0];

      $('input#title').val(bookmark['description']);
      $('textarea#description').val(bookmark['extended']);
      prepopulate_tags(bookmark['tags']);

      if (bookmark['shared'] == 'no') {
        $('#private').prop('checked', true);
      }
      if (bookmark['toread'] == 'yes') {
        $('#toread').prop('checked', true);
      }
      if (bookmark['time']) {
        var date = new Date(bookmark['time']);
        $('#bookmark-status').text("Previously saved on " + date.getFullYear() + "/" + date.getMonth() + "/" + date.getDate());
      }
      $('#submit span.text').text('Update bookmark');
      $('#spinner').addClass('hidden');
    })

    .fail(function(response) {
      if (response.status == '401') {
        alert("401 Unauthorised. Please check your username and API access token.");
      }
    });
}

/** Submit the form with Ajax */
function setUpFormSubmission() {
  $('#post-to-pinboard').on('submit', function(event) {
    event.preventDefault();

    var post_bookmark_api = "https://pinboard-bridge.herokuapp.com/posts/add?format=json&auth_token=" + auth_token() + "&" + serialized_inputs();

    $.get(post_bookmark_api, 'json')
      .done(function(response) {
        if (response['result_code'] == 'done') {
          console.log("Bookmark saved correctly.");
          Ladda.stopAll();
          $('#submit').addClass('success');
          setTimeout(function() {
            window.close();
            $('#submit').removeClass('success'); // for windows that aren't popups
          }, 900);
        } else if (response['result_code'] == 'must provide title') {
          Ladda.stopAll();
          $('#submit').addClass('fail');
          setTimeout(function() {
            $('#submit').removeClass('fail'); // let user try again
          }, 1900);
          $('#title').focus();
        }
      })

      .fail(function(response) {
        Ladda.stopAll();
        $('#submit').addClass('fail');
        if (response.status == '401') {
          alert("401 Unauthorised. Please check your username and API access token.");
        }
      });
  });

  $('input, textarea').on('blur', function() {
    $('body').animate({ scrollTop: 0 }, 200);
  });
}

function prepopulate_tags(tagString) {
  var tags = tagString.split(' ');
  for (var i = 0; i < tags.length; i++) {
    $('input#tags')[0].selectize.addOption({
      label: tags[i]
    });
    $('input#tags')[0].selectize.addItem(tags[i]);
  }
}

function setUpTagAutoComplete() {

  $('input#tags').selectize({
    delimiter: ' ',
    create: true,
    openOnFocus: false,
    maxOptions: 6,
    persist: false,
    hideSelected: true,
    diacritics: true,
    valueField: 'label',
    labelField: 'label',
    searchField: ['label'],
    plugins: ['restore_on_backspace'],
  });

  if (localStorage && localStorage['tags']) {
    var user_tags = JSON.parse(localStorage['tags']);
    console.log('Populating dropdown.');
    $.each(user_tags, function(key, value) {
      $('input#tags')[0].selectize.addOption({
        label: key
      });
    });
  }
}

function get_suggested_tags() {
  if (!urlParams['url']) { return; }
  var suggested_tags_api = "https://pinboard-bridge.herokuapp.com/posts/suggest?format=json&url=" + urlParams['url'] + "&auth_token=" + auth_token();

  $('#spinner').removeClass('hidden');
  $.get(suggested_tags_api, function(data) {
    show_suggested_tags(data);
    $('#spinner').addClass('hidden');
  }, 'json');
}

function update_user_tags() {
  if (!localStorage.tags) {
    console.log('Downloading user’s tags.');
    localStorage['tags'] = JSON.stringify([]);
    var all_tags_api = "https://pinboard-bridge.herokuapp.com/tags/get?format=json&auth_token=" + auth_token();

    $.get(all_tags_api, 'json')
      .done(function(response) {
        localStorage['tags'] = JSON.stringify(response);
        localStorage['tags-updated'] = new Date();
      })

      .fail(function(response) {
        if (response.status == '401') {
          alert("401 Unauthorised. Please check your username and API access token.");
        }
      });
  } else {
    console.log('Have tags already.');
  }
}

function show_suggested_tags(tag_suggestions) {
  if (!tag_suggestions) { return; }
  tag_suggestions = $.merge(tag_suggestions[0]['popular'], tag_suggestions[1]['recommended']); // flatten JSON
  tag_suggestions = tag_suggestions.map(function(tag) {
    return tag.toLowerCase(); // lowercase all tags
  });
  tag_suggestions = $.unique(tag_suggestions); // filter out duplicates
  $('#description')
  tag_suggestions = removeSpuriousResults(tag_suggestions); // empty the array if they are the default/broken suggestions
  tag_suggestions = removeOverlyCommonTags(tag_suggestions); // remove tags that appear very often across a wide range of pages

  var suggested_tags = [];
  for (var i = 0; i < tag_suggestions.length; i++) {
    var suggested_tag = tag_suggestions[i];
    var escaped = pin_escape(suggested_tag);
    var cooked  = pin_cook(suggested_tag);
    var suggested_tag = '<button type="button" class="suggested_tag" onclick="add_tag(\''  +
                escaped + '\'); $(this).hide(200); return false;">' + cooked + '</button>';
    suggested_tags.push(suggested_tag);
  }

  if (suggested_tags.length > 0) {
    $('#suggested').append(suggested_tags.join(" "));
    $('#suggestion_row').show();
    $('#suggested').show(800);
  } else {
    $('#suggestion_row th').addClass('none').text('No tag suggestions for this page.');
    $('#suggestion_row').show();
  }
}

/** Remove default set of tags that are suggested by the Pinboard API when there are no good suggestions. */
function removeSpuriousResults(tag_suggestions) {
  if ($.inArray('facebook', tag_suggestions) >= 0 && $.inArray('googlereader', tag_suggestions) >= 0 &&
      $.inArray('ifttt', tag_suggestions) >= 0 && $.inArray('objective-c', tag_suggestions) >= 0 &&
      $.inArray('twitter', tag_suggestions) >= 0 && $.inArray('twitterlink', tag_suggestions) >= 0 &&
      $.inArray('wsh', tag_suggestions) >= 0 && $.inArray('music', tag_suggestions) >= 0 &&
      $.inArray('04:22pm', tag_suggestions) >= 0 && $.inArray('1960s', tag_suggestions) >= 0) {
    return [];
  } else {
    return tag_suggestions;
  }
}

function removeOverlyCommonTags(tag_suggestions) {
  tag_suggestions = $.grep(tag_suggestions, function(tag, index) {
    tag = tag.toLowerCase();
    return (tag != 'bookmarks_bar' && tag != 'pin-later' && tag != 'unread' && tag != '*resources' &&
            tag != 'unlabeled' && tag != 'via:packrati.us' && tag != 'bookmarks_menu' && tag != 'from' &&
            tag != 'ifttt' && tag != 'later' && tag != 'saved' && tag != 'read' && tag != 'feedly' &&
            tag != 'for' && tag != 'recently' && tag != 'tobookmarks' && tag != 'from:ifttt');
  });
  return tag_suggestions;
}

function pin_escape(s) {
  s = s.replace(/\\/g, "\\\\");
  s = s.replace(/'/g, "\\'"); // "
  s = s.replace(/"/g, '"'); // "
  return s;
}

function pin_cook(s) {
  s = s.replace(/</g, '<');
  s = s.replace(/>/g, '>');
  s = s.replace(/"/g, '"');  //"
  return s;
}

RegExp.escape = function(text) {
  return text.replace(/[-[\]{}()*+?.,\\\\'"^$|#\s]/g, "\\$&"); //"'
}

function add_tag(tag) {
  $('input#tags')[0].selectize.addOption({
    label: tag
  });
  $('input#tags')[0].selectize.addItem(tag);
}

function pin_sort(a,b) {
  var l = a.toLowerCase ? a.toLowerCase() : a;
  var r = b.toLowerCase ? b.toLowerCase() : b;
  if (l == r) { return 0; }
  if (l > r) { return 1; }
  return -1;
}

function winclose(e) {
  var code;
  if (e.keyCode) {
    code = e.keyCode;
  } else {
    if (e.which) {
      code = e.which;
    }
  }
  if (code == 27) {
    if (!(pin_tagcomplete && pin_tagcomplete.active())) {
      window.close();
    }
  }
}<commit_msg>More ignored tag suggestions.<commit_after>var urlParams;

$(function() {
  resizeWindow();
  parseUrlParameters();
  authenticate();
  FastClick.attach(document.body);
  setUpFormSubmission();
  setUpTagAutoComplete();
  check_for_existing_bookmark_details();
  get_suggested_tags();
  update_user_tags();
  $('input#tags')[0].selectize.focus();
  Ladda.bind('button[type=submit]');
});

/** Ensure window is tall enough to show all form elements. */
function resizeWindow() {
  var min_width = 600;
  var min_height = 700;
  if (window.outerHeight < min_height) {
    window.resizeTo(min_width, min_height);
  }

  if (document.addEventListener) {
    document.addEventListener('keydown', winclose, false);
  } else {
    document.attachEvent('onkeydown', winclose);
  }
}

/** Parse URL query parameters into urlParams hash. */
function parseUrlParameters() {
  (window.onpopstate = function () {
    var match,
        pl     = /\+/g,  // Regex for replacing addition symbol with a space
        search = /([^&=]+)=?([^&]*)/g,
        decode = function (s) { return decodeURIComponent(s.replace(pl, " ")); },
        query  = window.location.search.substring(1);

    urlParams = {};
    while (match = search.exec(query))
       urlParams[decode(match[1])] = decode(match[2]);
  })();

  /* Set form inputs to values passed via URL query parameters. */
  $('input#url').val(urlParams['url']);
  $('input#title').val(urlParams['title']);
  $('textarea#description').val(urlParams['description']);
  $('#suggestion_row, #suggested').hide(); // We’ll show it again later if there are any suggestions
}

function authenticate() {
  if (!(urlParams['user'] && urlParams['token'])) {
    alert("You must provide both ‘user’ and ‘token’ parameters to this page to allow it to use the Pinboard API.");
    $('#submit').addClass('fail');
    $('#submit').prop('disabled', true)
    $('#spinner').addClass('hidden');
  }
}

function auth_token() {
  return urlParams['user'] + ':' + urlParams['token'];
}

function serialized_inputs() {
  var serialized_inputs = $('#url, #title, #description, #tags').serialize();
  if ($('#private').prop('checked')) {
    serialized_inputs += '&shared=no';
  }
  if ($('#toread').prop('checked')) {
    serialized_inputs += '&toread=yes';
  }
  return serialized_inputs;
}

/** Check for pre-existing bookmark for this URL. */
function check_for_existing_bookmark_details() {
  if (!urlParams['url']) { return; }
  var bookmark_details_api = "https://pinboard-bridge.herokuapp.com/posts/get?format=json&auth_token=" + auth_token() + "&url=" + urlParams['url'];

  $.get(bookmark_details_api, 'json')
    .done(function(response) {
      if (response['posts'].length < 1) { return; }
      var bookmark = response['posts'][0];

      $('input#title').val(bookmark['description']);
      $('textarea#description').val(bookmark['extended']);
      prepopulate_tags(bookmark['tags']);

      if (bookmark['shared'] == 'no') {
        $('#private').prop('checked', true);
      }
      if (bookmark['toread'] == 'yes') {
        $('#toread').prop('checked', true);
      }
      if (bookmark['time']) {
        var date = new Date(bookmark['time']);
        $('#bookmark-status').text("Previously saved on " + date.getFullYear() + "/" + date.getMonth() + "/" + date.getDate());
      }
      $('#submit span.text').text('Update bookmark');
      $('#spinner').addClass('hidden');
    })

    .fail(function(response) {
      if (response.status == '401') {
        alert("401 Unauthorised. Please check your username and API access token.");
      }
    });
}

/** Submit the form with Ajax */
function setUpFormSubmission() {
  $('#post-to-pinboard').on('submit', function(event) {
    event.preventDefault();

    var post_bookmark_api = "https://pinboard-bridge.herokuapp.com/posts/add?format=json&auth_token=" + auth_token() + "&" + serialized_inputs();

    $.get(post_bookmark_api, 'json')
      .done(function(response) {
        if (response['result_code'] == 'done') {
          console.log("Bookmark saved correctly.");
          Ladda.stopAll();
          $('#submit').addClass('success');
          setTimeout(function() {
            window.close();
            $('#submit').removeClass('success'); // for windows that aren't popups
          }, 900);
        } else if (response['result_code'] == 'must provide title') {
          Ladda.stopAll();
          $('#submit').addClass('fail');
          setTimeout(function() {
            $('#submit').removeClass('fail'); // let user try again
          }, 1900);
          $('#title').focus();
        }
      })

      .fail(function(response) {
        Ladda.stopAll();
        $('#submit').addClass('fail');
        if (response.status == '401') {
          alert("401 Unauthorised. Please check your username and API access token.");
        }
      });
  });

  $('input, textarea').on('blur', function() {
    $('body').animate({ scrollTop: 0 }, 200);
  });
}

function prepopulate_tags(tagString) {
  var tags = tagString.split(' ');
  for (var i = 0; i < tags.length; i++) {
    $('input#tags')[0].selectize.addOption({
      label: tags[i]
    });
    $('input#tags')[0].selectize.addItem(tags[i]);
  }
}

function setUpTagAutoComplete() {

  $('input#tags').selectize({
    delimiter: ' ',
    create: true,
    openOnFocus: false,
    maxOptions: 6,
    persist: false,
    hideSelected: true,
    diacritics: true,
    valueField: 'label',
    labelField: 'label',
    searchField: ['label'],
    plugins: ['restore_on_backspace'],
  });

  if (localStorage && localStorage['tags']) {
    var user_tags = JSON.parse(localStorage['tags']);
    console.log('Populating dropdown.');
    $.each(user_tags, function(key, value) {
      $('input#tags')[0].selectize.addOption({
        label: key
      });
    });
  }
}

function get_suggested_tags() {
  if (!urlParams['url']) { return; }
  var suggested_tags_api = "https://pinboard-bridge.herokuapp.com/posts/suggest?format=json&url=" + urlParams['url'] + "&auth_token=" + auth_token();

  $('#spinner').removeClass('hidden');
  $.get(suggested_tags_api, function(data) {
    show_suggested_tags(data);
    $('#spinner').addClass('hidden');
  }, 'json');
}

function update_user_tags() {
  if (!localStorage.tags) {
    console.log('Downloading user’s tags.');
    localStorage['tags'] = JSON.stringify([]);
    var all_tags_api = "https://pinboard-bridge.herokuapp.com/tags/get?format=json&auth_token=" + auth_token();

    $.get(all_tags_api, 'json')
      .done(function(response) {
        localStorage['tags'] = JSON.stringify(response);
        localStorage['tags-updated'] = new Date();
      })

      .fail(function(response) {
        if (response.status == '401') {
          alert("401 Unauthorised. Please check your username and API access token.");
        }
      });
  } else {
    console.log('Have tags already.');
  }
}

function show_suggested_tags(tag_suggestions) {
  if (!tag_suggestions) { return; }
  tag_suggestions = $.merge(tag_suggestions[0]['popular'], tag_suggestions[1]['recommended']); // flatten JSON
  tag_suggestions = tag_suggestions.map(function(tag) {
    return tag.toLowerCase(); // lowercase all tags
  });
  tag_suggestions = $.unique(tag_suggestions); // filter out duplicates
  $('#description')
  tag_suggestions = removeSpuriousResults(tag_suggestions); // empty the array if they are the default/broken suggestions
  tag_suggestions = removeOverlyCommonTags(tag_suggestions); // remove tags that appear very often across a wide range of pages

  var suggested_tags = [];
  for (var i = 0; i < tag_suggestions.length; i++) {
    var suggested_tag = tag_suggestions[i];
    var escaped = pin_escape(suggested_tag);
    var cooked  = pin_cook(suggested_tag);
    var suggested_tag = '<button type="button" class="suggested_tag" onclick="add_tag(\''  +
                escaped + '\'); $(this).hide(200); return false;">' + cooked + '</button>';
    suggested_tags.push(suggested_tag);
  }

  if (suggested_tags.length > 0) {
    $('#suggested').append(suggested_tags.join(" "));
    $('#suggestion_row').show();
    $('#suggested').show(800);
  } else {
    $('#suggestion_row th').addClass('none').text('No tag suggestions for this page.');
    $('#suggestion_row').show();
  }
}

/** Remove default set of tags that are suggested by the Pinboard API when there are no good suggestions. */
function removeSpuriousResults(tag_suggestions) {
  if ($.inArray('facebook', tag_suggestions) >= 0 && $.inArray('googlereader', tag_suggestions) >= 0 &&
      $.inArray('ifttt', tag_suggestions) >= 0 && $.inArray('objective-c', tag_suggestions) >= 0 &&
      $.inArray('twitter', tag_suggestions) >= 0 && $.inArray('twitterlink', tag_suggestions) >= 0 &&
      $.inArray('wsh', tag_suggestions) >= 0 && $.inArray('music', tag_suggestions) >= 0 &&
      $.inArray('04:22pm', tag_suggestions) >= 0 && $.inArray('1960s', tag_suggestions) >= 0) {
    return [];
  } else {
    return tag_suggestions;
  }
}

function removeOverlyCommonTags(tag_suggestions) {
  tag_suggestions = $.grep(tag_suggestions, function(tag, index) {
    tag = tag.toLowerCase();
    return (tag != 'bookmarks_bar' && tag != 'pin-later' && tag != 'unread' && tag != '*resources' &&
            tag != 'unlabeled' && tag != 'via:packrati.us' && tag != 'bookmarks_menu' && tag != 'from' &&
            tag != 'ifttt' && tag != 'later' && tag != 'saved' && tag != 'read' && tag != 'feedly' &&
            tag != 'for' && tag != 'recently' && tag != 'tobookmarks' && tag != 'from:ifttt' &&
            tag != 'instapaper' && tag != '!fromtwitter');
  });
  return tag_suggestions;
}

function pin_escape(s) {
  s = s.replace(/\\/g, "\\\\");
  s = s.replace(/'/g, "\\'"); // "
  s = s.replace(/"/g, '"'); // "
  return s;
}

function pin_cook(s) {
  s = s.replace(/</g, '<');
  s = s.replace(/>/g, '>');
  s = s.replace(/"/g, '"');  //"
  return s;
}

RegExp.escape = function(text) {
  return text.replace(/[-[\]{}()*+?.,\\\\'"^$|#\s]/g, "\\$&"); //"'
}

function add_tag(tag) {
  $('input#tags')[0].selectize.addOption({
    label: tag
  });
  $('input#tags')[0].selectize.addItem(tag);
}

function pin_sort(a,b) {
  var l = a.toLowerCase ? a.toLowerCase() : a;
  var r = b.toLowerCase ? b.toLowerCase() : b;
  if (l == r) { return 0; }
  if (l > r) { return 1; }
  return -1;
}

function winclose(e) {
  var code;
  if (e.keyCode) {
    code = e.keyCode;
  } else {
    if (e.which) {
      code = e.which;
    }
  }
  if (code == 27) {
    if (!(pin_tagcomplete && pin_tagcomplete.active())) {
      window.close();
    }
  }
}<|endoftext|>


<commit_before>'use strict';

var getTime = function (res) {
	var time = 0; // in seconds
	if (res.indexOf('<') !== -1) {
		return -1;
	}
	if (res.indexOf('h') !== -1) {
		res = res.split('h');
		time += Number(res[0]) * 3600;
		res = res[1];
	}
	if (res.indexOf('m') !== -1) {
		time += Number(res.split('m')[0]) * 60;
	}

	return time;
};

module.exports = function (a, b) {
	var resA, resB, timeA = 0, timeB = 0;
	resA = a.replace(/\s+/g, '');
	resB = b.replace(/\s+/g, '');

	timeA = getTime(resA);
	timeB = getTime(resB);

	if(timeA === timeB){
		return 0;
	}

	return timeA < timeB ? 1 : -1;
};<commit_msg>fix ci<commit_after>'use strict';

var getTime = function (res) {
	var time = 0; // in seconds
	if (res.indexOf('<') !== -1) {
		return -1;
	}
	if (res.indexOf('h') !== -1) {
		res = res.split('h');
		time += Number(res[0]) * 3600;
		res = res[1];
	}
	if (res.indexOf('m') !== -1) {
		time += Number(res.split('m')[0]) * 60;
	}

	return time;
};

module.exports = function (a, b) {
	var resA, resB, timeA = 0, timeB = 0;
	resA = a.replace(/\s+/g, '');
	resB = b.replace(/\s+/g, '');

	timeA = getTime(resA);
	timeB = getTime(resB);

	if (timeA === timeB) {
		return 0;
	}

	return timeA < timeB ? 1 : -1;
};
<|endoftext|>


<commit_before>/**
 * TemplateHelper
 *
 * @author: Harish Anchu <harishanchu@gmail.com>
 * @copyright 2015, Harish Anchu. All rights reserved.
 * @license Licensed under MIT (https://github.com/quorrajs/Ouch/blob/master/LICENSE)
 */
var util = require('util');
var ejs = require('ejs');
var fs = require('fs');

/**
 * Template helper
 *
 * @class
 */
function TemplateHelper() {
    /**
     * An array of variables to be passed to all templates
     *
     * @var {Object}
     * @protected
     */
    this.__variables;
}

/**
 * Sets the variables to be passed to all templates rendered
 * by this template helper.
 *
 * @param {Object} variables
 */
TemplateHelper.prototype.setVariables = function (variables) {
    this.__variables = variables || {};
};

/**
 * Returns all variables for this helper
 *
 * @returns {Object}
 */
TemplateHelper.prototype.getVariables = function () {
    return this.__variables;
};

/**
 * Given a template path, render it within its own scope. This
 * method also accepts an array of additional variables to be
 * passed to the template.
 *
 * @param {String} template
 * @param {Object} additionalVariables
 * @returns {String}
 */
TemplateHelper.prototype.render = function (template, additionalVariables) {
    var variables = this.getVariables();

    // Pass the helper to the template:
    variables["tpl"] = this;

    if (additionalVariables) {
        variables = util._extend(variables, additionalVariables);
    }

    return ejs.render(fs.readFileSync(template, 'utf-8'), variables);
};

/**
 * Convert a string to a slug version of itself
 *
 * @param   {String} original
 * @returns {String}
 */
TemplateHelper.prototype.slug = function (original) {
    var slug = original.replace(/ /g, "-");
    slug = slug.replace(/[^\w\d\-\_]/gi, '');
    return slug.toLocaleLowerCase();
};

/**
 * Escapes a string for output in an HTML document, but preserves
 * URIs within it, and converts them to clickable anchor elements.
 *
 * @param   {string} raw
 * @returns {string}
 */
TemplateHelper.prototype.escapeButPreserveUris = function (raw) {
    var escaped = ejs.render("<%= \"" + raw + "\" %>");
    return escaped.replace(/([A-z]+?:\/\/([-\w\.]+[-\w])+(:\d+)?(\/([\w\/_\.#-]*(\?\S+)?[^\.\s])?)?)/g,
        "$1");
};

module.exports = TemplateHelper;<commit_msg>self initialize this.__variables in TemplateHelper.js to guard against accessing property of undefined in tests<commit_after>/**
 * TemplateHelper
 *
 * @author: Harish Anchu <harishanchu@gmail.com>
 * @copyright 2015, Harish Anchu. All rights reserved.
 * @license Licensed under MIT (https://github.com/quorrajs/Ouch/blob/master/LICENSE)
 */
var util = require('util');
var ejs = require('ejs');
var fs = require('fs');

/**
 * Template helper
 *
 * @class
 */
function TemplateHelper() {
    /**
     * A map of variables to be passed to all templates
     *
     * @var {Object}
     * @protected
     */
    this.__variables = {};
}

/**
 * Sets the variables to be passed to all templates rendered
 * by this template helper.
 *
 * @param {Object} variables
 */
TemplateHelper.prototype.setVariables = function (variables) {
    this.__variables = variables || {};
};

/**
 * Returns all variables for this helper
 *
 * @returns {Object}
 */
TemplateHelper.prototype.getVariables = function () {
    return this.__variables;
};

/**
 * Given a template path, render it within its own scope. This
 * method also accepts an array of additional variables to be
 * passed to the template.
 *
 * @param {String} template
 * @param {Object} additionalVariables
 * @returns {String}
 */
TemplateHelper.prototype.render = function (template, additionalVariables) {
    var variables = this.getVariables();

    // Pass the helper to the template:
    variables["tpl"] = this;

    if (additionalVariables) {
        variables = util._extend(variables, additionalVariables);
    }

    return ejs.render(fs.readFileSync(template, 'utf-8'), variables);
};

/**
 * Convert a string to a slug version of itself
 *
 * @param   {String} original
 * @returns {String}
 */
TemplateHelper.prototype.slug = function (original) {
    var slug = original.replace(/ /g, "-");
    slug = slug.replace(/[^\w\d\-\_]/gi, '');
    return slug.toLocaleLowerCase();
};

/**
 * Escapes a string for output in an HTML document, but preserves
 * URIs within it, and converts them to clickable anchor elements.
 *
 * @param   {string} raw
 * @returns {string}
 */
TemplateHelper.prototype.escapeButPreserveUris = function (raw) {
    var escaped = ejs.render("<%= \"" + raw + "\" %>");
    return escaped.replace(/([A-z]+?:\/\/([-\w\.]+[-\w])+(:\d+)?(\/([\w\/_\.#-]*(\?\S+)?[^\.\s])?)?)/g,
        "$1");
};

module.exports = TemplateHelper;
<|endoftext|>