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

goog.provide('goog.net.FetchXmlHttp');
goog.provide('goog.net.FetchXmlHttpFactory');

goog.require('goog.asserts');
goog.require('goog.events.EventTarget');
goog.require('goog.functions');
goog.require('goog.log');
goog.require('goog.net.XhrLike');
goog.require('goog.net.XmlHttpFactory');

/**
 * Factory for creating Xhr objects that uses the native fetch() method.
 * https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
 * Note that this factory is intended for use in Service Worker only.
 * @param {!WorkerGlobalScope} worker The Service Worker global scope.
 * @extends {goog.net.XmlHttpFactory}
 * @struct
 * @constructor
 */
goog.net.FetchXmlHttpFactory = function(worker) {
  goog.net.FetchXmlHttpFactory.base(this, 'constructor');

  /** @private @final {!WorkerGlobalScope} */
  this.worker_ = worker;

  /** @private {!RequestCredentials|undefined} */
  this.credentialsMode_ = undefined;

  /** @private {!RequestCache|undefined} */
  this.cacheMode_ = undefined;
};
goog.inherits(goog.net.FetchXmlHttpFactory, goog.net.XmlHttpFactory);

/** @override */
goog.net.FetchXmlHttpFactory.prototype.createInstance = function() {
  var instance = new goog.net.FetchXmlHttp(this.worker_);
  if (this.credentialsMode_) {
    instance.setCredentialsMode(this.credentialsMode_);
  }
  if (this.cacheMode_) {
    instance.setCacheMode(this.cacheMode_);
  }
  return instance;
};

/** @override */
goog.net.FetchXmlHttpFactory.prototype.internalGetOptions =
    goog.functions.constant({});

/**
 * @param {!RequestCredentials} credentialsMode The credentials mode of the
 *     Service Worker fetch.
 */
goog.net.FetchXmlHttpFactory.prototype.setCredentialsMode = function(
    credentialsMode) {
  this.credentialsMode_ = credentialsMode;
};

/**
 * @param {!RequestCache} cacheMode The cache mode of the Service Worker fetch.
 */
goog.net.FetchXmlHttpFactory.prototype.setCacheMode = function(cacheMode) {
  this.cacheMode_ = cacheMode;
};

/**
 * FetchXmlHttp object constructor.
 * @param {!WorkerGlobalScope} worker
 * @extends {goog.events.EventTarget}
 * @implements {goog.net.XhrLike}
 * @constructor
 * @struct
 */
goog.net.FetchXmlHttp = function(worker) {
  goog.net.FetchXmlHttp.base(this, 'constructor');

  /** @private @final {!WorkerGlobalScope} */
  this.worker_ = worker;

  /** @private {RequestCredentials|undefined} */
  this.credentialsMode_ = undefined;

  /** @private {RequestCache|undefined} */
  this.cacheMode_ = undefined;

  /**
   * Request state.
   * @type {goog.net.FetchXmlHttp.RequestState}
   */
  this.readyState = goog.net.FetchXmlHttp.RequestState.UNSENT;

  /**
   * HTTP status.
   * @type {number}
   */
  this.status = 0;

  /**
   * HTTP status string.
   * @type {string}
   */
  this.statusText = '';

  /**
   * Content of the response.
   * @type {string}
   */
  this.responseText = '';

  /**
   * Document response entity body.
   * NOTE: This is always null and not supported by this class.
   * @final {null}
   */
  this.responseXML = null;

  /**
   * Method to call when the state changes.
   * @type {?function()}
   */
  this.onreadystatechange = null;

  /** @private {!Headers} */
  this.requestHeaders_ = new Headers();

  /** @private {?Headers} */
  this.responseHeaders_ = null;

  /**
   * Request method (GET or POST).
   * @private {string}
   */
  this.method_ = 'GET';

  /**
   * Request URL.
   * @private {string}
   */
  this.url_ = '';

  /**
   * Whether the request is in progress.
   * @private {boolean}
   */
  this.inProgress_ = false;

  /** @private @final {?goog.log.Logger} */
  this.logger_ = goog.log.getLogger('goog.net.FetchXmlHttp');

  /** @private {?Response} */
  this.fetchResponse_ = null;

  /** @private {!ReadableStreamDefaultReader|null} */
  this.currentReader_ = null;

  /** @private {?TextDecoder} */
  this.textDecoder_ = null;
};
goog.inherits(goog.net.FetchXmlHttp, goog.events.EventTarget);

/**
 * State of the requests.
 * @enum {number}
 */
goog.net.FetchXmlHttp.RequestState = {
  UNSENT: 0,
  OPENED: 1,
  HEADER_RECEIVED: 2,
  LOADING: 3,
  DONE: 4
};

/** @override */
goog.net.FetchXmlHttp.prototype.open = function(method, url, opt_async) {
  goog.asserts.assert(!!opt_async, 'Only async requests are supported.');
  if (this.readyState != goog.net.FetchXmlHttp.RequestState.UNSENT) {
    this.abort();
    throw new Error('Error reopening a connection');
  }

  this.method_ = method;
  this.url_ = url;

  this.readyState = goog.net.FetchXmlHttp.RequestState.OPENED;
  this.dispatchCallback_();
};

/** @override */
goog.net.FetchXmlHttp.prototype.send = function(opt_data) {
  if (this.readyState != goog.net.FetchXmlHttp.RequestState.OPENED) {
    this.abort();
    throw new Error('need to call open() first. ');
  }

  this.inProgress_ = true;
  var requestInit = {
    headers: this.requestHeaders_,
    method: this.method_,
    credentials: this.credentialsMode_,
    cache: this.cacheMode_
  };
  if (opt_data) {
    requestInit['body'] = opt_data;
  }
  this.worker_
      .fetch(new Request(this.url_, /** @type {!RequestInit} */ (requestInit)))
      .then(
          this.handleResponse_.bind(this), this.handleSendFailure_.bind(this));
};

/** @override */
goog.net.FetchXmlHttp.prototype.abort = function() {
  this.responseText = '';
  this.requestHeaders_ = new Headers();
  this.status = 0;

  if (!!this.currentReader_) {
    this.currentReader_.cancel('Request was aborted.');
  }

  if (((this.readyState >= goog.net.FetchXmlHttp.RequestState.OPENED) &&
       this.inProgress_) &&
      (this.readyState != goog.net.FetchXmlHttp.RequestState.DONE)) {
    this.inProgress_ = false;
    this.requestDone_(false);
    this.dispatchCallback_();
  }

  this.readyState = goog.net.FetchXmlHttp.RequestState.UNSENT;
};

/**
 * Handles the fetch response.
 * @param {!Response} response
 * @private
 */
goog.net.FetchXmlHttp.prototype.handleResponse_ = function(response) {
  if (!this.inProgress_) {
    // The request was aborted, ignore.
    return;
  }

  this.fetchResponse_ = response;

  if (!this.responseHeaders_) {
    this.responseHeaders_ = response.headers;
    this.readyState = goog.net.FetchXmlHttp.RequestState.HEADER_RECEIVED;
    this.dispatchCallback_();
  }
  // A callback may abort the request.
  if (!this.inProgress_) {
    // The request was aborted, ignore.
    return;
  }

  this.readyState = goog.net.FetchXmlHttp.RequestState.LOADING;
  this.dispatchCallback_();
  // A callback may abort the request.
  if (!this.inProgress_) {
    // The request was aborted, ignore.
    return;
  }

  if (typeof (goog.global.ReadableStream) !== 'undefined' &&
      'body' in response) {
    this.responseText = '';
    this.currentReader_ =
        /** @type {!ReadableStreamDefaultReader} */ (response.body.getReader());
    this.textDecoder_ = new TextDecoder();
    this.readInputFromFetch_();
  } else {
    response.text().then(
        this.handleResponseText_.bind(this),
        this.handleSendFailure_.bind(this));
  }
};

/**
 * Reads the next chunk of data from the fetch response.
 * @private
 */
goog.net.FetchXmlHttp.prototype.readInputFromFetch_ = function() {
  this.currentReader_.read()
      .then(this.handleDataFromStream_.bind(this))
      .catch(this.handleSendFailure_.bind(this));
};

/**
 * Handles a chunk of data from the fetch response stream reader.
 * @param {!IteratorResult} result
 * @private
 */
goog.net.FetchXmlHttp.prototype.handleDataFromStream_ = function(result) {
  var dataPacket = result.value ? /** @type {!Uint8Array} */ (result.value) :
                                  new Uint8Array(0);
  var newText = this.textDecoder_.decode(dataPacket, {stream: !result.done});
  if (newText) {
    this.responseText += newText;
  }

  if (result.done) {
    this.requestDone_(true);
  } else {
    this.dispatchCallback_();
  }

  if (this.readyState == goog.net.FetchXmlHttp.RequestState.LOADING) {
    this.readInputFromFetch_();
  }
};

/**
 * Handles the response text.
 * @param {string} responseText
 * @private
 */
goog.net.FetchXmlHttp.prototype.handleResponseText_ = function(responseText) {
  if (!this.inProgress_) {
    // The request was aborted, ignore.
    return;
  }
  this.responseText = responseText;
  this.requestDone_(true);
};

/**
 * Handles the send failure.
 * @param {*} error
 * @private
 */
goog.net.FetchXmlHttp.prototype.handleSendFailure_ = function(error) {
  var e = error instanceof Error ? error : Error(error);
  goog.log.warning(this.logger_, 'Failed to fetch url ' + this.url_, e);
  if (!this.inProgress_) {
    // The request was aborted, ignore.
    return;
  }
  this.requestDone_(true);
};

/**
 * Sets the request state to DONE and performs cleanup.
 * @param {boolean} setStatus whether to set the status and statusText fields,
 * this is not necessary when the request is aborted.
 * @private
 */
goog.net.FetchXmlHttp.prototype.requestDone_ = function(setStatus) {
  if (setStatus && this.fetchResponse_) {
    this.status = this.fetchResponse_.status;
    this.statusText = this.fetchResponse_.statusText;
  }

  this.readyState = goog.net.FetchXmlHttp.RequestState.DONE;

  this.fetchResponse_ = null;
  this.currentReader_ = null;
  this.textDecoder_ = null;

  this.dispatchCallback_();
};

/** @override */
goog.net.FetchXmlHttp.prototype.setRequestHeader = function(header, value) {
  this.requestHeaders_.append(header, value);
};

/** @override */
goog.net.FetchXmlHttp.prototype.getResponseHeader = function(header) {
  // TODO(b/70808323): This method should return null when the headers are not
  // present or the specified header is missing. The externs need to be fixed.
  if (!this.responseHeaders_) {
    goog.log.warning(
        this.logger_,
        'Attempting to get response header but no headers have been received ' +
            'for url: ' + this.url_);
    return '';
  }
  return this.responseHeaders_.get(header.toLowerCase()) || '';
};

/** @override */
goog.net.FetchXmlHttp.prototype.getAllResponseHeaders = function() {
  if (!this.responseHeaders_) {
    goog.log.warning(
        this.logger_,
        'Attempting to get all response headers but no headers have been ' +
            'received for url: ' + this.url_);
    return '';
  }
  var lines = [];
  var iter = this.responseHeaders_.entries();
  var entry = iter.next();
  while (!entry.done) {
    var pair = entry.value;
    lines.push(pair[0] + ': ' + pair[1]);
    entry = iter.next();
  }
  return lines.join('\r\n');
};

/**
 * @param {!RequestCredentials} credentialsMode The credentials mode of the
 *     Service Worker fetch.
 */
goog.net.FetchXmlHttp.prototype.setCredentialsMode = function(credentialsMode) {
  this.credentialsMode_ = credentialsMode;
};

/**
 * @param {!RequestCache} cacheMode The cache mode of the Service Worker fetch.
 */
goog.net.FetchXmlHttp.prototype.setCacheMode = function(cacheMode) {
  this.cacheMode_ = cacheMode;
};

/**
 * Dispatches the callback, if the callback attribute is defined.
 * @private
 */
goog.net.FetchXmlHttp.prototype.dispatchCallback_ = function() {
  if (this.onreadystatechange) {
    this.onreadystatechange.call(this);
  }
};
<commit_msg>[FetchXmlHttp] - Check to make sure the request is still in progress before handling new data from a fetch stream.<commit_after>// Copyright 2015 The Closure Library Authors. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS-IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

goog.provide('goog.net.FetchXmlHttp');
goog.provide('goog.net.FetchXmlHttpFactory');

goog.require('goog.asserts');
goog.require('goog.events.EventTarget');
goog.require('goog.functions');
goog.require('goog.log');
goog.require('goog.net.XhrLike');
goog.require('goog.net.XmlHttpFactory');

/**
 * Factory for creating Xhr objects that uses the native fetch() method.
 * https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
 * Note that this factory is intended for use in Service Worker only.
 * @param {!WorkerGlobalScope} worker The Service Worker global scope.
 * @extends {goog.net.XmlHttpFactory}
 * @struct
 * @constructor
 */
goog.net.FetchXmlHttpFactory = function(worker) {
  goog.net.FetchXmlHttpFactory.base(this, 'constructor');

  /** @private @final {!WorkerGlobalScope} */
  this.worker_ = worker;

  /** @private {!RequestCredentials|undefined} */
  this.credentialsMode_ = undefined;

  /** @private {!RequestCache|undefined} */
  this.cacheMode_ = undefined;
};
goog.inherits(goog.net.FetchXmlHttpFactory, goog.net.XmlHttpFactory);

/** @override */
goog.net.FetchXmlHttpFactory.prototype.createInstance = function() {
  var instance = new goog.net.FetchXmlHttp(this.worker_);
  if (this.credentialsMode_) {
    instance.setCredentialsMode(this.credentialsMode_);
  }
  if (this.cacheMode_) {
    instance.setCacheMode(this.cacheMode_);
  }
  return instance;
};

/** @override */
goog.net.FetchXmlHttpFactory.prototype.internalGetOptions =
    goog.functions.constant({});

/**
 * @param {!RequestCredentials} credentialsMode The credentials mode of the
 *     Service Worker fetch.
 */
goog.net.FetchXmlHttpFactory.prototype.setCredentialsMode = function(
    credentialsMode) {
  this.credentialsMode_ = credentialsMode;
};

/**
 * @param {!RequestCache} cacheMode The cache mode of the Service Worker fetch.
 */
goog.net.FetchXmlHttpFactory.prototype.setCacheMode = function(cacheMode) {
  this.cacheMode_ = cacheMode;
};

/**
 * FetchXmlHttp object constructor.
 * @param {!WorkerGlobalScope} worker
 * @extends {goog.events.EventTarget}
 * @implements {goog.net.XhrLike}
 * @constructor
 * @struct
 */
goog.net.FetchXmlHttp = function(worker) {
  goog.net.FetchXmlHttp.base(this, 'constructor');

  /** @private @final {!WorkerGlobalScope} */
  this.worker_ = worker;

  /** @private {RequestCredentials|undefined} */
  this.credentialsMode_ = undefined;

  /** @private {RequestCache|undefined} */
  this.cacheMode_ = undefined;

  /**
   * Request state.
   * @type {goog.net.FetchXmlHttp.RequestState}
   */
  this.readyState = goog.net.FetchXmlHttp.RequestState.UNSENT;

  /**
   * HTTP status.
   * @type {number}
   */
  this.status = 0;

  /**
   * HTTP status string.
   * @type {string}
   */
  this.statusText = '';

  /**
   * Content of the response.
   * @type {string}
   */
  this.responseText = '';

  /**
   * Document response entity body.
   * NOTE: This is always null and not supported by this class.
   * @final {null}
   */
  this.responseXML = null;

  /**
   * Method to call when the state changes.
   * @type {?function()}
   */
  this.onreadystatechange = null;

  /** @private {!Headers} */
  this.requestHeaders_ = new Headers();

  /** @private {?Headers} */
  this.responseHeaders_ = null;

  /**
   * Request method (GET or POST).
   * @private {string}
   */
  this.method_ = 'GET';

  /**
   * Request URL.
   * @private {string}
   */
  this.url_ = '';

  /**
   * Whether the request is in progress.
   * @private {boolean}
   */
  this.inProgress_ = false;

  /** @private @final {?goog.log.Logger} */
  this.logger_ = goog.log.getLogger('goog.net.FetchXmlHttp');

  /** @private {?Response} */
  this.fetchResponse_ = null;

  /** @private {!ReadableStreamDefaultReader|null} */
  this.currentReader_ = null;

  /** @private {?TextDecoder} */
  this.textDecoder_ = null;
};
goog.inherits(goog.net.FetchXmlHttp, goog.events.EventTarget);

/**
 * State of the requests.
 * @enum {number}
 */
goog.net.FetchXmlHttp.RequestState = {
  UNSENT: 0,
  OPENED: 1,
  HEADER_RECEIVED: 2,
  LOADING: 3,
  DONE: 4
};

/** @override */
goog.net.FetchXmlHttp.prototype.open = function(method, url, opt_async) {
  goog.asserts.assert(!!opt_async, 'Only async requests are supported.');
  if (this.readyState != goog.net.FetchXmlHttp.RequestState.UNSENT) {
    this.abort();
    throw new Error('Error reopening a connection');
  }

  this.method_ = method;
  this.url_ = url;

  this.readyState = goog.net.FetchXmlHttp.RequestState.OPENED;
  this.dispatchCallback_();
};

/** @override */
goog.net.FetchXmlHttp.prototype.send = function(opt_data) {
  if (this.readyState != goog.net.FetchXmlHttp.RequestState.OPENED) {
    this.abort();
    throw new Error('need to call open() first. ');
  }

  this.inProgress_ = true;
  var requestInit = {
    headers: this.requestHeaders_,
    method: this.method_,
    credentials: this.credentialsMode_,
    cache: this.cacheMode_
  };
  if (opt_data) {
    requestInit['body'] = opt_data;
  }
  this.worker_
      .fetch(new Request(this.url_, /** @type {!RequestInit} */ (requestInit)))
      .then(
          this.handleResponse_.bind(this), this.handleSendFailure_.bind(this));
};

/** @override */
goog.net.FetchXmlHttp.prototype.abort = function() {
  this.responseText = '';
  this.requestHeaders_ = new Headers();
  this.status = 0;

  if (!!this.currentReader_) {
    this.currentReader_.cancel('Request was aborted.');
  }

  if (((this.readyState >= goog.net.FetchXmlHttp.RequestState.OPENED) &&
       this.inProgress_) &&
      (this.readyState != goog.net.FetchXmlHttp.RequestState.DONE)) {
    this.inProgress_ = false;
    this.requestDone_(false);
  }

  this.readyState = goog.net.FetchXmlHttp.RequestState.UNSENT;
};

/**
 * Handles the fetch response.
 * @param {!Response} response
 * @private
 */
goog.net.FetchXmlHttp.prototype.handleResponse_ = function(response) {
  if (!this.inProgress_) {
    // The request was aborted, ignore.
    return;
  }

  this.fetchResponse_ = response;

  if (!this.responseHeaders_) {
    this.responseHeaders_ = response.headers;
    this.readyState = goog.net.FetchXmlHttp.RequestState.HEADER_RECEIVED;
    this.dispatchCallback_();
  }
  // A callback may abort the request.
  if (!this.inProgress_) {
    // The request was aborted, ignore.
    return;
  }

  this.readyState = goog.net.FetchXmlHttp.RequestState.LOADING;
  this.dispatchCallback_();
  // A callback may abort the request.
  if (!this.inProgress_) {
    // The request was aborted, ignore.
    return;
  }

  if (typeof (goog.global.ReadableStream) !== 'undefined' &&
      'body' in response) {
    this.responseText = '';
    this.currentReader_ =
        /** @type {!ReadableStreamDefaultReader} */ (response.body.getReader());
    this.textDecoder_ = new TextDecoder();
    this.readInputFromFetch_();
  } else {
    response.text().then(
        this.handleResponseText_.bind(this),
        this.handleSendFailure_.bind(this));
  }
};

/**
 * Reads the next chunk of data from the fetch response.
 * @private
 */
goog.net.FetchXmlHttp.prototype.readInputFromFetch_ = function() {
  this.currentReader_.read()
      .then(this.handleDataFromStream_.bind(this))
      .catch(this.handleSendFailure_.bind(this));
};

/**
 * Handles a chunk of data from the fetch response stream reader.
 * @param {!IteratorResult} result
 * @private
 */
goog.net.FetchXmlHttp.prototype.handleDataFromStream_ = function(result) {
  if (!this.inProgress_) {
    // The request was aborted, ignore.
    return;
  }

  var dataPacket = result.value ? /** @type {!Uint8Array} */ (result.value) :
                                  new Uint8Array(0);
  var newText = this.textDecoder_.decode(dataPacket, {stream: !result.done});
  if (newText) {
    this.responseText += newText;
  }

  if (result.done) {
    this.requestDone_(true);
  } else {
    this.dispatchCallback_();
  }

  if (this.readyState == goog.net.FetchXmlHttp.RequestState.LOADING) {
    this.readInputFromFetch_();
  }
};

/**
 * Handles the response text.
 * @param {string} responseText
 * @private
 */
goog.net.FetchXmlHttp.prototype.handleResponseText_ = function(responseText) {
  if (!this.inProgress_) {
    // The request was aborted, ignore.
    return;
  }
  this.responseText = responseText;
  this.requestDone_(true);
};

/**
 * Handles the send failure.
 * @param {*} error
 * @private
 */
goog.net.FetchXmlHttp.prototype.handleSendFailure_ = function(error) {
  var e = error instanceof Error ? error : Error(error);
  goog.log.warning(this.logger_, 'Failed to fetch url ' + this.url_, e);
  if (!this.inProgress_) {
    // The request was aborted, ignore.
    return;
  }
  this.requestDone_(true);
};

/**
 * Sets the request state to DONE and performs cleanup.
 * @param {boolean} setStatus whether to set the status and statusText fields,
 * this is not necessary when the request is aborted.
 * @private
 */
goog.net.FetchXmlHttp.prototype.requestDone_ = function(setStatus) {
  if (setStatus && this.fetchResponse_) {
    this.status = this.fetchResponse_.status;
    this.statusText = this.fetchResponse_.statusText;
  }

  this.readyState = goog.net.FetchXmlHttp.RequestState.DONE;

  this.fetchResponse_ = null;
  this.currentReader_ = null;
  this.textDecoder_ = null;

  this.dispatchCallback_();
};

/** @override */
goog.net.FetchXmlHttp.prototype.setRequestHeader = function(header, value) {
  this.requestHeaders_.append(header, value);
};

/** @override */
goog.net.FetchXmlHttp.prototype.getResponseHeader = function(header) {
  // TODO(b/70808323): This method should return null when the headers are not
  // present or the specified header is missing. The externs need to be fixed.
  if (!this.responseHeaders_) {
    goog.log.warning(
        this.logger_,
        'Attempting to get response header but no headers have been received ' +
            'for url: ' + this.url_);
    return '';
  }
  return this.responseHeaders_.get(header.toLowerCase()) || '';
};

/** @override */
goog.net.FetchXmlHttp.prototype.getAllResponseHeaders = function() {
  if (!this.responseHeaders_) {
    goog.log.warning(
        this.logger_,
        'Attempting to get all response headers but no headers have been ' +
            'received for url: ' + this.url_);
    return '';
  }
  var lines = [];
  var iter = this.responseHeaders_.entries();
  var entry = iter.next();
  while (!entry.done) {
    var pair = entry.value;
    lines.push(pair[0] + ': ' + pair[1]);
    entry = iter.next();
  }
  return lines.join('\r\n');
};

/**
 * @param {!RequestCredentials} credentialsMode The credentials mode of the
 *     Service Worker fetch.
 */
goog.net.FetchXmlHttp.prototype.setCredentialsMode = function(credentialsMode) {
  this.credentialsMode_ = credentialsMode;
};

/**
 * @param {!RequestCache} cacheMode The cache mode of the Service Worker fetch.
 */
goog.net.FetchXmlHttp.prototype.setCacheMode = function(cacheMode) {
  this.cacheMode_ = cacheMode;
};

/**
 * Dispatches the callback, if the callback attribute is defined.
 * @private
 */
goog.net.FetchXmlHttp.prototype.dispatchCallback_ = function() {
  if (this.onreadystatechange) {
    this.onreadystatechange.call(this);
  }
};
<|endoftext|>


<commit_before>/*---------------------------------------------------------
 * OpenERP web_gantt
 *---------------------------------------------------------*/
openerp.web_gantt = function (openerp) {
var _t = openerp.web._t;
var QWeb = openerp.web.qweb;

QWeb.add_template('/web_gantt/static/src/xml/web_gantt.xml');
openerp.web.views.add('gantt', 'openerp.web_gantt.GanttView');
openerp.web_gantt.GanttView = openerp.web.View.extend({

init: function(parent, dataset, view_id) {
        this._super(parent);
        this.view_manager = parent || new openerp.web.NullViewManager();
        this.dataset = dataset;
        this.model = dataset.model;
        this.view_id = view_id;
        this.domain = this.dataset.domain || [];
        this.context = this.dataset.context || {};
        this.has_been_loaded = $.Deferred();
    },

    start: function() {
        this._super();
        return this.rpc("/web/view/load", {"model": this.model, "view_id": this.view_id, "view_type": "gantt"}, this.on_loaded);
    },

    on_loaded: function(data) {
        this.fields_view = data,
        this.name =  this.fields_view.arch.attrs.string,
        this.view_id = this.fields_view.view_id,
        this.fields = this.fields_view.fields;

        this.date_start = this.fields_view.arch.attrs.date_start,
        this.date_delay = this.fields_view.arch.attrs.date_delay,
        this.date_stop = this.fields_view.arch.attrs.date_stop,
        this.day_length = this.fields_view.arch.attrs.day_length || 8;

        this.color_field = this.fields_view.arch.attrs.color,
        this.colors = this.fields_view.arch.attrs.colors;

        var level = this.fields_view.arch.children[0];
        this.parent = level.attrs.link,
        this.text = level.children.length ? level.children[0].attrs.name : level.attrs.name;

        if (!this.date_start) {
            return self.do_warn(_t("date_start is not defined "))
        }

        this.$element.html(QWeb.render("GanttView", {'height': $('.oe-application-container').height(), 'width': $('.oe-application-container').width()}));
        this.has_been_loaded.resolve();
    },

    on_project_loaded: function(projects) {

        if(!projects.length) return;
        var self = this,
            started_projects = _.filter(projects, function(res) {
            return res[self.date_start];
        });

        this.database_projects = started_projects;
        if(!started_projects.length)
            return self.do_warn(_t("date_start is not defined"));

        if(!self.name && started_projects.length) {
            var name = started_projects[0][self.parent];
            self.name = name instanceof Array? name[name.length - 1] : name;
        }

        $.when(this.project_starting_date(), this.get_project_duration(), this.calculate_difference())
            .then(function() {
                if(self.ganttChartControl) {
                    self.ganttChartControl.clearAll();
                    self.$element.find('#GanttView').empty();
                }
            })
            .then(this.group_projects())
            .then(this.generate_projects())
            .then(this.add_tasks())
            .done(this.init_gantt_view());
    },

    group_projects: function() {
        var def = $.Deferred(),
            self = this,
            projects = this.database_projects;

        if (!this.group_by.length) return def.resolve().promise();

        this.GroupProject = _.map(this.group_by, function(grp) {
                return _.groupBy(projects, function(prj) {
                    return prj[grp];
                })
            });
        return def.resolve().promise();
    },

    generate_projects : function() {
        var projects = this.database_projects,
            self = this;

        this.GanttTasks = [];
        if(this.group_by.length) {
            _.each(this.GroupProject, function(GrpProject, index) {
                self.GanttTasks.push([]);
                var counter = 0;
                _.each(GrpProject, function(grp, grpindex) {
                    var name = grpindex.split(',');
                    self.GanttTasks[index]
                        .push(new GanttTaskInfo(index + '_' + counter , name[name.length - 1], self.project_start_date, self.total_duration, 100, ""));
                    counter += 1;
                });
            })
        } else {
            this.GanttTasks.push(new GanttTaskInfo(0, self.name, self.project_start_date, self.total_duration, 100, ""));
        }
        this.GanttProjects = new GanttProjectInfo(0, self.name, self.project_start_date);
        return $.Deferred().resolve().promise();
    },

    add_tasks: function() {
        var self = this,
            tasks = this.database_projects;
        if (this.group_by.length) {
            _.each(this.GroupProject, function(grpproject, index) {
                var counter = 0;
                _.each(grpproject, function(prj, gindex) {
                    _.each(prj, function(task, tindex) {
                        var name = task[self.text];
                        if (task[self.text] instanceof Array) {
                            name = task[self.text][1];
                        }
                        self.GanttTasks[index][counter]
                            .addChildTask(new GanttTaskInfo(task.id, name, self.format_date(task[self.date_start]), self.project_duration[index], 100, ""))
                    });
                    counter += 1;
                })
            });
        }
        else {
            _.each(tasks, function(task, index){
                var name = task[self.text];
                if (task[self.text] instanceof Array) {
                    name = task[self.text][1];
                }
                self.GanttTasks[0].addChildTask(new GanttTaskInfo(task.id, name, self.format_date(task[self.date_start]), self.project_duration[index], 100, ""));
            });
        }
        return $.Deferred().resolve().promise();
    },

    get_project_duration: function() {

        var self = this,
            projects = this.database_projects;

        this.project_duration = [];

        _.each(projects, function(project, index) {
            if (self.date_stop && project[self.date_stop]) {
                self.project_duration.push(self.duration_difference(project[self.date_start], project[self.date_stop]));
            } else if(self.date_delay && project[self.date_delay]) {
                self.project_duration.push(project[self.date_delay]);
            } else {
                self.project_duration.push(0);
            }
        });

        this.max_project_duration = _.max(this.project_duration);
        return $.Deferred().resolve().promise();
    },

    duration_difference: function(start_date, end_date) {

        var DAY = 1000 * 60 * 60 * 24,
            date1_ms = openerp.web.auto_str_to_date(start_date).getTime(),
            date2_ms = openerp.web.auto_str_to_date(end_date).getTime(),
            difference_ms = Math.abs(date1_ms - date2_ms);

        var d = Math.floor(difference_ms / DAY),
            h = (difference_ms % DAY)/(1000 * 60 * 60),
            num = (d * this.day_length) + h;
        return parseFloat(num.toFixed(2));

    },

    calculate_difference: function() {
        var extend_end_date_day = Math.floor(this.max_project_duration / this.day_length),
            extend_end_date_hours = this.max_project_duration % this.day_length;

        this.project_end_date = this.project_end_date.add({days: extend_end_date_day, hours: extend_end_date_hours})

        var DAY = 1000 * 60 * 60 * 24,
            difference = Math.abs(this.project_start_date.getTime() - this.project_end_date.getTime()),
            day = Math.ceil(difference / DAY),
            hour = (difference % DAY)/(1000 * 60 * 60),
            DiffHour = (day * this.day_length) + hour;

        this.total_duration = parseFloat(DiffHour.toFixed(2));
        return $.Deferred().resolve().promise();
    },

    project_starting_date : function() {
        var self = this,
            projects = this.database_projects,
            min_date = _.min(projects, function(prj) {
                return new Date(prj[self.date_start]);
            }),
            max_date = _.max(projects, function(prj) {
                return self.format_date(prj[self.date_start]);
            });
        this.project_end_date =  this.format_date(max_date[self.date_start]);
        if (min_date) this.project_start_date = this.format_date(min_date[self.date_start]);
        else
            this.project_start_date = Date.today();
        return $.Deferred().resolve().promise();
    },

    init_gantt_view: function() {
        var self = this;

        if (this.group_by.length) {
            _.each(self.GanttTasks, function(tasks, index) {
                _.each(tasks, function(task, tindex) {
                    self.GanttProjects.addTask(task);
                });
            });
        }
        else {
            _.each(this.GanttTasks, function(tsk, index){
                self.GanttProjects.addTask(tsk);
            });
        }

        var ganttChartControl = this.ganttChartControl = new GanttChart();

        // Setup paths and behavior
        ganttChartControl.setImagePath("/web_gantt/static/lib/dhtmlxGantt/codebase/imgs/");
        ganttChartControl.setEditable(true);
        ganttChartControl.showTreePanel(true);
        ganttChartControl.showContextMenu(false);
        ganttChartControl.showDescTask(true,'d,s-f');
        ganttChartControl.showDescProject(true,'n,d');

        // Load data structure
        ganttChartControl.addProject(this.GanttProjects);

        // Create Gantt control
        ganttChartControl.create('GanttView');

        // Setup Events
        ganttChartControl.attachEvent("onTaskStartDrag", function(task) {
            var task_date = task.getEST();
            if(task_date.getHours()) {
                task_date.set({hour: task_date.getHours(), minute : task_date.getMinutes(), second:0});
            }
        });
        ganttChartControl.attachEvent("onTaskEndResize", function(task) {return self.ResizeTask(task);});
        ganttChartControl.attachEvent("onTaskEndDrag", function(task) {return self.ResizeTask(task);});
        ganttChartControl.attachEvent("onTaskDblClick", function(task) { return self.editTask(task);});
    },

    format_date : function(date) {
        var datetime_regex = /^(\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d)(?:\.\d+)?$/,
            date_regex = /^\d\d\d\d-\d\d-\d\d$/,
            time_regex = /^(\d\d:\d\d:\d\d)(?:\.\d+)?$/,
            def = $.Deferred();
        if(date_regex.exec(date)) {
            this.date_format = "yyyy-MM-dd";
        } else if(time_regex.exec(date)) {
            this.date_format = "HH:mm:ss";
        } else {
            this.date_format = "yyyy-MM-dd HH:mm:ss";
        }
        return openerp.web.auto_str_to_date(date);
    },

    ResizeTask: function(task) {

        var event_id = task.getId();

        if(!event_id)
            return this.do_warn(_t("Project can not be resized"));

        var data = {};
        data[this.date_start] = task.getEST().toString(this.date_format);

        if(this.date_stop) {
            var diff = task.getDuration() % this.day_length,
                finished_date = task.getFinishDate().add({hours: diff});
            data[this.date_stop] = finished_date.toString(this.date_format);
        } else {
            data[this.date_delay] = task.getDuration();
        }
        this.dataset
            .write(event_id, data, {})
            .done(function() {
                self.reloadView();
            });
    },

    editTask: function(task) {
        var self = this;
        var event_id = task.getId();
        if(!event_id)
            return false;

        if(event_id) event_id = parseInt(event_id, 10);

        var action_manager = new openerp.web.ActionManager(this);

        var dialog = new openerp.web.Dialog(this, {
            width: 800,
            height: 600,
            buttons : {
                Cancel : function() {
                    $(this).dialog('destroy');
                },
                Save : function() {
                    var form_view = action_manager.inner_viewmanager.views.form.controller;

                    form_view.do_save(function() {
                        self.reloadView();
                    });
                    $(this).dialog('destroy');
                }
            }
        }).start().open();
        action_manager.appendTo(dialog.$element);
        action_manager.do_action({
            res_model : this.dataset.model,
            res_id: event_id,
            views : [[false, 'form']],
            type : 'ir.actions.act_window',
            auto_search : false,
            flags : {
                search_view: false,
                sidebar : false,
                views_switcher : false,
                action_buttons : false,
                pager: false
            }
        });
    },

    reloadView: function() {
       self.on_project_loaded(self.database_projects);
    },

    do_show: function () {
        this.$element.show();
    },

    do_hide: function () {
        this.$element.hide();
    },

    do_search: function (domains, contexts, groupbys) {
        var self = this;
        this.group_by = groupbys;
        $.when(this.has_been_loaded).then(function() {
            self.dataset
                .read_slice([], {
                    domain: domains,
                    context: contexts
                })
                .done(function(projects) {
                    self.on_project_loaded(projects);
                });
        })

    }

});

// here you may tweak globals object, if any, and play with on_* or do_* callbacks on them

};
// vim:et fdc=0 fdl=0:
<commit_msg>[FIX] Events attached only to child tasks.<commit_after>/*---------------------------------------------------------
 * OpenERP web_gantt
 *---------------------------------------------------------*/
openerp.web_gantt = function (openerp) {
var _t = openerp.web._t;
var QWeb = openerp.web.qweb;

QWeb.add_template('/web_gantt/static/src/xml/web_gantt.xml');
openerp.web.views.add('gantt', 'openerp.web_gantt.GanttView');
openerp.web_gantt.GanttView = openerp.web.View.extend({

init: function(parent, dataset, view_id) {
        this._super(parent);
        this.view_manager = parent || new openerp.web.NullViewManager();
        this.dataset = dataset;
        this.model = dataset.model;
        this.view_id = view_id;
        this.domain = this.dataset.domain || [];
        this.context = this.dataset.context || {};
        this.has_been_loaded = $.Deferred();
    },

    start: function() {
        this._super();
        return this.rpc("/web/view/load", {"model": this.model, "view_id": this.view_id, "view_type": "gantt"}, this.on_loaded);
    },

    on_loaded: function(data) {
        this.fields_view = data,
        this.name =  this.fields_view.arch.attrs.string,
        this.view_id = this.fields_view.view_id,
        this.fields = this.fields_view.fields;

        this.date_start = this.fields_view.arch.attrs.date_start,
        this.date_delay = this.fields_view.arch.attrs.date_delay,
        this.date_stop = this.fields_view.arch.attrs.date_stop,
        this.day_length = this.fields_view.arch.attrs.day_length || 8;

        this.color_field = this.fields_view.arch.attrs.color,
        this.colors = this.fields_view.arch.attrs.colors;

        var level = this.fields_view.arch.children[0];
        this.parent = level.attrs.link,
        this.text = level.children.length ? level.children[0].attrs.name : level.attrs.name;

        if (!this.date_start) {
            return self.do_warn(_t("date_start is not defined "))
        }

        this.$element.html(QWeb.render("GanttView", {'height': $('.oe-application-container').height(), 'width': $('.oe-application-container').width()}));
        this.has_been_loaded.resolve();
    },

    on_project_loaded: function(projects) {

        if(!projects.length) return;
        var self = this,
            started_projects = _.filter(projects, function(res) {
            return res[self.date_start];
        });

        this.database_projects = started_projects;
        if(!started_projects.length)
            return self.do_warn(_t("date_start is not defined"));

        if(!self.name && started_projects.length) {
            var name = started_projects[0][self.parent];
            self.name = name instanceof Array? name[name.length - 1] : name;
        }

        $.when(this.project_starting_date(), this.get_project_duration(), this.calculate_difference())
            .then(function() {
                if(self.ganttChartControl) {
                    self.ganttChartControl.clearAll();
                    self.$element.find('#GanttView').empty();
                }
            })
            .then(this.group_projects())
            .then(this.generate_projects())
            .then(this.add_tasks())
            .done(this.init_gantt_view());
    },

    group_projects: function() {
        var def = $.Deferred(),
            self = this,
            projects = this.database_projects;

        if (!this.group_by.length) return def.resolve().promise();

        this.GroupProject = _.map(this.group_by, function(grp) {
                return _.groupBy(projects, function(prj) {
                    return prj[grp];
                })
            });
        return def.resolve().promise();
    },

    generate_projects : function() {
        var projects = this.database_projects,
            self = this;

        this.GanttTasks = [];
        if(this.group_by.length) {
            _.each(this.GroupProject, function(GrpProject, index) {
                self.GanttTasks.push([]);
                var counter = 0;
                _.each(GrpProject, function(grp, grpindex) {
                    var name = grpindex.split(',');
                    self.GanttTasks[index]
                        .push(new GanttTaskInfo(index + '_' + counter , name[name.length - 1], self.project_start_date, self.total_duration, 100, ""));
                    counter += 1;
                });
            })
        } else {
            this.GanttTasks.push(new GanttTaskInfo(0, self.name, self.project_start_date, self.total_duration, 100, ""));
        }
        this.GanttProjects = new GanttProjectInfo(0, self.name, self.project_start_date);
        return $.Deferred().resolve().promise();
    },

    add_tasks: function() {
        var self = this,
            tasks = this.database_projects;
        if (this.group_by.length) {
            _.each(this.GroupProject, function(grpproject, index) {
                var counter = 0;
                _.each(grpproject, function(prj, gindex) {
                    _.each(prj, function(task, tindex) {
                        var name = task[self.text];
                        if (task[self.text] instanceof Array) {
                            name = task[self.text][1];
                        }
                        self.GanttTasks[index][counter]
                            .addChildTask(new GanttTaskInfo(task.id, name, self.format_date(task[self.date_start]), self.project_duration[index], 100, ""))
                    });
                    counter += 1;
                })
            });
        }
        else {
            _.each(tasks, function(task, index){
                var name = task[self.text];
                if (task[self.text] instanceof Array) {
                    name = task[self.text][1];
                }
                self.GanttTasks[0].addChildTask(new GanttTaskInfo(task.id, name, self.format_date(task[self.date_start]), self.project_duration[index], 100, ""));
            });
        }
        return $.Deferred().resolve().promise();
    },

    get_project_duration: function() {

        var self = this,
            projects = this.database_projects;

        this.project_duration = [];

        _.each(projects, function(project, index) {
            if (self.date_stop && project[self.date_stop]) {
                self.project_duration.push(self.duration_difference(project[self.date_start], project[self.date_stop]));
            } else if(self.date_delay && project[self.date_delay]) {
                self.project_duration.push(project[self.date_delay]);
            } else {
                self.project_duration.push(0);
            }
        });

        this.max_project_duration = _.max(this.project_duration);
        return $.Deferred().resolve().promise();
    },

    duration_difference: function(start_date, end_date) {

        var DAY = 1000 * 60 * 60 * 24,
            date1_ms = openerp.web.auto_str_to_date(start_date).getTime(),
            date2_ms = openerp.web.auto_str_to_date(end_date).getTime(),
            difference_ms = Math.abs(date1_ms - date2_ms);

        var d = Math.floor(difference_ms / DAY),
            h = (difference_ms % DAY)/(1000 * 60 * 60),
            num = (d * this.day_length) + h;
        return parseFloat(num.toFixed(2));

    },

    calculate_difference: function() {
        var extend_end_date_day = Math.floor(this.max_project_duration / this.day_length),
            extend_end_date_hours = this.max_project_duration % this.day_length;

        this.project_end_date = this.project_end_date.add({days: extend_end_date_day, hours: extend_end_date_hours})

        var DAY = 1000 * 60 * 60 * 24,
            difference = Math.abs(this.project_start_date.getTime() - this.project_end_date.getTime()),
            day = Math.ceil(difference / DAY),
            hour = (difference % DAY)/(1000 * 60 * 60),
            DiffHour = (day * this.day_length) + hour;

        this.total_duration = parseFloat(DiffHour.toFixed(2));
        return $.Deferred().resolve().promise();
    },

    project_starting_date : function() {
        var self = this,
            projects = this.database_projects,
            min_date = _.min(projects, function(prj) {
                return new Date(prj[self.date_start]);
            }),
            max_date = _.max(projects, function(prj) {
                return self.format_date(prj[self.date_start]);
            });
        this.project_end_date =  this.format_date(max_date[self.date_start]);
        if (min_date) this.project_start_date = this.format_date(min_date[self.date_start]);
        else
            this.project_start_date = Date.today();
        return $.Deferred().resolve().promise();
    },

    init_gantt_view: function() {
        var self = this;

        if (this.group_by.length) {
            _.each(self.GanttTasks, function(tasks, index) {
                _.each(tasks, function(task, tindex) {
                    self.GanttProjects.addTask(task);
                });
            });
        }
        else {
            _.each(this.GanttTasks, function(tsk, index){
                self.GanttProjects.addTask(tsk);
            });
        }

        var ganttChartControl = this.ganttChartControl = new GanttChart();

        // Setup paths and behavior
        ganttChartControl.setImagePath("/web_gantt/static/lib/dhtmlxGantt/codebase/imgs/");
        ganttChartControl.setEditable(true);
        ganttChartControl.showTreePanel(true);
        ganttChartControl.showContextMenu(false);
        ganttChartControl.showDescTask(true,'d,s-f');
        ganttChartControl.showDescProject(true,'n,d');

        // Load data structure
        ganttChartControl.addProject(this.GanttProjects);

        // Create Gantt control
        ganttChartControl.create('GanttView');

        // Setup Events
        ganttChartControl.attachEvent("onTaskStartDrag", function(task) {
            if (task.parentTask) {
                var task_date = task.getEST();
                if (task_date.getHours()) {
                    task_date.set({
                        hour: task_date.getHours(),
                        minute: task_date.getMinutes(),
                        second: 0
                    });
                }
            }
        });

        ganttChartControl.attachEvent("onTaskEndResize", function(task) {return self.ResizeTask(task);});
        ganttChartControl.attachEvent("onTaskEndDrag", function(task) {return self.ResizeTask(task);});
        ganttChartControl.attachEvent("onTaskClick", function(task) { return self.editTask(task);});
    },

    format_date : function(date) {
        var datetime_regex = /^(\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d)(?:\.\d+)?$/,
            date_regex = /^\d\d\d\d-\d\d-\d\d$/,
            time_regex = /^(\d\d:\d\d:\d\d)(?:\.\d+)?$/,
            def = $.Deferred();
        if(date_regex.exec(date)) {
            this.date_format = "yyyy-MM-dd";
        } else if(time_regex.exec(date)) {
            this.date_format = "HH:mm:ss";
        } else {
            this.date_format = "yyyy-MM-dd HH:mm:ss";
        }
        return openerp.web.auto_str_to_date(date);
    },

    ResizeTask: function(task) {

        var self = this,
            event_id = task.getId();

        if(!event_id || !task.parentTask)
            return this.do_warn(_t("Project can not be resized"));

        var data = {};
        data[this.date_start] = task.getEST().toString(this.date_format);

        if(this.date_stop) {
            var diff = task.getDuration() % this.day_length,
                finished_date = task.getFinishDate().add({hours: diff});
            data[this.date_stop] = finished_date.toString(this.date_format);
        } else {
            data[this.date_delay] = task.getDuration();
        }
        this.dataset
            .write(event_id, data, {})
            .done(function() {
                self.reloadView();
            });
    },

    editTask: function(task) {

        var self = this;
        var event_id = task.getId();
        if(!event_id || !task.parentTask)
            return false;
        if(event_id) event_id = parseInt(event_id, 10);

        var action_manager = new openerp.web.ActionManager(this);

        var dialog = new openerp.web.Dialog(this, {
            width: 800,
            height: 600,
            buttons : {
                Cancel : function() {
                    $(this).dialog('destroy');
                },
                Save : function() {
                    var form_view = action_manager.inner_viewmanager.views.form.controller;

                    form_view.do_save(function() {
                        self.reloadView();
                    });
                    $(this).dialog('destroy');
                }
            }
        }).start().open();
        action_manager.appendTo(dialog.$element);
        action_manager.do_action({
            res_model : this.dataset.model,
            res_id: event_id,
            views : [[false, 'form']],
            type : 'ir.actions.act_window',
            auto_search : false,
            flags : {
                search_view: false,
                sidebar : false,
                views_switcher : false,
                action_buttons : false,
                pager: false
            }
        });
    },

    reloadView: function() {
       self.on_project_loaded(self.database_projects);
    },

    do_show: function () {
        this.$element.show();
    },

    do_hide: function () {
        this.$element.hide();
    },

    do_search: function (domains, contexts, groupbys) {
        var self = this;
        this.group_by = groupbys;
        $.when(this.has_been_loaded).then(function() {
            self.dataset
                .read_slice([], {
                    domain: domains,
                    context: contexts
                })
                .done(function(projects) {
                    self.on_project_loaded(projects);
                });
        })

    }

});

// here you may tweak globals object, if any, and play with on_* or do_* callbacks on them

};
// vim:et fdc=0 fdl=0:
<|endoftext|>


<commit_before>// ==UserScript==
// @name        Kurahen Premium
// @namespace   karachan.org
// @description Zestaw dodatkowych opcji dla forum młodzieżowo-katolickiego
// @version     1.0
// @include     http://www.karachan.org/*
// @include     http://karachan.org/*
// @exclude     http://www.karachan.org/*/src/*
// @exclude     http://karachan.org/*/src/*
// ==/UserScript==

(function () {
	'use strict';

	// Konfiguracja
	var customBBoardTitle = '/b/ - Random';
	var enableBetterFonts = true; // Podmienia domyślne czcionki na Roboto

	// Zaawansowana konfiguracja
	var bbCodes = ['b', 'i', 'u', 's', 'small', 'code', 'spoiler'];
	var wordfilters = [
		['#nowocioty', 'STAROCIOTY PAMIĘTAJĄ'],
		['#gimbo', 'xD'],
		['#penis', 'pisiorek'],
		['#wagina', 'cipuszka'],
		['#m__b', 'groźny WYKOPEK wykryty'],
		['#Lasoupeauxchoux', 'kapuśniaczek'],
		['#homoś', 'pedał'],
		['#korwinkrulempolski', 'kongres nowej prawicy'],
		['#1%', 'groźny LEWAK wykryty']
	];
	var boardsWithId = ['b', 'fz', 'z'];
	var colors = [
		'#FF8080',
		'#FFDD80',
		'#80FFB7',
		'#80D0FF',
		'#C680FF',
		'#FFAE80',
		'#D5FF80',
		'#80FFFD',
		'#8097FF',
		'#FF80CA',
		"#ff7f7f",
		"#779aef",
		"#b0de6f",
		"#cc66c0",
		"#5cb9a9",
		"#f3bb79",
		"#8d71e2",
		"#6dd168",
		"#be5f7e",
		"#7bc8f6"
	];

	var KurahenPremium = function () {
		var currentBoardName = this.getCurrentBoardName();

		if (currentBoardName === '') {
			return; // We are not on any useful page
		} else if (currentBoardName === 'b') {
			this.changeBoardTitle(customBBoardTitle);
		}
		this.updatePageTitle();
		this.disableNightStyle();
		this.setCookie('regulamin', 'accepted');
		this.insertButtonBar();
		this.replaceEmailFieldWithSelect();

		if (boardsWithId.indexOf(currentBoardName) > -1 && this.isCurrentWebpageThread()) {
			this.colorizeAndNamePosters();
		}

		if (enableBetterFonts) {
			this.changeFonts();
		}
	};

	KurahenPremium.prototype.changeBoardTitle = function (newTitle) {
		document.title = newTitle;
		document.getElementsByClassName('boardTitle')[0].textContent = newTitle;
	};

	KurahenPremium.prototype.updatePageTitle = function () {
		var page = parseInt(window.location.pathname.split('/')[2]);
		var prefix = '';

		if (this.isCurrentWebpageThread()) {
			prefix = this.getTopicFromFirstPostContent();
		} else if (!isNaN(page)) {
			prefix = 'Strona ' + page;
		}

		if (prefix.length > 0) {
			prefix += ' - ';
		}

		document.title = prefix + document.title;
	};

	KurahenPremium.prototype.setCookie = function (name, value) {
		document.cookie = encodeURIComponent(name) + '=' + encodeURIComponent(value) + '; path=/; max-age=2592000';
	};

	KurahenPremium.prototype.changeFonts = function () {
		var newLink = document.createElement('link');
		newLink.href = '//fonts.googleapis.com/css?family=Roboto:400,700&subset=latin,latin-ext';
		newLink.rel = 'stylesheet';
		var existingLink = document.getElementsByTagName('link')[0];
		existingLink.parentNode.insertBefore(newLink, existingLink);
		document.body.style.fontFamily = 'Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif';
	};

	KurahenPremium.prototype.getCurrentBoardName = function () {
		return window.location.pathname.split('/')[1];
	};

	KurahenPremium.prototype.isCurrentWebpageThread = function () {
		return window.location.pathname.split('/')[2] === 'res';
	};

	KurahenPremium.prototype.getTopicFromFirstPostContent = function () {
		var postContent = document.querySelector('.thread .postMessage').textContent;
		return postContent.substr(0, Math.min(postContent.length, 70));
	};

	KurahenPremium.prototype.disableNightStyle = function () {
		var option = document.querySelector('#stylechanger option[value$="night.css"]');
		option.disabled = true;
	};

	KurahenPremium.prototype.replaceEmailFieldWithSelect = function () {
		var emailField = document.querySelector('#postform input[name="email"]');

		var select = document.createElement('select');
		select.name = 'email';
		select.style.margin = '0';
		select.style.width = '236px';
		select.addEventListener('change', function () {
			//noinspection JSPotentiallyInvalidUsageOfThis
			if (this.options[this.selectedIndex].value === 'custom') {
				var textField = document.createElement('input');
				textField.type = 'text';
				textField.className = 'board-input';
				textField.name = 'email';
				select.parentNode.replaceChild(textField, select);
				textField.focus();
			}
		}, false);

		var optionBump = document.createElement('option');
		optionBump.value = '';
		optionBump.selected = true;
		optionBump.textContent = 'Podbij';
		select.appendChild(optionBump);

		var optionSage = document.createElement('option');
		optionSage.value = 'sage';
		optionSage.textContent = 'Saguj';
		select.appendChild(optionSage);

		var optionSpoiler = document.createElement('option');
		optionSpoiler.value = 'spoiler';
		optionSpoiler.textContent = 'Ukryj obrazek';
		select.appendChild(optionSpoiler);

		var optionCustom = document.createElement('option');
		optionCustom.value = 'custom';
		optionCustom.textContent = 'Wpisz własny...';
		select.appendChild(optionCustom);

		emailField.parentNode.replaceChild(select, emailField);
	};

	KurahenPremium.prototype.colorizeAndNamePosters = function () {
		var postersIds = document.getElementsByClassName("posteruid");
		var postersStats = {};

		var opId;
		for (var i = 0; i < postersIds.length; i++) {
			var posterId = this.parsePosterId(postersIds[i].textContent);
			if (i === 0) {
				opId = posterId;
			}

			postersIds[i].className += ' poster-id-' + posterId;
			if (posterId === opId) {
				postersIds[i].textContent = '\u00a0OP nitki';
			} else {
				postersIds[i].textContent = '\u00a0' + posterId;
			}

			if (isNaN(postersStats[posterId])) {
				postersStats[posterId] = 1;
			} else {
				postersStats[posterId]++;
			}
		}

		var style = document.createElement('style');
		style.type = 'text/css';
		for (var id in postersStats) {
			if (postersStats.hasOwnProperty(id) && postersStats[id] > 1) {
				style.textContent += '.poster-id-' + id + '{color:#000;background-color: ' + this.getNextColor() + ';}';
				style.textContent += '.poster-id-' + id + ':after{content:" (' + postersStats[id] + ' postów)\u00a0"}';
			}
		}
		document.getElementsByTagName('head')[0].appendChild(style);
	};

	/**
	 * @private
	 */
	KurahenPremium.prototype.getNextColor = function () {
		if (colors.length > 0) {
			return colors.shift();
		} else {
			return "#"+((1<<24)*Math.random()|0).toString(16); // Random color
		}
	};

	/**
	 * @private
	 */
	KurahenPremium.prototype.parsePosterId = function (text) {
		return text.trim().substr(5, 8).replace(/[\.|\/|\+|\-]/g, '_');
	};

	KurahenPremium.prototype.insertButtonBar = function () {
		var postForm = document.getElementById('postform');
		var textarea = document.querySelector("#postform textarea");
		var buttonBar = document.createElement('div');
		buttonBar.style.textAlign = 'center';

		this.insertTextFormattingButtons(textarea, buttonBar);
		this.insertWordfilterList(textarea, buttonBar);

		postForm.insertBefore(buttonBar, postForm.firstChild);
	};

	/**
	 * @private
	 */
	KurahenPremium.prototype.insertTextFormattingButtons = function (textarea, buttonBar) {
		var onButtonClick = function () {
			var startTag = '[' + this.value + ']';
			var endTag = '[/' + this.value + ']';

			var textBeforeSelection = textarea.value.substring(0, textarea.selectionStart);
			var selectedText = textarea.value.substring(textarea.selectionStart, textarea.selectionEnd);
			var textAfterSelection = textarea.value.substring(textarea.selectionEnd, textarea.value.length);

			textarea.value = textBeforeSelection + startTag + selectedText + endTag + textAfterSelection;

			textarea.focus();
			textarea.selectionStart += startTag.length;
			textarea.selectionEnd = textarea.selectionStart + selectedText.length;
		};

		for (var i = 0; i < bbCodes.length; i++) {
			var button = document.createElement('input');
			button.type = 'button';
			button.value = bbCodes[i];
			button.addEventListener('click', onButtonClick, false);
			buttonBar.appendChild(button);
		}
	};

	/**
	 * @private
	 */
	KurahenPremium.prototype.insertWordfilterList = function (textarea, buttonBar) {
		var wordfiltersSelect = document.createElement('select');

		var defaultOption = document.createElement('option');
		defaultOption.value = '';
		defaultOption.disabled = true;
		defaultOption.selected = true;
		defaultOption.textContent = 'WORDFILTRY';
		wordfiltersSelect.appendChild(defaultOption);

		wordfiltersSelect.addEventListener('change', function () {
			//noinspection JSPotentiallyInvalidUsageOfThis
			var textToInsert = this.options[this.selectedIndex].value;
			var textBeforeEndOfSelection = textarea.value.substring(0, textarea.selectionEnd);
			var textAfterEndOfSelection = textarea.value.substring(textarea.selectionEnd, textarea.value.length);

			textarea.value = textBeforeEndOfSelection + textToInsert + textAfterEndOfSelection;

			textarea.focus();
			textarea.selectionStart = textBeforeEndOfSelection.length + textToInsert.length;
			textarea.selectionEnd = textarea.selectionStart;

			//noinspection JSPotentiallyInvalidUsageOfThis
			this.selectedIndex = 0;
		}, false);

		for (var j = 0; j < wordfilters.length; j++) {
			var option = document.createElement('option');
			option.value = wordfilters[j][0];
			option.textContent = wordfilters[j][1];
			wordfiltersSelect.appendChild(option);
		}

		buttonBar.appendChild(wordfiltersSelect);
	};

	// Initialize script
	window.KurahenPremium = new KurahenPremium();
})();
<commit_msg>Add showing posters emails<commit_after>// ==UserScript==
// @name        Kurahen Premium
// @namespace   karachan.org
// @description Zestaw dodatkowych opcji dla forum młodzieżowo-katolickiego
// @version     1.0
// @include     http://www.karachan.org/*
// @include     http://karachan.org/*
// @exclude     http://www.karachan.org/*/src/*
// @exclude     http://karachan.org/*/src/*
// ==/UserScript==

(function () {
	'use strict';

	// Konfiguracja
	var customBBoardTitle = '/b/ - Random';
	var enableBetterFonts = true; // Podmienia domyślne czcionki na Roboto

	// Zaawansowana konfiguracja
	var bbCodes = ['b', 'i', 'u', 's', 'small', 'code', 'spoiler'];
	var wordfilters = [
		['#nowocioty', 'STAROCIOTY PAMIĘTAJĄ'],
		['#gimbo', 'xD'],
		['#penis', 'pisiorek'],
		['#wagina', 'cipuszka'],
		['#m__b', 'groźny WYKOPEK wykryty'],
		['#Lasoupeauxchoux', 'kapuśniaczek'],
		['#homoś', 'pedał'],
		['#korwinkrulempolski', 'kongres nowej prawicy'],
		['#1%', 'groźny LEWAK wykryty']
	];
	var boardsWithId = ['b', 'fz', 'z'];
	var colors = [
		'#FF8080',
		'#FFDD80',
		'#80FFB7',
		'#80D0FF',
		'#C680FF',
		'#FFAE80',
		'#D5FF80',
		'#80FFFD',
		'#8097FF',
		'#FF80CA',
		"#ff7f7f",
		"#779aef",
		"#b0de6f",
		"#cc66c0",
		"#5cb9a9",
		"#f3bb79",
		"#8d71e2",
		"#6dd168",
		"#be5f7e",
		"#7bc8f6"
	];

	var KurahenPremium = function () {
		var currentBoardName = this.getCurrentBoardName();

		if (currentBoardName === '') {
			return; // We are not on any useful page
		} else if (currentBoardName === 'b') {
			this.changeBoardTitle(customBBoardTitle);
		}
		this.updatePageTitle();
		this.disableNightStyle();
		this.setCookie('regulamin', 'accepted');
		this.insertButtonBar();
		this.replaceEmailFieldWithSelect();
		this.showAllPostersEmails();

		if (boardsWithId.indexOf(currentBoardName) > -1 && this.isCurrentWebpageThread()) {
			this.colorizeAndNamePosters();
		}

		if (enableBetterFonts) {
			this.changeFonts();
		}
	};

	KurahenPremium.prototype.changeBoardTitle = function (newTitle) {
		document.title = newTitle;
		document.getElementsByClassName('boardTitle')[0].textContent = newTitle;
	};

	KurahenPremium.prototype.updatePageTitle = function () {
		var page = parseInt(window.location.pathname.split('/')[2]);
		var prefix = '';

		if (this.isCurrentWebpageThread()) {
			prefix = this.getTopicFromFirstPostContent();
		} else if (!isNaN(page)) {
			prefix = 'Strona ' + page;
		}

		if (prefix.length > 0) {
			prefix += ' - ';
		}

		document.title = prefix + document.title;
	};

	KurahenPremium.prototype.setCookie = function (name, value) {
		document.cookie = encodeURIComponent(name) + '=' + encodeURIComponent(value) + '; path=/; max-age=2592000';
	};

	KurahenPremium.prototype.changeFonts = function () {
		var newLink = document.createElement('link');
		newLink.href = '//fonts.googleapis.com/css?family=Roboto:400,700&subset=latin,latin-ext';
		newLink.rel = 'stylesheet';
		var existingLink = document.getElementsByTagName('link')[0];
		existingLink.parentNode.insertBefore(newLink, existingLink);
		document.body.style.fontFamily = 'Roboto, "Helvetica Neue", Helvetica, Arial, sans-serif';
	};

	KurahenPremium.prototype.getCurrentBoardName = function () {
		return window.location.pathname.split('/')[1];
	};

	KurahenPremium.prototype.isCurrentWebpageThread = function () {
		return window.location.pathname.split('/')[2] === 'res';
	};

	KurahenPremium.prototype.getTopicFromFirstPostContent = function () {
		var postContent = document.querySelector('.thread .postMessage').textContent;
		return postContent.substr(0, Math.min(postContent.length, 70));
	};

	KurahenPremium.prototype.disableNightStyle = function () {
		var option = document.querySelector('#stylechanger option[value$="night.css"]');
		option.disabled = true;
	};

	KurahenPremium.prototype.replaceEmailFieldWithSelect = function () {
		var emailField = document.querySelector('#postform input[name="email"]');

		var select = document.createElement('select');
		select.name = 'email';
		select.style.margin = '0';
		select.style.width = '236px';
		select.addEventListener('change', function () {
			//noinspection JSPotentiallyInvalidUsageOfThis
			if (this.options[this.selectedIndex].value === 'custom') {
				var textField = document.createElement('input');
				textField.type = 'text';
				textField.className = 'board-input';
				textField.name = 'email';
				select.parentNode.replaceChild(textField, select);
				textField.focus();
			}
		}, false);

		var optionBump = document.createElement('option');
		optionBump.value = '';
		optionBump.selected = true;
		optionBump.textContent = 'Podbij';
		select.appendChild(optionBump);

		var optionSage = document.createElement('option');
		optionSage.value = 'sage';
		optionSage.textContent = 'Saguj';
		select.appendChild(optionSage);

		var optionSpoiler = document.createElement('option');
		optionSpoiler.value = 'spoiler';
		optionSpoiler.textContent = 'Ukryj obrazek';
		select.appendChild(optionSpoiler);

		var optionCustom = document.createElement('option');
		optionCustom.value = 'custom';
		optionCustom.textContent = 'Wpisz własny...';
		select.appendChild(optionCustom);

		emailField.parentNode.replaceChild(select, emailField);
	};

	KurahenPremium.prototype.showAllPostersEmails = function () {
		var postersEmails = document.getElementsByClassName('useremail');

		for (var i = 0; i < postersEmails.length; i++) {
			postersEmails[i].textContent += ' (' + this.parseMailto(postersEmails[i].getAttribute('href')) + ') ';
			postersEmails[i].removeAttribute('href');
		}
	};

	/**
	 * @private
	 */
	KurahenPremium.prototype.parseMailto = function (mailto) {
		return mailto.replace('mailto:', '');
	};

	KurahenPremium.prototype.colorizeAndNamePosters = function () {
		var postersIds = document.getElementsByClassName("posteruid");
		var postersStats = {};

		var opId;
		for (var i = 0; i < postersIds.length; i++) {
			var posterId = this.parsePosterId(postersIds[i].textContent);
			if (i === 0) {
				opId = posterId;
			}

			postersIds[i].className += ' poster-id-' + posterId;
			if (posterId === opId) {
				postersIds[i].textContent = '\u00a0OP nitki';
			} else {
				postersIds[i].textContent = '\u00a0' + posterId;
			}

			if (isNaN(postersStats[posterId])) {
				postersStats[posterId] = 1;
			} else {
				postersStats[posterId]++;
			}
		}

		var style = document.createElement('style');
		style.type = 'text/css';
		for (var id in postersStats) {
			if (postersStats.hasOwnProperty(id) && postersStats[id] > 1) {
				style.textContent += '.poster-id-' + id + '{color:#000;background-color: ' + this.getNextColor() + ';}';
				style.textContent += '.poster-id-' + id + ':after{content:" (' + postersStats[id] + ' postów)\u00a0"}';
			}
		}
		document.getElementsByTagName('head')[0].appendChild(style);
	};

	/**
	 * @private
	 */
	KurahenPremium.prototype.getNextColor = function () {
		if (colors.length > 0) {
			return colors.shift();
		} else {
			return "#"+((1<<24)*Math.random()|0).toString(16); // Random color
		}
	};

	/**
	 * @private
	 */
	KurahenPremium.prototype.parsePosterId = function (text) {
		return text.trim().substr(5, 8).replace(/[\.|\/|\+|\-]/g, '_');
	};

	KurahenPremium.prototype.insertButtonBar = function () {
		var postForm = document.getElementById('postform');
		var textarea = document.querySelector("#postform textarea");
		var buttonBar = document.createElement('div');
		buttonBar.style.textAlign = 'center';

		this.insertTextFormattingButtons(textarea, buttonBar);
		this.insertWordfilterList(textarea, buttonBar);

		postForm.insertBefore(buttonBar, postForm.firstChild);
	};

	/**
	 * @private
	 */
	KurahenPremium.prototype.insertTextFormattingButtons = function (textarea, buttonBar) {
		var onButtonClick = function () {
			var startTag = '[' + this.value + ']';
			var endTag = '[/' + this.value + ']';

			var textBeforeSelection = textarea.value.substring(0, textarea.selectionStart);
			var selectedText = textarea.value.substring(textarea.selectionStart, textarea.selectionEnd);
			var textAfterSelection = textarea.value.substring(textarea.selectionEnd, textarea.value.length);

			textarea.value = textBeforeSelection + startTag + selectedText + endTag + textAfterSelection;

			textarea.focus();
			textarea.selectionStart += startTag.length;
			textarea.selectionEnd = textarea.selectionStart + selectedText.length;
		};

		for (var i = 0; i < bbCodes.length; i++) {
			var button = document.createElement('input');
			button.type = 'button';
			button.value = bbCodes[i];
			button.addEventListener('click', onButtonClick, false);
			buttonBar.appendChild(button);
		}
	};

	/**
	 * @private
	 */
	KurahenPremium.prototype.insertWordfilterList = function (textarea, buttonBar) {
		var wordfiltersSelect = document.createElement('select');

		var defaultOption = document.createElement('option');
		defaultOption.value = '';
		defaultOption.disabled = true;
		defaultOption.selected = true;
		defaultOption.textContent = 'WORDFILTRY';
		wordfiltersSelect.appendChild(defaultOption);

		wordfiltersSelect.addEventListener('change', function () {
			//noinspection JSPotentiallyInvalidUsageOfThis
			var textToInsert = this.options[this.selectedIndex].value;
			var textBeforeEndOfSelection = textarea.value.substring(0, textarea.selectionEnd);
			var textAfterEndOfSelection = textarea.value.substring(textarea.selectionEnd, textarea.value.length);

			textarea.value = textBeforeEndOfSelection + textToInsert + textAfterEndOfSelection;

			textarea.focus();
			textarea.selectionStart = textBeforeEndOfSelection.length + textToInsert.length;
			textarea.selectionEnd = textarea.selectionStart;

			//noinspection JSPotentiallyInvalidUsageOfThis
			this.selectedIndex = 0;
		}, false);

		for (var j = 0; j < wordfilters.length; j++) {
			var option = document.createElement('option');
			option.value = wordfilters[j][0];
			option.textContent = wordfilters[j][1];
			wordfiltersSelect.appendChild(option);
		}

		buttonBar.appendChild(wordfiltersSelect);
	};

	// Initialize script
	window.KurahenPremium = new KurahenPremium();
})();
<|endoftext|>


<commit_before><commit_msg>support default operator and change label of count<commit_after><|endoftext|>


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

var chosenDirectory = null;
var apiKey = null;
var chooseDirButton = document.querySelector('#choose_dir');
var updateKeyButton = document.querySelector('#update_key');
var apiKeyBox = document.querySelector('#api_key');
var resetSessionButton = document.querySelector('#reset_session');
var directoryBox = document.querySelector('#directory');
var output = document.querySelector('#output');

function loadInitialFile(launchData) {
    chrome.storage.local.get(['selectedDir', 'apiKey'], function(items) {
      if (items.selectedDir) {
        // if an entry was retained earlier, see if it can be restored
        chrome.fileSystem.isRestorable(items.selectedDir, function(bIsRestorable) {
          // the entry is still there, load the content
          console.info("Restoring " + items.selectedDir);
          chrome.fileSystem.restoreEntry(items.selectedDir, function(dir) {
            if (dir) {
              chosenDirectory = dir;
              chrome.fileSystem.getDisplayPath(dir, function(path) {
                directoryBox.value = path;
              });
            }
          });
        });
      }
      if (items.apiKey) {
        apiKeyBox.value = items.apiKey;
        apiKey = items.apiKey;
      }
    });
    setInterval(run, 30000);
}

/*
 * getJSON is a simple XMLHttpRequest wrapper that parses JSON response.
 */
function getJSON(url, callback){
    var xmlhttp;
    xmlhttp = new XMLHttpRequest();
    xmlhttp.onreadystatechange = function(){
        if (xmlhttp.readyState == 4 && xmlhttp.status == 200){
            callback(JSON.parse(xmlhttp.responseText));
        } else if (xmlhttp.readyState == 4 && xmlhttp.status != 200) {
            output.innerHTML = "Failed. (Response Code: " + xmlhttp.status + ")";
        }
    }
    xmlhttp.open("GET", url, true);
    xmlhttp.send();

}

function resetSession(){
    var xmlhttp;
    xmlhttp = new XMLHttpRequest();
    xmlhttp.open("POST", "http://localhost:8000/reset_session", true);
    xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    xmlhttp.send("reset=true&key="+apiKey);
}

function handleData(data) {
    var count = 0;
    for (var i in data) {
        writeFile(chosenDirectory, i + ".txt", data[i]);
        count += 1;
    }
    var d = new Date();
    var dateString = "(@" + d.getHours() + ":" + d.getMinutes() + ")";
    output.innerHTML = "Updated " + count + " files. " + dateString;

}

function run() {
    var d = new Date();
    var dateString = "(@" + d.getHours() + ":" + d.getMinutes() + ")";
    if (apiKey && chosenDirectory) {
        output.innerHTML = "Updating... " + dateString;
        getJSON('https://www.livestreamalerts.com/recent_api_all?key='+apiKey, handleData);
    } else {
        output.innerHTML = "Not running. " + dateString;
    }
}

chooseDirButton.addEventListener('click', function(e) {
  chrome.fileSystem.chooseEntry({type: 'openDirectory'}, function(theDirectory) {
    if (!theDirectory) {
      return;
    }
    // use local storage to retain access to this file
    chrome.storage.local.set({'selectedDir': chrome.fileSystem.retainEntry(theDirectory)});
    chosenDirectory = theDirectory;
    chrome.fileSystem.getDisplayPath(chosenDirectory, function(path) {
      directoryBox.value = path;
      run();
    });
  });
});

updateKeyButton.addEventListener('click', function(e) {
  chrome.storage.local.set({'apiKey': apiKeyBox.value});
  apiKey = apiKeyBox.value;
  run();
});

function writeFile(dir, filename, contents) {
    // String conversion
    contents += '';
    dir.getFile(filename, {create: true}, function(fileDirectory) {
        var blob = new Blob([contents.replace(/\\n/g, "\r\n")], {type: 'text/plain'});
        fileDirectory.createWriter(function(writer) {
            writer.onwriteend = function() {
                if (writer.length != blob.size) {
                    writer.truncate(blob.size);
                    return;
                }
            };
            writer.write(blob);
        });
    }, function(error) {
        console.log(error);
    });
}

resetSessionButton.onclick=resetSession;

loadInitialFile(launchData);

<commit_msg>Use real website.<commit_after>//  Copyright 2016 Google Inc. All Rights Reserved.
//
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.

var chosenDirectory = null;
var apiKey = null;
var chooseDirButton = document.querySelector('#choose_dir');
var updateKeyButton = document.querySelector('#update_key');
var apiKeyBox = document.querySelector('#api_key');
var resetSessionButton = document.querySelector('#reset_session');
var directoryBox = document.querySelector('#directory');
var output = document.querySelector('#output');

function loadInitialFile(launchData) {
    chrome.storage.local.get(['selectedDir', 'apiKey'], function(items) {
      if (items.selectedDir) {
        // if an entry was retained earlier, see if it can be restored
        chrome.fileSystem.isRestorable(items.selectedDir, function(bIsRestorable) {
          // the entry is still there, load the content
          console.info("Restoring " + items.selectedDir);
          chrome.fileSystem.restoreEntry(items.selectedDir, function(dir) {
            if (dir) {
              chosenDirectory = dir;
              chrome.fileSystem.getDisplayPath(dir, function(path) {
                directoryBox.value = path;
              });
            }
          });
        });
      }
      if (items.apiKey) {
        apiKeyBox.value = items.apiKey;
        apiKey = items.apiKey;
      }
    });
    setInterval(run, 30000);
}

/*
 * getJSON is a simple XMLHttpRequest wrapper that parses JSON response.
 */
function getJSON(url, callback){
    var xmlhttp;
    xmlhttp = new XMLHttpRequest();
    xmlhttp.onreadystatechange = function(){
        if (xmlhttp.readyState == 4 && xmlhttp.status == 200){
            callback(JSON.parse(xmlhttp.responseText));
        } else if (xmlhttp.readyState == 4 && xmlhttp.status != 200) {
            output.innerHTML = "Failed. (Response Code: " + xmlhttp.status + ")";
        }
    }
    xmlhttp.open("GET", url, true);
    xmlhttp.send();

}

function resetSession(){
    var xmlhttp;
    xmlhttp = new XMLHttpRequest();
    xmlhttp.open("POST", "https://www.livestreamalerts.com/reset_session", true);
    xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
    xmlhttp.send("reset=true&key="+apiKey);
}

function handleData(data) {
    var count = 0;
    for (var i in data) {
        writeFile(chosenDirectory, i + ".txt", data[i]);
        count += 1;
    }
    var d = new Date();
    var dateString = "(@" + d.getHours() + ":" + d.getMinutes() + ")";
    output.innerHTML = "Updated " + count + " files. " + dateString;

}

function run() {
    var d = new Date();
    var dateString = "(@" + d.getHours() + ":" + d.getMinutes() + ")";
    if (apiKey && chosenDirectory) {
        output.innerHTML = "Updating... " + dateString;
        getJSON('https://www.livestreamalerts.com/recent_api_all?key='+apiKey, handleData);
    } else {
        output.innerHTML = "Not running. " + dateString;
    }
}

chooseDirButton.addEventListener('click', function(e) {
  chrome.fileSystem.chooseEntry({type: 'openDirectory'}, function(theDirectory) {
    if (!theDirectory) {
      return;
    }
    // use local storage to retain access to this file
    chrome.storage.local.set({'selectedDir': chrome.fileSystem.retainEntry(theDirectory)});
    chosenDirectory = theDirectory;
    chrome.fileSystem.getDisplayPath(chosenDirectory, function(path) {
      directoryBox.value = path;
      run();
    });
  });
});

updateKeyButton.addEventListener('click', function(e) {
  chrome.storage.local.set({'apiKey': apiKeyBox.value});
  apiKey = apiKeyBox.value;
  run();
});

function writeFile(dir, filename, contents) {
    // String conversion
    contents += '';
    dir.getFile(filename, {create: true}, function(fileDirectory) {
        var blob = new Blob([contents.replace(/\\n/g, "\r\n")], {type: 'text/plain'});
        fileDirectory.createWriter(function(writer) {
            writer.onwriteend = function() {
                if (writer.length != blob.size) {
                    writer.truncate(blob.size);
                    return;
                }
            };
            writer.write(blob);
        });
    }, function(error) {
        console.log(error);
    });
}

resetSessionButton.onclick=resetSession;

loadInitialFile(launchData);

<|endoftext|>


<commit_before><commit_msg>SQI related changes<commit_after><|endoftext|>